fix: redesign assessment result detail and pdf feature
This commit is contained in:
parent
78af6cc8f0
commit
3345e2408a
|
|
@ -72,8 +72,8 @@
|
||||||
--primary-color: #2555FF;
|
--primary-color: #2555FF;
|
||||||
--hover-primary-color: #0032e6;
|
--hover-primary-color: #0032e6;
|
||||||
--levelOne-color: #FF2F32;
|
--levelOne-color: #FF2F32;
|
||||||
--levelTwo-color: #FE6939;
|
--levelTwo-color: #DC6E4B;
|
||||||
--levelThree-color: #EED42D;
|
--levelThree-color: #EBB426;
|
||||||
--levelFour-color: #17C891;
|
--levelFour-color: #41CB91;
|
||||||
--levelFive-color: #0C7C59;
|
--levelFive-color: #0C7C59;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import html2pdf from "html2pdf.js";
|
import html2pdf from "html2pdf.js";
|
||||||
import useAuth from "@/hooks/useAuth";
|
import useAuth from "@/hooks/useAuth";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
|
@ -6,7 +6,6 @@ import { getAllAspectsAverageScore, getAllSubAspectsAverageScore, getAllVerified
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getAssessmentResultByIdQueryOptions, getVerifiedAssessmentResultByIdQueryOptions } from "@/modules/assessmentResultsManagement/queries/assessmentResultsManagaementQueries";
|
import { getAssessmentResultByIdQueryOptions, getVerifiedAssessmentResultByIdQueryOptions } from "@/modules/assessmentResultsManagement/queries/assessmentResultsManagaementQueries";
|
||||||
import { PieChart, Pie, Label, BarChart, Bar, CartesianGrid, XAxis, YAxis } from "recharts";
|
import { PieChart, Pie, Label, BarChart, Bar, CartesianGrid, XAxis, YAxis } from "recharts";
|
||||||
import { PolarAngleAxis, PolarRadiusAxis, PolarGrid, Radar, RadarChart } from "recharts"
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
@ -20,8 +19,10 @@ import {
|
||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
} from "@/shadcn/components/ui/chart"
|
} from "@/shadcn/components/ui/chart"
|
||||||
import { aspectQueryOptions } from "@/modules/aspectManagement/queries/aspectQueries";
|
import { aspectQueryOptions } from "@/modules/aspectManagement/queries/aspectQueries";
|
||||||
import { TbChevronDown, TbChevronLeft, TbChevronUp } from "react-icons/tb";
|
import { TbChevronDown, TbChevronLeft, TbChevronUp, TbFileTypePdf } from "react-icons/tb";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/shadcn/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/shadcn/components/ui/dropdown-menu";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
const getQueryParam = (param: string) => {
|
const getQueryParam = (param: string) => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -83,7 +84,6 @@ export default function AssessmentResultPage() {
|
||||||
if (score === undefined || score === null) return { color: 'grey' };
|
if (score === undefined || score === null) return { color: 'grey' };
|
||||||
|
|
||||||
let colorVar = '--levelOne-color';
|
let colorVar = '--levelOne-color';
|
||||||
let textColor = 'white';
|
|
||||||
let descLevel = '1';
|
let descLevel = '1';
|
||||||
|
|
||||||
if (score >= 1.50 && score < 2.50) {
|
if (score >= 1.50 && score < 2.50) {
|
||||||
|
|
@ -92,7 +92,7 @@ export default function AssessmentResultPage() {
|
||||||
} else if (score >= 2.50 && score < 3.50) {
|
} else if (score >= 2.50 && score < 3.50) {
|
||||||
colorVar = '--levelThree-color';
|
colorVar = '--levelThree-color';
|
||||||
descLevel = '3';
|
descLevel = '3';
|
||||||
} else if (score >= 3.50 && score < 4.49) {
|
} else if (score >= 3.50 && score < 4.50) {
|
||||||
colorVar = '--levelFour-color';
|
colorVar = '--levelFour-color';
|
||||||
descLevel = '4';
|
descLevel = '4';
|
||||||
} else if (score >= 4.50 && score <= 5) {
|
} else if (score >= 4.50 && score <= 5) {
|
||||||
|
|
@ -101,17 +101,17 @@ export default function AssessmentResultPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return isBg
|
return isBg
|
||||||
? { backgroundColor: `var(${colorVar})`, color: textColor, descLevel }
|
? { backgroundColor: `var(${colorVar})`, descLevel }
|
||||||
: { color: `var(${colorVar})`, descLevel };
|
: { color: `var(${colorVar})`, descLevel };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Warna aspek
|
// Warna aspek
|
||||||
const aspectsColors = [
|
const aspectsColors = [
|
||||||
"#DBED9B",
|
"#37DCCC",
|
||||||
"#FF3F9F",
|
"#FF8C8C",
|
||||||
"#877BDF",
|
"#51D0FD",
|
||||||
"#CFAF49",
|
"#FEA350",
|
||||||
"#5FD4E7",
|
"#AD8AFC",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Data diagram
|
// Data diagram
|
||||||
|
|
@ -190,27 +190,27 @@ export default function AssessmentResultPage() {
|
||||||
|
|
||||||
// Dropdown State
|
// Dropdown State
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [selectedItem, setSelectedItem] = useState('Hasil Sementara');
|
const [selectedItem, setSelectedItem] = useState('Hasil Assessment');
|
||||||
|
|
||||||
const handleDropdownToggle = () => {
|
const handleDropdownToggle = () => {
|
||||||
setIsOpen((prev) => !prev);
|
setIsOpen((prev) => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
setSelectedItem(prev =>
|
setSelectedItem(prev =>
|
||||||
prev === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara'
|
prev === 'Hasil Assessment' ? 'Hasil Terverifikasi' : 'Hasil Assessment'
|
||||||
);
|
);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pie Chart Component
|
// Pie Chart Component
|
||||||
function PieChartComponent({ chartData, totalScore, chartConfig }: { chartData: { aspectName: string, score: number, fill: string }[], totalScore: number, chartConfig: ChartConfig }) {
|
function PieChartComponent({ chartData, totalScore, chartConfig }: { chartData: { aspectName: string, score: number, fill: string }[], totalScore: number, chartConfig: ChartConfig }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full border-none">
|
<div className="flex flex-col w-full border-none">
|
||||||
<div className="flex-1 pb-0 w-72">
|
<div className="flex-1 pb-0 w-72">
|
||||||
<ChartContainer
|
<ChartContainer
|
||||||
config={chartConfig}
|
config={chartConfig}
|
||||||
className="mx-auto aspect-square max-h-64"
|
className="-ml-6 aspect-square max-h-64"
|
||||||
>
|
>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
|
|
@ -270,65 +270,40 @@ export default function AssessmentResultPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legend */}
|
{/* Legend */}
|
||||||
<div className="flex-col gap-2 text-sm justify-center items-start">
|
<div className="grid grid-cols-2 gap-x-4 text-xs">
|
||||||
{chartData.map((entry, index) => (
|
<div className="flex flex-col gap-2">
|
||||||
<div key={index} className="flex items-center gap-2">
|
{chartData.slice(0, 3).map((entry, index) => (
|
||||||
<span
|
<div key={index} className="flex items-center gap-2">
|
||||||
className="w-4 h-4"
|
<span
|
||||||
style={{ backgroundColor: entry.fill }}
|
className="w-4 h-4"
|
||||||
/>
|
style={{ backgroundColor: entry.fill }}
|
||||||
<span className="font-medium">{entry.aspectName}</span>
|
/>
|
||||||
</div>
|
<span className="font-medium">{entry.aspectName}</span>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{chartData.slice(3).map((entry, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="w-4 h-4"
|
||||||
|
style={{ backgroundColor: entry.fill }}
|
||||||
|
/>
|
||||||
|
<span className="font-medium">{entry.aspectName}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Radar Chart Component
|
|
||||||
function RadarChartComponent({ chartData, chartConfig }: { chartData: { aspectName: string, score: number }[], chartConfig: ChartConfig }) {
|
|
||||||
return (
|
|
||||||
<div className="flex-1 pb-0">
|
|
||||||
<ChartContainer
|
|
||||||
config={chartConfig}
|
|
||||||
className="mx-auto max-h-[250px]"
|
|
||||||
>
|
|
||||||
<RadarChart data={chartData}>
|
|
||||||
<ChartTooltip
|
|
||||||
cursor={false}
|
|
||||||
content={({ active, payload }) => {
|
|
||||||
if (active && payload && payload.length > 0) {
|
|
||||||
const { aspectName, score } = payload[0].payload;
|
|
||||||
return (
|
|
||||||
<div className="tooltip bg-white p-1 rounded-md">
|
|
||||||
<p>{`${aspectName} : ${score}`}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PolarAngleAxis dataKey="aspectName" tick={{ fontSize: 10 }} stroke="black" />
|
|
||||||
<PolarRadiusAxis angle={90} domain={[0, 5]} tick={{ fontSize: 10 }} tickCount={6} stroke="black" />
|
|
||||||
<PolarGrid radialLines={true} />
|
|
||||||
<Radar
|
|
||||||
dataKey="score"
|
|
||||||
fillOpacity={0}
|
|
||||||
stroke="#007BFF"
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
</RadarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bar Chart Component
|
// Bar Chart Component
|
||||||
function BarChartComponent({ barChartData, barChartConfig }: { barChartData: { subAspectName: string, score: number, fill: string, aspectId: string, aspectName: string }[], barChartConfig: ChartConfig }) {
|
function BarChartComponent({ barChartData, barChartConfig }: { barChartData: { subAspectName: string, score: number, fill: string, aspectId: string, aspectName: string }[], barChartConfig: ChartConfig }) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full h-full">
|
||||||
<ChartContainer config={barChartConfig}>
|
<ChartContainer config={barChartConfig}>
|
||||||
<BarChart accessibilityLayer data={barChartData} margin={{ bottom: 120 }}>
|
<BarChart accessibilityLayer data={barChartData} margin={{ top: 5, bottom: 128, left: -20 }}>
|
||||||
<CartesianGrid vertical={false} horizontal={true} />
|
<CartesianGrid vertical={false} horizontal={true} />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="subAspectName"
|
dataKey="subAspectName"
|
||||||
|
|
@ -338,7 +313,11 @@ export default function AssessmentResultPage() {
|
||||||
interval={0}
|
interval={0}
|
||||||
tick={customizedAxisTick}
|
tick={customizedAxisTick}
|
||||||
/>
|
/>
|
||||||
<YAxis domain={[0, 5]} tickCount={6} />
|
<YAxis
|
||||||
|
domain={[0, 5]}
|
||||||
|
tickFormatter={(value) => value.toFixed(1)}
|
||||||
|
ticks={[0, 0,5, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]}
|
||||||
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
cursor={false}
|
cursor={false}
|
||||||
content={({ active, payload }) => {
|
content={({ active, payload }) => {
|
||||||
|
|
@ -360,79 +339,76 @@ export default function AssessmentResultPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePrintPDF = async (isSuperAdmin: boolean) => {
|
const handlePrintPDF = async () => {
|
||||||
const pdfContainer = document.getElementById("pdfContainer");
|
const pdfContainer = document.getElementById("pdfContainer");
|
||||||
|
|
||||||
if (pdfContainer) {
|
if (pdfContainer) {
|
||||||
// Sembunyikan elemen yang tidak ingin dicetak
|
// Sembunyikan elemen yang tidak ingin dicetak
|
||||||
const buttonPrint = document.getElementById("button-print");
|
const buttonPrint = document.getElementById("button-print");
|
||||||
const noPrint = document.getElementById("no-print");
|
const noPrint = document.getElementById("no-print");
|
||||||
if (buttonPrint) buttonPrint.style.visibility = 'hidden';
|
if (buttonPrint) buttonPrint.style.visibility = 'hidden';
|
||||||
if (noPrint) noPrint.style.visibility = 'hidden';
|
if (noPrint) noPrint.style.visibility = 'hidden';
|
||||||
|
|
||||||
const pdfMargin = isSuperAdmin ? [10, 10, 10, -220] : [10, 10, 10, -230];
|
const options = {
|
||||||
const pdfWidth = isSuperAdmin ? 1575 : 1550;
|
margin: [10, 10, 10, -220],
|
||||||
|
image: { type: 'jpeg', quality: 0.98 },
|
||||||
const options = {
|
html2canvas: {
|
||||||
margin: pdfMargin,
|
scale: 2,
|
||||||
image: { type: 'jpeg', quality: 0.98 },
|
width: 1510,
|
||||||
html2canvas: {
|
height: pdfContainer.scrollHeight,
|
||||||
scale: 2,
|
},
|
||||||
width: pdfWidth, // Lebar tetap untuk ukuran A4 (landscape)
|
jsPDF: {
|
||||||
height: pdfContainer.scrollHeight, // Tinggi dinamis sesuai konten
|
unit: 'pt',
|
||||||
},
|
format: 'a4',
|
||||||
jsPDF: {
|
orientation: 'portrait',
|
||||||
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';
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Card className="flex flex-row w-full h-full border-none shadow-none" id="pdfContainer">
|
<Card className="flex flex-row w-full h-full border-none shadow-none" id="pdfContainer">
|
||||||
<div className="flex flex-col w-fit h-fit border-none shadow-none -ml-1 -pr-2">
|
<div className="flex flex-col w-fit h-fit border-r shadow-none -mt-6 pt-6">
|
||||||
<p className="font-bold mt-2 ml-2">Tingkatan Level Maturitas</p>
|
<p className="font-bold mb-4 text-lg">Tingkatan Level Maturitas</p>
|
||||||
<div className="flex flex-col mr-5 -ml-5 h-full">
|
<div className="flex flex-col mr-2 h-full">
|
||||||
{[
|
{[
|
||||||
{ level: 5, colorVar: '--levelFive-color', title: 'Implementasi Optimal', details: ['Otomatisasi', 'Terintegrasi', 'Membudaya'], textColor: 'white' },
|
{ 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: 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: 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: 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' }
|
{ level: 1, colorVar: '--levelOne-color', title: 'Implementasi Awal', details: ['Tidak Terukur', 'Tidak Konsisten', 'Risiko Tinggi'], textColor: 'white' }
|
||||||
].map(({ level, colorVar, title, details, textColor }, index) => (
|
].map(({ level, colorVar, title, details }) => (
|
||||||
<div key={level} className={`flex flex-row h-full border-none ${index > 0 ? '-mt-10' : ''}`}>
|
<div key={level} className="flex flex-row h-full border-none">
|
||||||
<svg className="w-30 h-32 pb-5" style={{ color: `var(${colorVar})` }} fill="currentColor" viewBox="0 0 24 24">
|
<div
|
||||||
<polygon points="12,4 19,10 19,24 12,19 5,24 5,10" />
|
className="w-20 h-20 text-white font-medium text-lg flex justify-center items-center"
|
||||||
<text x="12" y="16" textAnchor="middle" fill={textColor} fontSize="3" fontWeight="bold">
|
style={{ background: `var(${colorVar})` }}
|
||||||
Level {level}
|
>
|
||||||
</text>
|
<p className="text-center">Level {level}</p>
|
||||||
</svg>
|
</div>
|
||||||
<div className="flex flex-col items-start justify-center -ml-4">
|
<div className="flex flex-col items-start justify-center p-2">
|
||||||
<p className="text-xs font-bold whitespace-nowrap">{title}</p>
|
<p className="text-xs font-bold whitespace-nowrap">{title}</p>
|
||||||
{details.map((detail) => (
|
{details.map((detail) => (
|
||||||
<p key={detail} className="text-xs">{detail}</p>
|
<p key={detail} className="text-xs">{detail}</p>
|
||||||
|
|
@ -441,10 +417,37 @@ export default function AssessmentResultPage() {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Total verified score */}
|
||||||
|
<div className="pt-14">
|
||||||
|
{selectedItem === 'Hasil Assessment' ? (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-row h-16 border-t font-semibold justify-between items-center gap-2 pl-8 pr-6 -ml-6">
|
||||||
|
<p className="text-lg">Nilai Maturitas</p>
|
||||||
|
<span className="text-xl">{totalScore}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row h-16 font-semibold justify-between items-center gap-2 pl-8 pr-6 text-white -ml-6" style={getScoreStyleClass(Number(totalScore), true)}>
|
||||||
|
<p className="text-lg">Level Maturitas</p>
|
||||||
|
<span className="ml-2 text-xl">{getScoreStyleClass(Number(totalScore), true).descLevel}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-row h-16 mt-12 border-t font-semibold justify-between items-center gap-2 pl-8 pr-6 -ml-6">
|
||||||
|
<p className="text-lg">Nilai Maturitas</p>
|
||||||
|
<span className="text-xl">{totalVerifiedScore}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row h-16 font-semibold justify-between items-center gap-2 pl-8 pr-6 text-white -ml-6" style={getScoreStyleClass(Number(totalVerifiedScore), true)}>
|
||||||
|
<p className="text-lg">Level Maturitas</p>
|
||||||
|
<span className="ml-2 text-xl">{getScoreStyleClass(Number(totalVerifiedScore), true).descLevel}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="flex flex-col w-full h-fit border-none shadow-none -mt-6 -mr-5 p-4 bg-stone-50 overflow-hidden">
|
<Card className="flex flex-col w-full h-fit border-none shadow-none -mt-6 -mr-5 bg-stone-50 overflow-hidden">
|
||||||
<div className="flex flex-col w-full h-fit mb-6 justify-center items-start">
|
<div className="flex flex-col w-full h-fit mb-6 justify-center items-start bg-white p-4 border-b">
|
||||||
{/* Konten Header */}
|
{/* Konten Header */}
|
||||||
<div className="flex justify-between items-center w-full">
|
<div className="flex justify-between items-center w-full">
|
||||||
{isSuperAdmin ? (
|
{isSuperAdmin ? (
|
||||||
|
|
@ -461,22 +464,16 @@ export default function AssessmentResultPage() {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-2xl font-bold">Cyber Security Maturity Level Dashboard</p>
|
<p className="text-2xl font-bold">Dasboard Hasil Tingkat Kematangan Keamanan Cyber</p>
|
||||||
<p className="text-xs text-muted-foreground">Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah</p>
|
<p className="text-xs text-muted-foreground">Kelola dan Pantau Semua Permohonan Asesmen Dengan Mudah</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-row gap-2" id="button-print">
|
<div className="flex flex-row gap-2" id="button-print">
|
||||||
<button
|
|
||||||
onClick={() => handlePrintPDF(isSuperAdmin)}
|
|
||||||
className="bg-blue-600 text-white flex w-fit p-2 rounded-sm text-sm items-start justify-between outline-none ring-offset-0"
|
|
||||||
>
|
|
||||||
Cetak PDF
|
|
||||||
</button>
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
className="bg-blue-600 text-white flex w-44 p-2 pl-4 rounded-sm text-sm items-start justify-between outline-none ring-offset-0"
|
className="text-black flex w-44 p-2 pl-4 rounded-lg text-sm items-start justify-between border outline-none ring-offset-0"
|
||||||
onClick={handleDropdownToggle}
|
onClick={handleDropdownToggle}
|
||||||
>
|
>
|
||||||
{selectedItem}
|
{selectedItem}
|
||||||
|
|
@ -489,31 +486,38 @@ export default function AssessmentResultPage() {
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<DropdownMenuContent className="bg-white text-black flex w-44 rounded-sm text-sm items-start">
|
<DropdownMenuContent className="bg-white text-black flex w-44 rounded-sm text-sm items-start">
|
||||||
<DropdownMenuItem className="w-full" onClick={handleItemClick}>
|
<DropdownMenuItem className="w-full" onClick={handleItemClick}>
|
||||||
{selectedItem === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara'}
|
{selectedItem === 'Hasil Assessment' ? 'Hasil Terverifikasi' : 'Hasil Assessment'}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
)}
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => handlePrintPDF()}
|
||||||
|
className="bg-blue-600 text-white flex w-fit py-2 px-4 rounded-lg text-sm items-start justify-between outline-none ring-offset-0"
|
||||||
|
>
|
||||||
|
Cetak PDF
|
||||||
|
<TbFileTypePdf size={20} className="ml-2" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSuperAdmin &&
|
{isSuperAdmin &&
|
||||||
<Card className="flex flex-col w-full h-full mb-6 justify-center items-start">
|
<Card className="flex flex-col h-full mb-6 justify-center items-start mx-4">
|
||||||
<div className="flex flex-col border-b w-full p-4">
|
<div className="flex lg:flex-row flex-col text-xs h-full w-full justify-between p-6">
|
||||||
<p className="text-lg font-bold">{assessmentResult?.respondentName}</p>
|
|
||||||
<p className="text-sm">{assessmentResult?.position}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex lg:flex-row flex-col text-xs h-full w-full justify-between p-4">
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Username</p>
|
<p className="text-muted-foreground">Nama Responden</p>
|
||||||
<p>{assessmentResult?.username}</p>
|
<p>{assessmentResult?.respondentName}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Email</p>
|
<p className="text-muted-foreground">Posisi</p>
|
||||||
<p>{assessmentResult?.email}</p>
|
<p>{assessmentResult?.position}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">Nama Pengguna</p>
|
||||||
|
<p>{assessmentResult?.username}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
|
|
@ -525,6 +529,10 @@ export default function AssessmentResultPage() {
|
||||||
<p className="text-muted-foreground">Pengalaman Kerja</p>
|
<p className="text-muted-foreground">Pengalaman Kerja</p>
|
||||||
<p>{assessmentResult?.workExperience}</p>
|
<p>{assessmentResult?.workExperience}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">Email</p>
|
||||||
|
<p>{assessmentResult?.email}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -535,8 +543,6 @@ export default function AssessmentResultPage() {
|
||||||
<p className="text-muted-foreground">Alamat</p>
|
<p className="text-muted-foreground">Alamat</p>
|
||||||
<p>{assessmentResult?.address}</p>
|
<p>{assessmentResult?.address}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Tanggal Assessment</p>
|
<p className="text-muted-foreground">Tanggal Assessment</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -557,8 +563,10 @@ export default function AssessmentResultPage() {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">Status Verifikasi</p>
|
<p className="text-muted-foreground">Status Assesment</p>
|
||||||
<p>{assessmentResult?.statusAssessment}</p>
|
<p>{assessmentResult?.statusAssessment}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -567,86 +575,137 @@ export default function AssessmentResultPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
{/* Conditional rendering based on selectedItem */}
|
{/* Conditional rendering based on selectedItem */}
|
||||||
{selectedItem === 'Hasil Sementara' ? (
|
{selectedItem === 'Hasil Assessment' ? (
|
||||||
<>
|
<>
|
||||||
{/* Score Table */}
|
{/* Score Table */}
|
||||||
<p className="text-lg font-bold">Tabel Level Maturitas</p>
|
<p className="text-lg font-bold px-4">Tabel Nilai Hasil Assessment</p>
|
||||||
<Card className="flex flex-col w-full h-fit my-2 mb-6 overflow-hidden text-center">
|
<Card className="flex flex-col h-fit my-2 mb-6 overflow-hidden text-center mx-4">
|
||||||
<div className="flex flex-row w-full">
|
<table className="w-full table-fixed border-collapse border rounded-lg overflow-hidden">
|
||||||
{aspectsData?.data?.map((aspect) => (
|
<thead>
|
||||||
<div key={aspect.id} className="flex-col bg-white w-full h-full border-t border-x">
|
<tr>
|
||||||
<div className="flex flex-col font-bold items-center justify-center p-2 h-full w-full border-b" style={getScoreStyleClass(getAspectScore(aspect.id))}>
|
{aspectsData?.data?.map((aspect) => (
|
||||||
<p className="text-sm text-black">{aspect.name}</p>
|
<th
|
||||||
<span className="text-2xl">{formatScore(getAspectScore(aspect.id))}</span>
|
key={aspect.id}
|
||||||
</div>
|
colSpan={2}
|
||||||
{aspect.subAspects.map((subAspect) => (
|
className="text-center font-normal bg-white border border-gray-200 p-4 w-1/5"
|
||||||
<div key={subAspect.id} className="flex flex-col gap-2 p-2 h-full w-full">
|
>
|
||||||
<div className="flex flex-row gap-2 justify-between h-full w-full">
|
<div className="flex flex-col items-start">
|
||||||
<p className="text-xs text-muted-foreground text-left">{subAspect.name}</p>
|
<p className="text-sm text-black">{aspect.name}</p>
|
||||||
<span className="text-xs font-bold">{formatScore(getSubAspectScore(subAspect.id))}</span>
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"text-3xl font-bold",
|
||||||
|
)}
|
||||||
|
style={getScoreStyleClass(getAspectScore(aspect.id))}
|
||||||
|
>
|
||||||
|
{formatScore(getAspectScore(aspect.id))}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</th>
|
||||||
))}
|
))}
|
||||||
</div>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
|
{aspectsData && Array.from({ length: Math.max(...aspectsData.data.map(aspect => aspect.subAspects.length)) }).map((_, rowIndex) => (
|
||||||
{/* Total score */}
|
<tr key={rowIndex} className={rowIndex % 2 === 0 ? "bg-slate-100" : "bg-white"}>
|
||||||
<div className="flex flex-row w-full h-fit justify-center font-bold text-3xl gap-0.5">
|
{aspectsData?.data?.map((aspect) => (
|
||||||
<div className="flex flex-row w-full h-full p-3 justify-center gap-2 font-bold text-3xl bg-blue-600 text-white">
|
<React.Fragment key={aspect.id}>
|
||||||
<p className="text-center">Nilai Maturitas:</p>
|
{/* Sub-aspect Name Column (No Right Border) */}
|
||||||
<span>{totalScore}</span>
|
<td className="text-xs text-black p-2 border-t border-l border-b border-gray-200 w-full text-left">
|
||||||
</div>
|
{aspect.subAspects[rowIndex]?.name || ""}
|
||||||
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}>
|
</td>
|
||||||
<p className="text-center">Level Maturitas:</p>
|
{/* Sub-aspect Score Column (No Left Border and w-fit for flexible width) */}
|
||||||
<span className="ml-2">{getScoreStyleClass(Number(totalScore), true).descLevel}</span>
|
<td className="text-sm font-bold text-right p-2 border-t border-b border-r border-gray-200 w-fit">
|
||||||
</div>
|
{aspect.subAspects[rowIndex] ? formatScore(getSubAspectScore(aspect.subAspects[rowIndex].id)) : ""}
|
||||||
</div>
|
</td>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Verified Result Table */}
|
{/* Verified Result Table */}
|
||||||
<p className="text-lg font-bold">Tabel Level Maturitas Terverifikasi</p>
|
<p className="text-lg font-bold px-4">Tabel Nilai Hasil Assessment Terverifikasi</p>
|
||||||
<Card className="flex flex-col w-full h-fit my-2 mb-8 overflow-hidden border-y text-center">
|
<Card className="flex flex-col h-fit my-2 mb-6 overflow-hidden border-y text-center mx-4">
|
||||||
<div className="flex flex-row">
|
<table className="w-full table-fixed border-collapse border rounded-lg overflow-hidden">
|
||||||
{aspectsData?.data?.map((aspect) => (
|
<thead>
|
||||||
<div key={aspect.id} className="flex-col bg-white w-full h-full border-x border-t">
|
<tr>
|
||||||
<div className="flex flex-col font-bold items-center justify-center p-2 h-full w-full border-b" style={getScoreStyleClass(getAspectScore(aspect.id))}>
|
{aspectsData?.data?.map((aspect) => (
|
||||||
<p className="text-sm text-black">{aspect.name}</p>
|
<th
|
||||||
<span className="text-2xl">{formatScore(getVerifiedAspectScore(aspect.id))}</span>
|
key={aspect.id}
|
||||||
</div>
|
colSpan={2}
|
||||||
{aspect.subAspects.map((subAspect) => (
|
className="text-center font-normal bg-white border border-gray-200 p-4 w-1/5"
|
||||||
<div key={subAspect.id} className="flex flex-col gap-2 p-2 h-full w-full">
|
>
|
||||||
<div className="flex flex-row gap-2 justify-between h-full w-full">
|
<div className="flex flex-col items-start">
|
||||||
<p className="text-xs text-muted-foreground text-left">{subAspect.name}</p>
|
<p className="text-sm text-black">{aspect.name}</p>
|
||||||
<span className="text-xs font-bold">{formatScore(getVerifiedSubAspectScore(subAspect.id))}</span>
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"text-3xl font-bold",
|
||||||
|
)}
|
||||||
|
style={getScoreStyleClass(getVerifiedAspectScore(aspect.id))}
|
||||||
|
>
|
||||||
|
{formatScore(getVerifiedAspectScore(aspect.id))}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</th>
|
||||||
))}
|
))}
|
||||||
</div>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
|
{aspectsData && Array.from({ length: Math.max(...aspectsData.data.map(aspect => aspect.subAspects.length)) }).map((_, rowIndex) => (
|
||||||
{/* Total verified score */}
|
<tr key={rowIndex} className={rowIndex % 2 === 0 ? "bg-slate-100" : "bg-white"}>
|
||||||
<div className="flex flex-row w-full h-fit justify-center font-bold text-3xl gap-0.5">
|
{aspectsData?.data?.map((aspect) => (
|
||||||
<div className="flex flex-row w-full h-full p-3 justify-center gap-2 font-bold text-3xl bg-blue-600 text-white">
|
<React.Fragment key={aspect.id}>
|
||||||
<p className="text-center">Nilai Maturitas:</p>
|
{/* Sub-aspect Name Column (No Right Border) */}
|
||||||
<span>{totalScore}</span>
|
<td className="text-xs text-black p-2 border-t border-l border-b border-gray-200 w-full text-left">
|
||||||
</div>
|
{aspect.subAspects[rowIndex]?.name || ""}
|
||||||
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}>
|
</td>
|
||||||
<p className="text-center">Level Maturitas:</p>
|
{/* Sub-aspect Score Column (No Left Border and w-fit for flexible width) */}
|
||||||
<span className="ml-2">{getScoreStyleClass(Number(totalScore), true).descLevel}</span>
|
<td className="text-sm font-bold text-right p-2 border-t border-b border-r border-gray-200 w-fit">
|
||||||
</div>
|
{aspect.subAspects[rowIndex] ? formatScore(getVerifiedSubAspectScore(aspect.subAspects[rowIndex].id)) : ""}
|
||||||
</div>
|
</td>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card className="flex flex-col lg:flex-row gap-4 border-none shadow-none w-full" id="target-card">
|
<Card className="flex flex-row lg:flex-row gap-4 border-none shadow-none mx-4 bg-transparent">
|
||||||
|
{/* Bar Chart */}
|
||||||
|
{selectedItem === 'Hasil Assessment' ? (
|
||||||
|
<>
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader className="items-start">
|
||||||
|
<CardTitle className="text-lg">Diagram Nilai Hasil Assessment</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<BarChartComponent barChartData={sortedBarChartData} barChartConfig={barChartConfig} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader className="items-start">
|
||||||
|
<CardTitle className="text-lg">Diagram Nilai Hasil Assessment Terverifikasi</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<BarChartComponent barChartData={sortedVerifiedBarChartData} barChartConfig={barChartConfig} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Pie Chart */}
|
{/* Pie Chart */}
|
||||||
{selectedItem === 'Hasil Sementara' ? (
|
{selectedItem === 'Hasil Assessment' ? (
|
||||||
<Card className="flex flex-col w-full">
|
<Card className="flex flex-col w-64">
|
||||||
<CardHeader className="items-start pb-0">
|
<CardHeader className="items-start pb-0">
|
||||||
<CardTitle className="text-lg">Diagram Lingkaran</CardTitle>
|
<CardTitle className="text-lg">Diagram Lingkaran</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
@ -659,7 +718,7 @@ export default function AssessmentResultPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<Card className="flex flex-col w-full">
|
<Card className="flex flex-col w-64">
|
||||||
<CardHeader className="items-start pb-0">
|
<CardHeader className="items-start pb-0">
|
||||||
<CardTitle className="text-lg">Diagram Lingkaran Terverifikasi</CardTitle>
|
<CardTitle className="text-lg">Diagram Lingkaran Terverifikasi</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
@ -672,54 +731,6 @@ export default function AssessmentResultPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Radar Chart */}
|
|
||||||
{selectedItem === 'Hasil Sementara' ? (
|
|
||||||
<Card className="flex flex-col w-full">
|
|
||||||
<CardHeader className="items-start pb-0">
|
|
||||||
<CardTitle className="text-lg">Diagram Radar</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<RadarChartComponent chartData={chartData} chartConfig={chartConfig} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
) : (
|
|
||||||
<Card className="flex flex-col w-full">
|
|
||||||
<CardHeader className="items-start pb-0">
|
|
||||||
<CardTitle className="text-lg">Diagram Radar Terverifikasi</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<RadarChartComponent chartData={verifiedChartData} chartConfig={chartConfig} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="flex w-full h-fit border mt-4" id="bar-chart">
|
|
||||||
{/* Bar Chart */}
|
|
||||||
{selectedItem === 'Hasil Sementara' ? (
|
|
||||||
<>
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardHeader className="items-start">
|
|
||||||
<CardTitle className="text-lg">Diagram Batang</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<BarChartComponent barChartData={sortedBarChartData} barChartConfig={barChartConfig} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardHeader className="items-start">
|
|
||||||
<CardTitle className="text-lg">Diagram Batang Terverifikasi</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<BarChartComponent barChartData={sortedVerifiedBarChartData} barChartConfig={barChartConfig} />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</Card>
|
</Card>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user