2026-05-16 15:38:28 -05:00
|
|
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
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 {
|
|
|
|
|
BarChart2, Calendar, CreditCard, Loader2, Navigation, Plus,
|
|
|
|
|
Receipt, Search, Settings, Snowflake, Tag, Upload, User, X,
|
|
|
|
|
} from 'lucide-react';
|
2026-05-16 15:38:28 -05:00
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
import { api } from '@/api';
|
|
|
|
|
import { cn, fmt } from '@/lib/utils';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
|
|
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
|
|
|
// ─── Navigation commands ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
const MONTHS = [
|
|
|
|
|
'January','February','March','April','May','June',
|
|
|
|
|
'July','August','September','October','November','December',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const NAV_COMMANDS = [
|
|
|
|
|
{ id: 'nav-tracker', label: 'Go to Tracker', icon: Receipt, path: '/', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-bills', label: 'Go to Bills', icon: CreditCard, path: '/bills', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-calendar', label: 'Go to Calendar', icon: Calendar, path: '/calendar', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-summary', label: 'Go to Summary', icon: BarChart2, path: '/summary', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-analytics', label: 'Go to Analytics', icon: BarChart2, path: '/analytics', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-snowball', label: 'Go to Snowball', icon: Snowflake, path: '/snowball', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-categories', label: 'Go to Categories', icon: Tag, path: '/categories', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-data', label: 'Go to Data', icon: Upload, path: '/data', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-settings', label: 'Go to Settings', icon: Settings, path: '/settings', group: 'Navigate' },
|
|
|
|
|
{ id: 'nav-profile', label: 'Go to Profile', icon: User, path: '/profile', group: 'Navigate' },
|
|
|
|
|
{ id: 'action-new-bill', label: 'Add a new bill', icon: Plus, path: '/bills?new=1', group: 'Actions' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Generate jump-to-month commands for the current year ± 1
|
|
|
|
|
function buildMonthCommands() {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
const commands = [];
|
|
|
|
|
for (let y = year - 1; y <= year + 1; y++) {
|
|
|
|
|
for (let m = 1; m <= 12; m++) {
|
|
|
|
|
commands.push({
|
|
|
|
|
id: `jump-${y}-${m}`,
|
|
|
|
|
label: `Jump to ${MONTHS[m - 1]} ${y}`,
|
|
|
|
|
icon: Calendar,
|
|
|
|
|
path: `/?year=${y}&month=${m}`,
|
|
|
|
|
group: 'Jump to Month',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return commands;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-05-16 15:38:28 -05:00
|
|
|
function amountSearchText(...values) {
|
|
|
|
|
return values
|
|
|
|
|
.filter(value => value !== null && value !== undefined && Number.isFinite(Number(value)))
|
|
|
|
|
.flatMap(value => {
|
|
|
|
|
const num = Number(value);
|
|
|
|
|
return [String(num), num.toFixed(2), `$${num.toFixed(2)}`];
|
|
|
|
|
})
|
|
|
|
|
.join(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function billSearchText(bill) {
|
|
|
|
|
return [
|
|
|
|
|
bill.name,
|
|
|
|
|
bill.category_name,
|
|
|
|
|
bill.notes,
|
|
|
|
|
bill.billing_cycle,
|
|
|
|
|
bill.bucket,
|
|
|
|
|
bill.website,
|
|
|
|
|
amountSearchText(
|
|
|
|
|
bill.expected_amount,
|
|
|
|
|
bill.current_balance,
|
|
|
|
|
bill.minimum_payment,
|
|
|
|
|
bill.interest_rate,
|
|
|
|
|
),
|
|
|
|
|
].filter(Boolean).join(' ').toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shortcutLabel() {
|
|
|
|
|
if (typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.platform)) {
|
|
|
|
|
return 'Cmd K';
|
|
|
|
|
}
|
|
|
|
|
return 'Ctrl K';
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// ─── Result item components ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function BillResult({ bill, onOpenBills, onOpenTracker }) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
'group grid gap-2 rounded-lg border border-transparent p-2 transition-colors',
|
|
|
|
|
'hover:border-border/70 hover:bg-accent/65 sm:grid-cols-[1fr_auto]',
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onOpenBills(bill)}
|
|
|
|
|
className="flex min-w-0 items-center gap-3 rounded-md text-left focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
|
|
|
>
|
|
|
|
|
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
|
|
|
|
<Receipt className="h-4 w-4" />
|
|
|
|
|
</span>
|
|
|
|
|
<span className="min-w-0">
|
|
|
|
|
<span className="flex min-w-0 items-center gap-2">
|
|
|
|
|
<span className="truncate text-sm font-semibold text-foreground">{bill.name}</span>
|
|
|
|
|
{!bill.active && (
|
|
|
|
|
<span className="shrink-0 rounded-full border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
|
|
|
Inactive
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
<span className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
|
|
|
|
<span>{bill.category_name || 'Uncategorized'}</span>
|
|
|
|
|
<span>Due {bill.due_day}</span>
|
|
|
|
|
<span>{fmt(bill.expected_amount || 0)}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
<div className="flex items-center justify-end gap-1 pl-12 sm:pl-0">
|
|
|
|
|
<Button type="button" size="sm" variant="ghost" className="h-8 px-2.5 text-xs" onClick={() => onOpenTracker(bill)}>
|
|
|
|
|
Tracker
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="button" size="sm" variant="default" className="h-8 px-2.5 text-xs" onClick={() => onOpenBills(bill)}>
|
|
|
|
|
Bills
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CommandResult({ cmd, onRun }) {
|
|
|
|
|
const Icon = cmd.icon || Navigation;
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onRun(cmd)}
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex w-full items-center gap-3 rounded-lg border border-transparent p-2 text-left transition-colors',
|
|
|
|
|
'hover:border-border/70 hover:bg-accent/65 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted/60 text-muted-foreground">
|
|
|
|
|
<Icon className="h-4 w-4" />
|
|
|
|
|
</span>
|
|
|
|
|
<span className="min-w-0">
|
|
|
|
|
<span className="block truncate text-sm font-medium text-foreground">{cmd.label}</span>
|
|
|
|
|
<span className="mt-0.5 block text-xs text-muted-foreground">{cmd.group}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── Main component ───────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-05-16 15:38:28 -05:00
|
|
|
export default function CommandPalette() {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const inputRef = useRef(null);
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
const [query, setQuery] = useState('');
|
|
|
|
|
const [bills, setBills] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [loaded, setLoaded] = useState(false);
|
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
|
|
|
const monthCommands = useMemo(() => buildMonthCommands(), []);
|
2026-05-16 15:38:28 -05:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const openPalette = () => setOpen(true);
|
|
|
|
|
const onKeyDown = (event) => {
|
|
|
|
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
setOpen(value => !value);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('command-palette:open', openPalette);
|
|
|
|
|
window.addEventListener('keydown', onKeyDown);
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener('command-palette:open', openPalette);
|
|
|
|
|
window.removeEventListener('keydown', onKeyDown);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return;
|
|
|
|
|
window.setTimeout(() => inputRef.current?.focus(), 0);
|
|
|
|
|
if (loaded || loading) return;
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
api.allBills({ inactive: true })
|
|
|
|
|
.then(rows => {
|
|
|
|
|
setBills(Array.isArray(rows) ? rows : []);
|
|
|
|
|
setLoaded(true);
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
toast.error(err.message || 'Failed to load bills');
|
|
|
|
|
})
|
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
|
}, [loaded, loading, open]);
|
|
|
|
|
|
|
|
|
|
const close = () => {
|
|
|
|
|
setOpen(false);
|
|
|
|
|
setQuery('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openBills = (bill) => {
|
|
|
|
|
const params = new URLSearchParams({ search: bill.name });
|
|
|
|
|
if (!bill.active) params.set('inactive', '1');
|
|
|
|
|
navigate(`/bills?${params.toString()}`);
|
|
|
|
|
close();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openTracker = (bill) => {
|
|
|
|
|
const params = new URLSearchParams({ search: bill.name });
|
|
|
|
|
navigate(`/?${params.toString()}`);
|
|
|
|
|
close();
|
|
|
|
|
};
|
|
|
|
|
|
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
|
|
|
const runCommand = (cmd) => {
|
|
|
|
|
navigate(cmd.path);
|
|
|
|
|
close();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const allCommands = useMemo(() => [...NAV_COMMANDS, ...monthCommands], [monthCommands]);
|
|
|
|
|
|
|
|
|
|
const { billResults, commandResults } = useMemo(() => {
|
|
|
|
|
const q = query.trim().toLowerCase();
|
|
|
|
|
|
|
|
|
|
if (!q) {
|
|
|
|
|
const sortedBills = [...bills]
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
if (!!a.active !== !!b.active) return a.active ? -1 : 1;
|
|
|
|
|
return String(a.name || '').localeCompare(String(b.name || ''));
|
|
|
|
|
})
|
|
|
|
|
.slice(0, 6);
|
|
|
|
|
return {
|
|
|
|
|
billResults: sortedBills,
|
|
|
|
|
commandResults: NAV_COMMANDS.slice(0, 4),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const matchedBills = [...bills]
|
|
|
|
|
.filter(bill => billSearchText(bill).includes(q))
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
if (!!a.active !== !!b.active) return a.active ? -1 : 1;
|
|
|
|
|
return String(a.name || '').localeCompare(String(b.name || ''));
|
|
|
|
|
})
|
|
|
|
|
.slice(0, 8);
|
|
|
|
|
|
|
|
|
|
const matchedCommands = allCommands
|
|
|
|
|
.filter(cmd => cmd.label.toLowerCase().includes(q) || cmd.group.toLowerCase().includes(q))
|
|
|
|
|
.slice(0, 5);
|
|
|
|
|
|
|
|
|
|
return { billResults: matchedBills, commandResults: matchedCommands };
|
|
|
|
|
}, [bills, query, allCommands]);
|
|
|
|
|
|
|
|
|
|
const hasResults = billResults.length > 0 || commandResults.length > 0;
|
|
|
|
|
|
2026-05-16 15:38:28 -05:00
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
|
|
|
<DialogContent className="gap-0 overflow-hidden p-0 sm:max-w-2xl">
|
|
|
|
|
<DialogHeader className="sr-only">
|
|
|
|
|
<DialogTitle>Command palette</DialogTitle>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2 border-b border-border/70 px-4 py-3">
|
|
|
|
|
<Search className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
|
|
|
<Input
|
|
|
|
|
ref={inputRef}
|
|
|
|
|
value={query}
|
|
|
|
|
onChange={event => setQuery(event.target.value)}
|
|
|
|
|
onKeyDown={event => {
|
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
|
|
|
if (event.key === 'Enter') {
|
|
|
|
|
if (billResults[0]) openBills(billResults[0]);
|
|
|
|
|
else if (commandResults[0]) runCommand(commandResults[0]);
|
|
|
|
|
}
|
2026-05-16 15:38:28 -05:00
|
|
|
}}
|
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
|
|
|
placeholder="Search bills or type a command…"
|
2026-05-16 15:38:28 -05:00
|
|
|
className="h-9 border-0 bg-transparent px-0 text-base shadow-none focus-visible:ring-0"
|
|
|
|
|
/>
|
|
|
|
|
{query && (
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
size="icon"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="h-7 w-7 shrink-0"
|
|
|
|
|
onClick={() => setQuery('')}
|
|
|
|
|
aria-label="Clear search"
|
|
|
|
|
>
|
|
|
|
|
<X className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</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 className="max-h-[min(32rem,75svh)] overflow-y-auto p-2 space-y-1">
|
2026-05-16 15:38:28 -05:00
|
|
|
{loading ? (
|
|
|
|
|
<div className="flex items-center justify-center gap-2 px-4 py-12 text-sm text-muted-foreground">
|
|
|
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
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
|
|
|
Loading…
|
2026-05-16 15:38:28 -05:00
|
|
|
</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
|
|
|
) : !hasResults ? (
|
|
|
|
|
<div className="px-4 py-12 text-center text-sm text-muted-foreground">
|
|
|
|
|
No results.
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{commandResults.length > 0 && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60">
|
|
|
|
|
Commands
|
|
|
|
|
</p>
|
|
|
|
|
<div className="space-y-0.5">
|
|
|
|
|
{commandResults.map(cmd => (
|
|
|
|
|
<CommandResult key={cmd.id} cmd={cmd} onRun={runCommand} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{billResults.length > 0 && (
|
|
|
|
|
<div>
|
|
|
|
|
{commandResults.length > 0 && (
|
|
|
|
|
<p className="mt-3 mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60">
|
2026-05-16 15:38:28 -05:00
|
|
|
Bills
|
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
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<div className="space-y-0.5">
|
|
|
|
|
{billResults.map(bill => (
|
|
|
|
|
<BillResult
|
|
|
|
|
key={bill.id}
|
|
|
|
|
bill={bill}
|
|
|
|
|
onOpenBills={openBills}
|
|
|
|
|
onOpenTracker={openTracker}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
2026-05-16 15:38:28 -05:00
|
|
|
</div>
|
|
|
|
|
</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
|
|
|
)}
|
|
|
|
|
</>
|
2026-05-16 15:38:28 -05:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between border-t border-border/70 px-4 py-2 text-xs text-muted-foreground">
|
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
|
|
|
<span>Enter to open · Tab to focus</span>
|
2026-05-16 15:38:28 -05:00
|
|
|
<span>{shortcutLabel()}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|