import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; import ItemPicker from "../components/warehouse/ItemPicker"; import { formatDate, czechPlural } from "../utils/formatters"; import { warehouseReservationListOptions, type WarehouseReservation, } from "../lib/queries/warehouse"; import { useApiMutation } from "../lib/queries/mutations"; import { projectListOptions } from "../lib/queries/projects"; import { Button, Card, DataTable, Pagination, Modal, ConfirmDialog, Field, TextField, Select, StatusChip, PageHeader, PageEnter, FilterBar, EmptyState, LoadingState, type DataColumn, } from "../ui"; const PER_PAGE = 20; const STATUS_COLOR: Record< string, "success" | "error" | "warning" | "info" | "default" > = { ACTIVE: "success", FULFILLED: "info", CANCELLED: "error", }; const STATUS_LABEL: Record = { ACTIVE: "Aktivní", FULFILLED: "Splněna", CANCELLED: "Zrušena", }; const PlusIcon = ( ); const IssueIcon = ( ); const CancelIcon = ( ); interface ReservationForm { item_id: number | null; project_id: number | null; quantity: number; notes: string; } interface CreateReservationPayload { item_id: number; project_id: number; quantity: number; notes: string | null; } export default function WarehouseReservations() { const navigate = useNavigate(); const alert = useAlert(); const { hasPermission } = useAuth(); const [page, setPage] = useState(1); const [statusFilter, setStatusFilter] = useState(""); const [projectId, setProjectId] = useState(""); const [showCreateModal, setShowCreateModal] = useState(false); const [saving, setSaving] = useState(false); const [form, setForm] = useState({ item_id: null, project_id: null, quantity: 0, notes: "", }); const [cancelTarget, setCancelTarget] = useState( null, ); const [cancelling, setCancelling] = useState(false); const { data: projectsData } = useQuery(projectListOptions({ perPage: 100 })); const projects = (projectsData?.data as { id: number; project_number: string; name: string; }[]) ?? []; const { items, pagination, isPending, isFetching } = usePaginatedQuery( warehouseReservationListOptions({ page, perPage: PER_PAGE, status: statusFilter || undefined, project_id: projectId ? Number(projectId) : undefined, }), ); const createMutation = useApiMutation({ url: () => "/api/admin/warehouse/reservations", method: () => "POST", invalidate: ["warehouse"], onSuccess: () => { setShowCreateModal(false); setForm({ item_id: null, project_id: null, quantity: 0, notes: "" }); alert.success("Rezervace byla vytvořena"); }, }); const cancelMutation = useApiMutation({ url: (reservationId) => `/api/admin/warehouse/reservations/${reservationId}/cancel`, method: () => "POST", invalidate: ["warehouse"], onSuccess: () => { setCancelTarget(null); alert.success("Rezervace byla zrušena"); }, }); // All hooks above this line — permission gate is the last thing before render. if (!hasPermission("warehouse.view")) return ; const canOperate = hasPermission("warehouse.operate"); const resetFilters = () => { setStatusFilter(""); setProjectId(""); setPage(1); }; const hasActiveFilters = statusFilter || projectId; const handleCreate = async () => { if ( !form.item_id || !form.project_id || !Number.isFinite(form.quantity) || form.quantity <= 0 ) { alert.error("Vyplňte položku, projekt a množství"); return; } setSaving(true); try { await createMutation.mutateAsync({ item_id: form.item_id, project_id: form.project_id, quantity: form.quantity, notes: form.notes.trim() || null, }); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } finally { setSaving(false); } }; const handleCancel = async () => { if (!cancelTarget) return; setCancelling(true); try { await cancelMutation.mutateAsync(cancelTarget.id); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } finally { setCancelling(false); } }; if (isPending) { 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 ( setShowCreateModal(true)} > Nová rezervace ) : undefined } /> { setProjectId(val === "" ? "" : Number(val)); setPage(1); }} options={[ { value: "", label: "Všechny projekty" }, ...projects.map((p) => ({ value: String(p.id), label: `${p.project_number} – ${p.name}`, })), ]} /> )} {hasActiveFilters && ( )} columns={columns} rows={items} rowKey={(res) => res.id} empty={ hasActiveFilters ? ( ) : ( setShowCreateModal(true)} > Vytvořit první rezervaci ) : undefined } /> ) } /> {/* Create reservation modal */} setShowCreateModal(false)} onSubmit={handleCreate} title="Nová rezervace" submitText="Vytvořit rezervaci" loading={saving} > setForm((prev) => ({ ...prev, item_id: id }))} />