refactor: standardize quotation marks across all files and improve code consistency

This commit is contained in:
Julien Froidefond
2025-11-27 11:40:30 +01:00
parent cc1e8c20a6
commit b2efade4d5
107 changed files with 9471 additions and 5952 deletions

View File

@@ -1,13 +1,13 @@
"use client"
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Progress } from "@/components/ui/progress"
import type { BankingData } from "@/lib/types"
import { cn } from "@/lib/utils"
import { Building2 } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import type { BankingData } from "@/lib/types";
import { cn } from "@/lib/utils";
import { Building2 } from "lucide-react";
interface AccountsSummaryProps {
data: BankingData
data: BankingData;
}
export function AccountsSummary({ data }: AccountsSummaryProps) {
@@ -15,10 +15,12 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(amount)
}
}).format(amount);
};
const totalPositive = data.accounts.filter((a) => a.balance > 0).reduce((sum, a) => sum + a.balance, 0)
const totalPositive = data.accounts
.filter((a) => a.balance > 0)
.reduce((sum, a) => sum + a.balance, 0);
if (data.accounts.length === 0) {
return (
@@ -30,11 +32,13 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
<div className="flex flex-col items-center justify-center py-8 text-center">
<Building2 className="w-12 h-12 text-muted-foreground mb-3" />
<p className="text-muted-foreground">Aucun compte</p>
<p className="text-sm text-muted-foreground mt-1">Importez un fichier OFX pour ajouter un compte</p>
<p className="text-sm text-muted-foreground mt-1">
Importez un fichier OFX pour ajouter un compte
</p>
</div>
</CardContent>
</Card>
)
);
}
return (
@@ -45,7 +49,10 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
<CardContent>
<div className="space-y-4">
{data.accounts.map((account) => {
const percentage = totalPositive > 0 ? Math.max(0, (account.balance / totalPositive) * 100) : 0
const percentage =
totalPositive > 0
? Math.max(0, (account.balance / totalPositive) * 100)
: 0;
return (
<div key={account.id} className="space-y-2">
@@ -57,25 +64,31 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
<div>
<p className="font-medium text-sm">{account.name}</p>
<p className="text-xs text-muted-foreground">
{account.accountNumber.slice(-4).padStart(account.accountNumber.length, "*")}
{account.accountNumber
.slice(-4)
.padStart(account.accountNumber.length, "*")}
</p>
</div>
</div>
<span
className={cn(
"font-semibold tabular-nums",
account.balance >= 0 ? "text-emerald-600" : "text-red-600",
account.balance >= 0
? "text-emerald-600"
: "text-red-600",
)}
>
{formatCurrency(account.balance)}
</span>
</div>
{account.balance > 0 && <Progress value={percentage} className="h-1.5" />}
{account.balance > 0 && (
<Progress value={percentage} className="h-1.5" />
)}
</div>
)
);
})}
</div>
</CardContent>
</Card>
)
);
}

View File

