import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { usePOSStore } from '@/stores/pos.store' import { posMutations, posKeys, drawerReportOptions, type DrawerSession } from '@/api/pos' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Separator } from '@/components/ui/separator' import { Badge } from '@/components/ui/badge' import { ArrowDownToLine, ArrowUpFromLine } from 'lucide-react' import { toast } from 'sonner' import { ManagerOverrideDialog, requiresOverride } from './pos-manager-override' interface POSDrawerDialogProps { open: boolean onOpenChange: (open: boolean) => void drawer: DrawerSession | null } export function POSDrawerDialog({ open, onOpenChange, drawer }: POSDrawerDialogProps) { const queryClient = useQueryClient() const { locationId, registerId, setDrawerSession } = usePOSStore() const isOpen = drawer?.status === 'open' const [openingBalance, setOpeningBalance] = useState('200') const [closingBalance, setClosingBalance] = useState('') const [notes, setNotes] = useState('') const [adjustView, setAdjustView] = useState<'cash_in' | 'cash_out' | null>(null) const [adjAmount, setAdjAmount] = useState('') const [adjReason, setAdjReason] = useState('') const [overrideOpen, setOverrideOpen] = useState(false) const [pendingAdjustView, setPendingAdjustView] = useState<'cash_in' | 'cash_out' | null>(null) const [showZReport, setShowZReport] = useState(false) const [closedDrawerId, setClosedDrawerId] = useState(null) const [showXReport, setShowXReport] = useState(false) // Z Report data (after close) const { data: reportData } = useQuery(drawerReportOptions(closedDrawerId)) // X Report data (live, for open drawer) const { data: xReportData } = useQuery(drawerReportOptions(showXReport ? drawer?.id ?? null : null)) // Fetch adjustments for open drawer const { data: adjData } = useQuery({ queryKey: posKeys.drawerAdjustments(drawer?.id ?? ''), queryFn: () => posMutations.getAdjustments(drawer!.id), enabled: !!drawer?.id && isOpen, }) const adjustments = adjData?.data ?? [] const openMutation = useMutation({ mutationFn: () => posMutations.openDrawer({ locationId: locationId ?? undefined, registerId: registerId ?? undefined, openingBalance: parseFloat(openingBalance) || 0, }), onSuccess: async (session) => { setDrawerSession(session.id) await queryClient.invalidateQueries({ queryKey: posKeys.drawer(locationId ?? '') }) toast.success('Drawer opened') onOpenChange(false) }, onError: (err) => toast.error(err.message), }) const closeMutation = useMutation({ mutationFn: () => posMutations.closeDrawer(drawer!.id, { closingBalance: parseFloat(closingBalance) || 0, notes: notes || undefined, }), onSuccess: async (session) => { setDrawerSession(null) const overShort = parseFloat(session.overShort ?? '0') if (Math.abs(overShort) < 0.01) { toast.success('Drawer closed - balanced') } else { toast.warning(`Drawer closed - ${overShort > 0 ? 'over' : 'short'} $${Math.abs(overShort).toFixed(2)}`) } // Show Z report setClosedDrawerId(session.id) setShowZReport(true) await queryClient.invalidateQueries({ queryKey: posKeys.drawer(locationId ?? '') }) }, onError: (err) => toast.error(err.message), }) const adjustMutation = useMutation({ mutationFn: () => posMutations.addAdjustment(drawer!.id, { type: adjustView!, amount: parseFloat(adjAmount) || 0, reason: adjReason, }), onSuccess: (adj) => { queryClient.invalidateQueries({ queryKey: posKeys.drawerAdjustments(drawer!.id) }) toast.success(`${adj.type === 'cash_in' ? 'Cash added' : 'Cash removed'}: $${parseFloat(adj.amount).toFixed(2)}`) setAdjustView(null) setAdjAmount('') setAdjReason('') }, onError: (err) => toast.error(err.message), }) // Z Report view (shown after drawer close) if (showZReport && reportData) { const r = reportData return ( <> { onOpenChange(o); if (!o) { setShowZReport(false); setClosedDrawerId(null) } }}> Z Report — Drawer Closed ) } // X Report view (mid-shift snapshot) if (showXReport && xReportData) { return ( <> X Report — Current Shift ) } // Adjustment entry view if (adjustView && isOpen) { const isCashIn = adjustView === 'cash_in' return ( <> {isCashIn ? 'Cash In' : 'Cash Out'}
setAdjAmount(e.target.value)} placeholder="0.00" className="h-11 text-lg" autoFocus />
setAdjReason(e.target.value)} placeholder={isCashIn ? 'e.g. Extra change' : 'e.g. Bank deposit'} className="h-11" />
) } return ( <> {isOpen ? 'Drawer' : 'Open Drawer'} {isOpen ? (
Opening Balance ${parseFloat(drawer!.openingBalance).toFixed(2)}
Opened {new Date(drawer!.openedAt).toLocaleTimeString()}
{/* Cash In / Cash Out buttons */}
{/* X Report button */} {/* Adjustment history */} {adjustments.length > 0 && ( <>
Adjustments {adjustments.map((adj) => (
{adj.type === 'cash_in' ? 'IN' : 'OUT'} {adj.reason}
{adj.type === 'cash_in' ? '+' : '-'}${parseFloat(adj.amount).toFixed(2)}
))}
)} {/* Close drawer */}
setClosingBalance(e.target.value)} placeholder="Count the cash in the drawer" className="h-11 text-lg" />
setNotes(e.target.value)} placeholder="End of shift notes" className="h-11" />
) : (
setOpeningBalance(e.target.value)} placeholder="Starting cash amount" className="h-11 text-lg" autoFocus />
)}
{ if (pendingAdjustView) setAdjustView(pendingAdjustView) setPendingAdjustView(null) }} /> ) } // --- Shared report view used by both X and Z reports --- const PAYMENT_LABELS: Record = { cash: 'Cash', card_present: 'Card (Present)', card_keyed: 'Card (Keyed)', check: 'Check', account_charge: 'Account', unknown: 'Other', } function DrawerReportView({ report }: { report: any }) { const { session, sales, payments, discounts, cash, adjustments } = report return (
{/* Session info */}
{session.register &&
Register{session.register.name}
} {session.openedBy &&
Opened by{session.openedBy.firstName} {session.openedBy.lastName}
}
Opened{new Date(session.openedAt).toLocaleString()}
{session.closedAt && ( <> {session.closedBy &&
Closed by{session.closedBy.firstName} {session.closedBy.lastName}
}
Closed{new Date(session.closedAt).toLocaleString()}
)}
{/* Sales */}

