A responsive finance dashboard built with vanilla JavaScript, HTML, CSS, and Bootstrap 5. Track transactions, visualize spending patterns, and manage finances with role-based access and theme persistence.
Live Demo : findash-liart.vercel.app
- Dual Roles : Viewer (read-only) and Admin (full CRUD)
- Dark Mode : Light/dark theme switching with localStorage persistence
- Charts : Balance trend (line) and spending by category (doughnut)
- Filtering : Filter by type, search by description or category
- Sorting : Sort by date or amount, ascending or descending
- Pagination : Configurable transactions per page
- Responsive : Mobile-friendly layout using Bootstrap Grid
Prerequisites : Any modern web browser. No build tools required.
git clone https://github.com/yashwanth2706/findash.git
cd findashOpen dashboard.html directly in a browser, or serve it locally:
# Python
python -m http.server 8000
# Then open: http://localhost:8000/dashboard.htmlfindash/
├── dashboard.html # Main HTML entry point
├── css/
│ ├── style.css # Base styles, layout, components
│ └── darkmode.css # Dark mode overrides
├── js/
│ ├── core/
│ │ ├── appState.js # Single source of truth
│ │ ├── dispatcher.js # Routes actions through reducer
│ │ └── reducer.js # Pure function: state + action → new state
│ ├── data/
│ │ └── defaultTransactionData.js # Seed data
│ ├── helpers/
│ │ └── viewHelpers.js # Pure derived-state functions
│ ├── ui/
│ │ ├── domdiffer.js # DOM patching utilities
│ │ ├── modalHelper.js # Modal state and form validation
│ │ └── render.js # Pure render function (state → DOM)
│ ├── charts/
│ │ ├── chartFunctions.js # Chart computation logic
│ │ ├── chartHelpers.js # Chart utility functions
│ │ └── chartInstances.js # Chart.js instance setup
│ └── onBootEventListener.js # Entry point, event wiring
├── vercel.json # Vercel deployment config
└── README.md
FinDash follows a Redux-inspired unidirectional data flow pattern with a strict separation between state and UI.
User Action → Event Handler → Dispatcher → Reducer → New State → Render → DOM Diffing → Updated UI
Core (js/core/)
appState.js— Global state object, single source of truthreducer.js— Pure function that returns new state for each action typedispatcher.js— Calls reducer, updates state, triggers re-render
Helpers (js/helpers/)
viewHelpers.js contains pure derived-state functions:
getFilteredSorted()— Applies type filter, search term, and sort ordergetPaginatedData()— Slices filtered results for current pagecomputeSummary()— Calculates total income, expenses, and balancecomputeInsights()— Derives top spending category, income source, month-over-month changecomputeTrend()— Builds monthly balance data for the trend chart
UI (js/ui/)
render.js— Pure function: takes state, updates DOM via domdifferdomdiffer.js— Targeted patching:patchText(),patchClass(),patchStyle(),patchTbody()(keyed diffing for table rows)modalHelper.js— Modal open/close state and form validation
Charts (js/charts/)
chartFunctions.js— Data computation for chart datasetschartHelpers.js— Shared chart utilitieschartInstances.js— Chart.js instance creation and updates
Entry Point
onBootEventListener.js— Initializes state, attaches all event listeners, triggers first render
{
transactions: [], // Array of transaction objects
role: "viewer", // "viewer" | "admin"
filterType: "all", // "all" | "income" | "expense"
searchTerm: "", // Search query string
sortBy: "date_desc", // "date_desc" | "date_asc" | "amount_desc" | "amount_asc"
darkMode: false, // Theme preference
currentPage: 1, // Active pagination page
itemsPerPage: 5, // Transactions per page
deleteConfirmRequired: true // User preference for delete confirmation
}┌──────────────────────────────────────────────────┐
│ User Interface │
│ (Dashboard, Tables, Charts, Forms, Modals) │
└─────────────────────┬────────────────────────────┘
│ User Actions (click, input, change)
▼
┌──────────────────────────────────────────────────┐
│ Event Handlers │
│ (onBootEventListener.js) │
└─────────────────────┬────────────────────────────┘
│ dispatch({ type, payload })
▼
┌──────────────────────────────────────────────────┐
│ Dispatcher │
│ (dispatcher.js) │
└─────────────────────┬────────────────────────────┘
│ reducer(state, action)
▼
┌──────────────────────────────────────────────────┐
│ Reducer │
│ (reducer.js — Pure Function) │
│ ADD_TRANSACTION, UPDATE_TRANSACTION, │
│ DELETE_TRANSACTION, SET_FILTER, │
│ SET_SEARCH, SET_SORT, TOGGLE_DARK_MODE, ... │
└─────────────────────┬────────────────────────────┘
│ newState
▼
┌──────────────────────────────────────────────────┐
│ App State │
│ (appState.js — Single Source of Truth) │
│ Persisted to localStorage (transactions, prefs) │
└─────────────────────┬────────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ Render Function │
│ (render.js — Pure Function) │
│ Calls: getFilteredSorted(), getPaginatedData(), │
│ computeSummary(), computeInsights() │
└─────────────────────┬────────────────────────────┘
▼
┌──────────────────────────────────────────────────┐
│ DOM Diffing │
│ (domdiffer.js) │
│ Targeted patching, keyed diffing for table rows │
└─────────────────────┬────────────────────────────┘
▼
Updated UI
| Decision | Reasoning |
|---|---|
| Vanilla JS | No transpiler overhead; sufficient for this scope; deliberate learning choice |
| Redux Pattern | Predictable state mutations; easy to trace and debug |
| Pure Functions | No side effects in reducer or render; reproducible output |
| DOM Diffing | Avoids full re-renders; preserves form input state |
| Keyed Diffing | Efficient table row updates without rebuilding the list |
| Separate Dark Mode CSS | Scoped overrides in darkmode.csskeep base styles clean |
| localStorage | Simple client-side persistence without a backend |
| Permission | Viewer | Admin |
|---|---|---|
| View transactions | Yes | Yes |
| View charts and insights | Yes | Yes |
| Filter, search, sort | Yes | Yes |
| Toggle dark mode | Yes | Yes |
| Add transaction | No | Yes |
| Edit transaction | No | Yes |
| Delete transaction | No | Yes |
| Clear all transactions | No | Yes |
| Access settings | No | Yes |
| localStorage Key | Content | Type |
|---|---|---|
fintransactions |
Array of transactions | JSON |
darkMode |
Theme preference | Boolean |
skipDeleteConfirm |
Delete confirmation preference | Boolean |
{
id: "1712250123456", // Timestamp-based unique ID
date: "2024-04-04", // ISO date string
description: "Salary", // User-provided
category: "Work", // User-provided
amount: 5000, // Always positive number
type: "income" | "expense"
}| Layer | Technology |
|---|---|
| Markup | HTML5 |
| Styling | CSS3, Bootstrap 5.3.0 |
| Icons | Bootstrap Icons 1.11.3 |
| Scripting | JavaScript ES6+ (no JS frameworks) |
| Charts | Chart.js 4.4.0 |
| Persistence | localStorage API |
| Deployment | Vercel |
The project is a static site with no build step. Any static host works.
Vercel (current):
npm install -g vercel
vercelGitHub Pages : Push to main, enable Pages in repository settings.
Netlify : Connect the repository; leave build command and publish directory empty.
MIT