This is a submission for the KendoReact Free Components Challenge.
Project Overview
The app features three main views organized via tabs:
- Overview: Displays summary statistics (total income and expenses) and a pie chart of spending by category.
- Transactions: Lists all transactions in a grid, with options to add, edit, or delete entries using a form.
- Reports: Shows a line chart of monthly spending trends and provides AI-generated money-saving tips.
KendoReact Components Used
Here’s the list of 12 KendoReact Free components I’ll incorporate (exceeding the minimum requirement of 10):
- Grid: Displays the transaction list.
- Chart: Visualizes spending data (pie chart in Overview, line chart in Reports).
- Form: Manages transaction input.
- DatePicker: Selects transaction dates.
- Button: Triggers actions like form submission or fetching AI insights.
- DropDownList: Chooses transaction categories.
- Input: Enters transaction descriptions.
- NumericTextBox: Inputs transaction amounts.
- Switch: Toggles between income and expense types.
- TabStrip: Navigates between app sections.
- PanelBar: (Optional) Could be used for collapsible sections, but I’ll stick with TabStrip for simplicity.
- Tooltip: Adds hover information on the pie chart.
Step-by-Step Implementation
1. Project Setup
First, set up a new React project using Create React App and install the necessary KendoReact packages.
npx create-react-app finance-dashboard cd finance-dashboard npm install @progress/kendo-react-grid @progress/kendo-react-charts @progress/kendo-react-form @progress/kendo-react-inputs @progress/kendo-react-buttons @progress/kendo-react-dropdowns @progress/kendo-react-dateinputs @progress/kendo-react-layout @progress/kendo-theme-default
These packages cover the free components we need, including the default theme.
2. Custom Theme with ThemeBuilder
To enhance the app’s design, create a custom theme using KendoReact ThemeBuilder:
- Visit ThemeBuilder, start a new project, and select the Default theme as a base.
- Customize it for a finance app: use a dark background, green for income (e.g.,
#28a745
), and red for expenses (e.g.,#dc3545
). - Export the theme as
custom-theme.css
and save it insrc/
.
In src/App.js
, import the custom theme:
import './custom-theme.css';
This overrides the default KendoReact theme, aligning with the "Delightfully Designed" goal.
3. Data Structure and State Management
Define the transaction data structure:
{ id: number, date: Date, description: string, amount: number, category: string, type: 'income' | 'expense' }
Manage transactions using React’s useState
hook in the main App
component, with some initial sample data:
const initialTransactions = [ { id: 1, date: new Date(2023, 0, 1), description: 'Salary', amount: 3000, category: 'Income', type: 'income' }, { id: 2, date: new Date(2023, 0, 5), description: 'Groceries', amount: 100, category: 'Food', type: 'expense' }, { id: 3, date: new Date(2023, 0, 10), description: 'Bus', amount: 50, category: 'Transport', type: 'expense' }, ];
4. App Component Structure
The App
component serves as the root, managing state and rendering the tabbed interface.
src/App.js
:
import React, { useState } from 'react'; import { TabStrip, TabStripTab } from '@progress/kendo-react-layout'; import Overview from './Overview'; import Transactions from './Transactions'; import Reports from './Reports'; import './App.css'; import './custom-theme.css'; const App = () => { const [transactions, setTransactions] = useState(initialTransactions); const [selectedTab, setSelectedTab] = useState(0); const handleSelect = (e) => setSelectedTab(e.selected); const addTransaction = (newTransaction) => { setTransactions([...transactions, { ...newTransaction, id: transactions.length + 1 }]); }; const editTransaction = (updatedTransaction) => { setTransactions(transactions.map(t => t.id === updatedTransaction.id ? updatedTransaction : t)); }; const deleteTransaction = (id) => { setTransactions(transactions.filter(t => t.id !== id)); }; return ( <div className="app"> <h1>Personal Finance Dashboard</h1> <TabStrip selected={selectedTab} onSelect={handleSelect}> <TabStripTab title="Overview"> <Overview transactions={transactions} /> </TabStripTab> <TabStripTab title="Transactions"> <Transactions transactions={transactions} addTransaction={addTransaction} editTransaction={editTransaction} deleteTransaction={deleteTransaction} /> </TabStripTab> <TabStripTab title="Reports"> <Reports transactions={transactions} /> </TabStripTab> </TabStrip> </div> ); }; export default App;
src/App.css
(basic styling):
.app { padding: 20px; max-width: 1200px; margin: 0 auto; }
5. Transactions Component
This component displays a grid of transactions and a form for adding/editing them.
src/Transactions.js
:
import React, { useState } from 'react'; import { Grid, GridColumn } from '@progress/kendo-react-grid'; import { Button } from '@progress/kendo-react-buttons'; import TransactionForm from './TransactionForm'; const Transactions = ({ transactions, addTransaction, editTransaction, deleteTransaction }) => { const [showForm, setShowForm] = useState(false); const [editingTransaction, setEditingTransaction] = useState(null); const handleAdd = () => { setEditingTransaction(null); setShowForm(true); }; const handleEdit = (transaction) => { setEditingTransaction(transaction); setShowForm(true); }; const handleDelete = (id) => { if (window.confirm('Are you sure you want to delete this transaction?')) { deleteTransaction(id); } }; const handleFormSubmit = (transaction) => { if (editingTransaction) { editTransaction(transaction); } else { addTransaction(transaction); } setShowForm(false); }; return ( <div> <Button onClick={handleAdd}>Add Transaction</Button> {showForm && <TransactionForm transaction={editingTransaction} onSubmit={handleFormSubmit} />} <Grid data={transactions} style={{ marginTop: '20px' }}> <GridColumn field="date" title="Date" format="{0:d}" /> <GridColumn field="description" title="Description" /> <GridColumn field="amount" title="Amount" format="{0:c}" /> <GridColumn field="category" title="Category" /> <GridColumn field="type" title="Type" /> <GridColumn title="Actions" cell={(props) => ( <td> <Button onClick={() => handleEdit(props.dataItem)}>Edit</Button> <Button onClick={() => handleDelete(props.dataItem.id)}>Delete</Button> </td> )} /> </Grid> </div> ); }; export default Transactions;
6. Transaction Form Component
A reusable form for adding or editing transactions.
src/TransactionForm.js
:
import React from 'react'; import { Form, Field } from '@progress/kendo-react-form'; import { Input, NumericTextBox, DatePicker, DropDownList, Switch } from '@progress/kendo-react-inputs'; import { Button } from '@progress/kendo-react-buttons'; const categories = ['Food', 'Transport', 'Entertainment', 'Utilities', 'Income']; const TransactionForm = ({ transaction, onSubmit }) => { const handleSubmit = (data) => onSubmit(data); return ( <Form onSubmit={handleSubmit} initialValues={transaction || { date: new Date(), description: '', amount: 0, category: 'Food', type: 'expense' }} render={(formRenderProps) => ( <form onSubmit={formRenderProps.onSubmit} style={{ margin: '20px 0' }}> <Field name="date" component={DatePicker} label="Date" /> <Field name="description" component={Input} label="Description" /> <Field name="amount" component={NumericTextBox} label="Amount" /> <Field name="category" component={DropDownList} data={categories} label="Category" /> <Field name="type" component={Switch} onLabel="Income" offLabel="Expense" /> <Button type="submit" style={{ marginTop: '10px' }}>Save</Button> </form> )} /> ); }; export default TransactionForm;
7. Overview Component
Shows summary stats and a pie chart.
src/Overview.js
:
import React from 'react'; import { Chart, ChartSeries, ChartSeriesItem, ChartLegend } from '@progress/kendo-react-charts'; const Overview = ({ transactions }) => { const totalIncome = transactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0); const totalExpenses = transactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0); const spendingByCategory = transactions .filter(t => t.type === 'expense') .reduce((acc, t) => { acc[t.category] = (acc[t.category] || 0) + t.amount; return acc; }, {}); const chartData = Object.entries(spendingByCategory).map(([category, amount]) => ({ category, amount })); return ( <div> <h2>Summary</h2> <p>Total Income: ${totalIncome.toFixed(2)}</p> <p>Total Expenses: ${totalExpenses.toFixed(2)}</p> <h2>Spending by Category</h2> {chartData.length > 0 ? ( <Chart> <ChartSeries> <ChartSeriesItem type="pie" data={chartData} field="amount" categoryField="category" tooltip={{ visible: true }} /> </ChartSeries> <ChartLegend position="bottom" /> </Chart> ) : ( <p>No expenses to display.</p> )} </div> ); }; export default Overview;
8. Reports Component with AI Integration
Displays a monthly spending trend and AI insights using the OpenAI API.
src/Reports.js
:
import React, { useState } from 'react'; import { Chart, ChartSeries, ChartSeriesItem, ChartCategoryAxis, ChartCategoryAxisItem } from '@progress/kendo-react-charts'; import { Button } from '@progress/kendo-react-buttons'; const Reports = ({ transactions }) => { const [insights, setInsights] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const monthlySpending = transactions .filter(t => t.type === 'expense') .reduce((acc, t) => { const month = t.date.toLocaleString('en-us', { month: 'short', year: 'numeric' }); acc[month] = (acc[month] || 0) + t.amount; return acc; }, {}); const monthlyData = Object.entries(monthlySpending).map(([monthStr, amount]) => { const [monthName, year] = monthStr.split(' '); const monthIndex = new Date(Date.parse(monthName + ' 1, ' + year)).getMonth(); return { date: new Date(year, monthIndex, 1), amount }; }).sort((a, b) => a.date - b.date); const getAIInsights = async () => { setLoading(true); setError(null); try { const spendingSummary = transactions.filter(t => t.type === 'expense').reduce((acc, t) => { acc[t.category] = (acc[t.category] || 0) + t.amount; return acc; }, {}); const prompt = `Based on my spending: ${Object.entries(spendingSummary).map(([cat, amt]) => `${cat}: $${amt}`).join(', ')}, provide tips to save money.`; const response = await fetch('https://api.openai.com/v1/engines/davinci/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.REACT_APP_OPENAI_API_KEY}`, }, body: JSON.stringify({ prompt, max_tokens: 100 }), }); const data = await response.json(); setInsights(data.choices[0].text); } catch (err) { setError('Failed to fetch AI insights. Please try again.'); } finally { setLoading(false); } }; return ( <div> <h2>Monthly Spending Trend</h2> {monthlyData.length > 0 ? ( <Chart> <ChartCategoryAxis> <ChartCategoryAxisItem categories={monthlyData.map(d => d.date.toLocaleString('en-us', { month: 'short', year: 'numeric' }))} /> </ChartCategoryAxis> <ChartSeries> <ChartSeriesItem type="line" data={monthlyData} field="amount" /> </ChartSeries> </Chart> ) : ( <p>No expenses to display.</p> )} <h2>AI Insights</h2> <Button onClick={getAIInsights} disabled={loading}> {loading ? 'Loading...' : 'Get AI Insights'} </Button> {error && <p style={{ color: 'red' }}>{error}</p>} {insights && <p>{insights}</p>} </div> ); }; export default Reports;
For the OpenAI API, add your API key to a .env
file (not committed to version control):
REACT_APP_OPENAI_API_KEY=your-api-key-here
Note: In a production app, API calls should be handled via a backend for security. For this demo, I’m using a client-side call with a note for users to supply their own key.
9. Enhancing User Experience
- Responsiveness: KendoReact components are responsive, but adjust layouts with CSS (e.g., flexbox) if needed for smaller screens.
- Empty States: Added checks in
Overview
andReports
to handle cases with no data. - Persistence: Optionally, use local storage to persist transactions:
// In App.js const loadTransactions = () => JSON.parse(localStorage.getItem('transactions')) || initialTransactions; const [transactions, setTransactions] = useState(loadTransactions); const saveTransactions = (updatedTransactions) => { setTransactions(updatedTransactions); localStorage.setItem('transactions', JSON.stringify(updatedTransactions)); }; // Update addTransaction, editTransaction, deleteTransaction to call saveTransactions
Final Touches
- Testing: Verify that adding, editing, and deleting transactions work, charts render correctly, and AI insights fetch successfully.
- Documentation: For a challenge submission, include screenshots of the app (Overview, Transactions, Reports) and a link to the GitHub repository.
Summary
This Personal Finance Dashboard uses 12 KendoReact Free components to deliver a functional, visually appealing app. It integrates OpenAI for AI-driven insights and employs a custom theme via ThemeBuilder. The app meets the requirements by:
- Using at least 10 KendoReact components.
- Incorporating generative AI for money-saving tips.
- Featuring a custom design with ThemeBuilder.
You can extend this by adding more features like date filters or advanced analytics, but this provides a solid foundation. To run it, follow the setup steps, add your OpenAI API key, and execute npm start
. Enjoy managing your finances with style and intelligence!
Top comments (2)
making this
'Authorization':
Bearer ${process.env.REACT_APP_OPENAI_API_KEY}
,on client side is a bit sensitive
good luck
Thank you for the great suggestion.💯💯