Point Docker and Coolify compose to the Laravel rebuild app so mahasiswa, dosen, and admin flows are served from the new Laravel public entrypoint.
963 lines
37 KiB
PHP
963 lines
37 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Support\DosenNavigation;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\View\View;
|
|
|
|
class DosenPageController extends Controller
|
|
{
|
|
public function penawaran(Request $request): View
|
|
{
|
|
$user = $this->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, '/');
|
|
}
|
|
}
|