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Č', } const currentYear = new Date().getFullYear().toString().slice(-2) 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 quotation_prefix: string default_currency: string default_vat_rate: number order_type_code: string invoice_type_code: 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() { const alert = useAlert() const { hasPermission } = useAuth() const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [uploadingLogo, setUploadingLogo] = useState(false) const [logoUrl, setLogoUrl] = useState(null) const logoUrlRef = useRef(null) const [form, setForm] = useState({ company_name: '', street: '', city: '', postal_code: '', country: '', company_id: '', vat_id: '', quotation_prefix: 'N', default_currency: 'EUR', default_vat_rate: 21, order_type_code: '71', invoice_type_code: '81', }) const [customFields, setCustomFields] = useState([]) const customFieldKeyCounter = useRef(0) const [fieldOrder, setFieldOrder] = useState([...DEFAULT_FIELD_ORDER]) const [bankAccounts, setBankAccounts] = useState([]) 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 () => { try { const resp = await apiFetch(`${API_BASE}/company-settings/logo`) if (resp.ok) { const blob = await resp.blob() 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 || '', quotation_prefix: d.quotation_prefix || 'N', default_currency: d.default_currency || 'EUR', default_vat_rate: d.default_vat_rate || 21, order_type_code: d.order_type_code || '71', invoice_type_code: d.invoice_type_code || '81', }) 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 (d.has_logo) { fetchLogo() } } 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 URL on unmount useEffect(() => { return () => { if (logoUrlRef.current) URL.revokeObjectURL(logoUrlRef.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) => { const file = e.target.files?.[0] if (!file) return setUploadingLogo(true) try { const formData = new FormData() formData.append('logo', file) const response = await apiFetch(`${API_BASE}/company-settings/logo`, { method: 'POST', body: formData }) const result = await response.json() if (result.success) { alert.success(result.message || 'Logo bylo nahráno') fetchLogo() } else { alert.error(result.error || 'Nepodařilo se nahrát logo') } } catch { alert.error('Chyba připojení') } finally { setUploadingLogo(false) e.target.value = '' } } const updateField = (field: keyof CompanyForm, value: string | number) => { setForm(prev => ({ ...prev, [field]: value })) } if (!hasPermission('offers.settings')) 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 (

Nastavení firmy

Firemní údaje, číslování dokladů a výchozí hodnoty

{/* 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
)}
{/* Cislovani dokladu */}

Číslování dokladů

updateField('quotation_prefix', e.target.value)} className="admin-form-input" placeholder="N" style={{ maxWidth: 120 }} /> Formát: ROK/PREFIX/ČÍSLO — ukázka: {new Date().getFullYear()}/{form.quotation_prefix || 'N'}/001
updateField('order_type_code', e.target.value)} className="admin-form-input" placeholder="71" style={{ maxWidth: 120 }} /> Formát: RRKÓD#### — ukázka: {currentYear}{form.order_type_code || '71'}0001
updateField('invoice_type_code', e.target.value)} className="admin-form-input" placeholder="81" style={{ maxWidth: 120 }} /> Formát: RRKÓD#### — ukázka: {currentYear}{form.invoice_type_code || '81'}0001
{/* Default values */}

Výchozí hodnoty

updateField('default_vat_rate', parseFloat(e.target.value) || 0)} className="admin-form-input" step="0.1" />
) }