import { useState, useRef, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import toast from 'react-hot-toast' import { Plus, Pencil, Trash2, Printer, X, Check, ChevronDown, Zap, Settings2, ArrowUpDown, Layers, CheckSquare, SkipForward, GripVertical, ChevronUp, AlertTriangle, FlameKindling, } from 'lucide-react' import client from '../../api/client' // ── API ─────────────────────────────────────────────────────────────────────── const fetchZones = () => client.get('/api/prep-zones').then(r => r.data) const fetchPrinters = () => client.get('/api/system/printers').then(r => r.data) const fetchCategories = () => client.get('/api/products/categories').then(r => r.data) // ── Constants ───────────────────────────────────────────────────────────────── const AUTO_PRINT_OPTIONS = [ { value: 'none', label: 'Χωρίς εκτύπωση', desc: 'Μη αυτόματη εκτύπωση' }, { value: 'master', label: 'Μόνο κύριος', desc: 'Εκτύπωση στον κύριο' }, { value: 'all', label: 'Όλοι εκτυπωτές', desc: 'Master + δευτερεύοντες' }, ] const SORT_OPTIONS = [ { value: 'order_time', label: 'Σειρά παραγγελίας', desc: 'Όπως προστέθηκαν στο καλάθι' }, { value: 'item_count', label: 'Πλήθος (φθίνον)', desc: 'Πρώτα τα αντικείμενα με τη μεγαλύτερη ποσότητα' }, { value: 'alpha', label: 'Αλφαβητικά', desc: 'Ταξινόμηση κατά όνομα' }, ] const EMPTY_ZONE = { name: '', description: '', notification_name: '', printer_ids: [], master_printer_id: null, auto_print: 'none', master_copies: 1, secondary_copies: 1, sort_items_by: 'order_time', group_by_category: false, category_order: [], print_checkboxes: false, bypass_pending: false, bypass_kds: false, auto_ready_to_served: false, } // ── Helpers ─────────────────────────────────────────────────────────────────── function Toggle({ value, onChange, disabled }) { return ( ) } function CopySpinner({ value, onChange, min = 1, max = 9 }) { return (
{value}×
) } // ── Click-outside hook for dropdowns ───────────────────────────────────────── function useClickOutside(ref, onClose) { useEffect(() => { function handle(e) { if (ref.current && !ref.current.contains(e.target)) onClose() } document.addEventListener('mousedown', handle) return () => document.removeEventListener('mousedown', handle) }, [ref, onClose]) } // ── Master printer single-select ────────────────────────────────────────────── function MasterPrinterPicker({ printers, value, onChange }) { const [open, setOpen] = useState(false) const ref = useRef(null) useClickOutside(ref, () => setOpen(false)) const selected = printers.find(p => p.id === value) return (
{open && (
{printers.map(p => ( ))}
)}
) } // ── Secondary printers multi-select ────────────────────────────────────────── function SecondaryPrinterPicker({ printers, selected, masterId, onChange }) { const [open, setOpen] = useState(false) const ref = useRef(null) useClickOutside(ref, () => setOpen(false)) const selectedSet = new Set(selected) const eligible = printers.filter(p => p.id !== masterId) const selectedPrinters = eligible.filter(p => selectedSet.has(p.id)) const toggle = (id) => { const next = new Set(selectedSet) if (next.has(id)) next.delete(id); else next.add(id) onChange([...next]) } return (
{open && (
{eligible.length === 0 ? (
{printers.length <= 1 ? 'Δεν υπάρχουν άλλοι εκτυπωτές' : 'Ο μόνος εκτυπωτής είναι ο κύριος'}
) : eligible.map(p => { const on = selectedSet.has(p.id) return ( ) })}
Κλικ έξω για κλείσιμο
)}
) } // ── Category order modal ────────────────────────────────────────────────────── function CategoryOrderModal({ categories, order, onSave, onClose }) { const ranked = order.map(id => categories.find(c => c.id === id)).filter(Boolean) const unranked = categories.filter(c => !order.includes(c.id)) const [list, setList] = useState([...ranked, ...unranked]) const dragIdx = useRef(null) const onDragStart = (i) => { dragIdx.current = i } const onDragOver = (e, i) => { e.preventDefault() if (dragIdx.current === null || dragIdx.current === i) return const next = [...list] const [moved] = next.splice(dragIdx.current, 1) next.splice(i, 0, moved) dragIdx.current = i setList(next) } const onDragEnd = () => { dragIdx.current = null } const move = (i, dir) => { const j = i + dir if (j < 0 || j >= list.length) return const next = [...list]; [next[i], next[j]] = [next[j], next[i]] setList(next) } return (

Σειρά Κατηγοριών Εκτύπωσης

