design overhaul + expenses page
- Stripe-style theme: single indigo (#635BFF) brand, 6px radius, Inter scale; rewritten themePrimitives + MUI customizations (solid primary, near-black secondary, severity-aware Alert) - Sticky Header (brand-left nav, theme toggle, mobile drawer), inline Footer, route fade-in, per-route document.title - Home hero + feature cards (dead /dashboard,/reports links replaced) - Split-panel AuthPage with validation and password visibility - Data pages: PageHeader/EmptyState, breadcrumbed Fetch Request pages, admin table polish (numeric alignment, row-hover actions, skeletons, empty states), single-accent admin SideMenu - Global ToastProvider wired into app shell - New /expenses page: month-grouped feed, single-open accordion with inline detail (account, tags, amount, timestamps)
This commit is contained in:
85
src/Expense/ExpenseDetail.tsx
Normal file
85
src/Expense/ExpenseDetail.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
import { Box, Typography, Chip, Divider } from "@mui/material";
|
||||
import type { ExpenseItem } from "./types";
|
||||
import { formatCurrency, formatDate, formatDateTime } from "./types";
|
||||
|
||||
function Row({ label, value }: { label: string; value: React.ReactNode }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 2, alignItems: "baseline", py: 0.75 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ width: 160, flexShrink: 0 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>{value}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExpenseDetail({ item }: { item: ExpenseItem }) {
|
||||
const account = item.account;
|
||||
const tags = item.tags ?? [];
|
||||
const last4 = account?.number ? `…${account.number.slice(-4)}` : "";
|
||||
|
||||
return (
|
||||
<Box sx={{ pt: 1, pb: 0.5 }}>
|
||||
<Divider sx={{ mb: 1.5 }} />
|
||||
<Row
|
||||
label="Amount"
|
||||
value={
|
||||
<Typography variant="body2" fontWeight={600}>
|
||||
{formatCurrency(item.amount, account?.currency)}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<Row
|
||||
label="Account"
|
||||
value={
|
||||
<Typography variant="body2">
|
||||
{account ? `${account.name}${last4 ? ` (${last4})` : ""}` : "—"}
|
||||
{account?.type && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={account.type.replace(/_/g, " ")}
|
||||
variant="outlined"
|
||||
sx={{ ml: 1.5, fontSize: 11, height: 20 }}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<Row
|
||||
label="Tags"
|
||||
value={
|
||||
tags.length > 0 ? (
|
||||
<Box sx={{ display: "flex", gap: 0.75, flexWrap: "wrap" }}>
|
||||
{tags.map((tag, i) => (
|
||||
<Chip
|
||||
key={`${tag.name}-${i}`}
|
||||
size="small"
|
||||
label={`${tag.icon ?? ""} ${tag.name ?? ""}`.trim()}
|
||||
sx={{ fontSize: 12, height: 24 }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.disabled">
|
||||
No tags
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Row label="Date" value={<Typography variant="body2">{formatDate(item.occurred_at)}</Typography>} />
|
||||
<Row
|
||||
label="Transaction ID"
|
||||
value={
|
||||
<Typography variant="body2" sx={{ fontFamily: "monospace", fontSize: "0.8125rem" }}>
|
||||
{item.id}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<Row
|
||||
label="Created"
|
||||
value={<Typography variant="body2" color="text.secondary">{formatDateTime(item.created_at)}</Typography>}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user