diff --git a/app/Filament/Imports/StudentImporter.php b/app/Filament/Imports/StudentImporter.php new file mode 100644 index 0000000..ec9d68b --- /dev/null +++ b/app/Filament/Imports/StudentImporter.php @@ -0,0 +1,81 @@ +requiredMapping() + ->rules(['required', 'max:255']), + ImportColumn::make('nis') + ->rules(['required','max:255']), + ImportColumn::make('nisn') + ->rules(['required','max:255']), + ImportColumn::make('gender') + ->requiredMapping() + ->rules(['required', 'max:255']), + ImportColumn::make('birth_date') + ->rules(['date']), + ImportColumn::make('birth_place') + ->rules(['max:255']), + ImportColumn::make('address'), + ImportColumn::make('religion'), + ImportColumn::make('phone') + ->rules(['required','max:255']), + ImportColumn::make('email') + ->rules(['email', 'max:255']), + ImportColumn::make('parent_name') + ->rules(['required','max:255']), + ImportColumn::make('parent_phone') + ->rules(['required','max:255']), + ]; + } + + public function resolveRecord(): ?Student + { + // Cek duplikat NIS + if (Student::where('nis', $this->data['nis'])->exists()) { + throw new RowImportFailedException('NIS sudah terdaftar'); + } + + // Cek duplikat NISN + if (Student::where('nisn', $this->data['nisn'])->exists()) { + throw new RowImportFailedException('NISN sudah terdaftar'); + } + + // Cek duplikat phone + if (Student::where('phone', $this->data['phone'])->exists()) { + throw new RowImportFailedException('Nomor telepon sudah terdaftar'); + } + + // Cek duplikat email di tabel users + if (User::where('email', $this->data['email'])->exists()) { + throw new RowImportFailedException('Email sudah digunakan'); + } + + return new Student(); + } + + public static function getCompletedNotificationBody(Import $import): string + { + $body = 'Your student import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.'; + + if ($failedRowsCount = $import->getFailedRowsCount()) { + $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.'; + } + + return $body; + } +} diff --git a/app/Filament/Resources/StudentResource/Pages/ListStudents.php b/app/Filament/Resources/StudentResource/Pages/ListStudents.php index 4a8a138..b059686 100644 --- a/app/Filament/Resources/StudentResource/Pages/ListStudents.php +++ b/app/Filament/Resources/StudentResource/Pages/ListStudents.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\StudentResource\Pages; +use App\Filament\Imports\StudentImporter; use App\Filament\Resources\StudentResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; @@ -14,6 +15,8 @@ class ListStudents extends ListRecords { return [ Actions\CreateAction::make(), + Actions\ImportAction::make('importBrands') + ->importer(StudentImporter::class), ]; } } diff --git a/database/migrations/2025_05_25_043315_create_notifications_table.php b/database/migrations/2025_05_25_043315_create_notifications_table.php new file mode 100644 index 0000000..d738032 --- /dev/null +++ b/database/migrations/2025_05_25_043315_create_notifications_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; diff --git a/database/migrations/2025_05_25_043331_create_imports_table.php b/database/migrations/2025_05_25_043331_create_imports_table.php new file mode 100644 index 0000000..100b7d9 --- /dev/null +++ b/database/migrations/2025_05_25_043331_create_imports_table.php @@ -0,0 +1,35 @@ +id(); + $table->timestamp('completed_at')->nullable(); + $table->string('file_name'); + $table->string('file_path'); + $table->string('importer'); + $table->unsignedInteger('processed_rows')->default(0); + $table->unsignedInteger('total_rows'); + $table->unsignedInteger('successful_rows')->default(0); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('imports'); + } +}; diff --git a/database/migrations/2025_05_25_043332_create_exports_table.php b/database/migrations/2025_05_25_043332_create_exports_table.php new file mode 100644 index 0000000..a4aab11 --- /dev/null +++ b/database/migrations/2025_05_25_043332_create_exports_table.php @@ -0,0 +1,35 @@ +id(); + $table->timestamp('completed_at')->nullable(); + $table->string('file_disk'); + $table->string('file_name')->nullable(); + $table->string('exporter'); + $table->unsignedInteger('processed_rows')->default(0); + $table->unsignedInteger('total_rows'); + $table->unsignedInteger('successful_rows')->default(0); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('exports'); + } +}; diff --git a/database/migrations/2025_05_25_043333_create_failed_import_rows_table.php b/database/migrations/2025_05_25_043333_create_failed_import_rows_table.php new file mode 100644 index 0000000..260507e --- /dev/null +++ b/database/migrations/2025_05_25_043333_create_failed_import_rows_table.php @@ -0,0 +1,30 @@ +id(); + $table->json('data'); + $table->foreignId('import_id')->constrained()->cascadeOnDelete(); + $table->text('validation_error')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('failed_import_rows'); + } +};