feat(work-orders): new Mitchell1-style dashboard view (Phase 1 — layout + tabs)

Custom Filament Page at /app/work-orders/{id}/dashboard renders a
Mitchell1-inspired 3-column layout:
  - Top bar: WO#, status badge, actions (Edit / Tracking link)
  - Meta header row: creation / opened / ETA / responsible / urgency /
    paid amount (6 cells)
  - Left sidebar (300px): Client card (avatar, phone, email, status
    tag, 3-stat grid: visits/total/debt) + Vehicle card (photo, plate,
    VIN, mileage, engine, gearbox) + Repair history (latest 5 for this
    vehicle, links to dashboards)
  - Middle: tab bar (Lucrări/Piese/Diagnostic/Foto/Documente/Note)
    with Alpine-driven switching. Works & Parts show the tables read-
    only; add/edit still goes through existing EditWorkOrder Filament
    resource. Photos tab shows gallery from spatie/media-library.
  - Right (300px): Finance summary card (works cost, parts cost,
    discount, total, paid, balance) + placeholder for Timeline+Chat
    (Phase 2)
  - Responsive: right column collapses <1280px, left <900px.

'Vizualizare dashboard' button added on top of the existing
EditWorkOrder page so users can switch between edit form and info-
dense dashboard.

Fixed getSlug() signature (must match parent with ?Panel $panel).

+14 translations. All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-05 18:48:52 +00:00
parent 6de9c615f7
commit fa8704f8b6
5 changed files with 818 additions and 2 deletions
@@ -0,0 +1,92 @@
<?php
namespace App\Filament\Tenant\Pages;
use App\Models\Tenant\WorkOrder;
use Filament\Pages\Page;
/**
* Mitchell1-style 3-column dashboard view for a single WorkOrder.
* MVP Phase 1: layout + tabs (Works/Parts/Diagnostic/Photos/Docs/Notes).
* Editing still goes through /app/work-orders/{id}/edit for now.
*/
class WorkOrderDashboard extends Page
{
protected static string|\BackedEnum|null $navigationIcon = null;
protected static bool $shouldRegisterNavigation = false;
protected string $view = 'filament.tenant.pages.work-order-dashboard';
public WorkOrder $record;
public string $activeTab = 'works';
public static function getSlug(?\Filament\Panel $panel = null): string
{
return 'work-orders/{record}/dashboard';
}
public static function getRoutePath(?\Filament\Panel $panel = null): string
{
return 'work-orders/{record}/dashboard';
}
public function mount(int|string $record): void
{
$this->record = WorkOrder::with([
'client', 'vehicle', 'master',
'works.labor', 'works.master',
'parts.part', 'parts.batch',
'payments.user',
'subcontractJobs.subcontractor',
])->findOrFail($record);
}
public function setTab(string $tab): void
{
$this->activeTab = $tab;
}
public function getTitle(): string|\Illuminate\Contracts\Support\Htmlable
{
return __('Fișă') . ' ' . ($this->record->number ?? '');
}
public function getHeading(): string|\Illuminate\Contracts\Support\Htmlable
{
return '';
}
/** Latest 5 WOs for the vehicle (repair history sidebar). */
public function getRepairHistory(): \Illuminate\Support\Collection
{
if (! $this->record->vehicle_id) return collect();
return WorkOrder::where('vehicle_id', $this->record->vehicle_id)
->where('id', '!=', $this->record->id)
->orderByDesc('opened_at')
->limit(5)
->get(['id', 'number', 'opened_at', 'total', 'status']);
}
/** Aggregated finance data — used by right panel in future phases. */
public function getFinance(): array
{
$r = $this->record;
$worksSum = (float) $r->works->sum('total');
$partsSum = (float) $r->parts->sum('total');
$subtotal = $worksSum + $partsSum;
$discount = (float) $r->discount;
$total = max(0, $subtotal - $discount);
$paid = $r->paidAmount();
return [
'works_sum' => $worksSum,
'parts_sum' => $partsSum,
'subtotal' => $subtotal,
'discount' => $discount,
'total' => $total,
'paid' => $paid,
'balance' => max(0, $total - $paid),
];
}
}
@@ -32,6 +32,11 @@ class EditWorkOrder extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\Action::make('dashboard')
->label(__('Vizualizare dashboard'))
->icon('heroicon-m-squares-2x2')
->color('info')
->url(fn () => route('filament.tenant.pages.work-order-dashboard', ['record' => $this->record->id])),
Actions\Action::make('apply_template')
->label(__('Aplică șablon'))
->icon('heroicon-m-clipboard-document-list')