import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import toast from 'react-hot-toast' import client from '../../../api/client' import useAuthStore from '../../../store/authStore' function Toggle({ checked, onChange, disabled }) { return ( ) } const COMMON_TIMEZONES = [ 'Europe/Athens', 'Europe/London', 'Europe/Berlin', 'Europe/Paris', 'Europe/Rome', 'Europe/Madrid', 'Europe/Amsterdam', 'Europe/Brussels', 'Europe/Bucharest', 'Europe/Helsinki', 'Europe/Istanbul', 'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'UTC', ] function TimezoneSection() { const qc = useQueryClient() const { data: settings, isLoading } = useQuery({ queryKey: ['pos-settings'], queryFn: () => client.get('/api/settings/').then(r => r.data), staleTime: 30_000, }) const updateMut = useMutation({ mutationFn: ({ key, value }) => client.put(`/api/settings/${key}`, { value }), onSuccess: () => { toast.success('Αποθηκεύτηκε'); qc.invalidateQueries({ queryKey: ['pos-settings'] }) }, onError: () => toast.error('Σφάλμα αποθήκευσης'), }) const currentTz = settings?.['system.timezone']?.value ?? 'Europe/Athens' const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone return (

Ζώνη Ώρας

Η ζώνη ώρας που χρησιμοποιεί το backend για χρονοσφραγίδες. Αν οι ώρες έναρξης βάρδιας εμφανίζονται λανθασμένες, ρυθμίστε αυτό να ταιριάζει με την τοπική σας ζώνη.

{isLoading &&

Φόρτωση…

} {!isLoading && (
{updateMut.isPending && Αποθήκευση…}

Ζώνη ώρας browser: {browserTz} {browserTz !== currentTz && ( ⚠ Διαφέρει από τη ρύθμιση backend )}

Η αλλαγή ζώνης ώρας αποθηκεύεται και εφαρμόζεται στο frontend αμέσως. Για πλήρη εφαρμογή στον backend server (χρονοσφραγίδες), απαιτείται επανεκκίνηση του container.

)}
) } const LOCK_TIMEOUT_OPTIONS = [ { label: 'Απενεργοποιημένο', value: 0 }, { label: '1 λεπτό', value: 1 }, { label: '5 λεπτά', value: 5 }, { label: '10 λεπτά', value: 10 }, { label: '15 λεπτά', value: 15 }, { label: '30 λεπτά', value: 30 }, { label: '60 λεπτά', value: 60 }, ] const LOCK_SETTINGS_KEY = 'manager_lock_timeout' function AutoLockSection() { const raw = parseInt(localStorage.getItem(LOCK_SETTINGS_KEY) || '0', 10) const [timeout, setTimeout_] = useState(isNaN(raw) ? 0 : raw) function handleChange(val) { const n = parseInt(val, 10) setTimeout_(n) if (n > 0) { localStorage.setItem(LOCK_SETTINGS_KEY, String(n)) } else { localStorage.removeItem(LOCK_SETTINGS_KEY) } } return (

Αυτόματο Κλείδωμα Διαχειριστή

Αν δεν υπάρξει δραστηριότητα για το παρακάτω διάστημα, η οθόνη κλειδώνει και ζητάει PIN. Το 0 απενεργοποιεί το αυτόματο κλείδωμα.

{timeout > 0 && ( Κλείδωμα μετά από {timeout} {timeout === 1 ? 'λεπτό' : 'λεπτά'} αδράνειας )} {timeout === 0 && ( Μόνο χειροκίνητο κλείδωμα (κουμπί 🔒) )}
) } function ShiftSettingsSection() { const qc = useQueryClient() const { data: settings, isLoading } = useQuery({ queryKey: ['pos-settings'], queryFn: () => client.get('/api/settings/').then(r => r.data), staleTime: 30_000, }) const updateMut = useMutation({ mutationFn: ({ key, value }) => client.put(`/api/settings/${key}`, { value }), onSuccess: () => { toast.success('Αποθηκεύτηκε'); qc.invalidateQueries({ queryKey: ['pos-settings'] }) }, onError: () => toast.error('Σφάλμα αποθήκευσης'), }) function toggle(key, current) { updateMut.mutate({ key, value: current === 'true' ? 'false' : 'true' }) } const selfStart = settings?.['shifts.waiter_self_start']?.value ?? 'true' const selfEnd = settings?.['shifts.waiter_self_end']?.value ?? 'true' return (

Ρυθμίσεις Βάρδιας

Έλεγχος του τι επιτρέπεται να κάνουν οι σερβιτόροι μόνοι τους

{isLoading &&

Φόρτωση…

} {!isLoading && ( <>

Αυτόματη Έναρξη Βάρδιας

Οι σερβιτόροι μπορούν να ξεκινούν μόνοι τους τη βάρδια τους

toggle('shifts.waiter_self_start', selfStart)} disabled={updateMut.isPending} />

Αυτόματο Κλείσιμο Βάρδιας

Οι σερβιτόροι μπορούν να κλείνουν μόνοι τους τη βάρδια τους

toggle('shifts.waiter_self_end', selfEnd)} disabled={updateMut.isPending} />
)}
) } // ─── Flag definitions ───────────────────────────────────────────────────────── const FLAG_COLORS = [ '#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4', '#6b7280', '#dc2626', ] const RESTAURANT_EMOJIS = [ '🍽️', '🥂', '🍾', '🎂', '🎉', '🍰', '🥳', '👶', '🐶', '🐱', '♿', '🌿', '🥗', '⭐', '💎', '🔥', '❄️', '⏳', '🧹', '⚠️', ] function EmojiPicker({ value, onChange }) { const [open, setOpen] = useState(false) return (
{open && (
{RESTAURANT_EMOJIS.map(e => ( ))}
)}
) } function FlagDisplayModeSection() { const qc = useQueryClient() const { data: settings } = useQuery({ queryKey: ['pos-settings'], queryFn: () => client.get('/api/settings/').then(r => r.data), staleTime: 30_000, }) const updateMut = useMutation({ mutationFn: ({ key, value }) => client.put(`/api/settings/${key}`, { value }), onSuccess: () => { toast.success('Αποθηκεύτηκε'); qc.invalidateQueries({ queryKey: ['pos-settings'] }) }, onError: () => toast.error('Σφάλμα αποθήκευσης'), }) const current = settings?.['flags.display_mode']?.value ?? 'both' const options = [ { value: 'icon', label: '😀 Μόνο εικονίδιο' }, { value: 'text', label: 'Aa Μόνο κείμενο' }, { value: 'both', label: '😀 Aa Και τα δύο' }, ] return (
Εμφάνιση σημαιών στις κάρτες τραπεζιών
{options.map(o => ( ))}
) } function FlagDefsSection() { const qc = useQueryClient() const [editingId, setEditingId] = useState(null) const [editForm, setEditForm] = useState({}) const [newForm, setNewForm] = useState({ name: '', emoji: '', color: '#6b7280', text_color: null }) const [showNew, setShowNew] = useState(false) const { data: flags = [], isLoading } = useQuery({ queryKey: ['flag-defs'], queryFn: () => client.get('/api/flags/defs?include_inactive=true').then(r => r.data), staleTime: 30_000, }) const createMut = useMutation({ mutationFn: (body) => client.post('/api/flags/defs', body), onSuccess: () => { toast.success('Δημιουργήθηκε'); qc.invalidateQueries({ queryKey: ['flag-defs'] }); setShowNew(false); setNewForm({ name: '', emoji: '', color: '#6b7280', text_color: null }) }, onError: () => toast.error('Σφάλμα'), }) const updateMut = useMutation({ mutationFn: ({ id, ...body }) => client.put(`/api/flags/defs/${id}`, body), onSuccess: () => { toast.success('Αποθηκεύτηκε'); qc.invalidateQueries({ queryKey: ['flag-defs'] }); setEditingId(null) }, onError: () => toast.error('Σφάλμα αποθήκευσης'), }) const deleteMut = useMutation({ mutationFn: (id) => client.delete(`/api/flags/defs/${id}`), onSuccess: () => { toast.success('Απενεργοποιήθηκε'); qc.invalidateQueries({ queryKey: ['flag-defs'] }) }, onError: () => toast.error('Σφάλμα'), }) function startEdit(flag) { setEditingId(flag.id) setEditForm({ name: flag.name, emoji: flag.emoji || '', color: flag.color || '#6b7280', text_color: flag.text_color || null, sort_order: flag.sort_order }) } const rowStyle = { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 20px', borderBottom: '1px solid #f4f4f2' } return (

Σημαίες Τραπεζιών

Χρησιμοποιούνται για να επισημαίνετε καταστάσεις στα τραπέζια

{showNew && (
setNewForm(f => ({ ...f, emoji: v }))} /> setNewForm(f => ({ ...f, name: e.target.value }))} style={{ flex: 1, minWidth: 160, height: 36, borderRadius: 8, border: '1px solid #dfe2e6', padding: '0 12px', fontSize: 13, fontFamily: 'inherit' }} />
{FLAG_COLORS.map(c => (
Χρώμα γραφής: {[{ val: null, label: 'Α', bg: newForm.color || '#6b7280', text: '#ffffff' }, { val: '#000000', label: 'Α', bg: newForm.color || '#6b7280', text: '#000000' }].map(opt => ( ))}
)} {isLoading &&

Φόρτωση…

} {!isLoading && flags.length === 0 && (

Δεν υπάρχουν σημαίες ακόμα.

)} {flags.map(flag => (
{editingId === flag.id ? (
setEditForm(f => ({ ...f, emoji: v }))} /> setEditForm(f => ({ ...f, name: e.target.value }))} style={{ flex: 1, minWidth: 120, height: 32, borderRadius: 6, border: '1px solid #dfe2e6', padding: '0 10px', fontSize: 13, fontFamily: 'inherit' }} />
{FLAG_COLORS.map(c => (
{[{ val: null, text: '#ffffff' }, { val: '#000000', text: '#000000' }].map(opt => ( ))}
) : ( <>
{flag.emoji || '🏷️'}
{flag.name} {!flag.is_active && Ανενεργή} {flag.is_active && ( )} )}
))}
) } // ─── Quick message templates ────────────────────────────────────────────────── function QuickTemplatesSection() { const qc = useQueryClient() const [editingId, setEditingId] = useState(null) const [editBody, setEditBody] = useState('') const [newBody, setNewBody] = useState('') const [showNew, setShowNew] = useState(false) const { data: templates = [], isLoading } = useQuery({ queryKey: ['quick-templates'], queryFn: () => client.get('/api/messages/templates').then(r => r.data), staleTime: 30_000, }) const createMut = useMutation({ mutationFn: (body) => client.post('/api/messages/templates', body), onSuccess: () => { toast.success('Δημιουργήθηκε'); qc.invalidateQueries({ queryKey: ['quick-templates'] }); setShowNew(false); setNewBody('') }, onError: () => toast.error('Σφάλμα'), }) const updateMut = useMutation({ mutationFn: ({ id, body }) => client.put(`/api/messages/templates/${id}`, { body }), onSuccess: () => { toast.success('Αποθηκεύτηκε'); qc.invalidateQueries({ queryKey: ['quick-templates'] }); setEditingId(null) }, onError: () => toast.error('Σφάλμα αποθήκευσης'), }) const deleteMut = useMutation({ mutationFn: (id) => client.delete(`/api/messages/templates/${id}`), onSuccess: () => { toast.success('Διαγράφηκε'); qc.invalidateQueries({ queryKey: ['quick-templates'] }) }, onError: () => toast.error('Σφάλμα'), }) return (

Γρήγορα Μηνύματα

Πρότυπα μηνυμάτων για γρήγορη αποστολή στο προσωπικό

{showNew && (
setNewBody(e.target.value)} style={{ flex: 1, height: 36, borderRadius: 8, border: '1px solid #dfe2e6', padding: '0 12px', fontSize: 13, fontFamily: 'inherit' }} />
)} {isLoading &&

Φόρτωση…

} {!isLoading && templates.length === 0 && (

Δεν υπάρχουν πρότυπα ακόμα.

)} {templates.map((t, idx) => (
{idx + 1}. {editingId === t.id ? ( <> setEditBody(e.target.value)} style={{ flex: 1, height: 32, borderRadius: 6, border: '1px solid #dfe2e6', padding: '0 10px', fontSize: 13, fontFamily: 'inherit' }} /> ) : ( <> {t.body} )}
))}
) } function formatUptime(seconds) { const h = Math.floor(seconds / 3600) const m = Math.floor((seconds % 3600) / 60) const s = seconds % 60 return `${h}ω ${m}λ ${s}δ` } export default function AppInfoTab() { const user = useAuthStore(s => s.user) const qc = useQueryClient() const { data: status, isLoading } = useQuery({ queryKey: ['system-status'], queryFn: () => client.get('/api/system/status').then(r => r.data), refetchInterval: 30_000, }) const testPrint = useMutation({ mutationFn: (id) => client.post(`/api/system/printers/test?printer_id=${id}`), onSuccess: (res) => { const d = res.data d.success ? toast.success('Test print στάλθηκε!') : toast.error(`Σφάλμα: ${d.error}`) }, onError: () => toast.error('Σφάλμα επικοινωνίας'), }) if (isLoading) return
Φόρτωση…
return (
{/* System info */}

Σύστημα

Uptime
{formatUptime(status?.uptime_seconds ?? 0)}
Άδεια χρήσης
{status?.licensed ? 'Ενεργή' : 'Ανενεργή'}
Κατάσταση
{status?.locked ? 'Κλειδωμένο' : 'Λειτουργικό'}
{status?.expires_at && ( <>
Λήξη άδειας
{new Date(status.expires_at).toLocaleDateString('el-GR')}
)}
{/* Printers */}

Εκτυπωτές

{(!status?.printers || status.printers.length === 0) && (

Δεν βρέθηκαν εκτυπωτές.

)} {status?.printers?.map(p => (

{p.name}

{p.reachable ? 'Προσβάσιμος' : 'Μη προσβάσιμος'}
))}
{user?.role === 'sysadmin' && (

Sysadmin

Έλεγχος κλειδώματος συστήματος.

)}
) }