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 */}
{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 || '—'}
{formatDate(order.created_at)}
{order.attachment_name ? (
) : '—'}
{/* Items (read-only) */}
Položky
{order.items?.length > 0 ? (
| # |
Popis |
Množství |
Jednotka |
Jedn. cena |
V ceně |
Celkem |
{order.items.map((item, index) => {
const lineTotal = (Number(item.quantity) || 0) * (Number(item.unit_price) || 0)
return (
| {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
{hasPermission('orders.edit') && (
)}
{/* Status change confirmation */}
setStatusConfirm({ show: false, status: null })}
onConfirm={handleStatusChange}
title="Změnit stav objednávky"
message={`Opravdu chcete změnit stav objednávky "${order.order_number}" na "${STATUS_LABELS[statusConfirm.status]}"?${statusConfirm.status === 'dokoncena' ? ' Projekt bude automaticky dokončen.' : ''}`}
confirmText={TRANSITION_LABELS[statusConfirm.status] || 'Potvrdit'}
cancelText="Zrušit"
type="default"
/>
{/* Delete confirmation */}
setDeleteConfirm(false)}
onConfirm={handleDelete}
title="Smazat objednávku"
message={`Opravdu chcete smazat objednávku "${order.order_number}"? Bude smazán i přidružený projekt. Tato akce je nevratná.`}
confirmText="Smazat"
cancelText="Zrušit"
type="danger"
loading={deleting}
/>
)
}