Files
fintrack/components/rules/rule-group-card.tsx

253 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>
);
}