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} +
+
+
+
+ ); +}