Add mobile bottom tab bar and redesign mobile dashboard
Import the Finlio Mobile design: a fixed bottom tab bar (Accueil/Plan/Suivi/Flux) replaces the duplicated links in the mobile hamburger menu, and the dashboard gets a mobile-first layout — hero "reste à dépenser" card, revenus/épargne tiles, and a spending-by- category list with progress bars — built from the same $sections data the desktop tables use. Desktop dashboard is untouched. The hero card uses a fixed dark background (independent of the light/dark token swap) so its white text holds contrast in both themes, matching how the existing -fill tokens are documented. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a77be2dfba
commit
d97cc41ee2
@@ -1,4 +1,84 @@
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 space-y-6">
|
||||
@php
|
||||
$colors = [
|
||||
'revenu' => ['bg' => 'bg-positive-fill', 'row' => 'bg-positive-surface/40'],
|
||||
'depense' => ['bg' => 'bg-negative-fill', 'row' => 'bg-negative-surface/40'],
|
||||
'epargne' => ['bg' => 'bg-info-fill', 'row' => 'bg-info-surface/40'],
|
||||
];
|
||||
|
||||
// Ordered to alternate light/dark shades so adjacent donut slices stay distinguishable
|
||||
// even without relying on the border stroke (WCAG 1.4.11 graphical-object contrast).
|
||||
$chartPalettes = [
|
||||
'revenu' => ['#15803d', '#4ade80', '#166534', '#86efac', '#16a34a', '#22c55e'],
|
||||
'depense' => ['#991b1b', '#fb923c', '#c2410c', '#fdba74', '#dc2626', '#f97316'],
|
||||
'epargne' => ['#1e40af', '#93c5fd', '#1d4ed8', '#bfdbfe', '#2563eb', '#60a5fa'],
|
||||
];
|
||||
|
||||
// Mobile category dots cycle through a same-hue tonal ramp so adjacent rows
|
||||
// stay distinguishable (WCAG 1.4.11), independent of the desktop chart palette.
|
||||
$mobileDotRamp = ['#8c491a', '#b2622d', '#d67f48', '#f6a06b'];
|
||||
$depenseRows = $sections['depense']['rows']->sortByDesc('tracked')->values();
|
||||
@endphp
|
||||
|
||||
<div>
|
||||
{{-- Mobile: hero "reste à dépenser" card + tiles + spending-by-category list --}}
|
||||
<div class="sm:hidden max-w-md mx-auto px-4 py-6 space-y-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-display text-text">Bonjour {{ Str::of(auth()->user()->name)->before(' ') }}</h1>
|
||||
<span class="w-9 h-9 rounded-full bg-positive-fill text-surface flex items-center justify-center text-xs font-bold shrink-0">
|
||||
{{ Str::of(auth()->user()->name)->explode(' ')->map(fn ($p) => mb_substr($p, 0, 1))->take(2)->implode('') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl shadow p-5 flex flex-col gap-2 text-white" style="background-color: #643312">
|
||||
<span class="text-xs uppercase tracking-wide text-white/75">Reste à dépenser · {{ \Illuminate\Support\Carbon::create($year, (int) ($period === 'year' ? now()->month : $period))->translatedFormat('F') }}</span>
|
||||
<span class="font-display text-3xl">{{ number_format($sections['depense']['totals']['reste'], 2, ',', ' ') }} €</span>
|
||||
@php
|
||||
$depTotals = $sections['depense']['totals'];
|
||||
$spentPct = $depTotals['budget'] > 0 ? min(100, round($depTotals['tracked'] / $depTotals['budget'] * 100)) : 0;
|
||||
@endphp
|
||||
<div class="h-2 rounded-full bg-white/20 overflow-hidden" role="img" aria-label="{{ $spentPct }}% du budget dépenses utilisé">
|
||||
<div class="h-full rounded-full bg-white/80" style="width: {{ $spentPct }}%"></div>
|
||||
</div>
|
||||
<span class="text-xs text-white/70">{{ number_format($depTotals['tracked'], 2, ',', ' ') }} € dépensés sur {{ number_format($depTotals['budget'], 2, ',', ' ') }} € prévus</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2.5">
|
||||
<div class="flex-1 rounded-2xl shadow-sm p-4 flex flex-col gap-1 bg-positive-surface">
|
||||
<span class="text-[10px] uppercase tracking-wide text-positive">Revenus</span>
|
||||
<span class="font-display text-lg text-positive">{{ number_format($sections['revenu']['totals']['tracked'], 2, ',', ' ') }} €</span>
|
||||
</div>
|
||||
<div class="flex-1 rounded-2xl shadow-sm p-4 flex flex-col gap-1 bg-surface">
|
||||
<span class="text-[10px] uppercase tracking-wide text-text-muted">Épargne</span>
|
||||
<span class="font-display text-lg text-text">{{ number_format($sections['epargne']['totals']['tracked'], 2, ',', ' ') }} €</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-base font-semibold text-text">Dépenses par catégorie</h4>
|
||||
</div>
|
||||
|
||||
@if ($depenseRows->isEmpty())
|
||||
<p class="text-xs text-text-muted py-8 text-center">Aucune catégorie de dépenses.</p>
|
||||
@else
|
||||
<div class="flex flex-col gap-2.5">
|
||||
@foreach ($depenseRows as $i => $row)
|
||||
<div class="rounded-2xl shadow-sm p-3 flex items-center gap-3 bg-surface">
|
||||
<span class="w-10 h-10 rounded-full shrink-0" style="background-color: {{ $mobileDotRamp[$i % count($mobileDotRamp)] }}" aria-hidden="true"></span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-semibold text-sm text-text truncate">{{ $row['category']->name }}</div>
|
||||
<div class="h-1.5 rounded-full bg-surface-alt overflow-hidden mt-1.5" role="img" aria-label="{{ $row['pct'] }}% du budget utilisé">
|
||||
<div class="h-full rounded-full" style="width: {{ $row['pct'] }}%; background-color: {{ $mobileDotRamp[$i % count($mobileDotRamp)] }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-bold text-sm text-text shrink-0">{{ number_format($row['tracked'], 0, ',', ' ') }} €</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Desktop / tablet: full analysis tables + donut charts --}}
|
||||
<div class="hidden sm:block max-w-7xl mx-auto py-6 px-4 sm:px-6 space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<h1 class="text-3xl font-display text-text">Tableau de bord</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -27,22 +107,6 @@
|
||||
<p class="font-display text-2xl text-negative">{{ number_format($sections['depense']['totals']['reste'], 2, ',', ' ') }} €</p>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$colors = [
|
||||
'revenu' => ['bg' => 'bg-positive-fill', 'row' => 'bg-positive-surface/40'],
|
||||
'depense' => ['bg' => 'bg-negative-fill', 'row' => 'bg-negative-surface/40'],
|
||||
'epargne' => ['bg' => 'bg-info-fill', 'row' => 'bg-info-surface/40'],
|
||||
];
|
||||
|
||||
// Ordered to alternate light/dark shades so adjacent donut slices stay distinguishable
|
||||
// even without relying on the border stroke (WCAG 1.4.11 graphical-object contrast).
|
||||
$chartPalettes = [
|
||||
'revenu' => ['#15803d', '#4ade80', '#166534', '#86efac', '#16a34a', '#22c55e'],
|
||||
'depense' => ['#991b1b', '#fb923c', '#c2410c', '#fdba74', '#dc2626', '#f97316'],
|
||||
'epargne' => ['#1e40af', '#93c5fd', '#1d4ed8', '#bfdbfe', '#2563eb', '#60a5fa'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="bg-surface shadow rounded-3xl overflow-hidden">
|
||||
<div class="px-5 pt-4 pb-1">
|
||||
|
||||
@@ -116,31 +116,12 @@ new class extends Component
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Primary routes (Tableau de bord/Plan/Suivi/Flux) live in the bottom tab
|
||||
bar on mobile now — this menu only needs account/settings links. --}}
|
||||
<!-- Responsive Navigation Menu -->
|
||||
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden mt-2 bg-surface rounded-3xl shadow-sm px-2">
|
||||
<div class="pt-2 pb-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Tableau de bord') }}
|
||||
</x-responsive-nav-link>
|
||||
@if (Route::has('plan'))
|
||||
<x-responsive-nav-link :href="route('plan')" :active="request()->routeIs('plan')" wire:navigate>
|
||||
{{ __('Plan') }}
|
||||
</x-responsive-nav-link>
|
||||
@endif
|
||||
@if (Route::has('suivi'))
|
||||
<x-responsive-nav-link :href="route('suivi')" :active="request()->routeIs('suivi')" wire:navigate>
|
||||
{{ __('Suivi') }}
|
||||
</x-responsive-nav-link>
|
||||
@endif
|
||||
@if (Route::has('sankey'))
|
||||
<x-responsive-nav-link :href="route('sankey')" :active="request()->routeIs('sankey')" wire:navigate>
|
||||
{{ __('Flux') }}
|
||||
</x-responsive-nav-link>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Responsive Settings Options -->
|
||||
<div class="pt-4 pb-1 border-t border-border">
|
||||
<div class="pt-2 pb-1">
|
||||
<div class="px-4">
|
||||
<div class="font-medium text-base text-text" x-data="{{ json_encode(['name' => auth()->user()->name]) }}" x-text="name" x-on:profile-updated.window="name = $event.detail.name"></div>
|
||||
<div class="font-medium text-sm text-text-muted">{{ auth()->user()->email }}</div>
|
||||
|
||||
Reference in New Issue
Block a user