diff --git a/src/admin/pages/WarehouseReservations.tsx b/src/admin/pages/WarehouseReservations.tsx index 9bd064b..2ed8044 100644 --- a/src/admin/pages/WarehouseReservations.tsx +++ b/src/admin/pages/WarehouseReservations.tsx @@ -4,14 +4,11 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion, AnimatePresence } from "framer-motion"; -import Pagination from "../components/Pagination"; -import ConfirmModal from "../components/ConfirmModal"; -import FormModal from "../components/FormModal"; -import FormField from "../components/FormField"; -import ItemPicker from "../components/warehouse/ItemPicker"; -import useDebounce from "../hooks/useDebounce"; +import { motion } from "framer-motion"; +import Box from "@mui/material/Box"; +import IconButton from "@mui/material/IconButton"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; +import ItemPicker from "../components/warehouse/ItemPicker"; import apiFetch from "../utils/api"; import { formatDate, czechPlural } from "../utils/formatters"; @@ -20,13 +17,33 @@ import { type WarehouseReservation, } from "../lib/queries/warehouse"; import { projectListOptions } from "../lib/queries/projects"; +import { + Button, + Card, + DataTable, + Pagination, + Modal, + ConfirmDialog, + Field, + TextField, + Select, + StatusChip, + PageHeader, + FilterBar, + EmptyState, + LoadingState, + type DataColumn, +} from "../ui"; const PER_PAGE = 20; -const STATUS_BADGE: Record = { - ACTIVE: "admin-badge-active", - FULFILLED: "admin-badge-info", - CANCELLED: "admin-badge-danger", +const STATUS_COLOR: Record< + string, + "success" | "error" | "warning" | "info" | "default" +> = { + ACTIVE: "success", + FULFILLED: "info", + CANCELLED: "error", }; const STATUS_LABEL: Record = { @@ -35,6 +52,53 @@ const STATUS_LABEL: Record = { CANCELLED: "Zrušena", }; +const PlusIcon = ( + + + + +); + +const IssueIcon = ( + + + + +); + +const CancelIcon = ( + + + + + +); + interface ReservationForm { item_id: number | null; project_id: number | null; @@ -153,332 +217,270 @@ export default function WarehouseReservations() { }; if (isPending) { - return ( -
-
-
- ); + return ; } + const total = pagination?.total ?? items.length; + const subtitle = `${total} ${czechPlural(total, "rezervace", "rezervace", "rezervací")}`; + + const columns: DataColumn[] = [ + { + key: "item", + header: "Položka", + width: "22%", + bold: true, + render: (res) => res.item?.name || `ID: ${res.item_id}`, + }, + { + key: "project", + header: "Projekt", + width: "20%", + render: (res) => res.project?.name || "—", + }, + { + key: "quantity", + header: "Množství", + width: "10%", + mono: true, + render: (res) => String(Number(res.quantity)), + }, + { + key: "remaining_qty", + header: "Zbývá", + width: "10%", + mono: true, + render: (res) => String(Number(res.remaining_qty)), + }, + { + key: "status", + header: "Stav", + width: "11%", + render: (res) => ( + + ), + }, + { + key: "reserved_by", + header: "Rezervoval", + width: "15%", + render: (res) => + res.reserved_by_user + ? `${res.reserved_by_user.first_name} ${res.reserved_by_user.last_name}` + : "—", + }, + { + key: "created_at", + header: "Vytvořeno", + width: "12%", + mono: true, + render: (res) => formatDate(res.created_at), + }, + ...(canOperate + ? [ + { + key: "actions", + header: "Akce", + width: "10%", + align: "right" as const, + render: (res: WarehouseReservation) => + res.status === "ACTIVE" ? ( + + + navigate("/warehouse/issues/new", { + state: { + reservationId: res.id, + itemId: res.item_id, + projectId: res.project_id, + quantity: Number(res.remaining_qty), + itemName: res.item?.name, + }, + }) + } + aria-label="Vytvořit výdej" + title="Vytvořit výdej" + > + {IssueIcon} + + setCancelTarget(res)} + aria-label="Zrušit rezervaci" + title="Zrušit rezervaci" + > + {CancelIcon} + + + ) : null, + }, + ] + : []), + ]; + return ( -
+ -
-

Rezervace

-

- {pagination?.total ?? items.length}{" "} - {czechPlural( - pagination?.total ?? items.length, - "rezervace", - "rezervace", - "rezervací", - )} -

-
-
- {canOperate && ( - - )} -
+ Nová rezervace + + ) : undefined + } + />
- -
-
- { + setStatusFilter(val); + setPage(1); + }} + options={[ + { value: "", label: "Všechny stavy" }, + { value: "ACTIVE", label: "Aktivní" }, + { value: "FULFILLED", label: "Splněna" }, + { value: "CANCELLED", label: "Zrušena" }, + ]} + /> + + {projects.length > 0 && ( + + - {projects.length > 0 && ( - - )} - {hasActiveFilters && ( - - )} -
+ options={[ + { value: "", label: "Všechny projekty" }, + ...projects.map((p) => ({ + value: String(p.id), + label: `${p.project_number} – ${p.name}`, + })), + ]} + /> + + )} + {hasActiveFilters && ( + + )} + - - - {items.length === 0 && ( -
-
- - - - - - -
-

- {hasActiveFilters - ? "Žádné rezervace pro zadaný filtr." - : "Zatím nejsou žádné rezervace."} -

- {canOperate && !hasActiveFilters && ( - - )} -
- )} - {items.length > 0 && ( -
- - - - - - - - - - - {canOperate && } - - - - {items.map((res) => ( - - - - - - - - - {canOperate && ( - - )} - - ))} - -
PoložkaProjektMnožstvíZbýváStavRezervovalVytvořenoAkce
- {res.item?.name || `ID: ${res.item_id}`} - {res.project?.name || "—"}{Number(res.quantity)} - {Number(res.remaining_qty)} - - - {STATUS_LABEL[res.status] ?? res.status} - - - {res.reserved_by_user - ? `${res.reserved_by_user.first_name} ${res.reserved_by_user.last_name}` - : "—"} - - {formatDate(res.created_at)} - -
- {res.status === "ACTIVE" && ( - - )} - {res.status === "ACTIVE" && ( - - )} -
-
-
- )} -
-
- - -
-
+ + ) : undefined + } + /> + ) + } + /> + + {/* Create reservation modal */} - setShowCreateModal(false)} onSubmit={handleCreate} title="Nová rezervace" - submitLabel="Vytvořit rezervaci" + submitText="Vytvořit rezervaci" loading={saving} > -
- - setForm((prev) => ({ ...prev, item_id: id }))} - /> - - - - - - - setForm((prev) => ({ - ...prev, - quantity: Number(e.target.value), - })) - } - className="admin-form-input" - min="0" - step="0.001" - /> - - -