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) -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>
This commit is contained in:
64
src/main.jsx
64
src/main.jsx
@@ -5,22 +5,25 @@ import {
|
||||
BrowserRouter,
|
||||
Routes,
|
||||
Route,
|
||||
useNavigate
|
||||
useNavigate,
|
||||
useLocation,
|
||||
matchPath
|
||||
} from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
CssBaseline,
|
||||
CircularProgress,
|
||||
Toolbar
|
||||
CircularProgress
|
||||
} from "@mui/material";
|
||||
import Home from './Home';
|
||||
import FetchRequests from './FetchRequest/FetchRequestCreate';
|
||||
import FetchRequestDetail from './FetchRequest/FetchRequestDetail';
|
||||
import Expense from './Expense/Expense';
|
||||
import { AppProvider, useAppContext, Admin, ProfileRoutes } from '../react-openapi';
|
||||
import { AuthProvider, AuthPage, useAuth, ProfileCreate, ProfileEdit, ProfileView } from "../react-auth";
|
||||
import Header from './Header';
|
||||
import Footer from './Footer';
|
||||
import AppTheme from './shared-theme/AppTheme';
|
||||
import { ToastProvider } from './ui/Toast';
|
||||
import { specConfiguration } from './openapi-config';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
@@ -76,6 +79,7 @@ const routerMapping = [
|
||||
{ path: "/register", component: RegisterPage, headerTitle: "Register" },
|
||||
{ path: "/fetch-requests", component: FetchRequests, headerTitle: "Fetch Requests" },
|
||||
{ path: "/fetch-requests/:id", component: FetchRequestDetail, headerTitle: "Fetch Request" },
|
||||
{ path: "/expenses", component: Expense, headerTitle: "Expenses" },
|
||||
{ path: "/admin/*", component: Admin, headerTitle: "Admin" },
|
||||
{ path: "/profile/*", component: ProfileRoutes, headerTitle: "Profile" },
|
||||
];
|
||||
@@ -84,6 +88,16 @@ const routerMapping = [
|
||||
function AppContent() {
|
||||
const { authConfig, loading } = useAppContext();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
const matched = routerMapping.find((route) =>
|
||||
matchPath({ path: route.path, end: false }, location.pathname),
|
||||
);
|
||||
document.title = matched
|
||||
? `${matched.headerTitle} · Khata`
|
||||
: "Khata — Financial Ledger";
|
||||
}, [location.pathname]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -97,23 +111,37 @@ function AppContent() {
|
||||
<AuthProvider authConfig={authConfig} onUnauthorized={() => navigate("/login")}>
|
||||
<AppTheme>
|
||||
<CssBaseline enableColorScheme />
|
||||
<Header routerMapping={routerMapping} />
|
||||
<ToastProvider>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
|
||||
<Header routerMapping={routerMapping} />
|
||||
|
||||
<Box sx={{ pb: 8 }}>
|
||||
<Toolbar />
|
||||
<Box
|
||||
component="main"
|
||||
key={location.pathname}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
animation: "fadeIn 220ms ease-out",
|
||||
"@keyframes fadeIn": {
|
||||
from: { opacity: 0, transform: "translateY(4px)" },
|
||||
to: { opacity: 1, transform: "translateY(0)" },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
{routerMapping.map(({ path, component: Component }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
element={<Component basePath={path.replace(/\/\*$/, "")} />}
|
||||
/>
|
||||
))}
|
||||
</Routes>
|
||||
</Box>
|
||||
|
||||
<Routes>
|
||||
{routerMapping.map(({ path, component: Component }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
element={<Component basePath={path.replace(/\/\*$/, "")} />}
|
||||
/>
|
||||
))}
|
||||
</Routes>
|
||||
</Box>
|
||||
|
||||
<Footer />
|
||||
<Footer />
|
||||
</Box>
|
||||
</ToastProvider>
|
||||
</AppTheme>
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user