import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import toast from 'react-hot-toast' import client from '../api/client' // ── Helpers ─────────────────────────────────────────────────────────────────── function fmt(n) { if (n == null) return '—' return n.toLocaleString('el-GR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €' } function fmtDateTime(iso) { if (!iso) return '—' return new Date(iso).toLocaleString('el-GR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) } function daysSince(iso) { if (!iso) return null const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86400000) if (days === 0) return 'Σήμερα' if (days === 1) return '1 μέρα' return `${days} μέρες` } const STATUS_CONFIG = { open: { label: 'Ανοιχτή', bg: '#fee2e2', color: '#dc2626' }, closed: { label: 'Κλειστή', bg: '#dcfce7', color: '#16a34a' }, forgiven: { label: 'Χαρίστηκε', bg: '#f3f4f6', color: '#6b7280' }, } // ── Pay modal ───────────────────────────────────────────────────────────────── function PayModal({ tab, onClose, onPay, isPending }) { const [amount, setAmount] = useState(String(Math.round(tab.balance * 100) / 100)) const [method, setMethod] = useState('cash') const [notes, setNotes] = useState('') const parsed = parseFloat(amount) const canPay = parsed > 0 && parsed <= tab.balance + 0.005 return (
Πληρωμή Καρτέλας
{tab.customer_name}
Υπόλοιπο καρτέλας {fmt(tab.balance)}
setAmount(e.target.value)} autoFocus style={{ width: '100%', padding: '9px 12px', border: '1.5px solid #e5e7eb', borderRadius: 8, fontSize: 16, fontWeight: 600, outline: 'none', boxSizing: 'border-box' }} />
{[['cash', 'Μετρητά'], ['card', 'Κάρτα'], ['other', 'Άλλο']].map(([v, l]) => ( ))}
setNotes(e.target.value)} placeholder="προαιρετικό" style={{ width: '100%', padding: '8px 11px', border: '1px solid #e5e7eb', borderRadius: 8, fontSize: 13, outline: 'none', boxSizing: 'border-box' }} />
) } // ── Forgive confirm ─────────────────────────────────────────────────────────── function ForgiveModal({ tab, onClose, onForgive, isPending }) { const [reason, setReason] = useState('') return (
Χάρισμα Καρτέλας
{tab.customer_name} — υπόλοιπο {fmt(tab.balance)}
Το υπόλοιπο των {fmt(tab.balance)} θα διαγραφεί. Η ενέργεια δεν αναιρείται.
setReason(e.target.value)} placeholder="π.χ. Καλή θέληση" style={{ width: '100%', padding: '8px 11px', border: '1px solid #e5e7eb', borderRadius: 8, fontSize: 13, outline: 'none', boxSizing: 'border-box' }} />
) } // ── Tab detail card ─────────────────────────────────────────────────────────── function TabDetail({ tab, onPay, onClose, onForgive }) { const sc = STATUS_CONFIG[tab.status] || STATUS_CONFIG.open const canClose = tab.status === 'open' && tab.balance <= 0.005 && tab.entries.length > 0 return (
{/* Header */}
{tab.customer_name}
Άνοιγμα: {fmtDateTime(tab.opened_at)} · {daysSince(tab.opened_at)} ανοιχτή
0 ? '#dc2626' : '#16a34a' }}> {fmt(tab.balance)} {sc.label}
{/* Stats */}
{[ { label: 'Συνολικές χρεώσεις', value: fmt(tab.total_charged) }, { label: 'Πληρωμένο', value: fmt(tab.total_paid), color: '#16a34a' }, { label: 'Υπόλοιπο', value: fmt(tab.balance), color: tab.balance > 0 ? '#dc2626' : '#16a34a' }, ].map(s => (
{s.label}
{s.value}
))}
{/* Entries */} {tab.entries.length > 0 && (
Χρεώσεις
{tab.entries.map(e => (
{e.description || `Χρέωση #${e.id}`} {fmt(e.amount)}
))}
)} {/* Payments */} {tab.payments.length > 0 && (
Πληρωμές
{tab.payments.map(p => (
{p.received_by_name} · {fmtDateTime(p.created_at)} {p.payment_method && ({p.payment_method})} {p.notes && — {p.notes}} {fmt(p.amount)}
))}
)} {/* Actions */} {tab.status === 'open' && (
{tab.balance > 0.005 && ( )} {canClose && ( )}
)}
) } // ── Main page ───────────────────────────────────────────────────────────────── export default function TabsPage() { const qc = useQueryClient() const [showAll, setShowAll] = useState(false) const [modal, setModal] = useState(null) // { type: 'pay'|'forgive', tab } const { data: tabs = [], isLoading } = useQuery({ queryKey: ['tabs', showAll], queryFn: () => client.get('/api/tabs/', { params: showAll ? { tab_status: 'all' } : {} }).then(r => r.data), staleTime: 15_000, }) // Refetch tabs with all statuses when toggled const { data: allTabs = [] } = useQuery({ queryKey: ['tabs-all'], queryFn: () => client.get('/api/tabs/', { params: { tab_status: 'closed' } }).then(r => r.data), staleTime: 30_000, enabled: showAll, }) const payTab = useMutation({ mutationFn: ({ id, amount, payment_method, notes }) => client.post(`/api/tabs/${id}/pay`, { amount, payment_method, notes }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['tabs'] }) qc.invalidateQueries({ queryKey: ['tabs-all'] }) setModal(null) toast.success('Πληρωμή καταγράφηκε') }, onError: (e) => toast.error(e?.response?.data?.detail || 'Σφάλμα'), }) const closeTab = useMutation({ mutationFn: (id) => client.post(`/api/tabs/${id}/close`, {}), onSuccess: () => { qc.invalidateQueries({ queryKey: ['tabs'] }) toast.success('Καρτέλα έκλεισε') }, onError: (e) => toast.error(e?.response?.data?.detail || 'Σφάλμα'), }) const forgiveTab = useMutation({ mutationFn: ({ id, reason }) => client.post(`/api/tabs/${id}/forgive`, { reason }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['tabs'] }) setModal(null) toast.success('Το υπόλοιπο χαρίστηκε') }, onError: () => toast.error('Σφάλμα'), }) const openTabs = tabs.filter(t => t.status === 'open') const totalOutstanding = openTabs.reduce((s, t) => s + t.balance, 0) return (
{/* Header */}

Καρτέλες

{openTabs.length} ανοιχτές καρτέλες · Σύνολο οφειλόμενων:{' '} 0 ? '#dc2626' : '#16a34a' }}>{fmt(totalOutstanding)}

{/* Tab list */}
{isLoading &&
Φόρτωση…
} {!isLoading && openTabs.length === 0 && (
Δεν υπάρχουν ανοιχτές καρτέλες.
)} {openTabs.map(tab => ( setModal({ type: 'pay', tab: t })} onClose={t => closeTab.mutate(t.id)} onForgive={t => setModal({ type: 'forgive', tab: t })} /> ))} {showAll && allTabs.length > 0 && ( <>
Κλειστές / Χαρισμένες
{allTabs.map(tab => ( {}} onClose={() => {}} onForgive={() => {}} /> ))} )}
{modal?.type === 'pay' && ( setModal(null)} isPending={payTab.isPending} onPay={(amount, payment_method, notes) => payTab.mutate({ id: modal.tab.id, amount, payment_method, notes })} /> )} {modal?.type === 'forgive' && ( setModal(null)} isPending={forgiveTab.isPending} onForgive={(reason) => forgiveTab.mutate({ id: modal.tab.id, reason })} /> )}
) }