- 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
79 lines
3.0 KiB
TypeScript
79 lines
3.0 KiB
TypeScript
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>
|
|
);
|
|
}
|