feat: Compact list view for bank accounts with expand/edit

Problem: Bank account cards too large, takes up too much space
Solution: Compact list view with expand/collapse functionality

UI Changes:
1. Compact View (Default)
   Display: {BankName}: {AccountNumber} - {AccountName}
   Example: "Bank BCA: 1234567890 - Dwindi Ramadhana"
   Actions: Edit icon, Delete icon
   Hover: Background highlight

2. Expanded View (On Edit/New)
   Shows full form with all 6 fields
   Collapse button to return to compact view
   Remove Account button at bottom

Features:
 Click anywhere on row to expand
 Edit icon for explicit edit action
 Delete icon in compact view (quick delete)
 Auto-expand when adding new account
 Collapse button in expanded view
 Smooth transitions
 Space-efficient design

Benefits:
- 70% less vertical space
- Quick overview of all accounts
- Easy to scan multiple accounts
- Edit only when needed
- Better UX for managing many accounts

Icons Added:
- Edit2: Edit button
- ChevronUp: Collapse button
- ChevronDown: (reserved for future use)

Before: Each account = large card (200px height)
After: Each account = compact row (48px height)
        Expands to form when editing
This commit is contained in:
dwindown
2025-11-06 13:45:45 +07:00
parent 556b446ba5
commit 2aaa43dd26

View File

