import { useState } from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import useDebounce from "../hooks/useDebounce"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; import { warehouseSupplierListOptions, type WarehouseSupplier, } from "../lib/queries/warehouse"; import { useApiMutation } from "../lib/queries/mutations"; import { Button, Card, DataTable, Pagination, Modal, ConfirmDialog, Field, TextField, StatusChip, PageHeader, PageEnter, FilterBar, EmptyState, LoadingState, type DataColumn, } from "../ui"; const API_BASE = "/api/admin/warehouse/suppliers"; interface SupplierForm { name: string; ico: string; dic: string; contact_person: string; email: string; phone: string; address: string; notes: string; } const PER_PAGE = 20; const PlusIcon = ( ); const EditIcon = ( ); const DeleteIcon = ( ); export default function WarehouseSuppliers() { const alert = useAlert(); const { hasPermission } = useAuth(); const [page, setPage] = useState(1); const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 300); const { items: suppliers, pagination, isPending, isFetching, } = usePaginatedQuery( warehouseSupplierListOptions({ search: debouncedSearch || undefined, page, perPage: PER_PAGE, }), ); const [showModal, setShowModal] = useState(false); const [editingSupplier, setEditingSupplier] = useState(null); const [form, setForm] = useState({ name: "", ico: "", dic: "", contact_person: "", email: "", phone: "", address: "", notes: "", }); const [errors, setErrors] = useState>({}); const [deactivateConfirm, setDeactivateConfirm] = useState<{ show: boolean; supplier: WarehouseSupplier | null; }>({ show: false, supplier: null }); const submitMutation = useApiMutation< SupplierForm, { id?: number; message?: string } >({ url: () => editingSupplier ? `${API_BASE}/${editingSupplier.id}` : API_BASE, method: () => (editingSupplier ? "PUT" : "POST"), invalidate: ["warehouse"], onSuccess: (data) => { setShowModal(false); alert.success(data?.message || "Dodavatel byl uložen"); }, }); const deleteMutation = useApiMutation({ url: (id) => `${API_BASE}/${id}`, method: () => "DELETE", invalidate: ["warehouse"], onSuccess: (data) => { setDeactivateConfirm({ show: false, supplier: null }); alert.success(data?.message || "Dodavatel byl smazán"); }, }); // id is captured from the mutation variable (built into the URL), so it stays // correct even when called immediately after a state update. The non-strict // UpdateSupplierSchema strips the `id` field from the body. const toggleActiveMutation = useApiMutation< { id: number; is_active: boolean }, { message?: string } >({ url: ({ id }) => `${API_BASE}/${id}`, method: () => "PUT", invalidate: ["warehouse"], }); if (!hasPermission("warehouse.manage")) return ; const openCreateModal = () => { setEditingSupplier(null); setForm({ name: "", ico: "", dic: "", contact_person: "", email: "", phone: "", address: "", notes: "", }); setErrors({}); setShowModal(true); }; const openEditModal = (supplier: WarehouseSupplier) => { setEditingSupplier(supplier); setForm({ name: supplier.name, ico: supplier.ico || "", dic: supplier.dic || "", contact_person: supplier.contact_person || "", email: supplier.email || "", phone: supplier.phone || "", address: supplier.address || "", notes: supplier.notes || "", }); setErrors({}); setShowModal(true); }; const handleSubmit = async () => { const newErrors: Record = {}; if (!form.name.trim()) newErrors.name = "Zadejte název dodavatele"; setErrors(newErrors); if (Object.keys(newErrors).length > 0) return; try { await submitMutation.mutateAsync(form); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } }; const handleDeactivate = async () => { if (!deactivateConfirm.supplier) return; try { await deleteMutation.mutateAsync(deactivateConfirm.supplier.id); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } }; const toggleActive = async (supplier: WarehouseSupplier) => { try { await toggleActiveMutation.mutateAsync({ id: supplier.id, is_active: !supplier.is_active, }); alert.success( supplier.is_active ? "Dodavatel byl deaktivován" : "Dodavatel byl aktivován", ); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } }; if (isPending) { return ; } const total = pagination?.total ?? suppliers.length; const subtitle = `${total} ${ total === 1 ? "dodavatel" : total >= 2 && total <= 4 ? "dodavatelé" : "dodavatelů" }`; const columns: DataColumn[] = [ { key: "name", header: "Název", width: "20%", bold: true, render: (s) => s.name, }, { key: "ico", header: "IČO", width: "11%", mono: true, render: (s) => s.ico || "—", }, { key: "dic", header: "DIČ", width: "11%", mono: true, render: (s) => s.dic || "—", }, { key: "contact_person", header: "Kontaktní osoba", width: "16%", render: (s) => s.contact_person || "—", }, { key: "email", header: "E-mail", width: "16%", render: (s) => s.email || "—", }, { key: "phone", header: "Telefon", width: "12%", mono: true, render: (s) => s.phone || "—", }, { key: "status", header: "Stav", width: "8%", render: (s) => ( toggleActive(s)} /> ), }, { key: "actions", header: "Akce", width: "10%", align: "right", render: (s) => ( openEditModal(s)} aria-label="Upravit" title="Upravit" > {EditIcon} setDeactivateConfirm({ show: true, supplier: s })} aria-label={s.is_active ? "Deaktivovat" : "Smazat"} title={s.is_active ? "Deaktivovat" : "Smazat"} > {DeleteIcon} ), }, ]; return ( Přidat dodavatele } /> { setSearch(e.target.value); setPage(1); }} placeholder="Hledat dodavatele..." fullWidth /> columns={columns} rows={suppliers} rowKey={(s) => s.id} rowInactive={(s) => !s.is_active} empty={ search ? ( ) : ( Přidat prvního dodavatele } /> ) } /> {/* Add/Edit Modal */} setShowModal(false)} onSubmit={handleSubmit} title={editingSupplier ? "Upravit dodavatele" : "Přidat dodavatele"} loading={submitMutation.isPending} > { setForm({ ...form, name: e.target.value }); setErrors((prev) => ({ ...prev, name: "" })); }} placeholder="Název dodavatele" /> setForm({ ...form, ico: e.target.value })} placeholder="12345678" /> setForm({ ...form, dic: e.target.value })} placeholder="CZ12345678" /> setForm({ ...form, contact_person: e.target.value }) } placeholder="Jan Novák" /> setForm({ ...form, email: e.target.value })} placeholder="info@firma.cz" /> setForm({ ...form, phone: e.target.value })} placeholder="+420 123 456 789" /> setForm({ ...form, address: e.target.value })} placeholder="Ulice, město, PSČ" /> setForm({ ...form, notes: e.target.value })} placeholder="Volitelné poznámky" /> {/* Deactivate/Delete Confirmation */} setDeactivateConfirm({ show: false, supplier: null })} onConfirm={handleDeactivate} title={ deactivateConfirm.supplier?.is_active ? "Deaktivovat dodavatele" : "Smazat dodavatele" } message={ deactivateConfirm.supplier ? deactivateConfirm.supplier.is_active ? `Opravdu chcete deaktivovat dodavatele "${deactivateConfirm.supplier.name}"?` : `Opravdu chcete smazat dodavatele "${deactivateConfirm.supplier.name}"? Tato akce je nevratná.` : "" } confirmText={ deactivateConfirm.supplier?.is_active ? "Deaktivovat" : "Smazat" } confirmVariant="danger" loading={deleteMutation.isPending} /> ); }