diff --git a/src/admin/pages/OffersCustomers.tsx b/src/admin/pages/OffersCustomers.tsx index dcc3653..433e736 100644 --- a/src/admin/pages/OffersCustomers.tsx +++ b/src/admin/pages/OffersCustomers.tsx @@ -1,11 +1,11 @@ import { useState, useCallback, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; +import { motion } from "framer-motion"; +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 { motion } from "framer-motion"; -import ConfirmModal from "../components/ConfirmModal"; -import FormModal from "../components/FormModal"; -import FormField from "../components/FormField"; import Forbidden from "../components/Forbidden"; import { offerCustomersOptions, @@ -13,6 +13,22 @@ import { type CustomField, } from "../lib/queries/offers"; import { useApiMutation } from "../lib/queries/mutations"; +import { + Button, + Card, + DataTable, + Modal, + ConfirmDialog, + Field, + TextField, + CheckboxField, + StatusChip, + PageHeader, + FilterBar, + EmptyState, + LoadingState, + type DataColumn, +} from "../ui"; const API_BASE = "/api/admin"; @@ -42,6 +58,100 @@ interface CustomerForm { vat_id: string; } +const PlusIcon = ( + + + + +); +const SmallPlusIcon = ( + + + + +); +const EditIcon = ( + + + + +); +const DeleteIcon = ( + + + + +); +const RemoveIcon = ( + + + + +); +const UpIcon = ( + + + +); +const DownIcon = ( + + + +); + export default function OffersCustomers() { const alert = useAlert(); const { hasPermission } = useAuth(); @@ -261,489 +371,441 @@ export default function OffersCustomers() { const fullFieldOrder = getFullFieldOrder(); if (isPending) { - return ( -
-
-
- ); + return ; } + + const columns: DataColumn[] = [ + { + key: "name", + header: "Název", + width: "26%", + render: (c) => ( + + {c.name} + {c.street && ( + + {c.street} + + )} + + ), + }, + { + key: "city", + header: "Město", + width: "18%", + render: (c) => c.city || "—", + }, + { + key: "company_id", + header: "IČO", + width: "13%", + mono: true, + render: (c) => c.company_id || "—", + }, + { + key: "vat_id", + header: "DIČ", + width: "13%", + mono: true, + render: (c) => c.vat_id || "—", + }, + { + key: "quotation_count", + header: "Nabídky", + width: "10%", + render: (c) => ( + + ), + }, + { + key: "actions", + header: "Akce", + width: "10%", + align: "right", + render: (c) => { + const cannotDelete = c.quotation_count > 0; + return ( + + {hasPermission("customers.edit") && ( + openEditModal(c)} + aria-label="Upravit" + title="Upravit" + > + {EditIcon} + + )} + {hasPermission("customers.delete") && ( + // Disabled buttons don't fire title/tooltip, so wrap in a titled + // span when delete is blocked (customer still has quotations). + + setDeleteConfirm({ show: true, customer: c })} + aria-label={ + cannotDelete + ? "Nelze smazat zákazníka s nabídkami" + : "Smazat" + } + disabled={cannotDelete} + > + {DeleteIcon} + + + )} + + ); + }, + }, + ]; + return ( -
+ -
-

Zákazníci

-

Správa zákazníků pro nabídky

-
- {hasPermission("customers.create") && ( - - )} + + Přidat zákazníka + + ) : undefined + } + />
- -
-
- setSearch(e.target.value)} - className="admin-form-input" - placeholder="Hledat zákazníky..." - /> -
+ + + setSearch(e.target.value)} + placeholder="Hledat zákazníky..." + fullWidth + /> + + - {filteredCustomers.length === 0 ? ( -
-

- {search + + + columns={columns} + rows={filteredCustomers} + rowKey={(c) => c.id} + empty={ + - {!search && hasPermission("customers.create") && ( - - )} -

- ) : ( -
- - - - - - - - - - - - - {filteredCustomers.map((customer) => ( - - - - - - - - - ))} - -
NázevMěstoIČODIČNabídkyAkce
-
- {customer.name} -
- {customer.street && ( -
- {customer.street} -
- )} -
{customer.city || "—"}{customer.company_id || "—"}{customer.vat_id || "—"} - - {customer.quotation_count || 0} - - -
- {hasPermission("customers.edit") && ( - - )} - {hasPermission("customers.delete") && ( - - )} -
-
-
- )} -
-
+ : "Zatím nejsou žádní zákazníci." + } + action={ + !search && hasPermission("customers.create") ? ( + + ) : undefined + } + /> + } + /> + {/* Create/Edit Modal */} - -
- - + + setForm((prev) => ({ ...prev, name: e.target.value })) + } + placeholder="Název firmy / jméno" + /> + + + + setForm((prev) => ({ ...prev, street: e.target.value })) + } + /> + + + + - setForm((prev) => ({ ...prev, name: e.target.value })) + setForm((prev) => ({ ...prev, city: e.target.value })) } - className="admin-form-input" - placeholder="Název firmy / jméno" /> - - - + + - setForm((prev) => ({ ...prev, street: e.target.value })) + setForm((prev) => ({ + ...prev, + postal_code: e.target.value, + })) } - className="admin-form-input" /> - -
- - - setForm((prev) => ({ ...prev, city: e.target.value })) - } - className="admin-form-input" - /> - - - - setForm((prev) => ({ - ...prev, - postal_code: e.target.value, - })) - } - className="admin-form-input" - /> - -
- - + + + + setForm((prev) => ({ ...prev, country: e.target.value })) + } + /> + + + + - setForm((prev) => ({ ...prev, country: e.target.value })) + setForm((prev) => ({ + ...prev, + company_id: e.target.value, + })) } - className="admin-form-input" /> - -
- - - setForm((prev) => ({ - ...prev, - company_id: e.target.value, - })) - } - className="admin-form-input" - /> - - - - setForm((prev) => ({ ...prev, vat_id: e.target.value })) - } - className="admin-form-input" - /> - -
+ + + + setForm((prev) => ({ ...prev, vat_id: e.target.value })) + } + /> + + - {/* Dynamic custom fields */} -
- - {customFields.map((field, idx) => ( -
-
+ + 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], - name: e.target.value, - }; - setCustomFields(updated); - }} - className="admin-form-input" - placeholder="Např. Kontakt" - /> - - -
- { - const updated = [...customFields]; - updated[idx] = { - ...updated[idx], - value: e.target.value, - }; - setCustomFields(updated); - }} - className="admin-form-input" - style={{ flex: 1 }} - /> - -
-
-
- -
- ))} - -
- - {/* Field order for PDF */} -
- - - Určuje pořadí řádků v adresním bloku zákazníka na PDF nabídce. - -
- {fullFieldOrder.map((key, index) => ( -
-
- - -
- { + const key = `custom_${idx}`; + setFieldOrder((prev) => { + return prev + .filter((k) => k !== key) + .map((k) => { + if (k.startsWith("custom_")) { + const ki = parseInt(k.split("_")[1]); + if (ki > idx) return `custom_${ki - 1}`; + } + return k; + }); + }); + setCustomFields(customFields.filter((_, i) => i !== idx)); + }} + title="Odebrat pole" + aria-label="Odebrat pole" > - {getFieldDisplayName(key)} - -
- ))} -
-
-
-
+ {RemoveIcon} + +
+ + + + Zobrazit název v PDF + + } + checked={field.showLabel !== false} + onChange={(checked) => { + const updated = [...customFields]; + updated[idx] = { + ...updated[idx], + showLabel: checked, + }; + setCustomFields(updated); + }} + /> + + + ))} + + + + {/* Field order for PDF */} + + + Pořadí polí v PDF + + + Určuje pořadí řádků v adresním bloku zákazníka na PDF nabídce. + + + {fullFieldOrder.map((key, index) => ( + + + moveField(index, -1)} + disabled={index === 0} + title="Nahoru" + aria-label="Nahoru" + sx={{ p: 0.25 }} + > + {UpIcon} + + moveField(index, 1)} + disabled={index === fullFieldOrder.length - 1} + title="Dolů" + aria-label="Dolů" + sx={{ p: 0.25 }} + > + {DownIcon} + + + + {getFieldDisplayName(key)} + + + ))} + + + {/* Delete Confirm Modal */} - setDeleteConfirm({ show: false, customer: null })} onConfirm={handleDelete} @@ -751,9 +813,9 @@ export default function OffersCustomers() { message={`Opravdu chcete smazat zákazníka "${deleteConfirm.customer?.name}"? Tato akce je nevratná.`} confirmText="Smazat" cancelText="Zrušit" - type="danger" + confirmVariant="danger" loading={deleting} /> -
+ ); }