From d1bd2d055b92093ec98bdba695cfdb786d343300 Mon Sep 17 00:00:00 2001
From: Randa Firman Putra
Date: Sat, 20 Sep 2025 16:33:11 +0700
Subject: [PATCH] Bisa yuk
---
app/api/mahasiswa/total/route.ts | 100 ++++++++++++++-----
app/dashboard/page.tsx | 55 +++++++++++
app/page.tsx | 117 ++---------------------
components/dashboard/DashboardCharts.tsx | 25 +++++
components/dashboard/DashboardStats.tsx | 117 +++++++++++++++++++++++
5 files changed, 279 insertions(+), 135 deletions(-)
create mode 100644 components/dashboard/DashboardCharts.tsx
create mode 100644 components/dashboard/DashboardStats.tsx
diff --git a/app/api/mahasiswa/total/route.ts b/app/api/mahasiswa/total/route.ts
index 0d68841..c0985b4 100644
--- a/app/api/mahasiswa/total/route.ts
+++ b/app/api/mahasiswa/total/route.ts
@@ -29,78 +29,130 @@ export async function OPTIONS() {
});
}
-export async function GET() {
+export async function GET(request: Request) {
try {
+ // Ambil parameter tahun angkatan dari query string
+ const { searchParams } = new URL(request.url);
+ const tahunAngkatan = searchParams.get('tahun_angkatan');
+
// Jumlah mahasiswa
- const { count: totalMahasiswa } = await supabase
+ let totalMahasiswaQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true });
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ totalMahasiswaQuery = totalMahasiswaQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalMahasiswa } = await totalMahasiswaQuery;
// Ambil mahasiswa aktif
- const { count: mahasiswaAktif } = await supabase
+ let mahasiswaAktifQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true })
.eq('status_kuliah', 'Aktif');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ mahasiswaAktifQuery = mahasiswaAktifQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: mahasiswaAktif } = await mahasiswaAktifQuery;
// Ambil total lulus
- const { count: totalLulus } = await supabase
+ let totalLulusQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true })
.eq('status_kuliah', 'Lulus');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ totalLulusQuery = totalLulusQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalLulus } = await totalLulusQuery;
// Ambil pria lulus
- const { count: priaLulus } = await supabase
+ let priaLulusQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true })
.eq('status_kuliah', 'Lulus')
.eq('jk', 'Pria');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ priaLulusQuery = priaLulusQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: priaLulus } = await priaLulusQuery;
// Ambil wanita lulus
- const { count: wanitaLulus } = await supabase
+ let wanitaLulusQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true })
.eq('status_kuliah', 'Lulus')
.eq('jk', 'Wanita');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ wanitaLulusQuery = wanitaLulusQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: wanitaLulus } = await wanitaLulusQuery;
// Ambil total berprestasi
- const { count: totalBerprestasi } = await supabase
+ let prestasiTotalQuery = supabase
.from('prestasi_mahasiswa')
- .select('*', { count: 'exact', head: true });
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true });
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ prestasiTotalQuery = prestasiTotalQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalBerprestasi } = await prestasiTotalQuery;
// Ambil prestasi akademik
- const { count: prestasiAkademik } = await supabase
+ let prestasiAkademikQuery = supabase
.from('prestasi_mahasiswa')
- .select('*', { count: 'exact', head: true })
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true })
.eq('jenis_prestasi', 'Akademik');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ prestasiAkademikQuery = prestasiAkademikQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: prestasiAkademik } = await prestasiAkademikQuery;
// Ambil prestasi non-akademik
- const { count: prestasiNonAkademik } = await supabase
+ let prestasiNonAkademikQuery = supabase
.from('prestasi_mahasiswa')
- .select('*', { count: 'exact', head: true })
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true })
.eq('jenis_prestasi', 'Non-Akademik');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ prestasiNonAkademikQuery = prestasiNonAkademikQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: prestasiNonAkademik } = await prestasiNonAkademikQuery;
// Ambil total mahasiswa aktif + lulus
- const { count: totalMahasiswaAktifLulus } = await supabase
+ let totalMahasiswaAktifLulusQuery = supabase
.from('mahasiswa')
.select('*', { count: 'exact', head: true })
.in('status_kuliah', ['Aktif', 'Lulus']);
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ totalMahasiswaAktifLulusQuery = totalMahasiswaAktifLulusQuery.eq('tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalMahasiswaAktifLulus } = await totalMahasiswaAktifLulusQuery;
// Total mahasiswa beasiswa
- const { count: totalMahasiswaBeasiswa } = await supabase
- .from('beasiswa_mahasiswa')
- .select('*', { count: 'exact', head: true });
+ let beasiswaTotalQuery = supabase
+ .from('beasiswa_mahasiswa')
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true });
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ beasiswaTotalQuery = beasiswaTotalQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalMahasiswaBeasiswa } = await beasiswaTotalQuery;
// Total mahasiswa beasiswa Pemerintah
- const { count: totalMahasiswaBeasiswaPemerintah } = await supabase
- .from('beasiswa_mahasiswa')
- .select('*', { count: 'exact', head: true })
- .eq('jenis_beasiswa', 'Pemerintah');
+ let beasiswaPemerintahQuery = supabase
+ .from('beasiswa_mahasiswa')
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true })
+ .eq('jenis_beasiswa', 'Pemerintah');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ beasiswaPemerintahQuery = beasiswaPemerintahQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalMahasiswaBeasiswaPemerintah } = await beasiswaPemerintahQuery;
// Total mahasiswa beasiswa Non-Pemerintah
- const { count: totalMahasiswaBeasiswaNonPemerintah } = await supabase
- .from('beasiswa_mahasiswa')
- .select('*', { count: 'exact', head: true })
- .eq('jenis_beasiswa', 'Non-Pemerintah');
+ let beasiswaNonPemerintahQuery = supabase
+ .from('beasiswa_mahasiswa')
+ .select('*, mahasiswa!inner(tahun_angkatan)', { count: 'exact', head: true })
+ .eq('jenis_beasiswa', 'Non-Pemerintah');
+ if (tahunAngkatan && tahunAngkatan !== 'all') {
+ beasiswaNonPemerintahQuery = beasiswaNonPemerintahQuery.eq('mahasiswa.tahun_angkatan', parseInt(tahunAngkatan));
+ }
+ const { count: totalMahasiswaBeasiswaNonPemerintah } = await beasiswaNonPemerintahQuery;
const results: MahasiswaTotal = {
total_mahasiswa: totalMahasiswa || 0,
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index ec0230b..1c6bc18 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -32,11 +32,55 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ChevronDown, Navigation, ArrowUp } from "lucide-react";
+import DashboardStats, { DashboardStatsSkeleton } from "@/components/dashboard/DashboardStats";
+
+interface MahasiswaTotal {
+ total_mahasiswa: number;
+ mahasiswa_aktif: number;
+ total_lulus: number;
+ pria_lulus: number;
+ wanita_lulus: number;
+ total_berprestasi: number;
+ prestasi_akademik: number;
+ prestasi_non_akademik: number;
+ total_mahasiswa_aktif_lulus: number;
+ total_mahasiswa_beasiswa: number;
+ total_mahasiswa_beasiswa_pemerintah: number;
+ total_mahasiswa_beasiswa_non_pemerintah: number;
+}
export default function TotalMahasiswaPage() {
const [selectedYear, setSelectedYear] = useState("all");
const [dropdownOpen, setDropdownOpen] = useState(false);
const [showBackToTop, setShowBackToTop] = useState(false);
+ const [mahasiswaData, setMahasiswaData] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ // Fetch mahasiswa data based on selected year
+ useEffect(() => {
+ const fetchMahasiswaData = async () => {
+ setLoading(true);
+ try {
+ const url = selectedYear === "all"
+ ? '/api/mahasiswa/total'
+ : `/api/mahasiswa/total?tahun_angkatan=${selectedYear}`;
+
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error('Failed to fetch data');
+ }
+ const data = await response.json();
+ setMahasiswaData(data);
+ } catch (error) {
+ console.error('Error fetching mahasiswa data:', error);
+ setMahasiswaData(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchMahasiswaData();
+ }, [selectedYear]);
// Handle scroll event to show/hide back to top button
useEffect(() => {
@@ -107,6 +151,17 @@ export default function TotalMahasiswaPage() {
+ {/* Dashboard Stats Cards */}
+ {loading ? (
+
+ ) : mahasiswaData ? (
+
+ ) : (
+
+
Gagal memuat data statistik mahasiswa
+
+ )}
+
(
-
-
-
-
-
-
-
-
-
-
-);
export default function DashboardPage() {
const { theme } = useTheme();
@@ -108,12 +88,7 @@ export default function DashboardPage() {
Visualisasi Data Akademik Mahasiswa Informatika
{loading ? (
-
-
-
-
-
-
+
) : error ? (
@@ -129,89 +104,9 @@ export default function DashboardPage() {
) : (
<>
-
- {/* Kartu Total Mahasiswa */}
-
-
-
- Total Mahasiswa
-
-
-
-
- {mahasiswaData.total_mahasiswa}
-
- Aktif: {mahasiswaData.mahasiswa_aktif}
-
-
-
-
- {/* Kartu Total Kelulusan */}
-
-
-
- Total Kelulusan
-
-
-
-
- {mahasiswaData.total_lulus}
-
- Laki-laki: {mahasiswaData.pria_lulus}
- Perempuan: {mahasiswaData.wanita_lulus}
-
-
-
-
- {/* Kartu Total Prestasi */}
-
-
-
- Mahasiswa Berprestasi
-
-
-
-
- {mahasiswaData.total_berprestasi}
-
- Akademik: {mahasiswaData.prestasi_akademik}
- Non-Akademik: {mahasiswaData.prestasi_non_akademik}
-
-
-
-
- {/* Kartu Mahasiswa Beasiswa */}
-
-
-
- Mahasiswa Beasiswa
-
-
-
-
- {mahasiswaData.total_mahasiswa_beasiswa}
-
- Pemerintah: {mahasiswaData.total_mahasiswa_beasiswa_pemerintah}
- Non-Pemerintah: {mahasiswaData.total_mahasiswa_beasiswa_non_pemerintah}
-
-
-
-
-
- >
+
+
+ >
)
}
diff --git a/components/dashboard/DashboardCharts.tsx b/components/dashboard/DashboardCharts.tsx
new file mode 100644
index 0000000..09d3f61
--- /dev/null
+++ b/components/dashboard/DashboardCharts.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import StatistikMahasiswaChart from "@/components/charts/StatistikMahasiswaChart";
+import JenisPendaftaranChart from "@/components/charts/JenisPendaftaranChart";
+import AsalDaerahChart from "@/components/charts/AsalDaerahChart";
+import ProvinsiMahasiswaChart from "@/components/chartsDashboard/ProvinsiMahasiswaPieChart";
+
+export default function DashboardCharts() {
+ return (
+
+ );
+}
diff --git a/components/dashboard/DashboardStats.tsx b/components/dashboard/DashboardStats.tsx
new file mode 100644
index 0000000..eb67615
--- /dev/null
+++ b/components/dashboard/DashboardStats.tsx
@@ -0,0 +1,117 @@
+'use client';
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Users, GraduationCap, Trophy, BookOpen } from "lucide-react";
+
+// Skeleton loading component for stats cards
+export const DashboardStatsSkeleton = () => (
+
+ {[...Array(4)].map((_, i) => (
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+);
+
+interface MahasiswaTotal {
+ total_mahasiswa: number;
+ mahasiswa_aktif: number;
+ total_lulus: number;
+ pria_lulus: number;
+ wanita_lulus: number;
+ total_berprestasi: number;
+ prestasi_akademik: number;
+ prestasi_non_akademik: number;
+ total_mahasiswa_aktif_lulus: number;
+ total_mahasiswa_beasiswa: number;
+ total_mahasiswa_beasiswa_pemerintah: number;
+ total_mahasiswa_beasiswa_non_pemerintah: number;
+}
+
+interface DashboardStatsProps {
+ mahasiswaData: MahasiswaTotal;
+}
+
+export default function DashboardStats({ mahasiswaData }: DashboardStatsProps) {
+ return (
+
+ {/* Kartu Total Mahasiswa */}
+
+
+
+ Total Mahasiswa
+
+
+
+
+ {mahasiswaData.total_mahasiswa}
+
+ Aktif: {mahasiswaData.mahasiswa_aktif}
+
+
+
+
+ {/* Kartu Total Kelulusan */}
+
+
+
+ Total Kelulusan
+
+
+
+
+ {mahasiswaData.total_lulus}
+
+ Laki-laki: {mahasiswaData.pria_lulus}
+ Perempuan: {mahasiswaData.wanita_lulus}
+
+
+
+
+ {/* Kartu Total Prestasi */}
+
+
+
+ Mahasiswa Berprestasi
+
+
+
+
+ {mahasiswaData.total_berprestasi}
+
+ Akademik: {mahasiswaData.prestasi_akademik}
+ Non-Akademik: {mahasiswaData.prestasi_non_akademik}
+
+
+
+
+ {/* Kartu Mahasiswa Beasiswa */}
+
+
+
+ Mahasiswa Beasiswa
+
+
+
+
+ {mahasiswaData.total_mahasiswa_beasiswa}
+
+ Pemerintah: {mahasiswaData.total_mahasiswa_beasiswa_pemerintah}
+ Non-Pemerintah: {mahasiswaData.total_mahasiswa_beasiswa_non_pemerintah}
+
+
+
+
+ );
+}