@@ -1,47 +1,56 @@
"use client"
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import type { BankingData } from "@/lib/types"
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from "recharts"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { BankingData } from "@/lib/types";
import {
PieChart,
Pie,
Cell,
ResponsiveContainer,
Legend,
Tooltip,
} from "recharts";
interface CategoryBreakdownProps {
data: BankingData
data: BankingData;
}
export function CategoryBreakdown({ data }: CategoryBreakdownProps) {
// Get current month expenses by category
const thisMonth = new Date()
thisMonth.setDate(1)
const thisMonthStr = thisMonth.toISOString().slice(0, 7)
const thisMonth = new Date();
thisMonth.setDate(1);
const thisMonthStr = thisMonth.toISOString().slice(0, 7);
const monthExpenses = data.transactions.filter((t) => t.date.startsWith(thisMonthStr) && t.amount < 0)
const monthExpenses = data.transactions.filter(
(t) => t.date.startsWith(thisMonthStr) && t.amount < 0,
);
const categoryTotals = new Map<string, number>()
const categoryTotals = new Map<string, number>();
monthExpenses.forEach((t) => {
const catId = t.categoryId || "uncategorized"
const current = categoryTotals.get(catId) || 0
categoryTotals.set(catId, current + Math.abs(t.amount))
})
const catId = t.categoryId || "uncategorized";
const current = categoryTotals.get(catId) || 0;
categoryTotals.set(catId, current + Math.abs(t.amount));
});
const chartData = Array.from(categoryTotals.entries())
.map(([categoryId, total]) => {
const category = data.categories.find((c) => c.id === categoryId)
const category = data.categories.find((c) => c.id === categoryId);
return {
name: category?.name || "Non catégorisé",
value: total,
color: category?.color || "#94a3b8",
}
};
})
.sort((a, b) => b.value - a.value)
.slice(0, 6)
.slice(0, 6);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(value)
}
}).format(value);
};
if (chartData.length === 0) {
return (
@@ -55,7 +64,7 @@ export function CategoryBreakdown({ data }: CategoryBreakdownProps) {
</div>
</CardContent>
</Card>
)
);
}
return (
@@ -88,11 +97,15 @@ export function CategoryBreakdown({ data }: CategoryBreakdownProps) {
borderRadius: "8px",
}}
/>
<Legend formatter={(value) => <span className="text-sm text-foreground">{value}</span>} />
<Legend
formatter={(value) => (
<span className="text-sm text-foreground">{value}</span>
)}
/>
</PieChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
)
);
}

View File

@@ -1,46 +1,60 @@
"use client"
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { TrendingUp, TrendingDown, Wallet, CreditCard } from "lucide-react"
import type { BankingData } from "@/lib/types"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TrendingUp, TrendingDown, Wallet, CreditCard } from "lucide-react";
import type { BankingData } from "@/lib/types";
interface OverviewCardsProps {
data: BankingData
data: BankingData;
}
export function OverviewCards({ data }: OverviewCardsProps) {
const totalBalance = data.accounts.reduce((sum, acc) => sum + acc.balance, 0)
const totalBalance = data.accounts.reduce((sum, acc) => sum + acc.balance, 0);
const thisMonth = new Date()
thisMonth.setDate(1)
const thisMonthStr = thisMonth.toISOString().slice(0, 7)
const thisMonth = new Date();
thisMonth.setDate(1);
const thisMonthStr = thisMonth.toISOString().slice(0, 7);
const monthTransactions = data.transactions.filter((t) => t.date.startsWith(thisMonthStr))
const monthTransactions = data.transactions.filter((t) =>
t.date.startsWith(thisMonthStr),
);
const income = monthTransactions.filter((t) => t.amount > 0).reduce((sum, t) => sum + t.amount, 0)
const income = monthTransactions
.filter((t) => t.amount > 0)
.reduce((sum, t) => sum + t.amount, 0);
const expenses = monthTransactions.filter((t) => t.amount < 0).reduce((sum, t) => sum + Math.abs(t.amount), 0)
const expenses = monthTransactions
.filter((t) => t.amount < 0)
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
const reconciled = data.transactions.filter((t) => t.isReconciled).length
const total = data.transactions.length
const reconciledPercent = total > 0 ? Math.round((reconciled / total) * 100) : 0
const reconciled = data.transactions.filter((t) => t.isReconciled).length;
const total = data.transactions.length;
const reconciledPercent =
total > 0 ? Math.round((reconciled / total) * 100) : 0;
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(amount)
}
}).format(amount);
};
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Solde Total</CardTitle>
<CardTitle className="text-sm font-medium text-muted-foreground">
Solde Total
</CardTitle>
<Wallet className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className={cn("text-2xl font-bold", totalBalance >= 0 ? "text-emerald-600" : "text-red-600")}>
<div
className={cn(
"text-2xl font-bold",
totalBalance >= 0 ? "text-emerald-600" : "text-red-600",
)}
>
{formatCurrency(totalBalance)}
</div>
<p className="text-xs text-muted-foreground mt-1">
@@ -51,35 +65,49 @@ export function OverviewCards({ data }: OverviewCardsProps) {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Revenus du mois</CardTitle>
<CardTitle className="text-sm font-medium text-muted-foreground">
Revenus du mois
</CardTitle>
<TrendingUp className="h-4 w-4 text-emerald-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-emerald-600">{formatCurrency(income)}</div>
<div className="text-2xl font-bold text-emerald-600">
{formatCurrency(income)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{monthTransactions.filter((t) => t.amount > 0).length} opération
{monthTransactions.filter((t) => t.amount > 0).length > 1 ? "s" : ""}
{monthTransactions.filter((t) => t.amount > 0).length > 1
? "s"
: ""}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Dépenses du mois</CardTitle>
<CardTitle className="text-sm font-medium text-muted-foreground">
Dépenses du mois
</CardTitle>
<TrendingDown className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">{formatCurrency(expenses)}</div>
<div className="text-2xl font-bold text-red-600">
{formatCurrency(expenses)}
</div>
<p className="text-xs text-muted-foreground mt-1">
{monthTransactions.filter((t) => t.amount < 0).length} opération
{monthTransactions.filter((t) => t.amount < 0).length > 1 ? "s" : ""}
{monthTransactions.filter((t) => t.amount < 0).length > 1
? "s"
: ""}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Pointage</CardTitle>
<CardTitle className="text-sm font-medium text-muted-foreground">
Pointage
</CardTitle>
<CreditCard className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
@@ -90,7 +118,7 @@ export function OverviewCards({ data }: OverviewCardsProps) {
</CardContent>
</Card>
</div>
)
);
}
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";