Σύρετε για αλλαγή σειράς

{list.map((cat, i) => (
onDragStart(i)} onDragOver={e => onDragOver(e, i)} onDragEnd={onDragEnd} className="flex items-center gap-2.5 px-3 py-2.5 bg-gray-50 rounded-xl border border-gray-100 cursor-grab active:cursor-grabbing select-none group hover:bg-white hover:border-gray-200 hover:shadow-sm transition-all" > {cat.name}
{i + 1}
))} {list.length === 0 && (
Δεν υπάρχουν κατηγορίες
)}
) } // ── Section header ──────────────────────────────────────────────────────────── function Section({ icon: Icon_, label, children }) { return (
{label}
{children}
) } // ── Toggle row ──────────────────────────────────────────────────────────────── function ToggleRow({ label, desc, value, onChange, disabled, children, warning }) { return (
{label} {warning && }
{desc &&

{desc}

}
{value && children && (
{children}
)}
) } // ── Zone Form ───────────────────────────────────────────────────────────────── function ZoneForm({ initial, printers, categories, onSave, onCancel, saving }) { const z = initial || EMPTY_ZONE const [name, setName] = useState(z.name) const [description, setDesc] = useState(z.description || '') const [notifName, setNotifName] = useState(z.notification_name || '') const [allPrinterIds, setAllPrinterIds] = useState(z.printer_ids || []) const [masterId, setMasterId] = useState(z.master_printer_id ?? null) const [autoPrint, setAutoPrint] = useState(z.auto_print || 'none') const [masterCopies, setMasterCopies] = useState(z.master_copies || 1) const [secondaryCopies, setSecondaryCopies] = useState(z.secondary_copies || 1) const [sortBy, setSortBy] = useState(z.sort_items_by || 'order_time') const [groupCat, setGroupCat] = useState(z.group_by_category || false) const [catOrder, setCatOrder] = useState(z.category_order || []) const [checkboxes, setCheckboxes] = useState(z.print_checkboxes || false) const [bypassPending, setBypassPending] = useState(z.bypass_pending || false) const [bypassKds, setBypassKds] = useState(z.bypass_kds || false) const [autoReadyServed, setAutoReadyServed] = useState(z.auto_ready_to_served || false) const [catModal, setCatModal] = useState(false) const secondaryIds = allPrinterIds.filter(id => id !== masterId) const handleMasterChange = (id) => { setMasterId(id) if (id && !allPrinterIds.includes(id)) setAllPrinterIds(prev => [...prev, id]) } const handleSecondaryChange = (ids) => { const next = new Set(ids) if (masterId) next.add(masterId) setAllPrinterIds([...next]) } const handleBypassKds = (v) => { setBypassKds(v); if (v) setBypassPending(true) } const handleBypassPending = (v) => { setBypassPending(v); if (!v) setBypassKds(false) } const valid = name.trim().length > 0 const submit = (e) => { e.preventDefault() if (!valid) return onSave({ name: name.trim(), description: description.trim() || null, notification_name: notifName.trim() || null, printer_ids: allPrinterIds, master_printer_id: masterId || null, auto_print: autoPrint, master_copies: masterCopies, secondary_copies: secondaryCopies, sort_items_by: sortBy, group_by_category: groupCat, category_order: catOrder, print_checkboxes: checkboxes, bypass_pending: bypassPending, bypass_kds: bypassKds, auto_ready_to_served: autoReadyServed, }) } return ( <>
{/* Identity */}
setName(e.target.value)} placeholder="π.χ. Kitchen – Grill" autoFocus />
setNotifName(e.target.value)} placeholder="π.χ. Κουζίνα" />
setDesc(e.target.value)} placeholder="Προαιρετική περιγραφή" />
{/* Printers */}

Ένας μόνο, υψηλή προτεραιότητα.

Μόνο στη λειτουργία «Όλοι».

{AUTO_PRINT_OPTIONS.map(opt => ( ))}
{autoPrint !== 'none' && (
{autoPrint === 'all' && (
)}
)}
{/* Ticket formatting */}
{SORT_OPTIONS.map(opt => ( ))}
{/* KDS Behavior */}
{/* Actions */}
{catModal && ( setCatModal(false)} /> )} ) } // ── Zone Card ───────────────────────────────────────────────────────────────── function Badge({ children, variant = 'default' }) { const cls = { default: 'bg-gray-100 text-gray-600', primary: 'bg-primary-100 text-primary-700', green: 'bg-green-100 text-green-700', amber: 'bg-amber-100 text-amber-700', red: 'bg-red-100 text-red-700', }[variant] || 'bg-gray-100 text-gray-600' return ( {children} ) } function ZoneCard({ zone, printers, onEdit, onDelete }) { const [confirmDelete, setConfirmDelete] = useState(false) const printerMap = Object.fromEntries(printers.map(p => [p.id, p])) const masterPrinter = printerMap[zone.master_printer_id] const secondaryPrinters = (zone.secondary_printer_ids || []).map(id => printerMap[id]).filter(Boolean) const printLabel = { none: { text: 'Χωρίς εκτύπωση', variant: 'default' }, master: { text: 'Κύριος μόνο', variant: 'primary' }, all: { text: 'Όλοι', variant: 'primary' }, }[zone.auto_print] || { text: '—', variant: 'default' } const sortLabel = { order_time: 'Σειρά παρ.', item_count: 'Κατά πλήθος', alpha: 'Αλφαβητικά', }[zone.sort_items_by] || zone.sort_items_by return (

