fa8704f8b6
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>
696 lines
30 KiB
PHP
696 lines
30 KiB
PHP
<x-filament-panels::page>
|
||
@php
|
||
/** @var \App\Models\Tenant\WorkOrder $wo */
|
||
$wo = $this->record;
|
||
$finance = $this->getFinance();
|
||
$history = $this->getRepairHistory();
|
||
$tabs = [
|
||
'works' => ['label' => __('Lucrări'), 'count' => $wo->works->count()],
|
||
'parts' => ['label' => __('Piese'), 'count' => $wo->parts->count()],
|
||
'diag' => ['label' => __('Diagnostic'), 'count' => null],
|
||
'photos' => ['label' => __('Foto'), 'count' => $wo->getMedia('photos')->count() ?? 0],
|
||
'docs' => ['label' => __('Documente'), 'count' => null],
|
||
'notes' => ['label' => __('Note'), 'count' => null],
|
||
];
|
||
$statusClass = match ($wo->status) {
|
||
'in_work' => 'wd-status-active',
|
||
'awaiting_parts' => 'wd-status-warn',
|
||
'ready' => 'wd-status-ready',
|
||
'done' => 'wd-status-done',
|
||
'cancelled' => 'wd-status-cancel',
|
||
default => 'wd-status-new',
|
||
};
|
||
@endphp
|
||
|
||
<style>
|
||
/* ══ WORK-ORDER DASHBOARD (Mitchell1-style) ══ */
|
||
:root {
|
||
--wd-bg: #F7F7F5;
|
||
--wd-surface: #FFFFFF;
|
||
--wd-border: #E5E7EB;
|
||
--wd-border-md: #D1D5DB;
|
||
--wd-text: #111827;
|
||
--wd-text-2: #4B5563;
|
||
--wd-text-3: #9CA3AF;
|
||
--wd-blue: #2563EB;
|
||
--wd-blue-bg: #EFF6FF;
|
||
--wd-green: #16A34A;
|
||
--wd-green-bg: #DCFCE7;
|
||
--wd-amber: #D97706;
|
||
--wd-amber-bg: #FEF3C7;
|
||
--wd-red: #DC2626;
|
||
--wd-red-bg: #FEE2E2;
|
||
--wd-slate: #64748B;
|
||
}
|
||
.dark {
|
||
--wd-bg: #0F172A;
|
||
--wd-surface: #1E293B;
|
||
--wd-border: #334155;
|
||
--wd-border-md: #475569;
|
||
--wd-text: #F1F5F9;
|
||
--wd-text-2: #CBD5E1;
|
||
--wd-text-3: #94A3B8;
|
||
}
|
||
|
||
/* Break out of Filament content wrapper */
|
||
.fi-main-ctn:has(.wd-shell) { padding: 0 !important; }
|
||
.fi-main:has(.wd-shell) { padding: 0 !important; }
|
||
.fi-page:has(.wd-shell) > div { padding: 0 !important; gap: 0 !important; }
|
||
.fi-page:has(.wd-shell) .fi-header { display: none !important; }
|
||
|
||
.wd-shell {
|
||
background: var(--wd-bg);
|
||
color: var(--wd-text);
|
||
min-height: calc(100vh - 64px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* ── TOP BAR ── */
|
||
.wd-topbar {
|
||
background: var(--wd-surface);
|
||
border-bottom: 1px solid var(--wd-border);
|
||
padding: 10px 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
.wd-wo-title { font-size: 15px; font-weight: 700; letter-spacing: -.2px; }
|
||
.wd-status {
|
||
padding: 4px 10px;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: .3px;
|
||
cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
}
|
||
.wd-status-active { background: #DBEAFE; color: #1E40AF; }
|
||
.wd-status-warn { background: var(--wd-amber-bg); color: var(--wd-amber); }
|
||
.wd-status-ready { background: var(--wd-green-bg); color: #166534; }
|
||
.wd-status-done { background: #F3F4F6; color: #6B7280; }
|
||
.wd-status-cancel { background: var(--wd-red-bg); color: var(--wd-red); }
|
||
.wd-status-new { background: #EDE9FE; color: #6D28D9; }
|
||
|
||
.wd-btn {
|
||
padding: 7px 12px;
|
||
border: 1px solid var(--wd-border-md);
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
background: var(--wd-surface);
|
||
color: var(--wd-text);
|
||
cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
text-decoration: none;
|
||
}
|
||
.wd-btn:hover { background: var(--wd-bg); }
|
||
.wd-btn-primary { background: var(--wd-green); color: #fff; border-color: var(--wd-green); }
|
||
.wd-btn-primary:hover { background: #15803d; }
|
||
.wd-btn-secondary { background: var(--wd-text); color: #fff; border-color: var(--wd-text); }
|
||
.wd-btn-secondary:hover { background: #000; }
|
||
.wd-topbar-actions { margin-left: auto; display: flex; gap: 6px; }
|
||
|
||
/* ── META HEADER ROW ── */
|
||
.wd-meta {
|
||
background: var(--wd-surface);
|
||
border-bottom: 1px solid var(--wd-border);
|
||
padding: 10px 20px;
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.wd-meta-item { display: flex; flex-direction: column; gap: 3px; }
|
||
.wd-meta-lbl { font-size: 10px; color: var(--wd-text-3); text-transform: uppercase; letter-spacing: .4px; font-weight: 600; }
|
||
.wd-meta-val { font-size: 13px; color: var(--wd-text); }
|
||
.wd-meta-val.person { display: flex; align-items: center; gap: 6px; }
|
||
.wd-avatar {
|
||
width: 22px; height: 22px; border-radius: 50%;
|
||
background: var(--wd-blue-bg); color: var(--wd-blue);
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
font-size: 10px; font-weight: 700;
|
||
}
|
||
.wd-priority-dot {
|
||
display: inline-block; width: 8px; height: 8px; border-radius: 50%;
|
||
margin-right: 5px;
|
||
}
|
||
.wd-priority-normal { background: var(--wd-amber); }
|
||
.wd-priority-urgent, .wd-priority-express { background: var(--wd-red); }
|
||
|
||
/* ── 3-COLUMN GRID ── */
|
||
.wd-body {
|
||
flex: 1;
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr 300px;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
@media (max-width: 1280px) {
|
||
.wd-body { grid-template-columns: 260px 1fr; }
|
||
.wd-col-right { display: none; }
|
||
}
|
||
@media (max-width: 900px) {
|
||
.wd-body { grid-template-columns: 1fr; }
|
||
.wd-col-left { display: none; }
|
||
}
|
||
|
||
.wd-col-left, .wd-col-mid, .wd-col-right {
|
||
display: flex; flex-direction: column; gap: 12px;
|
||
overflow-y: auto;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* ── CARDS (common) ── */
|
||
.wd-card {
|
||
background: var(--wd-surface);
|
||
border: 1px solid var(--wd-border);
|
||
border-radius: 10px;
|
||
padding: 14px;
|
||
}
|
||
.wd-card-hd {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
}
|
||
.wd-card-hd h3 { font-size: 13px; font-weight: 600; margin: 0; }
|
||
.wd-card-hd a { font-size: 11px; color: var(--wd-blue); text-decoration: none; }
|
||
.wd-card-hd a:hover { text-decoration: underline; }
|
||
|
||
/* ── LEFT SIDEBAR ── */
|
||
.wd-person-row {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.wd-person-avatar {
|
||
width: 40px; height: 40px; border-radius: 50%;
|
||
background: var(--wd-bg); border: 1px solid var(--wd-border);
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
font-size: 15px; font-weight: 700; color: var(--wd-text-2);
|
||
flex-shrink: 0;
|
||
}
|
||
.wd-person-name { font-size: 13px; font-weight: 600; line-height: 1.2; }
|
||
.wd-person-phone { font-size: 11px; color: var(--wd-blue); display: block; margin-top: 2px; }
|
||
.wd-person-email { font-size: 11px; color: var(--wd-text-2); display: block; margin-top: 1px; }
|
||
.wd-tag {
|
||
display: inline-block;
|
||
background: var(--wd-blue-bg); color: var(--wd-blue);
|
||
padding: 2px 8px; border-radius: 4px;
|
||
font-size: 10px; font-weight: 600;
|
||
margin-top: 6px;
|
||
}
|
||
.wd-stat-grid {
|
||
display: grid; grid-template-columns: repeat(3, 1fr);
|
||
gap: 6px; margin-top: 10px;
|
||
}
|
||
.wd-stat-cell {
|
||
text-align: center;
|
||
background: var(--wd-bg);
|
||
border-radius: 6px;
|
||
padding: 6px 4px;
|
||
}
|
||
.wd-stat-cell .l { font-size: 9px; color: var(--wd-text-3); text-transform: uppercase; }
|
||
.wd-stat-cell .v { font-size: 12px; font-weight: 700; margin-top: 2px; }
|
||
.wd-stat-cell .v.money { color: var(--wd-green); }
|
||
.wd-stat-cell .v.debt { color: var(--wd-red); }
|
||
|
||
.wd-vehicle-photo {
|
||
width: 100%; aspect-ratio: 16/10; object-fit: cover;
|
||
background: var(--wd-bg);
|
||
border-radius: 6px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.wd-veh-title { font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||
.wd-veh-facts {
|
||
display: grid; grid-template-columns: auto 1fr;
|
||
gap: 4px 10px; font-size: 11px;
|
||
margin-top: 6px;
|
||
}
|
||
.wd-veh-facts dt { color: var(--wd-text-3); }
|
||
.wd-veh-facts dd { color: var(--wd-text); font-weight: 500; margin: 0; }
|
||
|
||
.wd-history-item {
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid var(--wd-border);
|
||
font-size: 11px;
|
||
display: flex; justify-content: space-between; gap: 8px;
|
||
}
|
||
.wd-history-item:last-child { border-bottom: 0; }
|
||
.wd-history-item .h-date { color: var(--wd-text-3); }
|
||
.wd-history-item .h-total { font-weight: 600; }
|
||
.wd-history-item a { color: var(--wd-text); text-decoration: none; }
|
||
.wd-history-item a:hover { color: var(--wd-blue); }
|
||
|
||
/* ── MIDDLE: TABS ── */
|
||
.wd-tabs-bar {
|
||
background: var(--wd-surface);
|
||
border: 1px solid var(--wd-border);
|
||
border-radius: 10px 10px 0 0;
|
||
border-bottom: 0;
|
||
display: flex;
|
||
padding: 0 8px;
|
||
overflow-x: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
.wd-tab {
|
||
padding: 12px 16px;
|
||
border-bottom: 2px solid transparent;
|
||
color: var(--wd-text-2);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
text-transform: uppercase;
|
||
letter-spacing: .3px;
|
||
background: none;
|
||
border-left: 0; border-right: 0; border-top: 0;
|
||
white-space: nowrap;
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
}
|
||
.wd-tab.active { color: var(--wd-blue); border-bottom-color: var(--wd-blue); font-weight: 600; }
|
||
.wd-tab-count {
|
||
background: var(--wd-bg); color: var(--wd-text-2);
|
||
padding: 1px 6px; border-radius: 8px;
|
||
font-size: 10px; font-weight: 600;
|
||
}
|
||
|
||
.wd-tab-content {
|
||
background: var(--wd-surface);
|
||
border: 1px solid var(--wd-border);
|
||
border-top: 0;
|
||
border-radius: 0 0 10px 10px;
|
||
padding: 16px;
|
||
flex: 1;
|
||
overflow: auto;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* Tables inside tabs */
|
||
.wd-tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||
.wd-tbl th, .wd-tbl td {
|
||
padding: 8px 10px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--wd-border);
|
||
}
|
||
.wd-tbl th {
|
||
font-size: 10px; color: var(--wd-text-3);
|
||
text-transform: uppercase; letter-spacing: .3px;
|
||
font-weight: 600; background: var(--wd-bg);
|
||
}
|
||
.wd-tbl td.right, .wd-tbl th.right { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.wd-tbl tr:last-child td { border-bottom: 0; }
|
||
.wd-tbl tr:hover td { background: rgba(37,99,235,.03); }
|
||
|
||
.wd-mini-badge {
|
||
display: inline-block;
|
||
padding: 2px 7px; border-radius: 4px;
|
||
font-size: 10px; font-weight: 600;
|
||
text-transform: uppercase;
|
||
}
|
||
.wd-mini-todo { background: #F3F4F6; color: #6B7280; }
|
||
.wd-mini-progress { background: var(--wd-blue-bg); color: var(--wd-blue); }
|
||
.wd-mini-done { background: var(--wd-green-bg); color: #166534; }
|
||
|
||
.wd-empty { padding: 40px 20px; text-align: center; color: var(--wd-text-3); font-size: 12px; }
|
||
.wd-empty-add { margin-top: 8px; }
|
||
|
||
.wd-footer-row {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
padding: 10px 4px; margin-top: 8px;
|
||
font-size: 12px;
|
||
border-top: 1px solid var(--wd-border);
|
||
}
|
||
.wd-footer-total { font-weight: 700; font-size: 13px; }
|
||
|
||
/* ── RIGHT SIDEBAR (placeholder for phase 2) ── */
|
||
.wd-placeholder {
|
||
padding: 20px;
|
||
text-align: center;
|
||
color: var(--wd-text-3);
|
||
font-size: 11px;
|
||
font-style: italic;
|
||
}
|
||
</style>
|
||
|
||
<div class="wd-shell" x-data="{ tab: @entangle('activeTab').live }">
|
||
|
||
{{-- ── TOP BAR ── --}}
|
||
<div class="wd-topbar">
|
||
<span class="wd-wo-title">{{ __('Fișă') }} {{ $wo->number }}</span>
|
||
<span class="wd-status {{ $statusClass }}">
|
||
{{ __(\App\Models\Tenant\WorkOrder::STATUSES[$wo->status] ?? $wo->status) }}
|
||
<span style="opacity:.6;">▾</span>
|
||
</span>
|
||
<div class="wd-topbar-actions">
|
||
<a class="wd-btn" href="{{ route('filament.tenant.resources.work-orders.edit', ['record' => $wo->id]) }}">
|
||
✎ {{ __('Editează') }}
|
||
</a>
|
||
@if ($wo->tracking_token)
|
||
<a class="wd-btn" target="_blank" href="{{ $wo->trackingUrl() }}">
|
||
🔗 {{ __('Link tracking') }}
|
||
</a>
|
||
@endif
|
||
<button type="button" class="wd-btn wd-btn-primary">{{ __('Salvează') }}</button>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── META HEADER ── --}}
|
||
<div class="wd-meta">
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('Data creării') }}</span>
|
||
<span class="wd-meta-val">{{ $wo->created_at?->format('d.m.Y H:i') ?? '—' }}</span>
|
||
</div>
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('Deschis') }}</span>
|
||
<span class="wd-meta-val">{{ $wo->opened_at?->format('d.m.Y H:i') ?? '—' }}</span>
|
||
</div>
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('ETA') }}</span>
|
||
<span class="wd-meta-val">{{ $wo->eta_at?->format('d.m.Y H:i') ?? '—' }}</span>
|
||
</div>
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('Responsabil') }}</span>
|
||
<span class="wd-meta-val person">
|
||
@if ($wo->master)
|
||
<span class="wd-avatar">{{ mb_strtoupper(mb_substr($wo->master->name, 0, 2)) }}</span>
|
||
{{ $wo->master->name }}
|
||
@else — @endif
|
||
</span>
|
||
</div>
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('Urgență') }}</span>
|
||
<span class="wd-meta-val">
|
||
<span class="wd-priority-dot wd-priority-{{ $wo->urgency ?? 'normal' }}"></span>
|
||
{{ __(\App\Models\Tenant\PricingCoefficient::URGENCY[$wo->urgency ?? 'normal'] ?? 'Normal') }}
|
||
</span>
|
||
</div>
|
||
<div class="wd-meta-item">
|
||
<span class="wd-meta-lbl">{{ __('Data plății') }}</span>
|
||
<span class="wd-meta-val">{{ $wo->paidAmount() > 0 ? number_format($wo->paidAmount(), 2, '.', ' ') . ' MDL' : '—' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── 3-COLUMN BODY ── --}}
|
||
<div class="wd-body">
|
||
{{-- ═══ LEFT SIDEBAR ═══ --}}
|
||
<div class="wd-col-left">
|
||
{{-- CLIENT CARD --}}
|
||
<div class="wd-card">
|
||
<div class="wd-card-hd">
|
||
<h3>{{ __('Client') }}</h3>
|
||
@if ($wo->client)
|
||
<a href="{{ route('filament.tenant.resources.clients.edit', ['record' => $wo->client_id]) }}">{{ __('Editează') }}</a>
|
||
@endif
|
||
</div>
|
||
@if ($wo->client)
|
||
<div class="wd-person-row">
|
||
<div class="wd-person-avatar">{{ mb_strtoupper(mb_substr($wo->client->name, 0, 1)) }}</div>
|
||
<div style="flex:1;min-width:0;">
|
||
<div class="wd-person-name">{{ $wo->client->name }}</div>
|
||
@if ($wo->client->phone)
|
||
<a href="tel:{{ $wo->client->phone }}" class="wd-person-phone">📞 {{ $wo->client->phone }}</a>
|
||
@endif
|
||
@if ($wo->client->email)
|
||
<span class="wd-person-email">{{ $wo->client->email }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@if (! empty($wo->client->status))
|
||
<span class="wd-tag">{{ __(ucfirst($wo->client->status)) }}</span>
|
||
@endif
|
||
@php
|
||
$totalOrders = \App\Models\Tenant\WorkOrder::where('client_id', $wo->client_id)->count();
|
||
$totalSpent = (float) \App\Models\Tenant\WorkOrder::where('client_id', $wo->client_id)->sum('total');
|
||
$debt = (float) $wo->client->balance;
|
||
@endphp
|
||
<div class="wd-stat-grid">
|
||
<div class="wd-stat-cell">
|
||
<div class="l">{{ __('Vizite') }}</div>
|
||
<div class="v">{{ $totalOrders }}</div>
|
||
</div>
|
||
<div class="wd-stat-cell">
|
||
<div class="l">{{ __('Total') }}</div>
|
||
<div class="v money">{{ number_format($totalSpent, 0, '.', ' ') }}</div>
|
||
</div>
|
||
<div class="wd-stat-cell">
|
||
<div class="l">{{ __('Datorie') }}</div>
|
||
<div class="v {{ $debt > 0 ? 'debt' : '' }}">{{ number_format($debt, 0, '.', ' ') }}</div>
|
||
</div>
|
||
</div>
|
||
@else
|
||
<div class="wd-empty">{{ __('Fără client') }}</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- VEHICLE CARD --}}
|
||
<div class="wd-card">
|
||
<div class="wd-card-hd">
|
||
<h3>{{ __('Automobil') }}</h3>
|
||
@if ($wo->vehicle)
|
||
<a href="{{ route('filament.tenant.resources.vehicles.edit', ['record' => $wo->vehicle_id]) }}">{{ __('Editează') }}</a>
|
||
@endif
|
||
</div>
|
||
@if ($wo->vehicle)
|
||
@php
|
||
$photoUrl = $wo->vehicle->getFirstMediaUrl('photos') ?? null;
|
||
@endphp
|
||
@if ($photoUrl)
|
||
<img class="wd-vehicle-photo" src="{{ $photoUrl }}" alt="{{ $wo->vehicle->make }}">
|
||
@else
|
||
<div class="wd-vehicle-photo" style="display:flex;align-items:center;justify-content:center;color:var(--wd-text-3);font-size:24px;">🚗</div>
|
||
@endif
|
||
<div class="wd-veh-title">{{ $wo->vehicle->make }} {{ $wo->vehicle->model }} {{ $wo->vehicle->year }}</div>
|
||
<dl class="wd-veh-facts">
|
||
@if ($wo->vehicle->plate)
|
||
<dt>{{ __('Nr.') }}</dt><dd>{{ $wo->vehicle->plate }}</dd>
|
||
@endif
|
||
@if ($wo->vehicle->vin)
|
||
<dt>VIN</dt><dd style="font-family:monospace;font-size:10px;">{{ $wo->vehicle->vin }}</dd>
|
||
@endif
|
||
@if ($wo->vehicle->mileage)
|
||
<dt>{{ __('Km') }}</dt><dd>{{ number_format($wo->vehicle->mileage, 0, '.', ' ') }}</dd>
|
||
@endif
|
||
@if ($wo->vehicle->engine)
|
||
<dt>{{ __('Motor') }}</dt><dd>{{ $wo->vehicle->engine }}</dd>
|
||
@endif
|
||
@if ($wo->vehicle->gearbox)
|
||
<dt>{{ __('Cutie') }}</dt><dd>{{ $wo->vehicle->gearbox }}</dd>
|
||
@endif
|
||
</dl>
|
||
@else
|
||
<div class="wd-empty">{{ __('Fără mașină') }}</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- REPAIR HISTORY --}}
|
||
<div class="wd-card">
|
||
<div class="wd-card-hd">
|
||
<h3>{{ __('Istoric reparații') }}</h3>
|
||
</div>
|
||
@forelse ($history as $h)
|
||
<div class="wd-history-item">
|
||
<a href="{{ route('filament.tenant.pages.work-order-dashboard', ['record' => $h->id]) }}">
|
||
<span class="h-date">{{ $h->opened_at?->format('d.m.Y') }}</span>
|
||
<span>{{ $h->number }}</span>
|
||
</a>
|
||
<span class="h-total">{{ number_format($h->total, 0, '.', ' ') }} MDL</span>
|
||
</div>
|
||
@empty
|
||
<div class="wd-empty" style="padding:16px 0;">{{ __('Prima vizită') }}</div>
|
||
@endforelse
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ═══ MIDDLE: TABS ═══ --}}
|
||
<div class="wd-col-mid">
|
||
<div class="wd-tabs-bar">
|
||
@foreach ($tabs as $key => $t)
|
||
<button type="button" class="wd-tab" :class="tab === '{{ $key }}' && 'active'" @click="tab = '{{ $key }}'">
|
||
{{ $t['label'] }}
|
||
@if (! is_null($t['count']))
|
||
<span class="wd-tab-count">{{ $t['count'] }}</span>
|
||
@endif
|
||
</button>
|
||
@endforeach
|
||
</div>
|
||
<div class="wd-tab-content">
|
||
{{-- ── WORKS TAB ── --}}
|
||
<div x-show="tab === 'works'" x-cloak>
|
||
@if ($wo->works->isNotEmpty())
|
||
<table class="wd-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>{{ __('Manoperă') }}</th>
|
||
<th class="right">{{ __('Ore') }}</th>
|
||
<th>{{ __('Mecanic') }}</th>
|
||
<th class="right">{{ __('Preț/h') }}</th>
|
||
<th class="right">{{ __('Total') }}</th>
|
||
<th>{{ __('Status') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($wo->works as $i => $w)
|
||
<tr>
|
||
<td>{{ $i + 1 }}</td>
|
||
<td>{{ $w->labor?->label() ?: $w->name }}</td>
|
||
<td class="right">{{ rtrim(rtrim(number_format($w->hours, 2), '0'), '.') }}</td>
|
||
<td>{{ $w->master?->name ?? '—' }}</td>
|
||
<td class="right">{{ number_format($w->price_per_hour, 2, '.', ' ') }}</td>
|
||
<td class="right">{{ number_format($w->total, 2, '.', ' ') }}</td>
|
||
<td>
|
||
<span class="wd-mini-badge wd-mini-{{ $w->status === 'done' ? 'done' : ($w->status === 'in_progress' ? 'progress' : 'todo') }}">
|
||
{{ __(\App\Models\Tenant\WorkOrderWork::STATUSES[$w->status] ?? $w->status) }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
<div class="wd-footer-row">
|
||
<a class="wd-btn" href="{{ route('filament.tenant.resources.work-orders.edit', ['record' => $wo->id, 'relation' => 0]) }}">+ {{ __('Adaugă manoperă') }}</a>
|
||
<span class="wd-footer-total">{{ __('Total manopere:') }} {{ number_format($finance['works_sum'], 2, '.', ' ') }} MDL</span>
|
||
</div>
|
||
@else
|
||
<div class="wd-empty">
|
||
{{ __('Nicio manoperă efectuată.') }}
|
||
<div class="wd-empty-add"><a class="wd-btn" href="{{ route('filament.tenant.resources.work-orders.edit', ['record' => $wo->id, 'relation' => 0]) }}">+ {{ __('Adaugă manoperă') }}</a></div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- ── PARTS TAB ── --}}
|
||
<div x-show="tab === 'parts'" x-cloak>
|
||
@if ($wo->parts->isNotEmpty())
|
||
<table class="wd-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>{{ __('Cod') }}</th>
|
||
<th>{{ __('Denumire') }}</th>
|
||
<th>{{ __('Brand') }}</th>
|
||
<th class="right">{{ __('Cant.') }}</th>
|
||
<th class="right">{{ __('Preț') }}</th>
|
||
<th class="right">{{ __('Total') }}</th>
|
||
<th>{{ __('Status') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($wo->parts as $p)
|
||
<tr>
|
||
<td style="font-family:monospace;font-size:11px;">{{ $p->article ?? '—' }}</td>
|
||
<td>{{ $p->name }}</td>
|
||
<td>{{ $p->brand ?? '—' }}</td>
|
||
<td class="right">{{ rtrim(rtrim(number_format($p->qty, 2), '0'), '.') }} {{ $p->unitLabel() ?: '' }}</td>
|
||
<td class="right">{{ number_format($p->sell_price, 2, '.', ' ') }}</td>
|
||
<td class="right">{{ number_format($p->total, 2, '.', ' ') }}</td>
|
||
<td>
|
||
<span class="wd-mini-badge wd-mini-{{ $p->status === 'installed' ? 'done' : ($p->status === 'delivered' ? 'progress' : 'todo') }}">
|
||
{{ __(\App\Models\Tenant\WorkOrderPart::STATUSES[$p->status] ?? $p->status) }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
<div class="wd-footer-row">
|
||
<a class="wd-btn" href="{{ route('filament.tenant.resources.work-orders.edit', ['record' => $wo->id, 'relation' => 1]) }}">+ {{ __('Adaugă piesă') }}</a>
|
||
<span class="wd-footer-total">{{ __('Total piese:') }} {{ number_format($finance['parts_sum'], 2, '.', ' ') }} MDL</span>
|
||
</div>
|
||
@else
|
||
<div class="wd-empty">
|
||
{{ __('Nicio piesă montată.') }}
|
||
<div class="wd-empty-add"><a class="wd-btn" href="{{ route('filament.tenant.resources.work-orders.edit', ['record' => $wo->id, 'relation' => 1]) }}">+ {{ __('Adaugă piesă') }}</a></div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- ── DIAGNOSTIC TAB ── --}}
|
||
<div x-show="tab === 'diag'" x-cloak>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||
<div>
|
||
<h4 style="font-size:11px;color:var(--wd-text-3);text-transform:uppercase;margin:0 0 8px;">{{ __('Plângere client') }}</h4>
|
||
<div style="background:var(--wd-bg);padding:10px 12px;border-radius:6px;font-size:12px;white-space:pre-wrap;min-height:60px;">{{ $wo->complaint ?: '—' }}</div>
|
||
</div>
|
||
<div>
|
||
<h4 style="font-size:11px;color:var(--wd-text-3);text-transform:uppercase;margin:0 0 8px;">{{ __('Diagnostic') }}</h4>
|
||
<div style="background:var(--wd-bg);padding:10px 12px;border-radius:6px;font-size:12px;white-space:pre-wrap;min-height:60px;">{{ $wo->diagnosis ?: '—' }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── PHOTOS TAB ── --}}
|
||
<div x-show="tab === 'photos'" x-cloak>
|
||
@php $photos = $wo->getMedia('photos'); @endphp
|
||
@if ($photos->isNotEmpty())
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;">
|
||
@foreach ($photos as $ph)
|
||
<a href="{{ $ph->getFullUrl() }}" target="_blank">
|
||
<img src="{{ $ph->getFullUrl() }}" style="width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:6px;border:1px solid var(--wd-border);">
|
||
</a>
|
||
@endforeach
|
||
</div>
|
||
@else
|
||
<div class="wd-empty">{{ __('Nicio fotografie') }}</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- ── DOCUMENTS TAB ── --}}
|
||
<div x-show="tab === 'docs'" x-cloak>
|
||
<div class="wd-empty">{{ __('Nicio factură emisă încă') }}</div>
|
||
</div>
|
||
|
||
{{-- ── NOTES TAB ── --}}
|
||
<div x-show="tab === 'notes'" x-cloak>
|
||
<div style="background:var(--wd-bg);padding:10px 12px;border-radius:6px;font-size:12px;white-space:pre-wrap;min-height:80px;">{{ $wo->notes ?: __('Nicio notă internă.') }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ═══ RIGHT COLUMN (placeholder — Phase 2) ═══ --}}
|
||
<div class="wd-col-right">
|
||
<div class="wd-card">
|
||
<div class="wd-card-hd">
|
||
<h3>{{ __('Finanțe') }}</h3>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:6px;font-size:12px;">
|
||
<div style="display:flex;justify-content:space-between;">
|
||
<span style="color:var(--wd-text-2);">{{ __('Cost manopere') }}</span>
|
||
<b>{{ number_format($finance['works_sum'], 2, '.', ' ') }}</b>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;">
|
||
<span style="color:var(--wd-text-2);">{{ __('Cost piese') }}</span>
|
||
<b>{{ number_format($finance['parts_sum'], 2, '.', ' ') }}</b>
|
||
</div>
|
||
@if ($finance['discount'] > 0)
|
||
<div style="display:flex;justify-content:space-between;">
|
||
<span style="color:var(--wd-text-2);">{{ __('Discount') }}</span>
|
||
<b style="color:var(--wd-red);">−{{ number_format($finance['discount'], 2, '.', ' ') }}</b>
|
||
</div>
|
||
@endif
|
||
<div style="border-top:1px solid var(--wd-border);padding-top:8px;display:flex;justify-content:space-between;font-size:15px;">
|
||
<span>{{ __('Total') }}</span>
|
||
<b>{{ number_format($finance['total'], 2, '.', ' ') }} MDL</b>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;color:var(--wd-green);">
|
||
<span>{{ __('Achitat') }}</span>
|
||
<b>{{ number_format($finance['paid'], 2, '.', ' ') }}</b>
|
||
</div>
|
||
@if ($finance['balance'] > 0)
|
||
<div style="display:flex;justify-content:space-between;color:var(--wd-red);font-weight:700;">
|
||
<span>{{ __('Rest de plată') }}</span>
|
||
<b>{{ number_format($finance['balance'], 2, '.', ' ') }} MDL</b>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<div class="wd-placeholder">
|
||
{{ __('Timeline & Chat — Phase 2') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</x-filament-panels::page>
|