View File

@@ -1,43 +1,43 @@
"use client"
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { CheckCircle2, Circle } from "lucide-react"
import { CategoryIcon } from "@/components/ui/category-icon"
import type { BankingData } from "@/lib/types"
import { cn } from "@/lib/utils"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { CheckCircle2, Circle } from "lucide-react";
import { CategoryIcon } from "@/components/ui/category-icon";
import type { BankingData } from "@/lib/types";
import { cn } from "@/lib/utils";
interface RecentTransactionsProps {
data: BankingData
data: BankingData;
}
export function RecentTransactions({ data }: RecentTransactionsProps) {
const recentTransactions = [...data.transactions]
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.slice(0, 10)
.slice(0, 10);
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR",
}).format(amount)
}
}).format(amount);
};
const formatDate = (dateStr: string) => {
return new Date(dateStr).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "short",
})
}
});
};
const getCategory = (categoryId: string | null) => {
if (!categoryId) return null
return data.categories.find((c) => c.id === categoryId)
}
if (!categoryId) return null;
return data.categories.find((c) => c.id === categoryId);
};
const getAccount = (accountId: string) => {
return data.accounts.find((a) => a.id === accountId)
}
return data.accounts.find((a) => a.id === accountId);
};
if (recentTransactions.length === 0) {
return (
@@ -48,11 +48,13 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
<CardContent>
<div className="flex flex-col items-center justify-center py-8 text-center">
<p className="text-muted-foreground">Aucune transaction</p>
<p className="text-sm text-muted-foreground mt-1">Importez un fichier OFX pour commencer</p>
<p className="text-sm text-muted-foreground mt-1">
Importez un fichier OFX pour commencer
</p>
</div>
</CardContent>
</Card>
)
);
}
return (
@@ -63,8 +65,8 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
<CardContent>
<div className="space-y-3">
{recentTransactions.map((transaction) => {
const category = getCategory(transaction.categoryId)
const account = getAccount(transaction.accountId)
const category = getCategory(transaction.categoryId);
const account = getAccount(transaction.accountId);
return (
<div
@@ -80,17 +82,32 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
</div>
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{transaction.description}</p>
<p className="font-medium truncate">
{transaction.description}
</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-muted-foreground">{formatDate(transaction.date)}</span>
{account && <span className="text-xs text-muted-foreground"> {account.name}</span>}
<span className="text-xs text-muted-foreground">
{formatDate(transaction.date)}
</span>
{account && (
<span className="text-xs text-muted-foreground">
{account.name}
</span>
)}
{category && (
<Badge
variant="secondary"
className="text-xs gap-1"
style={{ backgroundColor: `${category.color}20`, color: category.color }}
style={{
backgroundColor: `${category.color}20`,
color: category.color,
}}
>
<CategoryIcon icon={category.icon} color={category.color} size={12} />
<CategoryIcon
icon={category.icon}
color={category.color}
size={12}
/>
{category.name}
</Badge>
)}
@@ -100,17 +117,19 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
<div
className={cn(
"font-semibold tabular-nums",
transaction.amount >= 0 ? "text-emerald-600" : "text-red-600",
transaction.amount >= 0
? "text-emerald-600"
: "text-red-600",
)}
>
{transaction.amount >= 0 ? "+" : ""}
{formatCurrency(transaction.amount)}
</div>
</div>
)
);
})}
</div>
</CardContent>
</Card>
)
);
}

