feat: calendar enhancements — view modes, post CRUD, PDF, list
Closes 5 user-requested features in /app/calendar-board:
1. View mode switcher: Zi / Săpt / Lună / Custom / Listă
2. Editable post names + assignable default master per bay
3. Quick-add bay (+ Pod nou) from calendar toolbar — supports yard
spaces without a lift ("Curte 1", "Atelier electric")
4. PDF export of programări for printing
5. Inline list view alongside the matrix view
== View modes ==
$viewMode: day | week | month | custom | list
- Day view: 1 column, just today (or navigated day). Shift moves day by day.
- Week view: current 7-column matrix (unchanged default).
- Month view: 30/31 columns shown smaller (70px each). Shift moves by month.
- Custom: 2 date pickers for arbitrary start..end range (max 31 days).
- List view: flat sortable table with Data/Ora/Subiect/Client/Telefon/
Auto/Pod/Maistru/Status columns. Click row → opens detail panel.
getDays() computes the right day count + start anchor for each mode.
setViewMode() snaps weekStart to the right anchor (startOfMonth, today,
startOfWeek). shiftWeek delta semantics adapt: day mode shifts 1 day,
month mode shifts 1 month, others shift 7 days.
== Editable posts + default master ==
New PostResource (/app/posts) in Admin group: full CRUD with name,
color, hours_per_day, default_master_id, description, is_active,
sort_order. Gated by ADMIN_SETTINGS_EDIT.
Migration: posts.default_master_id FK → users (nullOnDelete).
Inline rename from calendar: click any post's row label opens a modal
with name field + default master dropdown. Saved values propagate
immediately to next appointment creation.
Auto-fill in new appointment: when creating an appointment via the "+"
cell button on a post row, master_id is pre-filled from
post.default_master_id (if not already set by groupBy='master' row).
== Quick-add bay ==
"+ Pod nou" button in toolbar opens a small modal (no full page nav):
name, color picker, hours/day, description. createPost() saves and
refreshes the row list. Designed for "yard space" use-cases — names
like "Curte 1" or "Atelier electric" are first-class, not workarounds.
== PDF export ==
"🖨 PDF programări" button calls exportPdf() which uses the existing
dompdf integration (already installed). Renders pdf/appointments.blade.php
grouped by day with table per day showing time/title/client+vehicle/
post/master/status. Romanian date headers ("Marți, 10 Iunie 2026").
streamDownload with filename programari_YYYY-MM-DD_YYYY-MM-DD.pdf.
== List view ==
getListAppointments() returns flat array of all appointments in the
visible period (date-range respects current viewMode), with full
client/vehicle/post/master joined. Status filter respected. Row click
opens the existing event detail panel.
== Tests ==
CalendarEnhancementsTest (8):
- viewMode='day' returns 1 day
- viewMode='month' returns 30 days for June 2026
- viewMode='custom' uses customStart..customEnd range
- quick-add post via Livewire createPost persists with all fields
- rename post updates name + default_master_id
- new appointment auto-fills master_id from post's default_master_id
- list view returns flat array with phone + post name joined
- exportPdf returns StreamedResponse with .pdf filename
Suite: 285 passed (802 assertions). Was 277.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Tenant\Resources;
|
||||
|
||||
use App\Auth\Permissions;
|
||||
use App\Filament\Tenant\Resources\PostResource\Pages;
|
||||
use App\Models\Tenant\Post;
|
||||
use App\Models\Tenant\User;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PostResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Post::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-wrench-screwdriver';
|
||||
|
||||
protected static ?string $navigationLabel = 'Posturi de lucru';
|
||||
|
||||
protected static string|\UnitEnum|null $navigationGroup = 'Admin';
|
||||
|
||||
protected static ?string $modelLabel = 'pod';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'posturi de lucru';
|
||||
|
||||
protected static ?int $navigationSort = 76;
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
return auth()->user()?->canDo(Permissions::ADMIN_SETTINGS_EDIT) ?? false;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Pod / Spațiu lucru')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Nume')
|
||||
->required()
|
||||
->maxLength(80)
|
||||
->placeholder('Ex: Pod 1, Curte 1, Atelier electric'),
|
||||
Forms\Components\ColorPicker::make('color')
|
||||
->default('#3b82f6'),
|
||||
Forms\Components\TextInput::make('hours_per_day')
|
||||
->label('Ore disponibile / zi')
|
||||
->numeric()
|
||||
->step(0.5)
|
||||
->default(10)
|
||||
->helperText('Capacitatea zilnică în ore'),
|
||||
Forms\Components\Select::make('default_master_id')
|
||||
->label('Mecanic implicit')
|
||||
->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->placeholder('Niciun mecanic implicit')
|
||||
->helperText('Va fi pre-completat când creezi o programare pentru acest pod'),
|
||||
Forms\Components\TextInput::make('description')
|
||||
->label('Descriere')
|
||||
->maxLength(255)
|
||||
->placeholder('Ex: cu lift, fără lift, doar diagnoză...')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('sort_order')
|
||||
->numeric()
|
||||
->default(100),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\ColorColumn::make('color'),
|
||||
Tables\Columns\TextColumn::make('hours_per_day')->label('Ore/zi')->sortable(),
|
||||
Tables\Columns\TextColumn::make('defaultMaster.name')->label('Mecanic implicit')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('description')->placeholder('—')->limit(40)->toggleable(),
|
||||
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label('Programări')->badge(),
|
||||
Tables\Columns\ToggleColumn::make('is_active')->label('Activ'),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->defaultSort('sort_order');
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPosts::route('/'),
|
||||
'create' => Pages\CreatePost::route('/create'),
|
||||
'edit' => Pages\EditPost::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Tenant\Resources\PostResource\Pages;
|
||||
|
||||
use App\Filament\Tenant\Resources\PostResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePost extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Tenant\Resources\PostResource\Pages;
|
||||
|
||||
use App\Filament\Tenant\Resources\PostResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPost extends EditRecord
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [Actions\DeleteAction::make()];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Tenant\Resources\PostResource\Pages;
|
||||
|
||||
use App\Filament\Tenant\Resources\PostResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPosts extends ListRecords
|
||||
{
|
||||
protected static string $resource = PostResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [Actions\CreateAction::make()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user