Sales

Transactions{sales.transactionCount}
Gross Sales${sales.grossSales.toFixed(2)}
{sales.refundTotal > 0 &&
Refunds-${sales.refundTotal.toFixed(2)}
}
Net Sales${sales.netSales.toFixed(2)}
{sales.voidCount > 0 &&
Voided{sales.voidCount}
}
{/* Payment breakdown */}

Payments

{Object.entries(payments as Record).map(([method, data]) => (
{PAYMENT_LABELS[method] ?? method} ({data.count}) ${data.total.toFixed(2)}
))} {Object.keys(payments).length === 0 &&

No payments

}
{/* Discounts */} {discounts.count > 0 && ( <>

Discounts

Total ({discounts.count})-${discounts.total.toFixed(2)}
)} {/* Cash accountability */}

Cash

Opening Balance${cash.openingBalance.toFixed(2)}
Cash Sales${cash.cashSales.toFixed(2)}
{cash.cashIn > 0 &&
Cash In+${cash.cashIn.toFixed(2)}
} {cash.cashOut > 0 &&
Cash Out-${cash.cashOut.toFixed(2)}
}
Expected${cash.expectedBalance.toFixed(2)}
{cash.actualBalance !== null && ( <>
Actual Count${cash.actualBalance.toFixed(2)}
{cash.overShort! >= 0 ? 'Over' : 'Short'} ${Math.abs(cash.overShort!).toFixed(2)}
)}
{/* Adjustments */} {adjustments.length > 0 && ( <>

Adjustments

{adjustments.map((adj: any) => (
{adj.type === 'cash_in' ? 'IN' : 'OUT'} {adj.reason}
${parseFloat(adj.amount).toFixed(2)}
))}
)}
) }