feat(mui): migrate Warehouse overview + replace residual admin-loading loaders with LoadingState
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
|||||||
FilterBar,
|
FilterBar,
|
||||||
Tabs,
|
Tabs,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
|
LoadingState,
|
||||||
type DataColumn,
|
type DataColumn,
|
||||||
type TabDef,
|
type TabDef,
|
||||||
} from "../ui";
|
} from "../ui";
|
||||||
@@ -450,11 +451,7 @@ export default function Offers() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return <LoadingState />;
|
||||||
<div className="admin-loading">
|
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = pagination?.total ?? quotations.length;
|
const total = pagination?.total ?? quotations.length;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import PlanGrid from "../components/PlanGrid";
|
|||||||
import PlanCellModal from "../components/PlanCellModal";
|
import PlanCellModal from "../components/PlanCellModal";
|
||||||
import PlanCategoriesModal from "../components/PlanCategoriesModal";
|
import PlanCategoriesModal from "../components/PlanCategoriesModal";
|
||||||
import Forbidden from "../components/Forbidden";
|
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 { projectListOptions, type Project } from "../lib/queries/projects";
|
||||||
import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan";
|
import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan";
|
||||||
// plan.css is imported once globally in AdminApp.tsx — no per-page re-import.
|
// plan.css is imported once globally in AdminApp.tsx — no per-page re-import.
|
||||||
@@ -670,11 +670,7 @@ export default function PlanWork() {
|
|||||||
</Box>
|
</Box>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{gridLoading && (
|
{gridLoading && <LoadingState />}
|
||||||
<div className="admin-loading">
|
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
Grid entrance: same pattern as the h1 / banner / toolbar above
|
Grid entrance: same pattern as the h1 / banner / toolbar above
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
DateField,
|
DateField,
|
||||||
StatusChip,
|
StatusChip,
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
|
LoadingState,
|
||||||
type DataColumn,
|
type DataColumn,
|
||||||
} from "../ui";
|
} from "../ui";
|
||||||
|
|
||||||
@@ -234,11 +235,7 @@ export default function Projects() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return <LoadingState />;
|
||||||
<div className="admin-loading">
|
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: DataColumn<Project>[] = [
|
const columns: DataColumn<Project>[] = [
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
SwitchField,
|
SwitchField,
|
||||||
Select,
|
Select,
|
||||||
StatusChip,
|
StatusChip,
|
||||||
|
LoadingState,
|
||||||
type DataColumn,
|
type DataColumn,
|
||||||
} from "../ui";
|
} from "../ui";
|
||||||
|
|
||||||
@@ -247,11 +248,7 @@ export default function Users() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return <LoadingState />;
|
||||||
<div className="admin-loading">
|
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: DataColumn<User>[] = [
|
const columns: DataColumn<User>[] = [
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
Field,
|
Field,
|
||||||
TextField,
|
TextField,
|
||||||
SwitchField,
|
SwitchField,
|
||||||
|
LoadingState,
|
||||||
type DataColumn,
|
type DataColumn,
|
||||||
} from "../ui";
|
} from "../ui";
|
||||||
|
|
||||||
@@ -222,11 +223,7 @@ export default function Vehicles() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return <LoadingState />;
|
||||||
<div className="admin-loading">
|
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: DataColumn<Vehicle>[] = [
|
const columns: DataColumn<Vehicle>[] = [
|
||||||
|
|||||||
@@ -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 { 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 { useAuth } from "../context/AuthContext";
|
||||||
import Forbidden from "../components/Forbidden";
|
import Forbidden from "../components/Forbidden";
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import {
|
import {
|
||||||
warehouseStockStatusOptions,
|
warehouseStockStatusOptions,
|
||||||
warehouseBelowMinimumOptions,
|
warehouseBelowMinimumOptions,
|
||||||
warehouseReservationListOptions,
|
warehouseReservationListOptions,
|
||||||
warehouseMovementLogOptions,
|
warehouseMovementLogOptions,
|
||||||
type WarehouseItem,
|
type WarehouseItem,
|
||||||
type WarehouseReservation,
|
|
||||||
} from "../lib/queries/warehouse";
|
} from "../lib/queries/warehouse";
|
||||||
import { formatCurrency, formatDate } from "../utils/formatters";
|
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<string, string> = {
|
const TYPE_LABEL: Record<string, string> = {
|
||||||
receipt: "Příjem",
|
receipt: "Příjem",
|
||||||
issue: "Výdej",
|
issue: "Výdej",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TYPE_TONE: Record<string, string> = {
|
// Movement type → StatusChip color (legacy: incoming=success, outgoing=info)
|
||||||
receipt: "admin-badge-incoming",
|
const TYPE_COLOR: Record<string, "success" | "info"> = {
|
||||||
issue: "admin-badge-outgoing",
|
receipt: "success",
|
||||||
|
issue: "info",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ReceiptIcon = (
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<polyline points="17 11 12 6 7 11" />
|
||||||
|
<line x1="12" y1="6" x2="12" y2="18" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const IssueIcon = (
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
<polyline points="7 13 12 18 17 13" />
|
||||||
|
<line x1="12" y1="18" x2="12" y2="6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
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() {
|
export default function Warehouse() {
|
||||||
const { hasPermission } = useAuth();
|
const { hasPermission } = useAuth();
|
||||||
const reducedMotion = useReducedMotion();
|
|
||||||
|
|
||||||
const { data: stockItems, isPending: stockPending } = useQuery(
|
const { data: stockItems, isPending: stockPending } = useQuery(
|
||||||
warehouseStockStatusOptions(),
|
warehouseStockStatusOptions(),
|
||||||
@@ -60,253 +117,270 @@ export default function Warehouse() {
|
|||||||
const isLoading =
|
const isLoading =
|
||||||
stockPending || belowMinPending || reservationsPending || movementsLoading;
|
stockPending || belowMinPending || reservationsPending || movementsLoading;
|
||||||
|
|
||||||
|
const belowMinColor: StatCardColor =
|
||||||
|
belowMinimumCount > 0 ? "error" : "warning";
|
||||||
|
|
||||||
|
const belowMinColumns: DataColumn<BelowMinRow>[] = [
|
||||||
|
{
|
||||||
|
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<MovementRow>[] = [
|
||||||
|
{
|
||||||
|
key: "date",
|
||||||
|
header: "Datum",
|
||||||
|
width: "12%",
|
||||||
|
mono: true,
|
||||||
|
render: (row) => formatDate(row.date),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "type",
|
||||||
|
header: "Typ",
|
||||||
|
width: "12%",
|
||||||
|
render: (row) => (
|
||||||
|
<StatusChip
|
||||||
|
label={TYPE_LABEL[row.type] ?? row.type}
|
||||||
|
color={TYPE_COLOR[row.type] ?? "info"}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="admin-page-header"
|
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.25 }}
|
transition={{ duration: 0.25 }}
|
||||||
>
|
>
|
||||||
<div>
|
<PageHeader
|
||||||
<h1 className="admin-page-title">Sklad</h1>
|
title="Sklad"
|
||||||
<p className="admin-page-subtitle">Přehled skladových zásob</p>
|
subtitle="Přehled skladových zásob"
|
||||||
</div>
|
actions={
|
||||||
<div className="admin-page-actions">
|
<Box sx={{ display: "flex", gap: 1.5, flexWrap: "wrap" }}>
|
||||||
{hasPermission("warehouse.operate") && (
|
{hasPermission("warehouse.operate") && (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Button
|
||||||
to="/warehouse/receipts/new"
|
component={RouterLink}
|
||||||
className="admin-btn admin-btn-primary"
|
to="/warehouse/receipts/new"
|
||||||
>
|
startIcon={ReceiptIcon}
|
||||||
<svg
|
>
|
||||||
width="18"
|
Nový příjem
|
||||||
height="18"
|
</Button>
|
||||||
viewBox="0 0 24 24"
|
<Button
|
||||||
fill="none"
|
component={RouterLink}
|
||||||
stroke="currentColor"
|
to="/warehouse/issues/new"
|
||||||
strokeWidth="2"
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
startIcon={IssueIcon}
|
||||||
|
>
|
||||||
|
Nový výdej
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{hasPermission("warehouse.manage") && (
|
||||||
|
<Button
|
||||||
|
component={RouterLink}
|
||||||
|
to="/warehouse/categories"
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
>
|
>
|
||||||
<polyline points="17 11 12 6 7 11" />
|
Kategorie
|
||||||
<line x1="12" y1="6" x2="12" y2="18" />
|
</Button>
|
||||||
</svg>
|
)}
|
||||||
Nový příjem
|
<Button
|
||||||
</Link>
|
component={RouterLink}
|
||||||
<Link
|
to="/warehouse/items"
|
||||||
to="/warehouse/issues/new"
|
variant="outlined"
|
||||||
className="admin-btn admin-btn-secondary"
|
color="inherit"
|
||||||
>
|
>
|
||||||
<svg
|
Položky
|
||||||
width="18"
|
</Button>
|
||||||
height="18"
|
<Button
|
||||||
viewBox="0 0 24 24"
|
component={RouterLink}
|
||||||
fill="none"
|
to="/warehouse/reports"
|
||||||
stroke="currentColor"
|
variant="outlined"
|
||||||
strokeWidth="2"
|
color="inherit"
|
||||||
>
|
>
|
||||||
<polyline points="7 13 12 18 17 13" />
|
Reporty
|
||||||
<line x1="12" y1="18" x2="12" y2="6" />
|
</Button>
|
||||||
</svg>
|
</Box>
|
||||||
Nový výdej
|
}
|
||||||
</Link>
|
/>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{hasPermission("warehouse.manage") && (
|
|
||||||
<Link
|
|
||||||
to="/warehouse/categories"
|
|
||||||
className="admin-btn admin-btn-secondary"
|
|
||||||
>
|
|
||||||
Kategorie
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Link to="/warehouse/items" className="admin-btn admin-btn-secondary">
|
|
||||||
Položky
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/warehouse/reports"
|
|
||||||
className="admin-btn admin-btn-secondary"
|
|
||||||
>
|
|
||||||
Reporty
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* KPI cards */}
|
{/* KPI cards */}
|
||||||
<div className="admin-kpi-grid admin-kpi-4">
|
<Box
|
||||||
<motion.div
|
component={motion.div}
|
||||||
className="admin-stat-card info"
|
initial={{ opacity: 0, y: 12 }}
|
||||||
initial={{ opacity: 0, y: 12 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
transition={{ duration: 0.25, delay: 0.06 }}
|
||||||
transition={{
|
sx={{
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
display: "grid",
|
||||||
delay: reducedMotion ? 0 : 0.06,
|
gridTemplateColumns: {
|
||||||
}}
|
xs: "1fr",
|
||||||
>
|
sm: "repeat(2, 1fr)",
|
||||||
<div className="admin-stat-label">Celkem položek</div>
|
lg: "repeat(4, 1fr)",
|
||||||
<div className="admin-stat-value admin-mono">
|
},
|
||||||
{isLoading ? "—" : totalItems}
|
gap: 2,
|
||||||
</div>
|
mb: 3,
|
||||||
</motion.div>
|
}}
|
||||||
|
>
|
||||||
<motion.div
|
<StatCard
|
||||||
className="admin-stat-card success"
|
color="info"
|
||||||
initial={{ opacity: 0, y: 12 }}
|
label="Celkem položek"
|
||||||
animate={{ opacity: 1, y: 0 }}
|
value={isLoading ? "—" : totalItems}
|
||||||
transition={{
|
/>
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
<StatCard
|
||||||
delay: reducedMotion ? 0 : 0.08,
|
color="success"
|
||||||
}}
|
label="Hodnota zásob"
|
||||||
>
|
value={isLoading ? "—" : formatCurrency(totalStockValue, "CZK")}
|
||||||
<div className="admin-stat-label">Hodnota zásob</div>
|
/>
|
||||||
<div className="admin-stat-value admin-mono">
|
<StatCard
|
||||||
{isLoading ? "—" : formatCurrency(totalStockValue, "CZK")}
|
color={belowMinColor}
|
||||||
</div>
|
label="Pod minimem"
|
||||||
</motion.div>
|
value={isLoading ? "—" : belowMinimumCount}
|
||||||
|
/>
|
||||||
<motion.div
|
<StatCard
|
||||||
className={`admin-stat-card ${belowMinimumCount > 0 ? "danger" : "warning"}`}
|
color="info"
|
||||||
initial={{ opacity: 0, y: 12 }}
|
label="Aktivní rezervace"
|
||||||
animate={{ opacity: 1, y: 0 }}
|
value={isLoading ? "—" : activeReservationCount}
|
||||||
transition={{
|
/>
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
</Box>
|
||||||
delay: reducedMotion ? 0 : 0.1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="admin-stat-label">Pod minimem</div>
|
|
||||||
<div className="admin-stat-value admin-mono">
|
|
||||||
{isLoading ? "—" : belowMinimumCount}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="admin-stat-card info"
|
|
||||||
initial={{ opacity: 0, y: 12 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{
|
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
|
||||||
delay: reducedMotion ? 0 : 0.12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="admin-stat-label">Aktivní rezervace</div>
|
|
||||||
<div className="admin-stat-value admin-mono">
|
|
||||||
{isLoading ? "—" : activeReservationCount}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Below minimum alert */}
|
{/* Below minimum alert */}
|
||||||
{belowMin.length > 0 && (
|
{belowMin.length > 0 && (
|
||||||
<motion.div
|
<Box
|
||||||
className="admin-card admin-card-danger"
|
component={motion.div}
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{
|
transition={{ duration: 0.25, delay: 0.14 }}
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
sx={{ mb: 3 }}
|
||||||
delay: reducedMotion ? 0 : 0.14,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="admin-card-header flex-between">
|
<Card sx={{ borderColor: "error.main", borderWidth: 1 }}>
|
||||||
<h2 className="admin-card-title" style={{ color: "var(--danger)" }}>
|
<Box
|
||||||
Položky pod minimem
|
sx={{
|
||||||
</h2>
|
display: "flex",
|
||||||
<Link
|
alignItems: "center",
|
||||||
to="/warehouse/reports"
|
justifyContent: "space-between",
|
||||||
className="admin-btn admin-btn-primary admin-btn-sm"
|
gap: 2,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reporty →
|
<Typography variant="h6" sx={{ color: "error.main" }}>
|
||||||
</Link>
|
Položky pod minimem
|
||||||
</div>
|
</Typography>
|
||||||
<div className="admin-card-body">
|
<Button
|
||||||
<div className="admin-table-responsive">
|
component={RouterLink}
|
||||||
<table className="admin-table">
|
to="/warehouse/reports"
|
||||||
<thead>
|
size="small"
|
||||||
<tr>
|
sx={{ flexShrink: 0 }}
|
||||||
<th>Název</th>
|
>
|
||||||
<th>K dispozici</th>
|
Reporty →
|
||||||
<th>Min. množství</th>
|
</Button>
|
||||||
</tr>
|
</Box>
|
||||||
</thead>
|
<DataTable<BelowMinRow>
|
||||||
<tbody>
|
columns={belowMinColumns}
|
||||||
{belowMin.map((item) => (
|
rows={belowMin.map((item) => ({
|
||||||
<tr key={item.id} className="admin-warehouse-row-danger">
|
id: item.id,
|
||||||
<td className="fw-500">{item.name}</td>
|
name: item.name,
|
||||||
<td className="admin-mono admin-warehouse-danger-value">
|
available_quantity: item.available_quantity ?? 0,
|
||||||
{item.available_quantity ?? 0}
|
min_quantity: item.min_quantity ?? 0,
|
||||||
</td>
|
}))}
|
||||||
<td className="admin-mono">{item.min_quantity ?? 0}</td>
|
rowKey={(item) => item.id}
|
||||||
</tr>
|
rowDanger={() => true}
|
||||||
))}
|
/>
|
||||||
</tbody>
|
</Card>
|
||||||
</table>
|
</Box>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Recent movements */}
|
{/* Recent movements */}
|
||||||
<motion.div
|
<Box
|
||||||
className="admin-card"
|
component={motion.div}
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{
|
transition={{ duration: 0.25, delay: 0.16 }}
|
||||||
duration: reducedMotion ? 0 : 0.25,
|
|
||||||
delay: reducedMotion ? 0 : 0.16,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="admin-card-header flex-between">
|
<Card>
|
||||||
<h2 className="admin-card-title">Poslední pohyby</h2>
|
<Box
|
||||||
<Link
|
sx={{
|
||||||
to="/warehouse/reports"
|
display: "flex",
|
||||||
className="admin-btn admin-btn-primary admin-btn-sm"
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 2,
|
||||||
|
flexWrap: "wrap",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Vše →
|
<Typography variant="h6">Poslední pohyby</Typography>
|
||||||
</Link>
|
<Button
|
||||||
</div>
|
component={RouterLink}
|
||||||
<div className="admin-card-body">
|
to="/warehouse/reports"
|
||||||
|
size="small"
|
||||||
|
sx={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
Vše →
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
{movementsLoading ? (
|
{movementsLoading ? (
|
||||||
<div className="admin-loading">
|
<LoadingState />
|
||||||
<div className="admin-spinner" />
|
|
||||||
</div>
|
|
||||||
) : recentMovements.length === 0 ? (
|
|
||||||
<div className="admin-empty-state">Žádné nedávné pohyby</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="admin-table-responsive">
|
<DataTable<MovementRow>
|
||||||
<table className="admin-table">
|
columns={movementColumns}
|
||||||
<thead>
|
rows={recentMovements.map((row, i) => ({ ...row, _idx: i }))}
|
||||||
<tr>
|
rowKey={(row) => row._idx}
|
||||||
<th>Datum</th>
|
empty={<EmptyState title="Žádné nedávné pohyby" />}
|
||||||
<th>Typ</th>
|
/>
|
||||||
<th>Doklad</th>
|
|
||||||
<th>Položka</th>
|
|
||||||
<th>Množství</th>
|
|
||||||
<th>Dodavatel / Projekt</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{recentMovements.map((row, i) => (
|
|
||||||
<tr key={i}>
|
|
||||||
<td className="admin-mono">{formatDate(row.date)}</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
className={`admin-badge ${TYPE_TONE[row.type] ?? ""}`}
|
|
||||||
>
|
|
||||||
{TYPE_LABEL[row.type] ?? row.type}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="admin-mono">
|
|
||||||
{row.document_number || "—"}
|
|
||||||
</td>
|
|
||||||
<td className="fw-500">{row.item_name}</td>
|
|
||||||
<td className="admin-mono">{row.quantity}</td>
|
|
||||||
<td>{row.supplier_or_project || "—"}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Card>
|
||||||
</motion.div>
|
</Box>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user