{zone.name}

{zone.bypass_kds && Bypass KDS} {!zone.bypass_kds && zone.bypass_pending && Bypass Pending} {zone.auto_ready_to_served && Auto Served}
{zone.notification_name && (

{zone.notification_name}

)} {zone.description &&

{zone.description}

}
{!confirmDelete ? ( ) : (
)}
Κύριος {masterPrinter ? ( <> {masterPrinter.name} {zone.auto_print !== 'none' && ( {zone.master_copies}× )} ) : ( Χωρίς )}
{secondaryPrinters.length > 0 && (
Δευτ. {secondaryPrinters.map(p => ( {p.name} ))} {zone.auto_print === 'all' && ( {zone.secondary_copies}× )}
)}
{printLabel.text} {sortLabel} {zone.group_by_category && Κατά κατηγορία} {zone.print_checkboxes && Checkboxes}
) } // ── Main Page ───────────────────────────────────────────────────────────────── export default function PrepZonesConfigPage() { const qc = useQueryClient() const [showForm, setShowForm] = useState(false) const [editing, setEditing] = useState(null) const { data: zones = [], isLoading: lZ } = useQuery({ queryKey: ['prep-zones'], queryFn: fetchZones }) const { data: printers = [], isLoading: lP } = useQuery({ queryKey: ['printers'], queryFn: fetchPrinters }) const { data: categories = [], isLoading: lC } = useQuery({ queryKey: ['categories'], queryFn: fetchCategories }) const invalidate = () => qc.invalidateQueries({ queryKey: ['prep-zones'] }) const create = useMutation({ mutationFn: body => client.post('/api/prep-zones', body).then(r => r.data), onSuccess: () => { invalidate(); setShowForm(false); toast.success('Ζώνη δημιουργήθηκε') }, onError: () => toast.error('Σφάλμα κατά τη δημιουργία'), }) const update = useMutation({ mutationFn: ({ id, ...body }) => client.put(`/api/prep-zones/${id}`, body).then(r => r.data), onSuccess: () => { invalidate(); setEditing(null); toast.success('Ζώνη ενημερώθηκε') }, onError: () => toast.error('Σφάλμα κατά την ενημέρωση'), }) const remove = useMutation({ mutationFn: id => client.delete(`/api/prep-zones/${id}`), onSuccess: () => { invalidate(); toast.success('Ζώνη διαγράφηκε') }, onError: () => toast.error('Σφάλμα κατά τη διαγραφή'), }) if (lZ || lP || lC) { return (
Φόρτωση…
) } const flatCategories = categories.flatMap(c => [c, ...(c.subcategories || [])]) const formOpen = showForm || !!editing return ( // h-full + overflow-y-auto makes this page scroll within the fixed AppLayout
{/* Page header */}

Ζώνες Προετοιμασίας

Ζώνες, εκτυπωτές και συμπεριφορά KDS

{!formOpen && ( )}
{/* Two-column layout when form is open */} {formOpen ? (
{/* Form panel */}

{showForm ? 'Νέα Ζώνη Προετοιμασίας' : `Επεξεργασία: ${editing?.name}`}

create.mutate(body) : (body) => update.mutate({ id: editing.id, ...body })} onCancel={() => { setShowForm(false); setEditing(null) }} saving={create.isPending || update.isPending} />
{/* Zone list sidebar */}
{zones.length === 0 ? (

Δεν υπάρχουν ζώνες ακόμα

) : zones.map(zone => ( { setShowForm(false); setEditing(z) }} onDelete={id => remove.mutate(id)} /> ))}
) : ( /* Full-width zone grid when no form open */ zones.length === 0 ? (
Δεν υπάρχουν ζώνες ακόμα

Δημιουργήστε την πρώτη ζώνη για να αρχίσετε.

) : (
{zones.map(zone => ( { setShowForm(false); setEditing(z) }} onDelete={id => remove.mutate(id)} /> ))}
) )}
) }