again n again
This commit is contained in:
156
app/api/mahasiswa/mkbelumdiambil/route.ts
Normal file
156
app/api/mahasiswa/mkbelumdiambil/route.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import supabase from '@/lib/db';
|
||||
|
||||
interface MKBelumDiambilData {
|
||||
tahun_angkatan: number;
|
||||
jenis_mk: string;
|
||||
jumlah_belum_diambil: number;
|
||||
total_mata_kuliah: number;
|
||||
total_mahasiswa: number;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const tahunAngkatan = searchParams.get('tahun_angkatan');
|
||||
|
||||
// 1. Ambil semua mata kuliah
|
||||
const { data: mataKuliah, error: errMK } = await supabase
|
||||
.from('mata_kuliah')
|
||||
.select('id_mk, jenis_mk');
|
||||
|
||||
if (errMK) {
|
||||
console.error('Error fetching mata kuliah:', errMK);
|
||||
throw errMK;
|
||||
}
|
||||
|
||||
// 2. Hitung total mata kuliah per kategori (total_mk CTE)
|
||||
const totalMKPerJenis = new Map<string, number>();
|
||||
mataKuliah?.forEach((mk) => {
|
||||
const jenis = mk.jenis_mk;
|
||||
totalMKPerJenis.set(jenis, (totalMKPerJenis.get(jenis) || 0) + 1);
|
||||
});
|
||||
|
||||
// 3. Ambil data mahasiswa
|
||||
let mahasiswaQuery = supabase
|
||||
.from('mahasiswa')
|
||||
.select('id_mahasiswa, tahun_angkatan');
|
||||
|
||||
// Jika ada filter tahun angkatan, terapkan filter
|
||||
if (tahunAngkatan && tahunAngkatan !== 'all') {
|
||||
mahasiswaQuery = mahasiswaQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
|
||||
}
|
||||
|
||||
const { data: mahasiswa, error: errMhs } = await mahasiswaQuery;
|
||||
|
||||
if (errMhs) {
|
||||
console.error('Error fetching mahasiswa:', errMhs);
|
||||
throw errMhs;
|
||||
}
|
||||
|
||||
// 4. Hitung total mahasiswa per angkatan (total_mhs CTE)
|
||||
const totalMhsPerAngkatan = new Map<number, number>();
|
||||
mahasiswa?.forEach((m) => {
|
||||
const tahun = m.tahun_angkatan;
|
||||
if (tahun) {
|
||||
totalMhsPerAngkatan.set(tahun, (totalMhsPerAngkatan.get(tahun) || 0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Ambil semua nilai mahasiswa
|
||||
const { data: nilaiMahasiswa, error: errNilai } = await supabase
|
||||
.from('nilai_mahasiswa')
|
||||
.select('id_mahasiswa, id_mk, nilai_huruf');
|
||||
|
||||
if (errNilai) {
|
||||
console.error('Error fetching nilai mahasiswa:', errNilai);
|
||||
throw errNilai;
|
||||
}
|
||||
|
||||
// 6. Buat Map untuk cek apakah mahasiswa sudah mengambil MK (dengan nilai != E)
|
||||
// Key: "id_mahasiswa-id_mk", Value: true jika sudah diambil dan nilai != E
|
||||
const sudahAmbilMap = new Map<string, boolean>();
|
||||
nilaiMahasiswa?.forEach((nilai) => {
|
||||
// Hanya dianggap sudah ambil jika nilai != E
|
||||
if (nilai.nilai_huruf !== 'E') {
|
||||
const key = `${nilai.id_mahasiswa}-${nilai.id_mk}`;
|
||||
sudahAmbilMap.set(key, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 7. Hitung mata kuliah belum diambil per angkatan per jenis MK (belum_ambil CTE)
|
||||
// Map dengan key yang unik menggunakan object
|
||||
const belumAmbilMap = new Map<string, { tahun: number; jenis: string; jumlah: number }>();
|
||||
|
||||
// CROSS JOIN mahasiswa dengan mata_kuliah
|
||||
mahasiswa?.forEach((mhs) => {
|
||||
if (!mhs.tahun_angkatan) return;
|
||||
|
||||
mataKuliah?.forEach((mk) => {
|
||||
const key = `${mhs.id_mahasiswa}-${mk.id_mk}`;
|
||||
|
||||
// WHERE NOT EXISTS: jika tidak ada di sudahAmbilMap, berarti belum diambil
|
||||
if (!sudahAmbilMap.has(key)) {
|
||||
// Gunakan separator yang aman (||) untuk menghindari konflik dengan nama jenis_mk
|
||||
const groupKey = `${mhs.tahun_angkatan}||${mk.jenis_mk}`;
|
||||
const existing = belumAmbilMap.get(groupKey);
|
||||
|
||||
if (existing) {
|
||||
existing.jumlah += 1;
|
||||
} else {
|
||||
belumAmbilMap.set(groupKey, {
|
||||
tahun: mhs.tahun_angkatan,
|
||||
jenis: mk.jenis_mk,
|
||||
jumlah: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 8. Gabungkan semua data (JOIN)
|
||||
const results: MKBelumDiambilData[] = [];
|
||||
|
||||
belumAmbilMap.forEach((value) => {
|
||||
const tahun_angkatan = value.tahun;
|
||||
const jenis_mk = value.jenis;
|
||||
const jumlah_belum_diambil = value.jumlah;
|
||||
|
||||
const total_mata_kuliah = totalMKPerJenis.get(jenis_mk) || 0;
|
||||
const total_mahasiswa = totalMhsPerAngkatan.get(tahun_angkatan) || 0;
|
||||
|
||||
results.push({
|
||||
tahun_angkatan,
|
||||
jenis_mk,
|
||||
jumlah_belum_diambil,
|
||||
total_mata_kuliah,
|
||||
total_mahasiswa,
|
||||
});
|
||||
});
|
||||
|
||||
// 9. Urutkan berdasarkan tahun_angkatan dan jenis_mk
|
||||
results.sort((a, b) => {
|
||||
if (a.tahun_angkatan !== b.tahun_angkatan) {
|
||||
return a.tahun_angkatan - b.tahun_angkatan;
|
||||
}
|
||||
// Urutkan jenis MK: Wajib, Pilihan Wajib, Pilihan
|
||||
const jenisOrder = ['Wajib', 'Pilihan Wajib', 'Pilihan'];
|
||||
return jenisOrder.indexOf(a.jenis_mk) - jenisOrder.indexOf(b.jenis_mk);
|
||||
});
|
||||
|
||||
return NextResponse.json(results, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in mkbelumdiambil API:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal Server Error', message: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +122,11 @@ export async function GET() {
|
||||
// Di SQL, jika nilai NULL, kondisi akan menghasilkan NULL (dianggap FALSE dalam CASE WHEN)
|
||||
let isDO = 0;
|
||||
|
||||
// Evaluasi 4 semester: semester BETWEEN 4 AND 7 AND (sks_total < 40 OR ipk <= 2.50)
|
||||
// Evaluasi 4 semester: semester BETWEEN 3 AND 6 AND (sks_total < 40 OR ipk <= 2.50)
|
||||
// Jika sksTotal NULL (tidak ada nilai), kondisi sksTotal < 40 akan NULL (FALSE)
|
||||
if (
|
||||
sem >= 4 &&
|
||||
sem <= 7 &&
|
||||
sem >= 3 &&
|
||||
sem <= 6 &&
|
||||
sksTotal !== null && // Pastikan ada data nilai
|
||||
(
|
||||
sksTotal < 40 ||
|
||||
@@ -135,10 +135,10 @@ export async function GET() {
|
||||
) {
|
||||
isDO = 1;
|
||||
}
|
||||
// Evaluasi 8 semester: semester BETWEEN 8 AND 13 AND (sks_total < 80 OR ipk <= 2.50)
|
||||
// Evaluasi 8 semester: semester BETWEEN 7 AND 11 AND (sks_total < 80 OR ipk <= 2.50)
|
||||
else if (
|
||||
sem >= 8 &&
|
||||
sem <= 13 &&
|
||||
sem >= 7 &&
|
||||
sem <= 11 &&
|
||||
sksTotal !== null && // Pastikan ada data nilai
|
||||
(
|
||||
sksTotal < 80 ||
|
||||
@@ -147,10 +147,10 @@ export async function GET() {
|
||||
) {
|
||||
isDO = 1;
|
||||
}
|
||||
// Evaluasi akhir masa studi: semester = 14 AND (sks_total < 144 OR ipk <= 2.00 OR jumlah_e > 0 OR sks_d > 14 OR min_wajib < 2.00 OR lulus_ta1 = 0 OR lulus_ta2 = 0)
|
||||
// Evaluasi akhir masa studi: semester = 12 AND (sks_total < 144 OR ipk <= 2.00 OR jumlah_e > 0 OR sks_d > 14 OR min_wajib < 2.00 OR lulus_ta1 = 0 OR lulus_ta2 = 0)
|
||||
// Di SQL: lulus_ta1 = 0 akan TRUE jika lulus_ta1 adalah 0, FALSE jika NULL atau 1
|
||||
else if (
|
||||
sem === 14 &&
|
||||
sem === 12 &&
|
||||
sksTotal !== null && // Pastikan ada data nilai
|
||||
(
|
||||
sksTotal < 144 ||
|
||||
|
||||
154
app/api/tabeldetail/mk-belum-diambil/route.ts
Normal file
154
app/api/tabeldetail/mk-belum-diambil/route.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import supabase from '@/lib/db';
|
||||
|
||||
interface MataKuliahBelumDiambil {
|
||||
nama_mk: string;
|
||||
jenis_mk: string;
|
||||
kode_mk: string;
|
||||
sks: number;
|
||||
semester: number;
|
||||
}
|
||||
|
||||
interface MahasiswaMKBelumDiambil {
|
||||
nim: string;
|
||||
nama: string;
|
||||
tahun_angkatan: number;
|
||||
sks_total: number;
|
||||
mk_belum_diambil: MataKuliahBelumDiambil[];
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const tahunAngkatan = searchParams.get('tahun_angkatan');
|
||||
const jenisMK = searchParams.get('jenis_mk');
|
||||
|
||||
// 1. Ambil data mahasiswa
|
||||
let mahasiswaQuery = supabase
|
||||
.from('mahasiswa')
|
||||
.select('id_mahasiswa, nim, nama, tahun_angkatan')
|
||||
.order('tahun_angkatan', { ascending: true })
|
||||
.order('nim', { ascending: true });
|
||||
|
||||
// Jika ada filter tahun angkatan, terapkan filter
|
||||
if (tahunAngkatan && tahunAngkatan !== 'all') {
|
||||
mahasiswaQuery = mahasiswaQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
|
||||
}
|
||||
|
||||
const { data: mahasiswa, error: errMhs } = await mahasiswaQuery;
|
||||
|
||||
if (errMhs) {
|
||||
console.error('Error fetching mahasiswa:', errMhs);
|
||||
throw errMhs;
|
||||
}
|
||||
|
||||
// 2. Ambil semua mata kuliah
|
||||
let mkQuery = supabase
|
||||
.from('mata_kuliah')
|
||||
.select('id_mk, kode_mk, nama_mk, jenis_mk, sks, semester')
|
||||
.order('semester', { ascending: true })
|
||||
.order('nama_mk', { ascending: true });
|
||||
|
||||
// Filter jenis MK jika ada
|
||||
if (jenisMK && jenisMK !== 'all') {
|
||||
mkQuery = mkQuery.eq('jenis_mk', jenisMK);
|
||||
}
|
||||
|
||||
const { data: mataKuliah, error: errMK } = await mkQuery;
|
||||
|
||||
if (errMK) {
|
||||
console.error('Error fetching mata kuliah:', errMK);
|
||||
throw errMK;
|
||||
}
|
||||
|
||||
// 3. Ambil semua nilai mahasiswa (hanya yang nilai != E, karena E dianggap belum lulus)
|
||||
const { data: nilaiMahasiswa, error: errNilai } = await supabase
|
||||
.from('nilai_mahasiswa')
|
||||
.select('id_mahasiswa, id_mk, nilai_huruf');
|
||||
|
||||
if (errNilai) {
|
||||
console.error('Error fetching nilai mahasiswa:', errNilai);
|
||||
throw errNilai;
|
||||
}
|
||||
|
||||
// 4. Buat Map untuk cek MK yang sudah diambil (nilai != E)
|
||||
// Key: "id_mahasiswa-id_mk", Value: true
|
||||
const sudahAmbilMap = new Map<string, boolean>();
|
||||
nilaiMahasiswa?.forEach((nilai) => {
|
||||
// Hanya dianggap sudah ambil jika nilai != E
|
||||
if (nilai.nilai_huruf !== 'E') {
|
||||
const key = `${nilai.id_mahasiswa}-${nilai.id_mk}`;
|
||||
sudahAmbilMap.set(key, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Hitung SKS total per mahasiswa (dari MK yang sudah lulus, nilai != E)
|
||||
const sksTotalMap = new Map<number, number>();
|
||||
nilaiMahasiswa?.forEach((nilai) => {
|
||||
if (nilai.nilai_huruf !== 'E') {
|
||||
const mk = mataKuliah?.find(m => m.id_mk === nilai.id_mk);
|
||||
if (mk) {
|
||||
const currentSKS = sksTotalMap.get(nilai.id_mahasiswa) || 0;
|
||||
sksTotalMap.set(nilai.id_mahasiswa, currentSKS + mk.sks);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Proses data mahasiswa dan MK belum diambil
|
||||
const results: MahasiswaMKBelumDiambil[] = [];
|
||||
|
||||
mahasiswa?.forEach((mhs) => {
|
||||
const mkBelumDiambil: MataKuliahBelumDiambil[] = [];
|
||||
|
||||
// CROSS JOIN: cek setiap MK untuk mahasiswa ini
|
||||
mataKuliah?.forEach((mk) => {
|
||||
const key = `${mhs.id_mahasiswa}-${mk.id_mk}`;
|
||||
|
||||
// WHERE nm.id_mk IS NULL: jika tidak ada di sudahAmbilMap, berarti belum diambil
|
||||
if (!sudahAmbilMap.has(key)) {
|
||||
mkBelumDiambil.push({
|
||||
nama_mk: mk.nama_mk,
|
||||
jenis_mk: mk.jenis_mk,
|
||||
kode_mk: mk.kode_mk,
|
||||
sks: mk.sks,
|
||||
semester: mk.semester
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Hanya tambahkan jika ada MK yang belum diambil
|
||||
if (mkBelumDiambil.length > 0) {
|
||||
results.push({
|
||||
nim: mhs.nim,
|
||||
nama: mhs.nama,
|
||||
tahun_angkatan: mhs.tahun_angkatan,
|
||||
sks_total: sksTotalMap.get(mhs.id_mahasiswa) || 0,
|
||||
mk_belum_diambil: mkBelumDiambil
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 7. Urutkan berdasarkan tahun_angkatan dan nim
|
||||
results.sort((a, b) => {
|
||||
if (a.tahun_angkatan !== b.tahun_angkatan) {
|
||||
return a.tahun_angkatan - b.tahun_angkatan;
|
||||
}
|
||||
return a.nim.localeCompare(b.nim);
|
||||
});
|
||||
|
||||
return NextResponse.json(results, {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in mk-belum-diambil API:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal Server Error', message: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,10 @@ export async function GET(request: NextRequest) {
|
||||
const alasanList: string[] = [];
|
||||
let isDO = false;
|
||||
|
||||
// Evaluasi 4 semester: semester BETWEEN 4 AND 7 AND (sks_total < 40 OR ipk <= 2.50)
|
||||
// Evaluasi 4 semester: semester BETWEEN 3 AND 6 AND (sks_total < 40 OR ipk <= 2.50)
|
||||
if (
|
||||
sem >= 4 &&
|
||||
sem <= 7 &&
|
||||
sem >= 3 &&
|
||||
sem <= 6 &&
|
||||
sksTotal !== null &&
|
||||
(
|
||||
sksTotal < 40 ||
|
||||
@@ -144,16 +144,16 @@ export async function GET(request: NextRequest) {
|
||||
) {
|
||||
isDO = true;
|
||||
if (sksTotal < 40) {
|
||||
alasanList.push('SKS kurang dari 40 pada evaluasi semester 4');
|
||||
alasanList.push('SKS kurang dari 40 pada evaluasi semester 3');
|
||||
}
|
||||
if (ipk !== null && ipk <= 2.50) {
|
||||
alasanList.push('IPK kurang dari 2.50 pada evaluasi semester 4');
|
||||
alasanList.push('IPK kurang dari 2.50 pada evaluasi semester 3');
|
||||
}
|
||||
}
|
||||
// Evaluasi 8 semester: semester BETWEEN 8 AND 13 AND (sks_total < 80 OR ipk <= 2.50)
|
||||
// Evaluasi 8 semester: semester BETWEEN 7 AND 11 AND (sks_total < 80 OR ipk <= 2.50)
|
||||
else if (
|
||||
sem >= 8 &&
|
||||
sem <= 13 &&
|
||||
sem >= 7 &&
|
||||
sem <= 11 &&
|
||||
sksTotal !== null &&
|
||||
(
|
||||
sksTotal < 80 ||
|
||||
@@ -162,15 +162,15 @@ export async function GET(request: NextRequest) {
|
||||
) {
|
||||
isDO = true;
|
||||
if (sksTotal < 80) {
|
||||
alasanList.push('SKS kurang dari 80 pada evaluasi semester 8');
|
||||
alasanList.push('SKS kurang dari 80 pada evaluasi semester 7');
|
||||
}
|
||||
if (ipk !== null && ipk <= 2.50) {
|
||||
alasanList.push('IPK kurang dari 2.50 pada evaluasi semester 8');
|
||||
alasanList.push('IPK kurang dari 2.50 pada evaluasi semester 7');
|
||||
}
|
||||
}
|
||||
// Evaluasi akhir masa studi: semester = 14
|
||||
else if (
|
||||
sem === 14 &&
|
||||
sem === 12 &&
|
||||
sksTotal !== null &&
|
||||
(
|
||||
sksTotal < 144 ||
|
||||
@@ -184,25 +184,25 @@ export async function GET(request: NextRequest) {
|
||||
) {
|
||||
isDO = true;
|
||||
if (sksTotal < 144) {
|
||||
alasanList.push('Belum mencapai 144 SKS pada akhir masa studi');
|
||||
alasanList.push('Belum mencapai 144 SKS pada evaluasi semester 12');
|
||||
}
|
||||
if (ipk !== null && ipk <= 2.00) {
|
||||
alasanList.push('IPK di bawah 2.00 pada akhir masa studi');
|
||||
alasanList.push('IPK di bawah 2.00 pada evaluasi semester 12');
|
||||
}
|
||||
if (jumlahE !== null && jumlahE > 0) {
|
||||
alasanList.push('Memiliki nilai E pada akhir masa studi');
|
||||
alasanList.push('Memiliki nilai E pada evaluasi semester 12');
|
||||
}
|
||||
if (sksD !== null && sksD > 14) {
|
||||
alasanList.push('Total SKS dari nilai D lebih dari 14 SKS');
|
||||
alasanList.push('Total SKS dari nilai D lebih dari 14 SKS pada evaluasi semester 12');
|
||||
}
|
||||
if (minWajib !== null && minWajib < 2.00) {
|
||||
alasanList.push('Nilai minimal mata kuliah wajib di bawah C');
|
||||
alasanList.push('Nilai minimal mata kuliah wajib di bawah C pada evaluasi semester 12');
|
||||
}
|
||||
if (lulusTA1 === 0) {
|
||||
alasanList.push('Belum lulus Tugas Akhir 1 (INF-55201-406)');
|
||||
alasanList.push('Belum lulus Tugas Akhir 1 (INF-55201-406) pada evaluasi semester 12');
|
||||
}
|
||||
if (lulusTA2 === 0) {
|
||||
alasanList.push('Belum lulus Tugas Akhir 2 (INF-55201-407)');
|
||||
alasanList.push('Belum lulus Tugas Akhir 2 (INF-55201-407) pada evaluasi semester 12');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import BimbinganDosenChart from "@/components/charts/BimbinganDosenChart";
|
||||
import BimbinganDosenPerAngkatanChart from "@/components/charts/BimbinganDosenPerAngkatanChart";
|
||||
import DistribusiIPKChart from "@/components/chartsDashboard/DistribusiIPKChart";
|
||||
import DistribusiIPKChartPerangkatan from "@/components/chartsDashboard/DistribusiIPKChartPerangkatan";
|
||||
import MKBelumDiambilChart from "@/components/chartsDashboard/MKBelumDiambilChart";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -130,9 +131,11 @@ export default function TotalMahasiswaPage() {
|
||||
{ id: 'academic', label: 'IPK & Jenis Pendaftaran' },
|
||||
{ id: 'study-duration', label: 'Kelulusan Tepat Waktu & Masa Studi' },
|
||||
{ id: 'expertise', label: 'Kelompok Keahlian' },
|
||||
{ id: 'dropout', label: 'Terancam Drop Out & Distribusi IPK' },
|
||||
{ id: 'scholarship', label: 'Beasiswa & Prestasi' },
|
||||
{ id: 'demographics', label: 'Asal Kabupaten & Provinsi' },
|
||||
{ id: 'bimbingan-dosen', label: 'Bimbingan Dosen' }
|
||||
{ id: 'bimbingan-dosen', label: 'Bimbingan Dosen' },
|
||||
{ id: 'mk-belum-diambil', label: 'Mata Kuliah Belum Diambil' }
|
||||
];
|
||||
|
||||
// Navigation menu items for per year data
|
||||
@@ -218,6 +221,10 @@ export default function TotalMahasiswaPage() {
|
||||
<div id="study-duration" className="grid grid-cols-1 md:grid-cols-2 gap-4 scroll-mt-24">
|
||||
<LulusTepatWaktuChart selectedYear={selectedYear} />
|
||||
<MasaStudiLulusChart selectedYear={selectedYear} />
|
||||
</div>
|
||||
|
||||
{/* Dropout Section */}
|
||||
<div id="dropout" className="grid grid-cols-1 md:grid-cols-2 gap-4 scroll-mt-24">
|
||||
<TerancamDOChart selectedYear={selectedYear} />
|
||||
<DistribusiIPKChart selectedYear={selectedYear} />
|
||||
</div>
|
||||
@@ -234,6 +241,11 @@ export default function TotalMahasiswaPage() {
|
||||
<TingkatPrestasiChart selectedYear={selectedYear} />
|
||||
</div>
|
||||
|
||||
{/* MK Belum Diambil Section */}
|
||||
<div id="mk-belum-diambil" className="grid grid-cols-1 md:grid-cols-2 gap-4 scroll-mt-24">
|
||||
<MKBelumDiambilChart selectedYear={selectedYear} />
|
||||
</div>
|
||||
|
||||
{/* Demographics Section */}
|
||||
<div id="demographics" className="grid grid-cols-1 md:grid-cols-2 gap-4 scroll-mt-24">
|
||||
<div className="col-span-1">
|
||||
|
||||
72
app/detail/mk-belum-diambil/page.tsx
Normal file
72
app/detail/mk-belum-diambil/page.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import MKBelumDiambilChart from "@/components/chartsDashboard/MKBelumDiambilChart";
|
||||
import FilterTahunAngkatan from "@/components/FilterTahunAngkatan";
|
||||
import TabelMKBelumDiambil from "@/components/chartstable/tabelmkbelumdiambil";
|
||||
|
||||
export default function MKBelumDiambilDetailPage() {
|
||||
const [selectedYear, setSelectedYear] = useState<string>("all");
|
||||
const [selectedJenisMK, setSelectedJenisMK] = useState<string>("all");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-[var(--background)] p-4">
|
||||
<div className="container mx-auto max-w-7xl space-y-2">
|
||||
{/* Filter Section */}
|
||||
<FilterTahunAngkatan
|
||||
selectedYear={selectedYear}
|
||||
onYearChange={setSelectedYear}
|
||||
/>
|
||||
|
||||
{/* Chart Section */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-1 gap-6">
|
||||
<MKBelumDiambilChart
|
||||
selectedYear={selectedYear}
|
||||
height="h-[400px] sm:h-[400px] lg:h-[400px]"
|
||||
showDetailButton={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tabel Section */}
|
||||
<TabelMKBelumDiambil
|
||||
selectedYear={selectedYear}
|
||||
selectedJenisMK={selectedJenisMK}
|
||||
onJenisMKChange={setSelectedJenisMK}
|
||||
/>
|
||||
|
||||
{/* Information Section */}
|
||||
<div className="bg-white dark:bg-slate-900 rounded-lg shadow-sm p-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Informasi Visualisasi
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-1 gap-6">
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
|
||||
Grafik dan Tabel Mata Kuliah Belum Diambil
|
||||
</h3>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-300 space-y-1">
|
||||
<li>• Menampilkan data mahasiswa yang masih memiliki mata kuliah yang belum diambil atau belum lulus</li>
|
||||
<li>• Grafik stacked bar horizontal menunjukkan jumlah MK belum diambil per jenis MK per tahun angkatan</li>
|
||||
<li>• Mata kuliah dikategorikan menjadi tiga jenis:</li>
|
||||
<li className="ml-4">- <strong className="text-blue-600 dark:text-blue-400">Wajib</strong>: Mata kuliah yang wajib diambil oleh semua mahasiswa</li>
|
||||
<li className="ml-4">- <strong className="text-green-600 dark:text-green-400">Pilihan Wajib</strong>: Mata kuliah pilihan yang wajib diambil minimal satu dari kelompoknya</li>
|
||||
<li className="ml-4">- <strong className="text-amber-600 dark:text-amber-400">Pilihan</strong>: Mata kuliah pilihan bebas</li>
|
||||
<li>• Mata kuliah dianggap <strong>belum diambil</strong> jika:</li>
|
||||
<li className="ml-4">- Mahasiswa belum pernah mengambil MK tersebut, ATAU</li>
|
||||
<li className="ml-4">- Mahasiswa mendapat nilai E (gagal/tidak lulus)</li>
|
||||
<li>• Tabel detail menampilkan daftar mahasiswa dengan:</li>
|
||||
<li className="ml-4">- Informasi dasar mahasiswa (NIM, nama, angkatan)</li>
|
||||
<li className="ml-4">- Total SKS yang sudah lulus (nilai != E)</li>
|
||||
<li className="ml-4">- Jumlah MK yang belum diambil</li>
|
||||
<li className="ml-4">- Dropdown detail MK belum diambil (kode MK, nama MK, jenis MK, SKS, semester)</li>
|
||||
<li>• Filter tersedia untuk tahun angkatan dan jenis MK untuk analisis lebih spesifik</li>
|
||||
<li>• Data dapat digunakan untuk monitoring progres akademik dan identifikasi mahasiswa yang perlu bimbingan</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ export default function TerancamDODetailPage() {
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-300 space-y-1">
|
||||
<li>• Menampilkan jumlah mahasiswa yang terancam drop out (DO) per tahun angkatan</li>
|
||||
<li>• Evaluasi dilakukan berdasarkan pedoman akademik UNTAN tahun 2023/2024</li>
|
||||
<li>• Kriteria evaluasi terdiri dari tiga tahap:</li>
|
||||
<li className="ml-4">- Evaluasi 4 semester: SKS minimal 40 dan IPK > 2.50</li>
|
||||
<li className="ml-4">- Evaluasi 8 semester: SKS minimal 80 dan IPK > 2.50</li>
|
||||
<li className="ml-4">- Evaluasi akhir masa studi: SKS minimal 144, IPK > 2.00, tidak ada nilai E, nilai D maksimal 10%, nilai mata kuliah wajib minimal C, dan lulus tugas akhir</li>
|
||||
<li>• Kriteria evaluasi terdiri dari tiga tahap (evaluasi dilakukan sebelum semester target):</li>
|
||||
<li className="ml-4">- <strong>Evaluasi semester 3</strong>: SKS minimal 40 dan IPK > 2.50 (sebelum masuk semester 4)</li>
|
||||
<li className="ml-4">- <strong>Evaluasi semester 7</strong>: SKS minimal 80 dan IPK > 2.50 (sebelum masuk semester 8)</li>
|
||||
<li className="ml-4">- <strong>Evaluasi semester 12</strong>: SKS minimal 144, IPK > 2.00, tidak ada nilai E, nilai D maksimal 10%, nilai mata kuliah wajib minimal C, dan lulus tugas akhir (sebelum semester 14)</li>
|
||||
<li>• Grafik batang vertikal yang menunjukkan jumlah mahasiswa terancam DO per tahun angkatan</li>
|
||||
<li>• Data dapat di-download dan dianalisis untuk monitoring akademik</li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user