user($request); $this->abortIfSuper($user); $rows = DB::table('tbmhs as tm') ->leftJoin('tbprodi as tp', 'tm.idProdi', '=', 'tp.idProdi') ->select(['tm.idmhs', 'tm.nim', 'tm.nmLengkap', 'tp.nmProdi', 'tm.thnmasuk', 'tm.status']) ->where('tm.idProdi', $user['prodi']) ->orderByDesc('tm.idmhs') ->paginate(20); return $this->table($request, 'Data Mahasiswa', 'Data mahasiswa pada program studi admin aktif.', ['NIM', 'Nama', 'Prodi', 'Angkatan', 'Status'], $rows, fn ($row) => [$row->nim, $row->nmLengkap, $row->nmProdi, $row->thnmasuk ?: '-', $row->status], 'admin.data.mahasiswa', [ 'actions' => [ ['label' => 'Tambah Mahasiswa', 'href' => route('admin.data.mahasiswa.create', [], false), 'variant' => 'dark'], ['label' => 'Impor CSV Mahasiswa', 'href' => route('admin.data.mahasiswa.import', [], false), 'variant' => 'dark'], ], 'rowActions' => fn ($row) => [ ['label' => 'Edit', 'href' => route('admin.data.mahasiswa.edit', ['id' => $row->idmhs], false)], ['label' => 'Hapus', 'href' => route('admin.data.mahasiswa.destroy', ['id' => $row->idmhs], false), 'method' => 'DELETE', 'confirm' => 'Hapus mahasiswa ini?'], ], ]); } public function createMahasiswa(Request $request): View { return $this->resourceForm($request, 'Tambah Mahasiswa', 'Tambah data mahasiswa pada program studi admin.', route('admin.data.mahasiswa.store'), route('admin.data.mahasiswa', [], false), $this->mahasiswaFields()); } public function storeMahasiswa(Request $request): RedirectResponse { $user = $this->user($request); $data = $request->validate([ 'nim' => ['required', 'string', 'max:30'], 'nmLengkap' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'email', 'max:255'], 'thnmasuk' => ['nullable', 'string', 'max:10'], 'password' => ['required', 'string', 'min:4'], 'status' => ['required', 'in:A,N,P'], 'bolehUploadDraft' => ['required', 'in:0,1'], 'noHP' => ['nullable', 'string', 'max:30'], 'noHPOrtu' => ['nullable', 'string', 'max:30'], ]); $data['idProdi'] = $user['prodi']; $data['password'] = md5($data['password']); DB::table('tbmhs')->insert($data); return redirect()->route('admin.data.mahasiswa')->with('success', 'Data mahasiswa berhasil ditambahkan.'); } public function editMahasiswa(Request $request, int $id): View { $user = $this->user($request); $row = DB::table('tbmhs')->where('idmhs', $id)->where('idProdi', $user['prodi'])->first(); abort_unless($row, 404); return $this->resourceForm($request, 'Edit Mahasiswa', 'Perbarui data mahasiswa.', route('admin.data.mahasiswa.update', ['id' => $id]), route('admin.data.mahasiswa', [], false), $this->mahasiswaFields($row, true), 'PUT'); } public function updateMahasiswa(Request $request, int $id): RedirectResponse { $user = $this->user($request); abort_unless(DB::table('tbmhs')->where('idmhs', $id)->where('idProdi', $user['prodi'])->exists(), 404); $data = $request->validate([ 'nim' => ['required', 'string', 'max:30'], 'nmLengkap' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'email', 'max:255'], 'thnmasuk' => ['nullable', 'string', 'max:10'], 'password' => ['nullable', 'string', 'min:4'], 'status' => ['required', 'in:A,N,P'], 'bolehUploadDraft' => ['required', 'in:0,1'], 'noHP' => ['nullable', 'string', 'max:30'], 'noHPOrtu' => ['nullable', 'string', 'max:30'], ]); if (($data['password'] ?? '') !== '') { $data['password'] = md5($data['password']); } else { unset($data['password']); } DB::table('tbmhs')->where('idmhs', $id)->update($data); return redirect()->route('admin.data.mahasiswa')->with('success', 'Data mahasiswa berhasil diperbarui.'); } public function destroyMahasiswa(Request $request, int $id): RedirectResponse { $user = $this->user($request); DB::table('tbmhs')->where('idmhs', $id)->where('idProdi', $user['prodi'])->delete(); return redirect()->route('admin.data.mahasiswa')->with('success', 'Data mahasiswa berhasil dihapus.'); } public function importMahasiswa(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); return view('admin.pages.mahasiswa-import', [ 'title' => 'Impor CSV Mahasiswa | SPOTA Rebuild', 'pageTitle' => 'Impor CSV Mahasiswa', 'pageDescription' => 'Upload CSV untuk menambah atau memperbarui data mahasiswa secara bulk berdasarkan NIM.', 'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'), 'sidebar' => AdminNavigation::build($user), 'user' => $user, 'requiredHeaders' => ['nim', 'nama', 'email', 'thnmasuk'], 'optionalHeaders' => ['password', 'status', 'noHP', 'noHPOrtu', 'bolehUploadDraft'], ]); } public function storeImportMahasiswa(Request $request): RedirectResponse { $user = $this->user($request); $this->abortIfSuper($user); $validated = $request->validate([ 'csv' => ['required', 'file', 'mimes:csv,txt', 'max:5120'], 'mode' => ['required', 'in:insert_only,upsert'], 'default_password' => ['nullable', 'string', 'min:4', 'max:50'], ]); $path = $request->file('csv')->getRealPath(); $handle = fopen($path, 'r'); if (! $handle) { return back()->with('error', 'File CSV tidak dapat dibaca.'); } $headers = fgetcsv($handle); if (! $headers) { fclose($handle); return back()->with('error', 'CSV kosong atau header tidak ditemukan.'); } $headers = array_map(fn ($header) => trim((string) $header), $headers); $normalized = array_map(fn ($header) => strtolower($header), $headers); $aliases = [ 'nama_lengkap' => 'nama', 'nmlengkap' => 'nama', 'tahunmasuk' => 'thnmasuk', 'tahun_masuk' => 'thnmasuk', 'nohp' => 'noHP', 'no_hp' => 'noHP', 'nohportu' => 'noHPOrtu', 'no_hp_ortu' => 'noHPOrtu', 'bolehuploaddraft' => 'bolehUploadDraft', 'boleh_upload_draft' => 'bolehUploadDraft', ]; $canonical = []; foreach ($normalized as $index => $header) { $canonical[$index] = $aliases[$header] ?? $headers[$index]; } $required = ['nim', 'nama', 'email', 'thnmasuk']; $missing = array_diff($required, $canonical); if (! empty($missing)) { fclose($handle); return back()->with('error', 'Header CSV wajib belum lengkap: '.implode(', ', $missing).'.'); } $inserted = 0; $updated = 0; $skipped = 0; $errors = []; $line = 1; $defaultPassword = $validated['default_password'] ?: '123456'; DB::beginTransaction(); try { while (($row = fgetcsv($handle)) !== false) { $line++; $data = []; foreach ($canonical as $index => $header) { $data[$header] = trim((string) ($row[$index] ?? '')); } if (count(array_filter($data, fn ($value) => $value !== '')) === 0) { continue; } $nim = $data['nim'] ?? ''; $nama = $data['nama'] ?? ''; $email = $data['email'] ?? ''; $thnmasuk = $data['thnmasuk'] ?? ''; if ($nim === '' || $nama === '' || $email === '' || $thnmasuk === '') { $skipped++; $errors[] = 'Baris '.$line.': nim, nama, email, dan thnmasuk wajib diisi.'; continue; } if (! preg_match('/^[A-Za-z0-9._-]+$/', $nim)) { $skipped++; $errors[] = 'Baris '.$line.': NIM tidak valid.'; continue; } if (! filter_var($email, FILTER_VALIDATE_EMAIL)) { $skipped++; $errors[] = 'Baris '.$line.': email tidak valid.'; continue; } $payload = [ 'nim' => $nim, 'nmLengkap' => $nama, 'email' => $email, 'idProdi' => $user['prodi'], 'thnmasuk' => $thnmasuk, 'status' => ($data['status'] ?? '') !== '' ? $data['status'] : 'A', 'noHP' => $data['noHP'] ?? '', 'noHPOrtu' => $data['noHPOrtu'] ?? '', 'bolehUploadDraft' => ($data['bolehUploadDraft'] ?? '') !== '' ? $data['bolehUploadDraft'] : '1', ]; $existing = DB::table('tbmhs') ->where('nim', $nim) ->where('idProdi', $user['prodi']) ->first(); if ($existing && $validated['mode'] === 'insert_only') { $skipped++; continue; } if ($existing) { if (($data['password'] ?? '') !== '') { $payload['password'] = md5($data['password']); } DB::table('tbmhs')->where('idmhs', $existing->idmhs)->update($payload); $updated++; } else { $payload['password'] = md5(($data['password'] ?? '') !== '' ? $data['password'] : $defaultPassword); DB::table('tbmhs')->insert($payload); $inserted++; } } DB::commit(); } catch (\Throwable $exception) { DB::rollBack(); fclose($handle); return back()->with('error', 'Import gagal: '.$exception->getMessage()); } fclose($handle); return redirect() ->route('admin.data.mahasiswa') ->with('success', 'Import selesai. Baru: '.$inserted.', diperbarui: '.$updated.', dilewati: '.$skipped.'.'.($errors ? ' Catatan: '.implode(' ', array_slice($errors, 0, 5)) : '')); } public function dosen(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tbdosen as td') ->leftJoin('tbprodi as tp', 'td.idProdi', '=', 'tp.idProdi') ->select(['td.iddosen', 'td.nip', 'td.nmLengkap', 'td.email', 'tp.nmProdi', 'td.status']) ->where('td.idProdi', $user['prodi']) ->orderBy('td.nmLengkap') ->paginate(20); return $this->table($request, 'Data Dosen', 'Data dosen pada program studi admin aktif.', ['NIP', 'Nama', 'Email', 'Prodi', 'Status'], $rows, fn ($row) => [$row->nip, $row->nmLengkap, $row->email ?: '-', $row->nmProdi, $row->status], 'admin.data.dosen', [ 'actions' => [ ['label' => 'Tambah Dosen', 'href' => route('admin.data.dosen.create', [], false), 'variant' => 'dark'], ], 'rowActions' => fn ($row) => [ ['label' => 'Edit', 'href' => route('admin.data.dosen.edit', ['id' => $row->iddosen], false)], ['label' => 'Hapus', 'href' => route('admin.data.dosen.destroy', ['id' => $row->iddosen], false), 'method' => 'DELETE', 'confirm' => 'Hapus dosen ini?'], ], ]); } public function createDosen(Request $request): View { return $this->resourceForm($request, 'Tambah Dosen', 'Tambah data dosen pada program studi admin.', route('admin.data.dosen.store'), route('admin.data.dosen', [], false), $this->dosenFields()); } public function storeDosen(Request $request): RedirectResponse { $user = $this->user($request); $data = $request->validate([ 'nip' => ['required', 'string', 'max:30'], 'nmLengkap' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'email', 'max:255'], 'nohp' => ['nullable', 'string', 'max:30'], 'jenis' => ['required', 'string', 'max:10'], 'password' => ['required', 'string', 'min:4'], 'status' => ['required', 'in:A,N,P'], ]); $data['idProdi'] = $user['prodi']; $data['password'] = md5($data['password']); DB::table('tbdosen')->insert($data); return redirect()->route('admin.data.dosen')->with('success', 'Data dosen berhasil ditambahkan.'); } public function editDosen(Request $request, int $id): View { $user = $this->user($request); $row = DB::table('tbdosen')->where('iddosen', $id)->where('idProdi', $user['prodi'])->first(); abort_unless($row, 404); return $this->resourceForm($request, 'Edit Dosen', 'Perbarui data dosen.', route('admin.data.dosen.update', ['id' => $id]), route('admin.data.dosen', [], false), $this->dosenFields($row, true), 'PUT'); } public function updateDosen(Request $request, int $id): RedirectResponse { $user = $this->user($request); abort_unless(DB::table('tbdosen')->where('iddosen', $id)->where('idProdi', $user['prodi'])->exists(), 404); $data = $request->validate([ 'nip' => ['required', 'string', 'max:30'], 'nmLengkap' => ['required', 'string', 'max:255'], 'email' => ['nullable', 'email', 'max:255'], 'nohp' => ['nullable', 'string', 'max:30'], 'jenis' => ['required', 'string', 'max:10'], 'password' => ['nullable', 'string', 'min:4'], 'status' => ['required', 'in:A,N,P'], ]); if (($data['password'] ?? '') !== '') { $data['password'] = md5($data['password']); } else { unset($data['password']); } DB::table('tbdosen')->where('iddosen', $id)->update($data); return redirect()->route('admin.data.dosen')->with('success', 'Data dosen berhasil diperbarui.'); } public function destroyDosen(Request $request, int $id): RedirectResponse { $user = $this->user($request); DB::table('tbdosen')->where('iddosen', $id)->where('idProdi', $user['prodi'])->delete(); return redirect()->route('admin.data.dosen')->with('success', 'Data dosen berhasil dihapus.'); } public function kk(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tb_kelompok_keahlian as kk') ->leftJoin('tbdosen as ketua', 'kk.ketuaKK', '=', 'ketua.iddosen') ->leftJoin('tbdosen as sekretaris', 'kk.sekretarisKK', '=', 'sekretaris.iddosen') ->select(['kk.idKK', 'kk.namaKK', 'kk.warnaLabel', 'ketua.nmLengkap as ketua', 'sekretaris.nmLengkap as sekretaris']) ->orderBy('kk.namaKK') ->paginate(20); return $this->table($request, 'Data Kelompok Keahlian', 'Daftar kelompok keahlian yang dipakai pada praoutline dan penawaran judul.', ['ID', 'Nama KK', 'Label', 'Ketua', 'Sekretaris'], $rows, fn ($row) => [$row->idKK, $row->namaKK, $row->warnaLabel ?: '-', $row->ketua ?: '-', $row->sekretaris ?: '-'], 'admin.data.kk', [ 'actions' => [ ['label' => 'Tambah KK', 'href' => route('admin.data.kk.create', [], false), 'variant' => 'dark'], ], 'rowActions' => fn ($row) => [ ['label' => 'Edit', 'href' => route('admin.data.kk.edit', ['id' => $row->idKK], false)], ['label' => 'Hapus', 'href' => route('admin.data.kk.destroy', ['id' => $row->idKK], false), 'method' => 'DELETE', 'confirm' => 'Hapus kelompok keahlian ini?'], ], ]); } public function createKk(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); return $this->resourceForm($request, 'Tambah Kelompok Keahlian', 'Tambah data kelompok keahlian.', route('admin.data.kk.store'), route('admin.data.kk', [], false), $this->kkFields()); } public function storeKk(Request $request): RedirectResponse { $user = $this->user($request); $this->abortIfSuper($user); $data = $request->validate([ 'namaKK' => ['required', 'string', 'max:255'], 'warnaLabel' => ['nullable', 'string', 'max:50'], 'ketuaKK' => ['nullable', 'integer'], 'sekretarisKK' => ['nullable', 'integer'], ]); DB::table('tb_kelompok_keahlian')->insert($data); return redirect()->route('admin.data.kk')->with('success', 'Data KK berhasil ditambahkan.'); } public function editKk(Request $request, int $id): View { $user = $this->user($request); $this->abortIfSuper($user); $row = DB::table('tb_kelompok_keahlian')->where('idKK', $id)->first(); abort_unless($row, 404); return $this->resourceForm($request, 'Edit Kelompok Keahlian', 'Perbarui data kelompok keahlian.', route('admin.data.kk.update', ['id' => $id]), route('admin.data.kk', [], false), $this->kkFields($row), 'PUT'); } public function updateKk(Request $request, int $id): RedirectResponse { $user = $this->user($request); $this->abortIfSuper($user); $data = $request->validate([ 'namaKK' => ['required', 'string', 'max:255'], 'warnaLabel' => ['nullable', 'string', 'max:50'], 'ketuaKK' => ['nullable', 'integer'], 'sekretarisKK' => ['nullable', 'integer'], ]); DB::table('tb_kelompok_keahlian')->where('idKK', $id)->update($data); return redirect()->route('admin.data.kk')->with('success', 'Data KK berhasil diperbarui.'); } public function destroyKk(Request $request, int $id): RedirectResponse { $user = $this->user($request); $this->abortIfSuper($user); DB::table('tb_kelompok_keahlian')->where('idKK', $id)->delete(); return redirect()->route('admin.data.kk')->with('success', 'Data KK berhasil dihapus.'); } public function fakultas(Request $request): View { $user = $this->user($request); $this->abortUnlessSuper($user); $rows = DB::table('tbfakultas')->select(['idFak', 'nmFakultas'])->orderBy('nmFakultas')->paginate(20); return $this->table($request, 'Data Fakultas', 'Data fakultas untuk superadmin.', ['ID', 'Nama Fakultas'], $rows, fn ($row) => [$row->idFak, $row->nmFakultas], 'admin.data.fakultas'); } public function jurusan(Request $request): View { $user = $this->user($request); $this->abortUnlessSuper($user); $rows = DB::table('tbjurusan as tj') ->leftJoin('tbfakultas as tf', 'tj.idFak', '=', 'tf.idFak') ->select(['tj.idJur', 'tj.nmJurusan', 'tf.nmFakultas']) ->orderBy('tj.nmJurusan') ->paginate(20); return $this->table($request, 'Data Jurusan', 'Data jurusan untuk superadmin.', ['ID', 'Jurusan', 'Fakultas'], $rows, fn ($row) => [$row->idJur, $row->nmJurusan, $row->nmFakultas ?: '-'], 'admin.data.jurusan'); } public function prodi(Request $request): View { $user = $this->user($request); $this->abortUnlessSuper($user); $rows = DB::table('tbprodi as tp') ->leftJoin('tbjurusan as tj', 'tp.idJur', '=', 'tj.idJur') ->leftJoin('tbfakultas as tf', 'tp.idFak', '=', 'tf.idFak') ->select(['tp.idProdi', 'tp.nmProdi', 'tj.nmJurusan', 'tf.nmFakultas']) ->orderBy('tp.nmProdi') ->paginate(20); return $this->table($request, 'Data Program Studi', 'Data program studi untuk superadmin.', ['ID', 'Program Studi', 'Jurusan', 'Fakultas'], $rows, fn ($row) => [$row->idProdi, $row->nmProdi, $row->nmJurusan ?: '-', $row->nmFakultas ?: '-'], 'admin.data.prodi'); } public function praoutline(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = $this->praoutlineBase($user) ->orderByDesc('tp.id') ->paginate(20); return $this->table($request, 'Daftar Draft Praoutline', 'Draft praoutline terbaru pada program studi admin.', ['ID', 'Mahasiswa', 'Judul', 'Tanggal Upload', 'Status'], $rows, fn ($row) => [$row->id, $row->mahasiswa.' ('.$row->nim.')', $row->judul, $this->formatDateTime(trim($row->tgl_upload.' '.$row->wkt_upload)), $this->statusLabel($row->status_usulan)], 'admin.praoutline.index'); } public function praoutlineSearch(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $keyword = trim((string) $request->query('q')); $query = $this->praoutlineBase($user)->orderByDesc('tp.id'); if ($keyword !== '') { $query->where(function ($query) use ($keyword) { $query->where('tp.judul', 'like', '%'.$keyword.'%') ->orWhere('tm.nmLengkap', 'like', '%'.$keyword.'%') ->orWhere('tp.nim', 'like', '%'.$keyword.'%'); }); } $rows = $query->paginate(20)->withQueryString(); return $this->table($request, 'Pencarian Praoutline', 'Cari draft berdasarkan judul, nama mahasiswa, atau NIM.', ['ID', 'Mahasiswa', 'Judul', 'Tanggal Upload', 'Status'], $rows, fn ($row) => [$row->id, $row->mahasiswa.' ('.$row->nim.')', $row->judul, $this->formatDateTime(trim($row->tgl_upload.' '.$row->wkt_upload)), $this->statusLabel($row->status_usulan)], 'admin.praoutline.search', ['search' => true, 'keyword' => $keyword]); } public function keputusan(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tbrekaphasil as trh') ->leftJoin('tbmhs as tm', 'trh.nim', '=', 'tm.nim') ->leftJoin('tbpraoutline as tp', 'trh.idpraoutline', '=', 'tp.id') ->select(['trh.id', 'trh.nim', 'tm.nmLengkap as mahasiswa', 'tp.judul', 'trh.kep_akhir', 'trh.tgl']) ->where('trh.idProdi', $user['prodi']) ->orderByDesc('trh.id') ->paginate(20); return $this->table($request, 'Kep. Penunjukan Dosen', 'Rekap keputusan dan penunjukan dosen dari data praoutline.', ['ID', 'Mahasiswa', 'Judul', 'Keputusan', 'Tanggal'], $rows, fn ($row) => [$row->id, ($row->mahasiswa ?: '-').' ('.$row->nim.')', $row->judul ?: '-', $this->statusLabel($row->kep_akhir), $this->formatDateTime($row->tgl)], 'admin.praoutline.keputusan'); } public function kepDraft(Request $request): View { return $this->keputusan($request)->with('pageTitle', 'Kep. Draft Praoutline'); } public function pemberitahuan(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tmp_notif_r as tn') ->select(['tn.id', 'tn.msg', 'tn.tgl', 'tn.jns_usr', 'tn.idkonten', 'tn.read']) ->where('tn.idProdi', $user['prodi']) ->orderByDesc('tn.id') ->paginate(20); return $this->table($request, 'Pemberitahuan', 'Pemberitahuan praoutline yang tercatat pada sistem.', ['ID', 'Isi', 'Jenis User', 'Konten', 'Status', 'Tanggal'], $rows, fn ($row) => [$row->id, $row->msg, $row->jns_usr, $row->idkonten, $row->read === 'Y' ? 'Dibaca' : 'Belum Dibaca', $this->formatDateTime($row->tgl)], 'admin.praoutline.pemberitahuan'); } public function pengumuman(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tbpengumuman') ->select(['id', 'judul', 'tujuan', 'publish', 'tgl']) ->where('idProdi', $user['prodi']) ->orderByDesc('id') ->paginate(20); return $this->table($request, 'Daftar Pengumuman', 'Pengumuman program studi yang ditampilkan di SPOTA.', ['ID', 'Judul', 'Tujuan', 'Publish', 'Tanggal'], $rows, fn ($row) => [$row->id, $row->judul, $row->tujuan, $row->publish, $this->formatDateTime($row->tgl)], 'admin.pengumuman.index', ['actions' => [['label' => 'Buat Pengumuman Baru', 'href' => route('admin.pengumuman.create', [], false), 'variant' => 'dark']]]); } public function createPengumuman(Request $request): View { return $this->form($request, 'Buat Pengumuman Baru', 'Tambah pengumuman untuk mahasiswa, dosen, atau seluruh pengguna program studi.', 'admin.pengumuman.create'); } public function storePengumuman(Request $request): RedirectResponse { $user = $this->user($request); $this->abortIfSuper($user); $validated = $request->validate([ 'judul' => ['required', 'string', 'max:255'], 'isi' => ['required', 'string'], 'tujuan' => ['required', 'in:A,M,D'], 'publish' => ['nullable', 'in:Y,N'], ]); DB::table('tbpengumuman')->insert([ 'idProdi' => $user['prodi'], 'judul' => $validated['judul'], 'isi' => $validated['isi'], 'tujuan' => $validated['tujuan'], 'tgl' => Carbon::now()->toDateTimeString(), 'author' => $user['id'], 'publish' => $validated['publish'] ?? 'Y', ]); return redirect()->route('admin.pengumuman.index')->with('success', 'Pengumuman berhasil dibuat.'); } public function jadwal(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('tbjadwal as tj') ->leftJoin('tbmhs as tm', 'tj.idMhs', '=', 'tm.idmhs') ->select(['tj.id', 'tj.jenis', 'tj.start', 'tj.ruangan', 'tj.publish', 'tm.nmLengkap as mahasiswa']) ->where('tj.idProdi', $user['prodi']) ->orderByDesc('tj.start') ->paginate(20); return $this->table($request, 'Manajemen Data Jadwal', 'Data jadwal seminar/sidang program studi.', ['ID', 'Jenis', 'Mahasiswa', 'Tanggal', 'Ruangan', 'Publish'], $rows, fn ($row) => [$row->id, $row->jenis, $row->mahasiswa ?: '-', $this->formatDateTime($row->start), $row->ruangan ?: '-', $row->publish], 'admin.jadwal.index'); } public function kalender(Request $request): View { return $this->jadwal($request)->with('pageTitle', 'Kalender Seminar/Sidang'); } public function profile(Request $request): View { $user = $this->user($request); $rows = collect([(object) [ 'username' => $user['username'], 'nama' => $user['nama_lengkap'], 'jabatan' => $user['jabatan'] ?: '-', 'email' => $user['email'] ?: '-', 'prodi' => $user['nmprodi'], 'level' => $user['lvl'] === 'S' ? 'Super Admin' : 'Admin Prodi', ]]); return $this->staticTable($request, 'Profil Saya', 'Profil admin yang sedang login.', ['Username', 'Nama', 'Jabatan', 'Email', 'Prodi', 'Level'], $rows, fn ($row) => [$row->username, $row->nama, $row->jabatan, $row->email, $row->prodi, $row->level], 'admin.profile'); } public function users(Request $request): View { $user = $this->user($request); $this->abortUnlessSuper($user); $rows = DB::table('tbadmin as ta') ->leftJoin('tbprodi as tp', 'ta.idProdi', '=', 'tp.idProdi') ->select(['ta.username', 'ta.nmLengkap', 'ta.jenisAdmin', 'ta.aktif', 'tp.nmProdi']) ->orderBy('ta.username') ->paginate(20); return $this->table($request, 'Manajemen Admin', 'Daftar akun administrator SPOTA.', ['Username', 'Nama', 'Level', 'Prodi', 'Aktif'], $rows, fn ($row) => [$row->username, $row->nmLengkap, $row->jenisAdmin, $row->nmProdi ?: 'Semua', $row->aktif], 'admin.users'); } public function pengaturan(Request $request): View { $user = $this->user($request); $this->abortIfSuper($user); $rows = DB::table('web_setting') ->select(['name', 'values']) ->where('idProdi', $user['prodi']) ->orderBy('name') ->paginate(20); return $this->table($request, 'Pengaturan Prodi', 'Pengaturan aktif program studi pada SPOTA.', ['Nama', 'Nilai'], $rows, fn ($row) => [$row->name, $row->values], 'admin.pengaturan'); } private function table(Request $request, string $title, string $description, array $columns, mixed $rows, callable $map, string $activeRoute, array $extra = []): View { $user = $this->user($request); return view('admin.pages.table', array_merge([ 'title' => $title.' | SPOTA Rebuild', 'pageTitle' => $title, 'pageDescription' => $description, 'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'), 'sidebar' => AdminNavigation::build($user), 'user' => $user, 'columns' => $columns, 'rows' => $rows, 'map' => $map, ], $extra)); } private function staticTable(Request $request, string $title, string $description, array $columns, mixed $rows, callable $map, string $activeRoute): View { return $this->table($request, $title, $description, $columns, $rows, $map, $activeRoute); } private function form(Request $request, string $title, string $description, string $activeRoute): View { $user = $this->user($request); return view('admin.pages.pengumuman-form', [ 'title' => $title.' | SPOTA Rebuild', 'pageTitle' => $title, 'pageDescription' => $description, 'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'), 'sidebar' => AdminNavigation::build($user), 'user' => $user, ]); } private function resourceForm(Request $request, string $title, string $description, string $action, string $cancel, array $fields, string $method = 'POST'): View { $user = $this->user($request); return view('admin.pages.resource-form', [ 'title' => $title.' | SPOTA Rebuild', 'pageTitle' => $title, 'pageDescription' => $description, 'sidebar' => AdminNavigation::build($user), 'user' => $user, 'action' => $action, 'cancel' => $cancel, 'fields' => $fields, 'method' => $method, ]); } private function mahasiswaFields(?object $row = null, bool $editing = false): array { return [ ['name' => 'nim', 'label' => 'NIM', 'value' => $row->nim ?? '', 'required' => true], ['name' => 'nmLengkap', 'label' => 'Nama Lengkap', 'value' => $row->nmLengkap ?? '', 'required' => true], ['name' => 'email', 'label' => 'Email', 'type' => 'email', 'value' => $row->email ?? ''], ['name' => 'thnmasuk', 'label' => 'Tahun Masuk', 'value' => $row->thnmasuk ?? ''], ['name' => 'password', 'label' => 'Password', 'type' => 'password', 'required' => ! $editing, 'help' => $editing ? 'Kosongkan jika tidak mengganti password.' : 'Password awal mahasiswa.'], ['name' => 'status', 'label' => 'Status', 'type' => 'select', 'value' => $row->status ?? 'A', 'options' => ['A' => 'Aktif', 'P' => 'Pending', 'N' => 'Nonaktif']], ['name' => 'bolehUploadDraft', 'label' => 'Boleh Upload Draft', 'type' => 'select', 'value' => $row->bolehUploadDraft ?? '1', 'options' => ['1' => 'Ya', '0' => 'Tidak']], ['name' => 'noHP', 'label' => 'No HP', 'value' => $row->noHP ?? ''], ['name' => 'noHPOrtu', 'label' => 'No HP Orang Tua', 'value' => $row->noHPOrtu ?? ''], ]; } private function dosenFields(?object $row = null, bool $editing = false): array { return [ ['name' => 'nip', 'label' => 'NIP', 'value' => $row->nip ?? '', 'required' => true], ['name' => 'nmLengkap', 'label' => 'Nama Lengkap', 'value' => $row->nmLengkap ?? '', 'required' => true], ['name' => 'email', 'label' => 'Email', 'type' => 'email', 'value' => $row->email ?? ''], ['name' => 'nohp', 'label' => 'No HP', 'value' => $row->nohp ?? ''], ['name' => 'jenis', 'label' => 'Jenis/Jabatan Dosen', 'value' => $row->jenis ?? 'D'], ['name' => 'password', 'label' => 'Password', 'type' => 'password', 'required' => ! $editing, 'help' => $editing ? 'Kosongkan jika tidak mengganti password.' : 'Password awal dosen.'], ['name' => 'status', 'label' => 'Status', 'type' => 'select', 'value' => $row->status ?? 'A', 'options' => ['A' => 'Aktif', 'P' => 'Pending', 'N' => 'Nonaktif']], ]; } private function kkFields(?object $row = null): array { $dosen = DB::table('tbdosen')->select(['iddosen', 'nmLengkap'])->orderBy('nmLengkap')->get(); $options = ['' => '- Pilih Dosen -']; foreach ($dosen as $item) { $options[$item->iddosen] = $item->nmLengkap; } return [ ['name' => 'namaKK', 'label' => 'Nama Kelompok Keahlian', 'value' => $row->namaKK ?? '', 'required' => true], ['name' => 'warnaLabel', 'label' => 'Warna Label', 'value' => $row->warnaLabel ?? '', 'help' => 'Opsional, mengikuti data legacy.'], ['name' => 'ketuaKK', 'label' => 'Ketua KK', 'type' => 'select', 'value' => $row->ketuaKK ?? '', 'options' => $options], ['name' => 'sekretarisKK', 'label' => 'Sekretaris KK', 'type' => 'select', 'value' => $row->sekretarisKK ?? '', 'options' => $options], ]; } private function praoutlineBase(array $user) { return DB::table('tbpraoutline as tp') ->leftJoin('tbmhs as tm', 'tp.nim', '=', 'tm.nim') ->select(['tp.id', 'tp.nim', 'tm.nmLengkap as mahasiswa', 'tp.judul', 'tp.tgl_upload', 'tp.wkt_upload', 'tp.status_usulan']) ->where('tp.idProdi', $user['prodi']); } private function user(Request $request): array { $auth = $request->session()->get('legacy_auth'); abort_unless(($auth['role'] ?? null) === 'admin', 403); return $auth['user']; } private function abortIfSuper(array $user): void { abort_if(($user['lvl'] ?? null) === 'S', 403); } private function abortUnlessSuper(array $user): void { abort_unless(($user['lvl'] ?? null) === 'S', 403); } private function statusLabel(?string $status): string { return match ($status) { '0' => 'Dalam Review', '1' => 'Disetujui', '2' => 'Ditolak', default => $status ?? '-', }; } private function formatDateTime(?string $value): string { if (! $value || trim($value) === '') { return '-'; } return Carbon::parse($value)->locale('id')->translatedFormat('j F Y, H:i'); } }