## 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) -9808a1fdesign overhaul + expenses page -d6856a5fix expenses list performance + dom nesting -3dd833ashared currency field + expenses rendered from react-openapi fields -e8c585bfix expenses grouping to parse occurred_at as DD-MM-YYYY strictly -cc940eeexpense list: split month totals, sticky headers + month pill -47d799fexpense list: clickable month pill + fix scroll-to-month -537aef9resolve relative media URLs against the API base in react-openapi -8e9a13dexpense list: replace accordion with flat Stripe-style rows -62dd06cexpense list: month accordions + month-year selector; drop unused x-data-grid -806bd42dropped 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>
194 lines
5.9 KiB
TypeScript
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>
|
|
);
|
|
}
|