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:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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))}
|
||||||
|
|||||||
Reference in New Issue
Block a user