View File

@@ -1,10 +1,10 @@
"use client"
"use client";
import { useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
LayoutDashboard,
Wallet,
@@ -15,7 +15,7 @@ import {
ChevronLeft,
ChevronRight,
Settings,
} from "lucide-react"
} from "lucide-react";
const navItems = [
{ href: "/", label: "Tableau de bord", icon: LayoutDashboard },
@@ -24,11 +24,11 @@ const navItems = [
{ href: "/transactions", label: "Transactions", icon: Upload },
{ href: "/categories", label: "Catégories", icon: Tags },
{ href: "/statistics", label: "Statistiques", icon: BarChart3 },
]
];
export function Sidebar() {
const pathname = usePathname()
const [collapsed, setCollapsed] = useState(false)
const pathname = usePathname();
const [collapsed, setCollapsed] = useState(false);
return (
<aside
@@ -46,36 +46,54 @@ export function Sidebar() {
<span className="font-semibold text-foreground">FinTrack</span>
</div>
)}
<Button variant="ghost" size="icon" onClick={() => setCollapsed(!collapsed)} className="ml-auto">
{collapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
<Button
variant="ghost"
size="icon"
onClick={() => setCollapsed(!collapsed)}
className="ml-auto"
>
{collapsed ? (
<ChevronRight className="w-4 h-4" />
) : (
<ChevronLeft className="w-4 h-4" />
)}
</Button>
</div>
<nav className="flex-1 p-2 space-y-1">
{navItems.map((item) => {
const isActive = pathname === item.href
const isActive = pathname === item.href;
return (
<Link key={item.href} href={item.href}>
<Button
variant={isActive ? "secondary" : "ghost"}
className={cn("w-full justify-start gap-3", collapsed && "justify-center px-2")}
className={cn(
"w-full justify-start gap-3",
collapsed && "justify-center px-2",
)}
>
<item.icon className="w-5 h-5 shrink-0" />
{!collapsed && <span>{item.label}</span>}
</Button>
</Link>
)
);
})}
</nav>
<div className="p-2 border-t border-border">
<Link href="/settings">
<Button variant="ghost" className={cn("w-full justify-start gap-3", collapsed && "justify-center px-2")}>
<Button
variant="ghost"
className={cn(
"w-full justify-start gap-3",
collapsed && "justify-center px-2",
)}
>
<Settings className="w-5 h-5 shrink-0" />
{!collapsed && <span>Paramètres</span>}
</Button>
</Link>
</div>
</aside>
)
);
}