import { useState, useMemo } from "react"; import { Calendar, Filter, AlertCircle, CheckCircle, Clock, RefreshCw, Upload, Database, Building2, XCircle, } from "lucide-react"; import type { ColumnDef } from "@tanstack/react-table"; import { createColumnHelper, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable, } from "@tanstack/react-table"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; interface BPJSSyncLog { id: string; timestamp: string; type: "import" | "sync"; status: "success" | "failed" | "in_progress"; claimsProcessed: number; claimsSuccess: number; claimsFailed: number; source: string; duration: number; errorMessage?: string; } interface BPJSSyncStats { totalSyncs: number; successfulSyncs: number; failedSyncs: number; lastSyncTime: string; totalClaimsProcessed: number; averageDuration: number; } export default function BPJSSync() { // Helper function to get default dates (1 month back) const getDefaultDates = () => { const endDate = new Date(); const startDate = new Date(); startDate.setMonth(startDate.getMonth() - 1); return { startDate, endDate }; }; const [statusInput, setStatusInput] = useState("all"); const [appliedStatus, setAppliedStatus] = useState("all"); const [isImporting, setIsImporting] = useState(false); const [isSyncing, setIsSyncing] = useState(false); const { startDate: defaultStartDate, endDate: defaultEndDate } = getDefaultDates(); const [startDateInput, setStartDateInput] = useState( defaultStartDate ); const [endDateInput, setEndDateInput] = useState( defaultEndDate ); const [appliedStartDate, setAppliedStartDate] = useState( defaultStartDate ); const [appliedEndDate, setAppliedEndDate] = useState( defaultEndDate ); const [hasDateFiltered, setHasDateFiltered] = useState(true); // Start with default filter applied const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString("id-ID", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }); }; // Sample BPJS sync logs data with additional entries for pagination const [syncLogs] = useState(() => { const baseLogs: BPJSSyncLog[] = [ { id: "1", timestamp: "2024-01-15T14:30:00Z", type: "sync", status: "success", claimsProcessed: 234, claimsSuccess: 230, claimsFailed: 4, source: "BPJS Kesehatan API", duration: 32, }, { id: "2", timestamp: "2024-01-15T10:15:00Z", type: "import", status: "success", claimsProcessed: 89, claimsSuccess: 89, claimsFailed: 0, source: "Hospital Billing System", duration: 15, }, { id: "3", timestamp: "2024-01-14T16:45:00Z", type: "sync", status: "failed", claimsProcessed: 0, claimsSuccess: 0, claimsFailed: 0, source: "BPJS Kesehatan API", duration: 0, errorMessage: "API rate limit exceeded", }, { id: "4", timestamp: "2024-01-14T09:30:00Z", type: "import", status: "success", claimsProcessed: 156, claimsSuccess: 150, claimsFailed: 6, source: "External Claims System", duration: 28, }, { id: "5", timestamp: "2024-01-13T13:20:00Z", type: "sync", status: "in_progress", claimsProcessed: 45, claimsSuccess: 45, claimsFailed: 0, source: "BPJS Kesehatan API", duration: 0, }, ]; // Generate additional logs for pagination const generated: BPJSSyncLog[] = Array.from({ length: 20 }).map( (_, idx) => { const n = idx + 6; const date = new Date(); date.setDate(date.getDate() - Math.floor(Math.random() * 30)); date.setHours( Math.floor(Math.random() * 24), Math.floor(Math.random() * 60) ); return { id: String(n), timestamp: date.toISOString(), type: Math.random() > 0.5 ? "sync" : "import", status: Math.random() > 0.8 ? "failed" : Math.random() > 0.1 ? "success" : "in_progress", claimsProcessed: Math.floor(Math.random() * 500) + 50, claimsSuccess: Math.floor(Math.random() * 450) + 40, claimsFailed: Math.floor(Math.random() * 20), source: [ "BPJS Kesehatan API", "Hospital Billing System", "External Claims System", "Pharmacy System", ][Math.floor(Math.random() * 4)], duration: Math.floor(Math.random() * 60) + 10, ...(Math.random() > 0.9 && { errorMessage: "Connection timeout" }), } as BPJSSyncLog; } ); return [...baseLogs, ...generated]; }); // Calculate statistics const stats: BPJSSyncStats = { totalSyncs: syncLogs.length, successfulSyncs: syncLogs.filter((log) => log.status === "success").length, failedSyncs: syncLogs.filter((log) => log.status === "failed").length, lastSyncTime: syncLogs[0]?.timestamp || "", totalClaimsProcessed: syncLogs.reduce( (sum, log) => sum + log.claimsProcessed, 0 ), averageDuration: syncLogs .filter((log) => log.duration > 0) .reduce((sum, log) => sum + log.duration, 0) / syncLogs.filter((log) => log.duration > 0).length || 0, }; // Filter sync logs based on date and status (status applied via button) const filteredLogs = useMemo(() => { return syncLogs.filter((log) => { const matchesStatus = appliedStatus === "all" || log.status === appliedStatus; let matchesDate = true; if (hasDateFiltered && (appliedStartDate || appliedEndDate)) { const logDate = new Date(log.timestamp); const startOk = !appliedStartDate || logDate >= appliedStartDate; const endOk = !appliedEndDate || logDate <= appliedEndDate; matchesDate = startOk && endOk; } return matchesStatus && matchesDate; }); }, [ syncLogs, appliedStatus, hasDateFiltered, appliedStartDate, appliedEndDate, ]); const columnHelper = createColumnHelper(); const columns: ColumnDef[] = useMemo( () => [ columnHelper.display({ id: "timeAndType", header: "Waktu & Type", cell: (info) => { const log = info.row.original; return (
{formatDate(log.timestamp)}
{log.type === "import" ? "Import" : "Sync"}
); }, }), columnHelper.display({ id: "source", header: "Source System", cell: (info) => { const log = info.row.original; return (
{log.source}
{log.errorMessage && (
{log.errorMessage}
)}
); }, }), columnHelper.display({ id: "status", header: "Status", cell: (info) => { const log = info.row.original; return (
{log.status === "success" ? ( ) : log.status === "failed" ? ( ) : ( )} {log.status === "success" ? "Berhasil" : log.status === "failed" ? "Gagal" : "Berlangsung"}
); }, }), columnHelper.display({ id: "claimsProcessed", header: "Claims Processed", cell: (info) => { const log = info.row.original; return (
{log.claimsProcessed.toLocaleString()}
{log.claimsSuccess > 0 && ( ✓ {log.claimsSuccess} )} {log.claimsFailed > 0 && ( ✗ {log.claimsFailed} )}
); }, }), columnHelper.display({ id: "successRate", header: "Success Rate", cell: (info) => { const log = info.row.original; const successRate = log.claimsProcessed > 0 ? Math.round((log.claimsSuccess / log.claimsProcessed) * 100) : 0; const displayRate = Math.min(100, successRate); // Batasi maksimal 100% return (
{successRate}%
); }, }), columnHelper.display({ id: "duration", header: "Duration", cell: (info) => { const log = info.row.original; return (
{log.duration > 0 ? `${log.duration}s` : "-"}
); }, }), ], [columnHelper] ); const table = useReactTable({ data: filteredLogs, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getRowId: (row) => row.id, initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, enableRowSelection: false, debugTable: false, }); const handleImport = async () => { setIsImporting(true); // Simulate import process setTimeout(() => { setIsImporting(false); // Add new log entry here }, 3000); }; const handleSync = async () => { setIsSyncing(true); // Simulate sync process setTimeout(() => { setIsSyncing(false); // Add new log entry here }, 5000); }; return (
{/* Header */}

BPJS Sync

Sinkronisasi dan integrasi data klaim BPJS dari sistem eksternal

{/* Statistics Cards */}

Total Sync

{stats.totalSyncs}

Berhasil

{stats.successfulSyncs}

Total Gagal

{stats.failedSyncs}

Avg Duration

{Math.round(stats.averageDuration)}s

{/* Filters */}
{/* Date range */}
setStartDateInput(date || undefined)} selectsStart startDate={startDateInput} endDate={endDateInput} placeholderText="Start Date" className="border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent w-36" dateFormat="dd MMM yyyy" /> s/d setEndDateInput(date || undefined)} selectsEnd startDate={startDateInput} endDate={endDateInput} minDate={startDateInput} placeholderText="End Date" className="border border-gray-300 rounded-md px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent w-36" dateFormat="dd MMM yyyy" />
{/* Status */}
{/* Buttons */}
{/* Sync Logs Table */}

Log Sinkronisasi BPJS

{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( ))} ))}
{flexRender( header.column.columnDef.header, header.getContext() )}
{flexRender( cell.column.columnDef.cell, cell.getContext() )}
{/* Pagination Controls */}
Page { const page = Number(e.target.value) - 1; if (!Number.isNaN(page)) table.setPageIndex(page); }} className="w-16 border border-gray-300 rounded px-2 py-1 text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> of {table.getPageCount() || 1}
{/* Empty State */} {filteredLogs.length === 0 && (

Tidak ada log sinkronisasi ditemukan

Tidak ada log yang sesuai dengan kriteria pencarian.

)}
); }