forked from CSI-KJSCE/Travel-policy-
code base
This commit is contained in:
152
frontend/src/pages/Report/components/FilterDataForm.jsx
Normal file
152
frontend/src/pages/Report/components/FilterDataForm.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Formik } from "formik";
|
||||
import { useSubmit, useRouteLoaderData, useNavigation } from "react-router-dom";
|
||||
import { filterDataFormFeilds } from "./FilterDataFormFeilds";
|
||||
import * as yup from "yup";
|
||||
import Input from "../../ApplicationForm/Input";
|
||||
import axios from "axios";
|
||||
|
||||
function FilterDataForm({ setReportData, setLoading }) {
|
||||
const { role, user } = useRouteLoaderData("Validator-Root")?.data;
|
||||
|
||||
const navigation = useNavigation();
|
||||
const isSubmittingNav = navigation.state === "submitting";
|
||||
|
||||
const prefilledData =
|
||||
user?.institute || user?.department
|
||||
? {
|
||||
institute: user?.institute,
|
||||
department: user?.department,
|
||||
}
|
||||
: null;
|
||||
|
||||
const formFields = prefilledData
|
||||
? filterDataFormFeilds.map((section) => ({
|
||||
...section,
|
||||
fields: section.fields.map((field) => ({
|
||||
...field,
|
||||
disabled: prefilledData[field.name],
|
||||
})),
|
||||
}))
|
||||
: filterDataFormFeilds;
|
||||
|
||||
const createInitialValuesScheme = (formFields) => {
|
||||
const schema = {};
|
||||
|
||||
formFields?.forEach((section) => {
|
||||
section?.fields?.forEach((field) => {
|
||||
if (prefilledData) {
|
||||
if (field.type === "miniForm" || field.type === "checkbox") {
|
||||
schema[field.name] = JSON.parse(prefilledData[field.name]);
|
||||
} else {
|
||||
schema[field.name] = prefilledData[field.name];
|
||||
}
|
||||
} else if (field.type === "checkbox") {
|
||||
schema[field.name] = false;
|
||||
} else if (field.type === "miniForm") {
|
||||
schema[field.name] = [];
|
||||
} else {
|
||||
schema[field.name] = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
const initialValuesSchema = createInitialValuesScheme(formFields);
|
||||
|
||||
const createValidationSchema = (formFields) => {
|
||||
const schema = {};
|
||||
|
||||
formFields?.forEach((section) => {
|
||||
section.fields?.forEach((field) => {
|
||||
if (field.validation) {
|
||||
schema[field.name] = field.validation;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return yup.object().shape(schema);
|
||||
};
|
||||
|
||||
const validationSchema = createValidationSchema(formFields);
|
||||
|
||||
const handleSubmit = async (values, { setSubmitting, setErrors }) => {
|
||||
const { institute, department, year, applicationType } = values;
|
||||
try {
|
||||
setLoading(true);
|
||||
const queryParams = new URLSearchParams();
|
||||
if (institute) queryParams.append("institute", institute);
|
||||
if (department) queryParams.append("department", department);
|
||||
if (year) queryParams.append("year", year);
|
||||
if (applicationType) queryParams.append("applicationType", applicationType);
|
||||
|
||||
const res = await axios.get(
|
||||
`http://localhost:3000/validator/getReportData?${queryParams.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
setReportData({data: res.data, query: values});
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) {
|
||||
setErrors({ submit: error.response.data.message });
|
||||
} else {
|
||||
setErrors({ submit: "An unexpected error occurred" });
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Trigger form submission on first render
|
||||
handleSubmit(initialValuesSchema, { setSubmitting: () => {}, setErrors: () => {} });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValuesSchema}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
setFieldValue, // Use setFieldValue for file handling
|
||||
isSubmitting,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit} className="bg-transparent">
|
||||
<Input
|
||||
values={values}
|
||||
errors={errors}
|
||||
touched={touched}
|
||||
handleChange={handleChange}
|
||||
handleBlur={handleBlur}
|
||||
setFieldValue={setFieldValue} // Pass setFieldValue for file handling
|
||||
formFeilds={formFields}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || isSubmittingNav}
|
||||
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"
|
||||
>
|
||||
{isSubmitting || isSubmittingNav ? "Gettting Data" : "Get Data"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterDataForm;
|
||||
@@ -0,0 +1,65 @@
|
||||
import * as yup from "yup";
|
||||
import {
|
||||
institutes,
|
||||
instituteDepartmentMapping,
|
||||
} from "../../../components/BaseData";
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const yearOptions = [];
|
||||
for (let year = 2018; year <= currentYear; year++) {
|
||||
yearOptions.push({ label: year.toString(), value: year.toString() });
|
||||
}
|
||||
|
||||
const filterDataFormFeilds = [
|
||||
{
|
||||
label: "Travel Polciy Report",
|
||||
fields: [
|
||||
{
|
||||
label: "Select Institute",
|
||||
name: "institute",
|
||||
type: "dropdown",
|
||||
options: {
|
||||
"": institutes,
|
||||
},
|
||||
validation: yup
|
||||
.string()
|
||||
.notRequired("Department selection is notRequired"),
|
||||
},
|
||||
{
|
||||
depend: "institute",
|
||||
label: "Select Department",
|
||||
name: "department",
|
||||
type: "dropdown",
|
||||
options: instituteDepartmentMapping,
|
||||
validation: yup
|
||||
.string()
|
||||
.notRequired("Department selection is notRequired"),
|
||||
},
|
||||
{
|
||||
label: "Select Application Type",
|
||||
name: "applicationType",
|
||||
type: "dropdown",
|
||||
options: {
|
||||
"": [
|
||||
{ label: "Student Applications", value: "STUDENT" },
|
||||
{ label: "Faculty Applications", value: "FACULTY" },
|
||||
],
|
||||
},
|
||||
validation: yup
|
||||
.string()
|
||||
.notRequired("Department selection is notRequired"),
|
||||
},
|
||||
{
|
||||
label: "Select Year",
|
||||
name: "year",
|
||||
type: "dropdown",
|
||||
options: {
|
||||
"": yearOptions,
|
||||
},
|
||||
validation: yup.string().notRequired("Year is required"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { filterDataFormFeilds };
|
||||
90
frontend/src/pages/Report/components/OverTheYearsLine.jsx
Normal file
90
frontend/src/pages/Report/components/OverTheYearsLine.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Line } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
function OverTheYearsLine() {
|
||||
const options = {
|
||||
responsive: true, // Make the chart responsive to window resizing
|
||||
maintainAspectRatio: false, // Allow chart size to change dynamically (optional)
|
||||
|
||||
// Title configuration
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Applications Over the Years", // Set a title for the chart
|
||||
font: {
|
||||
size: 16,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
// Customize tooltips
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
// Format tooltip labels
|
||||
return `${context.dataset.label}: ${context.raw} steps`;
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: "top", // Legend position: 'top', 'left', 'bottom', 'right'
|
||||
},
|
||||
},
|
||||
|
||||
// Scales configuration (e.g., setting up x and y axes)
|
||||
scales: {
|
||||
x: {
|
||||
// X-axis configuration (labels are auto-set)
|
||||
title: {
|
||||
display: true,
|
||||
text: "Year", // Label for the X-axis
|
||||
},
|
||||
},
|
||||
y: {
|
||||
// Y-axis configuration
|
||||
title: {
|
||||
display: true,
|
||||
text: "Number of Applications", // Label for the Y-axis
|
||||
},
|
||||
// ticks: {
|
||||
// // Custom tick marks
|
||||
// beginAtZero: true, // Start Y-axis from 0
|
||||
// stepSize: 9000, // Tick step size for Y-axis
|
||||
// },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = {
|
||||
labels: [2020, 2021, 2022, 2023, 2024],
|
||||
datasets: [
|
||||
{
|
||||
label: "Steps",
|
||||
data: [30, 50, 45, 90, 35],
|
||||
borderColor: "rgb(75, 192, 192)",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Line options={options} data={data} />;
|
||||
}
|
||||
|
||||
export default OverTheYearsLine;
|
||||
65
frontend/src/pages/Report/components/OverTheYearsPie.jsx
Normal file
65
frontend/src/pages/Report/components/OverTheYearsPie.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Pie } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
ArcElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
|
||||
// Register chart components
|
||||
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||
|
||||
function OverTheYearsPie() {
|
||||
const options = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "Steps Distribution Over Years",
|
||||
font: {
|
||||
size: 16,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
return `${context.label}: ${context.raw} steps`;
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: "top",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = {
|
||||
labels: ["2020", "2021", "2022", "2023", "2024"], // Labels for the pie slices
|
||||
datasets: [
|
||||
{
|
||||
data: [3000, 5000, 4500, 9000, 12000], // Data for the pie chart
|
||||
backgroundColor: [
|
||||
"rgba(75, 192, 192, 0.5)", // Color for the 2020 slice
|
||||
"rgba(255, 99, 132, 0.5)", // Color for the 2021 slice
|
||||
"rgba(54, 162, 235, 0.5)", // Color for the 2022 slice
|
||||
"rgba(153, 102, 255, 0.5)", // Color for the 2023 slice
|
||||
"rgba(255, 159, 64, 0.5)", // Color for the 2024 slice
|
||||
],
|
||||
borderColor: [
|
||||
"rgb(75, 192, 192)",
|
||||
"rgb(255, 99, 132)",
|
||||
"rgb(54, 162, 235)",
|
||||
"rgb(153, 102, 255)",
|
||||
"rgb(255, 159, 64)",
|
||||
],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Pie options={options} data={data} />;
|
||||
}
|
||||
|
||||
export default OverTheYearsPie;
|
||||
166
frontend/src/pages/Report/components/ReportPDF.jsx
Normal file
166
frontend/src/pages/Report/components/ReportPDF.jsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
View,
|
||||
Document,
|
||||
StyleSheet,
|
||||
Image,
|
||||
} from "@react-pdf/renderer";
|
||||
|
||||
// Create styles
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
flexDirection: "column",
|
||||
backgroundColor: "white",
|
||||
padding: 20,
|
||||
},
|
||||
sectionTitle: {
|
||||
textAlign: "center",
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
marginBottom: 10,
|
||||
},
|
||||
section: {
|
||||
margin: 10,
|
||||
padding: 10,
|
||||
},
|
||||
viewer: {
|
||||
width: "75vw", // Full width
|
||||
height: "100vh", // Full height
|
||||
},
|
||||
cardContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginVertical: 10,
|
||||
},
|
||||
card: {
|
||||
width: "45%",
|
||||
padding: 10,
|
||||
backgroundColor: "#f8e7d1",
|
||||
borderRadius: 5,
|
||||
textAlign: "center",
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
chartContainer: {
|
||||
marginVertical: 20,
|
||||
textAlign: "center",
|
||||
},
|
||||
table: {
|
||||
display: "table",
|
||||
width: "auto",
|
||||
borderStyle: "solid",
|
||||
borderWidth: 1,
|
||||
borderColor: "#bfbfbf",
|
||||
borderBottomWidth: 0,
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
tableRow: {
|
||||
flexDirection: "row",
|
||||
},
|
||||
tableColHeader: {
|
||||
width: "25%",
|
||||
borderStyle: "solid",
|
||||
borderColor: "#bfbfbf",
|
||||
borderRightWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
backgroundColor: "#f2f2f2",
|
||||
padding: 5,
|
||||
textAlign: "center",
|
||||
},
|
||||
tableCol: {
|
||||
width: "25%",
|
||||
borderStyle: "solid",
|
||||
borderColor: "#bfbfbf",
|
||||
borderRightWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
padding: 5,
|
||||
textAlign: "center",
|
||||
},
|
||||
tableCellHeader: {
|
||||
margin: 5,
|
||||
fontSize: 12,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
tableCell: {
|
||||
margin: 5,
|
||||
fontSize: 10,
|
||||
},
|
||||
image: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
},
|
||||
});
|
||||
|
||||
// Create Document Component
|
||||
const ReportPDF = ({ tableData, chartImages }) => {
|
||||
return (
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
{/* Title */}
|
||||
<Text style={styles.sectionTitle}>Travel Policy Report</Text>
|
||||
|
||||
{/* Summary Cards */}
|
||||
{/* <View style={styles.cardContainer}>
|
||||
<View style={styles.card}>
|
||||
<Text>Total Funds Deployed</Text>
|
||||
<Text>12,23,234</Text>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<Text>Enrollment Rate</Text>
|
||||
<Text>90%</Text>
|
||||
</View>
|
||||
</View> */}
|
||||
|
||||
{/* Table */}
|
||||
<View style={styles.table}>
|
||||
<View style={styles.tableRow}>
|
||||
<View style={styles.tableColHeader}>
|
||||
<Text style={styles.tableCellHeader}>ID</Text>
|
||||
</View>
|
||||
<View style={styles.tableColHeader}>
|
||||
<Text style={styles.tableCellHeader}>Stream</Text>
|
||||
</View>
|
||||
<View style={styles.tableColHeader}>
|
||||
<Text style={styles.tableCellHeader}>Scholarship</Text>
|
||||
</View>
|
||||
<View style={styles.tableColHeader}>
|
||||
<Text style={styles.tableCellHeader}>Funds</Text>
|
||||
</View>
|
||||
</View>
|
||||
{tableData?.map((row) => (
|
||||
<View key={row.id} style={styles.tableRow}>
|
||||
<View style={styles.tableCol}>
|
||||
<Text style={styles.tableCell}>{row.id}</Text>
|
||||
</View>
|
||||
<View style={styles.tableCol}>
|
||||
<Text style={styles.tableCell}>{row.Stream}</Text>
|
||||
</View>
|
||||
<View style={styles.tableCol}>
|
||||
<Text style={styles.tableCell}>{row.Scholarship}</Text>
|
||||
</View>
|
||||
<View style={styles.tableCol}>
|
||||
<Text style={styles.tableCell}>{row.Funds}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* Charts */}
|
||||
{chartImages?.barChart && (
|
||||
<Image src={chartImages.barChart} style={styles.image} />
|
||||
)}
|
||||
{chartImages?.pieChart1 && (
|
||||
<Image src={chartImages.pieChart1} style={styles.image} />
|
||||
)}
|
||||
|
||||
{chartImages?.pieChart2 && (
|
||||
<Image src={chartImages.pieChart2} style={styles.image} />
|
||||
)}
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportPDF;
|
||||
37
frontend/src/pages/Report/components/Table.jsx
Normal file
37
frontend/src/pages/Report/components/Table.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
const Table = ({ tableData }) => {
|
||||
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<table
|
||||
style={{
|
||||
width: "100%",
|
||||
borderCollapse: "collapse",
|
||||
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: "#f4f4f4" }}>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>ID</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Stream</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Scholarship</th>
|
||||
<th style={{ padding: "10px", border: "1px solid #ddd" }}>Funds</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableData?.map((row) => (
|
||||
<tr key={row.id}>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.id}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Stream}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Scholarship}</td>
|
||||
<td style={{ padding: "10px", border: "1px solid #ddd" }}>{row.Funds}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
146
frontend/src/pages/Report/components/approved.jsx
Normal file
146
frontend/src/pages/Report/components/approved.jsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState } from "react";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
const ChartWithDropdown = () => {
|
||||
// Chart data options for faculty, students, HOI, and HOD
|
||||
const chartDataOptions = {
|
||||
faculty: {
|
||||
approved: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Approved Applications (Faculty)",
|
||||
data: [100, 150, 200, 250, 300,400,900,132,920,1000,890 ,100],
|
||||
backgroundColor: "rgba(75, 192, 192, 0.5)",
|
||||
borderColor: "rgb(75, 192, 192)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
rejected: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Rejected Applications (Faculty)",
|
||||
data: [50, 60, 70, 80, 20,40,90,78,23,29,98,33],
|
||||
backgroundColor: "rgba(255, 99, 132, 0.5)",
|
||||
borderColor: "rgb(255, 99, 132)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
HOI: {
|
||||
approved: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Approved Applications (HOI)",
|
||||
data: [1200, 1500, 1800, 2200, 2500,2000,1999,3453,2345,5633,2345,5647],
|
||||
backgroundColor: "rgba(54, 162, 235, 0.5)",
|
||||
borderColor: "rgb(54, 162, 235)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
rejected: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Rejected Applications (HOI)",
|
||||
data: [200, 300, 400, 500, 450, 350, 320, 410, 360, 430, 300, 250],
|
||||
backgroundColor: "rgba(255, 159, 64, 0.5)",
|
||||
borderColor: "rgb(255, 159, 64)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
HOD: {
|
||||
approved: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Approved Applications (HOD)",
|
||||
data: [300, 400, 500, 450, 400, 350, 300, 250, 200, 150, 100, 50],
|
||||
backgroundColor: "rgba(153, 102, 255, 0.5)",
|
||||
borderColor: "rgb(153, 102, 255)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
rejected: {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Rejected Applications (HOD)",
|
||||
data: [30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140],
|
||||
backgroundColor: "rgba(255, 206, 86, 0.5)",
|
||||
borderColor: "rgb(255, 206, 86)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
title: { display: true, text: "Applications Over the Years" },
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: "Year" } },
|
||||
y: { title: { display: true, text: "Number of Applications" }, beginAtZero: true },
|
||||
},
|
||||
};
|
||||
|
||||
const [category, setCategory] = useState("faculty"); // Faculty, HOI, or HOD
|
||||
const [applicationType, setApplicationType] = useState("approved"); // Approved or Rejected
|
||||
|
||||
// Fetch the data based on the selected category and application type
|
||||
const data =
|
||||
chartDataOptions[category]?.[applicationType] ||
|
||||
chartDataOptions["faculty"]["approved"];
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", margin: "auto", padding: "20px", flexGrow: 1 }}>
|
||||
{/* Dropdown for selecting category */}
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<label htmlFor="category-select" style={{ marginRight: "10px" }}>
|
||||
Select Category:
|
||||
</label>
|
||||
<select
|
||||
id="category-select"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
style={{ padding: "5px", fontSize: "16px", marginRight: "20px", borderRadius: "15px", textAlign: "center", border: "2px solid black" }}
|
||||
>
|
||||
<option value="faculty">Faculty</option>
|
||||
<option value="HOI">HOI</option>
|
||||
<option value="HOD">HOD</option>
|
||||
</select>
|
||||
|
||||
{/* Dropdown for selecting application type */}
|
||||
<label htmlFor="type-select" style={{ marginRight: "10px" }}>
|
||||
Select Application Type:
|
||||
</label>
|
||||
<select
|
||||
id="type-select"
|
||||
value={applicationType}
|
||||
onChange={(e) => setApplicationType(e.target.value)}
|
||||
style={{ padding: "5px", fontSize: "16px", borderRadius: "15px", textAlign: "center", border: "2px solid black" }}
|
||||
>
|
||||
<option value="approved">Approved Applications</option>
|
||||
<option value="rejected">Rejected Applications</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Chart */}
|
||||
{data && <Bar data={data} options={chartOptions} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartWithDropdown;
|
||||
143
frontend/src/pages/Report/components/card.jsx
Normal file
143
frontend/src/pages/Report/components/card.jsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import React, { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import "./cards.css";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
|
||||
// Register chart.js components
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const Card = (props) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<motion.div
|
||||
layout
|
||||
onClick={() => setExpanded(!expanded)} // Toggle the state
|
||||
className="motion_card"
|
||||
>
|
||||
{!expanded && ( // Render CompactCard when NOT expanded
|
||||
<motion.div>
|
||||
<CompactCard param={props} />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{expanded && ( // Render ExpandedCard when expanded
|
||||
<motion.div>
|
||||
<ExpandedCard param={props} />
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Compact Card Component
|
||||
function CompactCard({ param }) {
|
||||
return (
|
||||
<div className="CompactCard">
|
||||
<div className="data">
|
||||
<h1>{param.title}</h1>
|
||||
</div>
|
||||
<span>{param.value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Expanded Card Component
|
||||
function ExpandedCard({ param }) {
|
||||
const barOptions = {
|
||||
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 chartData = {
|
||||
labels: ["Jan", "Feb", "Mar", "April", "May","June","July","Aug","Sep","Nov","Dec"], // Example years
|
||||
datasets: [
|
||||
{
|
||||
label: param.series[0].name, // e.g., "Applications"
|
||||
data: param.series[0].data, // e.g., [100, 150, 200, 250, 300]
|
||||
backgroundColor: "rgba(75, 192, 192, 0.6)", // Bar color
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="ExpandedCard"
|
||||
style={{
|
||||
position: "fixed", // Make it fixed to cover the whole screen
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)", // Semi-transparent background
|
||||
zIndex: 999, // Ensure it's on top of other elements
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="expandedCardContent"
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
padding: "20px",
|
||||
borderRadius: "10px",
|
||||
width: "70%",
|
||||
height: "70%"
|
||||
}}
|
||||
>
|
||||
<div className="data">
|
||||
<h1>{param.title}</h1>
|
||||
</div>
|
||||
<Bar options={barOptions} data={chartData} />
|
||||
<span>{param.value}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Card;
|
||||
63
frontend/src/pages/Report/components/cards.css
Normal file
63
frontend/src/pages/Report/components/cards.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.generalInfo{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
width: 30%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.cards{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
flex-direction: row;
|
||||
|
||||
}
|
||||
.Cards{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin: 20px;
|
||||
}
|
||||
.CompactCard{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 20px;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
cursor: pointer;
|
||||
background-color: antiquewhite;
|
||||
border-width: 5px;
|
||||
border-color: rgb(85, 85, 85);
|
||||
border-radius: 5px;
|
||||
filter: drop-shadow(2px 4px 6px rgb(114, 114, 114));
|
||||
}
|
||||
.CompactCard:hover
|
||||
{
|
||||
filter: drop-shadow(2px 4px 6px rgb(255, 255, 255));
|
||||
}
|
||||
.data>h1
|
||||
{
|
||||
font-size: large;
|
||||
font-weight: 1000;
|
||||
}
|
||||
.CompactCard>span
|
||||
{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.motionCard{
|
||||
gap: 20px;
|
||||
}
|
||||
.h{
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 10px;
|
||||
gap: 10px;
|
||||
|
||||
}
|
||||
28
frontend/src/pages/Report/components/cards.jsx
Normal file
28
frontend/src/pages/Report/components/cards.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import './cards.css'
|
||||
import React from 'react'
|
||||
import Card from './card'
|
||||
import { CardsData } from '../Data';
|
||||
|
||||
const Cards= () =>{
|
||||
return(
|
||||
<div className="Cards">
|
||||
{CardsData.map((card , id)=>{
|
||||
return(
|
||||
<div className="parentContainer">
|
||||
<Card
|
||||
title={card.title}
|
||||
color={card.color}
|
||||
value={card.value}
|
||||
series={card.series}
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
})}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Cards
|
||||
440
frontend/src/pages/Report/components/charts.jsx
Normal file
440
frontend/src/pages/Report/components/charts.jsx
Normal file
@@ -0,0 +1,440 @@
|
||||
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 = {};
|
||||
if (acceptedApplications) {
|
||||
for (const item of acceptedApplications) {
|
||||
const { institute, department, formData } = item;
|
||||
const { totalExpense } = formData;
|
||||
|
||||
if (!groupedData[institute]) {
|
||||
groupedData[institute] = {};
|
||||
}
|
||||
|
||||
if (query.institute) {
|
||||
if (!groupedData[institute][department]) {
|
||||
groupedData[institute][department] = {
|
||||
totalExpense: 0,
|
||||
applications: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Aggregate the data
|
||||
groupedData[institute][department].totalExpense +=
|
||||
parseFloat(totalExpense); // Summing the expenses
|
||||
groupedData[institute][department].applications += 1;
|
||||
} else {
|
||||
if (!groupedData[institute].applications) {
|
||||
groupedData[institute] = {
|
||||
totalExpense: 0,
|
||||
applications: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Aggregate the data
|
||||
groupedData[institute].totalExpense += parseFloat(totalExpense); // Summing the expenses
|
||||
groupedData[institute].applications += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Transform grouped data into desired table format
|
||||
if (query.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,
|
||||
Scholarship: departmentData.applications, // Assuming each application is one scholarship
|
||||
Purpose_of_Travel: departmentData.purposeOfTravel,
|
||||
Funds: departmentData.totalExpense.toFixed(2), // Formatting funds to 2 decimal places
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const institute in groupedData) {
|
||||
const instituteData = groupedData[institute];
|
||||
|
||||
tableData.push({
|
||||
id: tableData.length + 1,
|
||||
Stream: institute,
|
||||
Scholarship: instituteData.applications, // Assuming each application is one scholarship
|
||||
Purpose_of_Travel: instituteData.purposeOfTravel,
|
||||
Funds: instituteData.totalExpense.toFixed(2), // Formatting funds to 2 decimal places
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const [chartImages, setChartImages] = useState({
|
||||
barChart: null,
|
||||
pieChart1: null,
|
||||
pieChart2: null,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
||||
// 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">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">
|
||||
<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>
|
||||
|
||||
<PDFViewer style={{ width: "70vw", height: "100vh" }}>
|
||||
<ReportPDF tableData={tableData} chartImages={chartImages} />
|
||||
</PDFViewer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Charts;
|
||||
92
frontend/src/pages/Report/components/map.jsx
Normal file
92
frontend/src/pages/Report/components/map.jsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React, { useState } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import { Chart as ChartJS, LineElement, CategoryScale, LinearScale, PointElement, Title, Tooltip, Legend, Filler } from "chart.js";
|
||||
|
||||
// Register required Chart.js components
|
||||
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement, Title, Tooltip, Legend, Filler);
|
||||
|
||||
const ApprovalVsRejectionTrends = () => {
|
||||
// Sample data for Approved and Rejected Applications
|
||||
const applicationData = {
|
||||
faculty: {
|
||||
approved: [100, 150, 200, 250, 300, 400, 500, 450, 600, 550, 700, 650],
|
||||
rejected: [50, 60, 70, 80, 100, 90, 120, 110, 130, 100, 140, 120],
|
||||
},
|
||||
HOI: {
|
||||
approved: [500, 600, 700, 800, 750, 700, 900, 850, 1000, 950, 1100, 1050],
|
||||
rejected: [100, 120, 140, 150, 130, 110, 180, 150, 200, 170, 220, 190],
|
||||
},
|
||||
HOD: {
|
||||
approved: [300, 400, 350, 450, 500, 480, 550, 520, 600, 580, 650, 620],
|
||||
rejected: [80, 90, 100, 110, 120, 100, 140, 130, 150, 140, 160, 150],
|
||||
},
|
||||
};
|
||||
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
// State for selected category
|
||||
const [category, setCategory] = useState("faculty");
|
||||
|
||||
// Data for the Line Chart
|
||||
const lineChartData = {
|
||||
labels: months,
|
||||
datasets: [
|
||||
{
|
||||
label: "Approved Applications",
|
||||
data: applicationData[category].approved,
|
||||
borderColor: "rgb(75, 192, 192)",
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
tension: 0.4, // For a smooth curve
|
||||
fill: true,
|
||||
},
|
||||
{
|
||||
label: "Rejected Applications",
|
||||
data: applicationData[category].rejected,
|
||||
borderColor: "rgb(255, 99, 132)",
|
||||
backgroundColor: "rgba(255, 99, 132, 0.2)",
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const lineChartOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true, position: "top" },
|
||||
title: { display: true, text: "Approval vs. Rejection Trends" },
|
||||
},
|
||||
scales: {
|
||||
x: { title: { display: true, text: "Months" } },
|
||||
y: { title: { display: true, text: "Number of Applications" }, beginAtZero: true },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: "90%", margin: "auto", padding: "20px" }}>
|
||||
<h2 style={{ textAlign: "center" }}>Approval vs. Rejection Trends</h2>
|
||||
|
||||
{/* Dropdown to select category */}
|
||||
<div style={{ marginBottom: "20px", textAlign: "center" }}>
|
||||
<label htmlFor="category-select" style={{ marginRight: "10px" }}>
|
||||
Select Category:
|
||||
</label>
|
||||
<select
|
||||
id="category-select"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value)}
|
||||
style={{ padding: "5px", fontSize: "16px", borderRadius: "8px", border: "1px solid #ccc" }}
|
||||
>
|
||||
<option value="faculty">Faculty</option>
|
||||
<option value="HOI">HOI</option>
|
||||
<option value="HOD">HOD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Line Chart */}
|
||||
<Line data={lineChartData} options={lineChartOptions} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApprovalVsRejectionTrends;
|
||||
Reference in New Issue
Block a user