Files
app/src/admin/pages/CompanySettings.tsx
BOHA 4608494a3f initial commit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:46:51 +01:00

789 lines
33 KiB
TypeScript

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<string, string> = {
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<string | null>(null)
const logoUrlRef = useRef<string | null>(null)
const [form, setForm] = useState<CompanyForm>({
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<CustomField[]>([])
const customFieldKeyCounter = useRef(0)
const [fieldOrder, setFieldOrder] = useState<string[]>([...DEFAULT_FIELD_ORDER])
const [bankAccounts, setBankAccounts] = useState<BankAccount[]>([])
const [bankLoading, setBankLoading] = useState(true)
const [bankSaving, setBankSaving] = useState(false)
const [editingBank, setEditingBank] = useState<number | null>(null)
const [bankForm, setBankForm] = useState<BankForm>({ 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<HTMLInputElement>) => {
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 <Forbidden />
if (loading) {
return (
<div className="admin-skeleton" style={{ padding: 0, gap: '1.5rem' }}>
<div className="admin-skeleton-row" style={{ justifyContent: 'space-between' }}>
<div>
<div className="admin-skeleton-line h-8" style={{ width: '200px', marginBottom: '0.5rem' }} />
<div className="admin-skeleton-line" style={{ width: '140px' }} />
</div>
<div className="admin-skeleton-line h-10" style={{ width: '120px', borderRadius: '8px' }} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1.25rem' }}>
{[0, 1, 2, 3, 4, 5].map(i => (
<div key={i} className="admin-card">
<div className="admin-skeleton" style={{ gap: '1.25rem' }}>
<div className="admin-skeleton-line h-8" style={{ width: '60%' }} />
{[0, 1, 2].map(j => (
<div key={j} className="admin-skeleton-row">
<div className="admin-skeleton-line w-1/3" />
<div className="admin-skeleton-line w-1/2" />
</div>
))}
</div>
</div>
))}
</div>
</div>
)
}
const fullFieldOrder = getFullFieldOrder()
const renderBankButtonContent = (): React.ReactNode => {
if (bankSaving) {
return <><div className="admin-spinner admin-spinner-sm" />Ukládání...</>
}
if (editingBank !== null) return 'Uložit změny'
return (
<>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
</svg>
Přidat účet
</>
)
}
return (
<div>
<motion.div
className="admin-page-header"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
<div>
<h1 className="admin-page-title">Nastavení firmy</h1>
<p className="admin-page-subtitle">Firemní údaje, číslování dokladů a výchozí hodnoty</p>
</div>
<button onClick={handleSave} className="admin-btn admin-btn-primary" disabled={saving}>
{saving ? (
<>
<div className="admin-spinner admin-spinner-sm" />
Ukládání...
</>
) : 'Uložit nastavení'}
</button>
</motion.div>
<div className="offers-settings-grid">
{/* Company Info */}
<motion.div
className="admin-card"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.06 }}
>
<div className="admin-card-header">
<h3 className="admin-card-title">Firemní údaje</h3>
</div>
<div className="admin-card-body">
<div className="admin-form">
<FormField label="Název firmy">
<input type="text" value={form.company_name} onChange={(e) => updateField('company_name', e.target.value)} className="admin-form-input" />
</FormField>
<div className="admin-form-row">
<FormField label="Ulice">
<input type="text" value={form.street} onChange={(e) => updateField('street', e.target.value)} className="admin-form-input" />
</FormField>
<FormField label="Město">
<input type="text" value={form.city} onChange={(e) => updateField('city', e.target.value)} className="admin-form-input" />
</FormField>
</div>
<div className="admin-form-row">
<FormField label="PSČ">
<input type="text" value={form.postal_code} onChange={(e) => updateField('postal_code', e.target.value)} className="admin-form-input" />
</FormField>
<FormField label="Země">
<input type="text" value={form.country} onChange={(e) => updateField('country', e.target.value)} className="admin-form-input" />
</FormField>
</div>
<div className="admin-form-row">
<FormField label="IČO">
<input type="text" value={form.company_id} onChange={(e) => updateField('company_id', e.target.value)} className="admin-form-input" />
</FormField>
<FormField label="DIČ">
<input type="text" value={form.vat_id} onChange={(e) => updateField('vat_id', e.target.value)} className="admin-form-input" />
</FormField>
</div>
<div style={{ marginTop: 4 }}>
<label className="admin-form-label" style={{ display: 'block', marginBottom: 4 }}>Vlastní pole</label>
{customFields.map((field, idx) => (
<div key={field._key} style={{ marginBottom: 8 }}>
<div className="admin-form-row" style={{ marginBottom: 0, alignItems: 'flex-end' }}>
<FormField label={idx === 0 ? 'Název' : '\u00A0'} style={{ flex: 1 }}>
<input
type="text"
value={field.name}
onChange={(e) => {
const updated = [...customFields]
updated[idx] = { ...updated[idx], name: e.target.value }
setCustomFields(updated)
}}
className="admin-form-input"
placeholder="Např. Tel."
/>
</FormField>
<FormField label={idx === 0 ? 'Hodnota' : '\u00A0'} style={{ flex: 1 }}>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<input
type="text"
value={field.value}
onChange={(e) => {
const updated = [...customFields]
updated[idx] = { ...updated[idx], value: e.target.value }
setCustomFields(updated)
}}
className="admin-form-input"
style={{ flex: 1 }}
/>
<button
type="button"
onClick={() => {
const key = `custom_${idx}`
setFieldOrder(prev =>
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))
}}
className="admin-btn-icon danger"
title="Odebrat pole"
aria-label="Odebrat pole"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
</FormField>
</div>
<label className="admin-form-checkbox" style={{ marginTop: 4 }}>
<input
type="checkbox"
checked={field.showLabel !== false}
onChange={(e) => {
const updated = [...customFields]
updated[idx] = { ...updated[idx], showLabel: e.target.checked }
setCustomFields(updated)
}}
/>
<span style={{ fontSize: '0.8rem' }}>Zobrazit název v PDF</span>
</label>
</div>
))}
<button
type="button"
onClick={() => setCustomFields([...customFields, { name: '', value: '', showLabel: true, _key: `cf-${++customFieldKeyCounter.current}` }])}
className="admin-btn admin-btn-secondary"
style={{ marginTop: 4, fontSize: '0.85rem' }}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
</svg>
Přidat pole
</button>
</div>
</div>
</div>
</motion.div>
{/* Bank Accounts */}
<motion.div
className="admin-card"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.08 }}
>
<div className="admin-card-header">
<h3 className="admin-card-title">Bankovní účty</h3>
</div>
<div className="admin-card-body">
{bankLoading ? (
<div className="admin-skeleton" style={{ gap: '1rem' }}>
{[0, 1, 2].map(i => (
<div key={i} className="admin-skeleton-row">
<div className="admin-skeleton-line w-1/3" />
<div className="admin-skeleton-line w-1/4" />
<div className="admin-skeleton-line w-1/4" />
</div>
))}
</div>
) : (
<>
{bankAccounts.length > 0 && (
<div className="admin-table-responsive mb-4">
<table className="admin-table">
<thead>
<tr>
<th>Název</th>
<th>Banka</th>
<th>Číslo účtu</th>
<th>IBAN</th>
<th>BIC/SWIFT</th>
<th>Měna</th>
<th style={{ width: 70 }}>Výchozí</th>
<th style={{ width: 80 }}></th>
</tr>
</thead>
<tbody>
{bankAccounts.map(acc => (
<tr key={acc.id} style={editingBank === acc.id ? { background: 'var(--bg-tertiary)' } : undefined}>
<td>{acc.account_name}</td>
<td>{acc.bank_name}</td>
<td className="admin-mono">{acc.account_number}</td>
<td className="admin-mono">{acc.iban}</td>
<td className="admin-mono">{acc.bic}</td>
<td>{acc.currency}</td>
<td className="text-center">
{acc.is_default ? <span className="text-accent fw-600"></span> : '\u2013'}
</td>
<td>
<div style={{ display: 'flex', gap: 4 }}>
<button type="button" onClick={() => startEditBank(acc)} className="admin-btn-icon" title="Upravit" aria-label="Upravit">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
</button>
<button type="button" onClick={() => handleBankDelete(acc.id)} className="admin-btn-icon danger" title="Smazat" aria-label="Smazat">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<div style={{ background: 'var(--bg-tertiary)', borderRadius: 'var(--border-radius)', padding: 16 }}>
<h4 className="text-secondary" style={{ margin: '0 0 12px', fontSize: '0.9rem' }}>
{editingBank !== null ? 'Upravit účet' : 'Přidat nový účet'}
</h4>
<div className="admin-form">
<div className="admin-form-row">
<FormField label="Název účtu" required>
<input type="text" value={bankForm.account_name} onChange={e => setBankForm(f => ({ ...f, account_name: e.target.value }))} className="admin-form-input" placeholder="Např. Hlavní CZK účet" />
</FormField>
<FormField label="Název banky">
<input type="text" value={bankForm.bank_name} onChange={e => setBankForm(f => ({ ...f, bank_name: e.target.value }))} className="admin-form-input" placeholder="Např. MONETA Money Bank, a.s." />
</FormField>
</div>
<div className="admin-form-row">
<FormField label="Číslo účtu">
<input type="text" value={bankForm.account_number} onChange={e => setBankForm(f => ({ ...f, account_number: e.target.value }))} className="admin-form-input" placeholder="123456789/0600" />
</FormField>
<FormField label="Měna">
<select value={bankForm.currency} onChange={e => setBankForm(f => ({ ...f, currency: e.target.value }))} className="admin-form-select">
<option value="CZK">CZK</option>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
<option value="GBP">GBP</option>
</select>
</FormField>
</div>
<div className="admin-form-row">
<FormField label="IBAN">
<input type="text" value={bankForm.iban} onChange={e => setBankForm(f => ({ ...f, iban: e.target.value }))} className="admin-form-input" placeholder="CZ65 0800 0000 1920 0014 5399" />
</FormField>
<FormField label="BIC / SWIFT">
<input type="text" value={bankForm.bic} onChange={e => setBankForm(f => ({ ...f, bic: e.target.value }))} className="admin-form-input" placeholder="GIBACZPX" />
</FormField>
</div>
<label className="admin-form-checkbox">
<input type="checkbox" checked={bankForm.is_default} onChange={e => setBankForm(f => ({ ...f, is_default: e.target.checked }))} />
<span>Výchozí účet (použije se automaticky při vytváření faktury)</span>
</label>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<button type="button" onClick={handleBankSave} className="admin-btn admin-btn-primary" disabled={bankSaving} style={{ fontSize: '0.85rem' }}>
{renderBankButtonContent()}
</button>
{editingBank !== null && (
<button type="button" onClick={resetBankForm} className="admin-btn admin-btn-secondary" style={{ fontSize: '0.85rem' }}>
Zrušit
</button>
)}
</div>
</div>
</div>
</>
)}
</div>
</motion.div>
{/* PDF Field Order */}
<motion.div className="admin-card" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: 0.08 }}>
<div className="admin-card-header">
<h3 className="admin-card-title">Pořadí polí dodavatele v PDF</h3>
</div>
<div className="admin-card-body">
<small className="admin-form-hint" style={{ display: 'block', marginBottom: 12 }}>
Určuje pořadí řádků v adresním bloku dodavatele na PDF nabídce.
</small>
<div className="admin-reorder-list">
{fullFieldOrder.map((key, index) => (
<div key={key} className="admin-reorder-item">
<div className="admin-reorder-arrows">
<button type="button" onClick={() => moveField(index, -1)} disabled={index === 0} className="admin-btn-icon" title="Nahoru" aria-label="Nahoru">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 15l-6-6-6 6" /></svg>
</button>
<button type="button" onClick={() => moveField(index, 1)} disabled={index === fullFieldOrder.length - 1} className="admin-btn-icon" title="Dolů" aria-label="Dolů">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 9l6 6 6-6" /></svg>
</button>
</div>
<span className={`admin-reorder-label${key.startsWith('custom_') ? ' accent' : ''}`}>
{getFieldDisplayName(key)}
</span>
</div>
))}
</div>
</div>
</motion.div>
{/* Logo */}
<motion.div className="admin-card" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: 0.12 }}>
<div className="admin-card-header">
<h3 className="admin-card-title">Logo</h3>
</div>
<div className="admin-card-body">
<div className="offers-logo-section">
{logoUrl && (
<div className="offers-logo-preview">
<img src={logoUrl} alt="Logo" />
</div>
)}
<label className="admin-btn admin-btn-secondary" style={{ cursor: 'pointer' }}>
{uploadingLogo ? (
<><div className="admin-spinner admin-spinner-sm" />Nahrávání...</>
) : (
<>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
Nahrát logo
</>
)}
<input type="file" accept="image/*" onChange={handleLogoUpload} style={{ display: 'none' }} disabled={uploadingLogo} />
</label>
<small className="admin-form-hint">PNG, JPEG, GIF nebo WebP, max 5 MB</small>
</div>
</div>
</motion.div>
{/* Cislovani dokladu */}
<motion.div className="admin-card" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: 0.15 }}>
<div className="admin-card-header">
<h3 className="admin-card-title">Číslování dokladů</h3>
</div>
<div className="admin-card-body">
<div className="admin-form">
<FormField label="Nabídky — prefix">
<input type="text" value={form.quotation_prefix} onChange={(e) => updateField('quotation_prefix', e.target.value)} className="admin-form-input" placeholder="N" style={{ maxWidth: 120 }} />
<small className="admin-form-hint">
Formát: ROK/PREFIX/ČÍSLO ukázka: {new Date().getFullYear()}/{form.quotation_prefix || 'N'}/001
</small>
</FormField>
<hr style={{ border: 'none', borderTop: '1px solid var(--border-color)', margin: '0.75rem 0' }} />
<FormField label="Objednávky a projekty — typový kód">
<input type="text" value={form.order_type_code} onChange={(e) => updateField('order_type_code', e.target.value)} className="admin-form-input" placeholder="71" style={{ maxWidth: 120 }} />
<small className="admin-form-hint">
Formát: RRKÓD#### ukázka: {currentYear}{form.order_type_code || '71'}0001
</small>
</FormField>
<hr style={{ border: 'none', borderTop: '1px solid var(--border-color)', margin: '0.75rem 0' }} />
<FormField label="Faktury — typový kód">
<input type="text" value={form.invoice_type_code} onChange={(e) => updateField('invoice_type_code', e.target.value)} className="admin-form-input" placeholder="81" style={{ maxWidth: 120 }} />
<small className="admin-form-hint">
Formát: RRKÓD#### ukázka: {currentYear}{form.invoice_type_code || '81'}0001
</small>
</FormField>
</div>
</div>
</motion.div>
{/* Default values */}
<motion.div className="admin-card" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: 0.15 }}>
<div className="admin-card-header">
<h3 className="admin-card-title">Výchozí hodnoty</h3>
</div>
<div className="admin-card-body">
<div className="admin-form">
<div className="admin-form-row">
<FormField label="Výchozí měna">
<select value={form.default_currency} onChange={(e) => updateField('default_currency', e.target.value)} className="admin-form-select">
<option value="EUR">EUR</option>
<option value="USD">USD</option>
<option value="CZK">CZK</option>
<option value="GBP">GBP</option>
</select>
</FormField>
<FormField label="Výchozí sazba DPH (%)">
<input type="number" value={form.default_vat_rate} onChange={(e) => updateField('default_vat_rate', parseFloat(e.target.value) || 0)} className="admin-form-input" step="0.1" />
</FormField>
</div>
</div>
</div>
</motion.div>
</div>
</div>
)
}