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") && (
- <>
-
-
- 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 →
-
-
-
-
-
-
-
- | Název |
- K dispozici |
- Min. množství |
-
-
-
- {belowMin.map((item) => (
-
- | {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
+
) : (
-
-
-
-
- | Datum |
- Typ |
- Doklad |
- Položka |
- Množství |
- Dodavatel / Projekt |
-
-
-
- {recentMovements.map((row, i) => (
-
- | {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={}
+ />
)}
-
-
-
+
+
+
);
}