Задача: разработать веб-приложение для управления расписанием занятий в учебном заведении.
Требования к функционалу:
- Управление структурой данных:
— добавление новых дней недели, занятий, групп, преподавателей и аудиторий;
— редактирование существующих данных;
— удаление ненужных данных. - Отображение расписания:
— вывод расписания занятий на определённый день недели;
— возможность фильтрации данных по различным параметрам (группа, преподаватель, аудитория и т. д.);
— отображение информации о занятом и свободном времени в расписании. - Управление событиями:
— добавление событий (например, замена преподавателя, отмена занятия и т. п.);
— отображение событий в календаре;
— возможность редактирования и удаления событий. - Статистика:
— отображение статистики использования аудиторий, преподавателей и других параметров;
— возможность анализа данных для оптимизации расписания.
- Установка проекта
- Models and migrations
- Аудитория(Classroom)
- Преподаватель(Teacher)
- Группа(Group)
- Расписание занятий(Lesson)
- Создание factories и seeders
- Создание ресурсов
- Ресурс расписания
- Фильтр
- Таблица
- Действия
- Форма
- Генерация расписания на неделю
- Создание экспорта для пользователя сайта
- Создание календаря для расписания занятий
- Заключение
Установка проекта
Для начала установим проект и подключим нужные библиотеки
1 2 3 4 |
composer create-project laravel/laravel university-schedule --prefer-dist php artisan vendor:publish --tag=laravel-assets --ansi --force php artisan key:generate --ansi php artisan migrate --graceful --ansicd university-schedule |
Теперь можно подключить филамент
1 2 |
composer require filament/filament php artisan filament:install --panels |
Models and migrations
В нашем проекте будут следующие сущности
- Classroom — Аудитория
- Teacher — Преподаватель
- Group — Группа
- Lesson — Расписание занятий
Создадим файлы моделей и миграций
1 2 3 4 |
php artisan make:model Teacher -m php artisan make:model Classroom -m php artisan make:model Group -m php artisan make:model Lesson -m |
Аудитория(Classroom)
Эта сущность состоит из следующих полей
Имя поля | Описание | Тип |
Name | Имя аудитории | string |
Description | Описание | string |
Migration
1 2 3 4 5 6 7 8 9 |
public function up(): void { Schema::create('classrooms', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->timestamps(); }); } |
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Classroom extends Model { use HasFactory; protected $fillable = ['name','description']; public function lessons(): HasMany { return $this->hasMany(Lesson::class); } } |
Преподаватель(Teacher)
Эта сущность состоит из следующих полей
Имя поля | Описание | Тип |
LastName | Фамилия | string |
FirstName | Имя | string |
FatherName | Отчество | string |
Lection | Предмет | string |
Migration
1 2 3 4 5 6 7 8 9 10 11 |
public function up(): void { Schema::create('teachers', function (Blueprint $table) { $table->id(); $table->string('lastname'); $table->string('firstname'); $table->string('fathername'); $table->string('lection'); $table->timestamps(); }); } |
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Teacher extends Model { use HasFactory; protected $fillable = ['lastname','firstname','fathername','lection']; public function lessons(): HasMany { return $this->hasMany(Lesson::class); } public function fullName(): Attribute { return new Attribute( get: fn () => $this->lastname .' ' . $this->firstname.' (' . $this->lection.')' ); } } |
Группа(Group)
Эта сущность состоит из следующих полей
Имя поля | Описание | Тип |
Name | Имя группы | string |
Description | Описание | string |
Migration
1 2 3 4 5 6 7 8 9 |
public function up(): void { Schema::create('groups', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->timestamps(); }); } |
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Group extends Model { use HasFactory; protected $fillable = ['name','description']; public function lessons(): HasMany { return $this->hasMany(Lesson::class); } } |
Расписание занятий(Lesson)
Эта сущность состоит из следующих полей
Имя поля | Описание | Тип |
start_ln | Дата и время начала занятия | datetime |
end_ln | Дата и время окончания занятия | datetime |
Classroom | Аудитория | fk:Classroom_id |
Teacher | Преподаватель | fk:Teacher_id |
Group | Группа | fk:Group_id |
Migration
1 2 3 4 5 6 7 8 9 10 11 12 |
public function up(): void { Schema::create('lessons', function (Blueprint $table) { $table->id(); $table->dateTime('start_ln'); $table->dateTime('end_ln'); $table->foreignId('classroom_id')->constrained()->cascadeOnDelete(); $table->foreignId('teacher_id')->constrained()->cascadeOnDelete()->nullable(); $table->foreignId('group_id')->constrained()->cascadeOnDelete()->nullable(); $table->timestamps(); }); } |
Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Lesson extends Model { use HasFactory; protected $guarded = []; public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class); } public function teacher(): BelongsTo { return $this->belongsTo(Teacher::class); } public function group(): BelongsTo { return $this->belongsTo(Group::class); } protected function dtLesson(): Attribute { return Attribute::make( get: fn () => Carbon::parse($this->start_ln)->format("H:i")." - ".Carbon::parse($this->end_ln)->format("H:i"), ); } protected function isBusy(): Attribute { return Attribute::make( get: fn () => $this->teacher !== null && $this->group !== null, ); } protected $casts = [ 'is_busy' => 'boolean', 'dt_lesson' =>'string', ]; } |
Создание factories и seeders
Для создание тестовых данных для групп, учителей и аудиторий создадим фабрики выполнив в консоли команды:
1 2 3 |
php artisan make:factory GroupFactory php artisan make:factory ClassroomFactory php artisan make:factory TeacherFactory |
ClassroomFactory.php
1 2 3 4 5 6 7 |
public function definition(): array { return [ 'name' => $this->faker->secondaryAddress, 'description' => $this->faker->address, ]; } |
GroupFactory.php
1 2 3 4 5 6 7 |
public function definition(): array { return [ 'name' => $this->faker->numberBetween(1000,5000).' '.$this->faker->word, 'description' => $this->faker->text, ]; } |
TeacherFactory.php
1 2 3 4 5 6 7 8 9 |
public function definition(): array { return [ 'lastname' => $this->faker->lastName, 'firstname' => $this->faker->firstName, 'fathername' => $this->faker->suffix, 'lection' => $this->faker->jobTitle, ]; } |
В файле DatabaseSeeder.php добавляем следующие строки:
1 2 3 4 5 6 |
public function run(): void { \App\Models\Teacher::factory(10)->create(); \App\Models\Group::factory(10)->create(); \App\Models\Classroom::factory(10)->create(); } |
Сохраняем все файлы и запускаем миграцию
1 |
php artisan migrate:fresh --seed |
Создание ресурсов
Для создания ресурсов необходимо в консоли ввести следующие команды:
1 2 3 4 |
php artisan make:filament-resource Group --generate --simple php artisan make:filament-resource Teacher --generate --simple php artisan make:filament-resource Classroom --generate --simple php artisan make:filament-resource Lesson --generate |
Созданные ресурсы сразу появятся в меню. Нам остается только заменить значки.
1 2 3 4 5 6 7 8 |
//ClassroomResource protected static ?string $navigationIcon = 'heroicon-c-building-library'; //GroupResource protected static ?string $navigationIcon = 'heroicon-o-users'; //LessonResource protected static ?string $navigationIcon = 'heroicon-c-calendar-days'; //TeacherResource protected static ?string $navigationIcon = 'heroicon-s-academic-cap'; |
Создадим пользователя для админки
1 |
php artisan make:filament-user |
После создания ресурсов и пользователя можно войти в систему и внести тестовый данные
Ресурс расписания
Ресурс расписания будет выглядеть следующим образом
LessonResource.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
<?php namespace App\Filament\Resources; use App\Filament\Resources\LessonResource\Pages; use App\Filament\Resources\LessonResource\RelationManagers; use App\Models\Lesson; use App\Models\Teacher; use Carbon\Carbon; use Filament\Forms; use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Section; use Filament\Forms\Components\Split; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Actions\Action; use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\TernaryFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Support\Facades\DB; class LessonResource extends Resource { protected static ?string $model = Lesson::class; protected static ?string $navigationIcon = 'heroicon-c-calendar-days'; public static function form(Form $form): Form { return $form ->schema([ Section::make('Select a teacher and a group') ->schema([ Forms\Components\Select::make('teacher_id') ->relationship( name:'teacher' , titleAttribute: 'full_name' , modifyQueryUsing: fn (Builder $query,Lesson $lesson) => $query->whereNotIn('id', DB::table('lessons')->select('teacher_id')->where('start_ln', $lesson->start_ln)->whereNotNull('teacher_id')), ) ->getOptionLabelFromRecordUsing(fn (Teacher $record) => "{$record->full_name}") ->searchable() ->preload() ->required(), Forms\Components\Select::make('group_id') ->relationship('group', 'name',modifyQueryUsing: fn (Builder $query,Lesson $lesson) => $query->whereNotIn('id', DB::table('lessons')->select('group_id')->where('start_ln', $lesson->start_ln)->whereNotNull('group_id')), ) ->searchable() ->preload() ->required(), ]) ->description(fn (Lesson $lesson): string => Carbon::parse($lesson->start_ln)->format("d.m.y").' '.$lesson->dt_lesson.'. Room: '.$lesson->classroom->name) ]); } public static function table(Table $table): Table { $firstDayOfWeek = Carbon::now()->startOfWeek(); $weekEndDate = Carbon::now()->endOfWeek(); return $table ->columns([ Tables\Columns\TextColumn::make('dt_lesson') ->sortable(), Tables\Columns\IconColumn::make('is_busy') ->label('Busy') ->toggleable(), Tables\Columns\TextColumn::make('end_ln') ->dateTime() ->toggleable(isToggledHiddenByDefault: true) ->sortable(), Tables\Columns\TextColumn::make('classroom.name') ->sortable(), Tables\Columns\TextColumn::make('teacher.lection') ->sortable(), Tables\Columns\TextColumn::make('group.name') ->sortable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ TernaryFilter::make('Талоны') ->placeholder('Показать все записи') ->trueLabel('Показать свободные талоны') ->falseLabel('Показать занятые талоны') ->queries( true: fn (Builder $query) => $query->whereNull('teacher_id')->whereNull('group_id'), false: fn (Builder $query) => $query->whereNotNull('teacher_id')->whereNotNull('group_id'), blank: fn (Builder $query) => $query->get(), ), Tables\Filters\Filter::make('start_ln') ->form([ Forms\Components\DatePicker::make('start_from') ->placeholder(fn ($state): string => 'Dec 18, ' . now()->subYear()->format('Y')) ->default($firstDayOfWeek) ->native(false), Forms\Components\DatePicker::make('start_until') ->placeholder(fn ($state): string => now()->format('d.M.Y')) ->default($weekEndDate) ->native(false), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['start_from'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('start_ln', '>=', $date), ) ->when( $data['start_until'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('start_ln', '<=', $date), ); }) ->indicateUsing(function (array $data): array { $indicators = []; if ($data['start_from'] ?? null) { $indicators['start_from'] = 'Lesson from ' . Carbon::parse($data['start_from'])->toFormattedDateString(); } if ($data['start_until'] ?? null) { $indicators['start_until'] = 'Lesson until ' . Carbon::parse($data['start_until'])->toFormattedDateString(); } return $indicators; }), SelectFilter::make('classroom')->relationship('classroom', 'name'), SelectFilter::make('teacher')->relationship('teacher', 'lastname'), SelectFilter::make('group')->relationship('group', 'name'), ]) ->actions([ Tables\Actions\EditAction::make() ->hidden(fn (Lesson $record): bool => $record->is_busy), Action::make('Empty to lesson') ->color('danger') ->icon('heroicon-m-minus-circle') ->action(function (Lesson $record) { $record->update([ 'teacher_id' => null, 'group_id' => null, ]); }) ->visible(fn (Lesson $record): bool => $record->is_busy), //Tables\Actions\DeleteAction::make(), ]) ->groups([ Tables\Grouping\Group::make('start_ln') ->label('Lesson Date') ->date() ->collapsible(), ])->defaultGroup('start_ln') ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } public static function getPages(): array { return [ 'index' => Pages\ListLessons::route('/'), ]; } } |
Фильтр
Наш фильтр по умолчанию будет выводить текущую неделю
Для вычисления текущей недели мы использовали следующие переменные
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
->filters([ TernaryFilter::make('Талоны') ->placeholder('Показать все записи') ->trueLabel('Показать свободные талоны') ->falseLabel('Показать занятые талоны') ->queries( true: fn (Builder $query) => $query->whereNull('teacher_id')->whereNull('group_id'), false: fn (Builder $query) => $query->whereNotNull('teacher_id')->whereNotNull('group_id'), blank: fn (Builder $query) => $query->get(), ), Tables\Filters\Filter::make('start_ln') ->form([ Forms\Components\DatePicker::make('start_from') ->placeholder(fn ($state): string => 'Dec 18, ' . now()->subYear()->format('Y')) ->default($firstDayOfWeek) ->native(false), Forms\Components\DatePicker::make('start_until') ->placeholder(fn ($state): string => now()->format('d.M.Y')) ->default($weekEndDate) ->native(false), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['start_from'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('start_ln', '>=', $date), ) ->when( $data['start_until'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('start_ln', '<=', $date), ); }) ->indicateUsing(function (array $data): array { $indicators = []; if ($data['start_from'] ?? null) { $indicators['start_from'] = 'Lesson from ' . Carbon::parse($data['start_from'])->toFormattedDateString(); } if ($data['start_until'] ?? null) { $indicators['start_until'] = 'Lesson until ' . Carbon::parse($data['start_until'])->toFormattedDateString(); } return $indicators; }), SelectFilter::make('classroom')->relationship('classroom', 'name'), SelectFilter::make('teacher')->relationship('teacher', 'lastname'), SelectFilter::make('group')->relationship('group', 'name'), ]) |
Таблица
Таблица будет сгруппирована по дате лекции
1 2 3 4 5 6 |
->groups([ Tables\Grouping\Group::make('start_ln') ->label('Lesson Date') ->date() ->collapsible(), ])->defaultGroup('start_ln') |
И иметь следующие колонки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
->columns([ Tables\Columns\TextColumn::make('dt_lesson') ->dateTime() ->sortable(), Tables\Columns\IconColumn::make('is_busy') ->label('Busy') ->toggleable(), Tables\Columns\TextColumn::make('end_ln') ->dateTime() ->toggleable(isToggledHiddenByDefault: true) ->sortable(), Tables\Columns\TextColumn::make('classroom.name') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('teacher.full_name') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('group.name') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), |
Для определения занятости ячейки расписания был создан атрибут в модели Lesson
Действия
Нужно заменить стандартные действия на наши. Если аудитория занята, то вывести кнопку отменить, но если свободна, то вывести модальное окно для указания группы и учителя.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
->actions([ Tables\Actions\EditAction::make() ->hidden(fn (Lesson $record): bool => $record->is_busy), Tables\Actions\Action::make('Empty to lesson') ->color('danger') ->icon('heroicon-m-minus-circle') ->action(function (Lesson $record) { $record->update([ 'teacher_id' => null, 'group_id' => null, ]); }) ->visible(fn (Lesson $record): bool => $record->is_busy), //Tables\Actions\DeleteAction::make(), ]) |
Форма
Создавать элемент расписания можно только через генерацию на неделю. Редактировать можно уже сгенерированные элементы назначая им Учителя и Группу.
Форма редактирования будет выводить для выбора только два поля.
В поле Teacher можно будет выбрать учителя у которого нет занятий в это время в других аудиториях. Для этого мы используем параметр modifyQueryUsing
1 2 3 4 |
modifyQueryUsing: fn (Builder $query,Lesson $lesson) => $query->whereNotIn('id', DB::table('lessons')->select('teacher_id')->where('start_ln', $lesson->start_ln)->whereNotNull('teacher_id')), ) |
В поле Group можно будет выбрать группу у которой нет занятий в это время в других аудиториях. Для этого мы используем параметр modifyQueryUsing
1 2 3 4 |
modifyQueryUsing: fn (Builder $query,Lesson $lesson) => $query->whereNotIn('id', DB::table('lessons')->select('group_id')->where('start_ln', $lesson->start_ln)->whereNotNull('group_id')), ) |
Генерация расписания на неделю
Заменим кнопку «Добавить» на наше действие.
Для этого нужно найти файл с соответствующим названием.
\app\Filament\Resources\LessonResource\Pages\ListLessons.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<?php namespace App\Filament\Resources\LessonResource\Pages; use App\Filament\Resources\LessonResource; use App\Models\Lesson; use Carbon\Carbon; use Filament\Actions; use Filament\Resources\Pages\ListRecords; use Filament\Forms; class ListLessons extends ListRecords { protected static string $resource = LessonResource::class; protected function getHeaderActions(): array { $firstDayOfWeek = Carbon::now()->startOfWeek(); $weekEndDate = Carbon::now()->endOfWeek(); return [ Actions\Action::make('Generate lessons') ->form([ Forms\Components\DatePicker::make('start_from') ->placeholder(fn ($state): string => 'Dec 18, ' . now()->subYear()->format('Y')) ->default($firstDayOfWeek) ->native(false), Forms\Components\DatePicker::make('start_until') ->placeholder(fn ($state): string => now()->format('d.M.Y')) ->default($weekEndDate) ->native(false), Forms\Components\Select::make('classroom_id') ->relationship('classroom', 'name') ->required(), ]) ->action(function (array $data): void { $startDate = Carbon::parse($data['start_from']); $endDate = Carbon::parse($data['start_until']); $currentDate = $startDate; while ($currentDate <= $endDate) { $currentDate->startOfDay(); $startDT = $currentDate->copy()->addHours(8); $endDT = $startDT->copy()->addHour(); for ($i = 1; $i <= 10; $i++) { $lesson = new Lesson(); $lesson->start_ln =$startDT; $lesson->end_ln = $endDT; $lesson->classroom_id = $data['classroom_id']; $lesson->teacher_id = null; // пустое поле учителя $lesson->group_id = null; // пустое поле группы $lesson->save(); $startDT->addHour(); $endDT->addHour(); } $currentDate->addDay(); } }), ]; } } |
В функцию getHeaderActions удаляем action create и создаем свой.
- Получаем текущую неделю
- Создаем Action с формой для выбора дат и нужной нам аудитории
- После нажатия submit будет выполняться следующая функция, которая сгенерирует 10 ячеек лекций в с диапазоном в 1 час на каждый день в диапазоне нашей формы
Заменим кнопку «Добавить» на наше действие.
Создание экспорта для пользователя сайта
Начиная с версии 0.2 Filament Excel должен работать как с пакетами filament/filament
, так и с пакетами filament/tables
. Самое простое использование — это просто добавить ExportBulkAction к вашим массовым действиям.
Установка пакета
1 |
composerrequire psr/simple-cache:^2.0 pxlrbt/filament-excel |
Далее нам нужно добавить кнопку действия на странице списка записей для вашего ресурса.
1 2 3 4 5 6 7 8 9 10 |
ExportAction::make() ->exports([ ExcelExport::make() ->fromTable() ->withFilename(fn ($resource) => $resource::getModelLabel() . '-' . date('d-m-Y H:i')) ->withWriterType(\Maatwebsite\Excel\Excel::XLSX) ->withColumns([ Column::make('updated_at'), ]) ]), |
Теперь мы видим кнопку действия в заголовке таблицы.
Поскольку мы экспортируем из таблицы, все поля будут выгружены из нее, но, используя метод withColumns, мы добавим в выгрузку дополнительное поле updated_at.
Мы также указываем имя файла, используя метод withFilename. И поскольку этот пакет использует пакет laravel-excel, тип файла в методе withWriterType задается с помощью пакета laravel-excel.
После нажатия кнопки export мы загружаем экспортированные данные в виде файла XLSX.
Документацию по этой библиотеке можно найти по ссылке
Создание календаря для расписания занятий
Создадим командой файл с виджетом
1 |
php artisan make:filament-widget CalendarWidget |
Установите пакет Filament FullCalendar через Composer.
1 |
composer require saade/filament-fullcalendar:^3.0 |
Добавить в AdminPanelProvider.php следующие строки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
->plugin( FilamentFullCalendarPlugin::make() ->selectable() ->editable() ->config( [ 'firstDay' => 1, 'initialView' => 'dayGridWeek', 'headerToolbar' => [ 'left' => 'dayGridWeek,listWeek', 'center' => 'title', 'right' => 'prev,next today', ] ] ), ) |
и создадим страницу календаря
1 |
php artisan make:filament-page Calendar |
app\Filament\Pages\Calendar.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php namespace App\Filament\Pages; use App\Models\Classroom; use App\Models\Group; use App\Models\Teacher; use Filament\Pages\Page; class Calendar extends Page { public $teachers = []; public $classrooms = []; public $groups = []; public $status = null; public $selectedItem; // This will hold the selected teacher or audition public $buttonClicked; protected static ?string $navigationIcon = 'heroicon-o-document-text'; protected static string $view = 'filament.pages.calendar'; public function getSelect($type) { // Check which button was clicked and fetch data accordingly if ($type === 'teachers') { $this->teachers = Teacher::all(); // Fetch teachers $this->buttonClicked = 'teacher_id'; } elseif ($type === 'classrooms') { $this->classrooms = Classroom::all(); // Fetch auditions $this->buttonClicked = 'classroom_id'; } elseif ($type === 'groups') { $this->groups = Group::all(); // Fetch auditions $this->buttonClicked = 'group_id'; } } } |
resources\views\filament\pages\calendar.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<x-filament-panels::page> <x-filament::section> <x-slot name="heading"> Select type </x-slot> <div class="flex justify-end"> <x-filament::button class="ml-3" color="danger" outlined icon="heroicon-m-sparkles" wire:click="getSelect('teachers')"> Учителя </x-filament::button> <x-filament::button class="ml-3" color="info" outlined wire:click="getSelect('classrooms')"> Аудитории </x-filament::button> <x-filament::button class="ml-3" color='gray' outlined wire:click="getSelect('groups')"> Группы </x-filament::button> </div> </x-filament::section> <x-filament::input.wrapper> <x-filament::input.select wire:model.live="status" > @if($buttonClicked === 'teacher_id') @foreach($teachers as $teacher) <option value="{{ $teacher->id }}">{{ $teacher->fullName }}</option> @endforeach @elseif($buttonClicked === 'classroom_id') @foreach($classrooms as $classroom) <option value="{{ $classroom->id }}">{{ $classroom->name }}</option> @endforeach @elseif($buttonClicked === 'group_id') @foreach($groups as $group) <option value="{{ $group->id }}">{{ $group->name }}</option> @endforeach @endif </x-filament::input.select> </x-filament::input.wrapper> @livewire(\App\Filament\Widgets\CalendarWidget::class ,[ 'selectedStatus'=>$status, 'selectedType'=>$buttonClicked ] ,key(str()->random())) </x-filament-panels::page> |
CalendarWidget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?php namespace App\Filament\Widgets; use App\Models\Lesson; use Illuminate\Database\Eloquent\Model; use Saade\FilamentFullCalendar\Widgets\FullCalendarWidget; class CalendarWidget extends FullCalendarWidget { public Model | string | null $model = Lesson::class; public ?string $selectedStatus = ''; public ?string $selectedType = 'teacher_id'; public function fetchEvents(array $fetchInfo): array { return Lesson::where('start_ln', '>=', $fetchInfo['start']) ->where('start_ln', '<=', $fetchInfo['end']) ->where($this->selectedType, '=', $this->selectedStatus) ->get() ->map(function (Lesson $les) { $result = is_null($les->group) ? 'Empty' : $les->group->name.' - '.$les->teacher->fullName; return [ 'id' => $les->id, 'title' => $les->classroom->name.'('.$result.')', 'start' => $les->start_ln, 'end' => $les->end_ln, ]; }) ->toArray(); } public static function canView(): bool { return false; } } |
Заключение
В результате выполнения данной задачи будет разработано веб-приложение, которое позволит эффективно управлять расписанием занятий в учебном заведении. Это обеспечит прозрачность и доступность информации о расписании для всех участников образовательного процесса: студентов, преподавателей, администрации.