From 3ba86ffd013c9e699ffffddf3d8b69cd53e726d2 Mon Sep 17 00:00:00 2001 From: Randa Firman Putra Date: Tue, 26 Aug 2025 22:57:48 +0700 Subject: [PATCH] kesekian kalinya --- app/dashboard/page.tsx | 18 +- .../charts/StatistikPerAngkatanChart.tsx | 2 +- .../NamaBeasiswaDashPieChartPerangkatan.tsx | 221 ++++++++++++++++++ .../ProvinsiMahasiswaPieChart.tsx | 5 - .../StatusMahasiswaPieChartPerangkatan.tsx | 197 ++++++++++++++++ .../TingkatPrestasiDashChart.tsx | 2 +- .../TingkatPrestasiPieChartDash.tsx | 188 +++++++++++++++ .../kkdashboardpiechartperangkatan.tsx | 188 +++++++++++++++ 8 files changed, 812 insertions(+), 9 deletions(-) create mode 100644 components/chartsDashboard/NamaBeasiswaDashPieChartPerangkatan.tsx create mode 100644 components/chartsDashboard/StatusMahasiswaPieChartPerangkatan.tsx create mode 100644 components/chartsDashboard/TingkatPrestasiPieChartDash.tsx create mode 100644 components/chartsDashboard/kkdashboardpiechartperangkatan.tsx diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 641395b..da8a077 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -12,11 +12,15 @@ import AsalDaerahPerAngkatanChart from "@/components/charts/AsalDaerahPerAngkata import StatusMahasiswaChart from "@/components/charts/StatusMahasiswaChart"; import KelompokKeahlianStatusChart from "@/components/chartsDashboard/kkdashboardchart"; import KelompokKeahlianLulusTepatPieChart from "@/components/chartsDashboard/kkdashboardtepatpiechart"; +import KelompokKeahlianPieChartPerAngkatan from "@/components/chartsDashboard/kkdashboardpiechartperangkatan"; +import StatusMahasiswaPieChartPerangkatan from "@/components/chartsDashboard/StatusMahasiswaPieChartPerangkatan"; import MasaStudiAktifChart from "@/components/chartsDashboard/masastudiaktifchart"; import MasaStudiLulusChart from "@/components/chartsDashboard/masastudiluluschart"; import NamaBeasiswaChart from "@/components/chartsDashboard/NamaBeasiswaDashChart"; +import NamaBeasiswaDashPieChartPerangkatan from "@/components/chartsDashboard/NamaBeasiswaDashPieChartPerangkatan"; import TingkatPrestasiChart from "@/components/chartsDashboard/TingkatPrestasiDashChart"; import ProvinsiMahasiswaChart from "@/components/chartsDashboard/ProvinsiMahasiswaPieChart"; +import TingkatPrestasiPieChartDash from "@/components/chartsDashboard/TingkatPrestasiPieChartDash"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; export default function TotalMahasiswaPage() { @@ -84,12 +88,22 @@ export default function TotalMahasiswaPage() { {/* Overview Section */}
- + +
+ + {/* Status Section */} +
+ + +
+ +
+ +
{/* Demographics Section */}
-
diff --git a/components/charts/StatistikPerAngkatanChart.tsx b/components/charts/StatistikPerAngkatanChart.tsx index 95c7ab5..d6dfb1a 100644 --- a/components/charts/StatistikPerAngkatanChart.tsx +++ b/components/charts/StatistikPerAngkatanChart.tsx @@ -216,7 +216,7 @@ export default function StatistikPerAngkatanChart({ tahunAngkatan }: Props) { - Total Mahasiswa Angkatan {tahunAngkatan} + Jumlah Mahasiswa Angkatan {tahunAngkatan} diff --git a/components/chartsDashboard/NamaBeasiswaDashPieChartPerangkatan.tsx b/components/chartsDashboard/NamaBeasiswaDashPieChartPerangkatan.tsx new file mode 100644 index 0000000..a3f2f7b --- /dev/null +++ b/components/chartsDashboard/NamaBeasiswaDashPieChartPerangkatan.tsx @@ -0,0 +1,221 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { ApexOptions } from 'apexcharts'; +import { useTheme } from 'next-themes'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface NamaBeasiswaPerAngkatanData { + nama_beasiswa: string; + jumlah_nama_beasiswa: number; +} + +interface Props { + selectedYear: string; +} + +export default function NamaBeasiswaDashPieChartPerangkatan({ selectedYear }: Props) { + const { theme } = useTheme(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + const url = `/api/mahasiswa/nama-beasiswa-dashboard?tahunAngkatan=${selectedYear}`; + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch data: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + + if (!Array.isArray(result)) { + throw new Error('Invalid data format received from server'); + } + + // Filter data for selected year and group by nama_beasiswa + const yearData = result.filter((item: any) => + item.tahun_angkatan.toString() === selectedYear + ); + + // Group by nama_beasiswa and sum jumlah_nama_beasiswa + const groupedData = yearData.reduce((acc: { [key: string]: number }, item: any) => { + const namaBeasiswa = item.nama_beasiswa || 'Tidak Diketahui'; + acc[namaBeasiswa] = (acc[namaBeasiswa] || 0) + item.jumlah_nama_beasiswa; + return acc; + }, {}); + + // Convert to array format + const chartData = Object.entries(groupedData).map(([nama_beasiswa, jumlah_nama_beasiswa]) => ({ + nama_beasiswa, + jumlah_nama_beasiswa: jumlah_nama_beasiswa as number + })); + + // Sort by jumlah_nama_beasiswa descending + const sortedData = chartData.sort((a, b) => (b.jumlah_nama_beasiswa as number) - (a.jumlah_nama_beasiswa as number)); + setData(sortedData); + } catch (err) { + console.error('Error in fetchData:', err); + setError(err instanceof Error ? err.message : 'An error occurred while fetching data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedYear]); + + // Prepare data for pie chart + const series = data.map(item => item.jumlah_nama_beasiswa); + const labels = data.map(item => item.nama_beasiswa); + + const chartOptions: ApexOptions = { + chart: { + type: 'pie', + toolbar: { + show: true, + tools: { + download: true, + selection: true, + zoom: true, + zoomin: true, + zoomout: true, + pan: true, + reset: true + } + }, + background: theme === 'dark' ? '#0F172B' : '#fff', + }, + labels: labels, + dataLabels: { + enabled: true, + formatter: function (val: number) { + return `${val.toFixed(0)}%`; + }, + style: { + fontSize: '14px', + fontFamily: 'Inter, sans-serif', + fontWeight: '500' + } + }, + legend: { + position: 'bottom', + fontSize: '12px', + markers: { + size: 12, + }, + itemMargin: { + horizontal: 10, + vertical: 5, + }, + labels: { + colors: theme === 'dark' ? '#fff' : '#000' + } + }, + colors: [ + '#3B82F6', // Blue + '#EC4899', // Pink + '#10B981', // Green + '#F59E0B', // Yellow + '#EF4444', // Red + '#8B5CF6', // Purple + '#06B6D4', // Cyan + '#F97316', // Orange + '#84CC16', // Lime + '#06B6D4', // Teal + ], + tooltip: { + theme: theme === 'dark' ? 'dark' : 'light', + y: { + formatter: function (val: number) { + return val + ' mahasiswa'; + } + } + }, + plotOptions: { + pie: { + donut: { + size: '0%', + }, + offsetY: 0, + }, + }, + states: { + hover: { + filter: { + type: 'darken', + }, + }, + }, + }; + + if (loading) { + return ( + + + + Loading... + + + + ); + } + + if (error) { + return ( + + + + Error: {error} + + + + ); + } + + if (data.length === 0) { + return ( + + + + Tidak ada data yang tersedia + + + + ); + } + + return ( + + + + Nama Beasiswa + {selectedYear !== 'all' ? ` Angkatan ${selectedYear}` : ''} + + + +
+ {typeof window !== 'undefined' && ( + + )} +
+
+
+ ); +} diff --git a/components/chartsDashboard/ProvinsiMahasiswaPieChart.tsx b/components/chartsDashboard/ProvinsiMahasiswaPieChart.tsx index 8432c4b..3d21530 100644 --- a/components/chartsDashboard/ProvinsiMahasiswaPieChart.tsx +++ b/components/chartsDashboard/ProvinsiMahasiswaPieChart.tsx @@ -81,7 +81,6 @@ export default function ProvinsiMahasiswaPieChart() { style: { fontSize: '14px', fontWeight: 'bold', - colors: [theme === 'dark' ? '#fff' : '#000'] }, offsetY: -10, dropShadow: { @@ -107,10 +106,6 @@ export default function ProvinsiMahasiswaPieChart() { offsetY: 0, }, }, - stroke: { - width: 2, - colors: [theme === 'dark' ? '#0F172B' : '#fff'], - }, states: { hover: { filter: { diff --git a/components/chartsDashboard/StatusMahasiswaPieChartPerangkatan.tsx b/components/chartsDashboard/StatusMahasiswaPieChartPerangkatan.tsx new file mode 100644 index 0000000..4b36c4a --- /dev/null +++ b/components/chartsDashboard/StatusMahasiswaPieChartPerangkatan.tsx @@ -0,0 +1,197 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { ApexOptions } from 'apexcharts'; +import { useTheme } from 'next-themes'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface StatusMahasiswaPerAngkatanData { + status_kuliah: string; + jumlah: number; +} + +interface Props { + selectedYear: string; +} + +export default function StatusMahasiswaPieChartPerangkatan({ selectedYear }: Props) { + const { theme } = useTheme(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await fetch( + `/api/mahasiswa/status?tahun_angkatan=${selectedYear}` + ); + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + const result = await response.json(); + + // Filter data for selected year and group by status + const yearData = result.filter((item: any) => + item.tahun_angkatan.toString() === selectedYear + ); + + // Group by status_kuliah and sum jumlah + const groupedData = yearData.reduce((acc: { [key: string]: number }, item: any) => { + const status = item.status_kuliah || 'Tidak Diketahui'; + acc[status] = (acc[status] || 0) + item.jumlah; + return acc; + }, {}); + + // Convert to array format + const chartData = Object.entries(groupedData).map(([status_kuliah, jumlah]) => ({ + status_kuliah, + jumlah: jumlah as number + })); + + // Sort by jumlah descending + const sortedData = chartData.sort((a, b) => (b.jumlah as number) - (a.jumlah as number)); + setData(sortedData); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + fetchData(); + }, [selectedYear]); + + // Prepare data for pie chart + const series = data.map(item => item.jumlah); + const labels = data.map(item => item.status_kuliah); + + const chartOptions: ApexOptions = { + chart: { + type: 'pie', + toolbar: { + show: true, + }, + background: theme === 'dark' ? '#0F172B' : '#fff', + }, + labels: labels, + dataLabels: { + enabled: true, + formatter: function (val: number) { + return `${val.toFixed(0)}%`; + }, + style: { + fontSize: '14px', + fontFamily: 'Inter, sans-serif', + fontWeight: '500' + } + }, + legend: { + position: 'bottom', + fontSize: '12px', + markers: { + size: 12, + }, + itemMargin: { + horizontal: 10, + vertical: 5, + }, + labels: { + colors: theme === 'dark' ? '#fff' : '#000' + } + }, + colors: [ + '#00E396', // Aktif - Green + '#008FFB', // Lulus - Blue + '#FEB019', // Cuti - Orange + '#FF4560', // Non-Aktif - Red + '#775DD0', // Lainnya - Purple + ], + tooltip: { + theme: theme === 'dark' ? 'dark' : 'light', + y: { + formatter: function (val: number) { + return val + ' mahasiswa'; + } + } + }, + plotOptions: { + pie: { + donut: { + size: '0%', + }, + offsetY: 0, + }, + }, + states: { + hover: { + filter: { + type: 'darken', + }, + }, + }, + }; + + if (loading) { + return ( + + + + Loading... + + + + ); + } + + if (error) { + return ( + + + + Error: {error} + + + + ); + } + + if (data.length === 0) { + return ( + + + + Tidak ada data yang tersedia + + + + ); + } + + return ( + + + + Status Mahasiswa + {selectedYear !== 'all' ? ` Angkatan ${selectedYear}` : ''} + + + +
+ {typeof window !== 'undefined' && ( + + )} +
+
+
+ ); +} diff --git a/components/chartsDashboard/TingkatPrestasiDashChart.tsx b/components/chartsDashboard/TingkatPrestasiDashChart.tsx index 67f0298..91b5376 100644 --- a/components/chartsDashboard/TingkatPrestasiDashChart.tsx +++ b/components/chartsDashboard/TingkatPrestasiDashChart.tsx @@ -160,7 +160,7 @@ export default function TingkatPrestasiDashChart({ selectedYear }: Props) { show: true, color: theme === 'dark' ? '#374151' : '#E5E7EB' }, - tickAmount: 5, + tickAmount: 5 }, fill: { opacity: 1 diff --git a/components/chartsDashboard/TingkatPrestasiPieChartDash.tsx b/components/chartsDashboard/TingkatPrestasiPieChartDash.tsx new file mode 100644 index 0000000..c6d0037 --- /dev/null +++ b/components/chartsDashboard/TingkatPrestasiPieChartDash.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { ApexOptions } from 'apexcharts'; +import { useTheme } from 'next-themes'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +// Dynamically import ApexCharts to avoid SSR issues +const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface TingkatPrestasiData { + tahun_angkatan: number; + tingkat_prestasi: string; + tingkat_mahasiswa_prestasi: number; +} + +interface Props { + selectedYear: string; +} + +export default function TingkatPrestasiPieChart({ selectedYear }: Props) { + const { theme } = useTheme(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + const url = `/api/mahasiswa/tingkat-prestasi?tahunAngkatan=${selectedYear}`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch data: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + setData(result); + } catch (err) { + console.error('Error in fetchData:', err); + setError(err instanceof Error ? err.message : 'An error occurred while fetching data'); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedYear]); + + // Process data for series + const processSeriesData = () => { + // Data sudah difilter dari API berdasarkan tahun angkatan dan jenis prestasi + // Langsung gunakan data yang diterima dari API + const tingkat = [...new Set(data.map(item => item.tingkat_prestasi))].sort(); + const jumlahData = tingkat.map(t => { + const item = data.find(d => d.tingkat_prestasi === t); + return item ? item.tingkat_mahasiswa_prestasi : 0; + }); + return { + series: jumlahData, + labels: tingkat + }; + }; + + const { series, labels } = processSeriesData(); + + const chartOptions: ApexOptions = { + chart: { + type: 'pie', + background: theme === 'dark' ? '#0F172B' : '#fff', + toolbar: { + show: true, + tools: { + download: true, + selection: true, + zoom: true, + zoomin: true, + zoomout: true, + pan: true, + reset: true + } + } + }, + labels: labels, + colors: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4'], + legend: { + position: 'bottom', + fontSize: '14px', + markers: { + size: 12, + strokeWidth: 0 + }, + itemMargin: { + horizontal: 10 + }, + labels: { + colors: theme === 'dark' ? '#fff' : '#000' + } + }, + dataLabels: { + enabled: true, + formatter: function (val: number) { + return `${val.toFixed(0)}%`; + }, + style: { + fontSize: '14px', + fontFamily: 'Inter, sans-serif', + fontWeight: '500' + }, + offsetY: 0, + dropShadow: { + enabled: false + } + }, + tooltip: { + theme: theme === 'dark' ? 'dark' : 'light', + y: { + formatter: function (val: number) { + return val + " mahasiswa" + } + } + } + }; + + if (loading) { + return ( + + + + Loading... + + + + ); + } + + if (error) { + return ( + + + + Error: {error} + + + + ); + } + + if (data.length === 0) { + return ( + + + + Tidak ada data yang tersedia + + + + ); + } + + return ( + + + + Tingkat Prestasi + {selectedYear !== 'all' ? ` Angkatan ${selectedYear}` : ''} + + + +
+ {typeof window !== 'undefined' && ( + + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/components/chartsDashboard/kkdashboardpiechartperangkatan.tsx b/components/chartsDashboard/kkdashboardpiechartperangkatan.tsx new file mode 100644 index 0000000..5a547e8 --- /dev/null +++ b/components/chartsDashboard/kkdashboardpiechartperangkatan.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import dynamic from 'next/dynamic'; +import { ApexOptions } from 'apexcharts'; +import { useTheme } from 'next-themes'; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); + +interface KelompokKeahlianPerAngkatanData { + nama_kelompok: string; + jumlah_mahasiswa: number; +} + +interface Props { + selectedYear: string; +} + +export default function KelompokKeahlianPieChartPerAngkatan({ selectedYear }: Props) { + const { theme } = useTheme(); + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await fetch( + `/api/mahasiswa/kk-dashboard?tahun_angkatan=${selectedYear}` + ); + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + const result = await response.json(); + + // Group by nama_kelompok and sum jumlah_mahasiswa + const groupedData = result.reduce((acc: { [key: string]: number }, item: any) => { + const namaKelompok = item.nama_kelompok || 'Tidak Diketahui'; + acc[namaKelompok] = (acc[namaKelompok] || 0) + item.jumlah_mahasiswa; + return acc; + }, {}); + + // Convert to array format + const chartData = Object.entries(groupedData).map(([nama_kelompok, jumlah_mahasiswa]) => ({ + nama_kelompok, + jumlah_mahasiswa: jumlah_mahasiswa as number + })); + + // Sort by jumlah_mahasiswa descending + const sortedData = chartData.sort((a, b) => (b.jumlah_mahasiswa as number) - (a.jumlah_mahasiswa as number)); + setData(sortedData); + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setLoading(false); + } + }; + fetchData(); + }, [selectedYear]); + + // Prepare data for pie chart + const series = data.map(item => item.jumlah_mahasiswa); + const labels = data.map(item => item.nama_kelompok); + + const chartOptions: ApexOptions = { + chart: { + type: 'pie', + toolbar: { + show: true, + }, + background: theme === 'dark' ? '#0F172B' : '#fff', + }, + labels: labels, + dataLabels: { + enabled: true, + formatter: function (val: number) { + return `${val.toFixed(0)}%`; + }, + style: { + fontSize: '14px', + fontFamily: 'Inter, sans-serif', + fontWeight: '500' + } + }, + legend: { + position: 'bottom', + fontSize: '12px', + markers: { + size: 12, + }, + itemMargin: { + horizontal: 10, + vertical: 5, + }, + labels: { + colors: theme === 'dark' ? '#fff' : '#000' + } + }, + colors: [ + '#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0', '#8B5CF6', '#EC4899', '#06B6D4', '#F97316' + ], + tooltip: { + theme: theme === 'dark' ? 'dark' : 'light', + y: { + formatter: function (val: number) { + return val + ' mahasiswa'; + } + } + }, + plotOptions: { + pie: { + donut: { + size: '0%', + }, + offsetY: 0, + }, + }, + states: { + hover: { + filter: { + type: 'darken', + }, + }, + }, + }; + + if (loading) { + return ( + + + + Loading... + + + + ); + } + + if (error) { + return ( + + + + Error: {error} + + + + ); + } + + if (data.length === 0) { + return ( + + + + Tidak ada data yang tersedia + + + + ); + } + + return ( + + + + Kelompok Keahlian + {selectedYear !== 'all' ? ` Angkatan ${selectedYear}` : ''} + + + +
+ {typeof window !== 'undefined' && ( + + )} +
+
+
+ ); +}