refactor: decompose monolithic App.tsx into per-component files
- Extract views to src/components/views/ (Overview, Logbook, Trends, Settings, Auth) - Extract standalone components (EntryModal, ImportPreviewModal, ConfirmDialog, Sidebar, TopBar, MobileNav, EntryLedger, FiltersPanel, QuickAddPanel, etc.) - Move shared helpers to src/lib (dashboard, filters, userDisplay) and static data to src/data (accents, quickAdds) - App.tsx now orchestration-only (2971 -> 777 lines); no behavior change
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { Search, X } from "lucide-react";
|
||||
import { DEFAULT_FILTERS } from "../lib/filters";
|
||||
import { AppCard } from "./AppCard";
|
||||
import type { DateFilter, Filters, Flavour } from "../types";
|
||||
|
||||
type FiltersPanelProps = {
|
||||
filters: Filters;
|
||||
flavours: Flavour[];
|
||||
compact?: boolean;
|
||||
onChange: (filters: Filters) => void;
|
||||
};
|
||||
|
||||
export function FiltersPanel({ filters, flavours, compact = false, onChange }: FiltersPanelProps) {
|
||||
const set = <Key extends keyof Filters>(key: Key, value: Filters[Key]) => {
|
||||
onChange({ ...filters, [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<AppCard title="Filters" subtitle={compact ? "Scope the charts" : "Search the logbook"}>
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-1">
|
||||
<label className="field-label">
|
||||
Flavour
|
||||
<select className="field-control" value={filters.flavour} onChange={(event) => set("flavour", event.target.value)}>
|
||||
<option value="all">All flavours</option>
|
||||
{flavours.map((flavour) => (
|
||||
<option key={flavour.name} value={flavour.name}>
|
||||
{flavour.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="field-label">
|
||||
Date range
|
||||
<select className="field-control" value={filters.dateRange} onChange={(event) => set("dateRange", event.target.value as DateFilter)}>
|
||||
<option value="all">All time</option>
|
||||
<option value="today">Today</option>
|
||||
<option value="week">This week</option>
|
||||
<option value="month">This month</option>
|
||||
<option value="custom">Custom range</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{filters.dateRange === "custom" && (
|
||||
<>
|
||||
<label className="field-label">
|
||||
From
|
||||
<input className="field-control" type="date" value={filters.from} onChange={(event) => set("from", event.target.value)} />
|
||||
</label>
|
||||
<label className="field-label">
|
||||
To
|
||||
<input className="field-control" type="date" value={filters.to} onChange={(event) => set("to", event.target.value)} />
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
|
||||
<label className="field-label sm:col-span-2 xl:col-span-1">
|
||||
Store or location
|
||||
<span className="relative">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" size={16} aria-hidden="true" />
|
||||
<input
|
||||
className="field-control pl-9"
|
||||
type="search"
|
||||
placeholder="Tesco, Shell, corner shop..."
|
||||
value={filters.store}
|
||||
onChange={(event) => set("store", event.target.value)}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<button className="secondary-button sm:col-span-2 xl:col-span-1" type="button" onClick={() => onChange(DEFAULT_FILTERS)}>
|
||||
<X size={17} aria-hidden="true" />
|
||||
Clear filters
|
||||
</button>
|
||||
</div>
|
||||
</AppCard>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user