From bfe58ed286b7249fa34a30f5d101eee909b78071 Mon Sep 17 00:00:00 2001 From: Vasyka Date: Thu, 7 May 2026 13:10:27 +0000 Subject: [PATCH] Faza 1 (din lista de continuare): Calendar vizual cu FullCalendar 6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Custom Filament Page CalendarBoard la /app/calendar-board (group CRM) - FullCalendar 6.1.15 din CDN + locale RO - View-uri: zi (timeGridDay), săptămână (timeGridWeek default), lună - Programări colorate per maistru (din User.color) - Live event loading: Livewire $wire.getEvents(start, end) - Drag-drop reschedule: eventDrop → $wire.moveEvent(id, start, end) - Resize event (extinde durată): eventResize - Click slot gol → quick-create modal cu form Filament populat cu data/timpul - Câmpuri: title, time_start/end, client (live), vehicle (filtrat după client), master, post, notes - Click event → confirm + delete - Toolbar: prev/next/today + month/week/day switch - 07:00–21:00 grid cu sloturi 30 min, today indicator live - Modal stilizat (CSS scoped) cu close button + ESC Total tenant routes: 93. --- app/Filament/Tenant/Pages/CalendarBoard.php | 164 ++++++++++++++++++ .../filament/tenant/pages/calendar.blade.php | 139 +++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 app/Filament/Tenant/Pages/CalendarBoard.php create mode 100644 resources/views/filament/tenant/pages/calendar.blade.php diff --git a/app/Filament/Tenant/Pages/CalendarBoard.php b/app/Filament/Tenant/Pages/CalendarBoard.php new file mode 100644 index 0000000..a6196ef --- /dev/null +++ b/app/Filament/Tenant/Pages/CalendarBoard.php @@ -0,0 +1,164 @@ +whereBetween('date', [$start, $end]) + ->get() + ->map(fn (Appointment $a) => [ + 'id' => $a->id, + 'title' => trim($a->title ?: ($a->client?->name ?? '—')), + 'start' => $a->date->format('Y-m-d') . 'T' . ($a->time_start ?? '08:00:00'), + 'end' => $a->date->format('Y-m-d') . 'T' . ($a->time_end ?? '09:00:00'), + 'backgroundColor' => $a->color ?: ($a->master?->color ?? '#3b82f6'), + 'borderColor' => $a->color ?: ($a->master?->color ?? '#3b82f6'), + 'extendedProps' => [ + 'client' => $a->client?->name, + 'vehicle' => trim(($a->vehicle?->make ?? '') . ' ' . ($a->vehicle?->model ?? '')), + 'plate' => $a->vehicle?->plate, + 'master' => $a->master?->name, + 'post' => $a->post?->name, + 'status' => $a->status, + 'notes' => $a->notes, + ], + ])->all(); + } + + /** Drag-drop reschedule. */ + public function moveEvent(int $id, string $start, string $end): void + { + $a = Appointment::find($id); + if (! $a) return; + + [$startDate, $startTime] = $this->splitIso($start); + [, $endTime] = $this->splitIso($end); + + $a->update([ + 'date' => $startDate, + 'time_start' => $startTime, + 'time_end' => $endTime, + ]); + + Notification::make() + ->title('Programare mutată') + ->body($a->title . ' → ' . $startDate . ' ' . substr($startTime, 0, 5)) + ->success()->send(); + + $this->dispatch('events-changed'); + } + + public function quickCreate(string $start, string $end): void + { + $this->createData = [ + 'date' => substr($start, 0, 10), + 'time_start' => substr($start, 11, 5), + 'time_end' => substr($end, 11, 5), + ]; + $this->createForm->fill($this->createData); + $this->dispatch('open-create-modal'); + } + + public function createForm(Schema $schema): Schema + { + return $schema->components([ + Forms\Components\Hidden::make('date'), + Forms\Components\TextInput::make('title')->label('Subiect')->required(), + Schemas\Components\Section::make('Când') + ->columns(2) + ->schema([ + Forms\Components\TimePicker::make('time_start')->label('De la')->seconds(false)->required(), + Forms\Components\TimePicker::make('time_end')->label('Până la')->seconds(false)->required(), + ]), + Schemas\Components\Section::make('Cine') + ->columns(2) + ->schema([ + Forms\Components\Select::make('client_id')->label('Client') + ->options(fn () => Client::pluck('name', 'id')) + ->searchable() + ->live(), + Forms\Components\Select::make('vehicle_id')->label('Auto') + ->options(fn (\Filament\Schemas\Components\Utilities\Get $get) => $get('client_id') + ? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id') + : []), + Forms\Components\Select::make('master_id')->label('Maistru') + ->options(fn () => User::where('status', 'active')->pluck('name', 'id')) + ->searchable(), + Forms\Components\Select::make('post_id')->label('Pod') + ->options(fn () => Post::where('is_active', true)->pluck('name', 'id')) + ->searchable(), + ]), + Forms\Components\Textarea::make('notes')->rows(2), + ])->statePath('createData'); + } + + public function saveCreate(): void + { + $data = $this->createForm->getState(); + Appointment::create([ + 'date' => $data['date'], + 'time_start' => $data['time_start'], + 'time_end' => $data['time_end'], + 'title' => $data['title'], + 'client_id' => $data['client_id'] ?? null, + 'vehicle_id' => $data['vehicle_id'] ?? null, + 'master_id' => $data['master_id'] ?? null, + 'post_id' => $data['post_id'] ?? null, + 'notes' => $data['notes'] ?? null, + 'status' => 'scheduled', + ]); + $this->createData = []; + Notification::make()->title('Programare adăugată')->success()->send(); + $this->dispatch('close-create-modal'); + $this->dispatch('events-changed'); + } + + public function deleteEvent(int $id): void + { + Appointment::where('id', $id)->delete(); + Notification::make()->title('Programare ștearsă')->success()->send(); + $this->dispatch('events-changed'); + } + + protected function splitIso(string $iso): array + { + // "2026-05-07T10:30:00" → ["2026-05-07", "10:30:00"] + if (str_contains($iso, 'T')) { + return explode('T', $iso); + } + return [substr($iso, 0, 10), substr($iso, 11) ?: '08:00:00']; + } +} diff --git a/resources/views/filament/tenant/pages/calendar.blade.php b/resources/views/filament/tenant/pages/calendar.blade.php new file mode 100644 index 0000000..c59fc3a --- /dev/null +++ b/resources/views/filament/tenant/pages/calendar.blade.php @@ -0,0 +1,139 @@ + + + + + + + +
+
+
+
+ + {{-- Quick-create modal --}} +
+
+
+

Programare nouă

+ +
+
+ {{ $this->createForm }} +
+ + Anulează + + Salvează +
+
+
+
+
+