feat: add account merging functionality with dialog support; update bulk actions to include merge option
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m39s

This commit is contained in:
Julien Froidefond
2025-12-20 11:42:42 +01:00
parent 376bc8f84e
commit 8b81dfe8c0
5 changed files with 367 additions and 2 deletions

View File

@@ -0,0 +1,156 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertTriangle, Info } from "lucide-react";
import type { Account } from "@/lib/types";
import { getAccountBalance } from "@/lib/account-utils";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
interface AccountMergeSelectDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
accounts: Account[];
selectedAccountIds: string[];
onMerge: (sourceAccountId: string, targetAccountId: string) => Promise<void>;
formatCurrency: (amount: number) => string;
}
export function AccountMergeSelectDialog({
open,
onOpenChange,
accounts,
selectedAccountIds,
onMerge,
formatCurrency,
}: AccountMergeSelectDialogProps) {
const [targetAccountId, setTargetAccountId] = useState<string>("");
const [isMerging, setIsMerging] = useState(false);
const selectedAccounts = accounts.filter((a) =>
selectedAccountIds.includes(a.id),
);
const sourceAccountId =
selectedAccounts.length === 2 && targetAccountId
? selectedAccounts.find((a) => a.id !== targetAccountId)?.id || ""
: "";
const sourceAccount = accounts.find((a) => a.id === sourceAccountId);
const targetAccount = accounts.find((a) => a.id === targetAccountId);
// Initialiser avec le premier compte si pas encore sélectionné
if (open && selectedAccounts.length === 2 && !targetAccountId) {
setTargetAccountId(selectedAccounts[0].id);
}
const handleMerge = async () => {
if (!sourceAccountId || !targetAccountId || sourceAccountId === targetAccountId) {
return;
}
setIsMerging(true);
try {
await onMerge(sourceAccountId, targetAccountId);
setTargetAccountId("");
onOpenChange(false);
} catch (error) {
console.error("Error merging accounts:", error);
alert("Erreur lors de la fusion des comptes");
} finally {
setIsMerging(false);
}
};
const canMerge =
selectedAccounts.length === 2 &&
sourceAccountId &&
targetAccountId &&
sourceAccountId !== targetAccountId &&
sourceAccount &&
targetAccount;
if (selectedAccounts.length !== 2) {
return null;
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Fusionner des comptes</DialogTitle>
<DialogDescription>
Choisissez quel compte conserver. Toutes les transactions de l'autre
compte seront déplacées vers celui-ci.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<Alert>
<Info className="h-4 w-4" />
<AlertDescription>
Le compte sélectionné sera conservé et utilisé pour les prochains
imports. Choisissez celui qui a le bon bankId.
</AlertDescription>
</Alert>
<div className="space-y-3">
<Label>Compte à conserver (destination)</Label>
<RadioGroup value={targetAccountId} onValueChange={setTargetAccountId}>
{selectedAccounts.map((account) => (
<div
key={account.id}
className="flex items-start space-x-3 rounded-lg border p-3 hover:bg-accent"
>
<RadioGroupItem value={account.id} id={account.id} className="mt-1" />
<Label
htmlFor={account.id}
className="flex-1 cursor-pointer space-y-1"
>
<div className="font-medium">{account.name}</div>
<div className="text-xs text-muted-foreground space-y-0.5">
<div>Numéro: {account.accountNumber}</div>
<div>Bank ID: {account.bankId}</div>
<div>Solde: {formatCurrency(getAccountBalance(account))}</div>
</div>
</Label>
</div>
))}
</RadioGroup>
</div>
{canMerge && (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Attention :</strong> Toutes les transactions du compte "
{sourceAccount.name}" seront déplacées vers "{targetAccount.name}". Le
compte "{sourceAccount.name}" sera supprimé après la fusion. Cette
action est irréversible.
</AlertDescription>
</Alert>
)}
<div className="flex justify-end gap-2 pt-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>
Annuler
</Button>
<Button onClick={handleMerge} disabled={!canMerge || isMerging}>
{isMerging ? "Fusion en cours..." : "Fusionner"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}