fix(ui): unify list filters across all pages (bare controls, standard sizes)

Every list-page FilterBar now follows one spec: bare controls (no Field label rows — which broke flex-end alignment), standardized widths (search 1 1 320px grows; small selects 0 0 160px; entity selects 0 0 220px; MonthField 0 0 180px; dates 0 0 150px), consistent 'Všechny/Všichni/Všechna <entity>' defaults, and a placeholder on every search. TripsAdmin's separate month-Select + year-Select collapse into one MonthField (yyyy-MM, split at the query boundary) — matching Attendance/TripsHistory. The 4 labeled attendance/trips pages move off the Field wrapper; Projects & ReceivedInvoices move their inline search into a FilterBar. Chrome-verified all ~18 filter pages in light + the existing theme.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-07 11:49:28 +02:00
parent f1c533488a
commit f017867d5f
13 changed files with 104 additions and 139 deletions

View File

@@ -13,7 +13,6 @@ import {
Button, Button,
Card, Card,
ConfirmDialog, ConfirmDialog,
Field,
FilterBar, FilterBar,
LoadingState, LoadingState,
MonthField, MonthField,
@@ -200,25 +199,21 @@ export default function AttendanceAdmin() {
{/* Filters */} {/* Filters */}
<FilterBar> <FilterBar>
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 180px" }}>
<Field label="Měsíc"> <MonthField value={month} onChange={(val) => setMonth(val)} />
<MonthField value={month} onChange={(val) => setMonth(val)} />
</Field>
</Box> </Box>
<Box sx={{ flex: "1 1 240px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Field label="Zaměstnanec"> <Select
<Select value={filterUserId}
value={filterUserId} onChange={setFilterUserId}
onChange={setFilterUserId} options={[
options={[ { value: "", label: "Všichni zaměstnanci" },
{ value: "", label: "Všichni" }, ...data.users.map((user) => ({
...data.users.map((user) => ({ value: String(user.id),
value: String(user.id), label: user.name,
label: user.name, })),
})), ]}
]} />
/>
</Field>
</Box> </Box>
</FilterBar> </FilterBar>

View File

@@ -29,7 +29,6 @@ import {
Button, Button,
Card, Card,
DataTable, DataTable,
Field,
FilterBar, FilterBar,
MonthField, MonthField,
ProgressBar, ProgressBar,
@@ -515,10 +514,8 @@ export default function AttendanceHistory() {
{/* Filters */} {/* Filters */}
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 200px" }}> <Box sx={{ flex: "0 0 180px" }}>
<Field label="Měsíc"> <MonthField value={month} onChange={(val) => setMonth(val)} />
<MonthField value={month} onChange={(val) => setMonth(val)} />
</Field>
</Box> </Box>
</FilterBar> </FilterBar>

View File

@@ -311,7 +311,7 @@ export default function AuditLog() {
</Modal> </Modal>
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 220px" }}> <Box sx={{ flex: "1 1 320px" }}>
<TextField <TextField
value={filters.search} value={filters.search}
onChange={(e) => handleFilterChange("search", e.target.value)} onChange={(e) => handleFilterChange("search", e.target.value)}
@@ -319,14 +319,14 @@ export default function AuditLog() {
fullWidth fullWidth
/> />
</Box> </Box>
<Box sx={{ flex: "0 0 170px" }}> <Box sx={{ flex: "0 0 160px" }}>
<Select <Select
value={filters.action} value={filters.action}
onChange={(val) => handleFilterChange("action", val)} onChange={(val) => handleFilterChange("action", val)}
options={[{ value: "", label: "Všechny akce" }, ...ACTION_OPTIONS]} options={[{ value: "", label: "Všechny akce" }, ...ACTION_OPTIONS]}
/> />
</Box> </Box>
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={filters.entity_type} value={filters.entity_type}
onChange={(val) => handleFilterChange("entity_type", val)} onChange={(val) => handleFilterChange("entity_type", val)}

View File

@@ -739,7 +739,7 @@ export default function Offers() {
fullWidth fullWidth
/> />
</Box> </Box>
<Box sx={{ flex: "0 1 240px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={customerFilter === "" ? "" : String(customerFilter)} value={customerFilter === "" ? "" : String(customerFilter)}
onChange={(value) => { onChange={(value) => {

View File

@@ -25,6 +25,7 @@ import {
Modal, Modal,
ConfirmDialog, ConfirmDialog,
Field, Field,
FilterBar,
TextField, TextField,
Select, Select,
DateField, DateField,
@@ -365,17 +366,19 @@ export default function Projects() {
)} )}
</Box> </Box>
<Box sx={{ mb: 3 }}> <FilterBar>
<TextField <Box sx={{ flex: "1 1 320px" }}>
value={search} <TextField
onChange={(e) => { value={search}
setSearch(e.target.value); onChange={(e) => {
setPage(1); setSearch(e.target.value);
}} setPage(1);
placeholder="Hledat podle čísla, názvu nebo zákazníka..." }}
fullWidth placeholder="Hledat podle čísla, názvu nebo zákazníka..."
/> fullWidth
</Box> />
</Box>
</FilterBar>
<Card sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}> <Card sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}>
<DataTable<Project> <DataTable<Project>

View File

@@ -30,6 +30,7 @@ import {
Modal, Modal,
ConfirmDialog, ConfirmDialog,
Field, Field,
FilterBar,
TextField, TextField,
Select, Select,
DateField, DateField,
@@ -762,14 +763,16 @@ export default function ReceivedInvoices({
transition={{ duration: 0.25, delay: 0.08 }} transition={{ duration: 0.25, delay: 0.08 }}
> >
<Card> <Card>
<Box sx={{ mb: 2 }}> <FilterBar>
<TextField <Box sx={{ flex: "1 1 320px" }}>
value={search} <TextField
onChange={(e) => setSearch(e.target.value)} value={search}
placeholder="Hledat podle dodavatele nebo čísla faktury..." onChange={(e) => setSearch(e.target.value)}
fullWidth placeholder="Hledat podle dodavatele nebo čísla faktury..."
/> fullWidth
</Box> />
</Box>
</FilterBar>
{showListSkeleton ? ( {showListSkeleton ? (
<LoadingState /> <LoadingState />

View File

@@ -27,6 +27,7 @@ import {
TextField, TextField,
Select, Select,
DateField, DateField,
MonthField,
StatusChip, StatusChip,
FilterBar, FilterBar,
LoadingState, LoadingState,
@@ -181,12 +182,10 @@ const BusinessIcon = (
export default function TripsAdmin() { export default function TripsAdmin() {
const alert = useAlert(); const alert = useAlert();
const { hasPermission } = useAuth(); const { hasPermission } = useAuth();
const [filterMonth, setFilterMonth] = useState(() => const [filterPeriod, setFilterPeriod] = useState(() => {
String(new Date().getMonth() + 1), const now = new Date();
); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
const [filterYear, setFilterYear] = useState(() => });
String(new Date().getFullYear()),
);
const [filterVehicleId, setFilterVehicleId] = useState(""); const [filterVehicleId, setFilterVehicleId] = useState("");
const [filterUserId, setFilterUserId] = useState(""); const [filterUserId, setFilterUserId] = useState("");
const printRef = useRef<HTMLDivElement>(null); const printRef = useRef<HTMLDivElement>(null);
@@ -220,8 +219,8 @@ export default function TripsAdmin() {
const { data: tripsData, isPending } = useQuery( const { data: tripsData, isPending } = useQuery(
tripListOptions({ tripListOptions({
month: Number(filterMonth) || undefined, month: Number(filterPeriod.slice(5, 7)) || undefined,
year: Number(filterYear) || undefined, year: Number(filterPeriod.slice(0, 4)) || undefined,
vehicleId: filterVehicleId ? Number(filterVehicleId) : undefined, vehicleId: filterVehicleId ? Number(filterVehicleId) : undefined,
userId: filterUserId ? Number(filterUserId) : undefined, userId: filterUserId ? Number(filterUserId) : undefined,
perPage: 100, perPage: 100,
@@ -319,10 +318,10 @@ export default function TripsAdmin() {
}; };
const getPeriodName = () => const getPeriodName = () =>
new Date(Number(filterYear), Number(filterMonth) - 1).toLocaleString( new Date(
"cs-CZ", Number(filterPeriod.slice(0, 4)),
{ month: "long", year: "numeric" }, Number(filterPeriod.slice(5, 7)) - 1,
); ).toLocaleString("cs-CZ", { month: "long", year: "numeric" });
const getSelectedVehicleName = () => { const getSelectedVehicleName = () => {
if (!filterVehicleId) return null; if (!filterVehicleId) return null;
const v = vehicles.find((v) => String(v.id) === filterVehicleId); const v = vehicles.find((v) => String(v.id) === filterVehicleId);
@@ -566,61 +565,34 @@ export default function TripsAdmin() {
{/* Filters */} {/* Filters */}
<FilterBar> <FilterBar>
<Box sx={{ flex: "0 0 160px" }}> <Box sx={{ flex: "0 0 180px" }}>
<Field label="Měsíc"> <MonthField value={filterPeriod} onChange={setFilterPeriod} />
<Select
value={filterMonth}
onChange={setFilterMonth}
options={Array.from({ length: 12 }, (_, i) => ({
value: String(i + 1),
label: new Date(2000, i).toLocaleString("cs-CZ", {
month: "long",
}),
}))}
/>
</Field>
</Box> </Box>
<Box sx={{ flex: "0 0 120px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Field label="Rok"> <Select
<Select value={filterVehicleId}
value={filterYear} onChange={setFilterVehicleId}
onChange={setFilterYear} options={[
options={Array.from({ length: 5 }, (_, i) => { { value: "", label: "Všechna vozidla" },
const y = new Date().getFullYear() - 2 + i; ...vehicles.map((v) => ({
return { value: String(y), label: String(y) }; value: String(v.id),
})} label: `${v.spz} - ${v.name}`,
/> })),
</Field> ]}
/>
</Box> </Box>
<Box sx={{ flex: "1 1 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Field label="Vozidlo"> <Select
<Select value={filterUserId}
value={filterVehicleId} onChange={setFilterUserId}
onChange={setFilterVehicleId} options={[
options={[ { value: "", label: "Všichni řidiči" },
{ value: "", label: "Všechna vozidla" }, ...tripUsers.map((u) => ({
...vehicles.map((v) => ({ value: String(u.id),
value: String(v.id), label: u.name,
label: `${v.spz} - ${v.name}`, })),
})), ]}
]} />
/>
</Field>
</Box>
<Box sx={{ flex: "1 1 200px" }}>
<Field label="Řidič">
<Select
value={filterUserId}
onChange={setFilterUserId}
options={[
{ value: "", label: "Všichni řidiči" },
...tripUsers.map((u) => ({
value: String(u.id),
label: u.name,
})),
]}
/>
</Field>
</Box> </Box>
</FilterBar> </FilterBar>

View File

@@ -9,7 +9,6 @@ import { tripHistoryOptions, tripVehiclesOptions } from "../lib/queries/trips";
import { import {
Card, Card,
DataTable, DataTable,
Field,
Select, Select,
MonthField, MonthField,
StatusChip, StatusChip,
@@ -221,25 +220,21 @@ export default function TripsHistory() {
{/* Filters */} {/* Filters */}
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 200px" }}> <Box sx={{ flex: "0 0 180px" }}>
<Field label="Měsíc"> <MonthField value={month} onChange={(val) => setMonth(val)} />
<MonthField value={month} onChange={(val) => setMonth(val)} />
</Field>
</Box> </Box>
<Box sx={{ flex: "1 1 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Field label="Vozidlo"> <Select
<Select value={vehicleId}
value={vehicleId} onChange={(value) => setVehicleId(value)}
onChange={(value) => setVehicleId(value)} options={[
options={[ { value: "", label: "Všechna vozidla" },
{ value: "", label: "Všechna vozidla" }, ...vehicles.map((v) => ({
...vehicles.map((v) => ({ value: String(v.id),
value: String(v.id), label: `${v.spz} - ${v.name}`,
label: `${v.spz} - ${v.name}`, })),
})), ]}
]} />
/>
</Field>
</Box> </Box>
</FilterBar> </FilterBar>

View File

@@ -142,7 +142,7 @@ export default function WarehouseInventory() {
/> />
<FilterBar> <FilterBar>
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 160px" }}>
<Select <Select
value={statusFilter} value={statusFilter}
onChange={(val) => { onChange={(val) => {

View File

@@ -183,7 +183,7 @@ export default function WarehouseIssues() {
/> />
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 220px" }}> <Box sx={{ flex: "1 1 320px" }}>
<TextField <TextField
value={search} value={search}
onChange={(e) => { onChange={(e) => {
@@ -210,7 +210,7 @@ export default function WarehouseIssues() {
/> />
</Box> </Box>
{projects.length > 0 && ( {projects.length > 0 && (
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={projectId === "" ? "" : String(projectId)} value={projectId === "" ? "" : String(projectId)}
onChange={(val) => { onChange={(val) => {

View File

@@ -170,7 +170,7 @@ export default function WarehouseItems() {
/> />
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 280px" }}> <Box sx={{ flex: "1 1 320px" }}>
<TextField <TextField
value={search} value={search}
onChange={(e) => { onChange={(e) => {
@@ -182,7 +182,7 @@ export default function WarehouseItems() {
/> />
</Box> </Box>
{categories.length > 0 && ( {categories.length > 0 && (
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={categoryId === "" ? "" : String(categoryId)} value={categoryId === "" ? "" : String(categoryId)}
onChange={(val) => { onChange={(val) => {

View File

@@ -182,7 +182,7 @@ export default function WarehouseReceipts() {
/> />
<FilterBar> <FilterBar>
<Box sx={{ flex: "1 1 220px" }}> <Box sx={{ flex: "1 1 320px" }}>
<TextField <TextField
value={search} value={search}
onChange={(e) => { onChange={(e) => {
@@ -209,7 +209,7 @@ export default function WarehouseReceipts() {
/> />
</Box> </Box>
{suppliers.length > 0 && ( {suppliers.length > 0 && (
<Box sx={{ flex: "0 0 200px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={supplierId === "" ? "" : String(supplierId)} value={supplierId === "" ? "" : String(supplierId)}
onChange={(val) => { onChange={(val) => {

View File

@@ -323,7 +323,7 @@ function ProjectConsumptionTab({
<Box> <Box>
<FilterBar> <FilterBar>
{projects.length > 0 && ( {projects.length > 0 && (
<Box sx={{ flex: "0 0 280px" }}> <Box sx={{ flex: "0 0 220px" }}>
<Select <Select
value={projectId === "" ? "" : String(projectId)} value={projectId === "" ? "" : String(projectId)}
onChange={(val) => setProjectId(val === "" ? "" : Number(val))} onChange={(val) => setProjectId(val === "" ? "" : Number(val))}