diff --git a/resources/js/components/accounts/import-balances-drawer.tsx b/resources/js/components/accounts/import-balances-drawer.tsx index 1f2a7cea..e963d59e 100644 --- a/resources/js/components/accounts/import-balances-drawer.tsx +++ b/resources/js/components/accounts/import-balances-drawer.tsx @@ -645,7 +645,7 @@ export function ImportBalancesDrawer({ return ( -
+
{stepInfo.title} diff --git a/resources/js/components/transactions/import-step-preview.tsx b/resources/js/components/transactions/import-step-preview.tsx index 5f04bcb9..9f34c0a4 100644 --- a/resources/js/components/transactions/import-step-preview.tsx +++ b/resources/js/components/transactions/import-step-preview.tsx @@ -1,5 +1,6 @@ import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; import { Table, TableBody, @@ -16,6 +17,8 @@ interface ImportStepPreviewProps { currencyCode: string; onConfirm: () => void; onBack: () => void; + onSelectionChange: (index: number, selected: boolean) => void; + onSelectAll: (selected: boolean) => void; isImporting: boolean; } @@ -24,12 +27,31 @@ export function ImportStepPreview({ currencyCode, onConfirm, onBack, + onSelectionChange, + onSelectAll, isImporting, }: ImportStepPreviewProps) { const stats = useMemo(() => { - const newCount = transactions.filter((t) => !t.isDuplicate).length; + const selectableTransactions = transactions.filter( + (t) => !t.isDuplicate, + ); + const selectedCount = selectableTransactions.filter( + (t) => t.selected, + ).length; const duplicateCount = transactions.filter((t) => t.isDuplicate).length; - return { newCount, duplicateCount, total: transactions.length }; + const allSelected = + selectableTransactions.length > 0 && + selectedCount === selectableTransactions.length; + const someSelected = selectedCount > 0 && !allSelected; + + return { + selectedCount, + duplicateCount, + total: transactions.length, + selectableCount: selectableTransactions.length, + allSelected, + someSelected, + }; }, [transactions]); const formatAmount = (amount: number): string => { @@ -48,6 +70,10 @@ export function ImportStepPreview({ }); }; + const handleHeaderCheckboxChange = (checked: boolean) => { + onSelectAll(checked); + }; + return (
@@ -56,9 +82,9 @@ export function ImportStepPreview({

{stats.total}

-

New

+

Selected

- {stats.newCount} + {stats.selectedCount}

@@ -69,7 +95,7 @@ export function ImportStepPreview({
- {stats.newCount === 0 && stats.duplicateCount > 0 && ( + {stats.selectableCount === 0 && stats.duplicateCount > 0 && (

All transactions appear to be duplicates. No new @@ -82,19 +108,31 @@ export function ImportStepPreview({ - Date - Description - Amount + + + Status + Date + Description + Amount {transactions.length === 0 ? ( No valid transactions found @@ -110,16 +148,20 @@ export function ImportStepPreview({ : '' } > - - {formatDate( - transaction.transaction_date, - )} - - - {transaction.description} - - - {formatAmount(transaction.amount)} + + + onSelectionChange( + index, + checked === true, + ) + } + disabled={transaction.isDuplicate} + aria-label={`Select transaction: ${transaction.description}`} + /> {transaction.isDuplicate ? ( @@ -135,6 +177,17 @@ export function ImportStepPreview({ )} + + {formatDate( + transaction.transaction_date, + )} + + + {transaction.description} + + + {formatAmount(transaction.amount)} + )) )} @@ -152,11 +205,11 @@ export function ImportStepPreview({ diff --git a/resources/js/components/transactions/import-transactions-drawer.tsx b/resources/js/components/transactions/import-transactions-drawer.tsx index a4742d01..8a42bb1d 100644 --- a/resources/js/components/transactions/import-transactions-drawer.tsx +++ b/resources/js/components/transactions/import-transactions-drawer.tsx @@ -274,6 +274,7 @@ export function ImportTransactionsDrawer({ (transaction, index) => ({ ...transaction, isDuplicate: duplicateFlags[index], + selected: !duplicateFlags[index], }), ); @@ -308,9 +309,7 @@ export function ImportTransactionsDrawer({ setError(null); setImportErrors([]); - const newTransactions = state.transactions.filter( - (t) => !t.isDuplicate, - ); + const newTransactions = state.transactions.filter((t) => t.selected); const total = newTransactions.length; setImportTotal(total); setImportProgress(0); @@ -514,6 +513,24 @@ export function ImportTransactionsDrawer({ }); }; + const handleSelectionChange = (index: number, selected: boolean) => { + setState((prev) => ({ + ...prev, + transactions: prev.transactions.map((t, i) => + i === index ? { ...t, selected } : t, + ), + })); + }; + + const handleSelectAll = (selected: boolean) => { + setState((prev) => ({ + ...prev, + transactions: prev.transactions.map((t) => + t.isDuplicate ? t : { ...t, selected }, + ), + })); + }; + const moveToStep = (step: ImportStep) => { setState((prev) => ({ ...prev, step })); }; @@ -600,6 +617,8 @@ export function ImportTransactionsDrawer({ currencyCode={selectedAccount?.currency_code || 'USD'} onConfirm={handleConfirmImport} onBack={() => moveToStep(ImportStep.MapColumns)} + onSelectionChange={handleSelectionChange} + onSelectAll={handleSelectAll} isImporting={isImporting} /> ); @@ -687,7 +706,7 @@ export function ImportTransactionsDrawer({ return ( -
+
{stepInfo.title} diff --git a/resources/js/types/import.ts b/resources/js/types/import.ts index 71af7a26..fe1af2a0 100644 --- a/resources/js/types/import.ts +++ b/resources/js/types/import.ts @@ -28,6 +28,7 @@ export interface ParsedTransaction { amount: number; balance?: number | null; isDuplicate?: boolean; + selected?: boolean; validationErrors?: string[]; }