feat: enhance responsive design and layout consistency across various components, including dashboard, statistics, and rules pages

This commit is contained in:
Julien Froidefond
2025-12-01 08:34:28 +01:00
parent 86236aeb04
commit b3b25412ad
19 changed files with 731 additions and 349 deletions

View File

@@ -68,7 +68,7 @@ export default function DashboardPage() {
folders={data.folders}
value={selectedAccounts}
onChange={setSelectedAccounts}
className="w-[280px]"
className="w-full md:w-[280px]"
filteredTransactions={data.transactions}
/>
</div>

View File

@@ -238,10 +238,14 @@ export default function RulesPage() {
<PageHeader
title="Règles de catégorisation"
description={
<span className="flex items-center gap-2">
{transactionGroups.length} groupe
{transactionGroups.length > 1 ? "s" : ""} de transactions similaires
<Badge variant="secondary">{uncategorizedCount} non catégorisées</Badge>
<span className="flex items-center gap-2 flex-wrap">
<span className="text-xs md:text-base">
{transactionGroups.length} groupe
{transactionGroups.length > 1 ? "s" : ""} de transactions similaires
</span>
<Badge variant="secondary" className="text-[10px] md:text-xs">
{uncategorizedCount} non catégorisées
</Badge>
</span>
}
actions={
@@ -272,14 +276,14 @@ export default function RulesPage() {
/>
{transactionGroups.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-center">
<Sparkles className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-medium text-foreground mb-2">
<div className="flex flex-col items-center justify-center py-12 md:py-16 text-center px-4">
<Sparkles className="h-8 w-8 md:h-12 md:w-12 text-muted-foreground mb-3 md:mb-4" />
<h3 className="text-base md:text-lg font-medium text-foreground mb-2">
{uncategorizedCount === 0
? "Toutes les transactions sont catégorisées !"
: "Aucun groupe trouvé"}
</h3>
<p className="text-sm text-muted-foreground max-w-md">
<p className="text-xs md:text-sm text-muted-foreground max-w-md">
{uncategorizedCount === 0
? "Continuez à importer des transactions pour voir les suggestions de règles."
: filterMinCount > 1
@@ -288,7 +292,7 @@ export default function RulesPage() {
</p>
</div>
) : (
<div className="space-y-3">
<div className="space-y-2 md:space-y-3">
{transactionGroups.map((group) => (
<RuleGroupCard
key={group.key}

View File

@@ -35,6 +35,7 @@ import { Calendar as CalendarComponent } from "@/components/ui/calendar";
import { Button } from "@/components/ui/button";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { useIsMobile } from "@/hooks/use-mobile";
import type { Account, Category } from "@/lib/types";
type Period = "1month" | "3months" | "6months" | "12months" | "custom" | "all";
@@ -601,15 +602,15 @@ export default function StatisticsPage() {
description="Analysez vos dépenses et revenus"
/>
<Card className="mb-6">
<CardContent className="pt-4">
<div className="flex flex-wrap gap-4">
<Card className="mb-4 md:mb-6">
<CardContent className="pt-3 md:pt-4">
<div className="flex flex-wrap gap-2 md:gap-4">
<AccountFilterCombobox
accounts={data.accounts}
folders={data.folders}
value={selectedAccounts}
onChange={setSelectedAccounts}
className="w-[280px]"
className="w-full md:w-[280px]"
filteredTransactions={transactionsForAccountFilter}
/>
@@ -617,7 +618,7 @@ export default function StatisticsPage() {
categories={data.categories}
value={selectedCategories}
onChange={setSelectedCategories}
className="w-[220px]"
className="w-full md:w-[220px]"
filteredTransactions={transactionsForCategoryFilter}
/>
@@ -632,7 +633,7 @@ export default function StatisticsPage() {
}
}}
>
<SelectTrigger className="w-[150px]">
<SelectTrigger className="w-full md:w-[150px]">
<SelectValue placeholder="Période" />
</SelectTrigger>
<SelectContent>
@@ -648,7 +649,7 @@ export default function StatisticsPage() {
{period === "custom" && (
<Popover open={isCustomDatePickerOpen} onOpenChange={setIsCustomDatePickerOpen}>
<PopoverTrigger asChild>
<Button variant="outline" className="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 ? (
<>
@@ -727,7 +728,7 @@ export default function StatisticsPage() {
)}
{internalTransferCategory && (
<div className="flex items-center gap-2 px-3 py-2 border border-border rounded-md bg-[var(--card)]">
<div className="flex items-center gap-2 px-2 md:px-3 py-1.5 md:py-2 border border-border rounded-md bg-[var(--card)]">
<Checkbox
id="exclude-internal-transfers"
checked={excludeInternalTransfers}
@@ -735,7 +736,7 @@ export default function StatisticsPage() {
/>
<label
htmlFor="exclude-internal-transfers"
className="text-sm font-medium cursor-pointer select-none"
className="text-xs md:text-sm font-medium cursor-pointer select-none"
>
Exclure Virement interne
</label>
@@ -771,15 +772,15 @@ export default function StatisticsPage() {
</Card>
{/* Vue d'ensemble */}
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">Vue d'ensemble</h2>
<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>
<StatsSummaryCards
totalIncome={stats.totalIncome}
totalExpenses={stats.totalExpenses}
avgMonthlyExpenses={stats.avgMonthlyExpenses}
formatCurrency={formatCurrency}
/>
<div className="mt-6">
<div className="mt-4 md:mt-6">
<BalanceLineChart
aggregatedData={stats.aggregatedBalanceData}
perAccountData={stats.perAccountBalanceData}
@@ -787,7 +788,7 @@ export default function StatisticsPage() {
formatCurrency={formatCurrency}
/>
</div>
<div className="mt-6">
<div className="mt-4 md:mt-6">
<SavingsTrendChart
data={stats.savingsTrendData}
formatCurrency={formatCurrency}
@@ -796,9 +797,9 @@ export default function StatisticsPage() {
</section>
{/* Revenus et Dépenses */}
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">Revenus et Dépenses</h2>
<div className="grid gap-6 lg:grid-cols-2">
<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>
<div className="grid gap-4 md:gap-6 lg:grid-cols-2">
<MonthlyChart
data={stats.monthlyChartData}
formatCurrency={formatCurrency}
@@ -813,7 +814,7 @@ export default function StatisticsPage() {
/>
</div>
{stats.yearOverYearData.length > 0 && (
<div className="mt-6">
<div className="mt-4 md:mt-6">
<YearOverYearChart
data={stats.yearOverYearData}
formatCurrency={formatCurrency}
@@ -823,9 +824,9 @@ export default function StatisticsPage() {
</section>
{/* Analyse par Catégorie */}
<section className="mb-8">
<h2 className="text-2xl font-semibold mb-4">Analyse par Catégorie</h2>
<div className="grid gap-6">
<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>
<div className="grid gap-4 md:gap-6">
<CategoryPieChart
data={stats.categoryChartData}
dataByParent={stats.categoryChartDataByParent}
@@ -837,7 +838,7 @@ export default function StatisticsPage() {
formatCurrency={formatCurrency}
/>
</div>
<div className="mt-6">
<div className="hidden md:block mt-4 md:mt-6">
<CategoryTrendChart
data={stats.categoryTrendData}
dataByParent={stats.categoryTrendDataByParent}
@@ -845,7 +846,7 @@ export default function StatisticsPage() {
formatCurrency={formatCurrency}
/>
</div>
<div className="mt-6">
<div className="mt-4 md:mt-6">
<TopExpensesList
expenses={stats.topExpenses}
categories={data.categories}
@@ -885,6 +886,7 @@ function ActiveFilters({
customStartDate?: Date;
customEndDate?: Date;
}) {
const isMobile = useIsMobile();
const hasAccounts = !selectedAccounts.includes("all");
const hasCategories = !selectedCategories.includes("all");
const hasPeriod = period !== "all";
@@ -924,28 +926,28 @@ function ActiveFilters({
};
return (
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-border flex-wrap">
<Filter className="h-3.5 w-3.5 text-muted-foreground" />
<div className="flex items-center gap-1.5 md:gap-2 mt-2 md:mt-3 pt-2 md:pt-3 border-t border-border flex-wrap">
<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-1 text-xs font-normal">
<Wallet className="h-3 w-3" />
<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
onClick={() => onRemoveAccount(acc.id)}
className="ml-1 hover:text-foreground"
className="ml-0.5 md:ml-1 hover:text-foreground"
>
<X className="h-3 w-3" />
<X className="h-2.5 w-2.5 md:h-3 md:w-3" />
</button>
</Badge>
))}
{isUncategorized && (
<Badge variant="secondary" className="gap-1 text-xs font-normal">
<CircleSlash className="h-3 w-3" />
<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-1 hover:text-foreground">
<X className="h-3 w-3" />
<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>
)}
@@ -954,36 +956,36 @@ function ActiveFilters({
<Badge
key={cat.id}
variant="secondary"
className="gap-1 text-xs font-normal"
className="gap-0.5 md:gap-1 text-[10px] md:text-xs font-normal"
style={{
backgroundColor: `${cat.color}15`,
borderColor: `${cat.color}30`,
}}
>
<CategoryIcon icon={cat.icon} color={cat.color} size={12} />
<CategoryIcon icon={cat.icon} color={cat.color} size={isMobile ? 10 : 12} />
{cat.name}
<button
onClick={() => onRemoveCategory(cat.id)}
className="ml-1 hover:text-foreground"
className="ml-0.5 md:ml-1 hover:text-foreground"
>
<X className="h-3 w-3" />
<X className="h-2.5 w-2.5 md:h-3 md:w-3" />
</button>
</Badge>
))}
{hasPeriod && (
<Badge variant="secondary" className="gap-1 text-xs font-normal">
<Calendar className="h-3 w-3" />
<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-1 hover:text-foreground">
<X className="h-3 w-3" />
<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>
)}
<button
onClick={clearAll}
className="text-xs text-muted-foreground hover:text-foreground ml-auto"
className="text-[10px] md:text-xs text-muted-foreground hover:text-foreground ml-auto"
>
Effacer tout
</button>