From a08ca5302296e8535db05f15d98660d1046f05a2 Mon Sep 17 00:00:00 2001 From: petorikooru Date: Sat, 8 Aug 2026 20:13:57 +0700 Subject: [PATCH 01/10] Fixed the seeder, since before it would complain about permission error a lot --- database/factories/AsistenFactory.php | 28 +- database/factories/PraktikanFactory.php | 7 + database/seeders/DatabaseSeeder.php | 131 ++-- database/seeders/RolePermissionsSeeder.php | 719 +++++++++++---------- 4 files changed, 454 insertions(+), 431 deletions(-) diff --git a/database/factories/AsistenFactory.php b/database/factories/AsistenFactory.php index ff33d6b..ba49232 100644 --- a/database/factories/AsistenFactory.php +++ b/database/factories/AsistenFactory.php @@ -39,18 +39,26 @@ public function definition(): array ]; } - public function withRoles(array $roleName) + public function withRoles(array $roleNames) { - return $this->afterCreating(function (Asisten $asisten) { - // Assign role in Spatie - $asisten->assignRole($role); + return $this + ->state(function () use ($roleNames) { + $roleName = fake()->randomElement($roleNames); - // // Sync `role_id` in the asistens table - // if (!$asisten->role_id) { - // $asisten->role_id = $role->id; - // $asisten->save(); - // } + $role = Role::where('name', $roleName) + ->where('guard_name', 'asisten') + ->firstOrFail(); - }); + return [ + 'role_id' => $role->id, + ]; + }) + ->afterCreating(function (Asisten $asisten) { + $role = Role::where('id', $asisten->role_id) + ->where('guard_name', 'asisten') + ->firstOrFail(); + + $asisten->syncRoles([$role]); + }); } } diff --git a/database/factories/PraktikanFactory.php b/database/factories/PraktikanFactory.php index 10fbc84..1f485c3 100644 --- a/database/factories/PraktikanFactory.php +++ b/database/factories/PraktikanFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use App\Models\Kelas; +use App\Models\Praktikan; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -30,4 +31,10 @@ public function definition(): array 'updated_at' => now(), ]; } + public function withRole() + { + return $this->afterCreating(function (Praktikan $praktikan) { + $praktikan->assignRole('PRAKTIKAN'); + }); +} } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index eb76a53..f9b3dc8 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,6 +15,7 @@ use App\Models\SoalTp; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; +use Spatie\Permission\Models\Role; class DatabaseSeeder extends Seeder { @@ -24,74 +25,78 @@ class DatabaseSeeder extends Seeder public function run(): void { // First seeding - // Modul::factory(20)->create(); - // Role::create(['name' => 'SOFTWARE', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'KORDAS', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'WAKORDAS', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'KOORPRAK', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'ADMIN', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'HARDWARE', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'DDC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'ATC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'RDC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'HRD', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'CMD', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'MLC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); + $this->call(RolePermissionsSeeder::class); + + Modul::factory(20)->create(); // end Of First Seeding // //////////////////////////////////////////////////////////// // Second Seeding - // Kelas::factory(10)->create(); - - // Asisten::factory(10)->create(); - // $asisten = Asisten::factory(90)->withRoles(['SOFTWARE', 'KORDAS', 'WAKORDAS', 'KOORPRAK', 'ADMIN', 'HARDWARE', 'DDC', 'ATC', 'RDC', 'HRD', 'CMD', 'MLC'])->create(); - - // DB::table('configurations')->insert([ - // [ - // 'tp_activation' => 1, - // 'tubes_activation' => 0, - // 'polling_activation' => 1, - // 'secretfeature_activation' => 0, - // 'registrationPraktikan_activation' => 1, - // 'registrationAsisten_activation' => 1, - // 'kode_asisten' => 'BOT', - // 'created_at' => now(), - // 'updated_at' => now(), - // ], - // ]); - - // // buat polling - // $words = [ - // 'Terjaim', - // 'Terasik', - // 'Terganteng', - // 'Tercantik', - // 'Terjamet', - // 'Terwibu', - // 'Tergalak', - // 'Tergalau', - // 'Termager', - // 'Terkocak', - // 'Tergaring', - // 'Tersantuy', - // 'Terimut', - // 'Tercool', - // 'Terpelit', - // 'Terkece', - // 'Terkpop', - // ]; - - // foreach ($words as $word) { - // DB::table('jenis_pollings')->insert([ - // 'judul' => $word, - // 'created_at' => now(), - // 'updated_at' => now(), - // ]); - // } - - // // buat kode pelanggaran + Kelas::factory(10)->create(); + + Asisten::factory(30) + ->withRoles([ + 'SOFTWARE', + 'KORDAS', + 'WAKORDAS', + 'KOORPRAK', + 'ADMIN', + 'HARDWARE', + 'DDC', + 'ATC', + 'RDC', + 'HRD', + 'CMD', + 'MLC', + ]) + ->create(); + + DB::table('configurations')->insert([ + [ + 'tp_activation' => 1, + 'tubes_activation' => 0, + 'polling_activation' => 1, + 'secretfeature_activation' => 0, + 'registrationPraktikan_activation' => 1, + 'registrationAsisten_activation' => 1, + 'kode_asisten' => 'BOT', + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + + // buat polling + $words = [ + 'Terjaim', + 'Terasik', + 'Terganteng', + 'Tercantik', + 'Terjamet', + 'Terwibu', + 'Tergalak', + 'Tergalau', + 'Termager', + 'Terkocak', + 'Tergaring', + 'Tersantuy', + 'Terimut', + 'Tercool', + 'Terpelit', + 'Terkece', + 'Terkpop', + ]; + + foreach ($words as $word) { + DB::table('jenis_pollings')->insert([ + 'judul' => $word, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + // buat kode pelanggaran // DB::table('kode_pelanggarans')->insert([ // [ // 'pelanggaran' => 'Belum Input Nilai', @@ -108,7 +113,7 @@ public function run(): void // Third Seeding // // buat praktikan - // Praktikan::factory()->count(30)->create(); + Praktikan::factory()->count(30)->withRole()->create(); SoalTp::factory()->count(10)->create(); // Menghasilkan 10 record untuk tabel soal_tps SoalTa::factory()->count(10)->create(); // Menghasilkan 10 record untuk tabel soal_tas diff --git a/database/seeders/RolePermissionsSeeder.php b/database/seeders/RolePermissionsSeeder.php index 82b7635..52acab3 100644 --- a/database/seeders/RolePermissionsSeeder.php +++ b/database/seeders/RolePermissionsSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Spatie\Permission\PermissionRegistrar; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -13,374 +14,376 @@ class RolePermissionsSeeder extends Seeder */ public function run(): void { + app()[PermissionRegistrar::class]->forgetCachedPermissions(); // aslab Pro Ultra // List of permissions to create - // $permissions = [ - // // 'super-aslab', - // // 'aslab-features', - // // 'ddc-features', - // // 'atc-features', - // // 'rdc-features', - // // 'asisten-features', - // 'manage-role', - // 'manage-praktikum', - // 'laporan-praktikum', - // 'manage-plot', - // 'manage-pelanggaran', - // 'manage-modul', - // 'manage-soal', - // 'unlock-jawaban', - // 'tugas-pendahuluan', - // 'see-pelanggaran', - // 'lms-configuration', - // 'manage-profile', - // 'see-praktikum', - // 'see-history', - // 'see-soal', - // 'nilai-praktikan', - // 'see-plot', - // 'ranking-praktikan', - // 'see-polling', - // 'set-praktikan', - // 'reset-praktikan', - // 'check-tugas-pendahuluan', - // 'change-password', - // 'praktikan-regist', - // 'tp-configuration', - // 'logout', - // ]; - - // Role::create(['name' => 'SOFTWARE', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'KORDAS', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'WAKORDAS', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'KOORPRAK', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'ADMIN', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'HARDWARE', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'DDC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'ATC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'RDC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'HRD', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'CMD', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // Role::create(['name' => 'MLC', 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - - // // Create permissions - // foreach ($permissions as $permission) { - // Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); - // } + $permissions = [ + 'super-aslab', + 'aslab-features', + 'ddc-features', + 'atc-features', + 'rdc-features', + 'asisten-features', + 'manage-role', + 'manage-praktikum', + 'laporan-praktikum', + 'manage-plot', + 'manage-pelanggaran', + 'manage-modul', + 'manage-soal', + 'unlock-jawaban', + 'tugas-pendahuluan', + 'see-pelanggaran', + 'lms-configuration', + 'manage-profile', + 'see-praktikum', + 'see-history', + 'see-soal', + 'nilai-praktikan', + 'see-plot', + 'ranking-praktikan', + 'see-polling', + 'set-praktikan', + 'reset-praktikan', + 'check-tugas-pendahuluan', + 'change-password', + 'praktikan-regist', + 'tp-configuration', + 'logout', + ]; + + // Create permissions + foreach ($permissions as $permission) { + Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'asisten', 'created_at' => now(), 'updated_at' => now()]); + } + + $assistantRoles = [ + 'SOFTWARE', + 'KORDAS', + 'WAKORDAS', + 'KOORPRAK', + 'ADMIN', + 'HARDWARE', + 'DDC', + 'ATC', + 'RDC', + 'HRD', + 'CMD', + 'MLC', + ]; + + foreach ($assistantRoles as $roleName) { + Role::firstOrCreate([ + 'name' => $roleName, + 'guard_name' => 'asisten', + ]); + } + + Role::firstOrCreate([ + 'name' => 'PRAKTIKAN', + 'guard_name' => 'praktikan', + ]); - // Role::create(['name' => 'PRAKTIKAN', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'lihat-profile', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'lihat-nilai', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'lihat-modul', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'lihat-asisten', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'praktikum-lms', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'lihat-leaderboard', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'isi-polling', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - // Permission::create(['name' => 'ganti-password', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); - Permission::create(['name' => 'logout-praktikan', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'lihat-profile', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'lihat-nilai', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'lihat-modul', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'lihat-asisten', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'praktikum-lms', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'lihat-leaderboard', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'isi-polling', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'ganti-password', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); + Permission::firstOrCreate(['name' => 'logout-praktikan', 'guard_name' => 'praktikan', 'created_at' => now(), 'updated_at' => now()]); $role = Role::firstOrCreate(['name' => 'PRAKTIKAN', 'guard_name' => 'praktikan']); $role->givePermissionTo([ - // 'lihat-profile', - // 'lihat-nilai', - // 'lihat-modul', - // 'lihat-asisten', - // 'praktikum-lms', - // 'lihat-leaderboard', - // 'isi-polling', - // 'ganti-password', + 'lihat-profile', + 'lihat-nilai', + 'lihat-modul', + 'lihat-asisten', + 'praktikum-lms', + 'lihat-leaderboard', + 'isi-polling', + 'ganti-password', 'logout-praktikan', ]); - // $AslabProUltra = Role::firstOrCreate(['name' => 'SOFTWARE', 'guard_name' => 'asisten']); - - // $Kordas = Role::firstOrCreate(['name' => 'KORDAS', 'guard_name' => 'asisten']); - - // $Wakordas = Role::firstOrCreate(['name' => 'WAKORDAS', 'guard_name' => 'asisten']); - - // $Koorprak = Role::firstOrCreate(['name' => 'KOORPRAK', 'guard_name' => 'asisten']); - - // $Admin = Role::firstOrCreate(['name' => 'ADMIN', 'guard_name' => 'asisten']); - - // $Hardware = Role::firstOrCreate(['name' => 'HARDWARE', 'guard_name' => 'asisten']); - - // $AsistenSemiUltra = Role::firstOrCreate(['name' => 'DDC', 'guard_name' => 'asisten']); - - // $AsprakATC = Role::firstOrCreate(['name' => 'ATC', 'guard_name' => 'asisten']); - - // $AsprakRDC = Role::firstOrCreate(['name' => 'RDC', 'guard_name' => 'asisten']); - - // $AsprakCMD = Role::firstOrCreate(['name' => 'CMD', 'guard_name' => 'asisten']); - - // $AsprakHRD = Role::firstOrCreate(['name' => 'HRD', 'guard_name' => 'asisten']); - - // $AsprakMLC = Role::firstOrCreate(['name' => 'MLC', 'guard_name' => 'asisten']); - - // $AslabProUltra->givePermissionTo('manage-role'); - // $AslabProUltra->givePermissionTo('manage-praktikum'); - // $AslabProUltra->givePermissionTo('laporan-praktikum'); - // $AslabProUltra->givePermissionTo('manage-plot'); - // $AslabProUltra->givePermissionTo('manage-pelanggaran'); - // $AslabProUltra->givePermissionTo('manage-modul'); - // $AslabProUltra->givePermissionTo('manage-soal'); - // $AslabProUltra->givePermissionTo('unlock-jawaban'); - // $AslabProUltra->givePermissionTo('tugas-pendahuluan'); - // $AslabProUltra->givePermissionTo('lms-configuration'); - // $AslabProUltra->givePermissionTo('manage-profile'); - // $AslabProUltra->givePermissionTo('see-praktikum'); - // $AslabProUltra->givePermissionTo('see-history'); - // $AslabProUltra->givePermissionTo('see-soal'); - // $AslabProUltra->givePermissionTo('nilai-praktikan'); - // $AslabProUltra->givePermissionTo('see-plot'); - // $AslabProUltra->givePermissionTo('ranking-praktikan'); - // $AslabProUltra->givePermissionTo('see-polling'); - // $AslabProUltra->givePermissionTo('see-pelanggaran'); - // $AslabProUltra->givePermissionTo('set-praktikan'); - // $AslabProUltra->givePermissionTo('reset-praktikan'); - // $AslabProUltra->givePermissionTo('check-tugas-pendahuluan'); - // $AslabProUltra->givePermissionTo('change-password'); - // $AslabProUltra->givePermissionTo('tp-configuration'); - // $AslabProUltra->givePermissionTo('praktikan-regist'); - // $AslabProUltra->givePermissionTo('logout'); - - // $Kordas->givePermissionTo('manage-role'); - // $Kordas->givePermissionTo('manage-praktikum'); - // $Kordas->givePermissionTo('laporan-praktikum'); - // $Kordas->givePermissionTo('manage-plot'); - // $Kordas->givePermissionTo('manage-pelanggaran'); - // $Kordas->givePermissionTo('manage-modul'); - // $Kordas->givePermissionTo('manage-soal'); - // $Kordas->givePermissionTo('unlock-jawaban'); - // $Kordas->givePermissionTo('tugas-pendahuluan'); - // $Kordas->givePermissionTo('lms-configuration'); - // $Kordas->givePermissionTo('manage-profile'); - // $Kordas->givePermissionTo('see-praktikum'); - // $Kordas->givePermissionTo('see-history'); - // $Kordas->givePermissionTo('see-soal'); - // $Kordas->givePermissionTo('nilai-praktikan'); - // $Kordas->givePermissionTo('see-plot'); - // $Kordas->givePermissionTo('ranking-praktikan'); - // $Kordas->givePermissionTo('see-polling'); - // $Kordas->givePermissionTo('see-pelanggaran'); - // $Kordas->givePermissionTo('set-praktikan'); - // $Kordas->givePermissionTo('reset-praktikan'); - // $Kordas->givePermissionTo('check-tugas-pendahuluan'); - // $Kordas->givePermissionTo('change-password'); - // $Kordas->givePermissionTo('tp-configuration'); - // $Kordas->givePermissionTo('praktikan-regist'); - // $Kordas->givePermissionTo('logout'); - - // $Wakordas->givePermissionTo('manage-role'); - // $Wakordas->givePermissionTo('manage-praktikum'); - // $Wakordas->givePermissionTo('laporan-praktikum'); - // $Wakordas->givePermissionTo('manage-plot'); - // $Wakordas->givePermissionTo('manage-pelanggaran'); - // $Wakordas->givePermissionTo('manage-modul'); - // $Wakordas->givePermissionTo('manage-soal'); - // $Wakordas->givePermissionTo('unlock-jawaban'); - // $Wakordas->givePermissionTo('tugas-pendahuluan'); - // $Wakordas->givePermissionTo('lms-configuration'); - // $Wakordas->givePermissionTo('manage-profile'); - // $Wakordas->givePermissionTo('see-praktikum'); - // $Wakordas->givePermissionTo('see-history'); - // $Wakordas->givePermissionTo('see-soal'); - // $Wakordas->givePermissionTo('nilai-praktikan'); - // $Wakordas->givePermissionTo('see-plot'); - // $Wakordas->givePermissionTo('ranking-praktikan'); - // $Wakordas->givePermissionTo('see-polling'); - // $Wakordas->givePermissionTo('see-pelanggaran'); - // $Wakordas->givePermissionTo('set-praktikan'); - // $Wakordas->givePermissionTo('reset-praktikan'); - // $Wakordas->givePermissionTo('check-tugas-pendahuluan'); - // $Wakordas->givePermissionTo('change-password'); - // $Wakordas->givePermissionTo('tp-configuration'); - // $Wakordas->givePermissionTo('praktikan-regist'); - // $Wakordas->givePermissionTo('logout'); - - // $Koorprak->givePermissionTo('manage-praktikum'); - // $Koorprak->givePermissionTo('laporan-praktikum'); - // $Koorprak->givePermissionTo('manage-plot'); - // $Koorprak->givePermissionTo('manage-pelanggaran'); - // $Koorprak->givePermissionTo('manage-modul'); - // $Koorprak->givePermissionTo('manage-soal'); - // $Koorprak->givePermissionTo('unlock-jawaban'); - // $Koorprak->givePermissionTo('tugas-pendahuluan'); - // $Koorprak->givePermissionTo('lms-configuration'); - // $Koorprak->givePermissionTo('manage-profile'); - // $Koorprak->givePermissionTo('see-praktikum'); - // $Koorprak->givePermissionTo('see-history'); - // $Koorprak->givePermissionTo('see-soal'); - // $Koorprak->givePermissionTo('nilai-praktikan'); - // $Koorprak->givePermissionTo('see-plot'); - // $Koorprak->givePermissionTo('ranking-praktikan'); - // $Koorprak->givePermissionTo('see-polling'); - // $Koorprak->givePermissionTo('see-pelanggaran'); - // $Koorprak->givePermissionTo('set-praktikan'); - // $Koorprak->givePermissionTo('reset-praktikan'); - // $Koorprak->givePermissionTo('check-tugas-pendahuluan'); - // $Koorprak->givePermissionTo('change-password'); - // $Koorprak->givePermissionTo('tp-configuration'); - // $Koorprak->givePermissionTo('praktikan-regist'); - // $Koorprak->givePermissionTo('logout'); - - // $Admin->givePermissionTo('manage-praktikum'); - // $Admin->givePermissionTo('laporan-praktikum'); - // $Admin->givePermissionTo('manage-plot'); - // $Admin->givePermissionTo('manage-pelanggaran'); - // $Admin->givePermissionTo('manage-modul'); - // $Admin->givePermissionTo('manage-soal'); - // $Admin->givePermissionTo('unlock-jawaban'); - // $Admin->givePermissionTo('tugas-pendahuluan'); - // $Admin->givePermissionTo('lms-configuration'); - // $Admin->givePermissionTo('manage-profile'); - // $Admin->givePermissionTo('see-praktikum'); - // $Admin->givePermissionTo('see-history'); - // $Admin->givePermissionTo('see-soal'); - // $Admin->givePermissionTo('nilai-praktikan'); - // $Admin->givePermissionTo('see-plot'); - // $Admin->givePermissionTo('ranking-praktikan'); - // $Admin->givePermissionTo('see-polling'); - // $Admin->givePermissionTo('see-pelanggaran'); - // $Admin->givePermissionTo('set-praktikan'); - // $Admin->givePermissionTo('reset-praktikan'); - // $Admin->givePermissionTo('check-tugas-pendahuluan'); - // $Admin->givePermissionTo('change-password'); - // $Admin->givePermissionTo('tp-configuration'); - // $Admin->givePermissionTo('praktikan-regist'); - // $Admin->givePermissionTo('logout'); - - // $Hardware->givePermissionTo('manage-praktikum'); - // $Hardware->givePermissionTo('laporan-praktikum'); - // $Hardware->givePermissionTo('manage-plot'); - // $Hardware->givePermissionTo('manage-pelanggaran'); - // $Hardware->givePermissionTo('manage-modul'); - // $Hardware->givePermissionTo('manage-soal'); - // $Hardware->givePermissionTo('unlock-jawaban'); - // $Hardware->givePermissionTo('tugas-pendahuluan'); - // $Hardware->givePermissionTo('lms-configuration'); - // $Hardware->givePermissionTo('manage-profile'); - // $Hardware->givePermissionTo('see-praktikum'); - // $Hardware->givePermissionTo('see-history'); - // $Hardware->givePermissionTo('see-soal'); - // $Hardware->givePermissionTo('nilai-praktikan'); - // $Hardware->givePermissionTo('see-plot'); - // $Hardware->givePermissionTo('ranking-praktikan'); - // $Hardware->givePermissionTo('see-polling'); - // $Hardware->givePermissionTo('see-pelanggaran'); - // $Hardware->givePermissionTo('set-praktikan'); - // $Hardware->givePermissionTo('reset-praktikan'); - // $Hardware->givePermissionTo('check-tugas-pendahuluan'); - // $Hardware->givePermissionTo('change-password'); - // $Hardware->givePermissionTo('tp-configuration'); - // $Hardware->givePermissionTo('praktikan-regist'); - // $Hardware->givePermissionTo('logout'); - - // $AsistenSemiUltra->givePermissionTo('manage-profile'); - // $AsistenSemiUltra->givePermissionTo('see-praktikum'); - // $AsistenSemiUltra->givePermissionTo('see-history'); - // $AsistenSemiUltra->givePermissionTo('see-soal'); - // $AsistenSemiUltra->givePermissionTo('nilai-praktikan'); - // $AsistenSemiUltra->givePermissionTo('see-plot'); - // $AsistenSemiUltra->givePermissionTo('ranking-praktikan'); - // $AsistenSemiUltra->givePermissionTo('see-polling'); - // $AsistenSemiUltra->givePermissionTo('see-pelanggaran'); - // $AsistenSemiUltra->givePermissionTo('set-praktikan'); - // $AsistenSemiUltra->givePermissionTo('reset-praktikan'); - // $AsistenSemiUltra->givePermissionTo('check-tugas-pendahuluan'); - // $AsistenSemiUltra->givePermissionTo('change-password'); - // $AsistenSemiUltra->givePermissionTo('logout'); - // $AsistenSemiUltra->givePermissionTo('manage-modul'); - // $AsistenSemiUltra->givePermissionTo('manage-soal'); - // $AsistenSemiUltra->givePermissionTo('unlock-jawaban'); - // $AsistenSemiUltra->givePermissionTo('manage-praktikum'); - // $AsistenSemiUltra->givePermissionTo('see-pelanggaran'); - // $AsistenSemiUltra->givePermissionTo('tugas-pendahuluan'); - // $AsistenSemiUltra->givePermissionTo('tp-configuration'); - // $AsistenSemiUltra->givePermissionTo('praktikan-regist'); - - // $AsprakATC->givePermissionTo('manage-profile'); - // $AsprakATC->givePermissionTo('see-praktikum'); - // $AsprakATC->givePermissionTo('see-history'); - // $AsprakATC->givePermissionTo('see-soal'); - // $AsprakATC->givePermissionTo('nilai-praktikan'); - // $AsprakATC->givePermissionTo('see-plot'); - // $AsprakATC->givePermissionTo('ranking-praktikan'); - // $AsprakATC->givePermissionTo('see-polling'); - // $AsprakATC->givePermissionTo('see-pelanggaran'); - // $AsprakATC->givePermissionTo('set-praktikan'); - // $AsprakATC->givePermissionTo('reset-praktikan'); - // $AsprakATC->givePermissionTo('check-tugas-pendahuluan'); - // $AsprakATC->givePermissionTo('change-password'); - // $AsprakATC->givePermissionTo('logout'); - // $AsprakATC->givePermissionTo('manage-modul'); - // $AsprakATC->givePermissionTo('manage-soal'); - // $AsprakATC->givePermissionTo('unlock-jawaban'); - // $AsprakATC->givePermissionTo('tugas-pendahuluan'); - - // $AsprakRDC->givePermissionTo('manage-profile'); - // $AsprakRDC->givePermissionTo('see-praktikum'); - // $AsprakRDC->givePermissionTo('see-history'); - // $AsprakRDC->givePermissionTo('see-soal'); - // $AsprakRDC->givePermissionTo('nilai-praktikan'); - // $AsprakRDC->givePermissionTo('see-plot'); - // $AsprakRDC->givePermissionTo('ranking-praktikan'); - // $AsprakRDC->givePermissionTo('see-polling'); - // $AsprakRDC->givePermissionTo('see-pelanggaran'); - // $AsprakRDC->givePermissionTo('set-praktikan'); - // $AsprakRDC->givePermissionTo('reset-praktikan'); - // $AsprakRDC->givePermissionTo('check-tugas-pendahuluan'); - // $AsprakRDC->givePermissionTo('change-password'); - // $AsprakRDC->givePermissionTo('logout'); - // $AsprakRDC->givePermissionTo('manage-praktikum'); - // $AsprakRDC->givePermissionTo('tp-configuration'); - // $AsprakRDC->givePermissionTo('praktikan-regist'); - // $AsprakRDC->givePermissionTo('see-pelanggaran'); - - // $AsprakCMD->givePermissionTo('manage-profile'); - // $AsprakCMD->givePermissionTo('see-praktikum'); - // $AsprakCMD->givePermissionTo('see-history'); - // $AsprakCMD->givePermissionTo('see-soal'); - // $AsprakCMD->givePermissionTo('nilai-praktikan'); - // $AsprakCMD->givePermissionTo('see-plot'); - // $AsprakCMD->givePermissionTo('ranking-praktikan'); - // $AsprakCMD->givePermissionTo('see-polling'); - // $AsprakCMD->givePermissionTo('see-pelanggaran'); - // $AsprakCMD->givePermissionTo('set-praktikan'); - // $AsprakCMD->givePermissionTo('reset-praktikan'); - // $AsprakCMD->givePermissionTo('check-tugas-pendahuluan'); - // $AsprakCMD->givePermissionTo('change-password'); - // $AsprakCMD->givePermissionTo('logout'); - - // $AsprakHRD->givePermissionTo('manage-profile'); - // $AsprakHRD->givePermissionTo('see-praktikum'); - // $AsprakHRD->givePermissionTo('see-history'); - // $AsprakHRD->givePermissionTo('see-soal'); - // $AsprakHRD->givePermissionTo('nilai-praktikan'); - // $AsprakHRD->givePermissionTo('see-plot'); - // $AsprakHRD->givePermissionTo('ranking-praktikan'); - // $AsprakHRD->givePermissionTo('see-polling'); - // $AsprakHRD->givePermissionTo('see-pelanggaran'); - // $AsprakHRD->givePermissionTo('set-praktikan'); - // $AsprakHRD->givePermissionTo('reset-praktikan'); - // $AsprakHRD->givePermissionTo('check-tugas-pendahuluan'); - // $AsprakHRD->givePermissionTo('change-password'); - // $AsprakHRD->givePermissionTo('logout'); - - // $AsprakMLC->givePermissionTo('manage-profile'); - // $AsprakMLC->givePermissionTo('see-praktikum'); - // $AsprakMLC->givePermissionTo('see-history'); - // $AsprakMLC->givePermissionTo('see-soal'); - // $AsprakMLC->givePermissionTo('nilai-praktikan'); - // $AsprakMLC->givePermissionTo('see-plot'); - // $AsprakMLC->givePermissionTo('ranking-praktikan'); - // $AsprakMLC->givePermissionTo('see-polling'); - // $AsprakMLC->givePermissionTo('see-pelanggaran'); - // $AsprakMLC->givePermissionTo('set-praktikan'); - // $AsprakMLC->givePermissionTo('reset-praktikan'); - // $AsprakMLC->givePermissionTo('check-tugas-pendahuluan'); - // $AsprakMLC->givePermissionTo('change-password'); - // $AsprakMLC->givePermissionTo('logout'); + $AslabProUltra = Role::firstOrCreate(['name' => 'SOFTWARE', 'guard_name' => 'asisten']); + $Kordas = Role::firstOrCreate(['name' => 'KORDAS', 'guard_name' => 'asisten']); + $Wakordas = Role::firstOrCreate(['name' => 'WAKORDAS', 'guard_name' => 'asisten']); + $Koorprak = Role::firstOrCreate(['name' => 'KOORPRAK', 'guard_name' => 'asisten']); + $Admin = Role::firstOrCreate(['name' => 'ADMIN', 'guard_name' => 'asisten']); + $Hardware = Role::firstOrCreate(['name' => 'HARDWARE', 'guard_name' => 'asisten']); + $AsistenSemiUltra = Role::firstOrCreate(['name' => 'DDC', 'guard_name' => 'asisten']); + $AsprakATC = Role::firstOrCreate(['name' => 'ATC', 'guard_name' => 'asisten']); + $AsprakRDC = Role::firstOrCreate(['name' => 'RDC', 'guard_name' => 'asisten']); + $AsprakCMD = Role::firstOrCreate(['name' => 'CMD', 'guard_name' => 'asisten']); + $AsprakHRD = Role::firstOrCreate(['name' => 'HRD', 'guard_name' => 'asisten']); + $AsprakMLC = Role::firstOrCreate(['name' => 'MLC', 'guard_name' => 'asisten']); + $AslabProUltra->givePermissionTo('manage-role'); + $AslabProUltra->givePermissionTo('manage-praktikum'); + $AslabProUltra->givePermissionTo('laporan-praktikum'); + $AslabProUltra->givePermissionTo('manage-plot'); + $AslabProUltra->givePermissionTo('manage-pelanggaran'); + $AslabProUltra->givePermissionTo('manage-modul'); + $AslabProUltra->givePermissionTo('manage-soal'); + $AslabProUltra->givePermissionTo('unlock-jawaban'); + $AslabProUltra->givePermissionTo('tugas-pendahuluan'); + $AslabProUltra->givePermissionTo('lms-configuration'); + $AslabProUltra->givePermissionTo('manage-profile'); + $AslabProUltra->givePermissionTo('see-praktikum'); + $AslabProUltra->givePermissionTo('see-history'); + $AslabProUltra->givePermissionTo('see-soal'); + $AslabProUltra->givePermissionTo('nilai-praktikan'); + $AslabProUltra->givePermissionTo('see-plot'); + $AslabProUltra->givePermissionTo('ranking-praktikan'); + $AslabProUltra->givePermissionTo('see-polling'); + $AslabProUltra->givePermissionTo('see-pelanggaran'); + $AslabProUltra->givePermissionTo('set-praktikan'); + $AslabProUltra->givePermissionTo('reset-praktikan'); + $AslabProUltra->givePermissionTo('check-tugas-pendahuluan'); + $AslabProUltra->givePermissionTo('change-password'); + $AslabProUltra->givePermissionTo('tp-configuration'); + $AslabProUltra->givePermissionTo('praktikan-regist'); + $AslabProUltra->givePermissionTo('logout'); + + $Kordas->givePermissionTo('manage-role'); + $Kordas->givePermissionTo('manage-praktikum'); + $Kordas->givePermissionTo('laporan-praktikum'); + $Kordas->givePermissionTo('manage-plot'); + $Kordas->givePermissionTo('manage-pelanggaran'); + $Kordas->givePermissionTo('manage-modul'); + $Kordas->givePermissionTo('manage-soal'); + $Kordas->givePermissionTo('unlock-jawaban'); + $Kordas->givePermissionTo('tugas-pendahuluan'); + $Kordas->givePermissionTo('lms-configuration'); + $Kordas->givePermissionTo('manage-profile'); + $Kordas->givePermissionTo('see-praktikum'); + $Kordas->givePermissionTo('see-history'); + $Kordas->givePermissionTo('see-soal'); + $Kordas->givePermissionTo('nilai-praktikan'); + $Kordas->givePermissionTo('see-plot'); + $Kordas->givePermissionTo('ranking-praktikan'); + $Kordas->givePermissionTo('see-polling'); + $Kordas->givePermissionTo('see-pelanggaran'); + $Kordas->givePermissionTo('set-praktikan'); + $Kordas->givePermissionTo('reset-praktikan'); + $Kordas->givePermissionTo('check-tugas-pendahuluan'); + $Kordas->givePermissionTo('change-password'); + $Kordas->givePermissionTo('tp-configuration'); + $Kordas->givePermissionTo('praktikan-regist'); + $Kordas->givePermissionTo('logout'); + + $Wakordas->givePermissionTo('manage-role'); + $Wakordas->givePermissionTo('manage-praktikum'); + $Wakordas->givePermissionTo('laporan-praktikum'); + $Wakordas->givePermissionTo('manage-plot'); + $Wakordas->givePermissionTo('manage-pelanggaran'); + $Wakordas->givePermissionTo('manage-modul'); + $Wakordas->givePermissionTo('manage-soal'); + $Wakordas->givePermissionTo('unlock-jawaban'); + $Wakordas->givePermissionTo('tugas-pendahuluan'); + $Wakordas->givePermissionTo('lms-configuration'); + $Wakordas->givePermissionTo('manage-profile'); + $Wakordas->givePermissionTo('see-praktikum'); + $Wakordas->givePermissionTo('see-history'); + $Wakordas->givePermissionTo('see-soal'); + $Wakordas->givePermissionTo('nilai-praktikan'); + $Wakordas->givePermissionTo('see-plot'); + $Wakordas->givePermissionTo('ranking-praktikan'); + $Wakordas->givePermissionTo('see-polling'); + $Wakordas->givePermissionTo('see-pelanggaran'); + $Wakordas->givePermissionTo('set-praktikan'); + $Wakordas->givePermissionTo('reset-praktikan'); + $Wakordas->givePermissionTo('check-tugas-pendahuluan'); + $Wakordas->givePermissionTo('change-password'); + $Wakordas->givePermissionTo('tp-configuration'); + $Wakordas->givePermissionTo('praktikan-regist'); + $Wakordas->givePermissionTo('logout'); + + $Koorprak->givePermissionTo('manage-praktikum'); + $Koorprak->givePermissionTo('laporan-praktikum'); + $Koorprak->givePermissionTo('manage-plot'); + $Koorprak->givePermissionTo('manage-pelanggaran'); + $Koorprak->givePermissionTo('manage-modul'); + $Koorprak->givePermissionTo('manage-soal'); + $Koorprak->givePermissionTo('unlock-jawaban'); + $Koorprak->givePermissionTo('tugas-pendahuluan'); + $Koorprak->givePermissionTo('lms-configuration'); + $Koorprak->givePermissionTo('manage-profile'); + $Koorprak->givePermissionTo('see-praktikum'); + $Koorprak->givePermissionTo('see-history'); + $Koorprak->givePermissionTo('see-soal'); + $Koorprak->givePermissionTo('nilai-praktikan'); + $Koorprak->givePermissionTo('see-plot'); + $Koorprak->givePermissionTo('ranking-praktikan'); + $Koorprak->givePermissionTo('see-polling'); + $Koorprak->givePermissionTo('see-pelanggaran'); + $Koorprak->givePermissionTo('set-praktikan'); + $Koorprak->givePermissionTo('reset-praktikan'); + $Koorprak->givePermissionTo('check-tugas-pendahuluan'); + $Koorprak->givePermissionTo('change-password'); + $Koorprak->givePermissionTo('tp-configuration'); + $Koorprak->givePermissionTo('praktikan-regist'); + $Koorprak->givePermissionTo('logout'); + + $Admin->givePermissionTo('manage-praktikum'); + $Admin->givePermissionTo('laporan-praktikum'); + $Admin->givePermissionTo('manage-plot'); + $Admin->givePermissionTo('manage-pelanggaran'); + $Admin->givePermissionTo('manage-modul'); + $Admin->givePermissionTo('manage-soal'); + $Admin->givePermissionTo('unlock-jawaban'); + $Admin->givePermissionTo('tugas-pendahuluan'); + $Admin->givePermissionTo('lms-configuration'); + $Admin->givePermissionTo('manage-profile'); + $Admin->givePermissionTo('see-praktikum'); + $Admin->givePermissionTo('see-history'); + $Admin->givePermissionTo('see-soal'); + $Admin->givePermissionTo('nilai-praktikan'); + $Admin->givePermissionTo('see-plot'); + $Admin->givePermissionTo('ranking-praktikan'); + $Admin->givePermissionTo('see-polling'); + $Admin->givePermissionTo('see-pelanggaran'); + $Admin->givePermissionTo('set-praktikan'); + $Admin->givePermissionTo('reset-praktikan'); + $Admin->givePermissionTo('check-tugas-pendahuluan'); + $Admin->givePermissionTo('change-password'); + $Admin->givePermissionTo('tp-configuration'); + $Admin->givePermissionTo('praktikan-regist'); + $Admin->givePermissionTo('logout'); + + $Hardware->givePermissionTo('manage-praktikum'); + $Hardware->givePermissionTo('laporan-praktikum'); + $Hardware->givePermissionTo('manage-plot'); + $Hardware->givePermissionTo('manage-pelanggaran'); + $Hardware->givePermissionTo('manage-modul'); + $Hardware->givePermissionTo('manage-soal'); + $Hardware->givePermissionTo('unlock-jawaban'); + $Hardware->givePermissionTo('tugas-pendahuluan'); + $Hardware->givePermissionTo('lms-configuration'); + $Hardware->givePermissionTo('manage-profile'); + $Hardware->givePermissionTo('see-praktikum'); + $Hardware->givePermissionTo('see-history'); + $Hardware->givePermissionTo('see-soal'); + $Hardware->givePermissionTo('nilai-praktikan'); + $Hardware->givePermissionTo('see-plot'); + $Hardware->givePermissionTo('ranking-praktikan'); + $Hardware->givePermissionTo('see-polling'); + $Hardware->givePermissionTo('see-pelanggaran'); + $Hardware->givePermissionTo('set-praktikan'); + $Hardware->givePermissionTo('reset-praktikan'); + $Hardware->givePermissionTo('check-tugas-pendahuluan'); + $Hardware->givePermissionTo('change-password'); + $Hardware->givePermissionTo('tp-configuration'); + $Hardware->givePermissionTo('praktikan-regist'); + $Hardware->givePermissionTo('logout'); + + $AsistenSemiUltra->givePermissionTo('manage-profile'); + $AsistenSemiUltra->givePermissionTo('see-praktikum'); + $AsistenSemiUltra->givePermissionTo('see-history'); + $AsistenSemiUltra->givePermissionTo('see-soal'); + $AsistenSemiUltra->givePermissionTo('nilai-praktikan'); + $AsistenSemiUltra->givePermissionTo('see-plot'); + $AsistenSemiUltra->givePermissionTo('ranking-praktikan'); + $AsistenSemiUltra->givePermissionTo('see-polling'); + $AsistenSemiUltra->givePermissionTo('see-pelanggaran'); + $AsistenSemiUltra->givePermissionTo('set-praktikan'); + $AsistenSemiUltra->givePermissionTo('reset-praktikan'); + $AsistenSemiUltra->givePermissionTo('check-tugas-pendahuluan'); + $AsistenSemiUltra->givePermissionTo('change-password'); + $AsistenSemiUltra->givePermissionTo('logout'); + $AsistenSemiUltra->givePermissionTo('manage-modul'); + $AsistenSemiUltra->givePermissionTo('manage-soal'); + $AsistenSemiUltra->givePermissionTo('unlock-jawaban'); + $AsistenSemiUltra->givePermissionTo('manage-praktikum'); + $AsistenSemiUltra->givePermissionTo('see-pelanggaran'); + $AsistenSemiUltra->givePermissionTo('tugas-pendahuluan'); + $AsistenSemiUltra->givePermissionTo('tp-configuration'); + $AsistenSemiUltra->givePermissionTo('praktikan-regist'); + + $AsprakATC->givePermissionTo('manage-profile'); + $AsprakATC->givePermissionTo('see-praktikum'); + $AsprakATC->givePermissionTo('see-history'); + $AsprakATC->givePermissionTo('see-soal'); + $AsprakATC->givePermissionTo('nilai-praktikan'); + $AsprakATC->givePermissionTo('see-plot'); + $AsprakATC->givePermissionTo('ranking-praktikan'); + $AsprakATC->givePermissionTo('see-polling'); + $AsprakATC->givePermissionTo('see-pelanggaran'); + $AsprakATC->givePermissionTo('set-praktikan'); + $AsprakATC->givePermissionTo('reset-praktikan'); + $AsprakATC->givePermissionTo('check-tugas-pendahuluan'); + $AsprakATC->givePermissionTo('change-password'); + $AsprakATC->givePermissionTo('logout'); + $AsprakATC->givePermissionTo('manage-modul'); + $AsprakATC->givePermissionTo('manage-soal'); + $AsprakATC->givePermissionTo('unlock-jawaban'); + $AsprakATC->givePermissionTo('tugas-pendahuluan'); + + $AsprakRDC->givePermissionTo('manage-profile'); + $AsprakRDC->givePermissionTo('see-praktikum'); + $AsprakRDC->givePermissionTo('see-history'); + $AsprakRDC->givePermissionTo('see-soal'); + $AsprakRDC->givePermissionTo('nilai-praktikan'); + $AsprakRDC->givePermissionTo('see-plot'); + $AsprakRDC->givePermissionTo('ranking-praktikan'); + $AsprakRDC->givePermissionTo('see-polling'); + $AsprakRDC->givePermissionTo('see-pelanggaran'); + $AsprakRDC->givePermissionTo('set-praktikan'); + $AsprakRDC->givePermissionTo('reset-praktikan'); + $AsprakRDC->givePermissionTo('check-tugas-pendahuluan'); + $AsprakRDC->givePermissionTo('change-password'); + $AsprakRDC->givePermissionTo('logout'); + $AsprakRDC->givePermissionTo('manage-praktikum'); + $AsprakRDC->givePermissionTo('tp-configuration'); + $AsprakRDC->givePermissionTo('praktikan-regist'); + $AsprakRDC->givePermissionTo('see-pelanggaran'); + + $AsprakCMD->givePermissionTo('manage-profile'); + $AsprakCMD->givePermissionTo('see-praktikum'); + $AsprakCMD->givePermissionTo('see-history'); + $AsprakCMD->givePermissionTo('see-soal'); + $AsprakCMD->givePermissionTo('nilai-praktikan'); + $AsprakCMD->givePermissionTo('see-plot'); + $AsprakCMD->givePermissionTo('ranking-praktikan'); + $AsprakCMD->givePermissionTo('see-polling'); + $AsprakCMD->givePermissionTo('see-pelanggaran'); + $AsprakCMD->givePermissionTo('set-praktikan'); + $AsprakCMD->givePermissionTo('reset-praktikan'); + $AsprakCMD->givePermissionTo('check-tugas-pendahuluan'); + $AsprakCMD->givePermissionTo('change-password'); + $AsprakCMD->givePermissionTo('logout'); + + $AsprakHRD->givePermissionTo('manage-profile'); + $AsprakHRD->givePermissionTo('see-praktikum'); + $AsprakHRD->givePermissionTo('see-history'); + $AsprakHRD->givePermissionTo('see-soal'); + $AsprakHRD->givePermissionTo('nilai-praktikan'); + $AsprakHRD->givePermissionTo('see-plot'); + $AsprakHRD->givePermissionTo('ranking-praktikan'); + $AsprakHRD->givePermissionTo('see-polling'); + $AsprakHRD->givePermissionTo('see-pelanggaran'); + $AsprakHRD->givePermissionTo('set-praktikan'); + $AsprakHRD->givePermissionTo('reset-praktikan'); + $AsprakHRD->givePermissionTo('check-tugas-pendahuluan'); + $AsprakHRD->givePermissionTo('change-password'); + $AsprakHRD->givePermissionTo('logout'); + + $AsprakMLC->givePermissionTo('manage-profile'); + $AsprakMLC->givePermissionTo('see-praktikum'); + $AsprakMLC->givePermissionTo('see-history'); + $AsprakMLC->givePermissionTo('see-soal'); + $AsprakMLC->givePermissionTo('nilai-praktikan'); + $AsprakMLC->givePermissionTo('see-plot'); + $AsprakMLC->givePermissionTo('ranking-praktikan'); + $AsprakMLC->givePermissionTo('see-polling'); + $AsprakMLC->givePermissionTo('see-pelanggaran'); + $AsprakMLC->givePermissionTo('set-praktikan'); + $AsprakMLC->givePermissionTo('reset-praktikan'); + $AsprakMLC->givePermissionTo('check-tugas-pendahuluan'); + $AsprakMLC->givePermissionTo('change-password'); + $AsprakMLC->givePermissionTo('logout'); } } From fd59b3ebf737b05ef89aac402a788c3c18b4d553 Mon Sep 17 00:00:00 2001 From: petorikooru Date: Sat, 8 Aug 2026 20:25:17 +0700 Subject: [PATCH 02/10] Fixed a bug where the praktikans grade calculations in TA and TK missbehave when they dont select the answer in question(s) --- app/Http/Controllers/API/JawabanTAController.php | 5 +++-- app/Http/Controllers/API/JawabanTKController.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/API/JawabanTAController.php b/app/Http/Controllers/API/JawabanTAController.php index 47d4589..5917559 100644 --- a/app/Http/Controllers/API/JawabanTAController.php +++ b/app/Http/Controllers/API/JawabanTAController.php @@ -69,7 +69,7 @@ public function store(Request $request): JsonResponse 'praktikan_id' => ['required', 'integer'], 'modul_id' => ['required', 'integer'], 'answers' => ['present', 'array'], - 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tas,id'], + 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tas,id'. 'distinct'], 'answers.*.opsi_id' => ['required', 'integer', 'exists:soal_opsis,id'], ]); @@ -114,7 +114,8 @@ public function store(Request $request): JsonResponse ->get(); $correct = $jawaban->filter(fn (JawabanTa $item) => $item->opsi_id === $item->soal_ta?->opsi_benar_id)->count(); - $nilai = $jawaban->isNotEmpty() ? ($correct / $jawaban->count()) * 100 : 0; + $totalQuestions = SoalTa::where('modul_id', $modulId)->count(); + $nilai = $totalQuestions > 0 ? round(($correct / $totalQuestions) * 100, 2) : 0; return response()->json([ 'status' => 'success', diff --git a/app/Http/Controllers/API/JawabanTKController.php b/app/Http/Controllers/API/JawabanTKController.php index 847f090..142d9f0 100644 --- a/app/Http/Controllers/API/JawabanTKController.php +++ b/app/Http/Controllers/API/JawabanTKController.php @@ -69,7 +69,7 @@ public function store(Request $request): JsonResponse 'praktikan_id' => ['required', 'integer'], 'modul_id' => ['required', 'integer'], 'answers' => ['present', 'array'], - 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tks,id'], + 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tks,id', 'distinct'], 'answers.*.opsi_id' => ['required', 'integer', 'exists:soal_opsis,id'], ]); @@ -113,7 +113,8 @@ public function store(Request $request): JsonResponse ->get(); $correct = $jawaban->filter(fn (JawabanTk $item) => $item->opsi_id === $item->soal_tk?->opsi_benar_id)->count(); - $nilai = $jawaban->isNotEmpty() ? ($correct / $jawaban->count()) * 100 : 0; + $totalQuestions = SoalTk::where('modul_id', $modulId)->count(); + $nilai = $totalQuestions > 0 ? round(($correct / $totalQuestions) * 100, 2) : 0; return response()->json([ 'status' => 'success', From 7935478ea3b059cfe354599ae78fa391c5d0058c Mon Sep 17 00:00:00 2001 From: petorikooru Date: Sat, 8 Aug 2026 21:27:01 +0700 Subject: [PATCH 03/10] Added image input in TP in preperation for an addition of TP's flowchart type of question --- app/Http/Controllers/API/SoalTPController.php | 9 +- .../Assistants/Content/ContentLihatTP.jsx | 184 ++++++- .../Assistants/Modals/ModalInputNilai.jsx | 72 ++- .../Assistants/Soal/SoalInputEssay.jsx | 2 +- .../Praktikans/Sections/TugasPendahuluan.jsx | 502 ++++++++++++++++-- .../Pages/Praktikan/TugasPendahuluanPage.jsx | 87 ++- 6 files changed, 778 insertions(+), 78 deletions(-) diff --git a/app/Http/Controllers/API/SoalTPController.php b/app/Http/Controllers/API/SoalTPController.php index f756f2f..23f7f96 100644 --- a/app/Http/Controllers/API/SoalTPController.php +++ b/app/Http/Controllers/API/SoalTPController.php @@ -25,12 +25,14 @@ public function store(Request $request, $id) // Validasi input $request->validate([ 'soal' => 'required|string|max:10000', + 'enable_file_upload' => 'sometimes|boolean', ]); // Menyimpan soal baru $soal = SoalTp::create([ 'modul_id' => $id, 'soal' => $request->soal, + 'enable_file_upload' => $request->boolean('enable_file_upload'), ]); return response()->json([ @@ -76,7 +78,8 @@ public function update(Request $request, $id) 'modul_id' => 'required|integer|exists:moduls,id', // "isEssay" => "required|boolean", // "isProgram" => "required|boolean", - 'soal' => 'required|string|max:1000', + 'soal' => 'required|string|max:10000', + 'enable_file_upload' => 'sometimes|boolean', ]); $soal = SoalTp::find($id); if (! $soal) { @@ -97,6 +100,10 @@ public function update(Request $request, $id) // $soal->isEssay = $request->isEssay; // $soal->isProgram = $request->isProgram; $soal->soal = $request->soal; + if ($request->has('enable_file_upload')) { + $soal->enable_file_upload = + $request->boolean('enable_file_upload'); + } $soal->updated_at = now(); $soal->save(); diff --git a/resources/js/Components/Assistants/Content/ContentLihatTP.jsx b/resources/js/Components/Assistants/Content/ContentLihatTP.jsx index 452f106..02a5629 100644 --- a/resources/js/Components/Assistants/Content/ContentLihatTP.jsx +++ b/resources/js/Components/Assistants/Content/ContentLihatTP.jsx @@ -1,7 +1,120 @@ import MarkdownRenderer from "../../MarkdownRenderer"; +import { Image } from "@imagekit/react"; -export default function ContentLihatTP({ jawabanData, praktikan, modul }) { - const hasData = jawabanData && jawabanData.length > 0; +const parseFileAnswer = (value) => { + if (!value) { + return null; + } + + // Already an object + if ( + typeof value === "object" && + value?.type === "file" && + value?.url + ) { + return value; + } + + // Stored as JSON string in database + if (typeof value === "string") { + try { + const parsed = JSON.parse(value); + + if ( + parsed && + parsed.type === "file" && + parsed.url + ) { + return parsed; + } + } catch { + // Normal text answer, so ignore JSON parsing failure + } + } + + return null; +}; + +const AnswerContent = ({ answer }) => { + if (!answer) { + return ( + + Tidak ada jawaban + + ); + } + + const fileAnswer = parseFileAnswer(answer); + + // Image/file answer + if (fileAnswer) { + return ( +
+ + Jawaban Tugas Pendahuluan + + +
+ + + + + + + Buka gambar penuh + +
+
+ ); + } + + // Normal text answer + return ; +}; + +export default function ContentLihatTP({ + jawabanData, + praktikan, + modul, +}) { + const hasData = + Array.isArray(jawabanData) && + jawabanData.length > 0; return (
@@ -13,27 +126,33 @@ export default function ContentLihatTP({ jawabanData, praktikan, modul }) { Informasi Praktikan
+
Nama + {praktikan.nama}
+
NIM + {praktikan.nim}
+
Modul + {modul.judul} @@ -47,6 +166,7 @@ export default function ContentLihatTP({ jawabanData, praktikan, modul }) {

Jawaban Tugas Pendahuluan + {hasData && ( {jawabanData.length} soal @@ -58,19 +178,32 @@ export default function ContentLihatTP({ jawabanData, praktikan, modul }) {
{hasData ? ( jawabanData.map((item, index) => ( -
+
{/* Question */}
{index + 1} +
- +
{/* Answer */} -
+
- + + + Jawaban
+
- {item.jawaban ? ( - - ) : ( - - Tidak ada jawaban - - )} +
@@ -111,14 +249,26 @@ export default function ContentLihatTP({ jawabanData, praktikan, modul }) { stroke="currentColor" strokeWidth={1.5} > - - + + + -

Belum ada jawaban yang tersubmit untuk modul ini.

+ +

+ Belum ada jawaban yang + tersubmit untuk modul ini. +

)}

); -} \ No newline at end of file +} diff --git a/resources/js/Components/Assistants/Modals/ModalInputNilai.jsx b/resources/js/Components/Assistants/Modals/ModalInputNilai.jsx index dddb471..be40db5 100644 --- a/resources/js/Components/Assistants/Modals/ModalInputNilai.jsx +++ b/resources/js/Components/Assistants/Modals/ModalInputNilai.jsx @@ -251,18 +251,30 @@ export default function ModalInputNilai({ : []; return { - questions: entries.map((item, index) => ({ - soalId: item.soal_id ?? index, - question: - item.soal_text ?? "Soal tidak tersedia", - answer: - typeof item.jawaban === "string" - ? item.jawaban - : "-", - options: [], - selectedOptionId: null, - correctOptionId: null, - })), + questions: entries.map((item, index) => { + const parsed = + normalizeAnswerWithAttachment(item); + + return { + soalId: item.soal_id ?? index, + question: + item.soal_text ?? + "Soal tidak tersedia", + + answer: parsed.answerText, + + attachmentUrl: + parsed.attachmentUrl, + + attachmentFileId: + parsed.attachmentFileId, + + options: [], + selectedOptionId: null, + correctOptionId: null, + }; + }), + notice: data?.message, }; } @@ -325,18 +337,30 @@ export default function ModalInputNilai({ : []; return { - questions: entries.map((item, index) => ({ - soalId: item.soal_id ?? index, - question: - item.soal_text ?? "Soal tidak tersedia", - answer: - typeof item.jawaban === "string" - ? item.jawaban - : "-", - options: [], - selectedOptionId: null, - correctOptionId: null, - })), + questions: entries.map((item, index) => { + const parsed = + normalizeAnswerWithAttachment(item); + + return { + soalId: item.soal_id ?? index, + question: + item.soal_text ?? + "Soal tidak tersedia", + + answer: parsed.answerText, + + attachmentUrl: + parsed.attachmentUrl, + + attachmentFileId: + parsed.attachmentFileId, + + options: [], + selectedOptionId: null, + correctOptionId: null, + }; + }), + notice: data?.message, }; } diff --git a/resources/js/Components/Assistants/Soal/SoalInputEssay.jsx b/resources/js/Components/Assistants/Soal/SoalInputEssay.jsx index 1f338e8..d2e114e 100644 --- a/resources/js/Components/Assistants/Soal/SoalInputEssay.jsx +++ b/resources/js/Components/Assistants/Soal/SoalInputEssay.jsx @@ -53,7 +53,7 @@ export default function SoalInputEssay({ kategoriSoal, modul, modules = [], onMo const controller = getSoalController(kategoriSoal); const supportsFileUpload = useMemo( - () => ["jurnal", "fitb"].includes(kategoriSoal), + () => ["tp", "jurnal", "fitb"].includes(kategoriSoal), [kategoriSoal] ); diff --git a/resources/js/Components/Praktikans/Sections/TugasPendahuluan.jsx b/resources/js/Components/Praktikans/Sections/TugasPendahuluan.jsx index 1891ed5..e3223bb 100644 --- a/resources/js/Components/Praktikans/Sections/TugasPendahuluan.jsx +++ b/resources/js/Components/Praktikans/Sections/TugasPendahuluan.jsx @@ -1,6 +1,9 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { Image } from "@imagekit/react"; + import QuestionCommentInput from "./QuestionCommentInput"; import MarkdownRenderer from "../../MarkdownRenderer"; +import { useImageKitUpload } from "@/hooks/useImageKitUpload"; export default function TugasPendahuluan({ isLoading = false, @@ -16,26 +19,196 @@ export default function TugasPendahuluan({ showSubmitButton = false, submitLabel = "Simpan Jawaban", }) { + const [uploadingIndexes, setUploadingIndexes] = + useState({}); + + const [uploadProgress, setUploadProgress] = + useState({}); + + const [previews, setPreviews] = + useState({}); + + const { upload } = useImageKitUpload(); + useEffect(() => { - setQuestionsCount(questions.length); + setQuestionsCount( + Array.isArray(questions) + ? questions.length + : 0 + ); }, [questions, setQuestionsCount]); const handleInputChange = (index, value) => { const updated = [...answers]; + updated[index] = value; + + setAnswers(updated); + }; + + const triggerFileInput = (index) => { + const input = document.getElementById( + `tp-file-upload-${index}` + ); + + if (input) { + input.click(); + } + }; + + const handleFileUpload = async (index, file) => { + if (!file) { + return; + } + + // Same 10MB limit as Journal/FITB + if (file.size > 10485760) { + alert("File size must be less than 10MB"); + return; + } + + // Images only + if (!file.type.startsWith("image/")) { + alert("Please upload an image file"); + return; + } + + setUploadingIndexes((previous) => ({ + ...previous, + [index]: true, + })); + + setUploadProgress((previous) => ({ + ...previous, + [index]: 0, + })); + + try { + /* + * Local preview while upload is happening. + */ + const previewUrl = + URL.createObjectURL(file); + + setPreviews((previous) => ({ + ...previous, + [index]: previewUrl, + })); + + /* + * Same ImageKit hook used by Journal/FITB. + * + * Only change is the folder: + * daskom/jawaban-tp + */ + const uploadResult = await upload( + file, + "daskom/jawaban-tp", + null, + true + ); + + /* + * Use the exact same answer format + * as Journal/FITB. + */ + const updated = [...answers]; + + updated[index] = { + type: "file", + url: uploadResult.url, + fileId: uploadResult.fileId, + filePath: uploadResult.filePath, + }; + + setAnswers(updated); + } catch (error) { + console.error( + "TP upload error:", + error + ); + + alert( + "Failed to upload file. Please try again." + ); + + setPreviews((previous) => { + const next = { + ...previous, + }; + + if (next[index]) { + URL.revokeObjectURL( + next[index] + ); + + delete next[index]; + } + + return next; + }); + } finally { + setUploadingIndexes((previous) => { + const next = { + ...previous, + }; + + delete next[index]; + + return next; + }); + + setUploadProgress((previous) => { + const next = { + ...previous, + }; + + delete next[index]; + + return next; + }); + } + }; + + const handleDeleteFile = (index) => { + const updated = [...answers]; + + updated[index] = ""; + setAnswers(updated); + + setPreviews((previous) => { + const next = { + ...previous, + }; + + if (next[index]) { + URL.revokeObjectURL( + next[index] + ); + + delete next[index]; + } + + return next; + }); }; const handleSubmit = () => { if (onSubmitTask) { - onSubmitTask("TugasPendahuluan", answers); + onSubmitTask( + "TugasPendahuluan", + answers + ); } }; if (isLoading) { return (
-

Memuat soal tugas pendahuluan...

+

+ Memuat soal tugas pendahuluan... +

); } @@ -43,15 +216,23 @@ export default function TugasPendahuluan({ if (errorMessage) { return (
-

{errorMessage}

+

+ {errorMessage} +

); } - if (!Array.isArray(questions) || questions.length === 0) { + if ( + !Array.isArray(questions) || + questions.length === 0 + ) { return (
-

Belum ada soal tugas pendahuluan untuk modul ini.

+

+ Belum ada soal tugas pendahuluan + untuk modul ini. +

); } @@ -60,31 +241,277 @@ export default function TugasPendahuluan({ <>
- {questions.map((question, index) => ( -
-
- - {index + 1} - -
- + {questions.map( + (question, index) => { + const isFileUploadEnabled = + question.enable_file_upload || + false; + + const currentAnswer = + answers[index]; + + const isFileAnswer = + typeof currentAnswer === + "object" && + currentAnswer?.type === + "file"; + + return ( +
+
+ + {index + 1} + + +
+ +
+
+ + + + {isFileUploadEnabled ? ( +
+ {!isFileAnswer && + !previews[ + index + ] ? ( +
+ + +
+ +
+
+ ) : ( +
+ {uploadingIndexes[ + index + ] ? ( +
+
+ +

+ Uploading... +

+
+ ) : ( + <> + {isFileAnswer && + currentAnswer?.url ? ( + Uploaded TP answer + ) : previews[ + index + ] ? ( + TP answer preview + ) : null} + + + +
+ +
+ + )} +
+ )} + + { + const file = + event + .target + .files?.[0]; + + if ( + file + ) { + handleFileUpload( + index, + file + ); + } + + event.target.value = + ""; + }} + disabled={ + uploadingIndexes[ + index + ] + } + /> +
+ ) : ( + -
- ))} + ); + } + )}
@@ -93,9 +520,18 @@ export default function TugasPendahuluan({
)} diff --git a/resources/js/Pages/Praktikan/TugasPendahuluanPage.jsx b/resources/js/Pages/Praktikan/TugasPendahuluanPage.jsx index 3ed2510..6589331 100644 --- a/resources/js/Pages/Praktikan/TugasPendahuluanPage.jsx +++ b/resources/js/Pages/Praktikan/TugasPendahuluanPage.jsx @@ -21,8 +21,88 @@ const normaliseQuestions = (items) => id: item.id, text: item.soal ?? item.pertanyaan ?? "", questionType: "essay", + enable_file_upload: Boolean(item.enable_file_upload), })); +const isLikelyJsonObjectString = (value) => { + if (typeof value !== "string") { + return false; + } + + const trimmed = value.trim(); + + if (trimmed === "") { + return false; + } + + return ( + (trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]")) + ); +}; + +const hydrateAnswer = (storedAnswer) => { + if (storedAnswer === null || storedAnswer === undefined) { + return ""; + } + + if (typeof storedAnswer === "object") { + return storedAnswer; + } + + if (typeof storedAnswer !== "string") { + return String(storedAnswer ?? ""); + } + + const trimmed = storedAnswer.trim(); + + if (trimmed === "") { + return ""; + } + + if (isLikelyJsonObjectString(trimmed)) { + try { + const parsed = JSON.parse(trimmed); + + if (parsed && typeof parsed === "object") { + return parsed; + } + } catch (error) { + console.warn( + "[TP] Failed to parse stored answer", + error + ); + } + } + + return storedAnswer; +}; + +const serialiseAnswerForSubmission = (answerValue) => { + if (answerValue === null || answerValue === undefined) { + return ""; + } + + if (typeof answerValue === "object") { + try { + return JSON.stringify(answerValue); + } catch (error) { + console.warn( + "[TP] Failed to stringify answer", + error + ); + + return ""; + } + } + + if (typeof answerValue === "string") { + return answerValue; + } + + return String(answerValue ?? ""); +}; + export default function TugasPendahuluanPage() { const { auth } = usePage().props ?? {}; const praktikan = auth?.praktikan ?? null; @@ -153,7 +233,10 @@ export default function TugasPendahuluanPage() { storedAnswers.map((item) => [Number(item?.soal_id), item?.jawaban ?? ""]) ); - const initialAnswers = data.map((question) => answerMap.get(Number(question.id)) ?? ""); + const initialAnswers = data.map((question) => { + const storedAnswer = answerMap.get(Number(question.id)); + return hydrateAnswer(storedAnswer); + }); setAnswers(initialAnswers); }, [answersQuery.data, questionsQuery.data]); @@ -186,7 +269,7 @@ export default function TugasPendahuluanPage() { soal_id: Number(question.id), praktikan_id: praktikanId, modul_id: Number(selectedModul), - jawaban: String(currentAnswers[index] ?? "-").trim() || "-", + jawaban: serialiseAnswerForSubmission(currentAnswers[index]) || "-", })); submitMutation.mutate(payload); From 942eec7c23d2342ad1b4dac6bb67131bdd08f380 Mon Sep 17 00:00:00 2001 From: Raditya <135982042+petorikooru@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:56:23 +0700 Subject: [PATCH 04/10] Accidentally use dot instead of comma --- app/Http/Controllers/API/JawabanTAController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/API/JawabanTAController.php b/app/Http/Controllers/API/JawabanTAController.php index 5917559..7cc1072 100644 --- a/app/Http/Controllers/API/JawabanTAController.php +++ b/app/Http/Controllers/API/JawabanTAController.php @@ -69,7 +69,7 @@ public function store(Request $request): JsonResponse 'praktikan_id' => ['required', 'integer'], 'modul_id' => ['required', 'integer'], 'answers' => ['present', 'array'], - 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tas,id'. 'distinct'], + 'answers.*.soal_id' => ['required', 'integer', 'exists:soal_tas,id', 'distinct'], 'answers.*.opsi_id' => ['required', 'integer', 'exists:soal_opsis,id'], ]); From 017768e98480d257fa88902827384e1cf39d372d Mon Sep 17 00:00:00 2001 From: petorikooru Date: Sun, 9 Aug 2026 11:45:01 +0700 Subject: [PATCH 05/10] Added some features on Markdown Renderer --- resources/js/Components/MarkdownRenderer.jsx | 662 +++++++++++++++++-- 1 file changed, 598 insertions(+), 64 deletions(-) diff --git a/resources/js/Components/MarkdownRenderer.jsx b/resources/js/Components/MarkdownRenderer.jsx index cdcac9c..732f527 100644 --- a/resources/js/Components/MarkdownRenderer.jsx +++ b/resources/js/Components/MarkdownRenderer.jsx @@ -1,86 +1,620 @@ -import React, { useState, useEffect, useRef } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import remarkBreaks from 'remark-breaks'; -import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; -import c from 'react-syntax-highlighter/dist/esm/languages/prism/c'; -import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import mermaid from 'mermaid'; - -SyntaxHighlighter.registerLanguage('c', c); - -const Mermaid = ({ chart }) => { - const ref = useRef(null); - useEffect(() => { - mermaid.initialize({ startOnLoad: false, theme: 'dark' }); - - const renderChart = async () => { - if (ref.current) { - try { - const id = `mermaid-${Math.random().toString(36).substr(2, 9)}`; - const { svg } = await mermaid.render(id, chart); - if (ref.current) { - ref.current.innerHTML = svg; - } - } catch (error) { - console.error("Mermaid parsing error", error); - if (ref.current) { - ref.current.innerHTML = `
Failed to render Mermaid diagram
`; +import React, { useEffect, useRef, useState } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import remarkBreaks from "remark-breaks"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; +import "katex/dist/katex.min.css"; + +import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; +import c from "react-syntax-highlighter/dist/esm/languages/prism/c"; +import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; +import mermaid from "mermaid"; + +SyntaxHighlighter.registerLanguage("c", c); + +const parseMediaSize = (alt = "") => { + if (typeof alt !== "string") return { altText: "", width: null }; + + const match = alt.match( + /^(.*?)\s*\|\s*(\d+(?:\.\d+)?)\s*(px|%)?\s*$/i, + ); + + if (!match) return { altText: alt, width: null }; + + const altText = match[1].trim(); + const value = Number(match[2]); + const unit = (match[3] ?? "px").toLowerCase(); + + if (!Number.isFinite(value) || value <= 0) + return { altText: alt, width: null }; + + return { + altText, + width: + unit === "%" + ? `${Math.min(value, 100)}%` + : `${Math.min(value, 2000)}px`, + }; +}; + + +/* ============================================================ + * GitHub Alerts + * ============================================================ */ + +const ALERT_PATTERN = + /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\r?\n)?/i; + +const remarkGithubAlerts = () => (tree) => { + const walk = (node) => { + if (!Array.isArray(node?.children)) return; + + node.children.forEach((child) => { + if ( + child.type === "blockquote" && + Array.isArray(child.children) && + child.children.length + ) { + const paragraph = child.children[0]; + const first = paragraph?.children?.[0]; + + if ( + paragraph?.type === "paragraph" && + first?.type === "text" + ) { + const match = first.value.match(ALERT_PATTERN); + + if (match) { + const type = match[1].toLowerCase(); + + first.value = first.value.replace( + ALERT_PATTERN, + "", + ); + + if (!first.value) paragraph.children.shift(); + + while (paragraph.children[0]?.type === "break") + paragraph.children.shift(); + + if (!paragraph.children.length) + child.children.shift(); + + const old = + child.data?.hProperties?.className; + const classes = Array.isArray(old) + ? old + : old + ? [old] + : []; + + child.data = { + ...(child.data ?? {}), + hProperties: { + ...(child.data?.hProperties ?? {}), + className: [ + ...classes, + "markdown-alert", + `markdown-alert-${type}`, + ], + }, + }; } } } + + walk(child); + }); + }; + + walk(tree); +}; + + +/* ============================================================ + * Mermaid + * ============================================================ */ + +function Mermaid({ chart }) { + const ref = useRef(null); + + useEffect(() => { + mermaid.initialize({ + startOnLoad: false, + theme: "dark", + }); + + let cancelled = false; + + const render = async () => { + try { + const id = `mermaid-${Math.random() + .toString(36) + .slice(2, 11)}`; + + const { svg } = await mermaid.render(id, chart); + + if (!cancelled && ref.current) + ref.current.innerHTML = svg; + } catch (error) { + console.error("Mermaid parsing error", error); + + if (!cancelled && ref.current) + ref.current.innerHTML = + '
Failed to render Mermaid diagram
'; + } + }; + + render(); + + return () => { + cancelled = true; }; - renderChart(); }, [chart]); - return
; + + return ( +
+ ); +} + + +/* ============================================================ + * Copy + * ============================================================ */ + +function CopyButton({ value }) { + const [copied, setCopied] = useState(false); + + const copy = async () => { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(value); + } else { + const textarea = document.createElement("textarea"); + textarea.value = value; + textarea.style.cssText = + "position:fixed;opacity:0;pointer-events:none"; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand("copy"); + textarea.remove(); + } + + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch (error) { + console.error("Failed to copy code", error); + } + }; + + return ( + + ); +} + + +/* ============================================================ + * Code Block + * ============================================================ */ + +function FencedCodeBlock({ children }) { + const child = React.Children.only(children); + const className = child?.props?.className ?? ""; + const language = + /language-([\w-]+)/.exec(className)?.[1] ?? null; + const code = String( + child?.props?.children ?? "", + ).replace(/\n$/, ""); + + if (language === "mermaid") + return ; + + return ( +
+
+ + {language ?? "code"} + + + +
+ +
+ + {code} + +
+
+ ); +} + + +/* ============================================================ + * Media + * + * IMPORTANT: + * Use , NOT
. + * + * Markdown images normally live inside

, therefore: + * + *

-> valid + *

-> invalid hydration nesting + * ============================================================ */ + +const VIDEO_EXTENSIONS = /\.(mp4|webm|ogg|m4v|mov)$/i; + +const isVideoUrl = (src) => + typeof src === "string" && + src.trim() !== "" && + VIDEO_EXTENSIONS.test(src.split(/[?#]/)[0]); + +function MarkdownMedia({ src, alt, title }) { + if (!src) return null; + + const { altText, width } = parseMediaSize(alt); + const style = { + width: width ?? "100%", + maxWidth: "100%", + }; + + if (isVideoUrl(src)) { + return ( + + + + ); + } + + return ( + + + {altText} + + + ); +} + + +/* ============================================================ + * Alerts + * ============================================================ */ + +const ALERT_STYLES = { + note: { + label: "Note", + container: "border-blue-500/50 bg-blue-500/10", + title: "text-blue-400", + }, + tip: { + label: "Tip", + container: "border-emerald-500/50 bg-emerald-500/10", + title: "text-emerald-400", + }, + important: { + label: "Important", + container: "border-violet-500/50 bg-violet-500/10", + title: "text-violet-400", + }, + warning: { + label: "Warning", + container: "border-amber-500/50 bg-amber-500/10", + title: "text-amber-400", + }, + caution: { + label: "Caution", + container: "border-red-500/50 bg-red-500/10", + title: "text-red-400", + }, }; +const getAlertType = (className = "") => + /markdown-alert-(note|tip|important|warning|caution)/.exec( + className, + )?.[1] ?? null; -export default function MarkdownRenderer({ content, className = '' }) { - const processedContent = content; + +/* ============================================================ + * Renderer + * ============================================================ */ + +export default function MarkdownRenderer({ + content, + className = "", +}) { + const value = + typeof content === "string" + ? content + : String(content ?? ""); return ( -
+
; - } - - return !inline ? ( -
- ( + + {children} + + ), + + code: ({ + className = "", + children, + ...props + }) => ( + + {children} + + ), + + h1: ({ children }) => ( +

+ {children} +

+ ), + + h2: ({ children }) => ( +

+ {children} +

+ ), + + h3: ({ children }) => ( +

+ {children} +

+ ), + + h4: ({ children }) => ( +

+ {children} +

+ ), + + h5: ({ children }) => ( +
+ {children} +
+ ), + + h6: ({ children }) => ( +
+ {children} +
+ ), + + blockquote: ({ + className = "", + children, + ...props + }) => { + const type = + getAlertType(className); + + if (!type) + return ( +
- {codeString} - + {children} +
+ ); + + const alert = ALERT_STYLES[type]; + + return ( +
+
+ + + + + + + {alert.label} +
+ +
+ {children} +
- ) : ( - - {children} - ); }, - p: ({ children }) =>
{children}
, - ul: ({ children }) =>
    {children}
, - ol: ({ children }) =>
    {children}
, - table: ({ children }) =>
{children}
, - thead: ({ children }) => {children}, - tbody: ({ children }) => {children}, - th: ({ children }) => {children}, - td: ({ children }) => {children}, + + img: ({ src, alt, title }) => ( + + ), + + p: ({ children }) => ( +

+ {children} +

+ ), + + ul: ({ children }) => ( +
    + {children} +
+ ), + + ol: ({ children }) => ( +
    + {children} +
+ ), + + table: ({ children }) => ( +
+ + {children} +
+
+ ), + + thead: ({ children }) => ( + + {children} + + ), + + tbody: ({ children }) => ( + + {children} + + ), + + th: ({ children }) => ( + + {children} + + ), + + td: ({ children }) => ( + + {children} + + ), + + hr: () => ( +
+ ), }} > - {processedContent} + {value}
); From b5907f2fee91601bb6ba050855e366b510b6a63c Mon Sep 17 00:00:00 2001 From: petorikooru Date: Sun, 9 Aug 2026 11:47:33 +0700 Subject: [PATCH 06/10] Improved the UX of the Assistant Question Management --- package-lock.json | 340 ++- .../Assistants/Forms/FormSoalInput.jsx | 578 +++- .../Assistants/Modals/ModalBatchEditSoal.jsx | 913 ++++-- .../Modals/ModalBatchEditSoalPG.jsx | 857 ++++++ .../Assistants/Modals/ModalInputNilai.jsx | 1502 ++++----- .../Assistants/Modals/PairNavigator.jsx | 107 + .../Assistants/Soal/SoalInputEssay.jsx | 1007 ++++--- .../Assistants/Soal/SoalInputPG.jsx | 2679 +++++++++++++---- .../Assistants/Soal/SoalMarkdownEditor.jsx | 137 + .../Assistants/Soal/SoalPGEditor.jsx | 318 ++ 10 files changed, 6073 insertions(+), 2365 deletions(-) create mode 100644 resources/js/Components/Assistants/Modals/ModalBatchEditSoalPG.jsx create mode 100644 resources/js/Components/Assistants/Modals/PairNavigator.jsx create mode 100644 resources/js/Components/Assistants/Soal/SoalMarkdownEditor.jsx create mode 100644 resources/js/Components/Assistants/Soal/SoalPGEditor.jsx diff --git a/package-lock.json b/package-lock.json index 2f77143..af20264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "@inertiajs/inertia": "^0.11.1", "@tanstack/react-query": "^5.90.5", "@types/react": "^19.1.9", - "caniuse-lite": "^1.0.30001734", + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", "js-cookie": "^3.0.5", + "katex": "^0.18.2", "laravel-echo": "^2.2.4", "lodash": "^4.17.21", "mermaid": "^11.12.3", @@ -19,8 +21,10 @@ "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.0", "recharts": "^3.6.0", + "rehype-katex": "^7.0.1", "remark-breaks": "^4.0.0", - "remark-gfm": "^4.0.1" + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0" }, "devDependencies": { "@headlessui/react": "^2.0.0", @@ -1978,6 +1982,11 @@ "@types/unist": "*" } }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==" + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -2243,6 +2252,17 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", + "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2352,9 +2372,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "funding": [ { "type": "opencollective", @@ -2368,8 +2388,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/ccount": { "version": "2.0.1", @@ -3303,6 +3322,17 @@ "node": ">=10.0.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3768,6 +3798,83 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -3808,6 +3915,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -4104,14 +4226,13 @@ } }, "node_modules/katex": { - "version": "0.16.33", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.33.tgz", - "integrity": "sha512-q3N5u+1sY9Bu7T4nlXoiRBXWfwSefNGoKeOwekV+gw0cAXQlz2Ww6BLcmBxVDeXBMUDQv6fK5bcNaJLxob3ZQA==", + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.18.2.tgz", + "integrity": "sha512-3snve4y0SXTMequLim1FMiPvLGElySam0blN5xQZBqEY3lOJz1TnuaaiugnBZBqHzlSAGmFTYL7MCQkORuLKCA==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], - "license": "MIT", "dependencies": { "commander": "^8.3.0" }, @@ -4427,6 +4548,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -4608,6 +4747,29 @@ "uuid": "^11.1.0" } }, + "node_modules/mermaid/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/mermaid/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -4798,6 +4960,47 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/micromark-extension-math/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", @@ -5403,6 +5606,17 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -5999,6 +6213,47 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/rehype-katex/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/remark-breaks": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", @@ -6032,6 +6287,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -6776,6 +7046,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -6802,6 +7085,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -6918,6 +7214,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", @@ -7099,6 +7408,15 @@ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/resources/js/Components/Assistants/Forms/FormSoalInput.jsx b/resources/js/Components/Assistants/Forms/FormSoalInput.jsx index bf450db..1a63474 100644 --- a/resources/js/Components/Assistants/Forms/FormSoalInput.jsx +++ b/resources/js/Components/Assistants/Forms/FormSoalInput.jsx @@ -1,70 +1,327 @@ -import { Suspense, lazy, useEffect, useMemo, useState } from 'react'; -const SoalInputPG = lazy(() => import('../Soal/SoalInputPG')); -const SoalInputEssay = lazy(() => import('../Soal/SoalInputEssay')); -const ModalSaveSoal = lazy(() => import('../Modals/ModalSaveSoal')); +import { + Suspense, + lazy, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; + import { useModulesQuery } from "@/hooks/useModulesQuery"; import toast from "react-hot-toast"; +const SoalInputPG = lazy(() => import("../Soal/SoalInputPG")); +const SoalInputEssay = lazy(() => import("../Soal/SoalInputEssay")); + +const CATEGORIES = [ + ["tp", "TP", "Tes Pendahuluan"], + ["ta", "TA", "Tes Awal"], + ["fitb", "FITB", "Fill in the blank"], + ["jurnal", "Jurnal", "Jurnal"], + ["tm", "Mandiri", "Mandiri"], + ["tk", "TK", "Tes Keterampilan"], +]; + +const getScrollParent = (node) => { + for (let el = node?.parentElement; el && el !== document.body; el = el.parentElement) { + if (/(auto|scroll)/.test(getComputedStyle(el).overflowY)) return el; + } + + return window; +}; + + +function SoalNavigator({ count, active, onChange }) { + const ref = useRef(null); + const [capacity, setCapacity] = useState(8); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const update = () => + setCapacity(Math.max(1, Math.floor(el.clientWidth / 38))); + + update(); + + const observer = new ResizeObserver(update); + observer.observe(el); + + return () => observer.disconnect(); + }, []); + + if (!count) return null; + + const visible = count <= capacity ? count : Math.max(1, capacity - 2); + const start = Math.max( + 0, + Math.min(active - Math.floor(visible / 2), count - visible), + ); + const end = Math.min(count, start + visible); + + const go = (index) => { + if (index >= 0 && index < count) onChange(index); + }; + + return ( +
+ + +
+ {start > 0 && ( + + )} + + {Array.from( + { length: end - start }, + (_, i) => start + i, + ).map((index) => ( + + ))} + + {end < count && ( + + )} +
+ + + {active + 1} / {count} + + + +
+ ); +} + + export default function FormSoalInput({ isEditable = true }) { - const [isModalSaveOpen, setIsModalSaveOpen] = useState(false); + const rootRef = useRef(null); + const restoreScrollRef = useRef(null); + const [kategoriSoal, setKategoriSoal] = useState(""); const [selectedModul, setSelectedModul] = useState(""); + const [activeSoal, setActiveSoal] = useState(0); + const { data: moduls = [], isLoading: modulesLoading, isError: modulesError, error: modulesQueryError, + refetch: refetchModules, } = useModulesQuery(); - const handleModulChange = (e) => { - const value = e.target.value; - setSelectedModul(value); + + /* + * Restore scroll immediately after a card-triggered render. + * + * This also works when the container did not overflow before + * the click but becomes scrollable after mounting the new content. + */ + useLayoutEffect(() => { + const saved = restoreScrollRef.current; + if (!saved) return; + + restoreScrollRef.current = null; + + if (saved.target === window) { + window.scrollTo(saved.left, saved.top); + } else { + saved.target.scrollLeft = saved.left; + saved.target.scrollTop = saved.top; + } + }); + + + const preserveScroll = (callback) => { + const target = getScrollParent(rootRef.current); + + restoreScrollRef.current = + target === window + ? { + target, + top: window.scrollY, + left: window.scrollX, + } + : { + target, + top: target.scrollTop, + left: target.scrollLeft, + }; + + callback(); + }; + + + const selectedModuleData = useMemo( + () => + selectedModul + ? moduls.find( + (m) => String(m.idM) === String(selectedModul), + ) ?? null + : null, + [moduls, selectedModul], + ); + + + const counts = useMemo( + () => + selectedModuleData + ? { + tp: selectedModuleData.soal_tp_count ?? 0, + ta: selectedModuleData.soal_ta_count ?? 0, + fitb: selectedModuleData.soal_fitb_count ?? 0, + jurnal: selectedModuleData.soal_jurnal_count ?? 0, + tm: selectedModuleData.soal_tm_count ?? 0, + tk: selectedModuleData.soal_tk_count ?? 0, + } + : null, + [selectedModuleData], + ); + + const currentCount = counts?.[kategoriSoal] ?? 0; + + + /* + * Dropdowns and cards ultimately use the same state changes. + * No automatic scrolling occurs here. + */ + const changeCategory = (value) => { + setKategoriSoal(value); + setActiveSoal(0); }; - const handleCloseModalSave = () => { - setIsModalSaveOpen(false); - setKategoriSoal(""); - setSelectedModul(""); + const changeModule = (value) => { + setSelectedModul(value); + setActiveSoal(0); }; - const handleValidationError = ({ message, includeModuleNotice = false } = {}) => { - const baseMessage = message ?? "Soal belum ditambahkan!!"; - const notice = includeModuleNotice ? " Pastikan memilih modul terlebih dahulu." : ""; - toast.error(`${baseMessage}${notice}`.trim()); + + /* + * Only the question navigator intentionally scrolls. + */ + const goToSoal = (index) => { + if (index < 0 || index >= currentCount) return; + + setActiveSoal(index); + + requestAnimationFrame(() => { + const target = document.getElementById( + `soal-${kategoriSoal}-${index}`, + ); + + if (!target) return; + + const parent = getScrollParent(target); + + if (parent === window) { + window.scrollTo({ + top: + window.scrollY + + target.getBoundingClientRect().top - + 100, + behavior: "smooth", + }); + + return; + } + + parent.scrollTo({ + top: + parent.scrollTop + + target.getBoundingClientRect().top - + parent.getBoundingClientRect().top - + 100, + behavior: "smooth", + }); + }); }; + + useEffect(() => { + setActiveSoal(0); + }, [kategoriSoal, selectedModul]); + + + const handleValidationError = ({ + message, + includeModuleNotice = false, + } = {}) => + toast.error( + `${message ?? "Soal belum ditambahkan!!"}${ + includeModuleNotice + ? " Pastikan memilih modul terlebih dahulu." + : "" + }`.trim(), + ); + + const handleSuccessNotification = () => { toast.success("Soal berhasil ditambahkan!!"); + refetchModules(); }; - const selectedModuleData = useMemo(() => { - if (!selectedModul) { - return null; - } - return ( - moduls.find((module) => String(module.idM) === String(selectedModul)) ?? null - ); - }, [moduls, selectedModul]); + const renderSoal = () => { + if (!kategoriSoal || !selectedModul) return null; - const soalCountMeta = useMemo(() => { - if (!selectedModuleData) { - return null; + const props = { + kategoriSoal, + modul: selectedModul, + modules: moduls, + onModalSuccess: handleSuccessNotification, + onModalValidation: handleValidationError, + onChangeModul: changeModule, + isEditable, + }; + + if (["tp", "fitb", "jurnal", "tm"].includes(kategoriSoal)) { + return ; } - return { - tp: selectedModuleData.soal_tp_count ?? 0, - ta: selectedModuleData.soal_ta_count ?? 0, - fitb: selectedModuleData.soal_fitb_count ?? 0, - jurnal: selectedModuleData.soal_jurnal_count ?? 0, - tm: selectedModuleData.soal_tm_count ?? 0, - tk: selectedModuleData.soal_tk_count ?? 0, - }; - }, [selectedModuleData]); + if (["ta", "tk"].includes(kategoriSoal)) { + return ; + } + + return null; + }; + return ( -
- {/* Pilih kategori soal dan modul */} +
+ {/* Category + Module */}
+
+
+
- {selectedModuleData && soalCountMeta && ( -
- {[ - { key: 'tp', label: 'TP' }, - { key: 'ta', label: 'TA' }, - { key: 'fitb', label: 'FITB' }, - { key: 'jurnal', label: 'Jurnal' }, - { key: 'tm', label: 'Mandiri' }, - { key: 'tk', label: 'TK' }, - ].map((item) => ( -
-
- {item.label} -
-
- {soalCountMeta[item.key] ?? 0} -
-
- ))} -
+ {/* Module cards */} + {!selectedModul && ( +
+
+

+ Pilih Modul +

+ +

+ Pilih modul Indonesia atau English untuk mulai + mengelola soal. +

+
+ + {modulesLoading ? ( +

+ Memuat modul... +

+ ) : modulesError ? ( +

+ {modulesQueryError?.message ?? + "Gagal memuat modul"} +

+ ) : ( +
+ {moduls.map((m) => { + const english = + Number(m?.isEnglish ?? 0) === 1; + + return ( + + ); + })} +
+ )} +
)} - {/* Input soal berdasarkan kategori soal */} - Memuat soal...
}> - {(() => { - if (!kategoriSoal) return null; - const essayTypes = ["tp", "fitb", "jurnal", "tm"]; - const pgTypes = ["ta", "tk"]; - if (essayTypes.includes(kategoriSoal) && selectedModul) { - return ( - - ); - } - if (pgTypes.includes(kategoriSoal) && selectedModul) { + + {/* Category cards */} + {selectedModuleData && counts && ( +
+ {CATEGORIES.map(([key, label]) => { + const selected = kategoriSoal === key; + return ( - + ); - } - return null; - })()} - + })} +
+ )} - {isModalSaveOpen && ( - - - + + {/* Sticky navigator */} + {selectedModul && kategoriSoal && currentCount > 0 && ( +
+ +
)} + + + {/* Questions */} +
+ + Memuat soal... +
+ } + > + {renderSoal()} + +
); } diff --git a/resources/js/Components/Assistants/Modals/ModalBatchEditSoal.jsx b/resources/js/Components/Assistants/Modals/ModalBatchEditSoal.jsx index a3889b4..a16fe26 100644 --- a/resources/js/Components/Assistants/Modals/ModalBatchEditSoal.jsx +++ b/resources/js/Components/Assistants/Modals/ModalBatchEditSoal.jsx @@ -1,351 +1,692 @@ import { useEffect, useMemo, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import remarkBreaks from "remark-breaks"; -import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; -import c from 'react-syntax-highlighter/dist/esm/languages/prism/c'; -import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; + import { ModalOverlay } from "@/Components/Common/ModalPortal"; import ModalCloseButton from "@/Components/Common/ModalCloseButton"; +import DepthToggleButton from "@/Components/Common/DepthToggleButton"; + +import MarkdownRenderer from "../../MarkdownRenderer"; +import PairNavigator from "./PairNavigator"; + + +const getModuleId = (item) => { + const value = + item?.idM ?? + item?.id ?? + item?.value ?? + item?.uuid ?? + item?.ID; + + return value == null ? "" : String(value); +}; + + +const getModuleLabel = (item) => + item?.judul ?? + item?.nama ?? + item?.nama_modul ?? + item?.label ?? + `Modul ${getModuleId(item)}`; + + +const extractQuestions = (dataset) => { + if (Array.isArray(dataset)) return dataset; + + return [ + dataset?.soal, + dataset?.questions, + dataset?.items, + dataset?.data, + dataset?.data?.soal, + dataset?.data?.questions, + dataset?.data?.items, + dataset?.data?.data, + ].find(Array.isArray) ?? []; +}; + + +const emptyDraft = () => ({ + id: null, + soal: "", + enable_file_upload: false, + originalSoal: "", + originalEnableFileUpload: false, + _deleted: false, +}); + + +const createDrafts = (dataset) => + extractQuestions(dataset).map((item) => { + const soal = item?.soal ?? item?.pertanyaan ?? ""; + const upload = Boolean(item?.enable_file_upload); + + return { + id: item?.id ?? item?.soal_id ?? null, + soal, + enable_file_upload: upload, + originalSoal: soal, + originalEnableFileUpload: upload, + _deleted: false, + }; + }); + + +const activeCount = (items) => + items.filter( + (item) => + !item?._deleted && + String(item?.soal ?? "").trim(), + ).length; + -SyntaxHighlighter.registerLanguage('c', c); +function QuestionCard({ + item, + index, + side, + supportsFileUpload, + isSaving, + onChange, + onCreate, + onCopy, +}) { + if (!item) { + return ( +
+

+ Soal {index + 1} tidak ada +

+ +

+ Modul {side} memiliki soal lebih sedikit. +

+ + +
+ ); + } -const tabs = [ - { key: "text", label: "Text" }, - { key: "preview", label: "Preview" }, - { key: "split", label: "Side by Side" }, -]; + if (item._deleted) { + return ( +
+ + Soal {index + 1} akan dihapus + + + +
+ ); + } + + return ( +
+
+
+ + Soal {index + 1} + + + + {side} + + + {!item.id && ( + + BARU + + )} +
+ +
+ {onCopy && ( + + )} + + +
+
+ +
+
+ + +