import { useState, useEffect, useMemo } from 'react' import DOMPurify from 'dompurify' import { useAlert } from '../context/AlertContext' import { useAuth } from '../context/AuthContext' import { useParams, useNavigate, Link } from 'react-router-dom' import { motion } from 'framer-motion' import ConfirmModal from '../components/ConfirmModal' import Forbidden from '../components/Forbidden' import apiFetch from '../utils/api' import { formatCurrency, formatDate } from '../utils/formatters' const API_BASE = '/api/admin' const STATUS_LABELS = { prijata: 'Přijatá', v_realizaci: 'V realizaci', dokoncena: 'Dokončená', stornovana: 'Stornována' } const STATUS_CLASSES = { prijata: 'admin-badge-order-prijata', v_realizaci: 'admin-badge-order-realizace', dokoncena: 'admin-badge-order-dokoncena', stornovana: 'admin-badge-order-stornovana' } const TRANSITION_LABELS = { v_realizaci: 'Zahájit realizaci', dokoncena: 'Dokončit' } const TRANSITION_CLASSES = { v_realizaci: 'admin-btn admin-btn-primary', dokoncena: 'admin-btn admin-btn-primary' } export default function OrderDetail() { const { id } = useParams() const alert = useAlert() const { hasPermission } = useAuth() const navigate = useNavigate() const [loading, setLoading] = useState(true) const [order, setOrder] = useState(null) const [notes, setNotes] = useState('') const [saving, setSaving] = useState(false) const [statusChanging, setStatusChanging] = useState(null) const [statusConfirm, setStatusConfirm] = useState({ show: false, status: null }) const [editingNumber, setEditingNumber] = useState(false) const [orderNumber, setOrderNumber] = useState('') const [savingNumber, setSavingNumber] = useState(false) const [attachmentLoading, setAttachmentLoading] = useState(false) const [deleteConfirm, setDeleteConfirm] = useState(false) const [deleting, setDeleting] = useState(false) const fetchDetail = async () => { try { const response = await apiFetch(`${API_BASE}/orders.php?action=detail&id=${id}`) if (response.status === 401) return const result = await response.json() if (result.success) { setOrder(result.data) setNotes(result.data.notes || '') } else { alert.error(result.error || 'Nepodařilo se načíst objednávku') navigate('/orders') } } catch { alert.error('Chyba připojení') navigate('/orders') } finally { setLoading(false) } } useEffect(() => { fetchDetail() }, [id]) // eslint-disable-line react-hooks/exhaustive-deps const totals = useMemo(() => { if (!order?.items) return { subtotal: 0, vatAmount: 0, total: 0 } const subtotal = order.items.reduce((sum, item) => { if (Number(item.is_included_in_total)) { return sum + (Number(item.quantity) || 0) * (Number(item.unit_price) || 0) } return sum }, 0) const vatAmount = Number(order.apply_vat) ? subtotal * ((Number(order.vat_rate) || 0) / 100) : 0 return { subtotal, vatAmount, total: subtotal + vatAmount } }, [order]) if (!hasPermission('orders.view')) return const handleStatusChange = async () => { if (!statusConfirm.status) return setStatusChanging(statusConfirm.status) setStatusConfirm({ show: false, status: null }) try { const response = await apiFetch(`${API_BASE}/orders.php?id=${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: statusConfirm.status }) }) const result = await response.json() if (result.success) { alert.success(result.message || 'Stav byl změněn') fetchDetail() } else { alert.error(result.error || 'Nepodařilo se změnit stav') } } catch { alert.error('Chyba připojení') } finally { setStatusChanging(null) } } const handleStartEditNumber = () => { setOrderNumber(order.order_number) setEditingNumber(true) } const handleSaveNumber = async () => { const trimmed = orderNumber.trim() if (!trimmed) return if (trimmed === order.order_number) { setEditingNumber(false) return } setSavingNumber(true) try { const response = await apiFetch(`${API_BASE}/orders.php?id=${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order_number: trimmed }) }) const result = await response.json() if (result.success) { alert.success('Číslo objednávky bylo změněno') setEditingNumber(false) fetchDetail() } else { alert.error(result.error || 'Nepodařilo se změnit číslo') } } catch { alert.error('Chyba připojení') } finally { setSavingNumber(false) } } const handleSaveNotes = async () => { setSaving(true) try { const response = await apiFetch(`${API_BASE}/orders.php?id=${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ notes: notes }) }) const result = await response.json() if (result.success) { alert.success('Poznámky byly uloženy') } else { alert.error(result.error || 'Nepodařilo se uložit poznámky') } } catch { alert.error('Chyba připojení') } finally { setSaving(false) } } const handleViewAttachment = async () => { const newWindow = window.open('', '_blank') setAttachmentLoading(true) try { const response = await apiFetch(`${API_BASE}/orders.php?action=attachment&id=${id}`) if (!response.ok) { newWindow.close() alert.error('Nepodařilo se stáhnout přílohu') return } const blob = await response.blob() const url = URL.createObjectURL(blob) newWindow.location.href = url setTimeout(() => URL.revokeObjectURL(url), 60000) } catch { newWindow.close() alert.error('Chyba připojení') } finally { setAttachmentLoading(false) } } const handleDelete = async () => { setDeleting(true) try { const response = await apiFetch(`${API_BASE}/orders.php?id=${id}`, { method: 'DELETE' }) const result = await response.json() if (result.success) { alert.success(result.message || 'Objednávka byla smazána') navigate('/orders') } else { alert.error(result.error || 'Nepodařilo se smazat objednávku') } } catch { alert.error('Chyba připojení') } finally { setDeleting(false) setDeleteConfirm(false) } } if (loading) { return (
{[0, 1, 2, 3].map(i => (
))}
{[0, 1, 2].map(i => (
))}
) } if (!order) return null return (
{/* Header */}

{editingNumber ? ( Objednávka setOrderNumber(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSaveNumber() if (e.key === 'Escape') setEditingNumber(false) }} className="admin-form-input" style={{ width: '10rem', fontSize: '1rem', padding: '0.25rem 0.5rem', height: 'auto' }} autoFocus disabled={savingNumber} /> ) : ( Objednávka {order.order_number} {hasPermission('orders.edit') && ( )} )} {STATUS_LABELS[order.status] || order.status}

