import { SetStateAction, useEffect, useRef, useState } from "react"; import html2pdf from "html2pdf.js"; import useAuth from "@/hooks/useAuth"; import { createLazyFileRoute } from "@tanstack/react-router"; import { getAllAspectsAverageScore, getAllSubAspectsAverageScore, getAllVerifiedAspectsAverageScore, getAllVerifiedSubAspectsAverageScore } from "@/modules/assessmentResult/queries/assessmentResultQueries"; import { useQuery } from "@tanstack/react-query"; import { getAssessmentResultByIdQueryOptions, getVerifiedAssessmentResultByIdQueryOptions } from "@/modules/assessmentResultsManagement/queries/assessmentResultsManagaementQueries"; import { PieChart, Pie, Label, BarChart, Bar, CartesianGrid, XAxis, YAxis } from "recharts"; import { PolarAngleAxis, PolarRadiusAxis, PolarGrid, Radar, RadarChart } from "recharts" import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from "@/shadcn/components/ui/card" import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, } from "@/shadcn/components/ui/chart" import { aspectQueryOptions } from "@/modules/aspectManagement/queries/aspectQueries"; import { TbChevronDown, TbChevronLeft, TbChevronUp } from "react-icons/tb"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/shadcn/components/ui/dropdown-menu"; const getQueryParam = (param: string) => { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); }; export const Route = createLazyFileRoute("/_dashboardLayout/assessmentResult/")({ component: AssessmentResultPage, }); export default function AssessmentResultPage() { const { user } = useAuth(); const isSuperAdmin = user?.role === "super-admin"; const [assessmentId, setAssessmentId] = useState(undefined); useEffect(() => { const id = getQueryParam("id"); setAssessmentId(id ?? undefined); }, []); const { data: aspectsData } = useQuery(aspectQueryOptions(0, 10)); const { data: assessmentResult } = useQuery(getAssessmentResultByIdQueryOptions(assessmentId)); const { data: verifiedAssessmentResult } = useQuery(getVerifiedAssessmentResultByIdQueryOptions(assessmentId)); const { data: allAspectsScoreData } = useQuery(getAllAspectsAverageScore(assessmentId)); const { data: allSubAspectsScoreData } = useQuery(getAllSubAspectsAverageScore(assessmentId)); const { data: allVerifiedAspectsScoreData } = useQuery(getAllVerifiedAspectsAverageScore(assessmentId)); const { data: allVerifiedSubAspectsScoreData } = useQuery(getAllVerifiedSubAspectsAverageScore(assessmentId)); const getAspectScore = (aspectId: string) => { return allAspectsScoreData?.aspects?.find((score) => score.aspectId === aspectId)?.averageScore || undefined; }; const getSubAspectScore = (subAspectId: string) => { return allSubAspectsScoreData?.subAspects?.find((score) => score.subAspectId === subAspectId)?.averageScore || undefined; }; const getVerifiedAspectScore = (aspectId: string) => { return allVerifiedAspectsScoreData?.aspects?.find((score) => score.aspectId === aspectId)?.averageScore || undefined; }; const getVerifiedSubAspectScore = (subAspectId: string) => { return allVerifiedSubAspectsScoreData?.subAspects?.find((score) => score.subAspectId === subAspectId)?.averageScore || undefined; }; const formatScore = (score: string | number | undefined) => { if (score === null || score === undefined) return '0'; const parsedScore = typeof score === 'number' ? score : parseFloat(score || "NaN"); return !isNaN(parsedScore) ? parsedScore.toFixed(2) : '0'; }; const totalScore = parseFloat(formatScore(assessmentResult?.assessmentsResult)); const totalVerifiedScore = parseFloat(formatScore(verifiedAssessmentResult?.verifiedAssessmentsResult)); const getScoreStyleClass = (score: number | undefined, isBg: boolean = false) => { if (score === undefined || score === null) return { color: 'grey' }; let colorVar = '--levelOne-color'; let textColor = 'white'; if (score >= 1.50 && score < 2.50) { colorVar = '--levelTwo-color'; } else if (score >= 2.50 && score < 3.50) { colorVar = '--levelThree-color'; } else if (score >= 3.50 && score < 4.49) { colorVar = '--levelFour-color'; } else if (score >= 4.50 && score <= 5) { colorVar = '--levelFive-color'; } return isBg ? { backgroundColor: `var(${colorVar})`, color: textColor } : { color: `var(${colorVar})` }; }; const aspectsColors = [ "#DBED9B", "#FF3F9F", "#877BDF", "#CFAF49", "#5FD4E7", ]; const chartData = aspectsData?.data?.map((aspect, index) => ({ aspectName: aspect.name, score: Number(formatScore(getAspectScore(aspect.id))), fill: aspectsColors[index % aspectsColors.length], })) || []; const verifiedChartData = aspectsData?.data?.map((aspect, index) => ({ aspectName: aspect.name, score: Number(formatScore(getVerifiedAspectScore(aspect.id))), fill: aspectsColors[index % aspectsColors.length], })) || []; const barChartData = aspectsData?.data?.flatMap((aspect) => aspect.subAspects.map((subAspect) => ({ subAspectName: subAspect.name, score: Number(formatScore(getSubAspectScore(subAspect.id))), fill: "#005BFF", aspectId: aspect.id, aspectName: aspect.name })) ) || []; const verifiedBarChartData = aspectsData?.data?.flatMap((aspect) => aspect.subAspects.map((subAspect) => ({ subAspectName: subAspect.name, score: Number(formatScore(getVerifiedSubAspectScore(subAspect.id))), fill: "#005BFF", aspectId: aspect.id, aspectName: aspect.name })) ) || []; const sortedBarChartData = barChartData.sort((a, b) => (a.aspectId ?? '').localeCompare(b.aspectId ?? '')); const sortedVerifiedBarChartData = verifiedBarChartData.sort((a, b) => (a.aspectId ?? '').localeCompare(b.aspectId ?? '')); const chartConfig = aspectsData?.data?.reduce((config, aspect, index) => { config[aspect.name.toLowerCase()] = { label: aspect.name, color: aspectsColors[index % aspectsColors.length], }; return config; }, {} as ChartConfig) || {}; const barChartConfig = aspectsData?.data?.reduce((config, aspect, index) => { aspect.subAspects.forEach((subAspect) => { config[subAspect.name.toLowerCase()] = { label: subAspect.name, color: aspectsColors[index % aspectsColors.length], }; }); return config; }, {} as ChartConfig) || {}; const customizedAxisTick = (props: any) => { const { x, y, payload } = props; return ( {payload.value} ); }; const [isOpen, setIsOpen] = useState(false); const [selectedItem, setSelectedItem] = useState('Hasil Sementara'); const handleDropdownToggle = () => { setIsOpen((prev) => !prev); }; const handleItemClick = () => { setSelectedItem(prev => prev === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara' ); setIsOpen(false); // Tutup dropdown setelah klik item }; // Pie Chart Component function PieChartComponent({ chartData, totalScore, chartConfig }: { chartData: { aspectName: string, score: number, fill: string }[], totalScore: number, chartConfig: ChartConfig }) { return (
} /> { const radius = innerRadius + (outerRadius - innerRadius) * 0.5; const x = cx + radius * Math.cos(-midAngle * (Math.PI / 180)); const y = cy + radius * Math.sin(-midAngle * (Math.PI / 180)); return ( {chartData[index]?.score || ""} ); }} labelLine={false} >
{/* Legend */}
{chartData.map((entry, index) => (
{entry.aspectName}
))}
); } function RadarChartComponent({ chartData, chartConfig }: { chartData: { aspectName: string, score: number }[], chartConfig: ChartConfig }) { return (
{ if (active && payload && payload.length > 0) { const { aspectName, score } = payload[0].payload; return (

{`${aspectName} : ${score}`}

); } return null; }} />
); } function BarChartComponent({ barChartData, barChartConfig }: { barChartData: { subAspectName: string, score: number, fill: string, aspectId: string, aspectName: string }[], barChartConfig: ChartConfig }) { return (
{ if (active && payload && payload.length > 0) { const { subAspectName, score } = payload[0].payload; return (

{`${subAspectName} : ${score}`}

); } return null; }} />
); } const cardRef = useRef(null); // Create a ref for your component const handlePrintPDF = async () => { const element = cardRef.current; if (element) { // Sembunyikan elemen yang tidak ingin dicetak const buttonPrint = document.getElementById("button-print"); const noPrint = document.getElementById("no-print"); if (buttonPrint) buttonPrint.style.visibility = 'hidden'; // Menggunakan visibility untuk tetap menjaga ruang if (noPrint) noPrint.style.visibility = 'hidden'; // Menggunakan visibility untuk tetap menjaga ruang // Pastikan elemen memiliki lebar 100% element.style.width = '100%'; // Mengatur lebar konten const options = { margin: [0.4, 0.5, 0.4, 0], // Margin kecil untuk mengurangi ruang kosong image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, // Mengatur scale agar kualitas tinggi width: element.scrollWidth, height: element.scrollHeight, }, jsPDF: { unit: 'in', format: 'a4', orientation: 'landscape', } }; try { const pdfBlob: Blob = await html2pdf() .set(options) .from(element) .toPdf() .get('pdf') .then((pdf: any) => { pdf.setProperties({ title: 'Hasil_Asesemen_Level_Maturitas', }); return pdf.output('blob'); // Menghasilkan PDF sebagai blob dengan title }); // Buat URL dari Blob dan buka di tab baru untuk pratinjau const pdfURL = URL.createObjectURL(pdfBlob); window.open(pdfURL, '_blank'); // Pratinjau di tab baru tanpa unduh } catch (err) { console.error("Error generating PDF:", err); } finally { // Tampilkan kembali elemen yang disembunyikan if (buttonPrint) buttonPrint.style.visibility = 'visible'; // Mengembalikan visibility if (noPrint) noPrint.style.visibility = 'visible'; // Mengembalikan visibility element.style.width = ""; // Reset lebar setelah selesai } } }; return (

