fix: calendar board — 4 issues fixed

1. New form: Auto (vehicle) field first, client auto-filled via
   updatedNewApptVehicleId() when a vehicle is selected; getAllVehicleOptions()
   returns all vehicles as «plate — make model» for quick lookup

2. Scroll speed: JS intercepts wheel events on .cv-matrix-wrap and
   applies ×0.45 multiplier for horizontal scroll (shift+wheel or
   trackpad horizontal); scroll-behavior:smooth added to CSS

3. Event cards now show all 4 fields: 🔧 Lucrarea (title) +
   👤 Client + 🚗 Auto · plate + 🕐 Ora; each shown only if present

4. Delete bug fixed: replaced onclick=confirm() (races with wire:click)
   with wire:confirm= (Livewire native dialog — Cancel truly cancels)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 06:46:46 +00:00
parent f885c04f90
commit 753e81d292
2 changed files with 54 additions and 11 deletions
@@ -527,6 +527,23 @@ class CalendarBoard extends Page
return Vehicle::where('client_id', $clientId)->pluck('plate', 'id')->toArray();
}
public function getAllVehicleOptions(): array
{
return Vehicle::orderBy('plate')
->limit(300)
->get(['id', 'make', 'model', 'plate', 'client_id'])
->mapWithKeys(fn ($v) => [
$v->id => trim($v->plate . ' — ' . $v->make . ' ' . $v->model),
])->toArray();
}
public function updatedNewApptVehicleId($value): void
{
if ($value && $vehicle = Vehicle::find($value)) {
$this->newAppt['client_id'] = $vehicle->client_id;
}
}
private function calcHours(?string $start, ?string $end): float
{
if (! $start || ! $end) return 1.0;
@@ -86,7 +86,7 @@
.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-matrix-wrap { background:var(--cv-surface); border-radius:8px; padding:16px; box-shadow:var(--cv-shadow); overflow-x:auto; scroll-behavior:smooth; }
.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; }
@@ -113,13 +113,14 @@
.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 { background:#EBF5FF; border-left:3px solid var(--cv-blue); padding:5px 7px; margin-bottom:4px; border-radius:4px; cursor:grab; font-size:11px; transition:all .15s; line-height:1.35; }
.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-event-title { font-weight:700; color:var(--cv-blue-text); font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cv-event-client { color:var(--cv-text); font-size:11px; margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cv-event-vehicle { color:var(--cv-text-2); font-size:11px; margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cv-event-time { font-weight:600; color:var(--cv-text-3); font-size:10px; 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; }
@@ -346,9 +347,10 @@
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>
@if($e['title'])<div class="cv-event-title" style="color:{{ $e['color'] }}">{{ $e['title'] }}</div>@endif
@if($e['client_name'])<div class="cv-event-client">👤 {{ $e['client_name'] }}</div>@endif
@if($e['vehicle'] || $e['plate'])<div class="cv-event-vehicle">🚗 {{ $e['vehicle'] }}@if($e['plate']) · {{ $e['plate'] }}@endif</div>@endif
<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>
@@ -419,7 +421,7 @@
@endif
</div>
<div class="cv-panel-actions">
<button class="cv-btn" wire:click="deleteEvent({{ $openEvent['id'] }})" onclick="return confirm('Ștergi această programare?')">{{ __('🗑 Șterge') }}</button>
<button class="cv-btn" wire:click="deleteEvent({{ $openEvent['id'] }})" wire: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
@@ -450,8 +452,16 @@
<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">
<div class="cv-pfield"><label>{{ __('Auto *') }}</label>
<select wire:model.live="newAppt.vehicle_id">
<option value="">— {{ __('alege autovehicul') }} —</option>
@foreach ($this->getAllVehicleOptions() as $id => $label)
<option value="{{ $id }}">{{ $label }}</option>
@endforeach
</select>
</div>
<div class="cv-pfield"><label>{{ __('Client') }} <span style="font-weight:400;opacity:.6;">({{ __('se completează automat') }})</span></label>
<select wire:model="newAppt.client_id">
<option value="">—</option>
@foreach ($this->getClientOptions() as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@@ -534,4 +544,20 @@
</div>
@endif
</div>
<script>
document.addEventListener('livewire:navigated', initSlowScroll);
document.addEventListener('DOMContentLoaded', initSlowScroll);
function initSlowScroll() {
const wrap = document.querySelector('.cv-matrix-wrap');
if (!wrap || wrap._slowScroll) return;
wrap._slowScroll = true;
wrap.addEventListener('wheel', function(e) {
if (Math.abs(e.deltaX) < Math.abs(e.deltaY) && !e.shiftKey) return;
e.preventDefault();
const delta = e.shiftKey ? e.deltaY : e.deltaX;
wrap.scrollLeft += delta * 0.45;
}, { passive: false });
}
</script>
</x-filament-panels::page>