chore: clean up code by removing trailing whitespace and ensuring consistent formatting across various files = prettier
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user