dosenUser($request); $source = (string) $request->query('sumber', '0'); $statusFilter = (string) $request->query('status', 'Semua'); $kkFilter = (string) $request->query('kk', 'all'); $kkOptions = DB::table('tb_kelompok_keahlian') ->where('idKK', '!=', '8') ->orderBy('namaKK') ->get(['idKK', 'namaKK']) ->map(fn ($item) => [ 'value' => (string) $item->idKK, 'label' => $item->namaKK, ]) ->all(); $penawaranQuery = DB::table('tb_penawaran_judul as tpj') ->leftJoin('tb_ambil_judul as taj', function ($join) { $join->on('taj.idPenawaranAmbil', '=', 'tpj.idPenawaran') ->whereRaw('taj.waktuPengambilan = (SELECT MAX(t2.waktuPengambilan) FROM tb_ambil_judul t2 WHERE t2.idPenawaranAmbil = tpj.idPenawaran)'); }) ->leftJoin('tbmhs as tm', 'tm.idmhs', '=', 'taj.idMhs') ->leftJoin('tb_kelompok_keahlian as tkk', 'tkk.idKK', '=', 'tpj.kk') ->leftJoin('tbdosen as td', 'td.iddosen', '=', 'tpj.idDosen') ->select([ 'tpj.idPenawaran', 'tpj.idDosen', 'tpj.judul', 'tpj.deskripsi', 'tpj.waktuInput', 'taj.statusPengambilan', 'tm.nim', 'tm.nmLengkap', 'tkk.namaKK', 'td.nmLengkap as namaDosen', ]) ->where('td.idProdi', $user['prodi']); if ($source === '0') { $penawaranQuery->where('tpj.idDosen', $user['id']); } if ($kkFilter !== 'all') { $penawaranQuery->where('tpj.kk', $kkFilter); } if ($statusFilter === 'Belum Diambil') { $penawaranQuery->where(function ($query) { $query->whereNull('taj.statusPengambilan') ->orWhere('taj.statusPengambilan', '2'); }); } elseif ($statusFilter === 'Belum Diproses') { $penawaranQuery->where('taj.statusPengambilan', '0'); } elseif ($statusFilter === 'Diterima') { $penawaranQuery->where('taj.statusPengambilan', '1'); } $penawaran = $penawaranQuery ->orderByDesc('tpj.waktuInput') ->limit(100) ->get() ->map(function ($item) use ($user) { $takenBy = $item->nmLengkap ? $item->nmLengkap.' ('.$item->nim.')' : '-'; if ((string) $item->statusPengambilan === '2') { $takenBy = '-'; } return [ 'id' => $item->idPenawaran, 'judul' => $item->judul, 'deskripsi' => $item->deskripsi ?: '-', 'waktu' => $this->formatDateTime($item->waktuInput), 'kk' => $item->namaKK ?: '-', 'ditawarkanOleh' => $item->namaDosen ?: '-', 'mahasiswa' => $takenBy, 'status' => $this->statusPenawaran($item->statusPengambilan), 'editHref' => route('dosen.penawaran.edit', $item->idPenawaran), 'isMine' => (string) $item->idDosen === (string) $user['id'], 'destroyHref' => route('dosen.penawaran.destroy', $item->idPenawaran), 'approveHref' => route('dosen.penawaran.approve', $item->idPenawaran), 'rejectHref' => route('dosen.penawaran.reject', $item->idPenawaran), 'canApprove' => (string) $item->statusPengambilan === '0', ]; }) ->all(); return $this->renderPage('dosen.pages.penawaran', [ 'title' => 'Penawaran Judul | SPOTA Rebuild', 'pageTitle' => 'Penawaran Judul', 'pageDescription' => 'Daftar penawaran judul milik dosen yang sudah masuk pada sistem lama.', 'routeName' => 'dosen.penawaran.index', 'user' => $user, 'penawaran' => $penawaran, 'source' => $source, 'statusFilter' => $statusFilter, 'kkFilter' => $kkFilter, 'kkOptions' => $kkOptions, 'pageActions' => [ ['label' => 'Tambah Data', 'href' => route('dosen.penawaran.create'), 'variant' => 'dark'], ], ]); } public function createPenawaran(Request $request): View { $user = $this->dosenUser($request); return $this->renderPage('dosen.pages.penawaran-form', [ 'title' => 'Tambah Penawaran Judul | SPOTA Rebuild', 'pageTitle' => 'Tambah Data Penawaran Judul', 'pageDescription' => 'Tambahkan data penawaran judul baru langsung dari halaman rebuild.', 'routeName' => 'dosen.penawaran.index', 'user' => $user, 'formMode' => 'create', 'formAction' => route('dosen.penawaran.store'), 'formMethod' => 'POST', 'penawaranItem' => [ 'judul' => old('judul_penawaran', ''), 'deskripsi' => old('keterangan_penawaran', ''), ], ]); } public function storePenawaran(Request $request): RedirectResponse { $user = $this->dosenUser($request); $data = $request->validate([ 'judul_penawaran' => ['required', 'string', 'max:255'], 'keterangan_penawaran' => ['nullable', 'string'], ]); $kk = DB::table('tbdosen') ->where('iddosen', $user['id']) ->value('kelompokKeahlian'); DB::table('tb_penawaran_judul')->insert([ 'idDosen' => $user['id'], 'kk' => $kk ?: 0, 'judul' => trim($data['judul_penawaran']), 'deskripsi' => trim((string) ($data['keterangan_penawaran'] ?? '')), 'waktuInput' => now(), ]); return redirect()->route('dosen.penawaran.index')->with('success', 'Data Penawaran Judul Berhasil Disimpan'); } public function editPenawaran(Request $request, int $id): View { $user = $this->dosenUser($request); $penawaran = DB::table('tb_penawaran_judul') ->where('idPenawaran', $id) ->where('idDosen', $user['id']) ->first(); abort_unless($penawaran, 404); return $this->renderPage('dosen.pages.penawaran-form', [ 'title' => 'Edit Penawaran Judul | SPOTA Rebuild', 'pageTitle' => 'Edit Data Penawaran Judul', 'pageDescription' => 'Perbarui data penawaran judul milik dosen ini.', 'routeName' => 'dosen.penawaran.index', 'user' => $user, 'formMode' => 'edit', 'formAction' => route('dosen.penawaran.update', $id), 'formMethod' => 'PUT', 'penawaranItem' => [ 'judul' => old('judul_penawaran', $penawaran->judul), 'deskripsi' => old('keterangan_penawaran', $penawaran->deskripsi), ], ]); } public function updatePenawaran(Request $request, int $id): RedirectResponse { $user = $this->dosenUser($request); $data = $request->validate([ 'judul_penawaran' => ['required', 'string', 'max:255'], 'keterangan_penawaran' => ['nullable', 'string'], ]); $updated = DB::table('tb_penawaran_judul') ->where('idPenawaran', $id) ->where('idDosen', $user['id']) ->update([ 'judul' => trim($data['judul_penawaran']), 'deskripsi' => trim((string) ($data['keterangan_penawaran'] ?? '')), ]); abort_unless($updated !== 0, 404); return redirect()->route('dosen.penawaran.index')->with('success', 'Data berhasil diubah'); } public function destroyPenawaran(Request $request, int $id): RedirectResponse { $user = $this->dosenUser($request); $hasBooking = DB::table('tb_ambil_judul') ->where('idPenawaranAmbil', $id) ->exists(); if ($hasBooking) { return redirect()->route('dosen.penawaran.index')->with('error', 'Tidak dapat menghapus judul ini, judul ini pernah di booking mahasiswa sebelumnya'); } $deleted = DB::table('tb_penawaran_judul') ->where('idPenawaran', $id) ->where('idDosen', $user['id']) ->delete(); abort_unless($deleted !== 0, 404); return redirect()->route('dosen.penawaran.index')->with('success', 'Data Penawaran Judul Ini Telah Dihapus.'); } public function approvePenawaran(Request $request, int $id): RedirectResponse { $user = $this->dosenUser($request); $booking = $this->latestPenawaranBooking($id, $user['id']); if (! $booking) { return redirect()->route('dosen.penawaran.index')->with('error', 'Aksi gagal, data pengambilan judul tidak ditemukan.'); } DB::table('tb_ambil_judul') ->where('idAmbil', $booking->idAmbil) ->update([ 'statusPengambilan' => '1', 'waktuVerifikasi' => now(), ]); return redirect()->route('dosen.penawaran.index')->with('success', 'Berhasil menyetujui pengambilan judul.'); } public function rejectPenawaran(Request $request, int $id): RedirectResponse { $user = $this->dosenUser($request); $booking = $this->latestPenawaranBooking($id, $user['id']); if (! $booking) { return redirect()->route('dosen.penawaran.index')->with('error', 'Aksi gagal, data pengambilan judul tidak ditemukan.'); } DB::table('tb_ambil_judul') ->where('idAmbil', $booking->idAmbil) ->update([ 'statusPengambilan' => '2', 'waktuVerifikasi' => now(), ]); return redirect()->route('dosen.penawaran.index')->with('success', 'Berhasil menolak pengambilan judul.'); } public function daftarUsulan(Request $request): View { $user = $this->dosenUser($request); $usulan = DB::table('tbpraoutline as tp') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'tp.nim') ->leftJoin('tb_kelompok_keahlian as tkk', 'tkk.idKK', '=', 'tp.kelompokKeahlian') ->select([ 'tp.id', 'tp.judul', 'tp.nim', 'tm.nmLengkap', 'tp.thn_ajaran', 'tp.semester', 'tp.tgl_upload', 'tp.wkt_upload', 'tp.status_usulan', 'tkk.namaKK', ]) ->where('tp.idProdi', $user['prodi']) ->where('tp.status_usulan', '0') ->orderByDesc('tp.tgl_upload') ->orderByDesc('tp.wkt_upload') ->limit(30) ->get() ->map(fn ($item) => [ 'id' => $item->id, 'judul' => $item->judul, 'mahasiswa' => ($item->nmLengkap ?: 'Mahasiswa tidak ditemukan').' ('.$item->nim.')', 'periode' => trim(($item->thn_ajaran ?: '-').' / '.($item->semester ?: '-')), 'tanggal' => $this->formatDateTime(trim(($item->tgl_upload ?: '').' '.($item->wkt_upload ?: ''))), 'kk' => $item->namaKK ?: '-', 'status' => $this->statusUsulan($item->status_usulan), 'reviewHref' => route('dosen.praoutline.review', $item->id, false), ]) ->all(); return $this->renderPage('dosen.pages.daftar-usulan', [ 'title' => 'Daftar Usulan | SPOTA Rebuild', 'pageTitle' => 'Daftar Usulan', 'pageDescription' => 'Usulan judul terbaru yang masih dalam proses pada program studi dosen.', 'routeName' => 'dosen.praoutline.index', 'user' => $user, 'usulan' => $usulan, 'pageActions' => [ ['label' => 'Pencarian Usulan', 'href' => route('dosen.praoutline.cari'), 'variant' => 'light'], ], ]); } public function reviewSaya(Request $request): View { $user = $this->dosenUser($request); $reviews = DB::table('tbreview as tr') ->join('tbpraoutline as tp', 'tp.id', '=', 'tr.idpraoutline') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'tp.nim') ->select([ 'tp.id', 'tp.judul', 'tp.nim', 'tm.nmLengkap', 'tp.thn_ajaran', 'tp.semester', 'tp.tgl_upload', 'tp.status_usulan', ]) ->where('tr.reviewer', $user['nip']) ->distinct() ->orderByDesc('tp.tgl_upload') ->limit(30) ->get() ->map(fn ($item) => [ 'id' => $item->id, 'judul' => $item->judul, 'mahasiswa' => ($item->nmLengkap ?: 'Mahasiswa tidak ditemukan').' ('.$item->nim.')', 'periode' => trim(($item->thn_ajaran ?: '-').' / '.($item->semester ?: '-')), 'tanggal' => $this->formatDateTime($item->tgl_upload), 'status' => $this->statusUsulan($item->status_usulan), 'reviewHref' => route('dosen.praoutline.review', $item->id, false), ]) ->all(); return $this->renderPage('dosen.pages.review-saya', [ 'title' => 'Review Saya | SPOTA Rebuild', 'pageTitle' => 'Review Saya', 'pageDescription' => 'Usulan judul TA yang pernah diberi komentar atau tanggapan oleh dosen ini.', 'routeName' => 'dosen.praoutline.review-saya', 'user' => $user, 'reviews' => $reviews, ]); } public function cari(Request $request): View { $user = $this->dosenUser($request); $keyword = trim((string) $request->query('q', '')); $by = (string) $request->query('by', 'judul'); $results = []; if ($keyword !== '') { $query = DB::table('tbpraoutline as tp') ->join('tbmhs as tm', 'tm.nim', '=', 'tp.nim') ->leftJoin('tbrekaphasil as trh', 'trh.idpraoutline', '=', 'tp.id') ->select([ 'tp.id', 'tp.judul', 'tp.deskripsi', 'tp.nim', 'tm.nmLengkap', 'tp.tgl_upload', 'tp.status_usulan', 'trh.judul_final', 'trh.pemb1', 'trh.pemb2', 'trh.peng1', 'trh.peng2', ]); if ($by === 'nim') { $query->where('tp.nim', 'like', '%'.$keyword.'%'); } elseif ($by === 'dosen') { $query->where(function ($builder) use ($keyword) { $builder->where('trh.pemb1', 'like', '%'.$keyword.'%') ->orWhere('trh.pemb2', 'like', '%'.$keyword.'%') ->orWhere('trh.peng1', 'like', '%'.$keyword.'%') ->orWhere('trh.peng2', 'like', '%'.$keyword.'%'); }); } else { $query->where('tp.judul', 'like', '%'.$keyword.'%'); } $results = $query ->orderByDesc('tp.tgl_upload') ->limit(20) ->get() ->map(fn ($item) => [ 'id' => $item->id, 'judul' => $item->judul, 'mahasiswa' => $item->nmLengkap.' ('.$item->nim.')', 'deskripsi' => $item->deskripsi ? mb_strimwidth(strip_tags($item->deskripsi), 0, 200, '...') : '-', 'tanggal' => $this->formatDateTime($item->tgl_upload), 'status' => $this->statusUsulan($item->status_usulan), 'reviewHref' => route('dosen.praoutline.review', $item->id, false), ]) ->all(); } return $this->renderPage('dosen.pages.cari', [ 'title' => 'Pencarian Usulan | SPOTA Rebuild', 'pageTitle' => 'Pencarian Usulan', 'pageDescription' => 'Cari usulan berdasarkan NIM, judul, atau keterkaitan pembimbing dan penguji.', 'routeName' => 'dosen.praoutline.cari', 'user' => $user, 'keyword' => $keyword, 'searchBy' => $by, 'results' => $results, ]); } public function bimbingan(Request $request): View { $user = $this->dosenUser($request); $bimbingan = DB::table('tbrekaphasil as trh') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'trh.nim') ->select([ 'trh.id', 'trh.idpraoutline', 'trh.judul_final', 'trh.nim', 'tm.nmLengkap', 'trh.tahun_ajaran', 'trh.semester', 'trh.tgl_kep', ]) ->where('trh.idProdi', $user['prodi']) ->where('trh.kep_akhir', '1') ->where(function ($query) use ($user) { $query->where('trh.pemb1', $user['nip']) ->orWhere('trh.pemb2', $user['nip']) ->orWhere('trh.peng1', $user['nip']) ->orWhere('trh.peng2', $user['nip']); }) ->orderByDesc('trh.tgl_kep') ->limit(30) ->get() ->map(fn ($item) => [ 'id' => $item->id, 'judul' => $item->judul_final, 'mahasiswa' => ($item->nmLengkap ?: 'Mahasiswa tidak ditemukan').' ('.$item->nim.')', 'periode' => trim(($item->tahun_ajaran ?: '-').' / '.($item->semester ?: '-')), 'tanggal' => $this->formatDateTime($item->tgl_kep), 'reviewHref' => route('dosen.praoutline.review', $item->idpraoutline, false), ]) ->all(); return $this->renderPage('dosen.pages.bimbingan', [ 'title' => 'Daftar Bimbingan Saya | SPOTA Rebuild', 'pageTitle' => 'Daftar Bimbingan Saya', 'pageDescription' => 'Mahasiswa yang telah ditetapkan pada dosen ini sebagai pembimbing atau penguji.', 'routeName' => 'dosen.praoutline.bimbingan', 'user' => $user, 'bimbingan' => $bimbingan, ]); } public function statistik(Request $request): View { $user = $this->dosenUser($request); $draftStats = DB::table('tbpraoutline') ->selectRaw('semester') ->selectRaw("COUNT(IF(status_usulan='0',1,NULL)) as proses") ->selectRaw("COUNT(IF(status_usulan='1',1,NULL)) as terima") ->selectRaw("COUNT(IF(status_usulan='2',1,NULL)) as tolak") ->selectRaw("COUNT(IF(status_usulan='3',1,NULL)) as gugur") ->selectRaw('COUNT(*) as totaldraft') ->where('idProdi', $user['prodi']) ->groupBy('semester') ->orderByDesc('semester') ->get(); $dosenStats = DB::table('tbrekaphasil') ->selectRaw("COUNT(IF(pemb1 = ?, 1, NULL)) as pemb1", [$user['nip']]) ->selectRaw("COUNT(IF(pemb2 = ?, 1, NULL)) as pemb2", [$user['nip']]) ->selectRaw("COUNT(IF(peng1 = ?, 1, NULL)) as peng1", [$user['nip']]) ->selectRaw("COUNT(IF(peng2 = ?, 1, NULL)) as peng2", [$user['nip']]) ->where('idProdi', $user['prodi']) ->first(); return $this->renderPage('dosen.pages.statistik', [ 'title' => 'Statistik Usulan | SPOTA Rebuild', 'pageTitle' => 'Statistik Usulan', 'pageDescription' => 'Ringkasan statistik draft praoutline dan peran dosen pada data rekap aktif.', 'routeName' => 'dosen.praoutline.statistik', 'user' => $user, 'draftStats' => $draftStats, 'dosenStats' => $dosenStats, ]); } public function pemberitahuan(Request $request): View { $user = $this->dosenUser($request); $pemberitahuan = DB::table('tmp_notif_r as tnr') ->leftJoin('tbpraoutline as tp', 'tp.id', '=', 'tnr.idkonten') ->select(['tnr.msg', 'tnr.tgl', 'tnr.idkonten', 'tp.judul']) ->where('tnr.read', 'N') ->where('tnr.jns_usr', 'D') ->where('tnr.user', $user['nip']) ->where('tnr.idProdi', $user['prodi']) ->orderByDesc('tnr.tgl') ->limit(30) ->get() ->map(fn ($item) => [ 'msg' => $item->msg, 'tgl' => $item->tgl, 'title' => $item->judul, 'reviewHref' => $item->idkonten ? route('dosen.praoutline.review', $item->idkonten, false).'#post_review' : null, ]); return $this->renderPage('dosen.pages.pemberitahuan', [ 'title' => 'Pemberitahuan | SPOTA Rebuild', 'pageTitle' => 'Pemberitahuan', 'pageDescription' => 'Daftar tanggapan dan review baru yang belum dibaca oleh dosen ini.', 'routeName' => 'dosen.praoutline.pemberitahuan', 'user' => $user, 'pemberitahuan' => $pemberitahuan, ]); } public function pengumuman(Request $request): View { $user = $this->dosenUser($request); $pengumuman = DB::table('tbpengumuman as tp') ->select([ 'tp.id', 'tp.judul', 'tp.isi', 'tp.tgl', ]) ->where('tp.idProdi', $user['prodi']) ->whereIn('tp.tujuan', ['A', 'D']) ->orderByDesc('tp.tgl') ->limit(30) ->get(); $pengumuman = $pengumuman->map(fn ($item) => [ 'id' => $item->id, 'judul' => $item->judul, 'tgl' => $item->tgl, 'detailHref' => route('dosen.pengumuman.show', $item->id), ]); return $this->renderPage('dosen.pages.pengumuman', [ 'title' => 'Pengumuman | SPOTA Rebuild', 'pageTitle' => 'Daftar Pengumuman', 'pageDescription' => 'Pengumuman program studi yang ditujukan untuk dosen dan akses umum.', 'routeName' => 'dosen.pengumuman.index', 'user' => $user, 'pengumuman' => $pengumuman, ]); } public function reviewDetail(Request $request, int $id): View { $user = $this->dosenUser($request); $outline = DB::table('tbpraoutline as tp') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'tp.nim') ->leftJoin('tb_kelompok_keahlian as tkk', 'tkk.idKK', '=', 'tp.kelompokKeahlian') ->select([ 'tp.id', 'tp.judul', 'tp.deskripsi', 'tp.nim', 'tp.thn_ajaran', 'tp.semester', 'tp.tgl_upload', 'tp.wkt_upload', 'tp.status_usulan', 'tm.nmLengkap', 'tkk.namaKK', ]) ->where('tp.id', $id) ->where('tp.idProdi', $user['prodi']) ->first(); abort_unless($outline, 404); $reviews = DB::table('tbreview as tr') ->leftJoin('tbdosen as td', 'td.nip', '=', 'tr.reviewer') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'tr.reviewer') ->select([ 'tr.id', 'tr.review_text', 'tr.jenis_review', 'tr.putusan', 'tr.tgl', 'tr.wkt', 'tr.reviewer', 'td.nmLengkap as namaDosen', 'tm.nmLengkap as namaMahasiswa', ]) ->where('tr.idpraoutline', $outline->id) ->orderBy('tr.tgl') ->orderBy('tr.wkt') ->orderBy('tr.id') ->get() ->map(function ($item) { $isDecision = (string) $item->jenis_review === '1'; return [ 'id' => $item->id, 'author' => $item->namaDosen ?: ($item->namaMahasiswa ?: $item->reviewer), 'role' => $item->namaDosen ? 'Dosen' : 'Mahasiswa', 'timestamp' => $this->formatDateTime(trim(($item->tgl ?: '').' '.($item->wkt ?: ''))), 'body' => $item->review_text, 'type' => $isDecision ? 'Putusan' : 'Komentar', 'decision' => ! $isDecision ? null : ((string) $item->putusan === '1' ? 'Setuju' : 'Tidak Setuju'), ]; }) ->all(); return $this->renderPage('dosen.pages.review-detail', [ 'title' => 'Detail Review | SPOTA Rebuild', 'pageTitle' => 'Detail Review Usulan', 'pageDescription' => 'Riwayat komentar dan putusan pada usulan judul mahasiswa.', 'routeName' => 'dosen.praoutline.review', 'user' => $user, 'outline' => [ 'id' => $outline->id, 'judul' => $outline->judul, 'deskripsi' => $outline->deskripsi, 'mahasiswa' => ($outline->nmLengkap ?: 'Mahasiswa tidak ditemukan').' ('.$outline->nim.')', 'periode' => trim(($outline->thn_ajaran ?: '-').' / '.($outline->semester ?: '-')), 'tanggal' => $this->formatDateTime(trim(($outline->tgl_upload ?: '').' '.($outline->wkt_upload ?: ''))), 'status' => $this->statusUsulan($outline->status_usulan), 'kk' => $outline->namaKK ?: '-', ], 'reviews' => $reviews, 'pageActions' => [ ['label' => 'Kembali ke Review Saya', 'href' => route('dosen.praoutline.review-saya', [], false), 'variant' => 'light'], ], ]); } public function showPengumuman(Request $request, int $id): View { $user = $this->dosenUser($request); $pengumuman = DB::table('tbpengumuman') ->where('id', $id) ->where('idProdi', $user['prodi']) ->whereIn('tujuan', ['A', 'D']) ->first(); abort_unless($pengumuman, 404); $alreadyRead = DB::table('tmp_notif') ->where('idkonten', $id) ->where('iduser', $user['id']) ->where('typeuser', 'D') ->where('jenis', 'P') ->exists(); if (! $alreadyRead) { DB::table('tmp_notif')->insert([ 'idkonten' => $id, 'idProdi' => $user['prodi'], 'iduser' => $user['id'], 'typeuser' => 'D', 'date' => now(), 'jenis' => 'P', ]); } return $this->renderPage('dosen.pages.pengumuman-detail', [ 'title' => 'Lihat Pengumuman | SPOTA Rebuild', 'pageTitle' => 'Lihat Pengumuman', 'pageDescription' => 'Detail pengumuman dosen dari sistem SPOTA lama.', 'routeName' => 'dosen.pengumuman.show', 'user' => $user, 'pengumumanItem' => $pengumuman, 'pageActions' => [ ['label' => 'Kembali ke Pengumuman', 'href' => route('dosen.pengumuman.index'), 'variant' => 'light'], ], ]); } public function profile(Request $request): View { $user = $this->dosenUser($request); $profile = DB::table('tbdosen') ->where('iddosen', $user['id']) ->first(); return $this->renderPage('dosen.pages.profile', [ 'title' => 'Profil Dosen | SPOTA Rebuild', 'pageTitle' => 'Profil Dosen', 'pageDescription' => 'Kelola data akun dasar dosen yang aktif pada SPOTA.', 'routeName' => 'dosen.profile', 'user' => $user, 'profile' => $profile, ]); } public function updateProfile(Request $request): RedirectResponse { $user = $this->dosenUser($request); $data = $request->validate([ 'nmLengkap' => ['required', 'string', 'max:150'], 'email' => ['nullable', 'email', 'max:150'], 'nohp' => ['nullable', 'string', 'max:75'], 'password' => ['nullable', 'string', 'min:6', 'max:75', 'same:password_again'], 'password_again' => ['nullable', 'string', 'min:6', 'max:75'], ], [ 'password.same' => 'Konfirmasi password tidak sesuai.', ]); $payload = [ 'nmLengkap' => trim($data['nmLengkap']), 'email' => trim((string) ($data['email'] ?? '')), 'nohp' => trim((string) ($data['nohp'] ?? '')), ]; if (! empty($data['password'])) { $payload['password'] = md5($data['password']); } DB::table('tbdosen') ->where('iddosen', $user['id']) ->update($payload); $request->session()->put('legacy_auth.user', array_merge($user, [ 'nama_lengkap' => $payload['nmLengkap'], ])); return redirect()->route('dosen.profile')->with('success', 'Profil dosen berhasil diperbarui.'); } public function earlyWarning(Request $request): View { $user = $this->dosenUser($request); $records = DB::table('tbrekaphasil as trh') ->leftJoin('tbmhs as tm', 'tm.nim', '=', 'trh.nim') ->select([ 'trh.nim', 'tm.nmLengkap', 'tm.thnmasuk', 'trh.judul_final', 'trh.tgl_kep', ]) ->where('trh.idProdi', $user['prodi']) ->where(function ($query) use ($user) { $query->where('trh.pemb1', $user['nip']) ->orWhere('trh.pemb2', $user['nip']) ->orWhere('trh.peng1', $user['nip']) ->orWhere('trh.peng2', $user['nip']); }) ->orderBy('trh.tgl_kep') ->limit(50) ->get() ->map(function ($item) { $days = $item->tgl_kep ? Carbon::parse($item->tgl_kep)->diffInDays(now()) : null; $angkatan = $this->resolveAngkatan($item->nim, $item->thnmasuk ?? null); $tahunStudi = $angkatan ? (int) now()->format('Y') - $angkatan : null; if ($tahunStudi === null) { $severity = 'unknown'; $status = 'Angkatan Tidak Diketahui'; } elseif ($tahunStudi >= 8) { $severity = 'dropout'; $status = 'Ancaman DO'; } elseif ($tahunStudi >= 7) { $severity = 'critical'; $status = 'Kritis Akhir Studi'; } elseif ($tahunStudi >= 6) { $severity = 'warning'; $status = 'Warning'; } elseif ($tahunStudi >= 5) { $severity = 'watch'; $status = 'Perlu Pantau'; } else { $severity = 'safe'; $status = 'Aman'; } return [ 'mahasiswa' => ($item->nmLengkap ?: 'Mahasiswa tidak ditemukan').' ('.$item->nim.')', 'nim' => $item->nim, 'judul' => $item->judul_final ?: '-', 'tanggal' => $this->formatDateTime($item->tgl_kep), 'status' => $status, 'days' => $days, 'angkatan' => $angkatan, 'tahunStudi' => $tahunStudi, 'severity' => $severity, 'statusClass' => match ($severity) { 'dropout' => 'bg-[#3B0A0A] text-white border border-[#3B0A0A]', 'critical' => 'bg-[#7F1D1D] text-white border border-[#7F1D1D]', 'warning' => 'bg-rose-100 text-rose-700 border border-rose-200', 'watch' => 'bg-amber-100 text-amber-700 border border-amber-200', 'safe' => 'bg-emerald-100 text-emerald-700 border border-emerald-200', default => 'bg-slate-100 text-slate-700 border border-slate-200', }, 'warningText' => match ($severity) { 'dropout' => 'Mahasiswa sudah masuk ambang akhir masa studi dan perlu penanganan prioritas tertinggi.', 'critical' => 'Mahasiswa berada di tahun akhir masa studi dan berisiko tinggi jika progres tersendat.', 'warning' => 'Mahasiswa sudah melewati fase aman studi dan perlu tindak lanjut aktif.', 'watch' => 'Mahasiswa memasuki tahun studi lanjut, sebaiknya dipantau lebih ketat.', 'safe' => 'Mahasiswa masih dalam rentang studi yang relatif aman untuk monitoring rutin.', default => 'Angkatan mahasiswa belum dapat ditentukan dari data yang tersedia.', }, 'detailHref' => $this->legacyUrl('dosen/dashboard.php?page=early-warning'), ]; }) ->all(); $dropoutCount = collect($records)->where('severity', 'dropout')->count(); $criticalCount = collect($records)->where('severity', 'critical')->count(); $warningCount = collect($records)->where('severity', 'warning')->count(); $watchCount = collect($records)->where('severity', 'watch')->count(); $safeCount = collect($records)->where('severity', 'safe')->count(); return $this->renderPage('dosen.pages.early-warning', [ 'title' => 'Early Warning | SPOTA Rebuild', 'pageTitle' => 'Early Warning', 'pageDescription' => 'Pantauan mahasiswa bimbingan berdasarkan tanggal keputusan outline yang tersimpan.', 'routeName' => 'dosen.early-warning', 'user' => $user, 'records' => $records, 'summary' => [ 'dropoutCount' => $dropoutCount, 'criticalCount' => $criticalCount, 'warningCount' => $warningCount, 'watchCount' => $watchCount, 'safeCount' => $safeCount, 'totalCount' => count($records), ], ]); } public function praLirs(Request $request): View { $user = $this->dosenUser($request); return $this->renderPage('dosen.pages.pra-lirs', [ 'title' => 'Pra LIRS | SPOTA Rebuild', 'pageTitle' => 'Pra LIRS (Dosen PA)', 'pageDescription' => 'Integrasi Pra LIRS saat ini masih memakai sumber data eksternal yang sama dengan modul lama.', 'routeName' => 'dosen.pra-lirs', 'user' => $user, 'externalUrl' => 'https://informatika.untan.ac.id/API/public/getListMahasiswaPralirsPASaya.php?nip='.$user['nip'], ]); } private function renderPage(string $view, array $data): View { $data['sidebar'] = DosenNavigation::build($data['routeName']); $data['pageDate'] = $this->formatDateTime(now()->toDateTimeString()); return view($view, $data); } private function dosenUser(Request $request): array { $auth = $request->session()->get('legacy_auth'); abort_unless(($auth['role'] ?? null) === 'dosen', 403); return $auth['user']; } private function formatDateTime(?string $value): string { if (! $value || strtotime($value) === false) { return '-'; } return Carbon::parse($value)->locale('id')->translatedFormat('j F Y, H:i'); } private function statusPenawaran($status): string { return match ((string) $status) { '0' => 'Belum Diproses', '1' => 'Diterima', '2' => 'Belum Diambil', default => 'Belum Diambil', }; } private function statusUsulan($status): string { return match ((string) $status) { '1' => 'Judul Diterima', '2' => 'Judul Ditolak', '3' => 'Judul Gugur', default => 'Dalam Proses', }; } private function latestPenawaranBooking(int $idPenawaran, string $idDosen): ?object { return DB::table('tb_ambil_judul as taj') ->join('tb_penawaran_judul as tpj', 'tpj.idPenawaran', '=', 'taj.idPenawaranAmbil') ->select(['taj.idAmbil', 'taj.statusPengambilan']) ->where('taj.idPenawaranAmbil', $idPenawaran) ->where('tpj.idDosen', $idDosen) ->orderByDesc('taj.waktuPengambilan') ->first(); } private function resolveAngkatan(?string $nim, $thnMasuk): ?int { $thnMasuk = is_numeric($thnMasuk) ? (int) $thnMasuk : null; if ($thnMasuk && $thnMasuk > 2000 && $thnMasuk <= ((int) date('Y') + 1)) { return $thnMasuk; } $nim = (string) $nim; if (strlen($nim) >= 7 && preg_match('/^(?:[A-Z])(\d{6,})/i', $nim, $matches)) { $digits = $matches[1]; $angkatan2Digit = substr($digits, 4, 2); if (ctype_digit($angkatan2Digit)) { $year = 2000 + (int) $angkatan2Digit; if ($year <= ((int) date('Y') + 1)) { return $year; } } } return null; } private function legacyUrl(string $path): string { $baseUrl = rtrim((string) env('LEGACY_BASE_URL', 'http://127.0.0.1:8080'), '/'); return $baseUrl.'/'.ltrim($path, '/'); } }