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:
@@ -13,7 +13,7 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
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 {
|
||||
id: string;
|
||||
@@ -258,6 +258,9 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
|
||||
accounts = value;
|
||||
}
|
||||
|
||||
// Track which account is being edited (-1 = none, index = editing)
|
||||
const [editingIndex, setEditingIndex] = React.useState<number>(-1);
|
||||
|
||||
const addAccount = () => {
|
||||
const newAccounts = [...accounts, {
|
||||
account_name: '',
|
||||
@@ -268,11 +271,13 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
|
||||
bic: ''
|
||||
}];
|
||||
handleFieldChange(field.id, newAccounts);
|
||||
setEditingIndex(newAccounts.length - 1); // Auto-expand new account
|
||||
};
|
||||
|
||||
const removeAccount = (index: number) => {
|
||||
const newAccounts = accounts.filter((_, i) => i !== index);
|
||||
handleFieldChange(field.id, newAccounts);
|
||||
setEditingIndex(-1);
|
||||
};
|
||||
|
||||
const updateAccount = (index: number, key: keyof BankAccount, val: string) => {
|
||||
@@ -282,7 +287,7 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={field.id} className="space-y-4">
|
||||
<div key={field.id} className="space-y-3">
|
||||
<div>
|
||||
<Label>
|
||||
{field.title}
|
||||
@@ -296,103 +301,162 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{accounts.map((account, index) => (
|
||||
<div key={index} className="border rounded-lg p-4 space-y-3 bg-muted/30">
|
||||
<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={() => removeAccount(index)}
|
||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
{accounts.map((account, index) => {
|
||||
const isEditing = editingIndex === index;
|
||||
const displayText = account.bank_name && account.account_number && account.account_name
|
||||
? `${account.bank_name}: ${account.account_number} - ${account.account_name}`
|
||||
: 'New Account (click to edit)';
|
||||
|
||||
return (
|
||||
<div key={index} className="border rounded-lg overflow-hidden">
|
||||
{/* Compact view */}
|
||||
{!isEditing && (
|
||||
<div className="flex items-center justify-between p-3 bg-muted/30 hover:bg-muted/50 transition-colors">
|
||||
<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 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>
|
||||
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user