Files
simple-pos-system/manager_dashboard/src/pages/SettingsPage.jsx
bonamin 8f52156f5b Phase 3: scaffold Manager Dashboard — all pages, layout, routing
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.
2026-04-20 17:20:46 +03:00

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>
)
}