From e4038051e935970d4a365763204e2577aa32e14e Mon Sep 17 00:00:00 2001 From: BOHA Date: Sun, 7 Jun 2026 01:45:04 +0200 Subject: [PATCH] feat(mui): migrate Warehouse overview + replace residual admin-loading loaders with LoadingState Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/Offers.tsx | 7 +- src/admin/pages/PlanWork.tsx | 8 +- src/admin/pages/Projects.tsx | 7 +- src/admin/pages/Users.tsx | 7 +- src/admin/pages/Vehicles.tsx | 7 +- src/admin/pages/Warehouse.tsx | 530 +++++++++++++++++++--------------- 6 files changed, 312 insertions(+), 254 deletions(-) diff --git a/src/admin/pages/Offers.tsx b/src/admin/pages/Offers.tsx index 5a363b4..f012558 100644 --- a/src/admin/pages/Offers.tsx +++ b/src/admin/pages/Offers.tsx @@ -34,6 +34,7 @@ import { FilterBar, Tabs, PageHeader, + LoadingState, type DataColumn, type TabDef, } from "../ui"; @@ -450,11 +451,7 @@ export default function Offers() { }; if (isPending) { - return ( -
-
-
- ); + return ; } const total = pagination?.total ?? quotations.length; diff --git a/src/admin/pages/PlanWork.tsx b/src/admin/pages/PlanWork.tsx index 4653070..b96906f 100644 --- a/src/admin/pages/PlanWork.tsx +++ b/src/admin/pages/PlanWork.tsx @@ -15,7 +15,7 @@ import PlanGrid from "../components/PlanGrid"; import PlanCellModal from "../components/PlanCellModal"; import PlanCategoriesModal from "../components/PlanCategoriesModal"; import Forbidden from "../components/Forbidden"; -import { Button, Alert } from "../ui"; +import { Button, Alert, LoadingState } from "../ui"; import { projectListOptions, type Project } from "../lib/queries/projects"; import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan"; // plan.css is imported once globally in AdminApp.tsx — no per-page re-import. @@ -670,11 +670,7 @@ export default function PlanWork() { - {gridLoading && ( -
-
-
- )} + {gridLoading && } {/* Grid entrance: same pattern as the h1 / banner / toolbar above diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx index a439fc7..87f6e57 100644 --- a/src/admin/pages/Projects.tsx +++ b/src/admin/pages/Projects.tsx @@ -31,6 +31,7 @@ import { DateField, StatusChip, CheckboxField, + LoadingState, type DataColumn, } from "../ui"; @@ -234,11 +235,7 @@ export default function Projects() { }; if (isPending) { - return ( -
-
-
- ); + return ; } const columns: DataColumn[] = [ diff --git a/src/admin/pages/Users.tsx b/src/admin/pages/Users.tsx index 2d3437e..a0b2cf8 100644 --- a/src/admin/pages/Users.tsx +++ b/src/admin/pages/Users.tsx @@ -25,6 +25,7 @@ import { SwitchField, Select, StatusChip, + LoadingState, type DataColumn, } from "../ui"; @@ -247,11 +248,7 @@ export default function Users() { }; if (isPending) { - return ( -
-
-
- ); + return ; } const columns: DataColumn[] = [ diff --git a/src/admin/pages/Vehicles.tsx b/src/admin/pages/Vehicles.tsx index 07c2470..084de38 100644 --- a/src/admin/pages/Vehicles.tsx +++ b/src/admin/pages/Vehicles.tsx @@ -20,6 +20,7 @@ import { Field, TextField, SwitchField, + LoadingState, type DataColumn, } from "../ui"; @@ -222,11 +223,7 @@ export default function Vehicles() { }; if (isPending) { - return ( -
-
-
- ); + return ; } const columns: DataColumn[] = [ diff --git a/src/admin/pages/Warehouse.tsx b/src/admin/pages/Warehouse.tsx index 55e47fe..e7fac32 100644 --- a/src/admin/pages/Warehouse.tsx +++ b/src/admin/pages/Warehouse.tsx @@ -1,32 +1,89 @@ -import { Link } from "react-router-dom"; +import { Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; +import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; import { warehouseStockStatusOptions, warehouseBelowMinimumOptions, warehouseReservationListOptions, warehouseMovementLogOptions, type WarehouseItem, - type WarehouseReservation, } from "../lib/queries/warehouse"; import { formatCurrency, formatDate } from "../utils/formatters"; -import useReducedMotion from "../hooks/useReducedMotion"; +import { + Button, + Card, + DataTable, + StatCard, + StatusChip, + EmptyState, + LoadingState, + PageHeader, + type DataColumn, + type StatCardColor, +} from "../ui"; const TYPE_LABEL: Record = { receipt: "Příjem", issue: "Výdej", }; -const TYPE_TONE: Record = { - receipt: "admin-badge-incoming", - issue: "admin-badge-outgoing", +// Movement type → StatusChip color (legacy: incoming=success, outgoing=info) +const TYPE_COLOR: Record = { + receipt: "success", + issue: "info", }; +const ReceiptIcon = ( + + + + +); + +const IssueIcon = ( + + + + +); + +interface BelowMinRow { + id: number; + name: string; + available_quantity: number; + min_quantity: number; +} + +interface MovementRow { + _idx: number; + date: string; + type: string; + document_number: string; + item_name: string; + quantity: number; + supplier_or_project: string; +} + export default function Warehouse() { const { hasPermission } = useAuth(); - const reducedMotion = useReducedMotion(); const { data: stockItems, isPending: stockPending } = useQuery( warehouseStockStatusOptions(), @@ -60,253 +117,270 @@ export default function Warehouse() { const isLoading = stockPending || belowMinPending || reservationsPending || movementsLoading; + const belowMinColor: StatCardColor = + belowMinimumCount > 0 ? "error" : "warning"; + + const belowMinColumns: DataColumn[] = [ + { + key: "name", + header: "Název", + width: "50%", + bold: true, + render: (item) => item.name, + }, + { + key: "available_quantity", + header: "K dispozici", + width: "25%", + mono: true, + render: (item) => String(item.available_quantity), + }, + { + key: "min_quantity", + header: "Min. množství", + width: "25%", + mono: true, + render: (item) => String(item.min_quantity), + }, + ]; + + const movementColumns: DataColumn[] = [ + { + key: "date", + header: "Datum", + width: "12%", + mono: true, + render: (row) => formatDate(row.date), + }, + { + key: "type", + header: "Typ", + width: "12%", + render: (row) => ( + + ), + }, + { + key: "document_number", + header: "Doklad", + width: "16%", + mono: true, + render: (row) => row.document_number || "—", + }, + { + key: "item_name", + header: "Položka", + width: "28%", + bold: true, + render: (row) => row.item_name, + }, + { + key: "quantity", + header: "Množství", + width: "12%", + mono: true, + render: (row) => String(row.quantity), + }, + { + key: "supplier_or_project", + header: "Dodavatel / Projekt", + width: "20%", + render: (row) => row.supplier_or_project || "—", + }, + ]; + return ( -
+ -
-

Sklad

-

Přehled skladových zásob

-
-
- {hasPermission("warehouse.operate") && ( - <> - - + {hasPermission("warehouse.operate") && ( + <> + + + + )} + {hasPermission("warehouse.manage") && ( + - Nový příjem - - + )} +
+ Položky + + +
+ } + /> {/* KPI cards */} -
- -
Celkem položek
-
- {isLoading ? "—" : totalItems} -
-
- - -
Hodnota zásob
-
- {isLoading ? "—" : formatCurrency(totalStockValue, "CZK")} -
-
- - 0 ? "danger" : "warning"}`} - initial={{ opacity: 0, y: 12 }} - animate={{ opacity: 1, y: 0 }} - transition={{ - duration: reducedMotion ? 0 : 0.25, - delay: reducedMotion ? 0 : 0.1, - }} - > -
Pod minimem
-
- {isLoading ? "—" : belowMinimumCount} -
-
- - -
Aktivní rezervace
-
- {isLoading ? "—" : activeReservationCount} -
-
-
+ + + + + + {/* Below minimum alert */} {belowMin.length > 0 && ( - -
-

- Položky pod minimem -

- + - Reporty → - -
-
-
- - - - - - - - - - {belowMin.map((item) => ( - - - - - - ))} - -
NázevK dispoziciMin. množství
{item.name} - {item.available_quantity ?? 0} - {item.min_quantity ?? 0}
-
-
-
+ + Položky pod minimem + + + + + columns={belowMinColumns} + rows={belowMin.map((item) => ({ + id: item.id, + name: item.name, + available_quantity: item.available_quantity ?? 0, + min_quantity: item.min_quantity ?? 0, + }))} + rowKey={(item) => item.id} + rowDanger={() => true} + /> + + )} {/* Recent movements */} - -
-

Poslední pohyby

- + - Vše → - -
-
+ Poslední pohyby + + {movementsLoading ? ( -
-
-
- ) : recentMovements.length === 0 ? ( -
Žádné nedávné pohyby
+ ) : ( -
- - - - - - - - - - - - - {recentMovements.map((row, i) => ( - - - - - - - - - ))} - -
DatumTypDokladPoložkaMnožstvíDodavatel / Projekt
{formatDate(row.date)} - - {TYPE_LABEL[row.type] ?? row.type} - - - {row.document_number || "—"} - {row.item_name}{row.quantity}{row.supplier_or_project || "—"}
-
+ + columns={movementColumns} + rows={recentMovements.map((row, i) => ({ ...row, _idx: i }))} + rowKey={(row) => row._idx} + empty={} + /> )} -
- -
+ + + ); }