Pull Request branch dev-clone to main #1

Merged
gitea merged 429 commits from dev-clone into main 2024-12-23 09:31:34 +00:00
Showing only changes of commit ad775c5778 - Show all commits

View File

@ -1,4 +1,4 @@
import { SetStateAction, useEffect, useRef, useState } from "react"; import { useEffect, useRef, 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";
@ -10,7 +10,6 @@ import { PolarAngleAxis, PolarRadiusAxis, PolarGrid, Radar, RadarChart } from "r
import { import {
Card, Card,
CardContent, CardContent,
CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/shadcn/components/ui/card" } from "@/shadcn/components/ui/card"
@ -44,6 +43,7 @@ export default function AssessmentResultPage() {
setAssessmentId(id ?? undefined); setAssessmentId(id ?? undefined);
}, []); }, []);
//fetch data from API
const { data: aspectsData } = useQuery(aspectQueryOptions(0, 10)); const { data: aspectsData } = useQuery(aspectQueryOptions(0, 10));
const { data: assessmentResult } = useQuery(getAssessmentResultByIdQueryOptions(assessmentId)); const { data: assessmentResult } = useQuery(getAssessmentResultByIdQueryOptions(assessmentId));
const { data: verifiedAssessmentResult } = useQuery(getVerifiedAssessmentResultByIdQueryOptions(assessmentId)); const { data: verifiedAssessmentResult } = useQuery(getVerifiedAssessmentResultByIdQueryOptions(assessmentId));
@ -74,30 +74,38 @@ export default function AssessmentResultPage() {
return !isNaN(parsedScore) ? parsedScore.toFixed(2) : '0'; return !isNaN(parsedScore) ? parsedScore.toFixed(2) : '0';
}; };
// Total score
const totalScore = parseFloat(formatScore(assessmentResult?.assessmentsResult)); const totalScore = parseFloat(formatScore(assessmentResult?.assessmentsResult));
const totalVerifiedScore = parseFloat(formatScore(verifiedAssessmentResult?.verifiedAssessmentsResult)); const totalVerifiedScore = parseFloat(formatScore(verifiedAssessmentResult?.verifiedAssessmentsResult));
// Mengatur warna dan level maturitas berdasarkan skor
const getScoreStyleClass = (score: number | undefined, isBg: boolean = false) => { const getScoreStyleClass = (score: number | undefined, isBg: boolean = false) => {
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 textColor = 'white';
let descLevel = '1';
if (score >= 1.50 && score < 2.50) { if (score >= 1.50 && score < 2.50) {
colorVar = '--levelTwo-color'; colorVar = '--levelTwo-color';
descLevel = '2';
} else if (score >= 2.50 && score < 3.50) { } else if (score >= 2.50 && score < 3.50) {
colorVar = '--levelThree-color'; colorVar = '--levelThree-color';
descLevel = '3';
} else if (score >= 3.50 && score < 4.49) { } else if (score >= 3.50 && score < 4.49) {
colorVar = '--levelFour-color'; colorVar = '--levelFour-color';
descLevel = '4';
} else if (score >= 4.50 && score <= 5) { } else if (score >= 4.50 && score <= 5) {
colorVar = '--levelFive-color'; colorVar = '--levelFive-color';
descLevel = '5';
} }
return isBg return isBg
? { backgroundColor: `var(${colorVar})`, color: textColor } ? { backgroundColor: `var(${colorVar})`, color: textColor, descLevel }
: { color: `var(${colorVar})` }; : { color: `var(${colorVar})`, descLevel };
}; };
// Warna aspek
const aspectsColors = [ const aspectsColors = [
"#DBED9B", "#DBED9B",
"#FF3F9F", "#FF3F9F",
@ -106,6 +114,7 @@ export default function AssessmentResultPage() {
"#5FD4E7", "#5FD4E7",
]; ];
// Data diagram
const chartData = aspectsData?.data?.map((aspect, index) => ({ const chartData = aspectsData?.data?.map((aspect, index) => ({
aspectName: aspect.name, aspectName: aspect.name,
score: Number(formatScore(getAspectScore(aspect.id))), score: Number(formatScore(getAspectScore(aspect.id))),
@ -159,6 +168,7 @@ export default function AssessmentResultPage() {
return config; return config;
}, {} as ChartConfig) || {}; }, {} as ChartConfig) || {};
// Mengatur tampilan label sumbu X
const customizedAxisTick = (props: any) => { const customizedAxisTick = (props: any) => {
const { x, y, payload } = props; const { x, y, payload } = props;
return ( return (
@ -178,6 +188,7 @@ export default function AssessmentResultPage() {
); );
}; };
// Dropdown State
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState('Hasil Sementara'); const [selectedItem, setSelectedItem] = useState('Hasil Sementara');
@ -189,7 +200,7 @@ export default function AssessmentResultPage() {
setSelectedItem(prev => setSelectedItem(prev =>
prev === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara' prev === 'Hasil Sementara' ? 'Hasil Terverifikasi' : 'Hasil Sementara'
); );
setIsOpen(false); // Tutup dropdown setelah klik item setIsOpen(false);
}; };
// Pie Chart Component // Pie Chart Component
@ -274,6 +285,7 @@ export default function AssessmentResultPage() {
); );
} }
// Radar Chart Component
function RadarChartComponent({ chartData, chartConfig }: { chartData: { aspectName: string, score: number }[], chartConfig: ChartConfig }) { function RadarChartComponent({ chartData, chartConfig }: { chartData: { aspectName: string, score: number }[], chartConfig: ChartConfig }) {
return ( return (
<div className="flex-1 pb-0"> <div className="flex-1 pb-0">
@ -311,6 +323,7 @@ export default function AssessmentResultPage() {
); );
} }
// 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">
@ -347,28 +360,33 @@ export default function AssessmentResultPage() {
); );
} }
const cardRef = useRef<HTMLDivElement | null>(null); // Create a ref for your component const handlePrintPDF = async (isSuperAdmin: boolean) => {
const pdfContainer = document.getElementById("pdfContainer");
const targetCard = document.getElementById("target-card");
const barChart = document.getElementById("bar-chart");
const handlePrintPDF = async () => { if (pdfContainer && targetCard && barChart) {
const element = cardRef.current;
if (element) {
// 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'; // Menggunakan visibility untuk tetap menjaga ruang if (buttonPrint) buttonPrint.style.visibility = 'hidden';
if (noPrint) noPrint.style.visibility = 'hidden'; // Menggunakan visibility untuk tetap menjaga ruang if (noPrint) noPrint.style.visibility = 'hidden';
// Pastikan elemen memiliki lebar 100% // Tentukan marginTop sesuai dengan isSuperAdmin
element.style.width = '100%'; // Mengatur lebar konten const originalMarginTop = targetCard.style.marginTop || "0px";
targetCard.style.marginTop = isSuperAdmin ? "190px" : "0px";
// Tentukan marginTop sesuai dengan isSuperAdmin
const originalBarChart = barChart.style.marginTop || "0px";
barChart.style.marginTop = isSuperAdmin ? "0px" : "150px";
const options = { const options = {
margin: [0.4, 0.5, 0.4, 0], // Margin kecil untuk mengurangi ruang kosong margin: [0, 0.5, 0, 0],
image: { type: 'jpeg', quality: 0.98 }, image: { type: 'jpeg', quality: 0.98 },
html2canvas: { html2canvas: {
scale: 2, // Mengatur scale agar kualitas tinggi scale: 2,
width: element.scrollWidth, width: pdfContainer.scrollWidth,
height: element.scrollHeight, height: pdfContainer.scrollHeight,
}, },
jsPDF: { jsPDF: {
unit: 'in', unit: 'in',
@ -380,32 +398,34 @@ export default function AssessmentResultPage() {
try { try {
const pdfBlob: Blob = await html2pdf() const pdfBlob: Blob = await html2pdf()
.set(options) .set(options)
.from(element) .from(pdfContainer)
.toPdf() .toPdf()
.get('pdf') .get('pdf')
.then((pdf: any) => { .then((pdf: any) => {
pdf.setProperties({ pdf.setProperties({
title: 'Hasil_Asesemen_Level_Maturitas', title: 'Hasil_Asesemen_Level_Maturitas',
}); });
return pdf.output('blob'); // Menghasilkan PDF sebagai blob dengan title return pdf.output('blob');
}); });
// Buat URL dari Blob dan buka di tab baru untuk pratinjau
const pdfURL = URL.createObjectURL(pdfBlob); const pdfURL = URL.createObjectURL(pdfBlob);
window.open(pdfURL, '_blank'); // Pratinjau di tab baru tanpa unduh window.open(pdfURL, '_blank');
} catch (err) { } catch (err) {
console.error("Error generating PDF:", err); console.error("Error generating PDF:", err);
} finally { } finally {
// Tampilkan kembali elemen yang disembunyikan // Tampilkan kembali elemen yang disembunyikan
if (buttonPrint) buttonPrint.style.visibility = 'visible'; // Mengembalikan visibility if (buttonPrint) buttonPrint.style.visibility = 'visible';
if (noPrint) noPrint.style.visibility = 'visible'; // Mengembalikan visibility if (noPrint) noPrint.style.visibility = 'visible';
element.style.width = ""; // Reset lebar setelah selesai
// Reset marginTop ke nilai aslinya
targetCard.style.marginTop = originalMarginTop || "0px";
barChart.style.marginTop = originalBarChart || "0px";
} }
} }
}; };
return ( return (
<Card ref={cardRef} className="flex flex-row w-full h-full border-none shadow-none"> <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-none shadow-none -ml-1 -pr-2">
<p className="font-bold mt-2 ml-2">Tingkatan Level Maturitas</p> <p className="font-bold mt-2 ml-2">Tingkatan Level Maturitas</p>
<div className="flex flex-col mr-5 -ml-5 h-full"> <div className="flex flex-col mr-5 -ml-5 h-full">
@ -459,10 +479,10 @@ export default function AssessmentResultPage() {
<div className="flex flex-row gap-2" id="button-print"> <div className="flex flex-row gap-2" id="button-print">
<button <button
onClick={handlePrintPDF} 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" 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 ke PDF Cetak PDF
</button> </button>
<div className="flex"> <div className="flex">
<DropdownMenu> <DropdownMenu>
@ -492,13 +512,9 @@ export default function AssessmentResultPage() {
{isSuperAdmin && {isSuperAdmin &&
<Card className="flex flex-col w-full h-full mb-6 justify-center items-start"> <Card className="flex flex-col w-full h-full mb-6 justify-center items-start">
<div className="flex flex-row border-b w-full p-4 gap-4 items-center"> <div className="flex flex-col border-b w-full p-4">
<div className="flex w-16 h-16 rounded-full bg-slate-300">
</div>
<div className="flex flex-col">
<p className="text-lg font-bold">{assessmentResult?.respondentName}</p> <p className="text-lg font-bold">{assessmentResult?.respondentName}</p>
<p className="text-sm">{assessmentResult?.position}</p> <p className="text-sm">{assessmentResult?.position}</p>
</div>
</div> </div>
<div className="flex lg:flex-row flex-col text-xs h-full w-full justify-between p-4"> <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">
@ -566,7 +582,7 @@ export default function AssessmentResultPage() {
<> <>
{/* Score Table */} {/* Score Table */}
<p className="text-lg font-bold">Tabel Level Maturitas</p> <p className="text-lg font-bold">Tabel Level Maturitas</p>
<Card className="flex flex-col w-full h-fit my-2 mb-8 overflow-hidden text-center"> <Card className="flex flex-col w-full h-fit my-2 mb-6 overflow-hidden text-center">
<div className="flex flex-row w-full"> <div className="flex flex-row w-full">
{aspectsData?.data?.map((aspect) => ( {aspectsData?.data?.map((aspect) => (
<div key={aspect.id} className="flex-col bg-white w-full h-full border-t border-x"> <div key={aspect.id} className="flex-col bg-white w-full h-full border-t border-x">
@ -587,9 +603,15 @@ export default function AssessmentResultPage() {
</div> </div>
{/* Total score */} {/* Total score */}
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}> <div className="flex flex-row w-full h-fit justify-center font-bold text-3xl gap-0.5">
<p className="text-center">Level Maturitas:</p> <div className="flex flex-row w-full h-full p-3 justify-center gap-2 font-bold text-3xl bg-blue-600 text-white">
<span>{totalScore}</span> <p className="text-center">Nilai Maturitas:</p>
<span>{totalScore}</span>
</div>
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}>
<p className="text-center">Level Maturitas:</p>
<span className="ml-2">{getScoreStyleClass(Number(totalScore), true).descLevel}</span>
</div>
</div> </div>
</Card> </Card>
</> </>
@ -618,15 +640,21 @@ export default function AssessmentResultPage() {
</div> </div>
{/* Total verified score */} {/* Total verified score */}
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}> <div className="flex flex-row w-full h-fit justify-center font-bold text-3xl gap-0.5">
<p className="text-center">Level Maturitas:</p> <div className="flex flex-row w-full h-full p-3 justify-center gap-2 font-bold text-3xl bg-blue-600 text-white">
<span>{totalVerifiedScore}</span> <p className="text-center">Nilai Maturitas:</p>
<span>{totalScore}</span>
</div>
<div className="flex flex-row w-full h-fit p-3 justify-center gap-2 font-bold text-3xl" style={getScoreStyleClass(Number(totalScore), true)}>
<p className="text-center">Level Maturitas:</p>
<span className="ml-2">{getScoreStyleClass(Number(totalScore), true).descLevel}</span>
</div>
</div> </div>
</Card> </Card>
</> </>
)} )}
<Card className="flex flex-col lg:flex-row gap-8 border-none shadow-none w-full"> <Card className="flex flex-col lg:flex-row gap-4 border-none shadow-none w-full" id="target-card">
{/* Pie Chart */} {/* Pie Chart */}
{selectedItem === 'Hasil Sementara' ? ( {selectedItem === 'Hasil Sementara' ? (
<Card className="flex flex-col w-full"> <Card className="flex flex-col w-full">
@ -678,7 +706,7 @@ export default function AssessmentResultPage() {
)} )}
</Card> </Card>
<Card className="flex w-full h-fit border mt-8"> <Card className="flex w-full h-fit border mt-4" id="bar-chart">
{/* Bar Chart */} {/* Bar Chart */}
{selectedItem === 'Hasil Sementara' ? ( {selectedItem === 'Hasil Sementara' ? (
<> <>