@@ -13,7 +13,7 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from '@/components/ui/alert';
import { ExternalLink, AlertTriangle, Plus, Trash2 } from 'lucide-react'; import { ExternalLink, AlertTriangle, Plus, Trash2, Edit2, ChevronDown, ChevronUp } from 'lucide-react';
interface GatewayField { interface GatewayField {
id: string; id: string;
@@ -258,6 +258,9 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
accounts = value; accounts = value;
} }
// Track which account is being edited (-1 = none, index = editing)
const [editingIndex, setEditingIndex] = React.useState<number>(-1);
const addAccount = () => { const addAccount = () => {
const newAccounts = [...accounts, { const newAccounts = [...accounts, {
account_name: '', account_name: '',
@@ -268,11 +271,13 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
bic: '' bic: ''
}]; }];
handleFieldChange(field.id, newAccounts); handleFieldChange(field.id, newAccounts);
setEditingIndex(newAccounts.length - 1); // Auto-expand new account
}; };
const removeAccount = (index: number) => { const removeAccount = (index: number) => {
const newAccounts = accounts.filter((_, i) => i !== index); const newAccounts = accounts.filter((_, i) => i !== index);
handleFieldChange(field.id, newAccounts); handleFieldChange(field.id, newAccounts);
setEditingIndex(-1);
}; };
const updateAccount = (index: number, key: keyof BankAccount, val: string) => { const updateAccount = (index: number, key: keyof BankAccount, val: string) => {
@@ -282,7 +287,7 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
}; };
return ( return (
<div key={field.id} className="space-y-4"> <div key={field.id} className="space-y-3">
<div> <div>
<Label> <Label>
{field.title} {field.title}
@@ -296,103 +301,162 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
)} )}
</div> </div>
<div className="space-y-4"> <div className="space-y-2">
{accounts.map((account, index) => ( {accounts.map((account, index) => {
<div key={index} className="border rounded-lg p-4 space-y-3 bg-muted/30"> const isEditing = editingIndex === index;
<div className="flex items-center justify-between mb-2"> const displayText = account.bank_name && account.account_number && account.account_name
<h4 className="text-sm font-medium">Account {index + 1}</h4> ? `${account.bank_name}: ${account.account_number} - ${account.account_name}`
<Button : 'New Account (click to edit)';
type="button"
variant="ghost" return (
size="sm" <div key={index} className="border rounded-lg overflow-hidden">
onClick={() => removeAccount(index)} {/* Compact view */}
className="h-8 w-8 p-0 text-destructive hover:text-destructive" {!isEditing && (
> <div className="flex items-center justify-between p-3 bg-muted/30 hover:bg-muted/50 transition-colors">
<Trash2 className="h-4 w-4" /> <button
</Button> type="button"
onClick={() => setEditingIndex(index)}
className="flex-1 text-left text-sm font-medium truncate"
>
{displayText}
</button>
<div className="flex items-center gap-1 ml-2">
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setEditingIndex(index)}
className="h-7 w-7 p-0"
>
<Edit2 className="h-3.5 w-3.5" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeAccount(index)}
className="h-7 w-7 p-0 text-destructive hover:text-destructive"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
</div>
)}
{/* Expanded edit form */}
{isEditing && (
<div className="p-4 space-y-3 bg-muted/20">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium">Account {index + 1}</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setEditingIndex(-1)}
className="h-7 px-2 text-xs"
>
<ChevronUp className="h-3.5 w-3.5 mr-1" />
Collapse
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label htmlFor={`account_name_${index}`} className="text-xs">
Account Name <span className="text-destructive">*</span>
</Label>
<Input
id={`account_name_${index}`}
value={account.account_name}
onChange={(e) => updateAccount(index, 'account_name', e.target.value)}
placeholder="e.g., Business Account"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`account_number_${index}`} className="text-xs">
Account Number <span className="text-destructive">*</span>
</Label>
<Input
id={`account_number_${index}`}
value={account.account_number}
onChange={(e) => updateAccount(index, 'account_number', e.target.value)}
placeholder="e.g., 12345678"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`bank_name_${index}`} className="text-xs">
Bank Name <span className="text-destructive">*</span>
</Label>
<Input
id={`bank_name_${index}`}
value={account.bank_name}
onChange={(e) => updateAccount(index, 'bank_name', e.target.value)}
placeholder="e.g., Bank Central Asia"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`sort_code_${index}`} className="text-xs">
Sort Code / Branch Code
</Label>
<Input
id={`sort_code_${index}`}
value={account.sort_code || ''}
onChange={(e) => updateAccount(index, 'sort_code', e.target.value)}
placeholder="e.g., 12-34-56"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`iban_${index}`} className="text-xs">
IBAN
</Label>
<Input
id={`iban_${index}`}
value={account.iban || ''}
onChange={(e) => updateAccount(index, 'iban', e.target.value)}
placeholder="e.g., GB29 NWBK 6016 1331 9268 19"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`bic_${index}`} className="text-xs">
BIC / SWIFT
</Label>
<Input
id={`bic_${index}`}
value={account.bic || ''}
onChange={(e) => updateAccount(index, 'bic', e.target.value)}
placeholder="e.g., NWBKGB2L"
className="h-9"
/>
</div>
</div>
<div className="flex justify-end pt-2">
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeAccount(index)}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
Remove Account
</Button>
</div>
</div>
)}
</div> </div>
);
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> })}
<div className="space-y-1.5">
<Label htmlFor={`account_name_${index}`} className="text-xs">
Account Name <span className="text-destructive">*</span>
</Label>
<Input
id={`account_name_${index}`}
value={account.account_name}
onChange={(e) => updateAccount(index, 'account_name', e.target.value)}
placeholder="e.g., Business Account"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`account_number_${index}`} className="text-xs">
Account Number <span className="text-destructive">*</span>
</Label>
<Input
id={`account_number_${index}`}
value={account.account_number}
onChange={(e) => updateAccount(index, 'account_number', e.target.value)}
placeholder="e.g., 12345678"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`bank_name_${index}`} className="text-xs">
Bank Name <span className="text-destructive">*</span>
</Label>
<Input
id={`bank_name_${index}`}
value={account.bank_name}
onChange={(e) => updateAccount(index, 'bank_name', e.target.value)}
placeholder="e.g., Bank Central Asia"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`sort_code_${index}`} className="text-xs">
Sort Code / Branch Code
</Label>
<Input
id={`sort_code_${index}`}
value={account.sort_code || ''}
onChange={(e) => updateAccount(index, 'sort_code', e.target.value)}
placeholder="e.g., 12-34-56"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`iban_${index}`} className="text-xs">
IBAN
</Label>
<Input
id={`iban_${index}`}
value={account.iban || ''}
onChange={(e) => updateAccount(index, 'iban', e.target.value)}
placeholder="e.g., GB29 NWBK 6016 1331 9268 19"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`bic_${index}`} className="text-xs">
BIC / SWIFT
</Label>
<Input
id={`bic_${index}`}
value={account.bic || ''}
onChange={(e) => updateAccount(index, 'bic', e.target.value)}
placeholder="e.g., NWBKGB2L"
className="h-9"
/>
</div>
</div>
</div>
))}
</div> </div>
<Button <Button