--- url: /guide/introduction.md description: >- Scheduler Pro adds resource timelines, Gantt planning, availability-aware booking, recurring events and Kanban boards to Filament v4 and v5 panels. --- # Introduction Scheduler Pro adds resource timelines, Gantt planning, availability-aware booking, recurring events and Kanban boards to Filament panels. It reads your existing tables, edits through native Filament actions, and enforces the rules your business has: working hours, time off, buffers, double bookings and permissions. ## Who it's for * **Clinics and salons**: book people against staff and rooms that have working hours. * **Field teams and fleets**: assign jobs to vehicles or crews and see the week at a glance. * **Project teams**: plan with a Gantt chart and run the work on a board over the same model. * **Any Filament app** with a table of things that happen at a time, or move through stages. ## What's included **Scheduler** * Six views: timeline day, week and month (Gantt), calendar week, month and agenda. * Drag to move, drag edges to resize, drag across rows to reassign, draw on an empty row to create. * Hover previews, slot hints, conflict reasons on the drag ghost, undo for every change. * Availability, time off, buffers, your own rules and model policies. * Recurring series with "this event / all events". * A view slide-over for every event, with Edit and Delete. **Kanban** * Columns from an enum, an array, or a table your users manage. * Saved views as tabs with live counts, Display options per user, a `⋯` card menu. * Swimlanes, WIP limits, allowed transitions, keyboard drag and drop. **Everywhere** * Light and dark, right to left, English, French and Arabic. * Tenancy, time zones, global defaults. ## Requirements | Package | Version | |---|---| | PHP | 8.3+ | | Laravel | 11, 12, 13 | | Filament | 4.x, 5.x | Next: [Installation](/guide/installation). --- --- url: /guide/installation.md description: >- Install Scheduler Pro with Composer, publish its assets, and optionally publish translations. --- # Installation ## Require the package ```bash composer require hoceineel/filament-scheduler-pro ``` ## Publish the assets ```bash php artisan filament:assets ``` Filament's `filament:upgrade` hook republishes assets on every `composer update`, so this is only needed once. ## Generate a widget ```bash php artisan make:scheduler FrontDeskSchedule --model=Appointment --resource=Staff ``` Or a board: ```bash php artisan make:kanban TaskBoard --model=Task ``` See [Quick Start](/guide/quick-start). ## Translations English, French and Arabic ship in the box. Publish them to edit: ```bash php artisan vendor:publish --tag=scheduler-pro-translations ``` ## Panel colours Event and badge colours accept any Tailwind colour name (`sky`, `rose`…), hex values, or a `HasColor` enum. Names you registered on the panel with `->colors()` use your panel's shades. --- --- url: /guide/quick-start.md description: >- Generate a working Scheduler Pro widget from your tables in one command and put it on any Filament page. --- # Quick Start ## Generate ```bash php artisan make:scheduler FrontDeskSchedule --model=Appointment --resource=Staff ``` The generator reads your tables. It finds the start, end and title columns, the resource foreign key, and any `recurrence`, `progress` or `depends_on` columns, and writes a widget that works on first load: ```php [app/Filament/Widgets/FrontDeskSchedule.php] class FrontDeskSchedule extends SchedulerWidget { public function scheduler(Scheduler $scheduler): Scheduler { return $scheduler ->model(Appointment::class) ->resources(Staff::class) ->defaultView(SchedulerView::TimelineDay) ->visibleHours('08:00', '19:00') ->slotMinutes(15) ->preventOverlaps(); } } ``` ## Place it On any Filament page: ```php protected function getHeaderWidgets(): array { return [FrontDeskSchedule::class]; } ``` It is a regular Livewire component, so it works in any Blade view too: ```blade @livewire(\App\Filament\Widgets\FrontDeskSchedule::class) ``` ## Global defaults Set defaults for every scheduler in a service provider: ```php Scheduler::configureUsing(fn (Scheduler $scheduler) => $scheduler ->firstDayOfWeek(Carbon::MONDAY) ->slotMinutes(30)); ``` Next: [Your Data](/guide/your-data). --- --- url: /guide/your-data.md description: >- Point Scheduler Pro at your existing columns, resources and queries, and control how events and rows look. --- # Your Data Scheduler Pro works with your existing tables. Defaults are `starts_at`, `ends_at` and `title`. Point it elsewhere when yours differ: ```php $scheduler ->model(Shift::class) ->startAttribute('begins_at') ->endAttribute('finishes_at') ->titleAttribute('name') ->resources(Vehicle::class) ->resourceAttribute('vehicle_id') ->resourceTitleAttribute('plate') ->modifyQueryUsing(fn (Builder $query) => $query->where('cancelled', false)); ``` | Method | Purpose | |---|---| | `model()` | The event model. | | `startAttribute()` / `endAttribute()` | Datetime columns. Defaults `starts_at`, `ends_at`. | | `titleAttribute()` | Defaults `title`. | | `resources()` | Rows: a model class, a closure returning models, or any iterable. | | `resourceAttribute()` | The foreign key. Inferred from the relationship when possible. | | `resourceTitleAttribute()` | Row label. Rows are ordered by it. | | `modifyQueryUsing()` | Scope the events query. | ## Presentation ```php $scheduler ->eventColor(fn (Appointment $record) => $record->treatment) ->eventTitle(fn (Appointment $record) => $record->patient->name) ->eventDescription(fn (Appointment $record) => $record->status->getLabel()) ->resourceDescription(fn (Staff $resource) => $resource->role) ->resourceGroup(fn (Staff $resource) => $resource->team) ->resourceColor(fn (Staff $resource) => $resource->color) ->views([SchedulerView::TimelineDay, SchedulerView::TimelineWeek, SchedulerView::Agenda]) ->defaultView(SchedulerView::TimelineDay) ->visibleHours('07:00', '20:00') ->firstDayOfWeek(Carbon::MONDAY) ->slotMinutes(15) ->defaultEventMinutes(30) ->height('70vh'); ``` `eventColor()` accepts any `HasColor` enum, a Filament or Tailwind colour name, or a hex value. `resourceGroup()` creates collapsible row groups, like **Dentists**, **Hygienists** and **Rooms** in the demo. --- --- url: /guide/views.md description: >- Scheduler Pro's six views: timeline day, week and month, calendar week and month, and agenda. --- # Views Timeline views put resources in rows. Calendar views stack events by day. Every view you enable appears in one segmented control, and the view, zoom and filter a user last chose are remembered in their browser. ```php $scheduler ->views([ SchedulerView::TimelineDay, SchedulerView::TimelineWeek, SchedulerView::TimelineMonth, SchedulerView::Week, SchedulerView::Month, SchedulerView::Agenda, ]) ->defaultView(SchedulerView::TimelineDay); ``` ## Timeline day ## Timeline week ## Calendar week ## Calendar month ## Agenda ## Timeline month (Gantt) `TimelineMonth` is the Gantt view. See [Gantt](/guide/gantt). ## Mobile On phones the toolbar condenses to icons and the resource column narrows. Press and hold an event to pick it up; everything else scrolls normally. --- --- url: /guide/interactions.md description: >- Hover previews, slot hints, drag with live conflict feedback, native create modals, a date picker, a resource filter and zoom. --- # Working with the Schedule ## Hover to preview Truncated bars never hide information. Resting on an event shows its time, duration, resource, description and progress without opening anything. ## See the slot before you book it Hovering an empty row outlines the slot a click would create. If the slot is outside working hours or clashes with time off, the outline says why before you commit. ## Drag with feedback The ghost shows the new time while you drag and turns red with the reason when a rule would refuse the drop. Dragging near an edge scrolls the timeline. `Esc` cancels any drag, including drawing a new event. ## Book in a native modal Drawing across a row, or clicking a hinted slot, opens your Filament form in a slide-over with the resource and times already filled in. ## Jump anywhere, focus on the people you need Click the date title to open a month picker. The filter searches resources, hides any of them, or isolates one with **Only**. | Date picker | Resource filter | | --- | --- | | ![Date picker](/images/picker.png) | ![Resource filter](/images/filter.png) | ## Zoom Zoom timelines with the `−` `+` buttons, the `-` `+` keys, or `Ctrl`/`⌘` + scroll. Zoom is anchored on the pointer. ## Undo Every move and resize shows a toast with **Undo**. --- --- url: /guide/keyboard.md description: >- Every Scheduler Pro keyboard shortcut: navigation, views, moving events, filtering, zoom and the shortcut sheet. --- # Keyboard Press `?` inside the scheduler for the full list. | Key | Action | | --- | --- | | `T` | Go to today | | `←` `→` | Previous or next period | | `1`–`6` | Switch view | | `Enter` | Open the focused event | | `Alt` + `←` `→` | Move the focused event in time | | `Alt` + `↑` `↓` | Move the focused event to the previous or next resource (timelines) | | `/` | Filter resources | | `+` `-` | Zoom the timeline | | `Ctrl`/`⌘` + scroll | Zoom around the pointer | | `?` | Show the shortcut sheet | | `Esc` | Cancel a drag or close a panel | ## Where keys go Shortcuts work whenever focus is inside the scheduler, and also when nothing on the page has focus, for example right after clicking the grid. With several schedulers on one page, keys go to the one you last clicked. Keyboard moves run the same rules as dragging. A refused move shows its reason in a toast and focus stays on the event. --- --- url: /guide/event-details.md description: >- Clicking an event opens a slide-over with its details, Edit and Delete. Customise the infolist or skip straight to the form. --- # Event Details Clicking an event, or pressing `Enter` on a focused one, opens a slide-over with its start, end, duration, resource, repeat rule, progress and description. **Edit** swaps to the form in place; **Delete** removes it. ## Customise the details ```php $scheduler->infolist(fn (array $defaults) => [ ...$defaults, TextEntry::make('patient.phone')->label('Phone'), ]); ``` ## Skip to the form ```php $scheduler->viewable(false); ``` ## Centred modals Create, edit and view open as slide-overs. Use centred modals instead: ```php $scheduler->slideOver(false); ``` ## Actions The actions are native Filament actions on the widget: `viewEvent`, `createEvent`, `editEvent` and `deleteEvent`. Override any of them on your widget class the usual Filament way. --- --- url: /guide/forms.md description: >- Scheduler Pro builds a create and edit form from your attributes. Extend it, replace it, or control persistence. --- # Forms With no schema, Scheduler Pro builds a form from your attributes: title, resource, start, end, progress, dependencies and repeat. Extend it or replace it: ```php $scheduler->schema(fn (array $defaults) => [ ...$defaults, Select::make('phase')->options(Phase::class)->required(), ]); ``` Create and edit run as native Filament actions (`createEvent`, `editEvent`), so everything you know about Filament forms applies. * Field defaults are kept, and the dragged slot is filled in. * Rule violations appear as notifications and keep the modal open. * `RecurrenceField::field()` adds the **Repeat** section to your own schema. ## Persistence ```php $scheduler->createRecordUsing(fn (array $data) => Appointment::create([ ...$data, 'booked_by' => auth()->id(), ])); ``` ## Who can create ```php $scheduler->creatable(fn () => auth()->user()->isReceptionist()); ``` --- --- url: /guide/availability.md description: >- Give resources working hours and time off. Scheduler Pro draws them on the grid and can refuse bookings outside them. --- # Availability & Time Off Implement the contract on the resource model: ```php [app/Models/Staff.php] class Staff extends Model implements HasSchedulerAvailability { public function getSchedulerAvailability(): Availability { return Availability::make() ->weekdays('09:00-13:00', '14:00-18:00') ->on(Carbon::SATURDAY, '10:00-14:00') ->timeOff('2026-10-05 00:00', '2026-10-05 23:59', 'Conference'); } } ``` Or configure it on the scheduler: ```php $scheduler->resourceAvailability(fn (Staff $resource) => Availability::make()->weekdays('08:00-16:00')); ``` Night shifts such as `22:00-06:00` are supported. ## On the grid Non-working hours are hatched, time off is labelled, and each row shows how booked it is for the visible range. ## Enforce it ```php $scheduler->enforceAvailability(); ``` Bookings outside working hours or during time off are then refused, with the reason shown on the drag ghost and in the modal. --- --- url: /guide/rules.md description: >- Prevent double bookings with optional buffers, and add your own validation rules that run for drags, keyboard moves and modals. --- # Conflicts & Rules ## Overlaps ```php $scheduler->preventOverlaps(bufferMinutes: 10); ``` Overlapping bookings on the same resource are refused, with the clashing event named in the message. The buffer keeps a gap between bookings. ## Your own rules ```php $scheduler->validateUsing(function (Appointment $record, CarbonImmutable $start, ?Staff $resource): ?string { return $start->isWeekend() && ! $resource?->works_weekends ? 'Weekend bookings need a weekend practitioner.' : null; }); ``` Return a message to refuse the change, or `null` to allow it. ## Where rules run The same rules run for every change: * drag to move and drag to resize * drag across rows * keyboard moves with `Alt` + arrows * the create and edit modals The browser flags conflicts while you drag; the server re-checks before saving. --- --- url: /guide/permissions.md description: >- Scheduler Pro checks model policies automatically and supports your own gates for creating and editing events. --- # Permissions When a policy exists for the event model, `create`, `update` and `delete` are checked automatically. Locked events can't be dragged, and their modal opens read-only. Add your own gates: ```php $scheduler ->editable(fn (Appointment $record) => $record->status !== Status::Completed) ->creatable(fn () => auth()->user()->can('book')); ``` Turn policy checks off: ```php $scheduler->authorizeWithPolicies(false); ``` --- --- url: /guide/recurring-events.md description: >- Recurring series in Scheduler Pro: daily, weekly, monthly and yearly repeats, with this-event or all-events changes. --- # Recurring Events ## Add two JSON columns ```php $table->json('recurrence')->nullable(); $table->json('recurrence_exceptions')->nullable(); ``` ## Cast both to array ```php protected function casts(): array { return ['recurrence' => 'array', 'recurrence_exceptions' => 'array']; } ``` ## Point the scheduler at them ```php $scheduler->recurrenceAttribute('recurrence'); ``` ## How it behaves * The built-in form gains a **Repeat** section: daily, weekly on chosen days, monthly or yearly, with an interval, an end date or a count. * Dragging an occurrence asks whether to move just that event or the whole series. * Moving or editing a single occurrence detaches it into its own record and adds an exception to the series. Use `RecurrenceField::field()` in your own form schema. --- --- url: /guide/gantt.md description: >- Turn the timeline month view into a Gantt chart with dependency arrows, progress bars and milestones. --- # Gantt ```php $scheduler ->progressAttribute('progress') // 0–100 ->dependenciesAttribute('depends_on') // array of event keys ->defaultView(SchedulerView::TimelineMonth); ``` * Dependencies are drawn as arrows. When a task starts before the task it depends on has finished, its arrow turns red and dashed. * Progress fills the bar. * Zero-length events render as milestones. The demo runs a Gantt chart and a [Kanban board](/kanban/overview) over the same `Task` model. --- --- url: /kanban/overview.md description: >- Kanban boards for any Filament model with a status column: views, managed columns, swimlanes, WIP limits and transitions. --- # Kanban Overview The package ships a board for any model with a status column. It lives next to your scheduler: the demo's launch plan is a Gantt chart and a board over the same `Task` model. ## Generate ```bash php artisan make:kanban TaskBoard --model=Task --swimlanes=Member ``` The generator finds the status column (and its enum cast), a sort column, a description, a due date and progress, and writes: ```php [app/Filament/Widgets/TaskBoard.php] class TaskBoard extends KanbanWidget { public function kanban(Kanban $kanban): Kanban { return $kanban ->model(Task::class) ->columns(TaskStatus::class) ->sortAttribute('sort') ->swimlanes(Member::class) ->cardDescription(fn (Task $record): ?string => $record->summary) ->cardDate('due_at') ->cardProgress('progress'); } } ``` Place it like any widget: ```php protected function getHeaderWidgets(): array { return [TaskBoard::class]; } ``` ## At a glance | Feature | Page | |---|---| | Enum, array or `Column` columns | [Columns](/kanban/columns) | | Columns people add, recolour and reorder | [User-Managed Columns](/kanban/managed-columns) | | Descriptions, badges, dates, progress, avatars, stats | [Cards](/kanban/cards) | | Tabs with live counts | [Saved Views](/kanban/views) | | Grouping, ordering, density, `⋯` menu | [Display & Card Menu](/kanban/display) | | Allowed moves, WIP limits, rules, hooks | [Workflow Rules](/kanban/workflow) | --- --- url: /kanban/columns.md description: >- Kanban columns from a backed enum, an array, or Column objects with colours, icons, limits and descriptions. --- # Columns Columns come from a backed enum. Its `HasLabel`, `HasColor` and `HasIcon` implementations become the column header. There is no custom trait to add. ```php ->columns(TaskStatus::class) ``` Enums that implement Filament's `HasDescription` get a subtitle under the column name. ## Arrays and Column objects ```php ->columns([ 'todo' => 'To do', Column::make('doing')->label('In progress')->color('indigo')->icon(Heroicon::OutlinedPlayCircle)->limit(3), Column::make('done')->collapsed(), ]) ``` | `Column` method | Purpose | |---|---| | `label()` | Header text. | | `color()` | Any Tailwind colour name, Filament colour or hex. | | `icon()` | A Heroicon or icon name. | | `description()` | Subtitle under the name. | | `limit()` | WIP limit for the column. | | `collapsed()` | Start collapsed to a rail. | ## Status column ```php ->statusAttribute('stage') ``` Needed only when the column isn't called `status`. ## Collapsing People collapse columns to a rail from the header. The choice is remembered per user. ## Colours Any Tailwind colour name works for columns, badges and details, whether or not your panel registers it. --- --- url: /kanban/managed-columns.md description: >- Let people add, rename, recolour, reorder and delete Kanban columns stored in a table, like GitHub Projects. --- # User-Managed Columns Like GitHub Projects, a board can keep its columns in a table so people add, rename, recolour, reorder and delete them without a deploy. ## Create the table ```php Schema::create('stages', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('color')->nullable(); $table->string('description')->nullable(); $table->unsignedInteger('wip_limit')->nullable(); $table->unsignedInteger('sort')->default(0); $table->timestamps(); }); ``` ## Point the board at it ```php $kanban ->statusAttribute('stage_id') ->columns(Stage::class) ->manageColumns(); ``` ## Attribute names Rows are read from `name`, `color`, `description`, `wip_limit` and `sort`. Rename any of them, or pass `null` to leave one out: ```php ->columnAttributes(label: 'title', limit: null) ``` ## The column menu A column's `⋯` menu edits it, moves it left or right, and deletes it. Deleting a column that still holds cards asks where they should go. Colours are picked from swatches. ## Scoped boards For boards scoped to a project or tenant, pass a closure and say how to create: ```php ->columns(fn (): Collection => $this->project->stages()->orderBy('sort')->get()) ->createColumnUsing(fn (array $data): Stage => $this->project->stages()->create($data)) ->manageColumns(fn (): bool => auth()->user()->can('manageBoard', $this->project)) ``` --- --- url: /kanban/cards.md description: >- Kanban card content: rich descriptions, categories, badges with icons, relative due dates, progress, avatars and stats. --- # Cards ```php $kanban ->titleAttribute('title') ->cardDescription(fn (Task $record): ?string => $record->summary) ->cardColor(fn (Task $record): Phase => $record->phase) ->cardBadges(fn (Task $record): array => [$record->priority, 'Blocked' => 'danger']) ->cardDate('due_at') ->cardProgress('progress') ->cardAvatars(fn (Task $record): iterable => $record->assignees) ->cardStats(fn (Task $record): array => [ Stat::make('Subtasks', "{$record->done}/{$record->total}")->icon(Heroicon::OutlinedCheckCircle), Stat::make('Comments', $record->comments_count)->icon(Heroicon::OutlinedChatBubbleLeft), 'Points' => $record->points, ]); ``` | Method | What it shows | |---|---| | `cardDescription()` | Two lines of plain text. RichEditor HTML is accepted and shown in full in the card view. | | `cardColor()` | A coloured dot. A `HasLabel` enum also names the card's category. | | `cardBadges()` | Enums with their colour and icon, or `label => colour` pairs. | | `cardDate()` | "tomorrow", "in 3 days", red once overdue. | | `cardProgress()` | 0–100, drawn along the card's bottom edge. | | `cardAvatars()` | Models or names. | | `cardStats()` | `Stat` objects or `label => value` pairs, with icons. | ## Rules of thumb * Details with an empty value are skipped. * With swimlanes on, an avatar that repeats the lane's owner is hidden. * A card in the last column is never marked overdue. --- --- url: /kanban/views.md description: >- Saved Kanban views as tabs with live counts, plus built-in Overdue and Due this week tabs. --- # Saved Views Saved views sit above the board as tabs with live counts, the way GitHub Projects does it. Each view is a query: ```php ->views([ BoardView::make('mine')->label('Assigned to me')->icon(Heroicon::OutlinedUser) ->query(fn (Builder $query) => $query->whereBelongsTo(auth()->user(), 'assignee')), BoardView::make('urgent')->label('High priority')->icon(Heroicon::OutlinedFire) ->query(fn (Builder $query) => $query->withPriority([Priority::Urgent, Priority::High])), ]) ``` Boards with due dates also get **Overdue** and **Due this week** tabs, shown only while they have cards. The chosen tab is remembered per user. --- --- url: /kanban/display.md description: >- Per-user Display options for grouping, ordering, density and card fields, plus the card menu for moving, duplicating and deleting. --- # Display & Card Menu ## Display **Display** lets each person group by swimlane or not, order cards by hand, due date or title, pick comfortable or compact density, and choose which details show on cards. Set the starting density: ```php ->density(KanbanDensity::Compact) ``` ## Card menu Every card has a `⋯` menu, also opened with right-click or `.` on a focused card: * **Open** and **Edit** * **Move to top** and **Move to bottom** * **Move to** a column, **Move to lane** * **Duplicate** and **Delete** It is the keyboard and screen reader alternative to dragging, and focus stays on the card after every move. --- --- url: /kanban/ordering.md description: >- Manual card ordering with single-row writes, midpoint positions and automatic rebalancing. --- # Ordering ```php ->sortAttribute('sort') ``` With a sort attribute, people reorder cards by hand. * The board sends only the new neighbours. The server writes a single row with a position halfway between them. * When positions get too close, or legacy rows have none, that column is renumbered once in the same transaction. * Float, decimal and integer columns all work, so an existing `order_column` from spatie/eloquent-sortable can be reused. ::: tip Index `(status, sort)` for large boards. ::: Without a sort attribute, cards follow your query's order and only change column. --- --- url: /kanban/workflow.md description: >- Allowed Kanban transitions, WIP limits, custom rules, after-move hooks and policy-based permissions. --- # Workflow Rules ## Allowed moves Implement `HasKanbanTransitions` on the status enum: ```php enum TaskStatus: string implements HasColor, HasIcon, HasKanbanTransitions, HasLabel { public function getTransitions(): array { return match ($this) { self::Backlog => [self::Todo], self::Todo => [self::Backlog, self::InProgress], self::InProgress => [self::Todo, self::Review], self::Review => [self::InProgress, self::Done], self::Done => [self::Review], }; } } ``` Or define them on the board: ```php ->transitions(['todo' => [TaskStatus::InProgress]]) ``` Columns you don't list accept anything. Refused moves are flagged while you drag, before you drop: ## WIP limits ```php ->wipLimits(['in_progress' => 3]) ``` The header shows `3 / 3`, amber at the limit and red above it. Limits warn by default, like Jira. Refuse moves into a full column: ```php ->wipLimits(['in_progress' => 3], enforce: true) // or ->enforceWipLimits() ``` ## Your own rules ```php ->validateUsing(fn (Task $record, TaskStatus $to, ?Member $lane): ?string => ...) ``` Return a message to refuse a move. ## Hooks ```php ->afterMove(fn (Task $record, TaskStatus $from, TaskStatus $to) => ...) ``` Runs after a move is saved, for notifications or activity logs. ## Permissions With a policy on the model, `update` controls dragging and editing, `create` controls adding, and `delete` the delete action. Locked cards show a lock and can't be picked up. Every rule runs on the server for drags, keyboard moves, inline adds and both modals. --- --- url: /kanban/swimlanes.md description: >- Group a Kanban board into rows by owner or any relationship. Drag across rows to reassign. --- # Swimlanes ```php ->swimlanes(Member::class) ``` The foreign key is inferred from `Task::member()`. Set it when it isn't: ```php ->swimlaneAttribute('owner_id') ``` * Dragging across rows reassigns the card. * Cards without a lane collect in an **Unassigned** row. * People can hide lanes, isolate one with **Only**, collapse rows, or switch lanes off with **Display → No grouping**. ![Board without grouping](/images/board-flat.png) --- --- url: /kanban/editing.md description: >- Add cards inline, view them in a slide-over with Edit and Delete, and customise the infolist and form. --- # Adding & Editing ## Inline add Each column has an inline composer. Type a title, press `Enter` to add it and keep typing the next one, `Esc` to close. New cards go to the bottom of their column and lane. ## Card details Clicking a card opens it in a slide-over with its status, lane, due date, labels, details and full description, and **Edit** and **Delete** in the footer. ```php ->infolist(fn (array $defaults) => [...$defaults, TextEntry::make('owner.name')]) ->viewable(false) // skip straight to the form ->slideOver(false) // centred modals ``` ## The form ```php ->schema(fn (array $defaults) => [ ...$defaults, Select::make('priority')->options(Priority::class), RichEditor::make('summary'), ]) ``` Exactly as on the scheduler. | Drag with a drop line | Inline add | | --- | --- | | ![Dragging a card](/images/board-drag.png) | ![Inline composer](/images/board-quick-add.png) | --- --- url: /kanban/keyboard.md description: >- Kanban keyboard drag and drop with screen reader announcements, and every board shortcut. --- # Keyboard & Accessibility Cards are focusable. Every move is announced, for example "In progress, position 2 of 4". | Key | Action | |---|---| | Arrows | Move focus between cards | | `Space` | Pick up or drop the focused card | | `↑` `↓` while holding | Move within a column and across lanes | | `←` `→` while holding | Move across columns | | `Esc` | Put the card back | | `Enter` | Open the card | | `.` | Open the card menu | | `N` | Add a card to the focused column | | `/` | Search | | `?` | List the shortcuts | ![Moving a card with the keyboard](/images/board-keyboard.png) ## Search and filters The search box matches titles, descriptions, badges and avatars instantly. When filters hide cards, the toolbar says how many and offers **Clear filters**. Lane filters, collapsed columns and the grouping choice are remembered per user. ## Dark, RTL and phones | Arabic, right to left | Phone | | --- | --- | | ![Board in Arabic](/images/board-rtl.png) | | --- --- url: /reference/configuration.md description: >- Every Scheduler and Kanban configuration method in one place, with links to the guide pages that explain them. --- # Configuration ## Scheduler | Method | Page | |---|---| | `model()`, `modifyQueryUsing()` | [Your Data](/guide/your-data) | | `startAttribute()`, `endAttribute()`, `titleAttribute()` | [Your Data](/guide/your-data) | | `resources()`, `resourceAttribute()`, `resourceTitleAttribute()` | [Your Data](/guide/your-data) | | `resourceDescription()`, `resourceColor()`, `resourceGroup()` | [Your Data](/guide/your-data#presentation) | | `eventTitle()`, `eventDescription()`, `eventColor()` | [Your Data](/guide/your-data#presentation) | | `views()`, `defaultView()` | [Views](/guide/views) | | `visibleHours()`, `slotMinutes()`, `defaultEventMinutes()`, `firstDayOfWeek()`, `height()` | [Your Data](/guide/your-data#presentation) | | `infolist()`, `viewable()`, `slideOver()` | [Event Details](/guide/event-details) | | `schema()`, `createRecordUsing()`, `creatable()` | [Forms](/guide/forms) | | `resourceAvailability()`, `enforceAvailability()` | [Availability](/guide/availability) | | `preventOverlaps()`, `validateUsing()` | [Conflicts & Rules](/guide/rules) | | `editable()`, `authorizeWithPolicies()` | [Permissions](/guide/permissions) | | `recurrenceAttribute()` | [Recurring Events](/guide/recurring-events) | | `progressAttribute()`, `dependenciesAttribute()` | [Gantt](/guide/gantt) | | `timezone()`, `scopeToTenant()` | [Tenancy & Time Zones](/reference/tenancy) | ## Kanban | Method | Page | |---|---| | `model()`, `modifyQueryUsing()`, `statusAttribute()`, `titleAttribute()` | [Overview](/kanban/overview) | | `columns()` | [Columns](/kanban/columns) | | `manageColumns()`, `columnAttributes()`, `createColumnUsing()` | [User-Managed Columns](/kanban/managed-columns) | | `cardTitle()`, `cardDescription()`, `cardColor()`, `cardBadges()`, `cardDate()`, `cardProgress()`, `cardAvatars()`, `cardStats()` | [Cards](/kanban/cards) | | `views()` | [Saved Views](/kanban/views) | | `density()` | [Display](/kanban/display) | | `sortAttribute()` | [Ordering](/kanban/ordering) | | `transitions()`, `wipLimits()`, `enforceWipLimits()`, `validateUsing()`, `afterMove()` | [Workflow Rules](/kanban/workflow) | | `swimlanes()`, `swimlaneAttribute()`, `swimlaneTitleAttribute()`, `swimlaneDescription()`, `swimlaneColor()` | [Swimlanes](/kanban/swimlanes) | | `quickCreate()`, `schema()`, `createRecordUsing()`, `creatable()`, `editable()` | [Adding & Editing](/kanban/editing) | | `infolist()`, `viewable()`, `slideOver()` | [Adding & Editing](/kanban/editing#card-details) | ## Global defaults ```php Scheduler::configureUsing(fn (Scheduler $scheduler) => $scheduler->firstDayOfWeek(0)->slotMinutes(30)); Kanban::configureUsing(fn (Kanban $kanban) => $kanban->density(KanbanDensity::Compact)); ``` --- --- url: /reference/commands.md description: >- Artisan commands that generate Scheduler Pro widgets and Kanban boards from your tables. --- # Commands ## make:scheduler ```bash php artisan make:scheduler FrontDeskSchedule --model=Appointment --resource=Staff ``` | Option | Purpose | |---|---| | `name` | The widget class, e.g. `FrontDeskSchedule`. Asked for when omitted. | | `--model` | The Eloquent model that stores events. | | `--resource` | The Eloquent model shown as timeline rows. | | `--force` | Overwrite the widget if it exists. | Reads the table to find start, end and title columns, the resource foreign key, and `recurrence`, `progress` or `depends_on` columns. ## make:kanban ```bash php artisan make:kanban TaskBoard --model=Task --swimlanes=Member ``` | Option | Purpose | |---|---| | `name` | The widget class, e.g. `TaskBoard`. | | `--model` | The Eloquent model shown as cards. | | `--swimlanes` | The Eloquent model shown as swimlanes. | | `--force` | Overwrite the widget if it exists. | Finds the status column and its enum cast, a sort column, a description, a due date and progress. ## Assets and translations ```bash php artisan filament:assets php artisan vendor:publish --tag=scheduler-pro-translations ``` --- --- url: /reference/theming.md description: >- Scheduler Pro follows your panel's colours, radius, font and dark mode, mirrors for RTL, and exposes CSS variables. --- # Theming & RTL The widgets use your panel's colour, gray, radius and font tokens, follow the panel's dark mode, and mirror themselves for right-to-left locales. ## Right to left Arabic ships in the box. With an RTL locale, the timeline, toolbar, board and menus mirror. | Scheduler | Board | | --- | --- | | ![Scheduler in Arabic](/images/rtl.png) | ![Board in Arabic](/images/board-rtl.png) | ## CSS variables Override any `--sp-*` variable on `.sp-root`: ```css .sp-root { --sp-side: 280px; --sp-now: var(--primary-500); } ``` ## Motion Animations respect `prefers-reduced-motion`. --- --- url: /reference/tenancy.md description: >- Scheduler Pro scopes queries to the current Filament tenant and displays times in each user's time zone. --- # Tenancy & Time Zones ## Tenancy Inside a tenant-aware panel, queries are scoped to the current tenant and new records are associated with it. Opt out: ```php $scheduler->scopeToTenant(false); ``` ## Time zones Data is stored in `app.timezone` and displayed in the zone you give: ```php $scheduler->timezone(fn () => auth()->user()->timezone); ``` --- --- url: /reference/translations.md description: >- Scheduler Pro ships English, French and Arabic. Publish the language files to edit them or add a locale. --- # Translations English, French and Arabic ship in the box. ```bash php artisan vendor:publish --tag=scheduler-pro-translations ``` Files land in `lang/vendor/scheduler-pro/{locale}/`: | File | Covers | |---|---| | `scheduler.php` | Scheduler toolbar, views, rules, actions, shortcut sheet. | | `kanban.php` | Board toolbar, views, menus, columns, announcements. | Add a locale by copying the `en` folder. --- --- url: /reference/testing.md description: >- Test your scheduler rules and Kanban boards with Pest and Livewire: the widget endpoints are plain Livewire methods. --- # Testing The widgets' endpoints are plain Livewire methods, so you can test your rules directly. ## Scheduler moves ```php $result = Livewire::test(FrontDeskSchedule::class)->instance() ->moveEvent((string) $appointment->id, '2026-09-24T14:00:00', '2026-09-24T15:00:00', (string) $staff->id); expect($result['ok'])->toBeFalse() ->and($result['rejection']['reason'])->toBe('unavailable'); ``` ## Actions Create, view, edit and delete are native Filament actions: ```php use Filament\Actions\Testing\TestAction; Livewire::test(FrontDeskSchedule::class) ->mountAction(TestAction::make('viewEvent')->arguments(['event' => (string) $appointment->id])) ->assertMountedActionModalSee($appointment->title); Livewire::test(FrontDeskSchedule::class) ->callAction('editEvent', ['title' => 'Renamed'], ['event' => (string) $appointment->id]) ->assertNotified(); ``` ## Kanban ```php Livewire::test(TaskBoard::class) ->callAction('editCard', ['title' => 'Renamed'], ['card' => (string) $task->id]); ``` --- --- url: /reference/errors.md description: Scheduler Pro fails misconfiguration with messages that name the fix. --- # Errors Misconfiguration fails with a message that names the fix, for example: > Scheduler Pro: \[App\Models\Appointment::$recurrence_exceptions] must be cast to an array. Add 'recurrence_exceptions' => 'array' to the model's casts() method. Rule refusals are not errors. They return a reason (`unavailable`, `conflict`, `forbidden`, `transition`, `limit`, `custom`) shown on the drag ghost, in a toast, or in the modal. --- --- url: /changelog.md description: >- Scheduler Pro changelog: release notes for every version, listing added features, changes and fixes. --- # Changelog ## Unreleased * Kanban boards: `KanbanWidget`, enum columns, swimlanes, WIP limits, allowed transitions (`HasKanbanTransitions`), midpoint ordering with automatic rebalancing, inline add, keyboard drag and drop with screen reader announcements, undo, `make:kanban` generator. * Kanban views, card menu and user-managed columns: `BoardView` tabs with live counts plus built-in Overdue and Due this week, a Display panel (grouping, ordering, density, visible fields), a `⋯` card menu (move, duplicate, delete) on click, right-click and `.`, slide-over card details with `->infolist()`, `->cardStats()` with `Stat`, rich text descriptions, relative due dates, column descriptions from `HasDescription`, model-backed columns with `->manageColumns()` (add, edit, reorder, delete with card reassignment), and any Tailwind colour name without panel registration. * Fixed: board requests made after a menu or keyboard action could silently skip reloading, because `$wire` was resolved from an element that had been removed. * Scheduler toolbar: date picker, resource filter, zoom, hover previews, slot hints, now label, remembered view, zoom and filter. * Assets are versioned by content, so browsers pick up rebuilt files immediately. ## 1.0.0 - 2026-09-24 * Resource timeline (day, week, month), calendar week, month and agenda views. * Drag to move and reassign, resize from either edge, draw to create, keyboard rescheduling, undo. * Availability rules, time off, overlap prevention with buffers, custom validation, policy authorisation. * Recurring events with per-occurrence changes and series moves. * Gantt dependencies with late-link highlighting, progress bars and milestones. * `make:scheduler` generator, resource key inference, `HasSchedulerAvailability` contract. * Light and dark themes, RTL, English, French and Arabic translations.