248 lines
8.7 KiB
TypeScript
248 lines
8.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { ChevronDown, ChevronRight, Plus, Tag } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { CategoryCombobox } from "@/components/ui/category-combobox";
|
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
import { cn } from "@/lib/utils";
|
|
import type { Transaction, Category } from "@/lib/types";
|
|
|
|
interface TransactionGroup {
|
|
key: string;
|
|
displayName: string;
|
|
transactions: Transaction[];
|
|
totalAmount: number;
|
|
suggestedKeyword: string;
|
|
}
|
|
|
|
interface RuleGroupCardProps {
|
|
group: TransactionGroup;
|
|
isExpanded: boolean;
|
|
onToggleExpand: () => void;
|
|
onCreateRule: () => void;
|
|
onCategorize: (categoryId: string | null) => void;
|
|
categories: Category[];
|
|
formatCurrency: (amount: number) => string;
|
|
formatDate: (date: string) => string;
|
|
}
|
|
|
|
export function RuleGroupCard({
|
|
group,
|
|
isExpanded,
|
|
onToggleExpand,
|
|
onCreateRule,
|
|
onCategorize,
|
|
categories,
|
|
formatCurrency,
|
|
formatDate,
|
|
}: RuleGroupCardProps) {
|
|
const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null);
|
|
const isMobile = useIsMobile();
|
|
|
|
const avgAmount =
|
|
group.transactions.reduce((sum, t) => sum + t.amount, 0) /
|
|
group.transactions.length;
|
|
const isDebit = avgAmount < 0;
|
|
|
|
const handleCategorySelect = (categoryId: string | null) => {
|
|
setSelectedCategoryId(null); // Reset après sélection
|
|
onCategorize(categoryId);
|
|
};
|
|
|
|
return (
|
|
<div className="border border-border rounded-lg bg-card overflow-hidden">
|
|
{/* Header */}
|
|
<div
|
|
className="flex flex-col md:flex-row md:items-center gap-2 md:gap-3 p-3 md:p-4 cursor-pointer hover:bg-accent/5 transition-colors"
|
|
onClick={onToggleExpand}
|
|
>
|
|
<div className="flex items-center gap-2 md:gap-3 flex-1 min-w-0">
|
|
<Button variant="ghost" size="icon" className="h-5 w-5 md:h-6 md:w-6 shrink-0">
|
|
{isExpanded ? (
|
|
<ChevronDown className="h-3 w-3 md:h-4 md:w-4" />
|
|
) : (
|
|
<ChevronRight className="h-3 w-3 md:h-4 md:w-4" />
|
|
)}
|
|
</Button>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-1.5 md:gap-2 flex-wrap">
|
|
<span className="font-medium text-xs md:text-base text-foreground truncate">
|
|
{group.displayName}
|
|
</span>
|
|
<Badge variant="secondary" className="text-[10px] md:text-xs shrink-0">
|
|
{group.transactions.length} 💳
|
|
</Badge>
|
|
</div>
|
|
<div className="flex items-center gap-1.5 md:gap-2 mt-1 text-[10px] md:text-sm text-muted-foreground">
|
|
<Tag className="h-2.5 w-2.5 md:h-3 md:w-3" />
|
|
<span className="font-mono text-[10px] md:text-xs bg-muted px-1 md:px-1.5 py-0.5 rounded">
|
|
{group.suggestedKeyword}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{!isMobile && (
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-right">
|
|
<div
|
|
className={cn(
|
|
"font-semibold tabular-nums text-sm",
|
|
isDebit ? "text-destructive" : "text-success"
|
|
)}
|
|
>
|
|
{formatCurrency(group.totalAmount)}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
Moy: {formatCurrency(avgAmount)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
<div onClick={(e) => e.stopPropagation()}>
|
|
<CategoryCombobox
|
|
categories={categories}
|
|
value={selectedCategoryId}
|
|
onChange={handleCategorySelect}
|
|
placeholder="Catégoriser..."
|
|
width="w-[300px]"
|
|
buttonWidth="w-auto"
|
|
/>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onCreateRule();
|
|
}}
|
|
>
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
Créer règle
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isMobile && (
|
|
<div className="flex items-center gap-2 shrink-0 ml-7">
|
|
<div onClick={(e) => e.stopPropagation()} className="flex-1">
|
|
<CategoryCombobox
|
|
categories={categories}
|
|
value={selectedCategoryId}
|
|
onChange={handleCategorySelect}
|
|
placeholder="Catégoriser..."
|
|
width="w-full"
|
|
buttonWidth="w-full"
|
|
/>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onCreateRule();
|
|
}}
|
|
className="shrink-0"
|
|
>
|
|
<Plus className="h-3 w-3 md:h-4 md:w-4" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Expanded transactions list */}
|
|
{isExpanded && (
|
|
<div className="border-t border-border bg-muted/30">
|
|
{isMobile ? (
|
|
<div className="max-h-64 overflow-y-auto divide-y divide-border">
|
|
{group.transactions.map((transaction) => (
|
|
<div
|
|
key={transaction.id}
|
|
className="p-3 hover:bg-muted/50"
|
|
>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-xs md:text-sm font-medium truncate">
|
|
{transaction.description}
|
|
</p>
|
|
{transaction.memo && (
|
|
<p className="text-[10px] md:text-xs text-muted-foreground truncate mt-0.5">
|
|
{transaction.memo}
|
|
</p>
|
|
)}
|
|
<p className="text-[10px] md:text-xs text-muted-foreground mt-1">
|
|
{formatDate(transaction.date)}
|
|
</p>
|
|
</div>
|
|
<div
|
|
className={cn(
|
|
"text-xs md:text-sm font-semibold tabular-nums shrink-0",
|
|
transaction.amount < 0
|
|
? "text-destructive"
|
|
: "text-success"
|
|
)}
|
|
>
|
|
{formatCurrency(transaction.amount)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="max-h-64 overflow-y-auto">
|
|
<table className="w-full text-sm">
|
|
<thead className="bg-muted/50 sticky top-0">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
|
Date
|
|
</th>
|
|
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
|
Description
|
|
</th>
|
|
<th className="px-4 py-2 text-right font-medium text-muted-foreground">
|
|
Montant
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{group.transactions.map((transaction) => (
|
|
<tr
|
|
key={transaction.id}
|
|
className="border-t border-border/50 hover:bg-muted/50"
|
|
>
|
|
<td className="px-4 py-2 text-muted-foreground whitespace-nowrap">
|
|
{formatDate(transaction.date)}
|
|
</td>
|
|
<td className="px-4 py-2 truncate max-w-md">
|
|
{transaction.description}
|
|
{transaction.memo && (
|
|
<span className="text-muted-foreground ml-2">
|
|
({transaction.memo})
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td
|
|
className={cn(
|
|
"px-4 py-2 text-right tabular-nums whitespace-nowrap",
|
|
transaction.amount < 0
|
|
? "text-destructive"
|
|
: "text-success"
|
|
)}
|
|
>
|
|
{formatCurrency(transaction.amount)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|