import { useState, useEffect, useCallback, useRef } from "react"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import FormField from "../components/FormField"; import { motion } from "framer-motion"; import apiFetch from "../utils/api"; const API_BASE = "/api/admin"; const DEFAULT_FIELD_ORDER = [ "street", "city_postal", "country", "company_id", "vat_id", ]; const FIELD_LABELS: Record = { street: "Ulice", city_postal: "Město + PSČ", country: "Země", company_id: "IČO", vat_id: "DIČ", }; interface CustomField { name: string; value: string; showLabel: boolean; _key: string; } interface CompanyForm { company_name: string; street: string; city: string; postal_code: string; country: string; company_id: string; vat_id: string; } interface BankAccount { id: number; account_name: string; bank_name: string; account_number: string; iban: string; bic: string; currency: string; is_default: boolean; } interface BankForm { account_name: string; bank_name: string; account_number: string; iban: string; bic: string; currency: string; is_default: boolean; } export default function CompanySettings({ embedded, }: { embedded?: boolean } = {}) { const alert = useAlert(); const { hasPermission } = useAuth(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [uploadingLogo, setUploadingLogo] = useState(false); const [uploadingLogoDark, setUploadingLogoDark] = useState(false); const [logoUrl, setLogoUrl] = useState(null); const [logoUrlDark, setLogoUrlDark] = useState(null); const logoUrlRef = useRef(null); const logoUrlDarkRef = useRef(null); const [form, setForm] = useState({ company_name: "", street: "", city: "", postal_code: "", country: "", company_id: "", vat_id: "", }); const [customFields, setCustomFields] = useState([]); const customFieldKeyCounter = useRef(0); const [fieldOrder, setFieldOrder] = useState([ ...DEFAULT_FIELD_ORDER, ]); const [bankAccounts, setBankAccounts] = useState([]); const [availableCurrencies, setAvailableCurrencies] = useState([ "CZK", "EUR", "USD", "GBP", ]); const [bankLoading, setBankLoading] = useState(true); const [bankSaving, setBankSaving] = useState(false); const [editingBank, setEditingBank] = useState(null); const [bankForm, setBankForm] = useState({ account_name: "", bank_name: "", account_number: "", iban: "", bic: "", currency: "CZK", is_default: false, }); const getFullFieldOrder = useCallback((): string[] => { const allBuiltIn = [...DEFAULT_FIELD_ORDER]; const order = [...fieldOrder].filter((k) => k !== "company_name"); for (const f of allBuiltIn) { if (!order.includes(f)) order.push(f); } for (let i = 0; i < customFields.length; i++) { const key = `custom_${i}`; if (!order.includes(key)) order.push(key); } return order.filter((key) => { if (key.startsWith("custom_")) { const idx = parseInt(key.split("_")[1]); return idx < customFields.length; } return true; }); }, [fieldOrder, customFields]); const moveField = (index: number, direction: number) => { const order = getFullFieldOrder(); const newIndex = index + direction; if (newIndex < 0 || newIndex >= order.length) return; const updated = [...order]; [updated[index], updated[newIndex]] = [updated[newIndex], updated[index]]; setFieldOrder(updated); }; const getFieldDisplayName = (key: string): string => { if (FIELD_LABELS[key]) return FIELD_LABELS[key]; if (key.startsWith("custom_")) { const idx = parseInt(key.split("_")[1]); const cf = customFields[idx]; if (cf) return cf.name ? `${cf.name}: ${cf.value || "..."}` : cf.value || `Vlastní pole ${idx + 1}`; } return key; }; const fetchLogo = useCallback(async (variant: "light" | "dark" = "light") => { try { const resp = await apiFetch( `${API_BASE}/company-settings/logo?variant=${variant}`, ); if (resp.ok) { const blob = await resp.blob(); if (variant === "dark") { setLogoUrlDark((prev) => { if (prev) URL.revokeObjectURL(prev); const url = URL.createObjectURL(blob); logoUrlDarkRef.current = url; return url; }); } else { setLogoUrl((prev) => { if (prev) URL.revokeObjectURL(prev); const url = URL.createObjectURL(blob); logoUrlRef.current = url; return url; }); } } } catch { // ignore - no logo } }, []); const fetchData = useCallback(async () => { try { const response = await apiFetch(`${API_BASE}/company-settings`); if (response.status === 401) return; const result = await response.json(); if (result.success) { const d = result.data; setForm({ company_name: d.company_name || "", street: d.street || "", city: d.city || "", postal_code: d.postal_code || "", country: d.country || "", company_id: d.company_id || "", vat_id: d.vat_id || "", }); const cf = Array.isArray(d.custom_fields) && d.custom_fields.length > 0 ? d.custom_fields.map( (f: { name: string; value: string; showLabel?: boolean }) => ({ ...f, _key: `cf-${++customFieldKeyCounter.current}`, }), ) : []; setCustomFields(cf); if ( Array.isArray(d.supplier_field_order) && d.supplier_field_order.length > 0 ) { setFieldOrder(d.supplier_field_order); } else { setFieldOrder([...DEFAULT_FIELD_ORDER]); } if ( Array.isArray(d.available_currencies) && d.available_currencies.length > 0 ) { setAvailableCurrencies(d.available_currencies); } if (d.has_logo) { fetchLogo("light"); } if (d.has_logo_dark) { fetchLogo("dark"); } } else { alert.error(result.error || "Nepodařilo se načíst nastavení"); } } catch { alert.error("Chyba připojení"); } finally { setLoading(false); } }, [alert, fetchLogo]); const fetchBankAccounts = useCallback(async () => { try { const response = await apiFetch(`${API_BASE}/bank-accounts`); if (response.status === 401) return; const result = await response.json(); if (result.success) { setBankAccounts(result.data); } } catch { // ignore } finally { setBankLoading(false); } }, []); const resetBankForm = () => { setEditingBank(null); setBankForm({ account_name: "", bank_name: "", account_number: "", iban: "", bic: "", currency: "CZK", is_default: false, }); }; const handleBankSave = async () => { if (!bankForm.account_name.trim()) { alert.error("Název účtu je povinný"); return; } setBankSaving(true); try { const isEdit = editingBank !== null; const url = isEdit ? `${API_BASE}/bank-accounts/${editingBank}` : `${API_BASE}/bank-accounts`; const response = await apiFetch(url, { method: isEdit ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(bankForm), }); const result = await response.json(); if (result.success) { alert.success(result.message); resetBankForm(); fetchBankAccounts(); } else { alert.error(result.error || "Chyba při ukládání"); } } catch { alert.error("Chyba připojení"); } finally { setBankSaving(false); } }; const handleBankDelete = async (id: number) => { if (!confirm("Opravdu smazat tento bankovní účet?")) return; try { const response = await apiFetch(`${API_BASE}/bank-accounts/${id}`, { method: "DELETE", }); const result = await response.json(); if (result.success) { alert.success(result.message); if (editingBank === id) resetBankForm(); fetchBankAccounts(); } else { alert.error(result.error || "Chyba při mazání"); } } catch { alert.error("Chyba připojení"); } }; const startEditBank = (account: BankAccount) => { setEditingBank(account.id); setBankForm({ account_name: account.account_name || "", bank_name: account.bank_name || "", account_number: account.account_number || "", iban: account.iban || "", bic: account.bic || "", currency: account.currency || "CZK", is_default: !!account.is_default, }); }; useEffect(() => { fetchData(); fetchBankAccounts(); }, [fetchData, fetchBankAccounts]); // Cleanup blob URLs on unmount useEffect(() => { return () => { if (logoUrlRef.current) URL.revokeObjectURL(logoUrlRef.current); if (logoUrlDarkRef.current) URL.revokeObjectURL(logoUrlDarkRef.current); }; }, []); const handleSave = async () => { setSaving(true); try { const payload = { ...form, custom_fields: customFields.filter( (f) => f.name.trim() || f.value.trim(), ), supplier_field_order: getFullFieldOrder(), }; const response = await apiFetch(`${API_BASE}/company-settings`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); if (result.success) { alert.success(result.message || "Nastavení bylo uloženo"); } else { alert.error(result.error || "Nepodařilo se uložit nastavení"); } } catch { alert.error("Chyba připojení"); } finally { setSaving(false); } }; const handleLogoUpload = async ( e: React.ChangeEvent, variant: "light" | "dark" = "light", ) => { const file = e.target.files?.[0]; if (!file) return; const setUploading = variant === "dark" ? setUploadingLogoDark : setUploadingLogo; setUploading(true); try { const formData = new FormData(); formData.append("logo", file); const response = await apiFetch( `${API_BASE}/company-settings/logo?variant=${variant}`, { method: "POST", body: formData, }, ); const result = await response.json(); if (result.success) { alert.success(result.message || "Logo bylo nahráno"); fetchLogo(variant); } else { alert.error(result.error || "Nepodařilo se nahrát logo"); } } catch { alert.error("Chyba připojení"); } finally { setUploading(false); e.target.value = ""; } }; const updateField = (field: keyof CompanyForm, value: string | number) => { setForm((prev) => ({ ...prev, [field]: value })); }; if (!embedded && !hasPermission("settings.manage")) return ; if (loading) { return (
{[0, 1, 2, 3, 4, 5].map((i) => (
{[0, 1, 2].map((j) => (
))}
))}
); } const fullFieldOrder = getFullFieldOrder(); const renderBankButtonContent = (): React.ReactNode => { if (bankSaving) { return ( <>
Ukládání... ); } if (editingBank !== null) return "Uložit změny"; return ( <> Přidat účet ); }; return (
{!embedded && (

Nastavení firmy

Firemní údaje a bankovní účty

)}
{/* Company Info */}

Firemní údaje

updateField("company_name", e.target.value)} className="admin-form-input" />
updateField("street", e.target.value)} className="admin-form-input" /> updateField("city", e.target.value)} className="admin-form-input" />
updateField("postal_code", e.target.value)} className="admin-form-input" /> updateField("country", e.target.value)} className="admin-form-input" />
updateField("company_id", e.target.value)} className="admin-form-input" /> updateField("vat_id", e.target.value)} className="admin-form-input" />
{customFields.map((field, idx) => (
{ const updated = [...customFields]; updated[idx] = { ...updated[idx], name: e.target.value, }; setCustomFields(updated); }} className="admin-form-input" placeholder="Např. Tel." />
{ const updated = [...customFields]; updated[idx] = { ...updated[idx], value: e.target.value, }; setCustomFields(updated); }} className="admin-form-input" style={{ flex: 1 }} />
))}
{/* Bank Accounts */}

