Add budget progress gauge inside mobile category buttons

Each depense/epargne category button on the mobile quick-entry form
now shows a fill bar plus percentage of its monthly budget already
tracked, so the user sees at a glance which categories are close to
their limit while picking one. Categories without a budget defined
in Plan show no gauge (avoids a misleading 0%). Capped at 100% —
overspend detail already lives on the dashboard's "Manque" column.

3 new Pest tests for the percentage calculation (normal, capped,
no-budget cases). 61/61 tests pass, Pint clean, verified on mobile
against production data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jeremy bayse
2026-07-26 12:48:29 +02:00
co-authored by Claude Sonnet 5
parent 5861fc49fc
commit 613220f43a
3 changed files with 113 additions and 5 deletions
+45 -1
View File
@@ -6,6 +6,7 @@ namespace App\Livewire\Suivi;
use App\Enums\CategoryType;
use App\Models\BudgetLine;
use App\Models\Category;
use App\Models\RecurringTransaction;
use App\Models\Transaction;
use Livewire\Attributes\Layout;
@@ -108,6 +109,32 @@ class Index extends Component
]);
}
/**
* Budget planifié pour cette catégorie sur le mois en cours, en résolvant
* l'override du mois s'il existe sinon le montant par défaut de l'année.
*/
private function budgetForCurrentMonth(Category $category, int $year, int $month): ?float
{
$lines = $category->budgetLines()->where('year', $year)->get();
$override = $lines->firstWhere('month', $month);
if ($override) {
return (float) $override->amount;
}
$default = $lines->firstWhere('month', null);
return $default ? (float) $default->amount : null;
}
private function trackedForCategoryThisMonth(Category $category, int $year, int $month): float
{
return (float) $category->transactions()
->whereYear('date_effective', $year)
->whereMonth('date_effective', $month)
->sum('amount');
}
public function render()
{
$household = auth()->user()->currentHousehold;
@@ -159,10 +186,27 @@ class Index extends Component
->sortBy('day_of_month')
->values();
$categories = $household->categories()->orderBy('type')->orderBy('position')->get();
$categoryProgress = $categories
->whereIn('type', [CategoryType::Depense, CategoryType::Epargne])
->mapWithKeys(function (Category $category) use ($year, $month) {
$budget = $this->budgetForCurrentMonth($category, $year, $month);
if ($budget === null || $budget <= 0) {
return [$category->id => null];
}
$tracked = $this->trackedForCategoryThisMonth($category, $year, $month);
return [$category->id => min(100, round($tracked / $budget * 100))];
});
return view('livewire.suivi.index', [
'transactions' => $transactions,
'running' => $running,
'categories' => $household->categories()->orderBy('type')->orderBy('position')->get(),
'categories' => $categories,
'categoryProgress' => $categoryProgress,
'nbrThisYear' => $transactions->filter(fn (Transaction $t) => $t->date->year === $year)->count(),
'nbrTotal' => $transactions->count(),
'lastEntry' => $transactions->first(),