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
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 1m39s
This commit is contained in:
@@ -2,29 +2,35 @@
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Trash2, GitMerge } from "lucide-react";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AccountBulkActionsProps {
|
||||
selectedCount: number;
|
||||
selectedAccountIds: string[];
|
||||
onDelete: () => void;
|
||||
onMerge?: () => void;
|
||||
}
|
||||
|
||||
export function AccountBulkActions({
|
||||
selectedCount,
|
||||
selectedAccountIds: _selectedAccountIds,
|
||||
onDelete,
|
||||
onMerge,
|
||||
}: AccountBulkActionsProps) {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (selectedCount === 0) return null;
|
||||
|
||||
const canMerge = selectedCount === 2 && onMerge;
|
||||
|
||||
return (
|
||||
<Card className="bg-destructive/5 border-destructive/20 sticky top-0 z-10 mb-4">
|
||||
<CardContent className={cn("py-3", isMobile && "px-3")}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 sm:gap-4",
|
||||
"flex items-center gap-2 sm:gap-4 flex-wrap",
|
||||
isMobile && "flex-col sm:flex-row",
|
||||
)}
|
||||
>
|
||||
@@ -32,6 +38,19 @@ export function AccountBulkActions({
|
||||
{selectedCount} compte{selectedCount > 1 ? "s" : ""} sélectionné
|
||||
{selectedCount > 1 ? "s" : ""}
|
||||
</span>
|
||||
{canMerge && (
|
||||
<Button
|
||||
size={isMobile ? "sm" : "sm"}
|
||||
variant="default"
|
||||
onClick={onMerge}
|
||||
className={cn(isMobile && "w-full sm:w-auto")}
|
||||
>
|
||||
<GitMerge
|
||||
className={cn(isMobile ? "w-3.5 h-3.5" : "w-4 h-4", "mr-1")}
|
||||
/>
|
||||
Fusionner
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size={isMobile ? "sm" : "sm"}
|
||||
variant="destructive"
|
||||
|
||||
156
components/accounts/account-merge-select-dialog.tsx
Normal file
156
components/accounts/account-merge-select-dialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { AccountCard } from "./account-card";
|
||||
export { AccountEditDialog } from "./account-edit-dialog";
|
||||
export { AccountMergeSelectDialog } from "./account-merge-select-dialog";
|
||||
export { AccountBulkActions } from "./account-bulk-actions";
|
||||
export { accountTypeIcons, accountTypeLabels } from "./constants";
|
||||
|
||||
Reference in New Issue
Block a user