From c1fb50c6674707ff1ee06cddc783fa06874d4316 Mon Sep 17 00:00:00 2001 From: Kartik sharma Date: Wed, 16 Jul 2025 16:14:52 +0530 Subject: [PATCH] Creating a new weidget Attendance Overview and accroding to project selection data will seen. --- public/assets/vendor/css/core.css | 2 +- src/components/Charts/Skelton.jsx | 17 ++ src/components/Charts/flatColor.js | 14 ++ src/components/Dashboard/AttendanceChart.jsx | 163 +++++++++++++++++++ src/components/Dashboard/Dashboard.jsx | 98 ++++++----- src/components/Dashboard/Tasks.jsx | 43 +++-- src/components/Dashboard/Teams.jsx | 68 +++++--- src/components/Project/ProjectOverview.jsx | 2 +- src/hooks/useDashboard_Data.jsx | 95 +++++++---- src/pages/project/ProjectDetails.jsx | 16 +- src/repositories/GlobalRepository.jsx | 22 ++- 11 files changed, 408 insertions(+), 132 deletions(-) create mode 100644 src/components/Charts/Skelton.jsx create mode 100644 src/components/Charts/flatColor.js create mode 100644 src/components/Dashboard/AttendanceChart.jsx diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index aca2dc05..a60c46bb 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -836,7 +836,7 @@ progress { } .row { - --bs-gutter-x: 0.500rem; + --bs-gutter-x: 1.500rem; --bs-gutter-y: 0; display: flex; flex-wrap: wrap; diff --git a/src/components/Charts/Skelton.jsx b/src/components/Charts/Skelton.jsx new file mode 100644 index 00000000..f88b324d --- /dev/null +++ b/src/components/Charts/Skelton.jsx @@ -0,0 +1,17 @@ +import React from "react"; + +const ChartSkeleton = () => { + return ( +
+
+ +
+
+
+ ); +}; + +export default ChartSkeleton; \ No newline at end of file diff --git a/src/components/Charts/flatColor.js b/src/components/Charts/flatColor.js new file mode 100644 index 00000000..783863e8 --- /dev/null +++ b/src/components/Charts/flatColor.js @@ -0,0 +1,14 @@ +const flatColors = [ + "#E57373", "#64B5F6", "#81C784", "#FFB74D", + "#FF8A65", "#4DB6AC", "#A1887F", "#DCE775", + "#7986CB", "#AED581", "#4FC3F7", "#F06292", "#E0E0E0", + "#FFF176", "#A5D6A7", "#90CAF9", "#FFAB91", + "#E6EE9C", "#FFCC80", "#80DEEA", "#B0BEC5", + "#EF9A9A", "#FFCDD2", "#C5CAE9", "#F8BBD0", "#D1C4E9", + "#FFF9C4", "#C8E6C9", "#BBDEFB", "#FFECB3", + "#B2EBF2", "#CFD8DC", "#FBE9E7", "#FFFDE7", + "#DCEDC8", "#B3E5FC", "#FFF3E0", "#FCE4EC", + "#E0F7FA", "#ECEFF1", "#FFE0B2", "#FFD54F", "#FFA726", +]; + +export default flatColors; \ No newline at end of file diff --git a/src/components/Dashboard/AttendanceChart.jsx b/src/components/Dashboard/AttendanceChart.jsx new file mode 100644 index 00000000..3929c3e2 --- /dev/null +++ b/src/components/Dashboard/AttendanceChart.jsx @@ -0,0 +1,163 @@ + +import React, { useState, useMemo } from "react"; +import { useSelector } from "react-redux"; +import ReactApexChart from "react-apexcharts"; +import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data"; +import flatColors from "../Charts/flatColor"; +import ChartSkeleton from "../Charts/Skelton"; + +const formatDate = (dateStr) => { + const date = new Date(dateStr); + return date.toLocaleDateString("en-GB", { + day: "2-digit", + month: "long", + }); +}; + +const AttendanceOverview = () => { + const [dayRange, setDayRange] = useState(7); + const [view, setView] = useState("chart"); + + const projectId = useSelector((store) => store.localVariables.projectId); + const { attendanceOverviewData, loading, error } = useAttendanceOverviewData(projectId, dayRange); + + const { tableData, roles, dates } = useMemo(() => { + const map = new Map(); + + attendanceOverviewData.forEach((entry) => { + const date = formatDate(entry.date); + if (!map.has(date)) map.set(date, {}); + map.get(date)[entry.role.trim()] = entry.present; + }); + + const uniqueRoles = [...new Set(attendanceOverviewData.map((e) => e.role.trim()))]; + const sortedDates = [...map.keys()]; + const data = sortedDates.map((date) => { + const row = { date }; + uniqueRoles.forEach((role) => { + row[role] = map.get(date)?.[role] ?? 0; + }); + return row; + }); + + return { + tableData: data, + roles: uniqueRoles, + dates: sortedDates, + }; + }, [attendanceOverviewData]); + + const chartSeries = roles.map((role) => ({ + name: role, + data: tableData.map((row) => row[role]), + })); + + const chartOptions = { + chart: { + type: "bar", + stacked: true, + height: 400, + toolbar: { show: false }, + }, + plotOptions: { + bar: { + borderRadius: 8, + columnWidth: "60%", + }, + }, + xaxis: { + categories: tableData.map((row) => row.date), + }, + legend: { + position: "bottom", + }, + fill: { + opacity: 1, + }, + colors: roles.map((_, i) => flatColors[i % flatColors.length]), + }; + + return ( +
+ {/* Header */} +
+
+
Attendance Overview
+

Role-wise present count

+
+
+ + + +
+
+ + {/* Content */} +
+ {loading ? ( + + ) : error ? ( +

{error}

+ ) : view === "chart" ? ( +
+ +
+ ) : ( +
+ + + + + {dates.map((date, idx) => ( + + ))} + + + + {roles.map((role) => ( + + + {tableData.map((row, idx) => ( + + ))} + + ))} + +
Role{date}
{role}{row[role]}
+
+ )} +
+
+ ); + +}; + +export default AttendanceOverview; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 78ebfb54..311fa763 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,71 +1,69 @@ import React from "react"; -import { useSelector } from "react-redux"; // Import useSelector to access Redux state +import { useSelector } from "react-redux"; import { -useDashboardProjectsCardData, -useDashboardTeamsCardData, -useDashboardTasksCardData, + useDashboardProjectsCardData, + useDashboardTeamsCardData, + useDashboardTasksCardData, + useAttendanceOverviewData } from "../../hooks/useDashboard_Data"; + import Projects from "./Projects"; import Teams from "./Teams"; import TasksCard from "./Tasks"; import ProjectCompletionChart from "./ProjectCompletionChart"; import ProjectProgressChart from "./ProjectProgressChart"; import ProjectOverview from "../Project/ProjectOverview"; -// import Attendance from "./Attendance"; +import AttendanceOverview from "./AttendanceChart"; const Dashboard = () => { -const { projectsCardData } = useDashboardProjectsCardData(); -const { teamsCardData } = useDashboardTeamsCardData(); -const { tasksCardData } = useDashboardTasksCardData(); + const { projectsCardData } = useDashboardProjectsCardData(); + const { teamsCardData } = useDashboardTeamsCardData(); + const { tasksCardData } = useDashboardTasksCardData(); -// Get the selected project ID from Redux store -const selectedProjectId = useSelector( -(store) => store.localVariables.projectId -); + // Get the selected project ID from Redux store + const projectId = useSelector((store) => store.localVariables.projectId); + const isAllProjectsSelected = projectId === null; -// Determine if "All Projects" is selected -// selectedProjectId will be null when "All Projects" is chosen -const isAllProjectsSelected = selectedProjectId === null; + return ( +
+
+ {isAllProjectsSelected && ( +
+ +
+ )} -return ( -
-
+
+ +
-{isAllProjectsSelected && ( -
- -
-)} +
+ +
+ {isAllProjectsSelected && ( +
+ +
+ )} -
- -
+ {!isAllProjectsSelected && ( +
+ +
+ )} -
- -
- - -{isAllProjectsSelected && ( -
- -
-)} - -{! isAllProjectsSelected && ( -
- -
-)} -
- -
- - -
-
-); +
+ +
+ {!isAllProjectsSelected && ( +
+ {/* ✅ Removed unnecessary projectId prop */} +
+ )} +
+
+ ); }; export default Dashboard; \ No newline at end of file diff --git a/src/components/Dashboard/Tasks.jsx b/src/components/Dashboard/Tasks.jsx index fa924036..725f5e69 100644 --- a/src/components/Dashboard/Tasks.jsx +++ b/src/components/Dashboard/Tasks.jsx @@ -1,8 +1,11 @@ import React from "react"; +import { useSelector } from "react-redux"; import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data"; const TasksCard = () => { - const { tasksCardData } = useDashboardTasksCardData(); + const projectId = useSelector((store) => store.localVariables?.projectId); + const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId); + console.log(tasksCardData); return (
@@ -11,20 +14,34 @@ const TasksCard = () => { Tasks
-
-
-

- {tasksCardData.totalTasks?.toLocaleString()} -

- Total + + {loading ? ( + // Loader will be displayed when loading is true +
+
+ Loading... +
-
-

- {tasksCardData.completedTasks?.toLocaleString()} -

- Completed + ) : error ? ( + // Error message if there's an error +
{error}
+ ) : ( + // Actual data when loaded successfully +
+
+

+ {tasksCardData?.totalTasks?.toLocaleString()} +

+ Total +
+
+

+ {tasksCardData?.completedTasks?.toLocaleString()} +

+ Completed +
-
+ )}
); }; diff --git a/src/components/Dashboard/Teams.jsx b/src/components/Dashboard/Teams.jsx index f495caa7..00e881f5 100644 --- a/src/components/Dashboard/Teams.jsx +++ b/src/components/Dashboard/Teams.jsx @@ -1,29 +1,33 @@ import React, { useCallback, useEffect, useState } from "react"; +import { useSelector } from "react-redux"; import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data"; import eventBus from "../../services/eventBus"; const Teams = () => { - const { teamsCardData } = useDashboardTeamsCardData(); - const[totalEmployees,setTotalEmployee] = useState(0); - const[inToday,setInToday] = useState(0); + const projectId = useSelector((store) => store.localVariables?.projectId); + const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId); - useEffect(() =>{ - setTotalEmployee(teamsCardData.totalEmployees) - setInToday(teamsCardData.inToday) - },[teamsCardData.totalEmployees,teamsCardData.inToday]) + const [totalEmployees, setTotalEmployee] = useState(0); + const [inToday, setInToday] = useState(0); + + // Update state when API data arrives + useEffect(() => { + setTotalEmployee(teamsCardData?.totalEmployees || 0); + setInToday(teamsCardData?.inToday || 0); + }, [teamsCardData]); + + // Handle real-time updates via eventBus + const handler = useCallback((msg) => { + if (msg.activity === 1) { + setInToday((prev) => prev + 1); + } + }, []); - const handler = useCallback( - (msg) => { - if (msg.activity == 1) { - setInToday(prev => prev + 1); - } - }, - [inToday] - ); useEffect(() => { eventBus.on("attendance", handler); return () => eventBus.off("attendance", handler); }, [handler]); + return (
@@ -31,20 +35,30 @@ const Teams = () => { Teams
-
-
-

- {totalEmployees?.toLocaleString()} -

- Total Employees + + {loading ? ( + // Blue spinner loader +
+
+ Loading... +
-
-

- {inToday?.toLocaleString()} -

- In Today + ) : error ? ( + // Error message if data fetching fails +
{error}
+ ) : ( + // Display data once loaded +
+
+

{totalEmployees.toLocaleString()}

+ Total Employees +
+
+

{inToday.toLocaleString()}

+ In Today +
-
+ )}
); }; diff --git a/src/components/Project/ProjectOverview.jsx b/src/components/Project/ProjectOverview.jsx index bdf0562b..a2c57f8e 100644 --- a/src/components/Project/ProjectOverview.jsx +++ b/src/components/Project/ProjectOverview.jsx @@ -165,7 +165,7 @@ const ProjectOverview = ({ project }) => { }, [selectedProject]); return ( -
+
{" "} diff --git a/src/hooks/useDashboard_Data.jsx b/src/hooks/useDashboard_Data.jsx index a46aa0c8..831d9092 100644 --- a/src/hooks/useDashboard_Data.jsx +++ b/src/hooks/useDashboard_Data.jsx @@ -39,32 +39,32 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => { export const useDashboard_AttendanceData = (date, projectId) => { - const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); - const [isLineChartLoading, setLoading] = useState(false); - const [error, setError] = useState(""); + const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); + const [isLineChartLoading, setLoading] = useState(false); + const [error, setError] = useState(""); - useEffect(() => { - const fetchData = async () => { - setLoading(true); - setError(""); + useEffect(() => { + const fetchData = async () => { + setLoading(true); + setError(""); - try { - const response = await GlobalRepository.getDashboardAttendanceData(date,projectId); // date in 2nd param - setDashboard_AttendanceData(response.data); - } catch (err) { - setError("Failed to fetch dashboard data."); - console.error(err); - } finally { - setLoading(false); - } - }; + try { + const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param + setDashboard_AttendanceData(response.data); + } catch (err) { + setError("Failed to fetch dashboard data."); + console.error(err); + } finally { + setLoading(false); + } + }; - if (date && projectId !== null) { - fetchData(); - } - }, [date, projectId]); + if (date && projectId !== null) { + fetchData(); + } + }, [date, projectId]); - return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; + return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; }; @@ -97,36 +97,38 @@ export const useDashboardProjectsCardData = () => { }; // 🔹 Dashboard Teams Card Data Hook -export const useDashboardTeamsCardData = () => { - const [teamsCardData, setTeamsData] = useState([]); +export const useDashboardTeamsCardData = (projectId) => { + const [teamsCardData, setTeamsData] = useState({}); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); useEffect(() => { const fetchTeamsData = async () => { + if (!projectId) return; // ✅ Skip if projectId is not provided + setLoading(true); setError(""); try { - const response = await GlobalRepository.getDashboardTeamsCardData(); - setTeamsData(response.data); + const response = await GlobalRepository.getDashboardTeamsCardData(projectId); + setTeamsData(response.data); // ✅ Handle undefined/null } catch (err) { setError("Failed to fetch teams card data."); console.error(err); + setTeamsData({}); } finally { setLoading(false); } }; fetchTeamsData(); - }, []); + }, [projectId]); return { teamsCardData, loading, error }; }; -// 🔹 Dashboard Tasks Card Data Hook -export const useDashboardTasksCardData = () => { - const [tasksCardData, setTasksData] = useState([]); +export const useDashboardTasksCardData = (projectId) => { + const [tasksCardData, setTasksData] = useState({}); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); @@ -136,18 +138,47 @@ export const useDashboardTasksCardData = () => { setError(""); try { - const response = await GlobalRepository.getDashboardTasksCardData(); + const response = await GlobalRepository.getDashboardTasksCardData(projectId); setTasksData(response.data); } catch (err) { setError("Failed to fetch tasks card data."); console.error(err); + setTasksData({}); } finally { setLoading(false); } }; fetchTasksData(); - }, []); + }, [projectId]); return { tasksCardData, loading, error }; }; + + +export const useAttendanceOverviewData = (projectId, days) => { + const [attendanceOverviewData, setAttendanceOverviewData] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + if (!projectId || !days) return; + const fetchAttendanceOverview = async () => { + setLoading(true); + setError(""); + + try { + const response = await GlobalRepository.getAttendanceOverview(projectId, days); + setAttendanceOverviewData(response.data); + } catch (err) { + setError("Failed to fetch attendance overview data."); + } finally { + setLoading(false); + } + }; + + fetchAttendanceOverview(); + }, [projectId, days]); + + return { attendanceOverviewData, loading, error }; +}; diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index ff1be7bb..a218b1df 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -1,4 +1,4 @@ -import { useSelector } from "react-redux"; // Import useSelector +import { useSelector, useDispatch } from "react-redux"; // Import useSelector import React, { useState, useEffect, useCallback } from "react"; import ProjectOverview from "../../components/Project/ProjectOverview"; @@ -22,14 +22,25 @@ import { ComingSoonPage } from "../Misc/ComingSoonPage"; import Directory from "../Directory/Directory"; import eventBus from "../../services/eventBus"; import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart"; +import { useProjectName } from "../../hooks/useProjects"; +import AttendanceOverview from "../../components/Dashboard/AttendanceChart"; const ProjectDetails = () => { const projectId = useSelector((store) => store.localVariables.projectId); + const { projectNames, fetchData } = useProjectName(); + const dispatch = useDispatch() + + useEffect(() => { + if (projectId == null) { + dispatch(setProjectId(projectNames[0]?.id)); + } + }, [projectNames]) + const { - projects_Details, + projects_Details, loading: projectLoading, error: projectError, refetch, @@ -71,6 +82,7 @@ const ProjectDetails = () => {
+
diff --git a/src/repositories/GlobalRepository.jsx b/src/repositories/GlobalRepository.jsx index eda5312e..d3367fa6 100644 --- a/src/repositories/GlobalRepository.jsx +++ b/src/repositories/GlobalRepository.jsx @@ -26,12 +26,22 @@ const GlobalRepository = { getDashboardProjectsCardData: () => { return api.get(`/api/Dashboard/projects`); }, - getDashboardTeamsCardData: () => { - return api.get(`/api/Dashboard/teams`); - }, - getDashboardTasksCardData: () => { - return api.get(`/api/Dashboard/tasks`); - }, + + getDashboardTeamsCardData: (projectId) => { + const url = projectId + ? `/api/Dashboard/teams?projectId=${projectId}` + : `/api/Dashboard/teams`; + return api.get(url); +}, + + getDashboardTasksCardData: (projectId) => { + const url = projectId + ? `/api/Dashboard/tasks?projectId=${projectId}` + : `/api/Dashboard/tasks`; + return api.get(url); +}, + + getAttendanceOverview:(projectId,days)=>api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`) };