import { useState, useRef } 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 AresAdornment, { type AresCompany } from "../components/AresLookup";
import useDebounce from "../hooks/useDebounce";
import { usePaginatedQuery } from "../hooks/usePaginatedQuery";
import {
warehouseSupplierListOptions,
type WarehouseSupplier,
type WarehouseSupplierCustomField,
} from "../lib/queries/warehouse";
import { useApiMutation } from "../lib/queries/mutations";
import {
Button,
Card,
DataTable,
Pagination,
Modal,
ConfirmDialog,
Field,
TextField,
CheckboxField,
StatusChip,
PageHeader,
PageEnter,
FilterBar,
EmptyState,
LoadingState,
type DataColumn,
} from "../ui";
const API_BASE = "/api/admin/warehouse/suppliers";
// Mirror of the customers modal (minus the PDF field-order picker): the same
// company fields + "Vlastní pole"; contact person/e-mail/phone live as custom
// fields since the dedicated columns were dropped.
interface SupplierForm {
name: string;
ico: string;
dic: string;
street: string;
city: string;
postal_code: string;
country: string;
}
const EMPTY_SUPPLIER_FORM: SupplierForm = {
name: "",
ico: "",
dic: "",
street: "",
city: "",
postal_code: "",
country: "",
};
const PER_PAGE = 20;
const PlusIcon = (
);
const EditIcon = (
);
const DeleteIcon = (
);
const SmallPlusIcon = (
);
const RemoveIcon = (
);
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({ ...EMPTY_SUPPLIER_FORM });
const [customFields, setCustomFields] = useState<
WarehouseSupplierCustomField[]
>([]);
const customFieldKeyCounter = useRef(0);
const [errors, setErrors] = useState>({});
const [deactivateConfirm, setDeactivateConfirm] = useState<{
show: boolean;
supplier: WarehouseSupplier | null;
}>({ show: false, supplier: null });
const submitMutation = useApiMutation<
SupplierForm & {
custom_fields: Array<{
name: string;
value: string;
showLabel?: boolean;
}>;
},
{ id?: number; message?: string }
>({
url: () =>
editingSupplier ? `${API_BASE}/${editingSupplier.id}` : API_BASE,
method: () => (editingSupplier ? "PUT" : "POST"),
// issued-orders included: the PO form's supplier picker caches under
// ["issued-orders","suppliers"] and must see supplier CRUD immediately.
invalidate: ["warehouse", "issued-orders"],
onSuccess: (data) => {
setShowModal(false);
alert.success(data?.message || "Dodavatel byl uložen");
},
});
const deleteMutation = useApiMutation({
url: (id) => `${API_BASE}/${id}`,
method: () => "DELETE",
// issued-orders included: the PO form's supplier picker caches under
// ["issued-orders","suppliers"] and must see supplier CRUD immediately.
invalidate: ["warehouse", "issued-orders"],
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",
// issued-orders included: the PO form's supplier picker caches under
// ["issued-orders","suppliers"] and must see supplier CRUD immediately.
invalidate: ["warehouse", "issued-orders"],
});
if (!hasPermission("warehouse.manage")) return ;
// ARES prefill — overwrite the company fields with registry data (the
// button is an explicit user action, so overwriting is the expected UX).
const fillFromAres = (c: AresCompany) => {
setForm((prev) => ({
...prev,
name: c.name || prev.name,
ico: c.ico,
dic: c.dic || "",
street: c.street,
city: c.city,
postal_code: c.postal_code,
country: c.country,
}));
setErrors((prev) => ({ ...prev, name: "" }));
};
const openCreateModal = () => {
setEditingSupplier(null);
setForm({ ...EMPTY_SUPPLIER_FORM });
setCustomFields([]);
setErrors({});
setShowModal(true);
};
const openEditModal = (supplier: WarehouseSupplier) => {
setEditingSupplier(supplier);
setForm({
name: supplier.name,
ico: supplier.ico || "",
dic: supplier.dic || "",
street: supplier.street || "",
city: supplier.city || "",
postal_code: supplier.postal_code || "",
country: supplier.country || "",
});
setCustomFields(
Array.isArray(supplier.custom_fields) && supplier.custom_fields.length > 0
? supplier.custom_fields.map((f) => ({
...f,
_key: `cf-${++customFieldKeyCounter.current}`,
}))
: [],
);
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,
custom_fields: customFields
.filter((f) => f.name.trim() || f.value.trim())
.map((f) => ({
name: f.name,
value: f.value,
showLabel: f.showLabel,
})),
});
} 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: "street",
header: "Ulice",
width: "18%",
render: (s) => s.street || "—",
},
{
key: "city",
header: "Město",
width: "14%",
render: (s) => s.city || "—",
},
{
key: "status",
header: "Stav",
width: "8%",
render: (s) => (
toggleActive(s)}
title="Kliknutím přepnete stav"
/>
),
},
{
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 — mirror of the customers modal (minus the PDF
field-order picker) */}
setShowModal(false)}
onSubmit={handleSubmit}
title={editingSupplier ? "Upravit dodavatele" : "Přidat dodavatele"}
loading={submitMutation.isPending}
maxWidth="md"
>
{
setForm({ ...form, name: e.target.value });
setErrors((prev) => ({ ...prev, name: "" }));
}}
placeholder="Název firmy / jméno"
InputProps={{
endAdornment: (
),
}}
/>
setForm({ ...form, street: e.target.value })}
/>
setForm({ ...form, city: e.target.value })}
/>
setForm({ ...form, postal_code: e.target.value })
}
/>
setForm({ ...form, country: e.target.value })}
/>
setForm({ ...form, ico: e.target.value })}
InputProps={{
endAdornment: (
),
}}
/>
setForm({ ...form, dic: e.target.value })}
/>
{/* Dynamic custom fields — same editor as the customers modal */}
Vlastní pole
{customFields.map((field, idx) => (
{
const updated = [...customFields];
updated[idx] = { ...updated[idx], name: e.target.value };
setCustomFields(updated);
}}
placeholder="Např. Kontakt"
/>
{
const updated = [...customFields];
updated[idx] = {
...updated[idx],
value: e.target.value,
};
setCustomFields(updated);
}}
sx={{ flex: 1 }}
/>
setCustomFields(customFields.filter((_, i) => i !== idx))
}
title="Odebrat pole"
aria-label="Odebrat pole"
>
{RemoveIcon}
Zobrazit název v PDF
}
checked={field.showLabel !== false}
onChange={(checked) => {
const updated = [...customFields];
updated[idx] = { ...updated[idx], showLabel: checked };
setCustomFields(updated);
}}
/>
))}
{/* 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}
/>
);
}