diff --git a/app/Enums/ColumnColorOptions.php b/app/Enums/ColumnColorOptions.php new file mode 100644 index 0000000000..ffc3b66164 --- /dev/null +++ b/app/Enums/ColumnColorOptions.php @@ -0,0 +1,18 @@ +schema([ + TextInput::make('name') + ->label('Name') + ->required(), + TextInput::make('order') + ->numeric() + ->label('Priority Order') + ->required() + ->disabledOn('edit'), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('name') + ->label('Name') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('order') + ->label('Priority Order') + ->sortable(), + ]) + ->defaultSort('order') + ->filters([ + ]) + ->actions([ + Tables\Actions\ViewAction::make(), + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]) + ->reorderable('order'); + } + + public static function getRelations(): array + { + return [ + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListCaseItemPriorities::route('/'), + 'create' => Pages\CreateCaseItemPriority::route('/create'), + 'view' => Pages\ViewCaseItemPriority::route('/{record}'), + 'edit' => Pages\EditCaseItemPriority::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/CaseItemPriorityResource/Pages/CreateCaseItemPriority.php b/app/Filament/Resources/CaseItemPriorityResource/Pages/CreateCaseItemPriority.php new file mode 100644 index 0000000000..0e5abb3ad6 --- /dev/null +++ b/app/Filament/Resources/CaseItemPriorityResource/Pages/CreateCaseItemPriority.php @@ -0,0 +1,11 @@ +schema([ + TextInput::make('casenumber') + ->label('Case #') + ->required() + ->disabledOn('edit'), + MorphToSelect::make('respondent') + ->types([ + MorphToSelect\Type::make(Student::class) + ->getOptionLabelFromRecordUsing(fn (Student $student): string => "{$student->first_name} {$student->middle_name} {$student->last_name}") + ->titleAttribute('first_name'), + ]) + ->searchable() + ->label('Respondent'), + // TODO: Add Institution input + Select::make('state') + ->options(CaseItemStatus::pluck('name', 'id')) + ->relationship('state', 'name') + ->label('State') + ->required(), + // TODO: Add Type input + Select::make('priority') + ->relationship( + relationshipName: 'priority', + titleAttribute: 'name', + modifyOptionsQueryUsing: fn (Builder $query) => $query->orderBy('order'), + ) + ->label('Priority') + ->required(), + Select::make('assignedTo') + ->relationship( + relationshipName: 'assignedTo', + titleAttribute: 'name', + ) + ->label('Assigned To') + ->required() + ->searchable(['name', 'email']), + Textarea::make('close_details') + ->label('Close Details/Description') + ->nullable(), + Textarea::make('res_details') + ->label('Internal Case Details') + ->nullable(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('casenumber') + ->label('Case #') + ->searchable(), + Tables\Columns\TextColumn::make('priority.name') + ->label('Priority') + ->sortable(query: function (Builder $query, string $direction): Builder { + return $query->orderByPowerJoins('priority.order', $direction); + }), + Tables\Columns\TextColumn::make('state.name') + ->label('Status') + ->badge() + ->color(fn (CaseItem $caseItem) => $caseItem->state->color), + ]) + ->filters([ + Tables\Filters\SelectFilter::make('priority') + ->relationship('priority', 'name') + ->multiple(), + Tables\Filters\SelectFilter::make('state') + ->relationship('state', 'name') + ->multiple(), + ]) + ->actions([ + Tables\Actions\ViewAction::make(), + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListCaseItems::route('/'), + 'create' => Pages\CreateCaseItem::route('/create'), + 'view' => Pages\ViewCaseItem::route('/{record}'), + 'edit' => Pages\EditCaseItem::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/CaseItemResource/Pages/CreateCaseItem.php b/app/Filament/Resources/CaseItemResource/Pages/CreateCaseItem.php new file mode 100644 index 0000000000..66b4d5d0ad --- /dev/null +++ b/app/Filament/Resources/CaseItemResource/Pages/CreateCaseItem.php @@ -0,0 +1,11 @@ +schema([ + TextInput::make('name') + ->label('Name') + ->translateLabel() + ->required(), + Select::make('color') + ->label('Color') + ->translateLabel() + ->options(ColumnColorOptions::class) + ->required() + ->enum(ColumnColorOptions::class), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('name') + ->label('Name') + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('color') + ->label('Color') + ->badge() + ->color(fn (CaseItemStatus $caseItemStatus) => $caseItemStatus->color), + ]) + ->filters([ + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListCaseItemStatuses::route('/'), + 'create' => Pages\CreateCaseItemStatus::route('/create'), + 'edit' => Pages\EditCaseItemStatus::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/CaseItemStatusResource/Pages/CreateCaseItemStatus.php b/app/Filament/Resources/CaseItemStatusResource/Pages/CreateCaseItemStatus.php new file mode 100644 index 0000000000..762e4feee5 --- /dev/null +++ b/app/Filament/Resources/CaseItemStatusResource/Pages/CreateCaseItemStatus.php @@ -0,0 +1,11 @@ +listsForFields['student'] = RecordStudentItem::pluck('full', 'id')->toArray(); $this->listsForFields['institution'] = Institution::pluck('name', 'id')->toArray(); - $this->listsForFields['state'] = CaseItemStatus::pluck('status', 'id')->toArray(); + $this->listsForFields['state'] = CaseItemStatus::pluck('name', 'id')->toArray(); $this->listsForFields['type'] = CaseItemType::pluck('type', 'id')->toArray(); - $this->listsForFields['priority'] = CaseItemPriority::pluck('priority', 'id')->toArray(); + $this->listsForFields['priority'] = CaseItemPriority::pluck('name', 'id')->toArray(); $this->listsForFields['assigned_to'] = User::pluck('name', 'id')->toArray(); } } diff --git a/app/Http/Livewire/CaseItem/Index.php b/app/Http/Livewire/CaseItem/Index.php index ce7ee57d58..0951e9f696 100644 --- a/app/Http/Livewire/CaseItem/Index.php +++ b/app/Http/Livewire/CaseItem/Index.php @@ -69,7 +69,7 @@ public function mount() public function render() { - $query = CaseItem::with(['respondent', 'institution', 'state', 'type', 'priority', 'assignedTo', 'createdBy'])->advancedFilter([ + $query = CaseItem::with(['institution', 'state', 'type', 'priority', 'assignedTo', 'createdBy'])->advancedFilter([ 's' => $this->search ?: null, 'order_column' => $this->sortBy, 'order_direction' => $this->sortDirection, diff --git a/app/Models/CaseItem.php b/app/Models/CaseItem.php index bc8f718e3d..87aee37390 100644 --- a/app/Models/CaseItem.php +++ b/app/Models/CaseItem.php @@ -2,9 +2,9 @@ namespace App\Models; -use Carbon\Carbon; use DateTimeInterface; use App\Support\HasAdvancedFilter; +use Kirschbaum\PowerJoins\PowerJoins; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -16,17 +16,12 @@ class CaseItem extends BaseModel { use HasAdvancedFilter; use SoftDeletes; + use PowerJoins; public static $search = [ 'casenumber', ]; - protected $dates = [ - 'created_at', - 'updated_at', - 'deleted_at', - ]; - public $orderable = [ 'id', 'casenumber', @@ -81,6 +76,7 @@ public function state(): BelongsTo return $this->belongsTo(CaseItemStatus::class); } + // TODO public function type(): BelongsTo { return $this->belongsTo(CaseItemType::class); @@ -96,28 +92,14 @@ public function assignedTo(): BelongsTo return $this->belongsTo(User::class); } + // TODO: Figure this out and whether or not it can just be handled via auditing public function createdBy(): BelongsTo { return $this->belongsTo(User::class); } - public function getCreatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getUpdatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getDeletedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - protected function serializeDate(DateTimeInterface $date) + protected function serializeDate(DateTimeInterface $date): string { - return $date->format('Y-m-d H:i:s'); + return $date->format(config('project.datetime_format') ?? 'Y-m-d H:i:s'); } } diff --git a/app/Models/CaseItemPriority.php b/app/Models/CaseItemPriority.php index 59a665dac4..db05423eae 100644 --- a/app/Models/CaseItemPriority.php +++ b/app/Models/CaseItemPriority.php @@ -2,10 +2,10 @@ namespace App\Models; -use Carbon\Carbon; use DateTimeInterface; use App\Support\HasAdvancedFilter; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @mixin IdeHelperCaseItemPriority @@ -16,42 +16,27 @@ class CaseItemPriority extends BaseModel use SoftDeletes; protected $fillable = [ - 'priority', + 'name', + 'order', ]; public $orderable = [ 'id', - 'priority', + 'order', ]; public $filterable = [ 'id', - 'priority', + 'order', ]; - protected $dates = [ - 'created_at', - 'updated_at', - 'deleted_at', - ]; - - public function getCreatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getUpdatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getDeletedAtAttribute($value) + public function caseItems(): HasMany { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; + return $this->hasMany(CaseItem::class); } - protected function serializeDate(DateTimeInterface $date) + protected function serializeDate(DateTimeInterface $date): string { - return $date->format('Y-m-d H:i:s'); + return $date->format(config('project.datetime_format') ?? 'Y-m-d H:i:s'); } } diff --git a/app/Models/CaseItemStatus.php b/app/Models/CaseItemStatus.php index 8ab16273b9..eed45c92e1 100644 --- a/app/Models/CaseItemStatus.php +++ b/app/Models/CaseItemStatus.php @@ -2,7 +2,6 @@ namespace App\Models; -use Carbon\Carbon; use DateTimeInterface; use App\Support\HasAdvancedFilter; use Illuminate\Database\Eloquent\SoftDeletes; @@ -16,42 +15,27 @@ class CaseItemStatus extends BaseModel use SoftDeletes; protected $fillable = [ - 'status', + 'name', + 'color', ]; public $orderable = [ 'id', - 'status', + 'name', ]; public $filterable = [ 'id', - 'status', + 'name', ]; - protected $dates = [ - 'created_at', - 'updated_at', - 'deleted_at', - ]; - - public function getCreatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getUpdatedAtAttribute($value) - { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; - } - - public function getDeletedAtAttribute($value) + public function caseItems() { - return $value ? Carbon::createFromFormat('Y-m-d H:i:s', $value)->format(config('project.datetime_format')) : null; + return $this->hasMany(CaseItem::class); } protected function serializeDate(DateTimeInterface $date) { - return $date->format('Y-m-d H:i:s'); + return $date->format(config('project.datetime_format') ?? 'Y-m-d H:i:s'); } } diff --git a/app/Models/Student.php b/app/Models/Student.php index f22515bf9a..81922df91e 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -13,7 +13,7 @@ class Student extends Model { use HasFactory; - protected $primaryKey = null; + protected $primaryKey = 'student_id'; public $incrementing = false; @@ -27,9 +27,4 @@ public function cases(): MorphMany localKey: 'student_id' ); } - - public function mcWhereHas($relation, $callback = null, $operator = '>=', $count = 1) - { - // TODO: Create a whereHas and other methods that work with the different database connections - } } diff --git a/composer.json b/composer.json index 0b364ac1ac..fa5dee230e 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "ext-pdo": "*", "filament/filament": "^3.0", "guzzlehttp/guzzle": "^7.2", + "kirschbaum-development/eloquent-power-joins": "^2.7", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", "laravel/tinker": "^2.8", diff --git a/composer.lock b/composer.lock index 8f4775760e..cd8fb2450b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07981d38d2e551b7257fcba68ead7706", + "content-hash": "15072df36140056f11de4589073d8f3f", "packages": [ { "name": "aws/aws-crt-php", diff --git a/database/factories/CaseItemFactory.php b/database/factories/CaseItemFactory.php index 5411dee5e5..741cbc2588 100644 --- a/database/factories/CaseItemFactory.php +++ b/database/factories/CaseItemFactory.php @@ -2,8 +2,12 @@ namespace Database\Factories; +use App\Models\User; use App\Models\Student; use App\Models\CaseItem; +use App\Models\Institution; +use App\Models\CaseItemStatus; +use App\Models\CaseItemPriority; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -15,16 +19,49 @@ public function definition(): array { return [ 'casenumber' => $this->faker->randomNumber(9), - 'respondent_type' => 'App\Models\Student', 'respondent_id' => Student::factory(), + 'respondent_type' => function (array $attributes) { + return Student::find($attributes['respondent_id'])->getMorphClass(); + }, 'close_details' => $this->faker->sentence(), 'res_details' => $this->faker->sentence(), - 'institution_id' => $this->faker->randomNumber(9), - 'state_id' => $this->faker->randomNumber(9), + 'institution_id' => Institution::factory(), + 'state_id' => CaseItemStatus::factory(), 'type_id' => $this->faker->randomNumber(9), - 'priority_id' => $this->faker->randomNumber(9), - 'assigned_to_id' => $this->faker->randomNumber(9), + 'priority_id' => CaseItemPriority::factory(), + 'assigned_to_id' => User::factory(), 'created_by_id' => $this->faker->randomNumber(9), ]; } + + public function configure(): static + { + return $this->afterMaking(function (CaseItem $case) { + })->afterCreating(function (CaseItem $case) { + $this->generatePriority($case); + $this->generateStatus($case); + }); + } + + protected function generatePriority(CaseItem $case): void + { + $priority = CaseItemPriority::inRandomOrder()->first(); + + if (! $priority) { + $priority = CaseItemPriority::factory()->high()->create(); + } + + $case->priority()->associate($priority)->save(); + } + + protected function generateStatus(CaseItem $case): void + { + $priority = CaseItemStatus::inRandomOrder()->first(); + + if (! $priority) { + $priority = CaseItemStatus::factory()->open()->create(); + } + + $case->state()->associate($priority)->save(); + } } diff --git a/database/factories/CaseItemPriorityFactory.php b/database/factories/CaseItemPriorityFactory.php new file mode 100644 index 0000000000..c0370f7f4c --- /dev/null +++ b/database/factories/CaseItemPriorityFactory.php @@ -0,0 +1,50 @@ + + */ +class CaseItemPriorityFactory extends Factory +{ + public function definition(): array + { + return [ + 'name' => $this->faker->name, + 'order' => $this->faker->randomNumber(1), + ]; + } + + public function high(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'High', + 'order' => 1, + ]; + }); + } + + public function medium(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'Medium', + 'order' => 2, + ]; + }); + } + + public function low(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'Low', + 'order' => 3, + ]; + }); + } +} diff --git a/database/factories/CaseItemStatusFactory.php b/database/factories/CaseItemStatusFactory.php new file mode 100644 index 0000000000..ae9eb71968 --- /dev/null +++ b/database/factories/CaseItemStatusFactory.php @@ -0,0 +1,51 @@ + + */ +class CaseItemStatusFactory extends Factory +{ + public function definition(): array + { + return [ + 'name' => $this->faker->word, + 'color' => $this->faker->randomElement(ColumnColorOptions::cases())->value, + ]; + } + + public function open(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'Open', + 'color' => ColumnColorOptions::SUCCESS->value, + ]; + }); + } + + public function in_progress(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'In Progress', + 'color' => ColumnColorOptions::INFO->value, + ]; + }); + } + + public function closed(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => 'Closed', + 'color' => ColumnColorOptions::WARNING->value, + ]; + }); + } +} diff --git a/database/factories/InstitutionFactory.php b/database/factories/InstitutionFactory.php new file mode 100644 index 0000000000..46b065b416 --- /dev/null +++ b/database/factories/InstitutionFactory.php @@ -0,0 +1,21 @@ + + */ +class InstitutionFactory extends Factory +{ + public function definition(): array + { + return [ + 'code' => $this->faker->word(), + 'name' => $this->faker->company(), + 'description' => $this->faker->text(), + ]; + } +} diff --git a/database/migrations/2023_03_06_000009_create_case_item_statuses_table.php b/database/migrations/2023_03_06_000009_create_case_item_statuses_table.php index 2af97769fd..1dbb24d69c 100644 --- a/database/migrations/2023_03_06_000009_create_case_item_statuses_table.php +++ b/database/migrations/2023_03_06_000009_create_case_item_statuses_table.php @@ -6,11 +6,12 @@ class CreateCaseItemStatusesTable extends Migration { - public function up() + public function up(): void { Schema::create('case_item_statuses', function (Blueprint $table) { $table->bigIncrements('id'); - $table->string('status'); + $table->string('name'); + $table->string('color'); $table->timestamps(); $table->softDeletes(); }); diff --git a/database/migrations/2023_03_06_000011_create_case_item_priorities_table.php b/database/migrations/2023_03_06_000011_create_case_item_priorities_table.php index e51f94a0d4..661e0c604b 100644 --- a/database/migrations/2023_03_06_000011_create_case_item_priorities_table.php +++ b/database/migrations/2023_03_06_000011_create_case_item_priorities_table.php @@ -6,11 +6,12 @@ class CreateCaseItemPrioritiesTable extends Migration { - public function up() + public function up(): void { Schema::create('case_item_priorities', function (Blueprint $table) { $table->bigIncrements('id'); - $table->string('priority'); + $table->string('name'); + $table->integer('order'); $table->timestamps(); $table->softDeletes(); }); diff --git a/database/seeders/CaseItemPrioritySeeder.php b/database/seeders/CaseItemPrioritySeeder.php new file mode 100644 index 0000000000..d03989cfa0 --- /dev/null +++ b/database/seeders/CaseItemPrioritySeeder.php @@ -0,0 +1,21 @@ +count(3) + ->sequence( + ['name' => 'High', 'order' => 1], + ['name' => 'Medium', 'order' => 2], + ['name' => 'Low', 'order' => 3], + ) + ->create(); + } +} diff --git a/database/seeders/CaseItemSeeder.php b/database/seeders/CaseItemSeeder.php new file mode 100644 index 0000000000..cf3f836286 --- /dev/null +++ b/database/seeders/CaseItemSeeder.php @@ -0,0 +1,16 @@ +count(30) + ->create(); + } +} diff --git a/database/seeders/CaseItemStatusSeeder.php b/database/seeders/CaseItemStatusSeeder.php new file mode 100644 index 0000000000..9e1eb3404f --- /dev/null +++ b/database/seeders/CaseItemStatusSeeder.php @@ -0,0 +1,24 @@ +open() + ->create(); + + CaseItemStatus::factory() + ->in_progress() + ->create(); + + CaseItemStatus::factory() + ->closed() + ->create(); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 7e08fe3480..7450d12681 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,6 +14,9 @@ public function run(): void $this->call([ UsersTableSeeder::class, + CaseItemPrioritySeeder::class, + CaseItemStatusSeeder::class, + CaseItemSeeder::class, StudentSeeder::class, ]); } diff --git a/database/seeders/UsersTableSeeder.php b/database/seeders/UsersTableSeeder.php index 089db4ea6f..a4767f8923 100644 --- a/database/seeders/UsersTableSeeder.php +++ b/database/seeders/UsersTableSeeder.php @@ -15,7 +15,7 @@ public function run(): void $superAdmin = User::factory()->create([ 'name' => 'Super Admin', 'email' => 'superadmin@assist.com', - 'password' => bcrypt('password'), + 'password' => Hash::make('password'), ]); $superAdminRoles = Role::superAdmin()->get(); diff --git a/docs/local-setup.md b/docs/local-setup.md index 5e35eaf0cf..5b1d241b21 100644 --- a/docs/local-setup.md +++ b/docs/local-setup.md @@ -65,6 +65,7 @@ Finally, we will set up the application by running the following commands: ```bash sail artisan key:generate sail artisan migrate +sail artisan migrate --database=sis --path=database/migrations/sis sail artisan db:seed sail npm install sail npm run dev diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index 653d6c929d..8ac324acd7 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -622,30 +622,30 @@ class="{{ request()->is('admin/users*') ? 'sidebar-nav-active' : 'sidebar-nav' } @endcan - @can('permission_access') -