Files
khata-ui/src/Home.tsx
Vishesh 'ironeagle' Bangotra 25ec534597 Stripe-style UI overhaul + Expenses feed with month grouping (#15)
## Title
Stripe-style UI overhaul + Expenses feed with month grouping

## Summary
Rework the khata-ui frontend to a Stripe-grade design system and add a
rich, monthly-grouped Expenses experience driven by react-openapi's
shared, spec-configured field renderers.

## Highlights

### Design overhaul
- New design system: single indigo (#635BFF), 6px radius, Inter type
  scale, streamlined surfaces/inputs/feedback tokens
- Sticky app bar with brand-left header + inline footer, route titles
- Split-panel auth pages, Stripe-style Home hero + feature cards
- Toast provider + shared PageHeader/EmptyState UI primitives
- Admin polish: right-aligned numerics, row-hover actions, skeletons,
  richer empty states, breadcrumbed fetch-request flows

### Expenses page
- Monthly grouped feed with stat cards (total spent / this month / income)
- Month cards as single-open accordions with red-spent / green-income totals
- Flat transaction rows: logo, name/date, account, amount (no accordion)
- Floating month–year selector pill (scroll-spy) that expands + scrolls
  to the selected month
- Strict DD-MM-YYYY date parsing (day-first), no format guessing

### Shared react-openapi fields
- New `CurrencyField` + `formatCurrency` (cached Intl, sign-colored,
  sign-less amounts; form editing reuses NumberField)
- `resourceConfig.fieldTypes` override mechanism (amount → currency)
- Expenses page rendered via ListCellRenderer / DetailFieldRenderer /
  applyDisplayFormat — zero custom field renderers
- `resolveMediaUrl` resolves relative `/uploads/...` against the API
  base for every image field (Admin + Expenses logos)

### Fixes
- Perf: memoized rows, formatter caches, single-open accordions —
  cuts slow-click INP and DOM-nesting warnings
- Removed unused @mui/x-data-grid@7, which hoisted @mui/system@7 and
  crashed Box/createTheme at runtime (v5/v7 mix)

## Commits (10)
- 9808a1f design overhaul + expenses page
- d6856a5 fix expenses list performance + dom nesting
- 3dd833a shared currency field + expenses rendered from react-openapi fields
- e8c585b fix expenses grouping to parse occurred_at as DD-MM-YYYY strictly
- cc940ee expense list: split month totals, sticky headers + month pill
- 47d799f expense list: clickable month pill + fix scroll-to-month
- 537aef9 resolve relative media URLs against the API base in react-openapi
- 8e9a13d expense list: replace accordion with flat Stripe-style rows
- 62dd06c expense list: month accordions + month-year selector; drop unused x-data-grid
- 806bd42 dropped amount sign from CurrencyField.tsx

Reviewed-on: #15
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
2026-08-18 13:51:28 +00:00

194 lines
5.9 KiB
TypeScript

import * as React from "react";
import { Box, Typography, Button, Container, Grid, Paper, alpha } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import ReceiptLongIcon from "@mui/icons-material/ReceiptLong";
import SyncIcon from "@mui/icons-material/Sync";
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import { useAuth } from "../react-auth";
interface FeatureCardProps {
icon: React.ReactNode;
title: string;
description: string;
path: string;
}
function FeatureCard({ icon, title, description, path }: FeatureCardProps) {
const navigate = useNavigate();
const theme = useTheme();
return (
<Paper
elevation={0}
onClick={() => navigate(path)}
sx={{
p: 3,
borderRadius: 3,
border: "1px solid",
borderColor: "divider",
cursor: "pointer",
height: "100%",
display: "flex",
flexDirection: "column",
transition: "border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease",
"&:hover": {
borderColor: "transparent",
transform: "translateY(-2px)",
boxShadow: `0 8px 24px ${alpha(theme.palette.common.black, theme.palette.mode === "dark" ? 0.35 : 0.08)}`,
"& .feature-arrow": {
opacity: 1,
transform: "translateX(0)",
},
},
}}
>
<Box
sx={{
width: 40,
height: 40,
borderRadius: 2,
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: "action.hover",
color: "primary.main",
mb: 2,
}}
>
{icon}
</Box>
<Typography variant="subtitle1" fontWeight={600} sx={{ mb: 0.75 }}>
{title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ flex: 1, lineHeight: 1.6 }}>
{description}
</Typography>
<Box
className="feature-arrow"
sx={{
mt: 2,
display: "flex",
alignItems: "center",
gap: 0.5,
color: "primary.main",
fontWeight: 600,
opacity: 0,
transform: "translateX(-4px)",
transition: "opacity 180ms ease, transform 180ms ease",
}}
>
<Typography variant="caption" fontWeight={600} color="primary">
Open
</Typography>
<ArrowForwardIcon sx={{ fontSize: 14 }} />
</Box>
</Paper>
);
}
export default function Home() {
const navigate = useNavigate();
const { currentUser } = useAuth();
const features = [
{
icon: <ReceiptLongIcon />,
title: "Expenses",
description:
"Browse every transaction imported from your accounts — grouped by month, with entity, account, tags, and amount at a glance.",
path: "/expenses",
},
{
icon: <SyncIcon />,
title: "Fetch Requests",
description:
"Upload bank statements or configure email ingestion to auto-import transactions. Track pipeline status from pending through to completion.",
path: "/fetch-requests",
},
{
icon: <AccountBalanceWalletIcon />,
title: "Accounts & Entities",
description:
"Manage your accounts, expenses, tags, and payors through the OpenAPI-driven admin panel with full CRUD and server-side filters.",
path: "/admin",
},
{
icon: <AccountCircleIcon />,
title: "Profile",
description:
"View and edit your user profile — username, email, and account preferences — from the profile page.",
path: "/profile/me",
},
];
return (
<Box sx={{ width: "100%" }}>
<Container maxWidth="lg" sx={{ py: { xs: 6, md: 10 } }}>
<Box sx={{ maxWidth: 680, mx: { xs: "auto", md: 0 }, textAlign: { xs: "center", md: "left" } }}>
<Typography
variant="overline"
sx={{ color: "primary.main", mb: 1.5, display: "block", fontWeight: 700 }}
>
Khata · Financial Ledger
</Typography>
<Typography
variant="h1"
sx={{
color: "text.primary",
fontSize: { xs: "2.25rem", md: "3.25rem" },
letterSpacing: "-0.03em",
mb: 2,
}}
>
Your intelligent, extensible financial ledger.
</Typography>
<Typography
variant="body1"
color="text.secondary"
sx={{ maxWidth: 540, lineHeight: 1.7, fontSize: "1.0625rem" }}
>
Import transactions from bank statements, enrich them with entity and tag intelligence,
and stay on top of your cashflow — all in one place.
</Typography>
<Box sx={{ mt: 4, display: "flex", gap: 2, justifyContent: { xs: "center", md: "flex-start" }, flexWrap: "wrap" }}>
<Button
variant="contained"
size="large"
endIcon={<ArrowForwardIcon />}
onClick={() => navigate(currentUser ? "/fetch-requests" : "/login")}
sx={{ px: 3.5, py: 1.4, fontWeight: 600 }}
>
{currentUser ? "Enter App" : "Get Started"}
</Button>
<Button
variant="outlined"
size="large"
onClick={() => navigate("/fetch-requests")}
sx={{ px: 3.5, py: 1.4, fontWeight: 600 }}
>
Import Data
</Button>
</Box>
</Box>
<Grid container spacing={3} sx={{ mt: { xs: 4, md: 8 } }}>
{features.map((f) => (
<Grid key={f.title} item xs={12} sm={6} md={3}>
<FeatureCard {...f} />
</Grid>
))}
</Grid>
</Container>
</Box>
);
}