import { useEffect, 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 { Card, CardContent, 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, TbChevronRight, TbChevronUp, TbFileTypePdf } from "react-icons/tb"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/shadcn/components/ui/dropdown-menu"; import clsx from "clsx"; import React from "react"; import AppHeader from "@/components/AppHeader"; import { LeftSheet, LeftSheetContent } from "@/shadcn/components/ui/leftsheet"; import { ScrollArea } from "@mantine/core"; import data from "node_modules/backend/src/appEnv"; 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 [isMobile, setIsMobile] = useState(window.innerWidth <= 768); // Check for mobile screen const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [openNavbar, setOpenNavbar] = useState(false); const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(false); const toggle = () => { setOpenNavbar((prevState) => !prevState); }; // Adjust layout on screen resize window.addEventListener('resize', () => { setIsMobile(window.innerWidth <= 768); }); const toggleLeftSidebar = () => setIsLeftSidebarOpen(!isLeftSidebarOpen); const [assessmentId, setAssessmentId] = useState(undefined); useEffect(() => { const id = getQueryParam("id"); setAssessmentId(id ?? undefined); }, []); //fetch data from API 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)); // Pastikan status tersedia const assessmentStatus = assessmentResult?.statusAssessment; 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, assessmentStatus: string) => { if (assessmentStatus !== "selesai") return undefined; return allVerifiedAspectsScoreData?.aspects?.find((score) => score.aspectId === aspectId)?.averageScore || undefined; }; const getVerifiedSubAspectScore = (subAspectId: string, assessmentStatus: string) => { if (assessmentStatus !== "selesai") return undefined; 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'; }; // Total score const totalScore = parseFloat(formatScore(assessmentResult?.assessmentsResult)); const totalVerifiedScore = assessmentStatus === "selesai" ? parseFloat(formatScore(verifiedAssessmentResult?.verifiedAssessmentsResult)) : 0; // Mengatur warna dan level maturitas berdasarkan skor const getScoreStyleClass = (score: number | undefined, isBg: boolean = false) => { if (score === undefined || score === null) return { color: 'grey' }; let colorVar = '--levelOne-color'; let descLevel = '1'; if (score >= 1.50 && score < 2.50) { colorVar = '--levelTwo-color'; descLevel = '2'; } else if (score >= 2.50 && score < 3.50) { colorVar = '--levelThree-color'; descLevel = '3'; } else if (score >= 3.50 && score < 4.50) { colorVar = '--levelFour-color'; descLevel = '4'; } else if (score >= 4.50 && score <= 5) { colorVar = '--levelFive-color'; descLevel = '5'; } return isBg ? { backgroundColor: `var(${colorVar})`, descLevel } : { color: `var(${colorVar})`, descLevel }; }; // Warna aspek const aspectsColors = [ "#37DCCC", "#FF8C8C", "#51D0FD", "#FEA350", "#AD8AFC", ]; // Data diagram const chartData = aspectsData?.data?.map((aspect, index) => ({ aspectName: aspect.name, score: Number(formatScore(getAspectScore(aspect.id))), fill: aspectsColors[index % aspectsColors.length], })) || []; const verifiedChartData = assessmentStatus === "selesai" ? aspectsData?.data?.map((aspect, index) => ({ aspectName: aspect.name, score: Number(formatScore(getVerifiedAspectScore(aspect.id, assessmentStatus))), 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 = assessmentStatus === "selesai" ? aspectsData?.data?.flatMap((aspect) => aspect.subAspects.map((subAspect) => ({ subAspectName: subAspect.name, score: Number(formatScore(getVerifiedSubAspectScore(subAspect.id, assessmentStatus))), 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 = assessmentStatus === "selesai" ? aspectsData?.data?.reduce((config, aspect, index) => { config[aspect.name.toLowerCase()] = { label: aspect.name, color: aspectsColors[index % aspectsColors.length], }; return config; }, {} as ChartConfig) || {} : {}; const barChartConfig = assessmentStatus === "selesai" ? 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) || {} : {}; // Dropdown State const [isOpen, setIsOpen] = useState(false); const [selectedItem, setSelectedItem] = useState('Hasil Asesmen'); const handleDropdownToggle = () => { setIsOpen((prev) => !prev); }; const handleItemClick = () => { setSelectedItem(prev => prev === 'Hasil Asesmen' ? 'Hasil Terverifikasi' : 'Hasil Asesmen' ); setIsOpen(false); }; // 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}
))}
); } // Mengatur tampilan label sumbu X const customizedAxisTick = (props: any) => { const { x, y, payload } = props; return ( {payload.value.slice(0, 3)} ); }; // Bar Chart Component 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 handlePrintPDF = async () => { const pdfContainer = document.getElementById("pdfContainer"); if (pdfContainer) { // Sembunyikan elemen yang tidak ingin dicetak const buttonPrint = document.getElementById("button-print"); const noPrint = document.getElementById("no-print"); if (buttonPrint) buttonPrint.style.visibility = 'hidden'; if (noPrint) noPrint.style.visibility = 'hidden'; const options = { margin: [10, 10, 10, -220], image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, width: 1510, height: pdfContainer.scrollHeight, ignoreElements: (element: { tagName: string; }) => { // Abaikan elemen
dalam pdfContainer return element.tagName === 'HEADER'; }, }, jsPDF: { unit: 'pt', format: 'a4', orientation: 'portrait', } }; try { const pdfBlob: Blob = await html2pdf() .set(options) .from(pdfContainer) .toPdf() .get('pdf') .then((pdf: any) => { pdf.setProperties({ title: 'Hasil_Asesemen_Level_Maturitas', }); return pdf.output('blob'); }); const pdfURL = URL.createObjectURL(pdfBlob); window.open(pdfURL, '_blank'); } catch (err) { console.error("Error generating PDF:", err); } finally { // Tampilkan kembali elemen yang disembunyikan if (buttonPrint) buttonPrint.style.visibility = 'visible'; if (noPrint) noPrint.style.visibility = 'visible'; } } }; return ( {isMobile && ( setIsLeftSidebarOpen(open)}>

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 }) => (

Level {level}

{title}

{details.map((detail) => (

{detail}

))}
))}
{/* Total verified score */}
{selectedItem === 'Hasil Asesmen' ? ( <>

Nilai Maturitas

{totalScore}

Level Maturitas

{getScoreStyleClass(Number(totalScore), true).descLevel}
) : ( <>

Nilai Maturitas

{totalVerifiedScore}

Level Maturitas

{getScoreStyleClass(Number(totalVerifiedScore), true).descLevel}
)}
)}

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 }) => (

Level {level}

{title}

{details.map((detail) => (

{detail}

))}
))}
{/* Total verified score */}
{selectedItem === 'Hasil Asesmen' ? ( <>

Nilai Maturitas

{totalScore}

Level Maturitas

{getScoreStyleClass(Number(totalScore), true).descLevel}
) : ( <>

Nilai Maturitas

{totalVerifiedScore}

Level Maturitas

{getScoreStyleClass(Number(totalVerifiedScore), true).descLevel}
)}
{/* Konten Header */}
{isSuperAdmin ? (

Detail Hasil Asesmen

Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah

) : (

Dasboard Hasil Tingkat Kematangan Keamanan Cyber

Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah

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

Nama Responden

{assessmentResult?.respondentName}

Posisi

{assessmentResult?.position}

Nama Pengguna

{assessmentResult?.username}

Nama Perusahaan

{assessmentResult?.companyName}

Pengalaman Kerja

{assessmentResult?.workExperience}

Email

{assessmentResult?.email}

No. HP

{assessmentResult?.phoneNumber}

Alamat

{assessmentResult?.address}

Tanggal Asesmen

{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 Asesmen

{assessmentResult?.statusAssessment}

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

Tabel Nilai Hasil Asesmen

{aspectsData?.data?.map((aspect) => ( ))} {aspectsData && Array.from({ length: Math.max(...aspectsData.data.map(aspect => aspect.subAspects.length)) }).map((_, rowIndex) => ( {aspectsData?.data?.map((aspect) => ( {/* Sub-aspect Name Column (No Right Border) */} {/* Sub-aspect Score Column (No Left Border and w-fit for flexible width) */} ))} ))}

{aspect.name}

{formatScore(getAspectScore(aspect.id))}
{aspect.subAspects[rowIndex]?.name || ""} {aspect.subAspects[rowIndex] ? formatScore(getSubAspectScore(aspect.subAspects[rowIndex].id)) : ""}
) : ( <> {/* Verified Result Table */}

Tabel Nilai Hasil Asesmen Terverifikasi

{aspectsData?.data?.map((aspect) => ( ))} {aspectsData && Array.from({ length: Math.max(...aspectsData.data.map(aspect => aspect.subAspects.length)) }).map((_, rowIndex) => ( {aspectsData?.data?.map((aspect) => ( {/* Sub-aspect Name Column (No Right Border) */} {/* Sub-aspect Score Column (No Left Border and w-fit for flexible width) */} ))} ))}

{aspect.name}

{formatScore(getVerifiedAspectScore(aspect.id, assessmentStatus ?? ''))}
{aspect.subAspects[rowIndex]?.name || ""} {aspect.subAspects[rowIndex] ? formatScore(getVerifiedSubAspectScore(aspect.subAspects[rowIndex].id, assessmentStatus ?? '')) : ""}
)} {/* Bar Chart */} {selectedItem === 'Hasil Asesmen' ? ( <> Diagram Nilai Hasil Asesmen ) : ( <> Diagram Nilai Hasil Asesmen Terverifikasi )} {/* Pie Chart */} {selectedItem === 'Hasil Asesmen' ? ( ) : ( )}
); }