feat: enhance UI with new background gradients and responsive design adjustments across various components

This commit is contained in:
Julien Froidefond
2025-12-07 17:23:53 +01:00
parent b704cc5a84
commit d4db94d156
10 changed files with 399 additions and 191 deletions

View File

@@ -30,10 +30,26 @@ import {
} from "@/lib/store-db";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Building2, Folder, Plus, List, LayoutGrid } from "lucide-react";
import {
Building2,
Folder,
Plus,
List,
LayoutGrid,
MoreVertical,
Pencil,
Trash2,
} from "lucide-react";
import type { Account, Folder as FolderType } from "@/lib/types";
import { cn } from "@/lib/utils";
import { getAccountBalance } from "@/lib/account-utils";
import { useIsMobile } from "@/hooks/use-mobile";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// Composant wrapper pour les zones de drop des dossiers
function FolderDropZone({
@@ -61,6 +77,7 @@ function FolderDropZone({
export default function AccountsPage() {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const { data: metadata, isLoading: isLoadingMetadata } = useBankingMetadata();
const { data: accountsWithStats, isLoading: isLoadingAccounts } =
useAccountsWithStats();
@@ -376,12 +393,12 @@ export default function AccountsPage() {
<PageLayout>
<PageHeader
title="Comptes"
description="Gérez vos comptes bancaires et leur organisation"
description={isMobile ? undefined : "Gérez vos comptes bancaires et leur organisation"}
actions={
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
size={isMobile ? "icon" : "default"}
onClick={() => setIsCompactView(!isCompactView)}
title={isCompactView ? "Vue détaillée" : "Vue compacte"}
>
@@ -390,22 +407,31 @@ export default function AccountsPage() {
) : (
<List className="w-4 h-4" />
)}
{!isMobile && (
<span className="ml-2">
{isCompactView ? "Détaillée" : "Compacte"}
</span>
)}
</Button>
<Button onClick={handleNewFolder}>
<Plus className="w-4 h-4 mr-2" />
Nouveau dossier
<Button onClick={handleNewFolder} size={isMobile ? "icon" : "default"}>
<Plus className="w-4 h-4" />
{!isMobile && <span className="ml-2">Nouveau dossier</span>}
</Button>
</div>
}
rightContent={
<div className="text-right">
<p className="text-sm text-muted-foreground">Solde total</p>
<div className={cn("text-right", isMobile && "text-left")}>
{!isMobile && (
<p className="text-sm text-muted-foreground">Solde total</p>
)}
<p
className={cn(
"text-2xl font-bold",
isMobile ? "text-xl" : "text-2xl",
"font-bold",
totalBalance >= 0 ? "text-emerald-600" : "text-red-600",
)}
>
{isMobile && <span className="text-xs text-muted-foreground mr-2">Total:</span>}
{formatCurrency(totalBalance)}
</p>
</div>
@@ -440,32 +466,36 @@ export default function AccountsPage() {
{accountsByFolder["no-folder"] &&
accountsByFolder["no-folder"].length > 0 && (
<FolderDropZone folderId="root">
<div className="flex items-center gap-2 mb-4">
<Folder className="w-5 h-5 text-muted-foreground" />
<h2 className="text-lg font-semibold">Sans dossier</h2>
<span className="text-sm text-muted-foreground">
({accountsByFolder["no-folder"].length})
</span>
<span
className={cn(
"text-sm font-semibold tabular-nums ml-auto",
accountsByFolder["no-folder"].reduce(
(sum, a) => sum + getAccountBalance(a),
0,
) >= 0
? "text-emerald-600"
: "text-red-600",
)}
>
{formatCurrency(
accountsByFolder["no-folder"].reduce(
(sum, a) => sum + getAccountBalance(a),
0,
),
)}
</span>
<div className="flex items-center gap-2 mb-3 sm:mb-4">
<Folder className="w-5 h-5 text-muted-foreground shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h2 className="text-base sm:text-lg font-semibold">Sans dossier</h2>
<span className="text-xs sm:text-sm text-muted-foreground shrink-0">
({accountsByFolder["no-folder"].length})
</span>
<span
className={cn(
"text-xs sm:text-sm font-semibold tabular-nums shrink-0",
accountsByFolder["no-folder"].reduce(
(sum, a) => sum + getAccountBalance(a),
0,
) >= 0
? "text-emerald-600"
: "text-red-600",
)}
>
{formatCurrency(
accountsByFolder["no-folder"].reduce(
(sum, a) => sum + getAccountBalance(a),
0,
),
)}
</span>
</div>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<div className="grid gap-2 sm:gap-3 md:gap-4 grid-cols-2 sm:grid-cols-2 lg:grid-cols-3">
{accountsByFolder["no-folder"].map((account) => {
const folder = metadata.folders.find(
(f: FolderType) => f.id === account.folderId,
@@ -501,9 +531,9 @@ export default function AccountsPage() {
return (
<FolderDropZone key={folder.id} folderId={folder.id}>
<div className="flex items-center gap-2 mb-4">
<div className="flex items-center gap-2 mb-3 sm:mb-4">
<div
className="w-5 h-5 rounded flex items-center justify-center"
className="w-5 h-5 rounded flex items-center justify-center shrink-0"
style={{ backgroundColor: `${folder.color}20` }}
>
<Folder
@@ -511,41 +541,72 @@ export default function AccountsPage() {
style={{ color: folder.color }}
/>
</div>
<h2 className="text-lg font-semibold">{folder.name}</h2>
<span className="text-sm text-muted-foreground">
({folderAccounts.length})
</span>
{folderAccounts.length > 0 && (
<span
className={cn(
"text-sm font-semibold tabular-nums",
folderBalance >= 0
? "text-emerald-600"
: "text-red-600",
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h2 className="text-base sm:text-lg font-semibold truncate">
{folder.name}
</h2>
<span className="text-xs sm:text-sm text-muted-foreground shrink-0">
({folderAccounts.length})
</span>
{folderAccounts.length > 0 && (
<span
className={cn(
"text-xs sm:text-sm font-semibold tabular-nums shrink-0",
folderBalance >= 0
? "text-emerald-600"
: "text-red-600",
)}
>
{formatCurrency(folderBalance)}
</span>
)}
>
{formatCurrency(folderBalance)}
</span>
</div>
</div>
{isMobile ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 shrink-0">
<MoreVertical className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleEditFolder(folder)}>
<Pencil className="w-4 h-4 mr-2" />
Modifier
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDeleteFolder(folder.id)}
variant="destructive"
>
<Trash2 className="w-4 h-4 mr-2" />
Supprimer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<>
<Button
variant="ghost"
size="sm"
onClick={() => handleEditFolder(folder)}
className="ml-auto shrink-0"
>
Modifier
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteFolder(folder.id)}
className="text-destructive hover:text-destructive shrink-0"
>
Supprimer
</Button>
</>
)}
<Button
variant="ghost"
size="sm"
onClick={() => handleEditFolder(folder)}
className="ml-auto"
>
Modifier
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteFolder(folder.id)}
className="text-destructive hover:text-destructive"
>
Supprimer
</Button>
</div>
{folderAccounts.length > 0 ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<div className="grid gap-2 sm:gap-3 md:gap-4 grid-cols-2 sm:grid-cols-2 lg:grid-cols-3">
{folderAccounts.map((account) => {
const accountFolder = metadata.folders.find(
(f: FolderType) => f.id === account.folderId,

View File

@@ -122,4 +122,80 @@
body {
@apply bg-background text-foreground;
}
/* Background avec beau dégradé */
.page-background {
position: relative;
background: var(--background);
}
.page-background::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
background:
radial-gradient(ellipse 80% 50% at 50% -20%,
color-mix(in srgb, var(--chart-1) 25%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 60% 80% at -10% 50%,
color-mix(in srgb, var(--chart-2) 20%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 60% 80% at 110% 50%,
color-mix(in srgb, var(--chart-3) 20%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 80% 50% at 50% 120%,
color-mix(in srgb, var(--chart-4) 25%, transparent) 0%,
transparent 50%
),
linear-gradient(
135deg,
color-mix(in srgb, var(--chart-1) 8%, transparent) 0%,
transparent 25%,
transparent 75%,
color-mix(in srgb, var(--chart-2) 8%, transparent) 100%
);
background-size: 100% 100%;
pointer-events: none;
opacity: 1;
}
.dark .page-background::before {
background:
radial-gradient(ellipse 80% 50% at 50% -20%,
color-mix(in srgb, var(--chart-1) 35%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 60% 80% at -10% 50%,
color-mix(in srgb, var(--chart-2) 30%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 60% 80% at 110% 50%,
color-mix(in srgb, var(--chart-3) 30%, transparent) 0%,
transparent 50%
),
radial-gradient(ellipse 80% 50% at 50% 120%,
color-mix(in srgb, var(--chart-4) 35%, transparent) 0%,
transparent 50%
),
linear-gradient(
135deg,
color-mix(in srgb, var(--chart-1) 12%, transparent) 0%,
transparent 25%,
transparent 75%,
color-mix(in srgb, var(--chart-2) 12%, transparent) 100%
);
opacity: 1;
}
.page-content {
position: relative;
z-index: 1;
}
}

View File

@@ -47,8 +47,8 @@ export default function LoginPage() {
};
return (
<div className="min-h-screen flex items-center justify-center bg-[var(--background)] p-4">
<Card className="w-full max-w-md">
<div className="min-h-screen flex items-center justify-center bg-[var(--background)] p-4 page-background">
<Card className="w-full max-w-md page-content">
<CardHeader className="space-y-1">
<div className="flex items-center justify-center mb-4">
<Lock className="w-12 h-12 text-[var(--primary)]" />

View File

@@ -27,11 +27,11 @@ export default function DashboardPage() {
}
const filteredAccounts = data.accounts.filter((a) =>
selectedAccounts.includes(a.id),
selectedAccounts.includes(a.id)
);
const filteredAccountIds = new Set(filteredAccounts.map((a) => a.id));
const filteredTransactions = data.transactions.filter((t) =>
filteredAccountIds.has(t.accountId),
filteredAccountIds.has(t.accountId)
);
return {
@@ -60,26 +60,24 @@ export default function DashboardPage() {
}
/>
<Card className="mb-6">
<CardContent className="pt-4">
<div className="flex flex-wrap gap-4">
<AccountFilterCombobox
accounts={data.accounts}
folders={data.folders}
value={selectedAccounts}
onChange={setSelectedAccounts}
className="w-full md:w-[280px]"
filteredTransactions={data.transactions}
/>
</div>
<Card className="mb-4 sm:mb-6 border shadow-sm">
<CardContent className="px-4 py-4 sm:px-6 sm:py-6">
<AccountFilterCombobox
accounts={data.accounts}
folders={data.folders}
value={selectedAccounts}
onChange={setSelectedAccounts}
className="w-full md:w-[280px]"
filteredTransactions={data.transactions}
/>
</CardContent>
</Card>
<OverviewCards data={filteredData} />
<div className="grid gap-6 lg:grid-cols-2">
<div className="grid gap-4 sm:gap-6 lg:grid-cols-2">
<RecentTransactions data={filteredData} />
<div className="space-y-6">
<div className="space-y-4 sm:space-y-6">
<AccountsSummary data={filteredData} />
<CategoryBreakdown data={filteredData} />
</div>