feat(dashboard): clickable stat cards + 2 new WO counters + parts row link

Every card on the tenant main dashboard now links to a relevant page:

Operations widget (was 5 cards, now 7):
- Clienți → /app/clients
- Mașini → /app/vehicles
- Cereri noi → /app/leads pre-filtered by status=new
- Deal-uri active → /app/deals
- Programări azi → /app/calendar-board
- NEW: Fișe în lucru (in_work + awaiting_parts) → /app/work-orders filter
- NEW: Fișe gata predare (ready) → /app/work-orders filter

Finance widget (all 4 cards clickable now):
- Încasări → /app/payments
- Cheltuieli → /app/expenses
- Profit → /app/reports (with "Vezi raportul financiar" hint)
- Datorii → /app/work-orders pre-filtered by pay_status=unpaid

LowStockTable rows now navigate to /app/parts/{id}/edit on click and
have an explicit edit action for touch users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 05:55:39 +00:00
parent c9d400fd6e
commit 6e7c665433
5 changed files with 55 additions and 9 deletions
@@ -33,19 +33,25 @@ class FinanceOverview extends BaseWidget
Stat::make(__('Încasări (luna)'), number_format($income, 2, '.', ' ') . ' MDL') Stat::make(__('Încasări (luna)'), number_format($income, 2, '.', ' ') . ' MDL')
->icon('heroicon-o-arrow-trending-up') ->icon('heroicon-o-arrow-trending-up')
->color('success') ->color('success')
->description(now()->translatedFormat('F Y')), ->description(now()->translatedFormat('F Y'))
->url(url('/app/payments')),
Stat::make(__('Cheltuieli (luna)'), number_format($expenses, 2, '.', ' ') . ' MDL') Stat::make(__('Cheltuieli (luna)'), number_format($expenses, 2, '.', ' ') . ' MDL')
->icon('heroicon-o-arrow-trending-down') ->icon('heroicon-o-arrow-trending-down')
->color('danger'), ->color('danger')
->url(url('/app/expenses')),
Stat::make(__('Profit (luna)'), number_format($profit, 2, '.', ' ') . ' MDL') Stat::make(__('Profit (luna)'), number_format($profit, 2, '.', ' ') . ' MDL')
->icon('heroicon-o-banknotes') ->icon('heroicon-o-banknotes')
->color($profit >= 0 ? 'success' : 'danger'), ->color($profit >= 0 ? 'success' : 'danger')
->description(__('Vezi raportul financiar'))
->url(url('/app/reports')),
Stat::make(__('Datorii clienți'), number_format($debt, 2, '.', ' ') . ' MDL') Stat::make(__('Datorii clienți'), number_format($debt, 2, '.', ' ') . ' MDL')
->icon('heroicon-o-exclamation-circle') ->icon('heroicon-o-exclamation-circle')
->color($debt > 0 ? 'warning' : 'success'), ->color($debt > 0 ? 'warning' : 'success')
->description(__('Fișe cu plată în așteptare'))
->url(url('/app/work-orders') . '?tableFilters[pay_status][value]=unpaid'),
]; ];
} }
} }
@@ -3,6 +3,7 @@
namespace App\Filament\Tenant\Widgets; namespace App\Filament\Tenant\Widgets;
use App\Models\Tenant\Part; use App\Models\Tenant\Part;
use Filament\Actions\Action;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget; use Filament\Widgets\TableWidget as BaseWidget;
@@ -38,6 +39,13 @@ class LowStockTable extends BaseWidget
Tables\Columns\TextColumn::make('min_qty')->label(__('Min.'))->alignRight(), Tables\Columns\TextColumn::make('min_qty')->label(__('Min.'))->alignRight(),
Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—'), Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—'),
]) ])
->recordUrl(fn (Part $r) => url('/app/parts/' . $r->id . '/edit'))
->actions([
Action::make('edit')
->label(__('Editează'))
->icon('heroicon-m-pencil-square')
->url(fn (Part $r) => url('/app/parts/' . $r->id . '/edit')),
])
->paginated(false) ->paginated(false)
->defaultSort('qty'); ->defaultSort('qty');
} }
+25 -5
View File
@@ -7,6 +7,7 @@ use App\Models\Tenant\Client;
use App\Models\Tenant\Deal; use App\Models\Tenant\Deal;
use App\Models\Tenant\Lead; use App\Models\Tenant\Lead;
use App\Models\Tenant\Vehicle; use App\Models\Tenant\Vehicle;
use App\Models\Tenant\WorkOrder;
use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat; use Filament\Widgets\StatsOverviewWidget\Stat;
@@ -19,32 +20,51 @@ class StatsOverview extends BaseWidget
$newLeads = Lead::where('status', 'new')->count(); $newLeads = Lead::where('status', 'new')->count();
$openDeals = Deal::whereNotIn('stage', ['done', 'lost'])->count(); $openDeals = Deal::whereNotIn('stage', ['done', 'lost'])->count();
$todayAppointments = Appointment::whereDate('date', today())->count(); $todayAppointments = Appointment::whereDate('date', today())->count();
$activeWos = WorkOrder::whereIn('status', ['in_work', 'awaiting_parts'])->count();
$readyWos = WorkOrder::where('status', 'ready')->count();
return [ return [
Stat::make(__('Clienți'), Client::count()) Stat::make(__('Clienți'), Client::count())
->description(__('Total în baza de date')) ->description(__('Total în baza de date'))
->icon('heroicon-o-users') ->icon('heroicon-o-users')
->color('primary'), ->color('primary')
->url(url('/app/clients')),
Stat::make(__('Mașini'), Vehicle::count()) Stat::make(__('Mașini'), Vehicle::count())
->description(__('Total înregistrate')) ->description(__('Total înregistrate'))
->icon('heroicon-o-truck') ->icon('heroicon-o-truck')
->color('info'), ->color('info')
->url(url('/app/vehicles')),
Stat::make(__('Cereri noi'), $newLeads) Stat::make(__('Cereri noi'), $newLeads)
->description(__('De procesat')) ->description(__('De procesat'))
->icon('heroicon-o-inbox-arrow-down') ->icon('heroicon-o-inbox-arrow-down')
->color($newLeads > 0 ? 'warning' : 'success'), ->color($newLeads > 0 ? 'warning' : 'success')
->url(url('/app/leads') . '?tableFilters[status][value]=new'),
Stat::make(__('Deal-uri active'), $openDeals) Stat::make(__('Deal-uri active'), $openDeals)
->description(__('În pipeline')) ->description(__('În pipeline'))
->icon('heroicon-o-funnel') ->icon('heroicon-o-funnel')
->color('primary'), ->color('primary')
->url(url('/app/deals')),
Stat::make(__('Programări azi'), $todayAppointments) Stat::make(__('Programări azi'), $todayAppointments)
->description(today()->format('d.m.Y')) ->description(today()->format('d.m.Y'))
->icon('heroicon-o-calendar') ->icon('heroicon-o-calendar')
->color('success'), ->color('success')
->url(url('/app/calendar-board')),
Stat::make(__('Fișe în lucru'), $activeWos)
->description(__('În serviciu sau așteaptă piese'))
->icon('heroicon-o-wrench-screwdriver')
->color($activeWos > 0 ? 'primary' : 'success')
->url(url('/app/work-orders') . '?tableFilters[status][value]=in_work'),
Stat::make(__('Fișe gata predare'), $readyWos)
->description(__('Așteaptă client'))
->icon('heroicon-o-check-circle')
->color($readyWos > 0 ? 'success' : 'gray')
->url(url('/app/work-orders') . '?tableFilters[status][value]=ready'),
]; ];
} }
} }
+6
View File
@@ -276,6 +276,7 @@
"Avans achitat": "Advance paid", "Avans achitat": "Advance paid",
"Avg zile": "Avg days", "Avg zile": "Avg days",
"Azi": "Today", "Azi": "Today",
"Așteaptă client": "Awaiting customer",
"Așteaptă piese": "Awaiting parts", "Așteaptă piese": "Awaiting parts",
"Așteaptă piesele": "Awaiting parts", "Așteaptă piesele": "Awaiting parts",
"Aștept aprobare client": "Awaiting client approval", "Aștept aprobare client": "Awaiting client approval",
@@ -854,8 +855,11 @@
"Fișa nu are client asociat.": "This work order has no client attached.", "Fișa nu are client asociat.": "This work order has no client attached.",
"Fișe": "Orders", "Fișe": "Orders",
"Fișe active": "Active orders", "Fișe active": "Active orders",
"Fișe cu plată în așteptare": "Work orders with pending payment",
"Fișe deschise": "Open orders", "Fișe deschise": "Open orders",
"Fișe gata predare": "Ready for pickup",
"Fișe lucru": "Work orders", "Fișe lucru": "Work orders",
"Fișe în lucru": "Active work orders",
"Fișe închise": "Closed orders", "Fișe închise": "Closed orders",
"Fișier": "File", "Fișier": "File",
"Fișier Excel (.xlsx) sau CSV *": "Excel file (.xlsx) or CSV *", "Fișier Excel (.xlsx) sau CSV *": "Excel file (.xlsx) or CSV *",
@@ -2192,6 +2196,7 @@
"Vezi profit & pierdere (P&L)": "View P&L", "Vezi profit & pierdere (P&L)": "View P&L",
"Vezi propriul salariu": "View own salary", "Vezi propriul salariu": "View own salary",
"Vezi rapoarte & analitică": "View reports & analytics", "Vezi rapoarte & analitică": "View reports & analytics",
"Vezi raportul financiar": "Open financial report",
"Vezi salariile tuturor": "View all salaries", "Vezi salariile tuturor": "View all salaries",
"Vezi stoc (piese)": "View stock (parts)", "Vezi stoc (piese)": "View stock (parts)",
"Vezi toate fișele": "View all orders", "Vezi toate fișele": "View all orders",
@@ -2525,6 +2530,7 @@
"În lucru": "In progress", "În lucru": "In progress",
"În pipeline": "In pipeline", "În pipeline": "In pipeline",
"În procesare": "Processing", "În procesare": "Processing",
"În serviciu sau așteaptă piese": "In service or waiting for parts",
"În trial": "In trial", "În trial": "In trial",
"Înainte": "Back", "Înainte": "Back",
"Înapoi": "Back", "Înapoi": "Back",
+6
View File
@@ -276,6 +276,7 @@
"Avans achitat": "Аванс оплачен", "Avans achitat": "Аванс оплачен",
"Avg zile": "Ср. дней", "Avg zile": "Ср. дней",
"Azi": "Сегодня", "Azi": "Сегодня",
"Așteaptă client": "Ожидают клиента",
"Așteaptă piese": "Ждёт запчасти", "Așteaptă piese": "Ждёт запчасти",
"Așteaptă piesele": "Ждёт запчасти", "Așteaptă piesele": "Ждёт запчасти",
"Aștept aprobare client": "Ожидание одобрения клиента", "Aștept aprobare client": "Ожидание одобрения клиента",
@@ -854,8 +855,11 @@
"Fișa nu are client asociat.": "К заказ-наряду не привязан клиент.", "Fișa nu are client asociat.": "К заказ-наряду не привязан клиент.",
"Fișe": "Наряды", "Fișe": "Наряды",
"Fișe active": "Активные наряды", "Fișe active": "Активные наряды",
"Fișe cu plată în așteptare": "Заказы с ожидающей оплатой",
"Fișe deschise": "Открытые наряды", "Fișe deschise": "Открытые наряды",
"Fișe gata predare": "Готовы к выдаче",
"Fișe lucru": "Рабочие листы", "Fișe lucru": "Рабочие листы",
"Fișe în lucru": "Заказы в работе",
"Fișe închise": "Закрытые наряды", "Fișe închise": "Закрытые наряды",
"Fișier": "Файл", "Fișier": "Файл",
"Fișier Excel (.xlsx) sau CSV *": "Файл Excel (.xlsx) или CSV *", "Fișier Excel (.xlsx) sau CSV *": "Файл Excel (.xlsx) или CSV *",
@@ -2192,6 +2196,7 @@
"Vezi profit & pierdere (P&L)": "Просмотр P&L", "Vezi profit & pierdere (P&L)": "Просмотр P&L",
"Vezi propriul salariu": "Просмотр своей зарплаты", "Vezi propriul salariu": "Просмотр своей зарплаты",
"Vezi rapoarte & analitică": "Просмотр отчётов и аналитики", "Vezi rapoarte & analitică": "Просмотр отчётов и аналитики",
"Vezi raportul financiar": "Открыть финансовый отчёт",
"Vezi salariile tuturor": "Просмотр зарплат всех", "Vezi salariile tuturor": "Просмотр зарплат всех",
"Vezi stoc (piese)": "Просмотр склада (запчасти)", "Vezi stoc (piese)": "Просмотр склада (запчасти)",
"Vezi toate fișele": "Просмотр всех нарядов", "Vezi toate fișele": "Просмотр всех нарядов",
@@ -2525,6 +2530,7 @@
"În lucru": "В работе", "În lucru": "В работе",
"În pipeline": "В воронке", "În pipeline": "В воронке",
"În procesare": "В обработке", "În procesare": "В обработке",
"În serviciu sau așteaptă piese": "В работе или ожидание запчастей",
"În trial": "В триале", "În trial": "В триале",
"Înainte": "Назад", "Înainte": "Назад",
"Înapoi": "Назад", "Înapoi": "Назад",