Files
suivicpt/resources/views/livewire/suivi/index.blade.php
T
jeremy bayseandClaude Sonnet 5 52ebb9bc83 Apply Finlio cream/terracotta design system
Re-theme dashboard, suivi, plan, and flux pages plus nav from the
claude.ai/design import: warm cream/terracotta/olive palette, pill
nav/buttons/inputs, Caprasimo display font for headings. Dark mode
variant derived to hold the existing WCAG AA contrast ratios since
the import only specified light.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:21:17 +02:00

423 lines
27 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div>
{{-- Mobile: quick single-purpose entry form --}}
<div
class="sm:hidden max-w-md mx-auto px-4 py-6 space-y-5"
x-data="{ toast: false, message: '' }"
x-on:transaction-added.window="message = 'Opération ajoutée'; toast = true; setTimeout(() => toast = false, 2000)"
x-on:transaction-updated.window="message = 'Opération modifiée'; toast = true; setTimeout(() => toast = false, 2000)"
>
<h1 id="mobile-form-top" class="text-2xl font-display text-text">{{ $editingTransactionId ? 'Modifier l\'opération' : 'Nouvelle opération' }}</h1>
@if ($pendingRecurrences->isNotEmpty())
<div class="space-y-3">
<p class="text-sm font-medium text-text-muted">À confirmer ce mois-ci</p>
@foreach ($pendingRecurrences as $recurring)
<div class="bg-surface-alt border border-border-strong rounded-lg p-3" wire:key="pending-mobile-{{ $recurring->id }}">
<p class="text-sm text-text">{{ $recurring->name }} <span class="text-text-muted"> {{ $recurring->category->name }}</span></p>
<div class="mt-2 flex items-center gap-2">
<label for="pending-amount-mobile-{{ $recurring->id }}" class="sr-only">Montant pour {{ $recurring->name }}</label>
<input
x-ref="amount{{ $recurring->id }}"
id="pending-amount-mobile-{{ $recurring->id }}"
type="number"
step="0.01"
value="{{ $recurring->amount }}"
class="flex-1 bg-surface border-border-strong text-text rounded-md text-sm py-1.5"
/>
<button
type="button"
x-on:click="$wire.confirmRecurrence({{ $recurring->id }}, $refs.amount{{ $recurring->id }}.value)"
class="text-xs text-white bg-brand px-3 py-1.5 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Confirmer
</button>
<button
type="button"
wire:click="dismissRecurrence({{ $recurring->id }})"
wire:confirm="Ignorer cette récurrence pour ce mois ?"
class="text-xs text-text-muted hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Ignorer
</button>
</div>
</div>
@endforeach
</div>
@endif
<form wire:submit="addTransaction" class="space-y-5">
<fieldset>
<legend class="text-sm font-medium text-text-muted mb-2">Catégorie</legend>
<div class="space-y-4">
@foreach (['revenu' => ['label' => 'Revenus', 'ring' => 'ring-positive', 'active' => 'bg-positive-fill text-white border-positive-fill'], 'depense' => ['label' => 'Dépenses', 'ring' => 'ring-negative', 'active' => 'bg-negative-fill text-white border-negative-fill'], 'epargne' => ['label' => 'Épargne', 'ring' => 'ring-info', 'active' => 'bg-info-fill text-white border-info-fill']] as $typeValue => $meta)
@php $typeCategories = $categories->where('type', $typeValue); @endphp
@if ($typeCategories->isNotEmpty())
<div>
<p class="text-xs text-text-muted mb-1.5">{{ $meta['label'] }}</p>
<div class="grid grid-cols-2 gap-2">
@foreach ($typeCategories as $category)
@php
$isActive = (string) $categoryId === (string) $category->id;
$progress = $categoryProgress[$category->id] ?? null;
@endphp
<button
type="button"
wire:click="$set('categoryId', {{ $category->id }})"
aria-pressed="{{ $isActive ? 'true' : 'false' }}"
class="relative min-h-[52px] px-3 py-2 rounded-lg border text-sm font-medium text-left transition overflow-hidden focus:outline-none focus-visible:ring-2 {{ $meta['ring'] }}
{{ $isActive ? $meta['active'] : 'bg-surface border-border-strong text-text' }}"
>
@if ($progress !== null)
<span
aria-hidden="true"
class="absolute inset-y-0 left-0 {{ $isActive ? 'bg-white/25' : 'bg-text/10' }}"
style="width: {{ $progress['pct'] }}%"
></span>
@endif
<span class="relative flex flex-col gap-0.5">
<span class="flex items-center justify-between gap-2">
<span>{{ $category->name }}</span>
@if ($progress !== null)
<span class="text-xs font-normal {{ $isActive ? 'text-white/80' : 'text-text-muted' }}">{{ $progress['pct'] }}%</span>
@endif
</span>
@if ($progress !== null)
<span class="text-xs font-normal {{ $isActive ? 'text-white/70' : 'text-text-muted' }}">
{{ number_format($progress['remaining'], 0, ',', ' ') }} restants
</span>
@endif
</span>
</button>
@endforeach
</div>
</div>
@endif
@endforeach
</div>
<x-input-error :messages="$errors->get('categoryId')" class="mt-2" />
</fieldset>
<div>
<label for="mobile-amount" class="text-sm font-medium text-text-muted">Montant</label>
<div class="mt-1 relative">
<input
wire:model="amount"
id="mobile-amount"
type="number"
inputmode="decimal"
step="0.01"
placeholder="0.00"
class="w-full text-3xl font-semibold text-center py-4 rounded-lg bg-surface border-border-strong text-text focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
<span class="absolute right-4 top-1/2 -translate-y-1/2 text-2xl text-text-muted"></span>
</div>
<x-input-error :messages="$errors->get('amount')" class="mt-2" />
</div>
<div>
<label for="mobile-details" class="text-sm font-medium text-text-muted">Détails (optionnel)</label>
<input
wire:model="details"
id="mobile-details"
type="text"
class="mt-1 w-full py-3 px-3 rounded-lg bg-surface border-border-strong text-text focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
</div>
@if ($members->isNotEmpty())
<fieldset>
<legend class="text-sm font-medium text-text-muted mb-2">Payé par</legend>
<div class="grid grid-cols-2 gap-2">
<button
type="button"
wire:click="$set('paidByUserId', '')"
aria-pressed="{{ $paidByUserId === '' ? 'true' : 'false' }}"
class="min-h-[44px] px-3 py-2 rounded-lg border text-sm font-medium transition focus:outline-none focus-visible:ring-2 ring-brand
{{ $paidByUserId === '' ? 'bg-brand text-white border-brand' : 'bg-surface border-border-strong text-text' }}"
>
Commun
</button>
@foreach ($members as $member)
<button
type="button"
wire:click="$set('paidByUserId', '{{ $member->id }}')"
aria-pressed="{{ (string) $paidByUserId === (string) $member->id ? 'true' : 'false' }}"
class="min-h-[44px] px-3 py-2 rounded-lg border text-sm font-medium transition focus:outline-none focus-visible:ring-2 ring-brand
{{ (string) $paidByUserId === (string) $member->id ? 'bg-brand text-white border-brand' : 'bg-surface border-border-strong text-text' }}"
>
{{ $member->name }}
</button>
@endforeach
</div>
</fieldset>
@endif
<div class="flex items-center gap-3">
<button
type="submit"
class="flex-1 py-4 rounded-lg bg-brand text-white text-lg font-semibold focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
>
{{ $editingTransactionId ? 'Enregistrer' : 'Ajouter' }}
</button>
@if ($editingTransactionId)
<button
type="button"
wire:click="cancelEditTransaction"
class="py-4 px-4 rounded-lg border border-border-strong text-text text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Annuler
</button>
@endif
</div>
</form>
<div
x-show="toast"
x-cloak
x-transition
role="status"
class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-positive-fill text-white text-sm font-medium px-4 py-2 rounded-full shadow-lg"
>
<span x-text="'✓ ' + message"></span>
</div>
@if ($recentTransactions->isNotEmpty())
<div>
<p class="text-sm font-medium text-text-muted mb-2">Dernières opérations</p>
<ul class="space-y-2">
@foreach ($recentTransactions as $transaction)
<li class="bg-surface border border-border rounded-2xl px-4 py-3 flex items-center justify-between gap-2" wire:key="recent-{{ $transaction->id }}">
<div class="min-w-0">
<p class="text-sm text-text truncate">
{{ $transaction->category->name }}
@if ($transaction->details)
<span class="text-text-muted"> {{ $transaction->details }}</span>
@endif
</p>
<p class="text-xs text-text-muted">{{ $transaction->date->translatedFormat('d M Y') }} · {{ number_format($transaction->amount, 2, ',', ' ') }} </p>
</div>
<div class="flex items-center gap-3 shrink-0">
<button
wire:click="startEditTransaction({{ $transaction->id }})"
x-on:click="$nextTick(() => document.getElementById('mobile-form-top')?.scrollIntoView({ behavior: 'smooth' }))"
aria-label="Modifier {{ $transaction->category->name }} du {{ $transaction->date->translatedFormat('d M Y') }}"
class="text-xs text-brand hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Modifier
</button>
<button wire:click="deleteTransaction({{ $transaction->id }})" wire:confirm="Supprimer cet enregistrement ?" aria-label="Supprimer {{ $transaction->category->name }} du {{ $transaction->date->translatedFormat('d M Y') }}" class="text-xs text-danger hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
Suppr.
</button>
</div>
</li>
@endforeach
</ul>
</div>
@endif
</div>
{{-- Desktop / tablet: full table view --}}
<div class="hidden sm:block max-w-6xl mx-auto py-6 px-4 sm:px-6 space-y-6">
<h1 class="text-3xl font-display text-text">Suivi</h1>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div class="bg-surface shadow rounded-2xl p-5">
<p class="text-xs text-text-muted">Date du jour</p>
<p class="mt-1 text-lg font-bold text-text">{{ now()->translatedFormat('d M Y') }}</p>
</div>
<div class="bg-surface shadow rounded-2xl p-5">
<p class="text-xs text-text-muted">Dernier enreg.</p>
<p class="mt-1 text-lg font-bold text-brand">{{ $lastEntry?->date?->translatedFormat('d M Y') ?? '—' }}</p>
</div>
<div class="bg-surface shadow rounded-2xl p-5">
<p class="text-xs text-text-muted">Nbr enreg.</p>
<p class="mt-1 text-lg font-bold text-brand">{{ $nbrTotal }}</p>
<p class="text-xs text-text-muted">{{ $nbrThisYear }} cette année</p>
</div>
<div class="bg-surface shadow rounded-2xl p-5">
<p class="text-xs text-text-muted">Affectation</p>
<p class="mt-1 text-lg font-bold text-text">{{ number_format($unaffected, 2, ',', ' ') }} </p>
<p class="text-xs text-text-muted">revenus restent à affecter</p>
</div>
</div>
@if ($paidBySummary->isNotEmpty())
<div class="bg-surface shadow rounded-2xl p-5">
<p class="text-xs text-text-muted mb-2">Dépenses ce mois-ci, par personne</p>
<div class="flex flex-wrap gap-8">
@foreach ($paidBySummary as $entry)
<div>
<p class="text-xs text-text-muted">{{ $entry['label'] }}</p>
<p class="text-base font-bold text-text">{{ number_format($entry['total'], 2, ',', ' ') }} </p>
</div>
@endforeach
</div>
</div>
@endif
@if ($pendingRecurrences->isNotEmpty())
<div class="bg-surface shadow rounded-2xl overflow-hidden">
<div class="px-5 py-3 bg-positive-fill">
<h2 class="text-white font-bold text-sm">À confirmer ce mois-ci</h2>
</div>
<ul class="divide-y divide-border">
@foreach ($pendingRecurrences as $recurring)
<li class="px-4 py-3 flex items-center justify-between gap-3" wire:key="pending-desktop-{{ $recurring->id }}">
<div class="min-w-0">
<p class="text-sm text-text">{{ $recurring->name }} <span class="text-text-muted"> {{ $recurring->category->name }}</span></p>
<p class="text-xs text-text-muted">le {{ $recurring->day_of_month }} de chaque mois</p>
</div>
<div class="flex items-center gap-2 shrink-0">
<label for="pending-amount-{{ $recurring->id }}" class="sr-only">Montant pour {{ $recurring->name }}</label>
<input
x-ref="amountDesktop{{ $recurring->id }}"
id="pending-amount-{{ $recurring->id }}"
type="number"
step="0.01"
value="{{ $recurring->amount }}"
class="w-28 bg-surface border-border-strong text-text rounded-md text-sm py-1"
/>
<button
type="button"
x-on:click="$wire.confirmRecurrence({{ $recurring->id }}, $refs.amountDesktop{{ $recurring->id }}.value)"
class="text-xs text-white bg-brand px-3 py-1.5 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Confirmer
</button>
<button
type="button"
wire:click="dismissRecurrence({{ $recurring->id }})"
wire:confirm="Ignorer cette récurrence pour ce mois ?"
class="text-xs text-text-muted hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>
Ignorer
</button>
</div>
</li>
@endforeach
</ul>
</div>
@endif
<div class="bg-surface shadow rounded-3xl p-5">
<p class="text-base font-semibold text-text mb-4">{{ $editingTransactionId ? 'Modifier l\'opération' : 'Nouvelle opération' }}</p>
<form wire:submit="addTransaction" class="grid grid-cols-1 sm:grid-cols-6 gap-3 items-end">
<div>
<x-input-label for="date" value="Date" />
<x-text-input wire:model="date" id="date" type="date" class="block mt-1 w-full text-sm" />
</div>
<div>
<x-input-label for="categoryId" value="Catégorie" />
<select wire:model="categoryId" id="categoryId" class="block mt-1 w-full rounded-full bg-surface-alt border-border-strong text-text text-sm px-4 py-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
<option value=""></option>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->type->label() }} {{ $category->name }}</option>
@endforeach
</select>
<x-input-error :messages="$errors->get('categoryId')" class="mt-1" />
</div>
<div>
<x-input-label for="amount" value="Montant" />
<x-text-input wire:model="amount" id="amount" type="number" step="0.01" class="block mt-1 w-full text-sm" />
<x-input-error :messages="$errors->get('amount')" class="mt-1" />
</div>
<div class="sm:col-span-2">
<x-input-label for="details" value="Détails" />
<x-text-input wire:model="details" id="details" type="text" class="block mt-1 w-full text-sm" />
</div>
<div>
<x-input-label for="dateEffective" value="Date effective" />
<x-text-input wire:model="dateEffective" id="dateEffective" type="date" class="block mt-1 w-full text-sm" />
</div>
<div>
<x-input-label for="paidByUserId" value="Payé par" />
<select wire:model="paidByUserId" id="paidByUserId" class="block mt-1 w-full rounded-full bg-surface-alt border-border-strong text-text text-sm px-4 py-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
<option value="">Commun</option>
@foreach ($members as $member)
<option value="{{ $member->id }}">{{ $member->name }}</option>
@endforeach
</select>
</div>
<div class="sm:col-span-6 flex items-center gap-3">
<x-primary-button>{{ $editingTransactionId ? 'Enregistrer' : 'Ajouter' }}</x-primary-button>
@if ($editingTransactionId)
<button type="button" wire:click="cancelEditTransaction" class="text-sm text-text-muted hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
Annuler
</button>
@endif
</div>
</form>
</div>
@php
$amountColor = fn ($t) => match ($t->category->type->value) {
'depense' => 'text-negative',
'epargne' => 'text-info',
default => 'text-positive',
};
$amountPrefix = fn ($t) => $t->category->type->value === 'depense' ? '' : '+';
@endphp
<div class="flex items-center justify-end gap-2">
<label for="filterPaidBy" class="text-xs text-text-muted">Payé par</label>
<select wire:model.live="filterPaidBy" id="filterPaidBy" class="rounded-full bg-surface border-border-strong text-text text-sm px-4 py-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
<option value="">Tout le monde</option>
<option value="common">Commun</option>
@foreach ($members as $member)
<option value="{{ $member->id }}">{{ $member->name }}</option>
@endforeach
</select>
</div>
<div class="bg-surface shadow rounded-3xl overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<caption class="sr-only">Historique des transactions du foyer</caption>
<thead>
<tr class="bg-surface-alt text-xs text-text-muted uppercase">
<th scope="col" class="text-left px-4 py-2">Date</th>
<th scope="col" class="text-left px-4 py-2">Type</th>
<th scope="col" class="text-left px-4 py-2">Catégorie</th>
<th scope="col" class="text-right px-4 py-2">Montant</th>
<th scope="col" class="text-left px-4 py-2">Détails</th>
<th scope="col" class="text-left px-4 py-2">Payé par</th>
<th scope="col" class="text-right px-4 py-2">Balance</th>
<th scope="col" class="text-left px-4 py-2">Date effective</th>
<th scope="col" class="px-4 py-2"><span class="sr-only">Actions</span></th>
</tr>
</thead>
<tbody>
@forelse ($transactions as $transaction)
<tr class="border-t border-border/60">
<td class="px-4 py-2 whitespace-nowrap text-text">{{ $transaction->date->translatedFormat('d M Y') }}</td>
<td class="px-4 py-2"><span class="bg-negative-surface text-negative text-xs px-2.5 py-1 rounded-full">{{ $transaction->category->type->label() }}</span></td>
<td class="px-4 py-2 text-text">{{ $transaction->category->name }}</td>
<td class="px-4 py-2 text-right font-medium {{ $amountColor($transaction) }}">
{{ $amountPrefix($transaction) }}{{ number_format($transaction->amount, 2, ',', ' ') }}
</td>
<td class="px-4 py-2 text-text-muted">{{ $transaction->details }}</td>
<td class="px-4 py-2 text-text-muted">{{ $transaction->paidBy?->name ?? 'Commun' }}</td>
<td class="px-4 py-2 text-right font-semibold {{ ($running[$transaction->id] ?? 0) < 0 ? 'text-negative' : 'text-text' }}">
{{ number_format($running[$transaction->id] ?? 0, 2, ',', ' ') }}
</td>
<td class="px-4 py-2 whitespace-nowrap text-text-muted">{{ $transaction->date_effective->translatedFormat('d M Y') }}</td>
<td class="px-4 py-2 text-right whitespace-nowrap">
<button wire:click="startEditTransaction({{ $transaction->id }})" aria-label="Modifier l'opération {{ $transaction->category->name }} du {{ $transaction->date->translatedFormat('d M Y') }}" class="text-xs text-brand hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
Modifier
</button>
<button wire:click="deleteTransaction({{ $transaction->id }})" wire:confirm="Supprimer cet enregistrement ?" aria-label="Supprimer l'opération {{ $transaction->category->name }} du {{ $transaction->date->translatedFormat('d M Y') }}" class="text-xs text-danger hover:underline rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring ml-3">
Suppr.
</button>
</td>
</tr>
@empty
<tr><td colspan="9" class="px-4 py-3 text-sm text-text-muted">Aucun enregistrement.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>