Laravel 11 + Livewire 3/Volt + Alpine.js + Tailwind app for household budget planning, transaction tracking, and financial dashboards. - Multi-tenant households with member invites - Plan: monthly/yearly budget per category with overrides - Suivi: transaction log with running balance (desktop + mobile quick-entry) - Dashboard: budget vs tracked breakdown + Chart.js donuts - Flux: Sankey diagram of income allocation - WCAG 2.1 AA color tokens, dark mode, accessible tables/forms - 50 Pest tests Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
2.4 KiB
PHP
83 lines
2.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Livewire\Sankey;
|
|
|
|
use App\Enums\CategoryType;
|
|
use App\Models\Category;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
#[Layout('layouts.app')]
|
|
class Index extends Component
|
|
{
|
|
public int $year;
|
|
|
|
public string $period = 'year'; // 'year' or '1'..'12'
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->year = (int) now()->year;
|
|
$this->period = (string) now()->month;
|
|
}
|
|
|
|
private function trackedFor(Category $category): float
|
|
{
|
|
$query = $category->transactions()->whereYear('date_effective', $this->year);
|
|
|
|
if ($this->period !== 'year') {
|
|
$query->whereMonth('date_effective', (int) $this->period);
|
|
}
|
|
|
|
return (float) $query->sum('amount');
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$household = auth()->user()->currentHousehold;
|
|
|
|
$revenueCategories = $household->categories()->where('type', CategoryType::Revenu)->orderBy('position')->get();
|
|
$totalRevenue = $revenueCategories->sum(fn (Category $c) => $this->trackedFor($c));
|
|
|
|
$outflowCategories = $household->categories()
|
|
->whereIn('type', [CategoryType::Depense, CategoryType::Epargne])
|
|
->orderBy('type')
|
|
->orderBy('position')
|
|
->get()
|
|
->map(fn (Category $category) => [
|
|
'category' => $category,
|
|
'tracked' => $this->trackedFor($category),
|
|
])
|
|
->filter(fn ($row) => $row['tracked'] > 0)
|
|
->values();
|
|
|
|
$totalOutflow = $outflowCategories->sum('tracked');
|
|
$unallocated = max(0, $totalRevenue - $totalOutflow);
|
|
|
|
$flows = $outflowCategories->map(fn ($row) => [
|
|
'from' => 'Revenus',
|
|
'to' => $row['category']->name,
|
|
'flow' => (float) $row['tracked'],
|
|
'type' => $row['category']->type->value,
|
|
]);
|
|
|
|
if ($unallocated > 0) {
|
|
$flows->push([
|
|
'from' => 'Revenus',
|
|
'to' => 'Non affecté',
|
|
'flow' => $unallocated,
|
|
'type' => 'unallocated',
|
|
]);
|
|
}
|
|
|
|
return view('livewire.sankey.index', [
|
|
'flows' => $flows->values(),
|
|
'totalRevenue' => $totalRevenue,
|
|
'totalOutflow' => $totalOutflow,
|
|
'unallocated' => $unallocated,
|
|
'hasData' => $totalRevenue > 0,
|
|
]);
|
|
}
|
|
}
|