Files
portaldata/components/chartsDashboard/TerancamDOChart.tsx
Randa Firman Putra 700a153b86 again n again
2025-12-04 22:15:37 +07:00

256 lines
6.3 KiB
TypeScript

'use client';
import { useEffect, useMemo, 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';
import { Button } from '@/components/ui/button';
import { ExternalLink } from 'lucide-react';
import Link from 'next/link';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
interface TerancamDOResponse {
tahun_angkatan: number;
jumlah_mahasiswa_do: number;
}
interface TerancamDOChartProps {
selectedYear?: string;
height?: string;
showDetailButton?: boolean;
}
export default function TerancamDOChart({
selectedYear = 'all',
height = 'h-[300px] sm:h-[320px]',
showDetailButton = true,
}: TerancamDOChartProps) {
const { theme } = useTheme();
const [data, setData] = useState<TerancamDOResponse[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const url =
selectedYear === 'all'
? '/api/mahasiswa/terancamdo'
: `/api/mahasiswa/terancamdo?tahun_angkatan=${selectedYear}`;
const response = await fetch(url);
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Failed to fetch data: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const result = await response.json();
if (!Array.isArray(result)) {
throw new Error('Invalid data format received from server');
}
const sorted = [...result].sort(
(a, b) => a.tahun_angkatan - b.tahun_angkatan,
);
setData(sorted);
} catch (err) {
console.error('Error fetching terancam DO data:', err);
setError(
err instanceof Error ? err.message : 'Terjadi kesalahan saat memuat data',
);
} finally {
setLoading(false);
}
};
fetchData();
}, [selectedYear]);
const categories = useMemo(
() => data.map((item) => item.tahun_angkatan),
[data],
);
const series = useMemo(
() => [
{
name: 'Mahasiswa Terancam DO',
data: data.map((item) => item.jumlah_mahasiswa_do),
},
],
[data],
);
const chartOptions: ApexOptions = {
chart: {
type: 'bar',
toolbar: {
show: true,
},
background: 'transparent',
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '55%',
borderRadius: 2,
},
},
dataLabels: {
enabled: true,
formatter: (val: number) => val.toString(),
style: {
colors: [theme === 'dark' ? '#f9fafb' : '#1f2937'],
},
},
stroke: {
show: true,
width: 2,
colors: ['transparent'],
},
xaxis: {
categories,
title: {
text: 'Tahun Angkatan',
style: {
fontSize: '14px',
fontWeight: 'bold',
color: theme === 'dark' ? '#f9fafb' : '#1f2937',
},
},
labels: {
style: {
colors: theme === 'dark' ? '#e5e7eb' : '#4b5563',
},
},
},
yaxis: {
title: {
text: 'Jumlah Mahasiswa',
style: {
fontSize: '14px',
fontWeight: 'bold',
color: theme === 'dark' ? '#f9fafb' : '#1f2937',
},
},
labels: {
style: {
colors: theme === 'dark' ? '#e5e7eb' : '#4b5563',
},
},
min: 0,
forceNiceScale: true,
},
legend: {
show: false,
},
grid: {
borderColor: theme === 'dark' ? '#374151' : '#e5e7eb',
strokeDashArray: 4,
padding: {
top: 20,
right: 20,
left: 10,
},
},
colors: ['#ef4444'],
tooltip: {
theme: theme === 'dark' ? 'dark' : 'light',
y: {
formatter: (val: number) => `${val} mahasiswa`,
},
},
};
if (loading) {
return (
<Card className="bg-white dark:bg-slate-900 shadow-lg">
<CardHeader>
<CardTitle className="text-xl font-bold dark:text-white">
Loading...
</CardTitle>
</CardHeader>
</Card>
);
}
if (error) {
return (
<Card className="bg-white dark:bg-slate-900 shadow-lg border border-red-500/40">
<CardHeader>
<CardTitle className="text-xl font-bold text-red-500">
Gagal memuat data
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-red-600 dark:text-red-400 whitespace-pre-line">
{error}
</p>
</CardContent>
</Card>
);
}
if (!data.length) {
return (
<Card className="bg-white dark:bg-slate-900 shadow-lg">
<CardHeader>
<CardTitle className="text-xl font-bold dark:text-white">
Mahasiswa Terancam DO
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Tidak ada mahasiswa yang teridentifikasi terancam DO pada periode ini.
</p>
</CardContent>
</Card>
);
}
return (
<Card className="bg-white dark:bg-slate-900 shadow-lg">
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle className="text-xl font-bold dark:text-white">
Jumlah Mahasiswa Terancam Drop Out Berdasarkan Tahun Angkatan
{selectedYear !== 'all' ? ` Angkatan ${selectedYear}` : ''}
</CardTitle>
{showDetailButton && (
<Link href="/detail/terancam-do" target="_blank">
<Button variant="outline" size="sm" className="flex items-center gap-1 dark:text-white">
<ExternalLink className="size-3" />
Detail
</Button>
</Link>
)}
</div>
</CardHeader>
<CardContent>
<div className={`${height} w-full`}>
{typeof window !== 'undefined' && (
<Chart
options={chartOptions}
series={series}
type="bar"
height="100%"
width="100%"
/>
)}
</div>
</CardContent>
</Card>
);
}