Bisa yuk
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<string>("all");
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||
const [mahasiswaData, setMahasiswaData] = useState<MahasiswaTotal | null>(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() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Dashboard Stats Cards */}
|
||||
{loading ? (
|
||||
<DashboardStatsSkeleton />
|
||||
) : mahasiswaData ? (
|
||||
<DashboardStats mahasiswaData={mahasiswaData} />
|
||||
) : (
|
||||
<div className="mb-8 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||
<p className="text-red-600 dark:text-red-400">Gagal memuat data statistik mahasiswa</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:space-x-4">
|
||||
<FilterTahunAngkatan
|
||||
|
||||
117
app/page.tsx
117
app/page.tsx
@@ -1,13 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Users, GraduationCap, Trophy, BookOpen } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import StatistikMahasiswaChart from "@/components/charts/StatistikMahasiswaChart";
|
||||
import JenisPendaftaranChart from "@/components/charts/JenisPendaftaranChart";
|
||||
import AsalDaerahChart from "@/components/charts/AsalDaerahChart";
|
||||
import ProvinsiMahasiswaChart from "@/components/chartsDashboard/ProvinsiMahasiswaPieChart";
|
||||
import DashboardStats, { DashboardStatsSkeleton } from "@/components/dashboard/DashboardStats";
|
||||
import DashboardCharts from "@/components/dashboard/DashboardCharts";
|
||||
|
||||
interface MahasiswaTotal {
|
||||
total_mahasiswa: number;
|
||||
@@ -24,22 +20,6 @@ interface MahasiswaTotal {
|
||||
total_mahasiswa_beasiswa_non_pemerintah: number;
|
||||
}
|
||||
|
||||
// Skeleton loading component
|
||||
const CardSkeleton = () => (
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div className="h-4 w-24 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-4 w-4 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"></div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-8 w-16 bg-gray-200 dark:bg-gray-700 rounded animate-pulse mb-2"></div>
|
||||
<div className="flex justify-between">
|
||||
<div className="h-3 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-3 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { theme } = useTheme();
|
||||
@@ -108,12 +88,7 @@ export default function DashboardPage() {
|
||||
<h1 className="text-2xl font-bold mb-4">Visualisasi Data Akademik Mahasiswa Informatika</h1>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</div>
|
||||
<DashboardStatsSkeleton />
|
||||
) : error ? (
|
||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
@@ -129,89 +104,9 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-8">
|
||||
{/* Kartu Total Mahasiswa */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Total Mahasiswa
|
||||
</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_mahasiswa}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Aktif: <span className="text-green-500">{mahasiswaData.mahasiswa_aktif}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Total Kelulusan */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Total Kelulusan
|
||||
</CardTitle>
|
||||
<GraduationCap className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_lulus}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Laki-laki: <span className="text-blue-500">{mahasiswaData.pria_lulus}</span></span>
|
||||
<span className="dark:text-white">Perempuan: <span className="text-pink-500">{mahasiswaData.wanita_lulus}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Total Prestasi */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Mahasiswa Berprestasi
|
||||
</CardTitle>
|
||||
<Trophy className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_berprestasi}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Akademik: <span className="text-yellow-500">{mahasiswaData.prestasi_akademik}</span></span>
|
||||
<span className="dark:text-white">Non-Akademik: <span className="text-purple-500">{mahasiswaData.prestasi_non_akademik}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Mahasiswa Beasiswa */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Mahasiswa Beasiswa
|
||||
</CardTitle>
|
||||
<BookOpen className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_mahasiswa_beasiswa}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Pemerintah: <span className="text-green-500">{mahasiswaData.total_mahasiswa_beasiswa_pemerintah}</span></span>
|
||||
<span className="dark:text-white">Non-Pemerintah: <span className="text-amber-400">{mahasiswaData.total_mahasiswa_beasiswa_non_pemerintah}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="col-span-1">
|
||||
<StatistikMahasiswaChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<JenisPendaftaranChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<AsalDaerahChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<ProvinsiMahasiswaChart />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<DashboardStats mahasiswaData={mahasiswaData} />
|
||||
<DashboardCharts />
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
25
components/dashboard/DashboardCharts.tsx
Normal file
25
components/dashboard/DashboardCharts.tsx
Normal file
@@ -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 (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="col-span-1">
|
||||
<StatistikMahasiswaChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<JenisPendaftaranChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<AsalDaerahChart />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<ProvinsiMahasiswaChart />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
117
components/dashboard/DashboardStats.tsx
Normal file
117
components/dashboard/DashboardStats.tsx
Normal file
@@ -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 = () => (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-8">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<Card key={i} className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<div className="h-4 w-24 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-4 w-4 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse"></div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-8 w-16 bg-gray-200 dark:bg-gray-700 rounded animate-pulse mb-2"></div>
|
||||
<div className="flex justify-between">
|
||||
<div className="h-3 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-3 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-4">
|
||||
{/* Kartu Total Mahasiswa */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Total Mahasiswa
|
||||
</CardTitle>
|
||||
<Users className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_mahasiswa}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Aktif: <span className="text-green-500">{mahasiswaData.mahasiswa_aktif}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Total Kelulusan */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Total Kelulusan
|
||||
</CardTitle>
|
||||
<GraduationCap className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_lulus}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Laki-laki: <span className="text-blue-500">{mahasiswaData.pria_lulus}</span></span>
|
||||
<span className="dark:text-white">Perempuan: <span className="text-pink-500">{mahasiswaData.wanita_lulus}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Total Prestasi */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Mahasiswa Berprestasi
|
||||
</CardTitle>
|
||||
<Trophy className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_berprestasi}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Akademik: <span className="text-yellow-500">{mahasiswaData.prestasi_akademik}</span></span>
|
||||
<span className="dark:text-white">Non-Akademik: <span className="text-purple-500">{mahasiswaData.prestasi_non_akademik}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Mahasiswa Beasiswa */}
|
||||
<Card className="bg-white dark:bg-slate-900 shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-white">
|
||||
Mahasiswa Beasiswa
|
||||
</CardTitle>
|
||||
<BookOpen className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold dark:text-white">{mahasiswaData.total_mahasiswa_beasiswa}</div>
|
||||
<div className="flex justify-between mt-2 text-xs text-muted-foreground">
|
||||
<span className="dark:text-white">Pemerintah: <span className="text-green-500">{mahasiswaData.total_mahasiswa_beasiswa_pemerintah}</span></span>
|
||||
<span className="dark:text-white">Non-Pemerintah: <span className="text-amber-400">{mahasiswaData.total_mahasiswa_beasiswa_non_pemerintah}</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user