Includes: LoginPage (PIN pad), DashboardPage (30s polling table grid), OrderDetailPage (full actions), ProductsPage (CRUD + printer zone), WaitersPage (block/reset PIN/delete), TablesPage, ReportsPage (shift summary + order history + CSV export), SettingsPage (printers + test print + sysadmin lock/unlock). TailwindCSS, React Query, react-hot-toast. Docker Compose service on port 5174.
114 lines
4.6 KiB
JavaScript
114 lines
4.6 KiB
JavaScript
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 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 SettingsPage() {
|
|
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 <div className="flex items-center justify-center h-64 text-gray-400">Φόρτωση…</div>
|
|
|
|
return (
|
|
<div className="space-y-6 max-w-2xl">
|
|
<h1 className="text-xl font-bold text-gray-800">Ρυθμίσεις</h1>
|
|
|
|
{/* System info */}
|
|
<div className="card p-5 space-y-3">
|
|
<h2 className="font-semibold text-gray-700">Σύστημα</h2>
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
<div className="text-gray-500">Uptime</div>
|
|
<div className="font-medium text-gray-800">{formatUptime(status?.uptime_seconds ?? 0)}</div>
|
|
<div className="text-gray-500">Άδεια χρήσης</div>
|
|
<div className={`font-medium ${status?.licensed ? 'text-green-700' : 'text-red-600'}`}>
|
|
{status?.licensed ? 'Ενεργή' : 'Ανενεργή'}
|
|
</div>
|
|
<div className="text-gray-500">Κατάσταση</div>
|
|
<div className={`font-medium ${status?.locked ? 'text-red-600' : 'text-green-700'}`}>
|
|
{status?.locked ? 'Κλειδωμένο' : 'Λειτουργικό'}
|
|
</div>
|
|
{status?.expires_at && (
|
|
<>
|
|
<div className="text-gray-500">Λήξη άδειας</div>
|
|
<div className="font-medium text-gray-800">{new Date(status.expires_at).toLocaleDateString('el-GR')}</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Printers */}
|
|
<div className="card divide-y divide-gray-100">
|
|
<div className="px-5 py-4">
|
|
<h2 className="font-semibold text-gray-700">Εκτυπωτές</h2>
|
|
</div>
|
|
|
|
{(!status?.printers || status.printers.length === 0) && (
|
|
<p className="px-5 py-6 text-center text-gray-400 text-sm">Δεν βρέθηκαν εκτυπωτές.</p>
|
|
)}
|
|
|
|
{status?.printers?.map(p => (
|
|
<div key={p.id} className="flex items-center gap-4 px-5 py-3">
|
|
<div className="flex-1">
|
|
<p className="font-medium text-gray-800">{p.name}</p>
|
|
</div>
|
|
<span className={`text-xs font-semibold px-2 py-0.5 rounded-full ${p.reachable ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-600'}`}>
|
|
{p.reachable ? 'Προσβάσιμος' : 'Μη προσβάσιμος'}
|
|
</span>
|
|
<button
|
|
onClick={() => testPrint.mutate(p.id)}
|
|
disabled={testPrint.isPending}
|
|
className="btn btn-secondary text-sm px-3 py-1.5 min-h-0 h-9"
|
|
>
|
|
Test Print
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Sysadmin-only section */}
|
|
{user?.role === 'sysadmin' && (
|
|
<div className="card p-5 space-y-3 border-amber-200 bg-amber-50">
|
|
<h2 className="font-semibold text-amber-800">Sysadmin</h2>
|
|
<p className="text-sm text-amber-700">Έλεγχος κλειδώματος συστήματος.</p>
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => client.post('/api/system/unlock').then(() => { toast.success('Ξεκλειδώθηκε'); qc.invalidateQueries({ queryKey: ['system-status'] }) })}
|
|
className="btn btn-primary text-sm"
|
|
>
|
|
Ξεκλείδωμα
|
|
</button>
|
|
<button
|
|
onClick={() => client.post('/api/system/lock').then(() => { toast.success('Κλειδώθηκε'); qc.invalidateQueries({ queryKey: ['system-status'] }) })}
|
|
className="btn btn-danger text-sm"
|
|
>
|
|
Κλείδωμα
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|