refactor: standardize quotation marks in pnpm-lock.yaml and improve code formatting across various components; enhance readability and maintain consistency in code style

This commit is contained in:
Julien Froidefond
2025-12-23 11:42:02 +01:00
parent 01c1f25de2
commit c57daa9cc8
42 changed files with 4722 additions and 2758 deletions

View File

@@ -55,7 +55,8 @@ export function AccountCard({
const isMobile = useIsMobile();
const Icon = accountTypeIcons[account.type];
const realBalance = getAccountBalance(account);
const hasBalanceDifference = calculatedBalance !== undefined &&
const hasBalanceDifference =
calculatedBalance !== undefined &&
Math.abs(account.balance - calculatedBalance) > 0.01;
const {
@@ -200,7 +201,8 @@ export function AccountCard({
</span>
{hasBalanceDifference && (
<span className="text-xs text-destructive font-semibold">
(diff: {formatCurrency(account.balance - calculatedBalance)})
(diff: {formatCurrency(account.balance - calculatedBalance)}
)
</span>
)}
</div>

View File

@@ -137,7 +137,8 @@ export function AccountEditDialog({
placeholder="0.00"
/>
<p className="text-xs text-muted-foreground">
Solde de départ pour équilibrer le compte. Le balance sera calculé automatiquement (solde total - solde initial).
Solde de départ pour équilibrer le compte. Le balance sera calculé
automatiquement (solde total - solde initial).
</p>
</div>
<div className="space-y-2">

View File

@@ -54,7 +54,11 @@ export function AccountMergeSelectDialog({
}
const handleMerge = async () => {
if (!sourceAccountId || !targetAccountId || sourceAccountId === targetAccountId) {
if (
!sourceAccountId ||
!targetAccountId ||
sourceAccountId === targetAccountId
) {
return;
}
@@ -105,13 +109,20 @@ export function AccountMergeSelectDialog({
<div className="space-y-3">
<Label>Compte à conserver (destination)</Label>
<RadioGroup value={targetAccountId} onValueChange={setTargetAccountId}>
<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" />
<RadioGroupItem
value={account.id}
id={account.id}
className="mt-1"
/>
<Label
htmlFor={account.id}
className="flex-1 cursor-pointer space-y-1"
@@ -120,7 +131,9 @@ export function AccountMergeSelectDialog({
<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>
Solde: {formatCurrency(getAccountBalance(account))}
</div>
</div>
</Label>
</div>
@@ -133,9 +146,9 @@ export function AccountMergeSelectDialog({
<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.
{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>
)}
@@ -153,4 +166,3 @@ export function AccountMergeSelectDialog({
</Dialog>
);
}

View File

@@ -130,9 +130,7 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
<span
className={cn(
"font-bold tabular-nums text-base",
realBalance >= 0
? "text-success"
: "text-destructive",
realBalance >= 0 ? "text-success" : "text-destructive",
)}
>
{formatCurrency(realBalance)}

View File

@@ -19,7 +19,7 @@ interface OverviewCardsProps {
export function OverviewCards({ data }: OverviewCardsProps) {
const totalBalance = data.accounts.reduce(
(sum, acc) => sum + getAccountBalance(acc),
0
0,
);
const thisMonth = new Date();
@@ -27,7 +27,7 @@ export function OverviewCards({ data }: OverviewCardsProps) {
const thisMonthStr = thisMonth.toISOString().slice(0, 7);
const monthTransactions = data.transactions.filter((t) =>
t.date.startsWith(thisMonthStr)
t.date.startsWith(thisMonthStr),
);
const income = monthTransactions
@@ -44,7 +44,7 @@ export function OverviewCards({ data }: OverviewCardsProps) {
total > 0 ? ((reconciled / total) * 100).toFixed(2) : "0.00";
const categorized = data.transactions.filter(
(t) => t.categoryId !== null
(t) => t.categoryId !== null,
).length;
const categorizedPercent =
total > 0 ? ((categorized / total) * 100).toFixed(2) : "0.00";
@@ -61,7 +61,10 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card className="stat-card-gradient-1 card-hover group relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<Wallet className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-primary" strokeWidth={1} />
<Wallet
className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-primary"
strokeWidth={1}
/>
</div>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3 px-5 pt-5 sm:px-6 sm:pt-6 relative z-10">
<CardTitle className="text-[10px] sm:text-xs font-semibold text-muted-foreground/80 leading-tight uppercase tracking-wider flex-1 min-w-0">
@@ -72,9 +75,7 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<div
className={cn(
"text-2xl sm:text-3xl md:text-3xl lg:text-2xl xl:text-3xl font-black tracking-tight mb-3 leading-tight break-words",
totalBalance >= 0
? "text-success"
: "text-destructive"
totalBalance >= 0 ? "text-success" : "text-destructive",
)}
>
{formatCurrency(totalBalance)}
@@ -88,7 +89,10 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card className="stat-card-gradient-2 card-hover group relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<TrendingUp className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-success" strokeWidth={1} />
<TrendingUp
className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-success"
strokeWidth={1}
/>
</div>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3 px-5 pt-5 sm:px-6 sm:pt-6 relative z-10">
<CardTitle className="text-[10px] sm:text-xs font-semibold text-muted-foreground/80 leading-tight uppercase tracking-wider flex-1 min-w-0">
@@ -111,7 +115,10 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card className="stat-card-gradient-3 card-hover group relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<TrendingDown className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-destructive" strokeWidth={1} />
<TrendingDown
className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-destructive"
strokeWidth={1}
/>
</div>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3 px-5 pt-5 sm:px-6 sm:pt-6 relative z-10">
<CardTitle className="text-[10px] sm:text-xs font-semibold text-muted-foreground/80 leading-tight uppercase tracking-wider flex-1 min-w-0">
@@ -134,7 +141,10 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card className="stat-card-gradient-4 card-hover group relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<CreditCard className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-chart-4" strokeWidth={1} />
<CreditCard
className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-chart-4"
strokeWidth={1}
/>
</div>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3 px-5 pt-5 sm:px-6 sm:pt-6 relative z-10">
<CardTitle className="text-[10px] sm:text-xs font-semibold text-muted-foreground/80 leading-tight uppercase tracking-wider flex-1 min-w-0">
@@ -154,7 +164,10 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card className="stat-card-gradient-5 card-hover group relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<Tag className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-chart-5" strokeWidth={1} />
<Tag
className="h-20 w-20 sm:h-24 sm:w-24 md:h-28 md:w-28 text-chart-5"
strokeWidth={1}
/>
</div>
<CardHeader className="flex flex-row items-start justify-between space-y-0 pb-3 px-5 pt-5 sm:px-6 sm:pt-6 relative z-10">
<CardTitle className="text-[10px] sm:text-xs font-semibold text-muted-foreground/80 leading-tight uppercase tracking-wider flex-1 min-w-0">

View File

@@ -89,7 +89,7 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
"font-black tabular-nums text-sm md:text-base shrink-0 md:hidden",
transaction.amount >= 0
? "text-success"
: "text-destructive"
: "text-destructive",
)}
>
{transaction.amount >= 0 ? "+" : ""}
@@ -131,7 +131,7 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
"font-black tabular-nums text-base md:text-lg shrink-0 hidden md:block leading-tight",
transaction.amount >= 0
? "text-success"
: "text-destructive"
: "text-destructive",
)}
>
{transaction.amount >= 0 ? "+" : ""}

View File

@@ -93,7 +93,7 @@ function SidebarContent({
"w-full justify-start gap-3 h-12 rounded-2xl px-3",
isActive &&
"bg-gradient-to-r from-primary/15 via-primary/10 to-primary/5 border-2 border-primary/30 shadow-lg shadow-primary/10 backdrop-blur-sm",
collapsed && "justify-center px-2 w-12 mx-auto"
collapsed && "justify-center px-2 w-12 mx-auto",
)}
>
<item.icon
@@ -103,7 +103,7 @@ function SidebarContent({
<span
className={cn(
"font-semibold text-sm",
isActive && "text-primary font-bold"
isActive && "text-primary font-bold",
)}
>
{item.label}
@@ -118,7 +118,7 @@ function SidebarContent({
<div
className={cn(
"border-t border-border/30 pt-2",
collapsed ? "p-2" : "p-4"
collapsed ? "p-2" : "p-4",
)}
>
<Link href="/settings" onClick={handleLinkClick} className="block mb-2">
@@ -126,7 +126,7 @@ function SidebarContent({
variant="ghost"
className={cn(
"w-full justify-start gap-3 h-12 rounded-2xl px-3",
collapsed && "justify-center px-2 w-12 mx-auto"
collapsed && "justify-center px-2 w-12 mx-auto",
)}
>
<Settings className="w-5 h-5 shrink-0" />
@@ -141,7 +141,7 @@ function SidebarContent({
className={cn(
"w-full justify-start gap-3 h-12 rounded-2xl px-3 mb-2",
"text-destructive",
collapsed && "justify-center px-2 w-12 mx-auto"
collapsed && "justify-center px-2 w-12 mx-auto",
)}
>
<LogOut className="w-5 h-5 shrink-0" />
@@ -187,13 +187,13 @@ export function Sidebar({ open, onOpenChange }: SidebarProps) {
className={cn(
"hidden md:flex flex-col h-screen bg-sidebar text-sidebar-foreground border-r border-sidebar-border",
"backdrop-blur-xl",
collapsed ? "w-16" : "w-64"
collapsed ? "w-16" : "w-64",
)}
>
<div
className={cn(
"flex items-center border-b border-border/30",
collapsed ? "justify-center p-4" : "justify-between p-6"
collapsed ? "justify-center p-4" : "justify-between p-6",
)}
>
{!collapsed && (

View File

@@ -12,7 +12,7 @@ export function BackgroundProvider() {
const applyBackground = () => {
try {
const pageBackground = document.querySelector(
".page-background"
".page-background",
) as HTMLElement;
if (!pageBackground) return;
@@ -30,7 +30,7 @@ export function BackgroundProvider() {
"bg-gradient-orange",
"bg-solid-light",
"bg-solid-dark",
"bg-custom-image"
"bg-custom-image",
);
const root = document.documentElement;
@@ -39,7 +39,7 @@ export function BackgroundProvider() {
pageBackground.classList.add("bg-custom-image");
root.style.setProperty(
"--custom-background-image",
`url(${settings.customImageUrl})`
`url(${settings.customImageUrl})`,
);
} else {
pageBackground.classList.add(`bg-${settings.type || "default"}`);

View File

@@ -40,43 +40,50 @@ const DEFAULT_BACKGROUNDS: Array<{
{
value: "default",
label: "Neutre",
preview: "linear-gradient(135deg, oklch(0.985 0 0) 0%, oklch(0.97 0.005 260) 50%, oklch(0.985 0 0) 100%)",
preview:
"linear-gradient(135deg, oklch(0.985 0 0) 0%, oklch(0.97 0.005 260) 50%, oklch(0.985 0 0) 100%)",
description: "Fond neutre et élégant",
},
{
value: "gradient-blue",
label: "Océan",
preview: "linear-gradient(135deg, oklch(0.95 0.03 230) 0%, oklch(0.88 0.08 225) 50%, oklch(0.78 0.12 220) 100%)",
preview:
"linear-gradient(135deg, oklch(0.95 0.03 230) 0%, oklch(0.88 0.08 225) 50%, oklch(0.78 0.12 220) 100%)",
description: "Dégradé bleu apaisant",
},
{
value: "gradient-purple",
label: "Améthyste",
preview: "linear-gradient(135deg, oklch(0.95 0.04 300) 0%, oklch(0.88 0.1 295) 50%, oklch(0.78 0.15 290) 100%)",
preview:
"linear-gradient(135deg, oklch(0.95 0.04 300) 0%, oklch(0.88 0.1 295) 50%, oklch(0.78 0.15 290) 100%)",
description: "Dégradé violet sophistiqué",
},
{
value: "gradient-green",
label: "Forêt",
preview: "linear-gradient(135deg, oklch(0.95 0.04 160) 0%, oklch(0.88 0.1 155) 50%, oklch(0.78 0.14 150) 100%)",
preview:
"linear-gradient(135deg, oklch(0.95 0.04 160) 0%, oklch(0.88 0.1 155) 50%, oklch(0.78 0.14 150) 100%)",
description: "Dégradé vert naturel",
},
{
value: "gradient-orange",
label: "Aurore",
preview: "linear-gradient(135deg, oklch(0.97 0.03 80) 0%, oklch(0.92 0.08 60) 50%, oklch(0.85 0.14 45) 100%)",
preview:
"linear-gradient(135deg, oklch(0.97 0.03 80) 0%, oklch(0.92 0.08 60) 50%, oklch(0.85 0.14 45) 100%)",
description: "Dégradé orange chaleureux",
},
{
value: "solid-light",
label: "Lumineux",
preview: "linear-gradient(135deg, oklch(1 0 0) 0%, oklch(0.98 0.005 260) 100%)",
preview:
"linear-gradient(135deg, oklch(1 0 0) 0%, oklch(0.98 0.005 260) 100%)",
description: "Fond blanc épuré",
},
{
value: "solid-dark",
label: "Minuit",
preview: "linear-gradient(135deg, oklch(0.18 0.02 260) 0%, oklch(0.08 0.015 250) 100%)",
preview:
"linear-gradient(135deg, oklch(0.18 0.02 260) 0%, oklch(0.08 0.015 250) 100%)",
description: "Fond sombre immersif",
},
];
@@ -89,14 +96,14 @@ export function BackgroundCard() {
const currentSettings = useMemo<BackgroundSettings>(
() => backgroundSettings || { type: "default" },
[backgroundSettings]
[backgroundSettings],
);
const [customImageUrl, setCustomImageUrl] = useState(
currentSettings.customImageUrl || ""
currentSettings.customImageUrl || "",
);
const [showCustomInput, setShowCustomInput] = useState(
currentSettings.type === "custom-image"
currentSettings.type === "custom-image",
);
// Synchroniser customImageUrl avec les settings
@@ -112,7 +119,7 @@ export function BackgroundCard() {
const applyBackground = (settings: BackgroundSettings) => {
const root = document.documentElement;
const pageBackground = document.querySelector(
".page-background"
".page-background",
) as HTMLElement;
if (!pageBackground) return;
@@ -126,14 +133,14 @@ export function BackgroundCard() {
"bg-gradient-orange",
"bg-solid-light",
"bg-solid-dark",
"bg-custom-image"
"bg-custom-image",
);
if (settings.type === "custom-image" && settings.customImageUrl) {
pageBackground.classList.add("bg-custom-image");
root.style.setProperty(
"--custom-background-image",
`url(${settings.customImageUrl})`
`url(${settings.customImageUrl})`,
);
} else {
pageBackground.classList.add(`bg-${settings.type || "default"}`);
@@ -142,7 +149,7 @@ export function BackgroundCard() {
// Déclencher un événement personnalisé pour notifier les autres composants
window.dispatchEvent(
new CustomEvent("background-changed", { detail: settings })
new CustomEvent("background-changed", { detail: settings }),
);
};
@@ -222,7 +229,7 @@ export function BackgroundCard() {
"relative flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer transition-all",
currentSettings.type === bg.value
? "border-primary bg-primary/5"
: "border-border hover:border-primary/50"
: "border-border hover:border-primary/50",
)}
>
<RadioGroupItem
@@ -245,7 +252,7 @@ export function BackgroundCard() {
"relative flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer transition-all",
currentSettings.type === "custom-image"
? "border-primary bg-primary/5"
: "border-border hover:border-primary/50"
: "border-border hover:border-primary/50",
)}
>
<RadioGroupItem

View File

@@ -46,7 +46,7 @@ export function DangerZoneCard({
const result = await onDeduplicate();
if (result.deletedCount > 0) {
alert(
`${result.deletedCount} transaction${result.deletedCount > 1 ? "s" : ""} en double supprimée${result.deletedCount > 1 ? "s" : ""}`
`${result.deletedCount} transaction${result.deletedCount > 1 ? "s" : ""} en double supprimée${result.deletedCount > 1 ? "s" : ""}`,
);
} else {
alert("Aucun doublon trouvé");

View File

@@ -45,11 +45,12 @@ export function ReconcileDateRangeCard() {
setIsReconciling(true);
try {
const endDateStr = format(endDate, "yyyy-MM-dd");
const body: { endDate: string; startDate?: string; reconciled: boolean } = {
endDate: endDateStr,
reconciled: true,
};
const body: { endDate: string; startDate?: string; reconciled: boolean } =
{
endDate: endDateStr,
reconciled: true,
};
if (startDate) {
body.startDate = format(startDate, "yyyy-MM-dd");
}
@@ -69,7 +70,7 @@ export function ReconcileDateRangeCard() {
}
const result = await response.json();
// Invalider toutes les requêtes de transactions pour rafraîchir les données
invalidateAllTransactionQueries(queryClient);
@@ -118,7 +119,7 @@ export function ReconcileDateRangeCard() {
<>
{startDate ? (
<>
{format(startDate, "PPP", { locale: fr})} -{" "}
{format(startDate, "PPP", { locale: fr })} -{" "}
{format(endDate, "PPP", { locale: fr })}
</>
) : (
@@ -134,7 +135,10 @@ export function ReconcileDateRangeCard() {
<div className="p-3 flex gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">
Date de début <span className="text-xs text-muted-foreground">(optionnel)</span>
Date de début{" "}
<span className="text-xs text-muted-foreground">
(optionnel)
</span>
</label>
<div className="scale-90 origin-top-left">
<CalendarComponent
@@ -233,8 +237,8 @@ export function ReconcileDateRangeCard() {
) : (
<>jusqu'au {format(endDate, "PPP", { locale: fr })}</>
)}{" "}
comme pointées. Seules les opérations non encore pointées seront
modifiées.
comme pointées. Seules les opérations non encore pointées
seront modifiées.
</>
)}
</AlertDialogDescription>
@@ -257,4 +261,3 @@ export function ReconcileDateRangeCard() {
</Card>
);
}

View File

@@ -70,7 +70,7 @@ export function ThemeCard() {
"relative flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer transition-all",
currentTheme === themeOption.value
? "border-primary bg-primary/5"
: "border-border hover:border-primary/50"
: "border-border hover:border-primary/50",
)}
>
<RadioGroupItem

View File

@@ -42,7 +42,7 @@ export function CategoryBarChart({
}) => {
const categoryName = payload.value;
const item = displayData.find((d) => d.name === categoryName);
if (!item) {
return (
<text

View File

@@ -54,7 +54,7 @@ export function MonthlyChart({
// Formater les labels de manière plus compacte
const formatMonthLabel = (month: string) => {
// Format: "janv. 24" -> "janv 24" (enlever le point)
return month.replace('.', '');
return month.replace(".", "");
};
const chartContent = (
@@ -62,10 +62,18 @@ export function MonthlyChart({
{data.length > 0 ? (
<div className="h-[400px] sm:h-[300px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ left: 0, right: 10, top: 10, bottom: data.length > 6 ? 80 : 60 }}>
<LineChart
data={data}
margin={{
left: 0,
right: 10,
top: 10,
bottom: data.length > 6 ? 80 : 60,
}}
>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis
dataKey="month"
<XAxis
dataKey="month"
className="text-xs"
angle={data.length > 6 ? -45 : 0}
textAnchor={data.length > 6 ? "end" : "middle"}

View File

@@ -24,7 +24,10 @@ export function StatsSummaryCards({
<Card className="stat-card-textured relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<TrendingUp className="h-16 w-16 md:h-20 md:w-20 text-success" strokeWidth={1} />
<TrendingUp
className="h-16 w-16 md:h-20 md:w-20 text-success"
strokeWidth={1}
/>
</div>
<CardHeader className="pb-3 px-5 pt-5 relative z-10">
<CardTitle className="text-[10px] md:text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">
@@ -41,7 +44,10 @@ export function StatsSummaryCards({
<Card className="stat-card-textured relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<TrendingDown className="h-16 w-16 md:h-20 md:w-20 text-destructive" strokeWidth={1} />
<TrendingDown
className="h-16 w-16 md:h-20 md:w-20 text-destructive"
strokeWidth={1}
/>
</div>
<CardHeader className="pb-3 px-5 pt-5 relative z-10">
<CardTitle className="text-[10px] md:text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">
@@ -58,7 +64,10 @@ export function StatsSummaryCards({
<Card className="stat-card-textured relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<ArrowRight className="h-16 w-16 md:h-20 md:w-20 text-muted-foreground/40" strokeWidth={1} />
<ArrowRight
className="h-16 w-16 md:h-20 md:w-20 text-muted-foreground/40"
strokeWidth={1}
/>
</div>
<CardHeader className="pb-3 px-5 pt-5 relative z-10">
<CardTitle className="text-[10px] md:text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">
@@ -75,12 +84,12 @@ export function StatsSummaryCards({
<Card className="stat-card-textured relative overflow-hidden">
{/* Icône en arrière-plan */}
<div className="absolute bottom-2 right-2 opacity-[0.04] z-0 pointer-events-none">
<div className={cn(
"h-16 w-16 md:h-20 md:w-20 rounded-full border-2",
savings >= 0
? "border-success"
: "border-destructive"
)} />
<div
className={cn(
"h-16 w-16 md:h-20 md:w-20 rounded-full border-2",
savings >= 0 ? "border-success" : "border-destructive",
)}
/>
</div>
<CardHeader className="pb-3 px-5 pt-5 relative z-10">
<CardTitle className="text-[10px] md:text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider">

View File

@@ -4,12 +4,7 @@ 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";
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@/components/ui/tabs";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { useIsMobile } from "@/hooks/use-mobile";
import type { Transaction, Category } from "@/lib/types";
@@ -111,40 +106,41 @@ export function TopExpensesList({
"fr-FR",
)}
</span>
{expense.categoryId && (() => {
const expenseCategory = categories.find(
(c) => c.id === expense.categoryId,
);
// Afficher seulement si c'est une sous-catégorie (a un parentId)
if (expenseCategory?.parentId) {
return (
<Link
href={`/transactions?categoryIds=${expenseCategory.id}`}
className="inline-block"
>
<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: `${expenseCategory.color}20`,
color: expenseCategory.color,
borderColor: `${expenseCategory.color}30`,
}}
>
<CategoryIcon
icon={expenseCategory.icon}
color={expenseCategory.color}
size={isMobile ? 8 : 10}
/>
<span className="truncate max-w-[100px] md:max-w-none">
{expenseCategory.name}
</span>
</Badge>
</Link>
{expense.categoryId &&
(() => {
const expenseCategory = categories.find(
(c) => c.id === expense.categoryId,
);
}
return null;
})()}
// Afficher seulement si c'est une sous-catégorie (a un parentId)
if (expenseCategory?.parentId) {
return (
<Link
href={`/transactions?categoryIds=${expenseCategory.id}`}
className="inline-block"
>
<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: `${expenseCategory.color}20`,
color: expenseCategory.color,
borderColor: `${expenseCategory.color}30`,
}}
>
<CategoryIcon
icon={expenseCategory.icon}
color={expenseCategory.color}
size={isMobile ? 8 : 10}
/>
<span className="truncate max-w-[100px] md:max-w-none">
{expenseCategory.name}
</span>
</Badge>
</Link>
);
}
return null;
})()}
</div>
</div>
</div>

View File

@@ -245,26 +245,26 @@ export function TransactionFilters({
</div>
{customStartDate && customEndDate && (
<div className="flex gap-2 pt-2 border-t px-3 pb-3">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
onCustomStartDateChange(undefined);
onCustomEndDateChange(undefined);
}}
>
Réinitialiser
</Button>
<Button
size="sm"
className="flex-1"
onClick={() => onCustomDatePickerOpenChange(false)}
>
Valider
</Button>
</div>
)}
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
onCustomStartDateChange(undefined);
onCustomEndDateChange(undefined);
}}
>
Réinitialiser
</Button>
<Button
size="sm"
className="flex-1"
onClick={() => onCustomDatePickerOpenChange(false)}
>
Valider
</Button>
</div>
)}
</PopoverContent>
</Popover>
)}

View File

@@ -169,7 +169,7 @@ export function TransactionTable({
setFocusedIndex(index);
onMarkReconciled(transactionId);
},
[onMarkReconciled]
[onMarkReconciled],
);
const handleKeyDown = useCallback(
@@ -198,7 +198,7 @@ export function TransactionTable({
}
}
},
[focusedIndex, transactions, onMarkReconciled, virtualizer]
[focusedIndex, transactions, onMarkReconciled, virtualizer],
);
useEffect(() => {
@@ -215,7 +215,7 @@ export function TransactionTable({
(accountId: string) => {
return accounts.find((a) => a.id === accountId);
},
[accounts]
[accounts],
);
const getCategory = useCallback(
@@ -223,7 +223,7 @@ export function TransactionTable({
if (!categoryId) return null;
return categories.find((c) => c.id === categoryId);
},
[categories]
[categories],
);
return (
@@ -281,7 +281,7 @@ export function TransactionTable({
"p-4 md:p-4 space-y-3 hover:bg-muted/50 cursor-pointer border-b border-border",
transaction.isReconciled && "bg-emerald-500/5",
isFocused && "bg-primary/10 ring-1 ring-primary/30",
isDuplicate && "shadow-sm"
isDuplicate && "shadow-sm",
)}
>
<div className="flex items-start justify-between gap-3">
@@ -324,7 +324,7 @@ export function TransactionTable({
"font-semibold tabular-nums text-base md:text-base shrink-0",
transaction.amount >= 0
? "text-success"
: "text-destructive"
: "text-destructive",
)}
>
{transaction.amount >= 0 ? "+" : ""}
@@ -359,7 +359,7 @@ export function TransactionTable({
showBadge
align="start"
disabled={updatingTransactionIds.has(
transaction.id
transaction.id,
)}
/>
</div>
@@ -392,7 +392,7 @@ export function TransactionTable({
e.stopPropagation();
if (
confirm(
`Êtes-vous sûr de vouloir supprimer cette transaction ?\n\n${transaction.description}\n${formatCurrency(transaction.amount)}`
`Êtes-vous sûr de vouloir supprimer cette transaction ?\n\n${transaction.description}\n${formatCurrency(transaction.amount)}`,
)
) {
onDelete(transaction.id);
@@ -508,7 +508,7 @@ export function TransactionTable({
"grid grid-cols-[auto_120px_2fr_150px_180px_140px_auto_auto] gap-0 border-b border-border hover:bg-muted/50 cursor-pointer",
transaction.isReconciled && "bg-emerald-500/5",
isFocused && "bg-primary/10 ring-1 ring-primary/30",
isDuplicate && "shadow-sm"
isDuplicate && "shadow-sm",
)}
>
<div className="p-3" onClick={(e) => e.stopPropagation()}>
@@ -577,7 +577,7 @@ export function TransactionTable({
"p-3 text-right font-semibold tabular-nums",
transaction.amount >= 0
? "text-success"
: "text-destructive"
: "text-destructive",
)}
>
{transaction.amount >= 0 ? "+" : ""}
@@ -644,7 +644,7 @@ export function TransactionTable({
e.stopPropagation();
if (
confirm(
`Êtes-vous sûr de vouloir supprimer cette transaction ?\n\n${transaction.description}\n${formatCurrency(transaction.amount)}`
`Êtes-vous sûr de vouloir supprimer cette transaction ?\n\n${transaction.description}\n${formatCurrency(transaction.amount)}`,
)
) {
onDelete(transaction.id);

View File

@@ -22,7 +22,7 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
}
},
);
function Badge({

View File

@@ -33,7 +33,7 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
},
);
function Button({

View File

@@ -99,10 +99,7 @@ export function CategoryFilterCombobox({
if (isAll || isUncategorized) {
// Start fresh with this category and its children (if parent)
if (isParentCategory && childCategories.length > 0) {
newSelection = [
newValue,
...childCategories.map((child) => child.id),
];
newSelection = [newValue, ...childCategories.map((child) => child.id)];
} else {
newSelection = [newValue];
}
@@ -111,7 +108,7 @@ export function CategoryFilterCombobox({
if (isParentCategory && childCategories.length > 0) {
const childIds = childCategories.map((child) => child.id);
newSelection = value.filter(
(v) => v !== newValue && !childIds.includes(v)
(v) => v !== newValue && !childIds.includes(v),
);
} else {
newSelection = value.filter((v) => v !== newValue);

View File

@@ -15,7 +15,7 @@ function Checkbox({
data-slot="checkbox"
className={cn(
"peer border-input bg-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/30 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
{...props}
>

View File

@@ -39,7 +39,7 @@ function DialogOverlay({
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
className,
)}
{...props}
/>
@@ -61,7 +61,7 @@ function DialogContent({
data-slot="dialog-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] max-h-[calc(100vh-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 overflow-y-auto sm:max-w-lg",
className
className,
)}
{...props}
>
@@ -96,7 +96,7 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
className,
)}
{...props}
/>

View File

@@ -16,7 +16,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
"focus-visible:border-primary/50 focus-visible:ring-primary/20 focus-visible:ring-[3px] focus-visible:shadow-md focus-visible:shadow-primary/10",
"hover:border-primary/30 hover:shadow-sm",
"aria-invalid:ring-destructive/30 aria-invalid:border-destructive",
className
className,
)}
{...props}
/>

View File

@@ -8,7 +8,7 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
"bg-muted w-fit text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/15 [[data-slot=tooltip-content]_&]:text-background",
className
className,
)}
{...props}
/>