Bankovní účty

{bankLoading ? (
{[0, 1, 2].map((i) => (
))}
) : ( <> {bankAccounts.length > 0 && (
{bankAccounts.map((acc) => ( ))}
Název Banka Číslo účtu IBAN BIC/SWIFT Měna Výchozí
{acc.account_name} {acc.bank_name} {acc.account_number} {acc.iban} {acc.bic} {acc.currency} {acc.is_default ? ( ) : ( "\u2013" )}
)}

{editingBank !== null ? "Upravit účet" : "Přidat nový účet"}

setBankForm((f) => ({ ...f, account_name: e.target.value, })) } className="admin-form-input" placeholder="Např. Hlavní CZK účet" /> setBankForm((f) => ({ ...f, bank_name: e.target.value, })) } className="admin-form-input" placeholder="Např. MONETA Money Bank, a.s." />
setBankForm((f) => ({ ...f, account_number: e.target.value, })) } className="admin-form-input" placeholder="123456789/0600" />
setBankForm((f) => ({ ...f, iban: e.target.value })) } className="admin-form-input" placeholder="CZ65 0800 0000 1920 0014 5399" /> setBankForm((f) => ({ ...f, bic: e.target.value })) } className="admin-form-input" placeholder="GIBACZPX" />
{editingBank !== null && ( )}
)}
{/* PDF Field Order */}

Pořadí polí dodavatele v PDF

Určuje pořadí řádků v adresním bloku dodavatele na PDF nabídce.
{fullFieldOrder.map((key, index) => (
{getFieldDisplayName(key)}
))}
{/* Logo */}

Logo

{logoUrl && (
Logo (světlý režim)
)}
{embedded && ( )}
); }