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';
|
} 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,19 +301,62 @@ 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;
|
||||||
|
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">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h4 className="text-sm font-medium">Account {index + 1}</h4>
|
<h4 className="text-sm font-medium">Account {index + 1}</h4>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => removeAccount(index)}
|
onClick={() => setEditingIndex(-1)}
|
||||||
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
className="h-7 px-2 text-xs"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<ChevronUp className="h-3.5 w-3.5 mr-1" />
|
||||||
|
Collapse
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -391,8 +439,24 @@ export function GenericGatewayForm({ gateway, onSave, onCancel, hideFooter = fal
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user