Point Docker and Coolify compose to the Laravel rebuild app so mahasiswa, dosen, and admin flows are served from the new Laravel public entrypoint.
468 lines
18 KiB
PHP
468 lines
18 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\View\View;
|
|
|
|
class MahasiswaPageController extends Controller
|
|
{
|
|
public function statusUsulan(Request $request): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$outline = DB::table('tbpraoutline as tp')
|
|
->leftJoin('tbrekaphasil as trh', 'tp.id', '=', 'trh.idpraoutline')
|
|
->leftJoin('tb_kelompok_keahlian as kk', 'tp.kelompokKeahlian', '=', 'kk.idKK')
|
|
->select([
|
|
'tp.id',
|
|
'tp.judul',
|
|
'tp.deskripsi',
|
|
'tp.status_usulan',
|
|
'tp.tgl_upload',
|
|
'tp.wkt_upload',
|
|
'trh.ket',
|
|
'kk.namaKK as kelompok_keahlian',
|
|
])
|
|
->where('tp.nim', $user['nim'])
|
|
->where('tp.idProdi', $user['prodi'])
|
|
->orderByDesc('tp.id')
|
|
->first();
|
|
|
|
$reviews = [];
|
|
|
|
if ($outline) {
|
|
$reviews = DB::table('tbreview as tr')
|
|
->leftJoin('tbdosen as td', 'tr.reviewer', '=', 'td.nip')
|
|
->leftJoin('tbmhs as tm', 'tr.reviewer', '=', 'tm.nim')
|
|
->select([
|
|
'tr.review_text',
|
|
'tr.jenis_review',
|
|
'tr.putusan',
|
|
'tr.tgl',
|
|
'tr.wkt',
|
|
'tr.reviewer',
|
|
'td.nmLengkap as reviewer_name',
|
|
'tm.nmLengkap as mahasiswa_name',
|
|
])
|
|
->where('tr.idpraoutline', $outline->id)
|
|
->orderBy('tr.tgl')
|
|
->orderBy('tr.wkt')
|
|
->get()
|
|
->map(function ($item) {
|
|
$timestamp = trim(($item->tgl ?? '').' '.($item->wkt ?? ''));
|
|
|
|
return [
|
|
'author' => $item->reviewer_name ?: ($item->mahasiswa_name ?: $item->reviewer),
|
|
'role' => $item->reviewer_name ? 'Dosen' : 'Mahasiswa',
|
|
'timestamp' => $this->formatDateTime($timestamp),
|
|
'type' => $item->jenis_review === 'P' ? 'Putusan' : 'Review',
|
|
'decision' => $this->decisionLabel($item->putusan),
|
|
'body' => $item->review_text ?: '<p class="text-[#6B7280]">Tidak ada isi review.</p>',
|
|
];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
return view('mahasiswa.pages.status-usulan', [
|
|
'title' => 'Status Usulan Mahasiswa | SPOTA Rebuild',
|
|
'pageTitle' => 'Status Usulan',
|
|
'pageDescription' => 'Ringkasan draft praoutline terakhir, status keputusan, dan riwayat review dosen untuk mahasiswa yang sedang login.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.status-usulan'),
|
|
'user' => $user,
|
|
'outline' => $outline ? [
|
|
'id' => $outline->id,
|
|
'judul' => $outline->judul,
|
|
'deskripsi' => $outline->deskripsi,
|
|
'status' => $this->statusLabel($outline->status_usulan),
|
|
'statusClass' => $this->statusBadgeClass($outline->status_usulan),
|
|
'tanggal' => $this->formatDateTime(trim(($outline->tgl_upload ?? '').' '.($outline->wkt_upload ?? ''))),
|
|
'catatan' => $outline->ket,
|
|
'kelompokKeahlian' => $outline->kelompok_keahlian ?: '-',
|
|
] : null,
|
|
'reviews' => $reviews,
|
|
'pageActions' => $this->statusPageActions($outline),
|
|
]);
|
|
}
|
|
|
|
public function uploadPraoutline(Request $request): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$hasActiveDraft = DB::table('tbpraoutline')
|
|
->where('nim', $user['nim'])
|
|
->whereIn('status_usulan', ['0', '1'])
|
|
->exists();
|
|
|
|
$dosen = DB::table('tbdosen')
|
|
->select(['nmLengkap'])
|
|
->where('idProdi', $user['prodi'])
|
|
->where('status', 'A')
|
|
->orderBy('nmLengkap')
|
|
->get();
|
|
|
|
$kelompokKeahlian = DB::table('tb_kelompok_keahlian')
|
|
->select(['idKK', 'namaKK'])
|
|
->orderBy('namaKK')
|
|
->get();
|
|
|
|
return view('mahasiswa.pages.upload-praoutline', [
|
|
'title' => 'Ajukan Praoutline | SPOTA Rebuild',
|
|
'pageTitle' => 'Ajukan Outline Baru',
|
|
'pageDescription' => 'Form pengajuan draft praoutline mengikuti field utama pada SPOTA lama. File PDF tetap divalidasi sebelum disimpan.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.praoutline.upload'),
|
|
'user' => $user,
|
|
'hasActiveDraft' => $hasActiveDraft,
|
|
'dosen' => $dosen,
|
|
'kelompokKeahlian' => $kelompokKeahlian,
|
|
]);
|
|
}
|
|
|
|
public function storePraoutline(Request $request): RedirectResponse
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$hasActiveDraft = DB::table('tbpraoutline')
|
|
->where('nim', $user['nim'])
|
|
->whereIn('status_usulan', ['0', '1'])
|
|
->exists();
|
|
|
|
if ($hasActiveDraft) {
|
|
return back()->with('error', 'Draft praoutline aktif masih ada. Silakan lihat status usulan/review terlebih dahulu.');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'judul' => ['required', 'string', 'max:255'],
|
|
'deskripsi' => ['nullable', 'string'],
|
|
'berkas' => ['required', 'file', 'mimes:pdf', 'max:10240'],
|
|
'dosenpa' => ['required', 'string', 'max:255'],
|
|
'pilpemb1' => ['nullable', 'string', 'max:255'],
|
|
'pilpemb2' => ['nullable', 'string', 'max:255'],
|
|
'pilpemb3' => ['nullable', 'string', 'max:255'],
|
|
'pilpemb4' => ['nullable', 'string', 'max:255'],
|
|
'drekomjudul' => ['nullable', 'string', 'max:255'],
|
|
'kelompokKeahlian' => ['required', 'integer'],
|
|
]);
|
|
|
|
$file = $request->file('berkas');
|
|
$filename = $user['nim'].'-'.time().'.'.$file->getClientOriginalExtension();
|
|
$file->move(base_path('../files'), $filename);
|
|
|
|
DB::table('tbpraoutline')->insert([
|
|
'nim' => $user['nim'],
|
|
'judul' => $validated['judul'],
|
|
'deskripsi' => $validated['deskripsi'] ?? '',
|
|
'berkas' => $filename,
|
|
'idProdi' => $user['prodi'],
|
|
'tgl_upload' => Carbon::now()->toDateString(),
|
|
'wkt_upload' => Carbon::now()->format('H:i:s'),
|
|
'semester' => $this->currentSemester(),
|
|
'thn_ajaran' => $this->currentAcademicYear(),
|
|
'status_usulan' => '0',
|
|
'ket' => '',
|
|
'kelompokKeahlian' => (string) $validated['kelompokKeahlian'],
|
|
'kkTerkait' => '',
|
|
]);
|
|
|
|
return redirect()->route('mahasiswa.status-usulan')->with('success', 'Draft praoutline berhasil diajukan.');
|
|
}
|
|
|
|
public function pengumuman(Request $request): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$pengumuman = DB::table('tbpengumuman')
|
|
->select(['id', 'judul', 'isi', 'tgl'])
|
|
->where('idProdi', $user['prodi'])
|
|
->whereIn('tujuan', ['A', 'M'])
|
|
->orderByDesc('id')
|
|
->limit(25)
|
|
->get()
|
|
->map(function ($item) {
|
|
return [
|
|
'id' => $item->id,
|
|
'judul' => $item->judul,
|
|
'preview' => str($item->isi)->stripTags()->squish()->limit(180)->toString(),
|
|
'tgl' => $item->tgl,
|
|
'detailHref' => route('mahasiswa.pengumuman.show', ['id' => $item->id], false),
|
|
];
|
|
})
|
|
->all();
|
|
|
|
return view('mahasiswa.pages.pengumuman', [
|
|
'title' => 'Pengumuman Mahasiswa | SPOTA Rebuild',
|
|
'pageTitle' => 'Pengumuman',
|
|
'pageDescription' => 'Daftar pengumuman program studi yang ditujukan untuk mahasiswa pada data SPOTA aktif.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.pengumuman.index'),
|
|
'user' => $user,
|
|
'pengumuman' => $pengumuman,
|
|
]);
|
|
}
|
|
|
|
public function showPengumuman(Request $request, int $id): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$pengumuman = DB::table('tbpengumuman')
|
|
->select(['id', 'judul', 'isi', 'tgl'])
|
|
->where('id', $id)
|
|
->where('idProdi', $user['prodi'])
|
|
->whereIn('tujuan', ['A', 'M'])
|
|
->first();
|
|
|
|
abort_unless($pengumuman, 404);
|
|
|
|
return view('mahasiswa.pages.pengumuman-detail', [
|
|
'title' => 'Detail Pengumuman Mahasiswa | SPOTA Rebuild',
|
|
'pageTitle' => 'Detail Pengumuman',
|
|
'pageDescription' => 'Isi pengumuman mahasiswa dari basis data SPOTA aktif.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.pengumuman.index'),
|
|
'user' => $user,
|
|
'pageActions' => [
|
|
['label' => 'Kembali ke Pengumuman', 'href' => route('mahasiswa.pengumuman.index', [], false)],
|
|
],
|
|
'pengumuman' => [
|
|
'judul' => $pengumuman->judul,
|
|
'isi' => $pengumuman->isi,
|
|
'tgl' => $this->formatDateTime($pengumuman->tgl),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function penawaran(Request $request): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
$status = $request->query('status', '0');
|
|
$kk = $request->query('kk', 'Semua');
|
|
|
|
$query = DB::table('tb_penawaran_judul as tpj')
|
|
->leftJoin('tbdosen as td', 'tpj.idDosen', '=', 'td.iddosen')
|
|
->leftJoin('tb_kelompok_keahlian as kk', 'tpj.kk', '=', 'kk.idKK')
|
|
->leftJoin('tb_ambil_judul as taj', function ($join) {
|
|
$join->on('tpj.idPenawaran', '=', 'taj.idPenawaranAmbil')
|
|
->whereRaw('taj.idAmbil = (select max(taj2.idAmbil) from tb_ambil_judul taj2 where taj2.idPenawaranAmbil = tpj.idPenawaran)');
|
|
})
|
|
->leftJoin('tbmhs as tm', 'taj.idMhs', '=', 'tm.idmhs')
|
|
->select([
|
|
'tpj.idPenawaran',
|
|
'tpj.judul',
|
|
'tpj.deskripsi',
|
|
'tpj.waktuInput',
|
|
'td.nmLengkap as dosen',
|
|
'kk.namaKK as kk',
|
|
'taj.statusPengambilan',
|
|
'tm.nmLengkap as diambil_oleh',
|
|
])
|
|
->orderByDesc('tpj.idPenawaran');
|
|
|
|
if ($kk !== 'Semua') {
|
|
$query->where('tpj.kk', $kk);
|
|
}
|
|
|
|
if ($status === '0') {
|
|
$query->where(function ($query) {
|
|
$query->whereNull('taj.idAmbil')->orWhere('taj.statusPengambilan', '2');
|
|
});
|
|
} elseif ($status === '1') {
|
|
$query->whereNotNull('taj.idAmbil')->where('taj.statusPengambilan', '!=', '2');
|
|
}
|
|
|
|
$penawaran = $query->paginate(15)->withQueryString();
|
|
$kelompokKeahlian = DB::table('tb_kelompok_keahlian')
|
|
->select(['idKK', 'namaKK'])
|
|
->where('idKK', '!=', '8')
|
|
->orderBy('namaKK')
|
|
->get();
|
|
|
|
return view('mahasiswa.pages.penawaran', [
|
|
'title' => 'Penawaran Judul Mahasiswa | SPOTA Rebuild',
|
|
'pageTitle' => 'Penawaran Judul',
|
|
'pageDescription' => 'Daftar judul yang ditawarkan dosen. Mahasiswa dapat melihat detail dan booking judul yang masih tersedia.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.penawaran.index'),
|
|
'user' => $user,
|
|
'penawaran' => $penawaran,
|
|
'kelompokKeahlian' => $kelompokKeahlian,
|
|
'filters' => ['status' => $status, 'kk' => $kk],
|
|
]);
|
|
}
|
|
|
|
public function showPenawaran(Request $request, int $id): View
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$penawaran = DB::table('tb_penawaran_judul as tpj')
|
|
->leftJoin('tbdosen as td', 'tpj.idDosen', '=', 'td.iddosen')
|
|
->leftJoin('tb_kelompok_keahlian as kk', 'tpj.kk', '=', 'kk.idKK')
|
|
->select([
|
|
'tpj.idPenawaran',
|
|
'tpj.judul',
|
|
'tpj.deskripsi',
|
|
'tpj.waktuInput',
|
|
'td.nmLengkap as dosen',
|
|
'kk.namaKK as kk',
|
|
])
|
|
->where('tpj.idPenawaran', $id)
|
|
->first();
|
|
|
|
abort_unless($penawaran, 404);
|
|
|
|
return view('mahasiswa.pages.penawaran-detail', [
|
|
'title' => 'Detail Penawaran Judul | SPOTA Rebuild',
|
|
'pageTitle' => 'Detail Penawaran Judul',
|
|
'pageDescription' => 'Detail judul yang ditawarkan dosen dan tombol booking apabila mahasiswa ingin mengambil judul ini.',
|
|
'pageDate' => Carbon::now()->locale('id')->translatedFormat('j F Y, H:i'),
|
|
'sidebar' => $this->buildSidebar('mahasiswa.penawaran.index'),
|
|
'user' => $user,
|
|
'pageActions' => [
|
|
['label' => 'Kembali ke Penawaran', 'href' => route('mahasiswa.penawaran.index', [], false)],
|
|
],
|
|
'penawaran' => $penawaran,
|
|
]);
|
|
}
|
|
|
|
public function bookPenawaran(Request $request, int $id): RedirectResponse
|
|
{
|
|
$user = $this->getMahasiswaUser($request);
|
|
|
|
$latestStudentBooking = DB::table('tb_ambil_judul')
|
|
->where('idMhs', $user['id'])
|
|
->orderByDesc('waktuPengambilan')
|
|
->first();
|
|
|
|
if ($latestStudentBooking?->statusPengambilan === '0') {
|
|
return back()->with('error', 'Tidak dapat booking judul ini karena masih ada booking lain yang menunggu verifikasi dosen.');
|
|
}
|
|
|
|
if ($latestStudentBooking?->statusPengambilan === '1' && strtotime($latestStudentBooking->waktuPengambilan) >= strtotime('-30 days')) {
|
|
return back()->with('error', 'Tidak dapat booking judul ini karena booking sebelumnya sudah disetujui dosen.');
|
|
}
|
|
|
|
$latestTitleBooking = DB::table('tb_ambil_judul')
|
|
->where('idPenawaranAmbil', $id)
|
|
->orderByDesc('waktuPengambilan')
|
|
->first();
|
|
|
|
if ($latestTitleBooking && $latestTitleBooking->statusPengambilan !== '2') {
|
|
return back()->with('error', 'Judul ini sudah dibooking mahasiswa lain.');
|
|
}
|
|
|
|
DB::table('tb_ambil_judul')->insert([
|
|
'idPenawaranAmbil' => $id,
|
|
'idMhs' => $user['id'],
|
|
'statusPengambilan' => '0',
|
|
'waktuPengambilan' => Carbon::now()->toDateTimeString(),
|
|
]);
|
|
|
|
return redirect()->route('mahasiswa.penawaran.index')->with('success', 'Berhasil membooking judul ini. Tunggu verifikasi dari dosen penawar.');
|
|
}
|
|
|
|
private function getMahasiswaUser(Request $request): array
|
|
{
|
|
$auth = $request->session()->get('legacy_auth');
|
|
|
|
abort_unless(($auth['role'] ?? null) === 'mahasiswa', 403);
|
|
|
|
return $auth['user'];
|
|
}
|
|
|
|
private function buildSidebar(string $activeRoute): array
|
|
{
|
|
return [
|
|
'main' => [
|
|
['title' => 'Dashboard', 'href' => route('dashboard.mahasiswa'), 'icon' => 'home', 'active' => $activeRoute === 'dashboard.mahasiswa'],
|
|
],
|
|
'sections' => [
|
|
[
|
|
'title' => 'Praoutline',
|
|
'icon' => 'folder',
|
|
'items' => [
|
|
['title' => 'Status Usulan', 'href' => route('mahasiswa.status-usulan', [], false), 'icon' => 'chart', 'active' => $activeRoute === 'mahasiswa.status-usulan'],
|
|
['title' => 'Ajukan Outline Baru', 'href' => route('mahasiswa.praoutline.upload', [], false), 'icon' => 'clipboard', 'active' => $activeRoute === 'mahasiswa.praoutline.upload'],
|
|
['title' => 'Penawaran Judul', 'href' => route('mahasiswa.penawaran.index', [], false), 'icon' => 'briefcase', 'active' => $activeRoute === 'mahasiswa.penawaran.index'],
|
|
],
|
|
],
|
|
[
|
|
'title' => 'Informasi',
|
|
'icon' => 'bell',
|
|
'items' => [
|
|
['title' => 'Pengumuman', 'href' => route('mahasiswa.pengumuman.index', [], false), 'icon' => 'megaphone', 'active' => $activeRoute === 'mahasiswa.pengumuman.index'],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
private function formatDateTime(?string $value): string
|
|
{
|
|
if (! $value) {
|
|
return '-';
|
|
}
|
|
|
|
return Carbon::parse($value)->locale('id')->translatedFormat('j F Y, H:i');
|
|
}
|
|
|
|
private function statusLabel(?string $status): string
|
|
{
|
|
return match ($status) {
|
|
'0' => 'Dalam Review',
|
|
'1' => 'Disetujui',
|
|
'2' => 'Ditolak',
|
|
default => 'Belum Ada Draft',
|
|
};
|
|
}
|
|
|
|
private function statusBadgeClass(?string $status): string
|
|
{
|
|
return match ($status) {
|
|
'0' => 'bg-sky-100 text-sky-800',
|
|
'1' => 'bg-emerald-100 text-emerald-800',
|
|
'2' => 'bg-rose-100 text-rose-800',
|
|
default => 'bg-slate-100 text-slate-700',
|
|
};
|
|
}
|
|
|
|
private function decisionLabel(?string $status): ?string
|
|
{
|
|
return match ($status) {
|
|
'1' => 'Setuju',
|
|
'0' => 'Tidak Setuju',
|
|
'2' => 'Tolak',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function statusPageActions(?object $outline): array
|
|
{
|
|
$actions = [
|
|
['label' => 'Ajukan Outline Baru', 'href' => route('mahasiswa.praoutline.upload', [], false), 'variant' => ! $outline || $outline->status_usulan === '2' ? 'dark' : 'light'],
|
|
['label' => 'Lihat Penawaran Judul', 'href' => route('mahasiswa.penawaran.index', [], false)],
|
|
];
|
|
|
|
return $actions;
|
|
}
|
|
|
|
private function currentSemester(): string
|
|
{
|
|
return (int) date('n') >= 8 || (int) date('n') <= 1 ? 'Ganjil' : 'Genap';
|
|
}
|
|
|
|
private function currentAcademicYear(): string
|
|
{
|
|
$year = (int) date('Y');
|
|
|
|
if ((int) date('n') >= 8) {
|
|
return $year.'/'.($year + 1);
|
|
}
|
|
|
|
return ($year - 1).'/'.$year;
|
|
}
|
|
}
|