Tingkatan Level Maturitas

{[ { level: 5, colorVar: '--levelFive-color', title: 'Implementasi Optimal', details: ['Otomatisasi', 'Terintegrasi', 'Membudaya'], textColor: 'white' }, { level: 4, colorVar: '--levelFour-color', title: 'Implementasi Terkelola', details: ['Terorganisir', 'Review Berkala', 'Berkelanjutan'], textColor: 'white' }, { level: 3, colorVar: '--levelThree-color', title: 'Implementasi Terdefinisi', details: ['Terorganisir', 'Konsisten', 'Review Berkala'], textColor: 'white' }, { level: 2, colorVar: '--levelTwo-color', title: 'Implementasi Berulang', details: ['Terorganisir', 'Tidak Konsisten', 'Berulang'], textColor: 'white' }, { level: 1, colorVar: '--levelOne-color', title: 'Implementasi Awal', details: ['Tidak Terukur', 'Tidak Konsisten', 'Risiko Tinggi'], textColor: 'white' } ].map(({ level, colorVar, title, details, textColor }, index) => (
0 ? '-mt-10' : ''}`}> Level {level}

{title}

{details.map((detail) => (

{detail}

))}
))}
{/* Konten Header */}
{isSuperAdmin ? (

Detail Hasil Asesmen

Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah

) : (

Cyber Security Maturity Level Dashboard

Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah

)}
{selectedItem} {isOpen ? ( ) : ( )} {isOpen && ( {selectedItem === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara'} )}
{isSuperAdmin &&

{assessmentResult?.respondentName}

{assessmentResult?.position}

Username

{assessmentResult?.username}

Email

{assessmentResult?.email}

Nama Perusahaan

{assessmentResult?.companyName}

Pengalaman Kerja

{assessmentResult?.workExperience}

No. HP

{assessmentResult?.phoneNumber}

Alamat

{assessmentResult?.address}

Tanggal Assessment

{assessmentResult?.assessmentDate ? ( new Intl.DateTimeFormat("id-ID", { year: "numeric", month: "long", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: true, }) .format(new Date(assessmentResult.assessmentDate)) .replace(/\./g, ':') .replace('pukul ', '') ) : ( 'N/A' )}

Status Verifikasi

{assessmentResult?.statusAssessment}

} {/* Conditional rendering based on selectedItem */} {selectedItem === 'Hasil Sementara' ? ( <> {/* Score Table */}

Tabel Level Maturitas

{aspectsData?.data?.map((aspect) => (

{aspect.name}

{formatScore(getAspectScore(aspect.id))}
{aspect.subAspects.map((subAspect) => (

{subAspect.name}

{formatScore(getSubAspectScore(subAspect.id))}
))}
))}
{/* Total score */}

Level Maturitas:

{totalScore}
) : ( <> {/* Verified Result Table */}

Tabel Level Maturitas Terverifikasi

{aspectsData?.data?.map((aspect) => (

{aspect.name}

{formatScore(getVerifiedAspectScore(aspect.id))}
{aspect.subAspects.map((subAspect) => (

{subAspect.name}

{formatScore(getVerifiedSubAspectScore(subAspect.id))}
))}
))}
{/* Total verified score */}

Level Maturitas:

{totalVerifiedScore}
)} {/* Pie Chart */} {selectedItem === 'Hasil Sementara' ? ( Diagram Lingkaran ) : ( Diagram Lingkaran Terverifikasi )} {/* Radar Chart */} {selectedItem === 'Hasil Sementara' ? ( Diagram Radar ) : ( Diagram Radar Terverifikasi )} {/* Bar Chart */} {selectedItem === 'Hasil Sementara' ? ( <> Diagram Batang ) : ( <> Diagram Batang Terverifikasi )}
); }