feat: implement clickable category links in charts and lists; handle uncategorized transactions in URL parameters
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||
import {
|
||||
@@ -29,6 +30,48 @@ export function CategoryBarChart({
|
||||
}: CategoryBarChartProps) {
|
||||
const displayData = data.slice(0, maxItems).reverse(); // Reverse pour avoir le plus grand en haut
|
||||
|
||||
// Custom tick component for clickable labels
|
||||
const CustomYAxisTick = ({ x, y, payload }: any) => {
|
||||
const categoryName = payload.value;
|
||||
const item = displayData.find((d) => d.name === categoryName);
|
||||
|
||||
if (!item) {
|
||||
return (
|
||||
<text
|
||||
x={x}
|
||||
y={y}
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize={12}
|
||||
textAnchor="end"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{categoryName}
|
||||
</text>
|
||||
);
|
||||
}
|
||||
|
||||
const href =
|
||||
item.categoryId === null || item.categoryId === undefined
|
||||
? "/transactions?includeUncategorized=true"
|
||||
: `/transactions?categoryIds=${item.categoryId}`;
|
||||
|
||||
return (
|
||||
<Link href={href}>
|
||||
<text
|
||||
x={x}
|
||||
y={y}
|
||||
fill="var(--muted-foreground)"
|
||||
fontSize={12}
|
||||
className="hover:opacity-80 transition-opacity cursor-pointer"
|
||||
textAnchor="end"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{categoryName}
|
||||
</text>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -60,11 +103,7 @@ export function CategoryBarChart({
|
||||
dataKey="name"
|
||||
className="text-xs"
|
||||
width={90}
|
||||
tick={{ fill: "var(--muted-foreground)" }}
|
||||
tickFormatter={(value) => {
|
||||
const item = displayData.find((d) => d.name === value);
|
||||
return item ? value : "";
|
||||
}}
|
||||
tick={CustomYAxisTick}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ active, payload }) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||
@@ -13,7 +14,8 @@ export interface CategoryChartData {
|
||||
value: number;
|
||||
color: string;
|
||||
icon: string;
|
||||
[key: string]: string | number;
|
||||
categoryId?: string | null; // null for "Non catégorisé"
|
||||
[key: string]: string | number | null | undefined;
|
||||
}
|
||||
|
||||
interface CategoryPieChartProps {
|
||||
@@ -162,24 +164,32 @@ export function CategoryPieChart({
|
||||
Légende
|
||||
</div>
|
||||
<div className="max-h-[300px] overflow-y-auto overflow-x-hidden pr-2 space-y-2">
|
||||
{currentData.map((item, index) => (
|
||||
<div
|
||||
key={`legend-${index}`}
|
||||
className="flex items-center gap-2 text-sm"
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={item.icon}
|
||||
color={item.color}
|
||||
size={16}
|
||||
/>
|
||||
<span className="text-foreground flex-1 min-w-0 truncate">
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="text-foreground font-semibold tabular-nums whitespace-nowrap">
|
||||
{formatCurrency(item.value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{currentData.map((item, index) => {
|
||||
const href =
|
||||
item.categoryId === null || item.categoryId === undefined
|
||||
? "/transactions?includeUncategorized=true"
|
||||
: `/transactions?categoryIds=${item.categoryId}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={`legend-${index}`}
|
||||
href={href}
|
||||
className="flex items-center gap-2 text-sm hover:opacity-80 transition-opacity cursor-pointer"
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={item.icon}
|
||||
color={item.color}
|
||||
size={16}
|
||||
/>
|
||||
<span className="text-foreground flex-1 min-w-0 truncate">
|
||||
{item.name}
|
||||
</span>
|
||||
<span className="text-foreground font-semibold tabular-nums whitespace-nowrap">
|
||||
{formatCurrency(item.value)}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { CategoryIcon } from "@/components/ui/category-icon";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -53,24 +54,29 @@ export function TopExpensesList({
|
||||
{new Date(expense.date).toLocaleDateString("fr-FR")}
|
||||
</span>
|
||||
{category && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-[10px] md:text-xs px-1.5 md:px-2 py-0.5 inline-flex items-center gap-1 shrink-0"
|
||||
style={{
|
||||
backgroundColor: `${category.color}20`,
|
||||
color: category.color,
|
||||
borderColor: `${category.color}30`,
|
||||
}}
|
||||
<Link
|
||||
href={`/transactions?categoryIds=${category.id}`}
|
||||
className="inline-block"
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={category.icon}
|
||||
color={category.color}
|
||||
size={isMobile ? 8 : 10}
|
||||
/>
|
||||
<span className="truncate max-w-[120px] md:max-w-none">
|
||||
{category.name}
|
||||
</span>
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-[10px] md:text-xs px-1.5 md:px-2 py-0.5 inline-flex items-center gap-1 shrink-0 hover:opacity-80 transition-opacity cursor-pointer"
|
||||
style={{
|
||||
backgroundColor: `${category.color}20`,
|
||||
color: category.color,
|
||||
borderColor: `${category.color}30`,
|
||||
}}
|
||||
>
|
||||
<CategoryIcon
|
||||
icon={category.icon}
|
||||
color={category.color}
|
||||
size={isMobile ? 8 : 10}
|
||||
/>
|
||||
<span className="truncate max-w-[120px] md:max-w-none">
|
||||
{category.name}
|
||||
</span>
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user