{order.invoice ? ( Faktura {order.invoice.invoice_number} ) : ( hasPermission('invoices.create') && order.status === 'dokoncena' && ( Vytvořit fakturu ) )} {hasPermission('orders.edit') && order.valid_transitions?.filter(s => s !== 'stornovana').length > 0 && ( order.valid_transitions.filter(s => s !== 'stornovana').map(status => ( )) )} {hasPermission('orders.delete') && ( )}
{/* Info card */}

Informace

{order.quotation_number} {order.project_code && ( ({order.project_code}) )}
{order.project ? ( {order.project.project_number} — {order.project.name} ) : '—'}
{order.customer_name || '—'}
{order.customer_order_number || '—'}
{order.currency}
{formatDate(order.created_at)}
{order.attachment_name ? ( ) : '—'}
{/* Items (read-only) */}

Položky

{order.items?.length > 0 ? (
{order.items.map((item, index) => { const lineTotal = (Number(item.quantity) || 0) * (Number(item.unit_price) || 0) return ( ) })}
# Popis Množství Jednotka Jedn. cena V ceně Celkem
{index + 1}
{item.description || '—'}
{item.item_description && (
{item.item_description}
)}
{item.quantity} {item.unit || '—'} {formatCurrency(item.unit_price, order.currency)} {Number(item.is_included_in_total) ? 'Ano' : 'Ne'} {formatCurrency(lineTotal, order.currency)}
) : (

Žádné položky.

)} {/* Totals */}
Mezisoučet: {formatCurrency(totals.subtotal, order.currency)}
{Number(order.apply_vat) > 0 && (
DPH ({order.vat_rate}%): {formatCurrency(totals.vatAmount, order.currency)}
)}
Celkem k úhradě: {formatCurrency(totals.total, order.currency)}
{/* Sections (read-only) */} {order.sections?.length > 0 && (

Rozsah projektu

{order.scope_title && (
{order.scope_title}
)} {order.scope_description && (
{order.scope_description}
)}
{order.sections.map((section, index) => (
{index + 1}. {(order.language === 'CZ' ? (section.title_cz || section.title) : (section.title || section.title_cz)) || `Sekce ${index + 1}`}
{section.content && (
)}
))}
)} {/* Notes (editable) */}

Poznámky