chore: clean up code by removing trailing whitespace and ensuring consistent formatting across various files = prettier

This commit is contained in:
Julien Froidefond
2025-12-01 08:37:30 +01:00
parent 757b1b84ab
commit e715779de7
98 changed files with 5453 additions and 3126 deletions

View File

@@ -29,7 +29,11 @@ import { Badge } from "@/components/ui/badge";
import { CategoryIcon } from "@/components/ui/category-icon";
import { Checkbox } from "@/components/ui/checkbox";
import { Filter, X, Wallet, CircleSlash, Calendar } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Calendar as CalendarComponent } from "@/components/ui/calendar";
import { Button } from "@/components/ui/button";
import { format } from "date-fns";
@@ -43,10 +47,17 @@ export default function StatisticsPage() {
const { data, isLoading } = useBankingData();
const [period, setPeriod] = useState<Period>("6months");
const [selectedAccounts, setSelectedAccounts] = useState<string[]>(["all"]);
const [selectedCategories, setSelectedCategories] = useState<string[]>(["all"]);
const [excludeInternalTransfers, setExcludeInternalTransfers] = useState(true);
const [customStartDate, setCustomStartDate] = useState<Date | undefined>(undefined);
const [customEndDate, setCustomEndDate] = useState<Date | undefined>(undefined);
const [selectedCategories, setSelectedCategories] = useState<string[]>([
"all",
]);
const [excludeInternalTransfers, setExcludeInternalTransfers] =
useState(true);
const [customStartDate, setCustomStartDate] = useState<Date | undefined>(
undefined,
);
const [customEndDate, setCustomEndDate] = useState<Date | undefined>(
undefined,
);
const [isCustomDatePickerOpen, setIsCustomDatePickerOpen] = useState(false);
// Get start date based on period
@@ -80,7 +91,7 @@ export default function StatisticsPage() {
const internalTransferCategory = useMemo(() => {
if (!data) return null;
return data.categories.find(
(c) => c.name.toLowerCase() === "virement interne"
(c) => c.name.toLowerCase() === "virement interne",
);
}, [data]);
@@ -88,73 +99,93 @@ export default function StatisticsPage() {
const transactionsForAccountFilter = useMemo(() => {
if (!data) return [];
return data.transactions.filter((t) => {
const transactionDate = new Date(t.date);
if (endDate) {
// Custom date range
const endOfDay = new Date(endDate);
endOfDay.setHours(23, 59, 59, 999);
if (transactionDate < startDate || transactionDate > endOfDay) {
return false;
}
} else {
// Standard period
if (transactionDate < startDate) {
return false;
}
}
return true;
}).filter((t) => {
if (!selectedCategories.includes("all")) {
if (selectedCategories.includes("uncategorized")) {
return !t.categoryId;
return data.transactions
.filter((t) => {
const transactionDate = new Date(t.date);
if (endDate) {
// Custom date range
const endOfDay = new Date(endDate);
endOfDay.setHours(23, 59, 59, 999);
if (transactionDate < startDate || transactionDate > endOfDay) {
return false;
}
} else {
return t.categoryId && selectedCategories.includes(t.categoryId);
// Standard period
if (transactionDate < startDate) {
return false;
}
}
}
return true;
}).filter((t) => {
// Exclude "Virement interne" category if checkbox is checked
if (excludeInternalTransfers && internalTransferCategory) {
return t.categoryId !== internalTransferCategory.id;
}
return true;
});
}, [data, startDate, endDate, selectedCategories, excludeInternalTransfers, internalTransferCategory]);
return true;
})
.filter((t) => {
if (!selectedCategories.includes("all")) {
if (selectedCategories.includes("uncategorized")) {
return !t.categoryId;
} else {
return t.categoryId && selectedCategories.includes(t.categoryId);
}
}
return true;
})
.filter((t) => {
// Exclude "Virement interne" category if checkbox is checked
if (excludeInternalTransfers && internalTransferCategory) {
return t.categoryId !== internalTransferCategory.id;
}
return true;
});
}, [
data,
startDate,
endDate,
selectedCategories,
excludeInternalTransfers,
internalTransferCategory,
]);
// Transactions filtered for category filter (by accounts, period - not categories)
const transactionsForCategoryFilter = useMemo(() => {
if (!data) return [];
return data.transactions.filter((t) => {
const transactionDate = new Date(t.date);
if (endDate) {
// Custom date range
const endOfDay = new Date(endDate);
endOfDay.setHours(23, 59, 59, 999);
if (transactionDate < startDate || transactionDate > endOfDay) {
return false;
return data.transactions
.filter((t) => {
const transactionDate = new Date(t.date);
if (endDate) {
// Custom date range
const endOfDay = new Date(endDate);
endOfDay.setHours(23, 59, 59, 999);
if (transactionDate < startDate || transactionDate > endOfDay) {
return false;
}
} else {
// Standard period
if (transactionDate < startDate) {
return false;
}
}
} else {
// Standard period
if (transactionDate < startDate) {
return false;
return true;
})
.filter((t) => {
if (!selectedAccounts.includes("all")) {
return selectedAccounts.includes(t.accountId);
}
}
return true;
}).filter((t) => {
if (!selectedAccounts.includes("all")) {
return selectedAccounts.includes(t.accountId);
}
return true;
}).filter((t) => {
// Exclude "Virement interne" category if checkbox is checked
if (excludeInternalTransfers && internalTransferCategory) {
return t.categoryId !== internalTransferCategory.id;
}
return true;
});
}, [data, startDate, endDate, selectedAccounts, excludeInternalTransfers, internalTransferCategory]);
return true;
})
.filter((t) => {
// Exclude "Virement interne" category if checkbox is checked
if (excludeInternalTransfers && internalTransferCategory) {
return t.categoryId !== internalTransferCategory.id;
}
return true;
});
}, [
data,
startDate,
endDate,
selectedAccounts,
excludeInternalTransfers,
internalTransferCategory,
]);
const stats = useMemo(() => {
if (!data) return null;
@@ -174,8 +205,8 @@ export default function StatisticsPage() {
// Filter by accounts
if (!selectedAccounts.includes("all")) {
transactions = transactions.filter(
(t) => selectedAccounts.includes(t.accountId)
transactions = transactions.filter((t) =>
selectedAccounts.includes(t.accountId),
);
}
@@ -185,7 +216,7 @@ export default function StatisticsPage() {
transactions = transactions.filter((t) => !t.categoryId);
} else {
transactions = transactions.filter(
(t) => t.categoryId && selectedCategories.includes(t.categoryId)
(t) => t.categoryId && selectedCategories.includes(t.categoryId),
);
}
}
@@ -193,7 +224,7 @@ export default function StatisticsPage() {
// Exclude "Virement interne" category if checkbox is checked
if (excludeInternalTransfers && internalTransferCategory) {
transactions = transactions.filter(
(t) => t.categoryId !== internalTransferCategory.id
(t) => t.categoryId !== internalTransferCategory.id,
);
}
@@ -264,7 +295,9 @@ export default function StatisticsPage() {
categoryTotalsByParent.set(groupId, current + Math.abs(t.amount));
});
const categoryChartDataByParent = Array.from(categoryTotalsByParent.entries())
const categoryChartDataByParent = Array.from(
categoryTotalsByParent.entries(),
)
.map(([groupId, total]) => {
const category = data.categories.find((c) => c.id === groupId);
return {
@@ -278,7 +311,7 @@ export default function StatisticsPage() {
// Top expenses - deduplicate by ID and sort by amount (most negative first)
const uniqueTransactions = Array.from(
new Map(transactions.map((t) => [t.id, t])).values()
new Map(transactions.map((t) => [t.id, t])).values(),
);
const topExpenses = uniqueTransactions
.filter((t) => t.amount < 0)
@@ -304,7 +337,7 @@ export default function StatisticsPage() {
// Balance evolution - Aggregated (using filtered transactions)
const sortedFilteredTransactions = [...transactions].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
);
// Calculate starting balance: initialBalance + transactions before startDate
@@ -353,7 +386,7 @@ export default function StatisticsPage() {
});
const aggregatedBalanceData = Array.from(
aggregatedBalanceByDate.entries()
aggregatedBalanceByDate.entries(),
).map(([date, balance]) => ({
date: new Date(date).toLocaleDateString("fr-FR", {
day: "2-digit",
@@ -459,7 +492,7 @@ export default function StatisticsPage() {
.forEach((t) => {
const monthKey = t.date.substring(0, 7);
const catId = t.categoryId || "uncategorized";
if (!categoryTrendByMonth.has(monthKey)) {
categoryTrendByMonth.set(monthKey, new Map());
}
@@ -501,7 +534,7 @@ export default function StatisticsPage() {
// Category is a parent itself
groupId = category.id;
}
if (!categoryTrendByMonthByParent.has(monthKey)) {
categoryTrendByMonthByParent.set(monthKey, new Map());
}
@@ -581,7 +614,15 @@ export default function StatisticsPage() {
categoryTrendDataByParent,
yearOverYearData,
};
}, [data, startDate, endDate, selectedAccounts, selectedCategories, excludeInternalTransfers, internalTransferCategory]);
}, [
data,
startDate,
endDate,
selectedAccounts,
selectedCategories,
excludeInternalTransfers,
internalTransferCategory,
]);
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("fr-FR", {
@@ -646,9 +687,15 @@ export default function StatisticsPage() {
</Select>
{period === "custom" && (
<Popover open={isCustomDatePickerOpen} onOpenChange={setIsCustomDatePickerOpen}>
<Popover
open={isCustomDatePickerOpen}
onOpenChange={setIsCustomDatePickerOpen}
>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full md:w-[280px] justify-start text-left font-normal">
<Button
variant="outline"
className="w-full md:w-[280px] justify-start text-left font-normal"
>
<Calendar className="mr-2 h-4 w-4" />
{customStartDate && customEndDate ? (
<>
@@ -658,14 +705,18 @@ export default function StatisticsPage() {
) : customStartDate ? (
format(customStartDate, "PPP", { locale: fr })
) : (
<span className="text-muted-foreground">Sélectionner les dates</span>
<span className="text-muted-foreground">
Sélectionner les dates
</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<div className="p-4 space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium">Date de début</label>
<label className="text-sm font-medium">
Date de début
</label>
<CalendarComponent
mode="single"
selected={customStartDate}
@@ -684,7 +735,11 @@ export default function StatisticsPage() {
mode="single"
selected={customEndDate}
onSelect={(date) => {
if (date && customStartDate && date < customStartDate) {
if (
date &&
customStartDate &&
date < customStartDate
) {
return;
}
setCustomEndDate(date);
@@ -731,7 +786,9 @@ export default function StatisticsPage() {
<Checkbox
id="exclude-internal-transfers"
checked={excludeInternalTransfers}
onCheckedChange={(checked) => setExcludeInternalTransfers(checked === true)}
onCheckedChange={(checked) =>
setExcludeInternalTransfers(checked === true)
}
/>
<label
htmlFor="exclude-internal-transfers"
@@ -747,13 +804,17 @@ export default function StatisticsPage() {
selectedAccounts={selectedAccounts}
onRemoveAccount={(id) => {
const newAccounts = selectedAccounts.filter((a) => a !== id);
setSelectedAccounts(newAccounts.length > 0 ? newAccounts : ["all"]);
setSelectedAccounts(
newAccounts.length > 0 ? newAccounts : ["all"],
);
}}
onClearAccounts={() => setSelectedAccounts(["all"])}
selectedCategories={selectedCategories}
onRemoveCategory={(id) => {
const newCategories = selectedCategories.filter((c) => c !== id);
setSelectedCategories(newCategories.length > 0 ? newCategories : ["all"]);
setSelectedCategories(
newCategories.length > 0 ? newCategories : ["all"],
);
}}
onClearCategories={() => setSelectedCategories(["all"])}
period={period}
@@ -772,7 +833,9 @@ export default function StatisticsPage() {
{/* Vue d'ensemble */}
<section className="mb-4 md:mb-8">
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">Vue d'ensemble</h2>
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">
Vue d'ensemble
</h2>
<StatsSummaryCards
totalIncome={stats.totalIncome}
totalExpenses={stats.totalExpenses}
@@ -797,7 +860,9 @@ export default function StatisticsPage() {
{/* Revenus et Dépenses */}
<section className="mb-4 md:mb-8">
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">Revenus et Dépenses</h2>
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">
Revenus et Dépenses
</h2>
<div className="grid gap-4 md:gap-6 lg:grid-cols-2">
<MonthlyChart
data={stats.monthlyChartData}
@@ -824,7 +889,9 @@ export default function StatisticsPage() {
{/* Analyse par Catégorie */}
<section className="mb-4 md:mb-8">
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">Analyse par Catégorie</h2>
<h2 className="text-lg md:text-2xl font-semibold mb-3 md:mb-4">
Analyse par Catégorie
</h2>
<div className="grid gap-4 md:gap-6">
<CategoryPieChart
data={stats.categoryChartData}
@@ -853,7 +920,6 @@ export default function StatisticsPage() {
/>
</div>
</section>
</PageLayout>
);
}
@@ -895,7 +961,9 @@ function ActiveFilters({
if (!hasActiveFilters) return null;
const selectedAccs = accounts.filter((a) => selectedAccounts.includes(a.id));
const selectedCats = categories.filter((c) => selectedCategories.includes(c.id));
const selectedCats = categories.filter((c) =>
selectedCategories.includes(c.id),
);
const isUncategorized = selectedCategories.includes("uncategorized");
const getPeriodLabel = (p: Period) => {
@@ -929,7 +997,11 @@ function ActiveFilters({
<Filter className="h-3 w-3 md:h-3.5 md:w-3.5 text-muted-foreground" />
{selectedAccs.map((acc) => (
<Badge key={acc.id} variant="secondary" className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal">
<Badge
key={acc.id}
variant="secondary"
className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal"
>
<Wallet className="h-2.5 w-2.5 md:h-3 md:w-3" />
{acc.name}
<button
@@ -942,10 +1014,16 @@ function ActiveFilters({
))}
{isUncategorized && (
<Badge variant="secondary" className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal">
<Badge
variant="secondary"
className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal"
>
<CircleSlash className="h-2.5 w-2.5 md:h-3 md:w-3" />
Non catégorisé
<button onClick={onClearCategories} className="ml-0.5 md:ml-1 hover:text-foreground">
<button
onClick={onClearCategories}
className="ml-0.5 md:ml-1 hover:text-foreground"
>
<X className="h-2.5 w-2.5 md:h-3 md:w-3" />
</button>
</Badge>
@@ -961,7 +1039,11 @@ function ActiveFilters({
borderColor: `${cat.color}30`,
}}
>
<CategoryIcon icon={cat.icon} color={cat.color} size={isMobile ? 10 : 12} />
<CategoryIcon
icon={cat.icon}
color={cat.color}
size={isMobile ? 10 : 12}
/>
{cat.name}
<button
onClick={() => onRemoveCategory(cat.id)}
@@ -973,10 +1055,16 @@ function ActiveFilters({
))}
{hasPeriod && (
<Badge variant="secondary" className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal">
<Badge
variant="secondary"
className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal"
>
<Calendar className="h-2.5 w-2.5 md:h-3 md:w-3" />
{getPeriodLabel(period)}
<button onClick={onClearPeriod} className="ml-0.5 md:ml-1 hover:text-foreground">
<button
onClick={onClearPeriod}
className="ml-0.5 md:ml-1 hover:text-foreground"
>
<X className="h-2.5 w-2.5 md:h-3 md:w-3" />
</button>
</Badge>