Files
autocrm/app/Filament/Tenant/Pages/CalendarBoard.php
T
Vasyka bfe58ed286 Faza 1 (din lista de continuare): Calendar vizual cu FullCalendar 6
- 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.
2026-05-07 13:10:27 +00:00

165 lines
6.2 KiB
PHP

<?php
namespace App\Filament\Tenant\Pages;
use App\Models\Tenant\Appointment;
use App\Models\Tenant\Client;
use App\Models\Tenant\Post;
use App\Models\Tenant\User;
use App\Models\Tenant\Vehicle;
use Carbon\Carbon;
use Filament\Forms;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas;
use Filament\Schemas\Schema;
class CalendarBoard extends Page
{
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-calendar-days';
protected static ?string $navigationLabel = 'Calendar vizual';
protected static string|\UnitEnum|null $navigationGroup = 'CRM';
protected static ?int $navigationSort = 8;
protected static ?string $title = 'Calendar';
protected string $view = 'filament.tenant.pages.calendar';
public ?array $createData = [];
public ?array $editData = [];
public ?int $editId = null;
public function getEvents(string $start, string $end): array
{
return Appointment::with(['client:id,name', 'vehicle:id,plate,make,model', 'master:id,name,color', 'post:id,name,color'])
->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'];
}
}