'use client'; import { useState, useEffect } from 'react'; import { Wifi, WifiOff, RefreshCw, RotateCcw, Store, Receipt, CheckCircle2, AlertTriangle, Clock, Download, Search, Filter, Plus } from 'lucide-react'; import { toast } from 'sonner'; import { apiFetch } from '@/services/api/client'; import { SlideOver } from '@/components/ui/SlideOver'; interface POSTerminal { terminal_id: string; store_name: string; status: 'ONLINE' | 'IDLE' | 'OFFLINE'; ip_address: string; last_heartbeat: string; synced_today: number; pending_queue: number; } interface POSSyncStatus { gateway_status: string; active_terminals_count: number; synced_today_count: number; pending_retries_count: number; offline_revenue_today: number; last_sync_timestamp: string; terminals: POSTerminal[]; } interface POSSyncLog { sync_id: string; terminal_id: string; invoice_no: string; pos_transaction_id: string; items_count: number; total_amount: number; status: 'SYNCED' | 'FAILED' | 'PENDING_RETRY'; created_at: string; error_detail?: string | null; } export default function POSSyncPage() { const [statusData, setStatusData] = useState(null); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('ALL'); const [resettingTerminal, setResettingTerminal] = useState(null); // Test Sync Simulation Drawer State const [showSimModal, setShowSimModal] = useState(false); const [simTerminalId, setSimTerminalId] = useState('TERM-MAIN-01'); const [simInvoiceNo, setSimInvoiceNo] = useState(`POS-${new Date().getFullYear()}-0990`); const [simAmount, setSimAmount] = useState('1299.00'); const [simSubmitting, setSimSubmitting] = useState(false); const fetchSyncData = async () => { setLoading(true); try { const [statusRes, logsRes] = await Promise.all([ apiFetch('/api/v1/pos/status', { skipAuth: true }), apiFetch<{ total: number; logs: POSSyncLog[] }>('/api/v1/pos/logs', { skipAuth: true }), ]); setStatusData(statusRes); setLogs(logsRes.logs || []); } catch (err: any) { toast.error(err?.message || 'Failed to load POS sync telemetry'); } finally { setLoading(false); } }; useEffect(() => { fetchSyncData(); }, []); const handleResetTerminal = async (terminalId: string) => { setResettingTerminal(terminalId); try { const res = await apiFetch<{ status: string; message: string }>(`/api/v1/pos/terminals/${terminalId}/reset`, { method: 'POST', }); toast.success(res.message); fetchSyncData(); } catch (err: any) { toast.error(err?.message || 'Failed to reset terminal sync'); } finally { setResettingTerminal(null); } }; const handleSimulateSync = async (e: React.FormEvent) => { e.preventDefault(); setSimSubmitting(true); try { const payload = { device_id: 'DEV-POS-TABLET-01', terminal_id: simTerminalId, store_id: 'STORE-CP-MAIN', transactions: [ { pos_transaction_id: `TX-${Date.now()}`, local_sequence: Math.floor(Math.random() * 1000), invoice_no: simInvoiceNo, total_amount: parseFloat(simAmount) || 999.0, created_at: new Date().toISOString(), items: [ { variant_id: 'VAR-IP15P-CLR-128', qty: 1, unit_price: parseFloat(simAmount) || 999.0, }, ], }, ], }; const res = await apiFetch<{ status: string; synced_count: number }>('/api/v1/pos/sync', { method: 'POST', body: JSON.stringify(payload), }); toast.success(`POS Transaction batch synced! (${res.synced_count} transaction processed)`); setShowSimModal(false); fetchSyncData(); } catch (err: any) { toast.error(err?.message || 'Failed to sync POS transaction batch'); } finally { setSimSubmitting(false); } }; const filteredLogs = logs.filter((log) => { const matchesSearch = log.invoice_no.toLowerCase().includes(searchQuery.toLowerCase()) || log.terminal_id.toLowerCase().includes(searchQuery.toLowerCase()) || log.pos_transaction_id.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStatus = statusFilter === 'ALL' || log.status === statusFilter; return matchesSearch && matchesStatus; }); return (
{/* Top Action Bar */}

Offline POS Synchronization

Monitor real-time sync telemetry, terminal heartbeat statuses, and offline retail billing transaction logs.

{/* Metric Cards Grid */}
Gateway Status {statusData?.gateway_status || 'ONLINE'}
{statusData?.active_terminals_count ?? 0} Active Terminals
Synced Today
{statusData?.synced_today_count ?? 0} Transactions
Offline Revenue Today
₹{(statusData?.offline_revenue_today ?? 0).toLocaleString('en-IN', { minimumFractionDigits: 2 })}
Pending Sync Retries
{statusData?.pending_retries_count || 0} Queue Retries
{/* Terminal Health Grid */}

Registered POS Terminals

{statusData?.terminals.map((term) => (
{term.terminal_id}
{term.status === 'ONLINE' ? : } {term.status}

{term.store_name}

IP Address: {term.ip_address}
Last Heartbeat: {term.last_heartbeat}
Synced Today: {term.synced_today} bills
))}
{/* POS Sync Audit Logs Table */}

Sync Audit History Logs

Transaction audit batch entries submitted from retail POS terminals.

setSearchQuery(e.target.value)} className="w-full rounded-lg border border-slate-300 bg-slate-50 pl-9 pr-3 py-1.5 text-xs text-slate-900 focus:border-blue-500 focus:bg-white focus:outline-none dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100" />
{/* Table Content */}
{filteredLogs.length === 0 ? ( ) : ( filteredLogs.map((log) => ( )) )}
Sync ID Terminal ID Invoice No POS Tx ID Items Total Amount Status Sync Timestamp
No POS sync log entries found matching criteria.
{log.sync_id} {log.terminal_id} {log.invoice_no} {log.pos_transaction_id} {log.items_count} ₹{log.total_amount.toFixed(2)} {log.status} {new Date(log.created_at).toLocaleString()}
{/* Test Sync Simulation Drawer */} setShowSimModal(false)} title="Simulate POS Sync Transaction Payload" >

Submit a test offline retail invoice transaction payload to verify gateway idempotency and stock deduction handling.

setSimTerminalId(e.target.value)} required className="w-full rounded-lg border border-slate-300 bg-white p-2 font-mono text-xs text-slate-900 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100" />
setSimInvoiceNo(e.target.value)} required className="w-full rounded-lg border border-slate-300 bg-white p-2 font-mono text-xs text-slate-900 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100" />
setSimAmount(e.target.value)} required className="w-full rounded-lg border border-slate-300 bg-white p-2 font-mono text-xs text-slate-900 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-100" />
); }