feat: bump client-services (accumulated feature work + deploy fixes)
Snapshot of in-progress work across local_backend, manager_dashboard, and waiter_pwa (pricing, chat, fiscal, prep zones, recovery codes, CRM, inventory, permissions), plus the nginx/docker-compose deploy fixes for the Unraid + NPM reverse-proxy setup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import toast from 'react-hot-toast'
|
||||
import { ChevronDown, Search } from 'lucide-react'
|
||||
import client from '../api/client'
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
@@ -121,130 +122,174 @@ function ForgiveModal({ tab, onClose, onForgive, isPending }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ── Tab detail card ───────────────────────────────────────────────────────────
|
||||
// ── Tab detail card (collapsible) ─────────────────────────────────────────────
|
||||
|
||||
function TabDetail({ tab, onPay, onClose, onForgive }) {
|
||||
function TabDetail({ tab, onPay, onClose, onForgive, defaultExpanded = false }) {
|
||||
const [expanded, setExpanded] = useState(defaultExpanded)
|
||||
const sc = STATUS_CONFIG[tab.status] || STATUS_CONFIG.open
|
||||
const canClose = tab.status === 'open' && tab.balance <= 0.005 && tab.entries.length > 0
|
||||
|
||||
return (
|
||||
<div style={{ border: '1px solid #e5e7eb', borderRadius: 12, overflow: 'hidden', background: 'white' }}>
|
||||
{/* Header */}
|
||||
<div style={{ padding: '14px 18px', background: '#f9fafb', borderBottom: '1px solid #e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 8 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: '#111315' }}>{tab.customer_name}</div>
|
||||
<div style={{ fontSize: 12, color: '#9ca3af', marginTop: 1 }}>
|
||||
Άνοιγμα: {fmtDateTime(tab.opened_at)} · {daysSince(tab.opened_at)} ανοιχτή
|
||||
{/* Clickable header — always visible */}
|
||||
<button
|
||||
onClick={() => setExpanded(e => !e)}
|
||||
style={{
|
||||
width: '100%', textAlign: 'left',
|
||||
padding: '14px 18px', background: '#f9fafb',
|
||||
borderBottom: expanded ? '1px solid #e5e7eb' : 'none',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
flexWrap: 'wrap', gap: 8, cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
style={{
|
||||
flexShrink: 0, color: '#9ca3af',
|
||||
transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)',
|
||||
transition: 'transform 0.18s',
|
||||
}}
|
||||
/>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: '#111315' }}>{tab.customer_name}</div>
|
||||
<div style={{ fontSize: 12, color: '#9ca3af', marginTop: 1 }}>
|
||||
Άνοιγμα: {fmtDateTime(tab.opened_at)} · {daysSince(tab.opened_at)} ανοιχτή
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
|
||||
<span style={{ fontSize: 22, fontWeight: 800, color: tab.balance > 0 ? '#dc2626' : '#16a34a' }}>
|
||||
{fmt(tab.balance)}
|
||||
</span>
|
||||
<span style={{ fontSize: 11.5, fontWeight: 700, padding: '2px 8px', borderRadius: 99, background: sc.bg, color: sc.color }}>{sc.label}</span>
|
||||
<span style={{ fontSize: 11.5, fontWeight: 700, padding: '2px 8px', borderRadius: 99, background: sc.bg, color: sc.color }}>
|
||||
{sc.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Stats */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 1, background: '#f0f0ef' }}>
|
||||
{[
|
||||
{ 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 => (
|
||||
<div key={s.label} style={{ background: 'white', padding: '10px 14px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 10, color: '#9ca3af', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.04em' }}>{s.label}</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 800, color: s.color || '#111315', marginTop: 2 }}>{s.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Entries */}
|
||||
{tab.entries.length > 0 && (
|
||||
<div style={{ padding: '12px 18px', borderBottom: '1px solid #f0f0ef' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Χρεώσεις</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{tab.entries.map(e => (
|
||||
<div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13 }}>
|
||||
<span style={{ color: '#374151' }}>{e.description || `Χρέωση #${e.id}`}</span>
|
||||
<span style={{ fontWeight: 600, color: '#dc2626', whiteSpace: 'nowrap', marginLeft: 12 }}>{fmt(e.amount)}</span>
|
||||
{/* Expanded body */}
|
||||
{expanded && (
|
||||
<>
|
||||
{/* Stats */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 1, background: '#f0f0ef' }}>
|
||||
{[
|
||||
{ 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 => (
|
||||
<div key={s.label} style={{ background: 'white', padding: '10px 14px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 10, color: '#9ca3af', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.04em' }}>{s.label}</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 800, color: s.color || '#111315', marginTop: 2 }}>{s.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Payments */}
|
||||
{tab.payments.length > 0 && (
|
||||
<div style={{ padding: '12px 18px', borderBottom: '1px solid #f0f0ef' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Πληρωμές</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{tab.payments.map(p => (
|
||||
<div key={p.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13 }}>
|
||||
<span style={{ color: '#374151' }}>
|
||||
{p.received_by_name} · {fmtDateTime(p.created_at)}
|
||||
{p.payment_method && <span style={{ color: '#9ca3af', marginLeft: 6 }}>({p.payment_method})</span>}
|
||||
{p.notes && <span style={{ color: '#9ca3af', marginLeft: 6 }}>— {p.notes}</span>}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, color: '#16a34a', whiteSpace: 'nowrap', marginLeft: 12 }}>{fmt(p.amount)}</span>
|
||||
{/* Entries */}
|
||||
{tab.entries.length > 0 && (
|
||||
<div style={{ padding: '12px 18px', borderBottom: '1px solid #f0f0ef' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Χρεώσεις</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{tab.entries.map(e => (
|
||||
<div key={e.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13 }}>
|
||||
<span style={{ color: '#374151' }}>{e.description || `Χρέωση #${e.id}`}</span>
|
||||
<span style={{ fontWeight: 600, color: '#dc2626', whiteSpace: 'nowrap', marginLeft: 12 }}>{fmt(e.amount)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
{tab.status === 'open' && (
|
||||
<div style={{ padding: '12px 18px', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{tab.balance > 0.005 && (
|
||||
<button onClick={() => onPay(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: 'none', background: '#16a34a', color: 'white', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}>
|
||||
Πληρωμή
|
||||
</button>
|
||||
{/* Payments */}
|
||||
{tab.payments.length > 0 && (
|
||||
<div style={{ padding: '12px 18px', borderBottom: '1px solid #f0f0ef' }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 8 }}>Πληρωμές</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{tab.payments.map(p => (
|
||||
<div key={p.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13 }}>
|
||||
<span style={{ color: '#374151' }}>
|
||||
{p.received_by_name} · {fmtDateTime(p.created_at)}
|
||||
{p.payment_method && <span style={{ color: '#9ca3af', marginLeft: 6 }}>({p.payment_method})</span>}
|
||||
{p.notes && <span style={{ color: '#9ca3af', marginLeft: 6 }}>— {p.notes}</span>}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, color: '#16a34a', whiteSpace: 'nowrap', marginLeft: 12 }}>{fmt(p.amount)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{canClose && (
|
||||
<button onClick={() => onClose(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid #d1d5db', background: 'white', color: '#374151', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}>
|
||||
Κλείσιμο Καρτέλας
|
||||
</button>
|
||||
|
||||
{/* Actions */}
|
||||
{tab.status === 'open' && (
|
||||
<div style={{ padding: '12px 18px', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{tab.balance > 0.005 && (
|
||||
<button onClick={() => onPay(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: 'none', background: '#16a34a', color: 'white', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}>
|
||||
Πληρωμή
|
||||
</button>
|
||||
)}
|
||||
{canClose && (
|
||||
<button onClick={() => onClose(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid #d1d5db', background: 'white', color: '#374151', fontSize: 13, fontWeight: 600, cursor: 'pointer' }}>
|
||||
Κλείσιμο Καρτέλας
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => onForgive(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid #fecaca', background: 'white', color: '#dc2626', fontSize: 13, fontWeight: 500, cursor: 'pointer', marginLeft: 'auto' }}>
|
||||
Χάρισμα Υπολοίπου
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => onForgive(tab)}
|
||||
style={{ padding: '7px 16px', borderRadius: 8, border: '1px solid #fecaca', background: 'white', color: '#dc2626', fontSize: 13, fontWeight: 500, cursor: 'pointer', marginLeft: 'auto' }}>
|
||||
Χάρισμα Υπολοίπου
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Filter chip ───────────────────────────────────────────────────────────────
|
||||
|
||||
function FilterChip({ label, active, onClick }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-3 py-1 rounded-full text-[12px] font-semibold border transition-colors ${
|
||||
active
|
||||
? 'bg-slate-800 text-white border-slate-800'
|
||||
: 'bg-white text-slate-500 border-slate-200 hover:border-slate-400 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Main page ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function TabsPage() {
|
||||
const qc = useQueryClient()
|
||||
const [showAll, setShowAll] = useState(false)
|
||||
const [modal, setModal] = useState(null) // { type: 'pay'|'forgive', tab }
|
||||
const [statusFilter, setStatusFilter] = useState('open') // 'open' | 'closed' | 'all'
|
||||
const [search, setSearch] = useState('')
|
||||
const [modal, setModal] = useState(null)
|
||||
|
||||
const { data: tabs = [], isLoading } = useQuery({
|
||||
queryKey: ['tabs', showAll],
|
||||
queryFn: () => client.get('/api/tabs/', { params: showAll ? { tab_status: 'all' } : {} }).then(r => r.data),
|
||||
// Fetch open and closed separately — merge for client-side filtering
|
||||
const { data: openTabs = [], isLoading: loadingOpen } = useQuery({
|
||||
queryKey: ['tabs-open'],
|
||||
queryFn: () => client.get('/api/tabs/').then(r => r.data),
|
||||
staleTime: 15_000,
|
||||
})
|
||||
|
||||
// Refetch tabs with all statuses when toggled
|
||||
const { data: allTabs = [] } = useQuery({
|
||||
queryKey: ['tabs-all'],
|
||||
const { data: closedTabs = [], isLoading: loadingClosed } = useQuery({
|
||||
queryKey: ['tabs-closed'],
|
||||
queryFn: () => client.get('/api/tabs/', { params: { tab_status: 'closed' } }).then(r => r.data),
|
||||
staleTime: 30_000,
|
||||
enabled: showAll,
|
||||
staleTime: 15_000,
|
||||
})
|
||||
const tabs = [...openTabs, ...closedTabs]
|
||||
const isLoading = loadingOpen || loadingClosed
|
||||
|
||||
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'] })
|
||||
qc.invalidateQueries({ queryKey: ['tabs-open'] }); qc.invalidateQueries({ queryKey: ['tabs-closed'] })
|
||||
setModal(null)
|
||||
toast.success('Πληρωμή καταγράφηκε')
|
||||
},
|
||||
@@ -254,7 +299,7 @@ export default function TabsPage() {
|
||||
const closeTab = useMutation({
|
||||
mutationFn: (id) => client.post(`/api/tabs/${id}/close`, {}),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['tabs'] })
|
||||
qc.invalidateQueries({ queryKey: ['tabs-open'] }); qc.invalidateQueries({ queryKey: ['tabs-closed'] })
|
||||
toast.success('Καρτέλα έκλεισε')
|
||||
},
|
||||
onError: (e) => toast.error(e?.response?.data?.detail || 'Σφάλμα'),
|
||||
@@ -263,61 +308,85 @@ export default function TabsPage() {
|
||||
const forgiveTab = useMutation({
|
||||
mutationFn: ({ id, reason }) => client.post(`/api/tabs/${id}/forgive`, { reason }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['tabs'] })
|
||||
qc.invalidateQueries({ queryKey: ['tabs-open'] }); qc.invalidateQueries({ queryKey: ['tabs-closed'] })
|
||||
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)
|
||||
// Live client-side filtering
|
||||
const filtered = useMemo(() => {
|
||||
let result = tabs
|
||||
if (statusFilter !== 'all') {
|
||||
result = result.filter(t => {
|
||||
if (statusFilter === 'open') return t.status === 'open'
|
||||
if (statusFilter === 'closed') return t.status === 'closed' || t.status === 'forgiven'
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (search.trim()) {
|
||||
const q = search.trim().toLowerCase()
|
||||
result = result.filter(t => t.customer_name?.toLowerCase().includes(q))
|
||||
}
|
||||
return result
|
||||
}, [tabs, statusFilter, search])
|
||||
|
||||
const openCount = tabs.filter(t => t.status === 'open').length
|
||||
const closedCount = tabs.filter(t => t.status === 'closed' || t.status === 'forgiven').length
|
||||
const totalOutstanding = tabs.filter(t => t.status === 'open').reduce((s, t) => s + t.balance, 0)
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
{/* Header */}
|
||||
<div style={{ padding: '18px 28px 14px', borderBottom: '1px solid #f0f0ef', flexShrink: 0, background: 'white', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<h1 style={{ margin: 0, fontSize: 20, fontWeight: 800, color: '#111315' }}>Καρτέλες</h1>
|
||||
<p style={{ margin: '2px 0 0', fontSize: 13, color: '#9ca3af' }}>
|
||||
{openTabs.length} ανοιχτές καρτέλες · Σύνολο οφειλόμενων:{' '}
|
||||
<strong style={{ color: totalOutstanding > 0 ? '#dc2626' : '#16a34a' }}>{fmt(totalOutstanding)}</strong>
|
||||
</p>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
{/* Toolbar */}
|
||||
<div className="px-6 py-3 border-b border-slate-200 bg-white shrink-0 flex items-center gap-3 flex-wrap">
|
||||
{/* Search */}
|
||||
<div className="relative flex items-center">
|
||||
<Search className="absolute left-2.5 h-3.5 w-3.5 text-slate-400 pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="Αναζήτηση πελάτη…"
|
||||
className="pl-8 pr-3 py-1.5 text-[13px] bg-slate-50 border border-slate-200 rounded-lg w-48 focus:outline-none focus:ring-2 focus:ring-sky-500/30 focus:border-sky-400 transition placeholder:text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status filter chips */}
|
||||
<div className="flex items-center gap-2">
|
||||
<FilterChip label={`Ανοιχτές (${openCount})`} active={statusFilter === 'open'} onClick={() => setStatusFilter('open')} />
|
||||
<FilterChip label={`Κλειστές (${closedCount})`} active={statusFilter === 'closed'} onClick={() => setStatusFilter('closed')} />
|
||||
<FilterChip label="Όλες" active={statusFilter === 'all'} onClick={() => setStatusFilter('all')} />
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
<div className="ml-auto text-[12px] text-slate-400">
|
||||
{statusFilter === 'open' && totalOutstanding > 0 && (
|
||||
<span>Σύνολο: <strong className="text-red-600">{fmt(totalOutstanding)}</strong></span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAll(s => !s)}
|
||||
style={{ padding: '7px 14px', borderRadius: 8, border: '1px solid #e5e7eb', background: 'white', fontSize: 12.5, cursor: 'pointer', color: '#6b7280' }}
|
||||
>
|
||||
{showAll ? 'Μόνο ανοιχτές' : 'Εμφάνιση κλειστών'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab list */}
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '16px 28px', display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{isLoading && <div style={{ color: '#9ca3af', fontSize: 13, textAlign: 'center', padding: '48px 0' }}>Φόρτωση…</div>}
|
||||
{!isLoading && openTabs.length === 0 && (
|
||||
<div style={{ textAlign: 'center', color: '#9ca3af', fontSize: 14, padding: '48px 0' }}>
|
||||
Δεν υπάρχουν ανοιχτές καρτέλες.
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4 flex flex-col gap-3">
|
||||
{isLoading && (
|
||||
<div className="text-center text-slate-400 text-[13px] py-12">Φόρτωση…</div>
|
||||
)}
|
||||
{!isLoading && filtered.length === 0 && (
|
||||
<div className="text-center text-slate-400 text-[14px] py-12">
|
||||
{search ? 'Δεν βρέθηκαν αποτελέσματα.' : 'Δεν υπάρχουν καρτέλες.'}
|
||||
</div>
|
||||
)}
|
||||
{openTabs.map(tab => (
|
||||
{filtered.map(tab => (
|
||||
<TabDetail
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
defaultExpanded={false}
|
||||
onPay={t => setModal({ type: 'pay', tab: t })}
|
||||
onClose={t => closeTab.mutate(t.id)}
|
||||
onForgive={t => setModal({ type: 'forgive', tab: t })}
|
||||
/>
|
||||
))}
|
||||
|
||||
{showAll && allTabs.length > 0 && (
|
||||
<>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.05em', marginTop: 8 }}>Κλειστές / Χαρισμένες</div>
|
||||
{allTabs.map(tab => (
|
||||
<TabDetail key={tab.id} tab={tab} onPay={() => {}} onClose={() => {}} onForgive={() => {}} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{modal?.type === 'pay' && (
|
||||
|
||||
Reference in New Issue
Block a user