March 18, 2025

ikayaniaamirshahzad@gmail.com

A Personal Finance Dashboard with KendoReact


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):

  1. Grid: Displays the transaction list.
  2. Chart: Visualizes spending data (pie chart in Overview, line chart in Reports).
  3. Form: Manages transaction input.
  4. DatePicker: Selects transaction dates.
  5. Button: Triggers actions like form submission or fetching AI insights.
  6. DropDownList: Chooses transaction categories.
  7. Input: Enters transaction descriptions.
  8. NumericTextBox: Inputs transaction amounts.
  9. Switch: Toggles between income and expense types.
  10. TabStrip: Navigates between app sections.
  11. PanelBar: (Optional) Could be used for collapsible sections, but I’ll stick with TabStrip for simplicity.
  12. 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
Enter fullscreen mode

Exit fullscreen mode

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 in src/.

In src/App.js, import the custom theme:

import './custom-theme.css';
Enter fullscreen mode

Exit fullscreen mode

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'
}
Enter fullscreen mode

Exit fullscreen mode

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' },
];
Enter fullscreen mode

Exit fullscreen mode



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;
Enter fullscreen mode

Exit fullscreen mode

src/App.css (basic styling):

.app {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}
Enter fullscreen mode

Exit fullscreen mode



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;
Enter fullscreen mode

Exit fullscreen mode



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;
Enter fullscreen mode

Exit fullscreen mode



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;
Enter fullscreen mode

Exit fullscreen mode



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;
Enter fullscreen mode

Exit fullscreen mode

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
Enter fullscreen mode

Exit fullscreen mode

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 and Reports 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
Enter fullscreen mode

Exit fullscreen mode



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!



Source link

Leave a Comment