import { useMemo } from 'react' import { ArrowDownRightIcon, ArrowUpDownIcon, ArrowUpRightIcon, BanknoteIcon, CalendarIcon, DollarSignIcon, FileTextIcon, ListIcon, ReceiptIcon, } from 'lucide-react' import _ from '@/lib/translate' import { cn } from '@/lib/utils' import { Table, TableBody, TableCell, TableHead, TableRow } from '@/components/ui/table' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { COLUMN_MAPS_TO_OPTIONS, ColumnMapsTo } from './import_utils' const AMOUNT_COLUMNS: ColumnMapsTo[] = ['Amount', 'Withdrawal', 'Deposit', 'Balance'] const DATE_LIKE = /\d{1,4}[/\-.\s]\d{1,2}[/\-.\s]\d{1,4}|\d{1,2}[\s-][a-z]{3}/i type Props = { rows: string[][] /** Column index -> mapped field */ columnMapping: Record headerIndex: number | null editable?: boolean disabled?: boolean onChangeMapping?: (columnIndex: number, mapsTo: ColumnMapsTo) => void /** Set the header row (or null to mark the table as having no header). */ onSetHeader?: (rowIndex: number | null) => void } /** * A preview of extracted rows with CSV-style colour coding: the header row is highlighted, * detected transaction rows are green, and mapped columns are emphasised. When `editable`, a * compact row of column -> field dropdowns sits at the top, and row numbers can be clicked to * set/clear the header row. */ const RawTableGrid = ({ rows, columnMapping, headerIndex, editable, disabled, onChangeMapping, onSetHeader }: Props) => { // Tabular (XLSX) cells can be numbers/dates, not strings - coerce so .trim()/render are safe. const stringRows = useMemo( () => rows.map((row) => row.map((cell) => (cell == null ? '' : String(cell)))), [rows], ) const numColumns = useMemo(() => stringRows.reduce((max, row) => Math.max(max, row.length), 0), [stringRows]) const validColumns = useMemo( () => Object.entries(columnMapping).filter(([, m]) => m && m !== 'Do not import').map(([i]) => Number(i)), [columnMapping], ) const dateColumn = useMemo(() => Object.entries(columnMapping).find(([, m]) => m === 'Date')?.[0], [columnMapping]) const amountColumns = useMemo( () => Object.entries(columnMapping).filter(([, m]) => ['Amount', 'Withdrawal', 'Deposit'].includes(m)).map(([i]) => Number(i)), [columnMapping], ) // Approximate the backend's transaction-row detection so the highlighting tracks edits live. const transactionRows = useMemo(() => { const set = new Set() if (dateColumn === undefined) return set const dateIdx = Number(dateColumn) stringRows.forEach((row, index) => { if (index === headerIndex) return const dateCell = (row[dateIdx] ?? '').trim() if (!dateCell || !DATE_LIKE.test(dateCell)) return if (amountColumns.some((c) => (row[c] ?? '').trim() !== '')) set.add(index) }) return set }, [stringRows, headerIndex, dateColumn, amountColumns]) return ( {editable && ( {Array.from({ length: numColumns }).map((_unused, columnIndex) => ( ))} )} {stringRows.map((row, index) => { const isHeaderRow = index === headerIndex const isTransactionRow = transactionRows.has(index) return ( {editable && onSetHeader ? ( {isHeaderRow ? _('This is the header row. Click to mark the table as having no header.') : _('Click to set this as the header row.')} ) : ( {index + 1} )} {Array.from({ length: numColumns }).map((_unused, cellIndex) => { const columnType = columnMapping[cellIndex] const isValidColumn = validColumns.includes(cellIndex) const isAmountColumn = AMOUNT_COLUMNS.includes(columnType) const cellText = row[cellIndex] ?? '' // Read-only header row: icon + label. if (isHeaderRow) { return (
{columnType && ( {_(columnType)} )} {cellText}
) } return (
{cellText}
) })}
) })}
) } const ColumnHeaderIcon = ({ columnType }: { columnType?: ColumnMapsTo }) => { switch (columnType) { case 'Amount': return case 'Withdrawal': return case 'Deposit': return case 'Balance': return case 'Date': return case 'Description': return case 'Reference': return case 'Transaction Type': return case 'Debit/Credit': return default: return null } } export default RawTableGrid