461 lines
12 KiB
JavaScript
461 lines
12 KiB
JavaScript
import React, { useEffect, useRef, useState } from "react";
|
|
import ChartWithDropdown from "./approved";
|
|
import Cards from "./cards";
|
|
import "./cards.css";
|
|
import { Bar, Pie } from "react-chartjs-2";
|
|
import {
|
|
Chart as ChartJS,
|
|
CategoryScale,
|
|
LinearScale,
|
|
PointElement,
|
|
LineElement,
|
|
BarElement,
|
|
ArcElement,
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
} from "chart.js";
|
|
import ChartDataLabels from "chartjs-plugin-datalabels";
|
|
import Table from "./Table";
|
|
import { PDFDownloadLink, PDFViewer } from "@react-pdf/renderer";
|
|
import ApprovalVsRejectionTrends from "./map";
|
|
import ReportPDF from "./ReportPDF";
|
|
|
|
// Register chart components for all three types (Line, Bar, Pie)
|
|
ChartJS.register(
|
|
CategoryScale,
|
|
LinearScale,
|
|
PointElement,
|
|
LineElement,
|
|
BarElement,
|
|
ArcElement,
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
ChartDataLabels
|
|
);
|
|
|
|
function Charts({ reportData }) {
|
|
const { data, query } = reportData;
|
|
|
|
if (!data) {
|
|
return (
|
|
<div className="text-center text-xl text-red-700 py-10">
|
|
No Data Found
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const { acceptedApplications, rejectedApplications, pendingApplications } =
|
|
data;
|
|
|
|
const tableData = [];
|
|
const groupedData = {};
|
|
|
|
// --- Data Processing for Table --- (Simple aggregation logic)
|
|
if (acceptedApplications) {
|
|
for (const item of acceptedApplications) {
|
|
const { institute, department, formData } = item;
|
|
const { totalExpense } = formData;
|
|
|
|
// Initialize institute object if not exists
|
|
if (!groupedData[institute]) {
|
|
groupedData[institute] = {};
|
|
}
|
|
|
|
// If filtering by specific institute, we group by Department (e.g. Computer, IT, Mech)
|
|
if (query.institute) {
|
|
if (!groupedData[institute][department]) {
|
|
groupedData[institute][department] = {
|
|
totalExpense: 0,
|
|
applications: 0,
|
|
};
|
|
}
|
|
|
|
// Add expense and increment count
|
|
groupedData[institute][department].totalExpense +=
|
|
parseFloat(totalExpense);
|
|
groupedData[institute][department].applications += 1;
|
|
} else {
|
|
// If viewing all institutes, we group by Institute (e.g. KJSCE, KJSIM)
|
|
if (!groupedData[institute].applications) {
|
|
groupedData[institute] = {
|
|
totalExpense: 0,
|
|
applications: 0,
|
|
};
|
|
}
|
|
|
|
// Add expense and increment count
|
|
groupedData[institute].totalExpense += parseFloat(totalExpense);
|
|
groupedData[institute].applications += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Transform Groups to Array for Display ---
|
|
if (query.institute) {
|
|
// Loop through departments for the selected institute
|
|
for (const institute in groupedData) {
|
|
for (const department in groupedData[institute]) {
|
|
const departmentData = groupedData[institute][department];
|
|
|
|
tableData.push({
|
|
id: tableData.length + 1,
|
|
Stream: department, // 'Stream' here refers to the Department name
|
|
Scholarship: departmentData.applications, // Number of applications
|
|
Purpose_of_Travel: departmentData.purposeOfTravel, // (Placeholder)
|
|
Funds: departmentData.totalExpense.toFixed(2), // Total money spent
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
// Loop through all institutes
|
|
for (const institute in groupedData) {
|
|
const instituteData = groupedData[institute];
|
|
|
|
tableData.push({
|
|
id: tableData.length + 1,
|
|
Stream: institute, // 'Stream' here is the Institute name
|
|
Scholarship: instituteData.applications, // Number of applications
|
|
Purpose_of_Travel: instituteData.purposeOfTravel,
|
|
Funds: instituteData.totalExpense.toFixed(2), // Total money spent
|
|
});
|
|
}
|
|
}
|
|
|
|
const [chartImages, setChartImages] = useState({
|
|
barChart: null,
|
|
pieChart1: null,
|
|
pieChart2: null,
|
|
isLoading: false,
|
|
});
|
|
|
|
// --- Chart Configuration (Preserved for future use) ---
|
|
|
|
// Line Chart Data and Options
|
|
const lineOptions = {
|
|
responsive: true,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: "Number of Applications Over the Years ",
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: "Year",
|
|
},
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: "Number of Applications",
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const lineData = {
|
|
labels: [2020, 2021, 2022, 2023, 2024],
|
|
datasets: [
|
|
{
|
|
label: "Applications",
|
|
data: [1200, 1500, 1800, 2200, 2500], // Updated data for number of applications
|
|
borderColor: "rgb(75, 192, 192)",
|
|
fill: false,
|
|
tension: 0.1,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Bar Chart Data and Options
|
|
const barOptions = {
|
|
responsive: true,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: "Number of Applications Over the Years ",
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: "Month",
|
|
},
|
|
},
|
|
y: {
|
|
title: {
|
|
display: true,
|
|
text: "Number of Applications",
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const barData = {
|
|
labels: [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"Aug",
|
|
"Sep",
|
|
"Nov",
|
|
"Dec",
|
|
],
|
|
datasets: [
|
|
{
|
|
label: "Applications",
|
|
data: [
|
|
1200, 1500, 1800, 2200, 200, 800, 1235, 604, 2345, 2523, 3453, 6453,
|
|
], // Updated data for number of applications
|
|
backgroundColor: "rgba(75, 192, 192, 0.5)",
|
|
borderColor: "rgb(75, 192, 192)",
|
|
borderWidth: 1,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Pie Chart Data and Options
|
|
const pieOptions = {
|
|
responsive: true,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: "Purpose of Travel",
|
|
},
|
|
},
|
|
};
|
|
|
|
const pieData = {
|
|
labels: ["Academic", "Research", "Personal", "Other"],
|
|
datasets: [
|
|
{
|
|
data: [1200, 1500, 1800, 2200], // Updated data for number of applications
|
|
backgroundColor: [
|
|
"rgba(75, 192, 192, 0.5)",
|
|
"rgba(255, 99, 132, 0.5)",
|
|
"rgba(54, 162, 235, 0.5)",
|
|
"rgba(153, 102, 255, 0.5)",
|
|
],
|
|
borderColor: [
|
|
"rgb(75, 192, 192)",
|
|
"rgb(255, 99, 132)",
|
|
"rgb(54, 162, 235)",
|
|
"rgb(153, 102, 255)",
|
|
],
|
|
borderWidth: 1,
|
|
},
|
|
],
|
|
};
|
|
|
|
const pie_Options = {
|
|
responsive: true,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: "Travel",
|
|
},
|
|
},
|
|
};
|
|
|
|
const pie_Data = {
|
|
labels: ["Domestic", "International", "Local"],
|
|
datasets: [
|
|
{
|
|
data: [1200, 1500, 1800], // Updated data for number of applications
|
|
backgroundColor: [
|
|
"rgba(79, 246, 96, 0.5)",
|
|
"rgba(255, 99, 132, 0.5)",
|
|
"rgba(54, 162, 235, 0.5)",
|
|
],
|
|
borderColor: [
|
|
"rgb(79, 246, 96)",
|
|
"rgb(255, 99, 132)",
|
|
"rgb(54, 162, 235)",
|
|
],
|
|
borderWidth: 1,
|
|
},
|
|
],
|
|
};
|
|
|
|
const barChartRef = useRef();
|
|
const pieChartRef1 = useRef();
|
|
const pieChartRef2 = useRef();
|
|
|
|
// Note: Chart generation logic for PDF is currently disabled as we focus on the Table view.
|
|
// Uncomment this when we are ready to integrate charts into the PDF report.
|
|
/*
|
|
const loadChartsInPdf = () => {
|
|
const barChartInstance = barChartRef.current;
|
|
const pieChartInstance1 = pieChartRef1.current;
|
|
const pieChartInstance2 = pieChartRef2.current;
|
|
|
|
if (barChartInstance) {
|
|
const barBase64Image = barChartInstance.toBase64Image();
|
|
setChartImages((prevImages) => ({
|
|
...prevImages,
|
|
barChart: barBase64Image,
|
|
}));
|
|
}
|
|
|
|
if (pieChartInstance1) {
|
|
const pieBase64Image = pieChartInstance1.toBase64Image();
|
|
setChartImages((prevImages) => ({
|
|
...prevImages,
|
|
pieChart1: pieBase64Image,
|
|
}));
|
|
}
|
|
|
|
if (pieChartInstance2) {
|
|
const pieBase64Image = pieChartInstance2.toBase64Image();
|
|
setChartImages((prevImages) => ({
|
|
...prevImages,
|
|
pieChart2: pieBase64Image,
|
|
}));
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
setChartImages((prevImages) => ({ ...prevImages, isLoading: true }));
|
|
|
|
const handleRender = () => {
|
|
loadChartsInPdf();
|
|
setChartImages((prevImages) => ({ ...prevImages, isLoading: false }));
|
|
};
|
|
|
|
const barChartInstance = barChartRef.current;
|
|
const pieChartInstance1 = pieChartRef1.current;
|
|
const pieChartInstance2 = pieChartRef2.current;
|
|
|
|
if (barChartInstance) {
|
|
barChartInstance.options.animation.onComplete = handleRender;
|
|
}
|
|
|
|
if (pieChartInstance1) {
|
|
pieChartInstance1.options.animation.onComplete = handleRender;
|
|
}
|
|
|
|
if (pieChartInstance2) {
|
|
pieChartInstance2.options.animation.onComplete = handleRender;
|
|
}
|
|
|
|
return () => {
|
|
if (barChartInstance) {
|
|
barChartInstance.options.animation.onComplete = null;
|
|
}
|
|
if (pieChartInstance1) {
|
|
pieChartInstance1.options.animation.onComplete = null;
|
|
}
|
|
if (pieChartInstance2) {
|
|
pieChartInstance2.options.animation.onComplete = null;
|
|
}
|
|
};
|
|
}, []);
|
|
*/
|
|
|
|
return (
|
|
<div className="p-10">
|
|
<h1 className="text-3xl mb-6 text-gray-800 dark:text-google-text">
|
|
Travel Policy Report
|
|
</h1>
|
|
|
|
{/* Container for all three charts */}
|
|
{/* <div className="grid grid-cols-1 md:grid-cols-1 lg:grid-cols-[2fr,1fr] gap-6">
|
|
<div className="w-full">
|
|
<Bar options={barOptions} data={barData} ref={barChartRef} />
|
|
</div>
|
|
|
|
<div className="w-full">
|
|
<Pie options={pieOptions} data={pieData} ref={pieChartRef1} />
|
|
</div>
|
|
</div> */}
|
|
|
|
{/* <div className="cards">
|
|
<Cards />
|
|
|
|
<div className="generalInfo">
|
|
<div className="card2">
|
|
<ChartWithDropdown />
|
|
</div>
|
|
</div>
|
|
</div> */}
|
|
|
|
{/* <div className="hh">
|
|
<ApprovalVsRejectionTrends />
|
|
</div> */}
|
|
|
|
{/* Line Chart */}
|
|
{/* <div className="w-full">
|
|
<Line options={lineOptions} data={lineData} />*/}
|
|
|
|
<div className="flex flex-col gap-10 items-center justify-center my-10">
|
|
<div className="w-full">
|
|
{/* Reuse the Table component to show aggregated data */}
|
|
<Table tableData={tableData} />
|
|
</div>
|
|
|
|
{/*
|
|
<div>
|
|
<Pie options={pie_Options} data={pie_Data} ref={pieChartRef2} />
|
|
</div> */}
|
|
</div>
|
|
|
|
{chartImages.isLoading ? (
|
|
<div className="text-center text-xl text-red-700 py-10">
|
|
Generating PDF Report...
|
|
</div>
|
|
) : (
|
|
<div className="pdfreport">
|
|
<PDFDownloadLink
|
|
document={
|
|
<ReportPDF tableData={tableData} chartImages={chartImages} />
|
|
}
|
|
fileName={`report_${query.institute || "allInstitutes"}_${
|
|
query.department || "allDepartments"
|
|
}_${query.year || "allYears"}_${
|
|
query.applicationType || "allApplications"
|
|
}.pdf`}
|
|
>
|
|
{({ blob, url, loading, error }) =>
|
|
loading ? (
|
|
<div className="text-center text-xl text-red-700 py-10">
|
|
Getting Your PDF Report Ready...
|
|
</div>
|
|
) : (
|
|
<button
|
|
disabled={loading}
|
|
className="w-full flex items-center justify-center bg-gradient-to-r from-red-600 to-red-800 hover:from-red-800 hover:to-red-600 text-white font-semibold py-2 px-4 rounded-lg shadow-lg transform transition duration-300 ease-in-out disabled:bg-gray-400"
|
|
type="button"
|
|
>
|
|
Download PDF
|
|
</button>
|
|
)
|
|
}
|
|
</PDFDownloadLink>
|
|
|
|
<div className="mt-8 hidden md:block">
|
|
<PDFViewer style={{ width: "70vw", height: "100vh" }}>
|
|
<ReportPDF tableData={tableData} chartImages={chartImages} />
|
|
</PDFViewer>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Charts;
|