diff --git a/.dockerignore b/.dockerignore index 389ba10..566633b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,3 +19,4 @@ README.md + diff --git a/app/accounts/page.tsx b/app/accounts/page.tsx index e06ac77..3bec9ce 100644 --- a/app/accounts/page.tsx +++ b/app/accounts/page.tsx @@ -393,7 +393,11 @@ export default function AccountsPage() { - @@ -431,7 +438,11 @@ export default function AccountsPage() { totalBalance >= 0 ? "text-emerald-600" : "text-red-600", )} > - {isMobile && Total:} + {isMobile && ( + + Total: + + )} {formatCurrency(totalBalance)}

@@ -470,7 +481,9 @@ export default function AccountsPage() {
-

Sans dossier

+

+ Sans dossier +

({accountsByFolder["no-folder"].length}) @@ -566,12 +579,18 @@ export default function AccountsPage() { {isMobile ? ( - - handleEditFolder(folder)}> + handleEditFolder(folder)} + > Modifier diff --git a/app/globals.css b/app/globals.css index 24fc8e0..c050aaf 100644 --- a/app/globals.css +++ b/app/globals.css @@ -7,58 +7,63 @@ /* Background fintech ultra-moderne avec dégradé vibrant */ --background: oklch(0.98 0.008 280); --foreground: oklch(0.1 0.015 280); - + /* Cards avec glassmorphism très prononcé */ --card: oklch(1 0 0 / 0.6); --card-foreground: oklch(0.1 0.015 280); --card-hover: oklch(1 0 0 / 0.75); - + /* Popover avec backdrop blur très fort */ --popover: oklch(1 0 0 / 0.95); --popover-foreground: oklch(0.1 0.015 280); - + /* Primary: Bleu/violet fintech très vibrant avec gradient */ --primary: oklch(0.6 0.25 270); --primary-foreground: oklch(0.99 0 0); - --primary-gradient: linear-gradient(135deg, oklch(0.6 0.25 270) 0%, oklch(0.65 0.22 300) 50%, oklch(0.62 0.24 250) 100%); + --primary-gradient: linear-gradient( + 135deg, + oklch(0.6 0.25 270) 0%, + oklch(0.65 0.22 300) 50%, + oklch(0.62 0.24 250) 100% + ); --primary-light: oklch(0.7 0.2 270); - + /* Secondary avec teinte subtile */ --secondary: oklch(0.97 0.008 280); --secondary-foreground: oklch(0.25 0.015 280); - + /* Muted plus doux */ --muted: oklch(0.96 0.006 280); --muted-foreground: oklch(0.5 0.012 280); - + /* Accent avec couleur très vibrante */ --accent: oklch(0.95 0.015 260); --accent-foreground: oklch(0.6 0.25 270); - + /* Destructive moderne */ --destructive: oklch(0.6 0.26 25); --destructive-foreground: oklch(0.99 0 0); - + /* Bordures fines et subtiles */ --border: oklch(0.9 0.008 280 / 0.4); --input: oklch(0.97 0.006 280); --ring: oklch(0.6 0.25 270 / 0.5); - + /* Chart colors très vibrantes */ --chart-1: oklch(0.65 0.25 270); --chart-2: oklch(0.7 0.22 180); --chart-3: oklch(0.75 0.2 120); --chart-4: oklch(0.7 0.25 320); --chart-5: oklch(0.65 0.22 40); - + /* Success et Warning pour fintech */ --success: oklch(0.68 0.2 150); --success-foreground: oklch(0.99 0 0); --warning: oklch(0.78 0.2 80); --warning-foreground: oklch(0.1 0.015 280); - + --radius: 1.25rem; - + /* Sidebar moderne avec glassmorphism très prononcé */ --sidebar: oklch(1 0 0 / 0.5); --sidebar-foreground: oklch(0.2 0.015 280); @@ -74,56 +79,61 @@ /* Background sombre fintech avec dégradé vibrant */ --background: oklch(0.1 0.015 280); --foreground: oklch(0.97 0.005 280); - + /* Cards avec effet glassmorphism sombre très prononcé */ --card: oklch(0.15 0.015 280 / 0.6); --card-foreground: oklch(0.97 0.005 280); --card-hover: oklch(0.18 0.015 280 / 0.75); - + /* Popover avec backdrop blur très fort */ --popover: oklch(0.15 0.015 280 / 0.95); --popover-foreground: oklch(0.97 0.005 280); - + /* Primary: Bleu/violet très brillant pour dark mode */ --primary: oklch(0.72 0.28 270); --primary-foreground: oklch(0.1 0.015 280); - --primary-gradient: linear-gradient(135deg, oklch(0.72 0.28 270) 0%, oklch(0.75 0.25 300) 50%, oklch(0.73 0.27 250) 100%); + --primary-gradient: linear-gradient( + 135deg, + oklch(0.72 0.28 270) 0%, + oklch(0.75 0.25 300) 50%, + oklch(0.73 0.27 250) 100% + ); --primary-light: oklch(0.78 0.23 270); - + /* Secondary avec plus de contraste */ --secondary: oklch(0.22 0.015 280); --secondary-foreground: oklch(0.95 0.005 280); - + /* Muted plus visible */ --muted: oklch(0.22 0.015 280); --muted-foreground: oklch(0.7 0.012 280); - + /* Accent très vibrant */ --accent: oklch(0.26 0.02 260); --accent-foreground: oklch(0.72 0.28 270); - + /* Destructive moderne */ --destructive: oklch(0.68 0.27 25); --destructive-foreground: oklch(0.97 0 0); - + /* Bordures subtiles */ --border: oklch(0.25 0.015 280 / 0.4); --input: oklch(0.22 0.015 280); --ring: oklch(0.72 0.28 270 / 0.6); - + /* Chart colors très vibrantes pour dark */ --chart-1: oklch(0.75 0.27 270); --chart-2: oklch(0.8 0.25 180); --chart-3: oklch(0.85 0.23 120); --chart-4: oklch(0.8 0.27 320); --chart-5: oklch(0.75 0.25 40); - + /* Success et Warning pour fintech dark */ --success: oklch(0.73 0.22 150); --success-foreground: oklch(0.1 0.015 280); --warning: oklch(0.8 0.22 80); --warning-foreground: oklch(0.1 0.015 280); - + /* Sidebar dark moderne avec glassmorphism très prononcé */ --sidebar: oklch(0.12 0.015 280 / 0.5); --sidebar-foreground: oklch(0.95 0.005 280); @@ -182,21 +192,28 @@ } body { @apply bg-background text-foreground; - font-feature-settings: "rlig" 1, "calt" 1; + font-feature-settings: + "rlig" 1, + "calt" 1; letter-spacing: -0.01em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - - h1, h2, h3, h4, h5, h6 { + + h1, + h2, + h3, + h4, + h5, + h6 { letter-spacing: -0.02em; font-weight: 700; } - + p { letter-spacing: -0.005em; } - + /* Animations fintech modernes et fluides */ @keyframes fade-in { from { @@ -208,7 +225,7 @@ transform: translateY(0); } } - + @keyframes slide-in { from { opacity: 0; @@ -219,7 +236,7 @@ transform: translateX(0); } } - + @keyframes scale-in { from { opacity: 0; @@ -230,7 +247,7 @@ transform: scale(1); } } - + @keyframes shimmer { 0% { background-position: -1000px 0; @@ -239,28 +256,31 @@ background-position: 1000px 0; } } - + @keyframes gradient-shift { - 0%, 100% { + 0%, + 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } } - + /* Smooth scrolling */ html { scroll-behavior: smooth; } - + /* Amélioration des transitions globales avec easing fintech */ * { - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter; + transition-property: + color, background-color, border-color, text-decoration-color, fill, + stroke, opacity, box-shadow, transform, filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 200ms; } - + /* Désactiver COMPLÈTEMENT toutes les animations pour les popovers et selects - APPROCHE AGRESSIVE */ [data-slot="popover-content"], [data-slot="select-content"], @@ -277,7 +297,7 @@ opacity: 1 !important; will-change: auto !important; } - + /* Désactiver TOUTES les classes Tailwind d'animation - sélecteurs ultra-spécifiques */ [data-slot="popover-content"].animate-in, [data-slot="select-content"].animate-in, @@ -303,7 +323,7 @@ opacity: 1 !important; will-change: auto !important; } - + /* Override pour tous les états data-state */ [data-slot="popover-content"][data-state="open"], [data-slot="popover-content"][data-state="closed"], @@ -316,18 +336,18 @@ /* Ne pas toucher au transform car il est utilisé pour le positionnement */ opacity: 1 !important; } - + /* Glassmorphism effect très prononcé */ .glass { background: var(--card); backdrop-filter: blur(40px) saturate(200%); -webkit-backdrop-filter: blur(40px) saturate(200%); border: 1px solid var(--border); - box-shadow: + box-shadow: 0 8px 32px -8px color-mix(in srgb, var(--primary) 10%, transparent), inset 0 1px 0 0 color-mix(in srgb, white 20%, transparent); } - + /* Gradient text effect */ .gradient-text { background: var(--primary-gradient); @@ -336,53 +356,57 @@ background-clip: text; font-weight: 700; } - + /* Card hover effect avec élévation */ .card-hover { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); position: relative; } - + .card-hover::before { content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, color-mix(in srgb, var(--primary) 30%, transparent) 0%, color-mix(in srgb, var(--chart-2) 20%, transparent) 100% ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask: + linear-gradient(#fff 0 0) content-box, + linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.4s ease; } - + .card-hover:hover { transform: translateY(-4px) scale(1.01); - box-shadow: + box-shadow: 0 20px 40px -12px color-mix(in srgb, var(--primary) 20%, transparent), 0 8px 16px -8px color-mix(in srgb, var(--foreground) 10%, transparent), inset 0 1px 0 0 color-mix(in srgb, white 30%, transparent); } - + .card-hover:hover::before { opacity: 1; } - + /* Background fintech moderne avec dégradés animés et formes abstraites */ .page-background { position: relative; - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, oklch(0.98 0.01 280) 0%, oklch(0.97 0.012 270) 50%, oklch(0.98 0.01 290) 100% ); overflow: hidden; } - + .page-background::before { content: ""; position: fixed; @@ -393,23 +417,28 @@ z-index: 0; background: /* Formes abstraites circulaires */ - radial-gradient(circle 800px at 10% 20%, + radial-gradient( + circle 800px at 10% 20%, color-mix(in srgb, var(--primary) 15%, transparent) 0%, transparent 50% ), - radial-gradient(circle 600px at 90% 80%, + radial-gradient( + circle 600px at 90% 80%, color-mix(in srgb, var(--chart-2) 12%, transparent) 0%, transparent 50% ), - radial-gradient(circle 500px at 50% 50%, + radial-gradient( + circle 500px at 50% 50%, color-mix(in srgb, var(--chart-3) 10%, transparent) 0%, transparent 60% ), - radial-gradient(circle 400px at 20% 70%, + radial-gradient( + circle 400px at 20% 70%, color-mix(in srgb, var(--chart-4) 8%, transparent) 0%, transparent 65% ), - radial-gradient(circle 300px at 80% 30%, + radial-gradient( + circle 300px at 80% 30%, color-mix(in srgb, var(--primary-light) 6%, transparent) 0%, transparent 70% ); @@ -419,45 +448,51 @@ transition: opacity 0.5s ease; animation: gradient-shift 25s ease infinite; } - + .dark .page-background { - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, oklch(0.1 0.015 280) 0%, oklch(0.11 0.018 270) 50%, oklch(0.1 0.015 290) 100% ); } - + .dark .page-background::before { - background: - radial-gradient(circle 800px at 10% 20%, + background: + radial-gradient( + circle 800px at 10% 20%, color-mix(in srgb, var(--primary) 22%, transparent) 0%, transparent 50% ), - radial-gradient(circle 600px at 90% 80%, + radial-gradient( + circle 600px at 90% 80%, color-mix(in srgb, var(--chart-2) 18%, transparent) 0%, transparent 50% ), - radial-gradient(circle 500px at 50% 50%, + radial-gradient( + circle 500px at 50% 50%, color-mix(in srgb, var(--chart-3) 15%, transparent) 0%, transparent 60% ), - radial-gradient(circle 400px at 20% 70%, + radial-gradient( + circle 400px at 20% 70%, color-mix(in srgb, var(--chart-4) 12%, transparent) 0%, transparent 65% ), - radial-gradient(circle 300px at 80% 30%, + radial-gradient( + circle 300px at 80% 30%, color-mix(in srgb, var(--primary-light) 10%, transparent) 0%, transparent 70% ); opacity: 1; } - + .page-content { position: relative; z-index: 1; } - + /* Fintech card styles avec glassmorphism très prononcé */ .fintech-card { background: var(--card); @@ -465,7 +500,7 @@ -webkit-backdrop-filter: blur(50px) saturate(200%); border: 1px solid var(--border); border-radius: var(--radius); - box-shadow: + box-shadow: 0 8px 32px -8px color-mix(in srgb, var(--primary) 8%, transparent), 0 2px 8px -2px color-mix(in srgb, var(--foreground) 3%, transparent), inset 0 1px 0 0 color-mix(in srgb, white 25%, transparent); @@ -473,7 +508,7 @@ position: relative; overflow: hidden; } - + .fintech-card::after { content: ""; position: absolute; @@ -481,26 +516,28 @@ left: 0; right: 0; height: 1px; - background: linear-gradient(90deg, + background: linear-gradient( + 90deg, transparent 0%, color-mix(in srgb, white 40%, transparent) 50%, transparent 100% ); opacity: 0.5; } - + .fintech-card:hover { transform: translateY(-6px) scale(1.01); - box-shadow: + box-shadow: 0 24px 48px -12px color-mix(in srgb, var(--primary) 25%, transparent), 0 8px 24px -8px color-mix(in srgb, var(--foreground) 8%, transparent), inset 0 1px 0 0 color-mix(in srgb, white 40%, transparent); border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); } - + /* Gradient backgrounds for stat cards très vibrants */ .stat-card-gradient-1 { - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, color-mix(in srgb, var(--primary) 12%, var(--card)) 0%, color-mix(in srgb, var(--primary-light) 8%, var(--card)) 50%, color-mix(in srgb, var(--primary) 6%, var(--card)) 100% @@ -508,7 +545,7 @@ position: relative; overflow: hidden; } - + .stat-card-gradient-1::before { content: ""; position: absolute; @@ -516,16 +553,18 @@ right: -50%; width: 200%; height: 200%; - background: radial-gradient(circle, + background: radial-gradient( + circle, color-mix(in srgb, var(--primary) 20%, transparent) 0%, transparent 70% ); opacity: 0.3; animation: gradient-shift 8s ease infinite; } - + .stat-card-gradient-2 { - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, color-mix(in srgb, var(--success) 12%, var(--card)) 0%, color-mix(in srgb, var(--success) 8%, var(--card)) 50%, color-mix(in srgb, var(--success) 6%, var(--card)) 100% @@ -533,7 +572,7 @@ position: relative; overflow: hidden; } - + .stat-card-gradient-2::before { content: ""; position: absolute; @@ -541,16 +580,18 @@ right: -50%; width: 200%; height: 200%; - background: radial-gradient(circle, + background: radial-gradient( + circle, color-mix(in srgb, var(--success) 20%, transparent) 0%, transparent 70% ); opacity: 0.3; animation: gradient-shift 8s ease infinite; } - + .stat-card-gradient-3 { - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, color-mix(in srgb, var(--destructive) 12%, var(--card)) 0%, color-mix(in srgb, var(--destructive) 8%, var(--card)) 50%, color-mix(in srgb, var(--destructive) 6%, var(--card)) 100% @@ -558,7 +599,7 @@ position: relative; overflow: hidden; } - + .stat-card-gradient-3::before { content: ""; position: absolute; @@ -566,16 +607,18 @@ right: -50%; width: 200%; height: 200%; - background: radial-gradient(circle, + background: radial-gradient( + circle, color-mix(in srgb, var(--destructive) 20%, transparent) 0%, transparent 70% ); opacity: 0.3; animation: gradient-shift 8s ease infinite; } - + .stat-card-gradient-4 { - background: linear-gradient(135deg, + background: linear-gradient( + 135deg, color-mix(in srgb, var(--chart-4) 12%, var(--card)) 0%, color-mix(in srgb, var(--chart-4) 8%, var(--card)) 50%, color-mix(in srgb, var(--chart-4) 6%, var(--card)) 100% @@ -583,7 +626,7 @@ position: relative; overflow: hidden; } - + .stat-card-gradient-4::before { content: ""; position: absolute; @@ -591,7 +634,8 @@ right: -50%; width: 200%; height: 200%; - background: radial-gradient(circle, + background: radial-gradient( + circle, color-mix(in srgb, var(--chart-4) 20%, transparent) 0%, transparent 70% ); diff --git a/app/page.tsx b/app/page.tsx index 82a97e4..80aab17 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -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 { diff --git a/app/statistics/page.tsx b/app/statistics/page.tsx index 037ec05..0e741fb 100644 --- a/app/statistics/page.tsx +++ b/app/statistics/page.tsx @@ -62,10 +62,10 @@ export default function StatisticsPage() { const [excludeInternalTransfers, setExcludeInternalTransfers] = useState(true); const [customStartDate, setCustomStartDate] = useState( - undefined + undefined, ); const [customEndDate, setCustomEndDate] = useState( - undefined + undefined, ); const [isCustomDatePickerOpen, setIsCustomDatePickerOpen] = useState(false); @@ -100,7 +100,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]); @@ -216,7 +216,7 @@ export default function StatisticsPage() { // Filter by accounts if (!selectedAccounts.includes("all")) { transactions = transactions.filter((t) => - selectedAccounts.includes(t.accountId) + selectedAccounts.includes(t.accountId), ); } @@ -226,7 +226,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), ); } } @@ -234,7 +234,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, ); } @@ -306,7 +306,7 @@ export default function StatisticsPage() { }); const categoryChartDataByParent = Array.from( - categoryTotalsByParent.entries() + categoryTotalsByParent.entries(), ) .map(([groupId, total]) => { const category = data.categories.find((c) => c.id === groupId); @@ -321,7 +321,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) @@ -347,7 +347,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 @@ -359,7 +359,7 @@ export default function StatisticsPage() { // Start with initial balances runningBalance = accountsToUse.reduce( (sum, acc) => sum + (acc.initialBalance || 0), - 0 + 0, ); // Add all transactions before the start date for these accounts @@ -396,7 +396,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", @@ -853,7 +853,7 @@ export default function StatisticsPage() { onRemoveAccount={(id) => { const newAccounts = selectedAccounts.filter((a) => a !== id); setSelectedAccounts( - newAccounts.length > 0 ? newAccounts : ["all"] + newAccounts.length > 0 ? newAccounts : ["all"], ); }} onClearAccounts={() => setSelectedAccounts(["all"])} @@ -861,7 +861,7 @@ export default function StatisticsPage() { onRemoveCategory={(id) => { const newCategories = selectedCategories.filter((c) => c !== id); setSelectedCategories( - newCategories.length > 0 ? newCategories : ["all"] + newCategories.length > 0 ? newCategories : ["all"], ); }} onClearCategories={() => setSelectedCategories(["all"])} @@ -1043,17 +1043,17 @@ export default function StatisticsPage() { onRemoveAccount={(id) => { const newAccounts = selectedAccounts.filter((a) => a !== id); setSelectedAccounts( - newAccounts.length > 0 ? newAccounts : ["all"] + newAccounts.length > 0 ? newAccounts : ["all"], ); }} onClearAccounts={() => setSelectedAccounts(["all"])} selectedCategories={selectedCategories} onRemoveCategory={(id) => { const newCategories = selectedCategories.filter( - (c) => c !== id + (c) => c !== id, ); setSelectedCategories( - newCategories.length > 0 ? newCategories : ["all"] + newCategories.length > 0 ? newCategories : ["all"], ); }} onClearCategories={() => setSelectedCategories(["all"])} @@ -1203,7 +1203,7 @@ function ActiveFilters({ const selectedAccs = accounts.filter((a) => selectedAccounts.includes(a.id)); const selectedCats = categories.filter((c) => - selectedCategories.includes(c.id) + selectedCategories.includes(c.id), ); const isUncategorized = selectedCategories.includes("uncategorized"); diff --git a/components/accounts/account-bulk-actions.tsx b/components/accounts/account-bulk-actions.tsx index f2471a8..2333825 100644 --- a/components/accounts/account-bulk-actions.tsx +++ b/components/accounts/account-bulk-actions.tsx @@ -16,38 +16,31 @@ export function AccountBulkActions({ onDelete, }: AccountBulkActionsProps) { const isMobile = useIsMobile(); - + if (selectedCount === 0) return null; return ( - -
- + +
+ {selectedCount} compte{selectedCount > 1 ? "s" : ""} sélectionné {selectedCount > 1 ? "s" : ""} -
diff --git a/components/accounts/account-card.tsx b/components/accounts/account-card.tsx index bcc8ef8..a635ede 100644 --- a/components/accounts/account-card.tsx +++ b/components/accounts/account-card.tsx @@ -82,7 +82,7 @@ export function AccountCard({ "relative group", isSelected && "ring-2 ring-primary shadow-lg shadow-primary/10", isDragging && "bg-muted/80 opacity-60", - "hover:scale-[1.02] transition-transform duration-200" + "hover:scale-[1.02] transition-transform duration-200", )} > @@ -111,7 +111,7 @@ export function AccountCard({
{account.name} @@ -184,7 +184,7 @@ export function AccountCard({ ? "text-base" : "text-xl", !compact && !isMobile && "mb-1.5", - realBalance >= 0 ? "text-emerald-600" : "text-red-600" + realBalance >= 0 ? "text-emerald-600" : "text-red-600", )} > {formatCurrency(realBalance)} @@ -194,7 +194,7 @@ export function AccountCard({ href={`/transactions?accountId=${account.id}`} className={cn( "text-muted-foreground hover:text-primary hover:underline shrink-0 whitespace-nowrap", - isMobile ? "text-[9px]" : "text-xs" + isMobile ? "text-[9px]" : "text-xs", )} > {transactionCount} txns @@ -207,7 +207,7 @@ export function AccountCard({ className={cn( "flex items-center justify-between gap-2 mt-1", isMobile ? "text-[10px]" : "text-xs", - "text-muted-foreground" + "text-muted-foreground", )} > -
-
-
- -
-
-

{account.name}

-

- {account.accountNumber} -

-
-
- = 0 ? "text-emerald-600 dark:text-emerald-400" : "text-red-600 dark:text-red-400", - )} - > - {formatCurrency(realBalance)} - + return ( +
+
+
+
+ +
+
+

{account.name}

+

+ {account.accountNumber} +

- {realBalance > 0 && ( - - )}
- ); + = 0 + ? "text-emerald-600 dark:text-emerald-400" + : "text-red-600 dark:text-red-400", + )} + > + {formatCurrency(realBalance)} + +
+ {realBalance > 0 && ( + + )} +
+ ); })}
)} @@ -154,7 +159,9 @@ export function AccountsSummary({ data }: AccountsSummaryProps) { return ( - Mes Comptes + + Mes Comptes +
@@ -178,7 +185,9 @@ export function AccountsSummary({ data }: AccountsSummaryProps) { return ( - Mes Comptes + + Mes Comptes +
@@ -214,7 +223,10 @@ export function AccountsSummary({ data }: AccountsSummaryProps) { : 0; return ( -
+
@@ -241,7 +253,10 @@ export function AccountsSummary({ data }: AccountsSummaryProps) {
{realBalance > 0 && ( - + )}
); diff --git a/components/dashboard/overview-cards.tsx b/components/dashboard/overview-cards.tsx index 3761f42..2a7e57d 100644 --- a/components/dashboard/overview-cards.tsx +++ b/components/dashboard/overview-cards.tsx @@ -13,7 +13,7 @@ interface OverviewCardsProps { export function OverviewCards({ data }: OverviewCardsProps) { const totalBalance = data.accounts.reduce( (sum, acc) => sum + getAccountBalance(acc), - 0 + 0, ); const thisMonth = new Date(); @@ -21,7 +21,7 @@ export function OverviewCards({ data }: OverviewCardsProps) { const thisMonthStr = thisMonth.toISOString().slice(0, 7); const monthTransactions = data.transactions.filter((t) => - t.date.startsWith(thisMonthStr) + t.date.startsWith(thisMonthStr), ); const income = monthTransactions @@ -62,7 +62,7 @@ export function OverviewCards({ data }: OverviewCardsProps) { "text-2xl sm:text-3xl md:text-3xl lg:text-4xl xl:text-5xl font-black tracking-tight mb-4 leading-none", totalBalance >= 0 ? "text-emerald-600 dark:text-emerald-400" - : "text-red-600 dark:text-red-400" + : "text-red-600 dark:text-red-400", )} > {formatCurrency(totalBalance)} diff --git a/components/dashboard/recent-transactions.tsx b/components/dashboard/recent-transactions.tsx index be2a3b3..0845c76 100644 --- a/components/dashboard/recent-transactions.tsx +++ b/components/dashboard/recent-transactions.tsx @@ -43,11 +43,15 @@ export function RecentTransactions({ data }: RecentTransactionsProps) { return ( - Transactions récentes + + Transactions récentes +
-

Aucune transaction

+

+ Aucune transaction +

Importez un fichier OFX pour commencer

diff --git a/components/dashboard/sidebar.tsx b/components/dashboard/sidebar.tsx index af8282b..0620ea0 100644 --- a/components/dashboard/sidebar.tsx +++ b/components/dashboard/sidebar.tsx @@ -69,7 +69,9 @@ function SidebarContent({
- FinTrack + + FinTrack +
)}
@@ -85,19 +87,26 @@ function SidebarContent({ className={cn( "w-full justify-start gap-4 h-12 rounded-2xl transition-all duration-300", "hover:bg-muted/70 hover:scale-[1.02] hover:shadow-md", - isActive && "bg-gradient-to-r from-primary/15 via-primary/10 to-primary/5 border-2 border-primary/30 shadow-lg shadow-primary/10 backdrop-blur-sm", + isActive && + "bg-gradient-to-r from-primary/15 via-primary/10 to-primary/5 border-2 border-primary/30 shadow-lg shadow-primary/10 backdrop-blur-sm", collapsed && "justify-center px-2 w-12", )} > - + {!collapsed && ( - {item.label} + + {item.label} + )} @@ -115,7 +124,9 @@ function SidebarContent({ )} > - {!collapsed && Paramètres} + {!collapsed && ( + Paramètres + )}
@@ -173,7 +186,9 @@ export function Sidebar({ open, onOpenChange }: SidebarProps) {
- FinTrack + + FinTrack +
)}
)} diff --git a/components/transactions/transaction-filters.tsx b/components/transactions/transaction-filters.tsx index cf28ba5..2797790 100644 --- a/components/transactions/transaction-filters.tsx +++ b/components/transactions/transaction-filters.tsx @@ -256,7 +256,7 @@ export function TransactionFilters({ onRemoveCategory={(id) => { const newCategories = selectedCategories.filter((c) => c !== id); onCategoriesChange( - newCategories.length > 0 ? newCategories : ["all"] + newCategories.length > 0 ? newCategories : ["all"], ); }} onClearCategories={() => onCategoriesChange(["all"])} @@ -367,7 +367,7 @@ function ActiveFilters({ const selectedAccs = accounts.filter((a) => selectedAccounts.includes(a.id)); const selectedCats = categories.filter((c) => - selectedCategories.includes(c.id) + selectedCategories.includes(c.id), ); const isUncategorized = selectedCategories.includes("uncategorized"); diff --git a/components/ui/account-filter-combobox.tsx b/components/ui/account-filter-combobox.tsx index f1a6d14..eac95a2 100644 --- a/components/ui/account-filter-combobox.tsx +++ b/components/ui/account-filter-combobox.tsx @@ -64,7 +64,7 @@ export function AccountFilterCombobox({ // Get root folders (folders without parent) - same as folders/page.tsx const rootFolders = useMemo( () => folders.filter((f) => f.parentId === null), - [folders] + [folders], ); // Get child folders for a given parent - same as FolderTreeItem @@ -78,7 +78,7 @@ export function AccountFilterCombobox({ // Get accounts without folder const orphanAccounts = useMemo( () => accounts.filter((a) => !a.folderId), - [accounts] + [accounts], ); const selectedAccounts = accounts.filter((a) => value.includes(a.id)); @@ -89,7 +89,7 @@ export function AccountFilterCombobox({ const directAccounts = getFolderAccounts(folderId); const childFoldersList = getChildFolders(folderId); const childAccounts = childFoldersList.flatMap((cf) => - getAllAccountsInFolder(cf.id) + getAllAccountsInFolder(cf.id), ); return [...directAccounts, ...childAccounts]; }; @@ -126,7 +126,7 @@ export function AccountFilterCombobox({ if (allSelected) { const newSelection = value.filter( - (v) => !allFolderAccountIds.includes(v) + (v) => !allFolderAccountIds.includes(v), ); onChange(newSelection.length > 0 ? newSelection : ["all"]); } else { @@ -153,7 +153,7 @@ export function AccountFilterCombobox({ const folderAccounts = getAllAccountsInFolder(folderId); if (folderAccounts.length === 0) return false; const selectedCount = folderAccounts.filter((a) => - value.includes(a.id) + value.includes(a.id), ).length; return selectedCount > 0 && selectedCount < folderAccounts.length; }; @@ -185,7 +185,7 @@ export function AccountFilterCombobox({
@@ -213,7 +213,7 @@ export function AccountFilterCombobox({ @@ -222,7 +222,7 @@ export function AccountFilterCombobox({ {/* Child folders - recursive */} {childFoldersList.map((childFolder) => - renderFolder(childFolder, depth + 1, currentPath) + renderFolder(childFolder, depth + 1, currentPath), )}
); @@ -237,7 +237,7 @@ export function AccountFilterCombobox({ aria-expanded={open} className={cn( "justify-between min-w-0 h-10 w-full text-sm font-normal", - className + className, )} >
@@ -304,7 +304,10 @@ export function AccountFilterCombobox({ ( {formatCurrency( - filteredTransactions.reduce((sum, t) => sum + t.amount, 0) + filteredTransactions.reduce( + (sum, t) => sum + t.amount, + 0, + ), )} ) @@ -312,7 +315,7 @@ export function AccountFilterCombobox({ @@ -348,7 +351,7 @@ export function AccountFilterCombobox({ "ml-auto h-4 w-4 shrink-0", value.includes(account.id) ? "opacity-100" - : "opacity-0" + : "opacity-0", )} /> diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 904b9d1..80e6954 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -9,7 +9,8 @@ const buttonVariants = cva( { variants: { variant: { - default: "bg-gradient-to-r from-primary via-primary/95 to-primary/90 text-primary-foreground hover:from-primary/95 hover:via-primary hover:to-primary/95 shadow-xl shadow-primary/30 hover:shadow-2xl hover:shadow-primary/40 hover:scale-[1.03] active:scale-[0.97] backdrop-blur-sm border border-primary/20", + default: + "bg-gradient-to-r from-primary via-primary/95 to-primary/90 text-primary-foreground hover:from-primary/95 hover:via-primary hover:to-primary/95 shadow-xl shadow-primary/30 hover:shadow-2xl hover:shadow-primary/40 hover:scale-[1.03] active:scale-[0.97] backdrop-blur-sm border border-primary/20", destructive: "bg-gradient-to-r from-destructive via-destructive/95 to-destructive/90 text-white hover:from-destructive/95 hover:via-destructive hover:to-destructive/95 shadow-xl shadow-destructive/30 hover:shadow-2xl hover:shadow-destructive/40 hover:scale-[1.03] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 active:scale-[0.97] backdrop-blur-sm border border-destructive/20", outline: diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 50e357e..34c49d1 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -10,7 +10,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) { "fintech-card", "bg-card text-card-foreground flex flex-col", "transition-all duration-300 ease-out", - className + className, )} {...props} /> @@ -23,7 +23,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-header" className={cn( "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", - className + className, )} {...props} /> @@ -56,7 +56,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) { data-slot="card-action" className={cn( "col-start-2 row-span-2 row-start-1 self-start justify-self-end", - className + className, )} {...props} /> diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx index b656993..ee28352 100644 --- a/components/ui/popover.tsx +++ b/components/ui/popover.tsx @@ -30,17 +30,21 @@ function PopoverContent({ if (!element) return; const disableAnimations = () => { - element.style.setProperty('animation', 'none', 'important'); - element.style.setProperty('transition', 'none', 'important'); + element.style.setProperty("animation", "none", "important"); + element.style.setProperty("transition", "none", "important"); // Ne pas toucher au transform car il est utilisé pour le positionnement - element.style.setProperty('opacity', '1', 'important'); - element.style.setProperty('will-change', 'auto', 'important'); - + element.style.setProperty("opacity", "1", "important"); + element.style.setProperty("will-change", "auto", "important"); + // Supprimer toutes les classes d'animation Tailwind - const classesToRemove = Array.from(element.classList).filter(cls => - cls.includes('animate') || cls.includes('fade') || cls.includes('zoom') || cls.includes('slide') + const classesToRemove = Array.from(element.classList).filter( + (cls) => + cls.includes("animate") || + cls.includes("fade") || + cls.includes("zoom") || + cls.includes("slide"), ); - classesToRemove.forEach(cls => element.classList.remove(cls)); + classesToRemove.forEach((cls) => element.classList.remove(cls)); }; // Désactiver immédiatement @@ -53,7 +57,7 @@ function PopoverContent({ observer.observe(element, { attributes: true, - attributeFilter: ['class', 'data-state'], + attributeFilter: ["class", "data-state"], subtree: false, }); @@ -84,11 +88,13 @@ function PopoverContent({ "bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-hidden", className, )} - style={{ - animation: 'none !important', - transition: 'none !important', - opacity: '1 !important', - } as React.CSSProperties} + style={ + { + animation: "none !important", + transition: "none !important", + opacity: "1 !important", + } as React.CSSProperties + } {...props} /> diff --git a/components/ui/select.tsx b/components/ui/select.tsx index 7cc0783..131aee8 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -63,17 +63,21 @@ function SelectContent({ if (!element) return; const disableAnimations = () => { - element.style.setProperty('animation', 'none', 'important'); - element.style.setProperty('transition', 'none', 'important'); + element.style.setProperty("animation", "none", "important"); + element.style.setProperty("transition", "none", "important"); // Ne pas toucher au transform car il est utilisé pour le positionnement - element.style.setProperty('opacity', '1', 'important'); - element.style.setProperty('will-change', 'auto', 'important'); - + element.style.setProperty("opacity", "1", "important"); + element.style.setProperty("will-change", "auto", "important"); + // Supprimer toutes les classes d'animation Tailwind - const classesToRemove = Array.from(element.classList).filter(cls => - cls.includes('animate') || cls.includes('fade') || cls.includes('zoom') || cls.includes('slide') + const classesToRemove = Array.from(element.classList).filter( + (cls) => + cls.includes("animate") || + cls.includes("fade") || + cls.includes("zoom") || + cls.includes("slide"), ); - classesToRemove.forEach(cls => element.classList.remove(cls)); + classesToRemove.forEach((cls) => element.classList.remove(cls)); }; // Désactiver immédiatement @@ -86,7 +90,7 @@ function SelectContent({ observer.observe(element, { attributes: true, - attributeFilter: ['class', 'data-state'], + attributeFilter: ["class", "data-state"], subtree: false, }); @@ -116,11 +120,13 @@ function SelectContent({ className, )} position={position} - style={{ - animation: 'none !important', - transition: 'none !important', - opacity: '1 !important', - } as React.CSSProperties} + style={ + { + animation: "none !important", + transition: "none !important", + opacity: "1 !important", + } as React.CSSProperties + } {...props} >