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 (
)
}
// ── Toggle row ────────────────────────────────────────────────────────────────
function ToggleRow({ label, desc, value, onChange, disabled, children, warning }) {
return (
{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 (
<>
{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)}
/>
))}
)
)}
)
}