diff --git a/manager_dashboard/src/pages/reports/operations/PrinterHistory.jsx b/manager_dashboard/src/pages/reports/operations/PrinterHistory.jsx
index 56b5e20..536c470 100644
--- a/manager_dashboard/src/pages/reports/operations/PrinterHistory.jsx
+++ b/manager_dashboard/src/pages/reports/operations/PrinterHistory.jsx
@@ -1,6 +1,7 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
-import { Printer, AlertCircle, Trophy } from 'lucide-react'
+import { Printer, AlertCircle, Hash, Euro, FileText, X, ChevronDown, Loader2 } from 'lucide-react'
+import toast from 'react-hot-toast'
import client from '../../../api/client'
import { FilterBar, FilterSelect, FilterDateInput, WorkDayDateToggle } from '../shared/FilterBar'
import { Panel, DataTable, THead, TH, TR, TD, StatusBadge } from '../shared/TablePrimitives'
@@ -8,17 +9,263 @@ import StatCard from '../shared/StatCard'
import EmptyState from '../shared/EmptyState'
import SkeletonTable from '../shared/SkeletonTable'
import ExportButton from '../shared/ExportButton'
-import { fmtNum, fmtDate, fmtTime, fmtDateTime } from '../shared/reportDesignTokens'
+import { fmtNum, fmtEUR, fmtDate, fmtTime, fmtDateTime } from '../shared/reportDesignTokens'
function today() { return new Date().toISOString().slice(0, 10) }
function monthAgo() { const d = new Date(); d.setDate(d.getDate() - 30); return d.toISOString().slice(0, 10) }
+// ─── Print Summary Modal ────────────────────────────────────────────────────
+
+const PRINT_TYPES = [
+ {
+ id: 'quick',
+ label: 'Γρήγορη Σύνοψη',
+ desc: 'Συγκεντρωτικά στατιστικά — λίγο χαρτί',
+ },
+ {
+ id: 'orders',
+ label: 'Όλες οι Παραγγελίες',
+ desc: 'Κάθε παραγγελία με σύνολο, χωρίς ανάλυση ειδών',
+ },
+ {
+ id: 'detailed',
+ label: 'Πλήρης Ανάλυση',
+ desc: 'Κάθε παραγγελία + όλα τα είδη — καταναλώνει πολύ χαρτί',
+ },
+]
+
+function PrintSummaryModal({ onClose, logs, stats, printers, printerF, queryParams }) {
+ const [printType, setPrintType] = useState('quick')
+ const [targetPrinter, setTargetPrinter] = useState('browser')
+ const [printing, setPrinting] = useState(false)
+
+ const printerOptions = [
+ { value: 'browser', label: 'Εκτύπωση μέσω browser' },
+ ...printers.map(p => ({ value: String(p.id), label: p.name })),
+ ]
+
+ async function handlePrint() {
+ if (targetPrinter !== 'browser') {
+ await handleThermalPrint()
+ return
+ }
+ handleBrowserPrint()
+ }
+
+ async function handleThermalPrint() {
+ // Map frontend print types to backend modes
+ // quick/orders → simple, detailed → extensive
+ const mode = printType === 'detailed' ? 'extensive' : 'simple'
+ const printerTargetId = printerF === 'all' ? 0 : parseInt(printerF, 10)
+
+ const fromDt = queryParams.from || (new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10) + 'T00:00:00')
+ const toDt = queryParams.to || (new Date().toISOString().slice(0, 10) + 'T23:59:59')
+
+ setPrinting(true)
+ try {
+ await client.post('/api/reports/print/printer', {
+ printer_target_id: printerTargetId,
+ printer_id: parseInt(targetPrinter, 10),
+ mode,
+ from_dt: fromDt,
+ to_dt: toDt,
+ })
+ toast.success('Η εκτύπωση στάλθηκε στον εκτυπωτή')
+ onClose()
+ } catch {
+ toast.error('Αποτυχία αποστολής στον εκτυπωτή')
+ } finally {
+ setPrinting(false)
+ }
+ }
+
+ function handleBrowserPrint() {
+ const win = window.open('', '_blank', 'width=800,height=700')
+ if (!win) { onClose(); return }
+
+ const styles = `
+
+ `
+
+ const filterLabel = printerF === 'all'
+ ? 'Όλοι οι Εκτυπωτές'
+ : (printers.find(p => String(p.id) === printerF)?.name || printerF)
+
+ let body = ''
+
+ if (printType === 'quick') {
+ const topItems = Object.entries(stats.itemCounts).sort((a, b) => b[1] - a[1]).slice(0, 3)
+ const topWaiters = Object.entries(stats.waiterCounts).sort((a, b) => b[1] - a[1]).slice(0, 3)
+ const avgItems = stats.total > 0 ? (stats.totalItemQty / stats.total).toFixed(1) : '—'
+
+ body = `
+
Σύνοψη Εκτυπωτή
+
+
Εκτυπωτής${filterLabel}
+
Περίοδος${stats.periodLabel}
+
+ Στατιστικά
+
+
Συνολικές Εκτυπώσεις${fmtNum(stats.total)}
+
Αποτυχημένες Εκτυπώσεις${fmtNum(stats.failed)}
+ ${stats.totalAmount != null ? `
Συνολικό Ποσό Παραγγελιών${fmtEUR(stats.totalAmount)}
` : ''}
+
Μέσος Αριθμός Ειδών ανά Παραγγελία${avgItems}
+
+ Top 3 Πιο Εκτυπωμένα Είδη
+
+
+ ${topItems.length ? topItems.map(([name, qty], i) => `- ${i + 1}. ${name} — ${qty} τεμ.
`).join('') : '- —
'}
+
+
+ ${topWaiters.length ? `
+ Εκτυπώσεις ανά Σερβιτόρο
+
+ ${topWaiters.map(([name, cnt]) => `
${name}${cnt}
`).join('')}
+
` : ''}
+ `
+ } else if (printType === 'orders') {
+ body = `
+ Λίστα Παραγγελιών
+
+
Εκτυπωτής${filterLabel}
+
Σύνολο Εγγραφών${logs.length}
+
+ Παραγγελίες
+ ${logs.map(j => `
+
+
+
Εκτυπωτής${j.printer_name}
+
Αποτέλεσμα${j.success ? '✓ Επιτυχία' : '✗ Αποτυχία'}
+ ${j.order_total != null ? `
Σύνολο${fmtEUR(j.order_total)}
` : ''}
+
+ `).join('')}
+ `
+ } else {
+ body = `
+ Πλήρης Ανάλυση Εκτυπώσεων
+
+
Εκτυπωτής${filterLabel}
+
Σύνολο Εγγραφών${logs.length}
+
+ ${logs.map(j => `
+
+
+
Εκτυπωτής${j.printer_name}
+
Αποτέλεσμα${j.success ? '✓ Επιτυχία' : '✗ Αποτυχία'}
+ ${(j.items || []).map(i => `
${i.name}×${i.quantity}
`).join('')}
+
+ `).join('')}
+ `
+ }
+
+ win.document.write(`Ιστορικό Εκτυπωτή${styles}${body}`)
+ win.document.close()
+ win.focus()
+ win.print()
+ onClose()
+ } // end handleBrowserPrint
+
+ return (
+ { if (e.target === e.currentTarget) onClose() }}
+ >
+
+
+
Εκτύπωση Σύνοψης
+
+
+
+
+
+
Τύπος Εκτύπωσης
+
+ {PRINT_TYPES.map(pt => (
+
+ ))}
+
+
+
+
+
Εκτυπωτής Προορισμού
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+// ─── Main Component ─────────────────────────────────────────────────────────
+
export default function PrinterHistory() {
const [mode, setMode] = useState('range')
const [from, setFrom] = useState(monthAgo())
const [to, setTo] = useState(today())
const [businessDayId, setBusinessDayId] = useState('all')
const [printerF, setPrinterF] = useState('all')
+ const [showPrintModal, setShowPrintModal] = useState(false)
const { data: printersData } = useQuery({ queryKey: ['meta-printers'], queryFn: () => client.get('/api/reports/meta/printers').then(r => r.data), staleTime: 5 * 60 * 1000 })
const { data: bdData } = useQuery({ queryKey: ['business-days-list'], queryFn: () => client.get('/api/reports/business-days').then(r => r.data), staleTime: 60 * 1000 })
@@ -35,17 +282,35 @@ export default function PrinterHistory() {
staleTime: 60 * 1000,
})
- const printerOptions = [{ value: 'all', label: 'Όλοι οι Εκτυπωτές' }, ...((printersData?.printers || []).map(p => ({ value: String(p.id), label: p.name })))]
+ const printers = printersData?.printers || []
+ const printerOptions = [{ value: 'all', label: 'Όλοι οι Εκτυπωτές' }, ...printers.map(p => ({ value: String(p.id), label: p.name }))]
const bdOptions = [{ value: 'all', label: 'Όλες οι Εργάσιμες Μέρες' }, ...((bdData?.business_days || []).map(bd => ({ value: String(bd.id), label: `${fmtDate(bd.opened_at)} · ${fmtTime(bd.opened_at)}` })))]
const logs = data?.logs || []
const total = data?.total || 0
const failed = data?.failed || 0
- // Find most printed item
+ // Derived stats for StatCards + Print modal
const itemCounts = {}
- logs.forEach(l => (l.items || []).forEach(i => { itemCounts[i.name] = (itemCounts[i.name] || 0) + i.quantity }))
+ const waiterCounts = {}
+ let totalItemQty = 0
+ let totalAmount = null
+
+ logs.forEach(l => {
+ if (l.waiter) waiterCounts[l.waiter] = (waiterCounts[l.waiter] || 0) + 1
+ if (l.order_total != null) totalAmount = (totalAmount || 0) + l.order_total
+ ;(l.items || []).forEach(i => {
+ itemCounts[i.name] = (itemCounts[i.name] || 0) + i.quantity
+ totalItemQty += i.quantity
+ })
+ })
+
const topItem = Object.entries(itemCounts).sort((a, b) => b[1] - a[1])[0]
+ const periodLabel = mode === 'workday'
+ ? (bdOptions.find(o => o.value === businessDayId)?.label || 'Εργάσιμη Μέρα')
+ : `${from} → ${to}`
+
+ const stats = { total, failed, totalAmount, itemCounts, waiterCounts, totalItemQty, periodLabel, topItem }
if (isLoading) return
if (isError) return (
@@ -58,7 +323,16 @@ export default function PrinterHistory() {
return (
+
+
+
+
}>
{mode === 'workday' ? (
@@ -73,10 +347,17 @@ export default function PrinterHistory() {
-
+
0 ? 'ελέγξτε τον εκτυπωτή' : 'όλα καλά'} icon={AlertCircle} />
-
+
+
@@ -115,6 +396,17 @@ export default function PrinterHistory() {
)}
+
+ {showPrintModal && (
+
setShowPrintModal(false)}
+ logs={logs}
+ stats={stats}
+ printers={printers}
+ printerF={printerF}
+ queryParams={queryParams}
+ />
+ )}
)
}