204 lines
5.7 KiB
JavaScript
204 lines
5.7 KiB
JavaScript
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: 2,
|
|
columnWidth: "60%",
|
|
},
|
|
},
|
|
xaxis: {
|
|
categories: tableData.map((row) => row.date),
|
|
},
|
|
yaxis: {
|
|
show: true,
|
|
axisBorder: {
|
|
show: true,
|
|
color: "#78909C",
|
|
offsetX: 0,
|
|
offsetY: 0,
|
|
},
|
|
axisTicks: {
|
|
show: true,
|
|
borderType: "solid",
|
|
color: "#78909C",
|
|
width: 6,
|
|
offsetX: 0,
|
|
offsetY: 0,
|
|
},
|
|
},
|
|
legend: {
|
|
position: "bottom",
|
|
},
|
|
fill: {
|
|
opacity: 1,
|
|
},
|
|
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="bg-white p-4 rounded shadow d-flex flex-column"
|
|
>
|
|
{/* Header */}
|
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
|
<div className="card-title mb-0 text-start">
|
|
<h5 className="mb-1 fw-bold">Attendance Overview</h5>
|
|
<p className="card-subtitle">Role-wise present count</p>
|
|
</div>
|
|
<div className="d-flex gap-2">
|
|
<select
|
|
className="form-select form-select-sm"
|
|
value={dayRange}
|
|
onChange={(e) => setDayRange(Number(e.target.value))}
|
|
>
|
|
<option value={7}>Last 7 Days</option>
|
|
<option value={15}>Last 15 Days</option>
|
|
<option value={30}>Last 30 Days</option>
|
|
</select>
|
|
<button
|
|
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
|
onClick={() => setView("chart")}
|
|
title="Chart View"
|
|
>
|
|
<i className="bx bx-bar-chart-alt-2"></i>
|
|
</button>
|
|
<button
|
|
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
|
onClick={() => setView("table")}
|
|
title="Table View"
|
|
>
|
|
<i className="bx bx-task text-success"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
|
|
{loading ? (
|
|
<ChartSkeleton />
|
|
) : error ? (
|
|
<p className="text-danger">{error}</p>
|
|
) : view === "chart" ? (
|
|
<div className="w-100">
|
|
<ReactApexChart
|
|
options={chartOptions}
|
|
series={chartSeries}
|
|
type="bar"
|
|
height={400}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div
|
|
className="table-responsive w-100"
|
|
style={{ maxHeight: "350px", overflowY: "auto" }}
|
|
>
|
|
<table className="table table-bordered table-sm text-start align-middle mb-0">
|
|
<thead
|
|
className="table-light"
|
|
style={{ position: "sticky", top: 0, zIndex: 1 }}
|
|
>
|
|
<tr>
|
|
<th style={{ background: "#f8f9fa", textTransform: "none" }}>
|
|
Role
|
|
</th>
|
|
{dates.map((date, idx) => (
|
|
<th
|
|
key={idx}
|
|
style={{ background: "#f8f9fa", textTransform: "none" }}
|
|
>
|
|
{date}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
{roles.map((role) => (
|
|
<tr key={role}>
|
|
<td>{role}</td>
|
|
{tableData.map((row, idx) => {
|
|
const value = row[role];
|
|
const cellStyle =
|
|
value > 0 ? { backgroundColor: "#d5d5d5" } : {};
|
|
return (
|
|
<td key={idx} style={cellStyle}>
|
|
{value}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AttendanceOverview;
|