2026-05-30 21:52:02 -05:00
|
|
|
import React, { useEffect, useState } from 'react';
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
import { cn } from '@/lib/utils';
|
2026-05-30 21:52:02 -05:00
|
|
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
|
|
|
|
|
export function fmt(isoStr) {
|
|
|
|
|
if (!isoStr) return '—';
|
|
|
|
|
const d = new Date(isoStr);
|
|
|
|
|
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
|
|
|
|
|
+ ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function importErrorState(err, fallback) {
|
|
|
|
|
const data = err?.data || {};
|
|
|
|
|
return {
|
|
|
|
|
message: err?.message || data.message || data.error || fallback,
|
|
|
|
|
error: data.error || fallback,
|
|
|
|
|
code: data.code || err?.code || null,
|
|
|
|
|
details: Array.isArray(data.details) ? data.details : (Array.isArray(err?.details) ? err.details : []),
|
|
|
|
|
error_id: data.error_id || null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 21:52:02 -05:00
|
|
|
export function SectionCard({
|
|
|
|
|
title,
|
|
|
|
|
subtitle,
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
collapsible = false,
|
|
|
|
|
defaultOpen = true,
|
|
|
|
|
storageKey,
|
|
|
|
|
summary,
|
|
|
|
|
actions,
|
|
|
|
|
}) {
|
|
|
|
|
const [open, setOpen] = useState(() => {
|
|
|
|
|
if (!collapsible || !storageKey || typeof window === 'undefined') return defaultOpen;
|
|
|
|
|
const stored = window.localStorage.getItem(storageKey);
|
|
|
|
|
return stored === null ? defaultOpen : stored === 'true';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!collapsible || !storageKey || typeof window === 'undefined') return;
|
|
|
|
|
window.localStorage.setItem(storageKey, String(open));
|
|
|
|
|
}, [collapsible, open, storageKey]);
|
|
|
|
|
|
|
|
|
|
const headerContent = (
|
|
|
|
|
<>
|
|
|
|
|
{collapsible && (
|
|
|
|
|
open
|
|
|
|
|
? <ChevronDown className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
|
|
|
: <ChevronRight className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
|
|
|
|
|
)}
|
|
|
|
|
<div className="min-w-0 flex-1">
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
<h2 className="text-xs font-bold uppercase tracking-widest text-muted-foreground">{title}</h2>
|
2026-05-30 21:52:02 -05:00
|
|
|
{subtitle && <p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>}
|
|
|
|
|
{collapsible && !open && summary && (
|
|
|
|
|
<p className="mt-1 truncate text-xs font-medium text-muted-foreground/80">{summary}</p>
|
|
|
|
|
)}
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
</div>
|
2026-05-30 21:52:02 -05:00
|
|
|
{actions && <div className="shrink-0">{actions}</div>}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn('table-surface mb-6', className)}>
|
|
|
|
|
{collapsible ? (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setOpen(value => !value)}
|
|
|
|
|
className="flex w-full items-start gap-2 border-b border-border/50 px-6 py-4 text-left transition-colors hover:bg-muted/20"
|
|
|
|
|
aria-expanded={open}
|
|
|
|
|
>
|
|
|
|
|
{headerContent}
|
|
|
|
|
</button>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex items-start gap-2 border-b border-border/50 px-6 py-4">
|
|
|
|
|
{headerContent}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{open && <div className="divide-y divide-border/50">{children}</div>}
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function CountPill({ label, value }) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="rounded-lg border border-border/60 bg-muted/25 px-3 py-2">
|
|
|
|
|
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">{label}</p>
|
|
|
|
|
<p className="mt-1 text-sm font-semibold tabular-nums">{value ?? 0}</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|