Files
Vasyka 3c92ea5475 feat(hints): scale coverage — 11 new registry entries + inject across pages
Registry grows from 14 → 25 hints. New entries:
- wo.dashboard.topbar_actions / meta / tabs / history / finance /
  timeline / bottom
- wo.create.overview
- crm.clients.overview / vehicles.overview
- finante.dashboard.stats / expenses.overview

"?" icons injected into:
- WO dashboard: top-bar actions, meta header, tabs bar, history card,
  finance card, timeline card, bottom action bar (in addition to the
  existing dashboard title, PDF preview, chat)
- CalendarBoard: title
- PipelineBoard: title
- Excel import wizard: step 1
- Main tenant dashboard (via PanelsRenderHook::PAGE_START scoped to
  Dashboard::class): small banner with a "?" and a shortcut to the
  FAQ page for users who don't see the icons

Users now see hint icons on the primary daily-use screens; more can
be added by dropping <x-hint key="..." /> anywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 09:57:54 +00:00

538 lines
35 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<x-filament-panels::page>
@php
$days = $this->getDays();
$rows = $this->getRows();
$matrix = $this->getMatrix();
$stats = $this->getStats();
$openEvent = $this->getOpenEvent();
@endphp
<style>
:root {
--cv-bg: #F5F7FA;
--cv-surface: #FFFFFF;
--cv-border: #E2E8F0;
--cv-text: #1A202C;
--cv-text-2: #4A5568;
--cv-text-3: #718096;
--cv-blue: #3B82F6;
--cv-blue-bg: #EBF5FF;
--cv-blue-text: #2563EB;
--cv-green: #10B981;
--cv-green-text: #059669;
--cv-orange: #D97706;
--cv-orange-bg: #FED7AA;
--cv-orange-text: #9A3412;
--cv-red: #DC2626;
--cv-red-bg: #FECACA;
--cv-red-text: #991B1B;
--cv-row-bg: #F7FAFC;
--cv-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.dark {
--cv-bg: #0f172a;
--cv-surface: #1f2937;
--cv-border: #374151;
--cv-text: #f1f5f9;
--cv-text-2: #cbd5e1;
--cv-text-3: #94a3b8;
--cv-row-bg: #111827;
--cv-blue-bg: #1e293b;
--cv-orange-bg: #422006;
--cv-red-bg: #450a0a;
}
.fi-main-ctn:has(.cv-shell) { padding: 0 !important; }
.fi-main:has(.cv-shell) { padding: 0 !important; }
.fi-page:has(.cv-shell) > div { padding: 0 !important; gap: 0 !important; }
.fi-page:has(.cv-shell) .fi-header { display: none !important; }
.cv-shell { background:var(--cv-bg); color:var(--cv-text); padding:24px; min-height:calc(100vh - 64px); font-size:13px; }
.cv-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; }
.cv-head h1 { font-size:24px; font-weight:700; margin:0; }
.cv-breadcrumb { font-size:13px; color:var(--cv-text-3); margin-bottom:6px; }
.cv-btn { padding:8px 14px; border:1px solid var(--cv-border); border-radius:6px; background:var(--cv-surface); cursor:pointer; font-size:14px; color:var(--cv-text); text-decoration:none; }
.cv-btn:hover { background:var(--cv-row-bg); }
.cv-btn-primary { background:var(--cv-blue); color:#fff !important; border-color:var(--cv-blue); }
.cv-btn-primary:hover { background:#2563EB; }
.cv-btn-icon { padding:8px 10px; }
.cv-btn-group { display:flex; gap:8px; }
/* KPI bar */
.cv-kpi-bar { display:grid; grid-template-columns:repeat(4, 1fr); gap:12px; margin-bottom:16px; }
.cv-kpi { background:var(--cv-surface); padding:14px 16px; border-radius:8px; box-shadow:var(--cv-shadow); }
.cv-kpi-label { font-size:11px; color:var(--cv-text-3); text-transform:uppercase; letter-spacing:.5px; font-weight:600; }
.cv-kpi-value { font-size:22px; font-weight:700; margin-top:4px; }
.cv-kpi-sub { font-size:12px; color:var(--cv-text-3); margin-top:2px; }
.cv-kpi-value.green { color:var(--cv-green-text); }
.cv-kpi-value.orange { color:var(--cv-orange); }
.cv-kpi-value.red { color:var(--cv-red); }
/* Toolbar */
.cv-toolbar { display:flex; gap:12px; align-items:center; background:var(--cv-surface); padding:12px 16px; border-radius:8px; box-shadow:var(--cv-shadow); margin-bottom:16px; flex-wrap:wrap; }
.cv-toolbar-group { display:flex; gap:8px; align-items:center; }
.cv-toolbar-label { font-size:12px; color:var(--cv-text-3); font-weight:500; text-transform:uppercase; letter-spacing:.5px; }
.cv-date { font-size:16px; font-weight:600; min-width:220px; text-align:center; }
.cv-view-switcher { display:inline-flex; background:#EDF2F7; padding:3px; border-radius:6px; }
.dark .cv-view-switcher { background:#111827; }
.cv-view-switcher button { padding:6px 14px; border:none; background:transparent; cursor:pointer; border-radius:4px; font-size:13px; font-weight:500; color:var(--cv-text-2); }
.cv-view-switcher button.active { background:var(--cv-surface); color:var(--cv-text); box-shadow:0 1px 2px rgba(0,0,0,0.1); }
.cv-select { padding:8px 12px; border:1px solid var(--cv-border); border-radius:6px; background:var(--cv-surface); font-size:14px; color:var(--cv-text); }
/* Notes */
.cv-notes { background:#FFFBEB; border:1px solid #FCD34D; padding:12px 14px; border-radius:6px; font-size:13px; color:#78350F; line-height:1.5; margin-bottom:16px; }
.dark .cv-notes { background:#422006; color:#fde68a; border-color:#854d0e; }
.cv-notes strong { display:block; margin-bottom:4px; }
/* Matrix */
.cv-matrix-wrap { background:var(--cv-surface); border-radius:8px; padding:16px; box-shadow:var(--cv-shadow); overflow-x:auto; }
.cv-grid { display:grid; grid-template-columns:140px repeat(7, minmax(160px, 1fr)); gap:1px; background:var(--cv-border); border-radius:6px; overflow:hidden; min-width:1240px; }
.cv-cell { background:var(--cv-surface); padding:10px 8px; min-height:90px; position:relative; }
.cv-header-cell { background:var(--cv-row-bg); text-align:center; font-weight:600; font-size:13px; padding:10px; }
.cv-day-name { display:block; font-size:11px; text-transform:uppercase; color:var(--cv-text-3); letter-spacing:.5px; }
.cv-day-num { display:block; font-size:18px; margin-top:2px; }
.cv-header-cell.today { background:var(--cv-blue-bg); }
.cv-header-cell.today .cv-day-num { color:var(--cv-blue-text); }
.cv-header-cell.closed { opacity:0.6; }
.cv-row-label { background:var(--cv-row-bg); display:flex; align-items:center; gap:8px; font-weight:600; font-size:14px; padding:10px 12px; }
.cv-row-color { width:12px; height:12px; border-radius:50%; flex-shrink:0; }
.cv-row-meta { font-size:11px; color:var(--cv-text-3); font-weight:400; margin-top:2px; }
.cv-cell-body { cursor:pointer; }
.cv-cell-body:hover { background:var(--cv-row-bg); }
.cv-cell-body.weekend { background:#FAFAFA; }
.dark .cv-cell-body.weekend { background:#0a0f1c; }
.cv-cell-body.closed { background:repeating-linear-gradient(45deg, #FAFAFA, #FAFAFA 4px, #F1F5F9 4px, #F1F5F9 8px); cursor:not-allowed; }
.dark .cv-cell-body.closed { background:repeating-linear-gradient(45deg, #0a0f1c, #0a0f1c 4px, #111827 4px, #111827 8px); }
.cv-cell.over { background:var(--cv-blue-bg) !important; }
.cv-load { position:absolute; top:4px; right:6px; font-size:10px; background:rgba(255,255,255,0.92); padding:1px 6px; border-radius:8px; color:var(--cv-text-2); font-weight:600; }
.dark .cv-load { background:rgba(0,0,0,0.5); color:var(--cv-text-2); }
.cv-load.warn { background:var(--cv-orange-bg); color:var(--cv-orange-text); }
.cv-load.full { background:var(--cv-red-bg); color:var(--cv-red-text); }
.cv-event { background:#EBF5FF; border-left:3px solid var(--cv-blue); padding:6px 8px; margin-bottom:4px; border-radius:4px; cursor:grab; font-size:12px; transition:all .15s; }
.cv-event:hover { transform:translateY(-1px); box-shadow:0 2px 6px rgba(0,0,0,0.08); }
.cv-event:active { cursor:grabbing; }
.cv-event.dragging { opacity:0.4; }
.cv-event-title { font-weight:600; color:var(--cv-blue-text); }
.cv-event-meta { color:var(--cv-text-2); font-size:11px; margin-top:2px; }
.cv-event-time { font-weight:500; color:var(--cv-text); font-size:11px; margin-top:2px; }
.cv-add { position:absolute; bottom:4px; right:4px; width:24px; height:24px; border-radius:50%; background:var(--cv-blue); color:white; border:none; cursor:pointer; font-size:16px; line-height:22px; opacity:0; transition:opacity .15s; box-shadow:0 2px 4px rgba(0,0,0,0.15); }
.cv-cell:hover .cv-add { opacity:1; }
.cv-legend { background:var(--cv-surface); border-radius:8px; padding:16px; box-shadow:var(--cv-shadow); margin-top:16px; }
.cv-legend h3 { font-size:14px; margin-bottom:12px; color:var(--cv-text); }
.cv-legend-row { display:flex; gap:32px; flex-wrap:wrap; align-items:flex-start; }
.cv-legend-col { font-size:11px; color:var(--cv-text-3); text-transform:uppercase; margin-bottom:8px; font-weight:600; }
.cv-legend-items { display:flex; gap:16px; flex-wrap:wrap; font-size:12px; color:var(--cv-text-2); align-items:center; }
.cv-legend-item { display:inline-flex; gap:6px; align-items:center; }
.cv-legend-dot { width:12px; height:12px; border-radius:3px; }
/* Side panel */
.cv-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.25); z-index:100; opacity:0; pointer-events:none; transition:opacity .2s; }
.cv-overlay.open { opacity:1; pointer-events:all; }
.cv-panel { position:fixed; right:0; top:0; bottom:0; width:440px; max-width:92vw; background:var(--cv-surface); border-left:1px solid var(--cv-border); z-index:101; transform:translateX(100%); transition:transform .25s cubic-bezier(.4,0,.2,1); display:flex; flex-direction:column; }
.cv-panel.open { transform:translateX(0); }
.cv-panel-head { padding:16px 20px; border-bottom:1px solid var(--cv-border); display:flex; align-items:flex-start; justify-content:space-between; }
.cv-panel-title { font-size:15px; font-weight:600; }
.cv-panel-sub { font-size:11px; color:var(--cv-text-3); margin-top:2px; }
.cv-close { width:28px; height:28px; border-radius:6px; border:1px solid var(--cv-border); background:var(--cv-row-bg); cursor:pointer; display:flex; align-items:center; justify-content:center; }
.cv-panel-body { padding:16px 20px; flex:1; overflow-y:auto; }
.cv-pfield { margin-bottom:12px; }
.cv-pfield label { display:block; font-size:10px; font-weight:600; color:var(--cv-text-3); text-transform:uppercase; letter-spacing:.5px; margin-bottom:4px; }
.cv-pfield input, .cv-pfield textarea, .cv-pfield select { width:100%; padding:8px 10px; font-size:13px; border:1px solid var(--cv-border); border-radius:6px; background:var(--cv-surface); color:var(--cv-text); outline:none; font-family:inherit; }
.cv-pfield input:focus { border-color:var(--cv-blue); }
.cv-pfield-val { font-size:13px; color:var(--cv-text); }
.cv-panel-actions { padding:12px 20px; border-top:1px solid var(--cv-border); display:flex; gap:8px; }
.cv-panel-actions .cv-btn { flex:1; text-align:center; }
.cv-two-cols { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
</style>
<div class="cv-shell" x-data="{ dragEventId: null }">
<div class="cv-head">
<div>
<div class="cv-breadcrumb">{{ __('CRM Programări ') }} <strong style="color:var(--cv-text);">{{ __('Calendar vizual') }}</strong></div>
<h1>{{ __('Calendar vizual') }}<x-hint key="crm.calendar.overview" /></h1>
</div>
<div class="cv-btn-group">
@php
$_days = $this->getDays();
$_from = $_days[0]['date'] ?? today()->toDateString();
$_to = end($_days)['date'] ?? today()->toDateString();
$_pdfUrl = url('/app/appointments/pdf?from=' . $_from . '&to=' . $_to);
$_pdfDl = $_pdfUrl . '&download=1';
@endphp
<div x-data="{ pdfOpen: false }" style="display:inline-block;">
<button type="button" class="cv-btn" @click="pdfOpen = true">{{ __('🖨 PDF programări') }}</button>
<div x-show="pdfOpen" x-cloak
style="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;padding:24px;"
@click.self="pdfOpen = false"
@keydown.escape.window="pdfOpen = false">
<div style="background:#fff;border-radius:8px;width:100%;max-width:1000px;height:90vh;display:flex;flex-direction:column;overflow:hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #e5e7eb;">
<b style="font-size:14px;">{{ __('Programări') }} {{ $_from }} {{ $_to }}</b>
<div style="display:flex;gap:12px;align-items:center;">
<a href="{{ $_pdfDl }}" style="font-size:13px;color:#2563eb;text-decoration:none;">⬇️ {{ __('Descarcă') }}</a>
<a href="{{ $_pdfUrl }}" target="_blank" style="font-size:13px;color:#2563eb;text-decoration:none;">↗️ {{ __('Deschide în tab nou') }}</a>
<button type="button" @click="pdfOpen = false" style="background:none;border:none;font-size:20px;cursor:pointer;color:#6b7280;"></button>
</div>
</div>
<iframe x-bind:src="pdfOpen ? '{{ $_pdfUrl }}#toolbar=1&navpanes=0' : ''"
style="flex:1;width:100%;border:none;" title="PDF"></iframe>
</div>
</div>
</div>
<button class="cv-btn" wire:click="openNewPostForm">{{ __('+ Pod nou') }}</button>
<button class="cv-btn cv-btn-primary" wire:click="openNewForm">{{ __('+ Programare nouă') }}</button>
</div>
</div>
<div class="cv-kpi-bar">
<div class="cv-kpi">
<div class="cv-kpi-label">{{ __('Ore programate') }}</div>
<div class="cv-kpi-value">{{ $stats['scheduled_hours'] }} / {{ $stats['capacity_hours'] }}</div>
<div class="cv-kpi-sub">{{ __('săptămâna curentă') }} · {{ $stats['utilization_pct'] }}% {{ __('capacitate') }}</div>
</div>
<div class="cv-kpi">
<div class="cv-kpi-label">{{ __('Fișe deschise') }}</div>
<div class="cv-kpi-value orange">{{ $stats['open_count'] }}</div>
<div class="cv-kpi-sub">{{ __('programări active') }}</div>
</div>
<div class="cv-kpi">
<div class="cv-kpi-label">{{ __('Confirmate') }}</div>
<div class="cv-kpi-value green">{{ $stats['confirmed_count'] }} / {{ $stats['total_count'] }}</div>
<div class="cv-kpi-sub">{{ $stats['confirmation_rate_pct'] }}% {{ __('rata confirmare') }}</div>
</div>
<div class="cv-kpi">
<div class="cv-kpi-label">{{ __('No-show alert') }}</div>
<div class="cv-kpi-value red">{{ $stats['no_show_alert'] }}</div>
<div class="cv-kpi-sub">{{ __('programări neconfirmate &lt; 24h') }}</div>
</div>
</div>
<div class="cv-toolbar">
<div class="cv-toolbar-group">
<button class="cv-btn cv-btn-icon" wire:click="shiftWeek(-1)"></button>
<div class="cv-date">{{ $this->getWeekLabel() }}</div>
<button class="cv-btn cv-btn-icon" wire:click="shiftWeek(1)"></button>
<button class="cv-btn" wire:click="setWeekToday">{{ __('Astăzi') }}</button>
</div>
<div class="cv-toolbar-group">
<span class="cv-toolbar-label">{{ __('Mod:') }}</span>
<div class="cv-view-switcher">
<button class="{{ $viewMode === 'day' ? 'active' : '' }}" wire:click="setViewMode('day')">{{ __('Zi') }}</button>
<button class="{{ $viewMode === 'week' ? 'active' : '' }}" wire:click="setViewMode('week')">{{ __('Săpt') }}</button>
<button class="{{ $viewMode === 'month' ? 'active' : '' }}" wire:click="setViewMode('month')">{{ __('Lună') }}</button>
<button class="{{ $viewMode === 'custom' ? 'active' : '' }}" wire:click="setViewMode('custom')">{{ __('Custom') }}</button>
<button class="{{ $viewMode === 'list' ? 'active' : '' }}" wire:click="setViewMode('list')">{{ __('Listă') }}</button>
</div>
</div>
<div class="cv-toolbar-group">
<span class="cv-toolbar-label">{{ __('Grupare:') }}</span>
<div class="cv-view-switcher">
<button class="{{ $groupBy === 'post' ? 'active' : '' }}" wire:click="setGroupBy('post')">{{ __('Pod') }}</button>
<button class="{{ $groupBy === 'master' ? 'active' : '' }}" wire:click="setGroupBy('master')">{{ __('Mecanic') }}</button>
</div>
</div>
@if ($viewMode === 'custom')
<div class="cv-toolbar-group">
<span class="cv-toolbar-label">{{ __('De la:') }}</span>
<input type="date" class="cv-select" wire:model.live="customStart">
<span class="cv-toolbar-label">{{ __('Până la:') }}</span>
<input type="date" class="cv-select" wire:model.live="customEnd">
</div>
@endif
<div class="cv-toolbar-group" style="margin-left:auto;">
<span class="cv-toolbar-label">{{ __('Filtru:') }}</span>
<select class="cv-select" wire:change="setMasterFilter($event.target.value)">
<option value="">{{ __('Toți mecanicii') }}</option>
@foreach ($this->getMasterOptions() as $id => $name)
<option value="{{ $id }}" {{ $masterFilter == $id ? 'selected' : '' }}>{{ $name }}</option>
@endforeach
</select>
<select class="cv-select" wire:change="setStatusFilter($event.target.value)">
<option value="all">{{ __('Toate statusurile') }}</option>
<option value="confirmed" @if($statusFilter==='confirmed')selected @endif>{{ __('Confirmate') }}</option>
<option value="unconfirmed" @if($statusFilter==='unconfirmed')selected @endif>{{ __('Neconfirmate') }}</option>
<option value="in_work" @if($statusFilter==='in_work')selected @endif>{{ __('În lucru') }}</option>
</select>
</div>
</div>
<div class="cv-notes">
<strong>{{ __('⚙ Cum funcționează:') }}</strong>
{{ __('Matricea') }} <strong>{{ $groupBy === 'post' ? __('Pod') : __('Mecanic') }} × {{ __('Zile') }}</strong>. {{ __('Drag-and-drop între celule pentru a reprograma. Indicator de încărcare pe fiecare celulă (ore planificate / capacitate). Click pe celulă goală → programare rapidă. Click pe eveniment → detalii.') }}
</div>
@if ($viewMode === 'list')
@php $list = $this->getListAppointments(); @endphp
<div class="cv-matrix-wrap">
@if (empty($list))
<div style="padding:32px; text-align:center; color:#718096;">{{ __('Nicio programare în perioada selectată.') }}</div>
@else
<table style="width:100%; border-collapse:collapse; font-size:13px;">
<thead>
<tr style="background:#f7fafc;">
@foreach (['Data', 'Ora', 'Subiect', 'Client', 'Telefon', 'Auto', 'Pod', 'Maistru', 'Status'] as $h)
<th style="text-align:left; padding:10px 12px; font-size:11px; text-transform:uppercase; color:#4a5568; border-bottom:1px solid var(--cv-border);">{{ $h }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($list as $a)
<tr wire:click="openEvent({{ $a['id'] }})" style="cursor:pointer; border-bottom:1px solid var(--cv-border);">
<td style="padding:10px 12px; font-weight:500;">{{ $a['date'] }}</td>
<td style="padding:10px 12px;">{{ $a['time'] }}</td>
<td style="padding:10px 12px; font-weight:500;">{{ $a['title'] }}</td>
<td style="padding:10px 12px;">{{ $a['client_name'] ?? '—' }}</td>
<td style="padding:10px 12px; color:var(--cv-blue);">{{ $a['client_phone'] ?? '—' }}</td>
<td style="padding:10px 12px;">{{ $a['vehicle'] }}</td>
<td style="padding:10px 12px;">{{ $a['post_name'] }}</td>
<td style="padding:10px 12px;">{{ $a['master_name'] }}</td>
<td style="padding:10px 12px;"><span style="background:#EDF2F7;padding:2px 8px;border-radius:4px;font-size:11px;">{{ ucfirst($a['status']) }}</span></td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
@else
<div class="cv-matrix-wrap">
<div class="cv-grid" style="grid-template-columns: 140px repeat({{ count($days) }}, minmax({{ $viewMode === 'month' ? '70' : '160' }}px, 1fr)); min-width: {{ 140 + count($days) * ($viewMode === 'month' ? 75 : 165) }}px;">
<div class="cv-cell cv-header-cell" style="background:#EDF2F7;">{{ $groupBy === 'post' ? __('Pod / Zi') : __('Mecanic / Zi') }}</div>
@foreach ($days as $day)
<div class="cv-cell cv-header-cell {{ $day['is_today'] ? 'today' : '' }} {{ $day['is_closed'] ? 'closed' : '' }}">
<span class="cv-day-name">{{ $day['name'] }}</span>
<span class="cv-day-num">{{ $day['label'] }}</span>
</div>
@endforeach
@foreach ($rows as $row)
<div class="cv-cell cv-row-label" @if ($row['kind'] === 'post' && $row['id']) wire:click="openRenamePost({{ $row['id'] }})" style="cursor:pointer;" title="{{ __('Click pentru a redenumi') }}" @endif>
<span class="cv-row-color" style="background:{{ $row['color'] }}"></span>
<div>
<div>{{ $row['name'] }} @if ($row['kind'] === 'post') <span style="font-size:10px; opacity:0.5;"></span>@endif</div>
<div class="cv-row-meta">{{ $row['meta'] }}</div>
</div>
</div>
@foreach ($days as $day)
@php
$cell = $matrix[$row['id']][$day['date']] ?? ['events' => [], 'load_hours' => 0, 'capacity' => $row['capacity_hours']];
$loadClass = '';
$cap = $cell['capacity'] ?: 10;
$loadRatio = $cap > 0 ? $cell['load_hours'] / $cap : 0;
if ($loadRatio >= 0.9) $loadClass = 'full';
elseif ($loadRatio >= 0.5) $loadClass = 'warn';
@endphp
<div class="cv-cell cv-cell-body {{ $day['is_weekend'] ? 'weekend' : '' }} {{ $day['is_closed'] ? 'closed' : '' }}"
@if (! $day['is_closed'])
@dragover.prevent="$el.classList.add('over')"
@dragleave="$el.classList.remove('over')"
@drop.prevent="
$el.classList.remove('over');
if (dragEventId) { $wire.moveEvent(dragEventId, {{ $row['id'] }}, '{{ $day['date'] }}'); dragEventId = null; }
"
@endif>
@if (! $day['is_closed'])
<span class="cv-load {{ $loadClass }}">{{ rtrim(rtrim(number_format($cell['load_hours'], 1), '0'), '.') ?: '0' }}/{{ (int)$cap }}</span>
@foreach ($cell['events'] as $e)
<div class="cv-event"
draggable="true"
style="background:{{ $e['color'] }}22; border-left-color:{{ $e['color'] }}"
wire:click="openEvent({{ $e['id'] }})"
@dragstart="dragEventId = {{ $e['id'] }}; $el.classList.add('dragging')"
@dragend="$el.classList.remove('dragging')">
<div class="cv-event-title" style="color:{{ $e['color'] }}">{{ $e['client_name'] ?: $e['title'] }}</div>
<div class="cv-event-meta">{{ $e['vehicle'] ?: '—' }}@if($e['plate']) · {{ $e['plate'] }}@endif</div>
<div class="cv-event-time">{{ $e['time'] }}@if($e['master_initial']) · {{ $e['master_initial'] }}@endif</div>
</div>
@endforeach
<button class="cv-add" type="button" wire:click="openNewForm({{ $row['id'] }}, '{{ $day['date'] }}')">+</button>
@endif
</div>
@endforeach
@endforeach
</div>
</div>
@endif
<div class="cv-legend">
<h3>{{ __('Legendă') }}</h3>
<div class="cv-legend-row">
<div>
<div class="cv-legend-col">{{ __('Mecanici') }}</div>
<div class="cv-legend-items">
@foreach ($this->getMasterOptions() as $id => $name)
@php
$u = \App\Models\Tenant\User::find($id);
$color = $u?->color ?: '#94a3b8';
$spec = $u?->specialization ?: '';
@endphp
<div class="cv-legend-item"><span class="cv-legend-dot" style="background:{{ $color }}"></span> {{ $name }} @if($spec)· {{ implode(' / ', array_map(fn($p) => __(trim($p)), explode('/', $spec))) }} @endif</div>
@endforeach
</div>
</div>
<div>
<div class="cv-legend-col">{{ __('Încărcare celulă') }}</div>
<div class="cv-legend-items">
<div class="cv-legend-item"><span class="cv-load" style="position:static;">{{ __('05h/10') }}</span> {{ __('liber/ușor') }}</div>
<div class="cv-legend-item"><span class="cv-load warn" style="position:static;">{{ __('58.5h/10') }}</span> {{ __('mediu') }}</div>
<div class="cv-legend-item"><span class="cv-load full" style="position:static;">{{ __('≥9h/10') }}</span> {{ __('plin') }}</div>
</div>
</div>
<div>
<div class="cv-legend-col">{{ __('Stare zi') }}</div>
<div class="cv-legend-items">
<div class="cv-legend-item"><span class="cv-legend-dot" style="background:#FAFAFA; border:1px solid var(--cv-border);"></span> {{ __('Weekend') }}</div>
<div class="cv-legend-item"><span class="cv-legend-dot" style="background:repeating-linear-gradient(45deg,#FAFAFA,#FAFAFA 2px,#F1F5F9 2px,#F1F5F9 4px); border:1px solid var(--cv-border);"></span> {{ __('Închis (Duminică/sărbătoare)') }}</div>
<div class="cv-legend-item"><span class="cv-legend-dot" style="background:var(--cv-blue-bg); border:1px solid var(--cv-blue);"></span> {{ __('Astăzi') }}</div>
</div>
</div>
</div>
</div>
<div class="cv-overlay {{ $openEventId ? 'open' : '' }}" wire:click="closeEvent"></div>
<div class="cv-panel {{ $openEventId ? 'open' : '' }}">
@if ($openEvent)
<div class="cv-panel-head">
<div>
<div class="cv-panel-title">{{ $openEvent['title'] ?: $openEvent['client_name'] }}</div>
<div class="cv-panel-sub">{{ $openEvent['date'] }} · {{ $openEvent['time'] }} · {{ ucfirst($openEvent['status']) }}</div>
</div>
<div class="cv-close" wire:click="closeEvent"></div>
</div>
<div class="cv-panel-body">
<div class="cv-two-cols">
@if ($openEvent['client_name'])<div class="cv-pfield"><label>{{ __('Client') }}</label><div class="cv-pfield-val">{{ $openEvent['client_name'] }}</div></div>@endif
@if ($openEvent['client_phone'])<div class="cv-pfield"><label>{{ __('Telefon') }}</label><div class="cv-pfield-val" style="color:var(--cv-blue);">{{ $openEvent['client_phone'] }}</div></div>@endif
@if ($openEvent['vehicle'])<div class="cv-pfield"><label>{{ __('Auto') }}</label><div class="cv-pfield-val">{{ $openEvent['vehicle'] }}</div></div>@endif
@if ($openEvent['plate'])<div class="cv-pfield"><label>{{ __('Numar') }}</label><div class="cv-pfield-val">{{ $openEvent['plate'] }}</div></div>@endif
@if ($openEvent['master_name'])<div class="cv-pfield"><label>{{ __('Maistru') }}</label><div class="cv-pfield-val">{{ $openEvent['master_name'] }}</div></div>@endif
@if ($openEvent['post_name'])<div class="cv-pfield"><label>{{ __('Pod') }}</label><div class="cv-pfield-val">{{ $openEvent['post_name'] }}</div></div>@endif
</div>
@if ($openEvent['notes'])
<div class="cv-pfield"><label>{{ __('Notițe') }}</label><div class="cv-pfield-val" style="white-space:pre-wrap;">{{ $openEvent['notes'] }}</div></div>
@endif
</div>
<div class="cv-panel-actions">
<button class="cv-btn" wire:click="deleteEvent({{ $openEvent['id'] }})" onclick="return confirm('Ștergi această programare?')">{{ __('🗑 Șterge') }}</button>
<a class="cv-btn cv-btn-primary" href="{{ route('filament.tenant.resources.appointments.edit', ['record' => $openEvent['id']]) }}">{{ __('↗ Editare') }}</a>
</div>
@endif
</div>
<div class="cv-overlay {{ $showNewForm ? 'open' : '' }}" wire:click="$set('showNewForm', false)"></div>
<div class="cv-panel {{ $showNewForm ? 'open' : '' }}">
@if ($showNewForm)
<div class="cv-panel-head">
<div>
<div class="cv-panel-title">{{ __('Programare nouă') }}</div>
<div class="cv-panel-sub">{{ $newAppt['date'] ?? '' }}</div>
</div>
<div class="cv-close" wire:click="$set('showNewForm', false)"></div>
</div>
<div class="cv-panel-body">
<div class="cv-pfield"><label>{{ __('Subiect *') }}</label><input wire:model="newAppt.title" type="text" placeholder="{{ __('Schimb ulei + filtru') }}"></div>
<div class="cv-two-cols">
<div class="cv-pfield"><label>{{ __('Data *') }}</label><input wire:model="newAppt.date" type="date"></div>
<div class="cv-pfield"><label>{{ __('Pod') }}</label>
<select wire:model="newAppt.post_id">
<option value=""></option>
@foreach ($this->getPostOptions() as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
</div>
<div class="cv-pfield"><label>{{ __('De la') }}</label><input wire:model="newAppt.time_start" type="time"></div>
<div class="cv-pfield"><label>{{ __('Până la') }}</label><input wire:model="newAppt.time_end" type="time"></div>
</div>
<div class="cv-pfield"><label>{{ __('Client') }}</label>
<select wire:model.live="newAppt.client_id">
<option value=""></option>
@foreach ($this->getClientOptions() as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
</div>
<div class="cv-pfield"><label>{{ __('Maistru') }}</label>
<select wire:model="newAppt.master_id">
<option value=""></option>
@foreach ($this->getMasterOptions() as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
</div>
<div class="cv-pfield"><label>{{ __('Notițe') }}</label><textarea wire:model="newAppt.notes" rows="3"></textarea></div>
</div>
<div class="cv-panel-actions">
<button class="cv-btn" wire:click="$set('showNewForm', false)">{{ __('Anulează') }}</button>
<button class="cv-btn cv-btn-primary" wire:click="createAppt">{{ __('Salvează') }}</button>
</div>
@endif
</div>
{{-- NEW POST QUICK-ADD MODAL --}}
@if ($showNewPostForm)
<div style="position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:9999; display:flex; align-items:center; justify-content:center;" wire:click="$set('showNewPostForm', false)">
<div style="background:white; border-radius:12px; padding:20px; max-width:420px; width:92%;" wire:click.stop>
<h2 style="font-size:18px; font-weight:600; margin-bottom:12px;">{{ __('+ Pod / spațiu nou') }}</h2>
<p style="font-size:13px; color:#4a5568; margin-bottom:16px;">{{ __('Poți adăuga un loc de muncă (cu lift sau fără — ex: curte, atelier electric).') }}</p>
<div class="cv-pfield">
<label>{{ __('Nume *') }}</label>
<input type="text" wire:model="newPost.name" placeholder="{{ __('Ex: Curte 1, Pod 4, Atelier electric') }}" autofocus>
</div>
<div class="cv-two-cols">
<div class="cv-pfield">
<label>{{ __('Culoare') }}</label>
<input type="color" wire:model="newPost.color" style="height:40px; padding:4px;">
</div>
<div class="cv-pfield">
<label>{{ __('Ore/zi') }}</label>
<input type="number" wire:model="newPost.hours_per_day" min="1" step="0.5">
</div>
</div>
<div class="cv-pfield">
<label>{{ __('Descriere (opțional)') }}</label>
<input type="text" wire:model="newPost.description" placeholder="{{ __('Ex: fără lift, doar diagnoză...') }}">
</div>
<div style="display:flex; gap:8px; margin-top:16px;">
<button class="cv-btn" wire:click="$set('showNewPostForm', false)" style="flex:1;">{{ __('Anulează') }}</button>
<button class="cv-btn cv-btn-primary" wire:click="createPost" style="flex:1;">{{ __('Adaugă') }}</button>
</div>
</div>
</div>
@endif
{{-- RENAME POST MODAL --}}
@if ($renamingPostId)
<div style="position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:9999; display:flex; align-items:center; justify-content:center;" wire:click="$set('renamingPostId', null)">
<div style="background:white; border-radius:12px; padding:20px; max-width:420px; width:92%;" wire:click.stop>
<h2 style="font-size:18px; font-weight:600; margin-bottom:12px;">{{ __('✏ Editează pod') }}</h2>
<div class="cv-pfield">
<label>{{ __('Nume *') }}</label>
<input type="text" wire:model="renamingPostName" autofocus>
</div>
<div class="cv-pfield">
<label>{{ __('Mecanic implicit') }}</label>
<select wire:model="renamingPostMasterId">
<option value="">{{ __('— niciunul —') }}</option>
@foreach ($this->getMasterOptions() as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
<p style="font-size:11px; color:#718096; margin-top:4px;">{{ __('Pre-completat automat la programări noi pe acest pod.') }}</p>
</div>
<div style="display:flex; gap:8px; margin-top:16px;">
<button class="cv-btn" wire:click="$set('renamingPostId', null)" style="flex:1;">{{ __('Anulează') }}</button>
<button class="cv-btn cv-btn-primary" wire:click="saveRenamePost" style="flex:1;">{{ __('Salvează') }}</button>
</div>
</div>
</div>
@endif
</div>
</x-filament-panels::page>