diff --git a/src/actions/bmdashboard/injuryActions.js b/src/actions/bmdashboard/injuryActions.js
index 549486366f..3b82056ad2 100644
--- a/src/actions/bmdashboard/injuryActions.js
+++ b/src/actions/bmdashboard/injuryActions.js
@@ -5,7 +5,6 @@ import {
FETCH_INJURIES_FAILURE
} from './types';
import { ENDPOINTS } from '../../utils/URL';
-
export const FETCH_BM_INJURY_DATA_REQUEST = 'FETCH_BM_INJURY_DATA_REQUEST';
export const FETCH_BM_INJURY_DATA_SUCCESS = 'FETCH_BM_INJURY_DATA_SUCCESS';
export const FETCH_BM_INJURY_DATA_FAILURE = 'FETCH_BM_INJURY_DATA_FAILURE';
@@ -13,6 +12,7 @@ export const RESET_BM_INJURY_DATA = 'RESET_BM_INJURY_DATA';
export const FETCH_BM_INJURY_SEVERITIES = 'FETCH_BM_INJURY_SEVERITIES';
export const FETCH_BM_INJURY_TYPES = 'FETCH_BM_INJURY_TYPES';
export const FETCH_BM_INJURY_PROJECTS = 'FETCH_BM_INJURY_PROJECTS';
+export const FETCH_BM_INJURY_OVER_TIME = 'FETCH_BM_INJURY_OVER_TIME';
// Legacy constants for backward compatibility
export const GET_INJURY_SEVERITY = 'GET_INJURY_SEVERITY';
@@ -47,6 +47,7 @@ const setInjuryDataError = payload => ({ type: FETCH_BM_INJURY_DATA_FAILURE, pay
const setInjurySeverities = payload => ({ type: FETCH_BM_INJURY_SEVERITIES, payload });
const setInjuryTypes = payload => ({ type: FETCH_BM_INJURY_TYPES, payload });
const setInjuryProjects = payload => ({ type: FETCH_BM_INJURY_PROJECTS, payload });
+const setInjuryOverTime = payload => ({ type: FETCH_BM_INJURY_OVER_TIME, payload });
// Legacy action creators for backward compatibility
export const setInjurySeverity = payload => ({
@@ -179,3 +180,33 @@ export const getInjuryData = async (projectId, startDate, endDate) => {
// Return the data directly
return response.data;
};
+
+export const fetchInjuriesOverTime = (filters = {}) => {
+ return async dispatch => {
+ try {
+ const params = {};
+
+ if (filters.projectIds?.length) {
+ params.projectIds = filters.projectIds.join(',');
+ }
+ if (filters.startDate && filters.endDate) {
+ params.startDate = filters.startDate;
+ params.endDate = filters.endDate;
+ }
+ if (filters.types?.length) {
+ params.types = filters.types.join(',');
+ }
+ if (filters.departments?.length) {
+ params.departments = filters.departments.join(',');
+ }
+ if (filters.severities?.length) {
+ params.severities = filters.severities.join(',');
+ }
+
+ const res = await axios.get(ENDPOINTS.BM_INJURY_OVER_TIME, { params });
+ dispatch(setInjuryOverTime(res.data));
+ } catch (err) {
+ dispatch(setErrors(err.response?.data?.error || err.message));
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.jsx b/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.jsx
new file mode 100644
index 0000000000..a6045d17d2
--- /dev/null
+++ b/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.jsx
@@ -0,0 +1,363 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { connect, useDispatch, useSelector } from 'react-redux';
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ LabelList,
+ ResponsiveContainer,
+} from 'recharts';
+import { Select, DatePicker, Spin } from 'antd';
+import dayjs from 'dayjs';
+
+import { fetchInjuriesOverTime } from '../../../actions/bmdashboard/injuryActions';
+import styles from './InjuriesOverTimeChart.module.css';
+
+const { Option } = Select;
+const { RangePicker } = DatePicker;
+
+const shortId = id => (id ? String(id).slice(-6) : 'unknown');
+const generateColors = n =>
+ Array.from({ length: n }, (_, i) => `hsl(${Math.round((360 / Math.max(n, 1)) * i)},60%,55%)`);
+
+function CustomTooltip({ active, payload, label, darkMode }) {
+ if (!active || !payload || payload.length === 0) return null;
+ return (
+
+
{label}
+ {payload
+ .filter(p => p?.value != null && Number(p.value) > 0)
+ .map(p => (
+
+
+ {p.name}
+ {p.value}
+
+ ))}
+
+ );
+}
+
+function InjuriesOverTimeLine({ darkMode = false }) {
+ const dispatch = useDispatch();
+
+ const rawData = useSelector(state => state.bmInjury?.injuryOverTimeData || []);
+
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const [selProjects, setSelProjects] = useState([]);
+ const [dateRange, setDateRange] = useState([null, null]);
+ const [selInjTypes, setSelInjTypes] = useState([]);
+ const [selDepts, setSelDepts] = useState([]);
+ const [selSeverities, setSelSeverities] = useState([]);
+ const [projectOptions, setProjectOptions] = useState([]);
+ const [departmentOptions, setDepartmentOptions] = useState([]);
+ const [injuryTypeOptions, setInjuryTypeOptions] = useState([]);
+ const [severityOptions, setSeverityOptions] = useState([]);
+
+ useEffect(() => {
+ setLoading(true);
+ setError(null);
+ Promise.resolve(
+ dispatch(
+ fetchInjuriesOverTime({
+ projectIds: selProjects,
+ startDate: dateRange?.[0] ? dayjs(dateRange[0]).format('YYYY-MM-DD') : undefined,
+ endDate: dateRange?.[1] ? dayjs(dateRange[1]).format('YYYY-MM-DD') : undefined,
+ types: selInjTypes,
+ departments: selDepts,
+ severities: selSeverities,
+ }),
+ ),
+ )
+ .catch(err => setError(err?.message || 'Failed to load data'))
+ .finally(() => setLoading(false));
+ }, [dispatch, selProjects, selInjTypes, selDepts, dateRange, selSeverities]);
+
+ useEffect(() => {
+ if (!rawData.length) return;
+
+ setProjectOptions(prev => {
+ const next = new Map(prev.map(project => [project.id, project]));
+ rawData.forEach(record => {
+ const id = String(record.projectId || '');
+ if (id) next.set(id, { id, label: `Project ...${shortId(id)}` });
+ });
+ return Array.from(next.values());
+ });
+
+ setDepartmentOptions(prev => {
+ const next = new Set(prev);
+ rawData.forEach(record => {
+ if (record.department) next.add(record.department);
+ });
+ return Array.from(next);
+ });
+
+ setInjuryTypeOptions(prev => {
+ const next = new Set(prev);
+ rawData.forEach(record => {
+ if (record.injuryType) next.add(record.injuryType);
+ });
+ return Array.from(next);
+ });
+
+ setSeverityOptions(prev => {
+ const next = new Set(prev);
+ rawData.forEach(record => {
+ if (record.severity) next.add(record.severity);
+ });
+ return Array.from(next);
+ });
+ }, [rawData]);
+
+ const filtered = useMemo(() => {
+ const [start, end] = dateRange || [null, null];
+ return rawData.filter(r => {
+ const pid = String(r.projectId);
+ const keepProject = selProjects.length === 0 || selProjects.includes(pid);
+ const keepDept = selDepts.length === 0 || selDepts.includes(r.department);
+ const keepType = selInjTypes.length === 0 || selInjTypes.includes(r.injuryType);
+ const keepSev = selSeverities.length === 0 || selSeverities.includes(r.severity);
+ if (!r.date) return false;
+
+ let keepDate = true;
+ const d = dayjs(r.date);
+ if (start && !end) keepDate = d.isSame(start, 'day') || d.isAfter(start);
+ if (!start && end) keepDate = d.isSame(end, 'day') || d.isBefore(end);
+ if (start && end)
+ keepDate =
+ (d.isSame(start, 'day') || d.isAfter(start)) && (d.isSame(end, 'day') || d.isBefore(end));
+
+ return keepProject && keepDept && keepType && keepSev && keepDate;
+ });
+ }, [rawData, selProjects, selDepts, selInjTypes, selSeverities, dateRange]);
+
+ const visibleProjectIds = useMemo(
+ () => Array.from(new Set(filtered.map(r => String(r.projectId)))),
+ [filtered],
+ );
+ const visibleProjects = useMemo(
+ () => visibleProjectIds.map(id => ({ id, label: `Project …${shortId(id)}` })),
+ [visibleProjectIds],
+ );
+
+ const chartData = useMemo(() => {
+ const keysSet = new Set(filtered.map(r => dayjs(r.date).format('YYYY-MM')));
+ const monthKeys = Array.from(keysSet).sort((a, b) => dayjs(a).valueOf() - dayjs(b).valueOf());
+
+ const totals = new Map();
+ filtered.forEach(r => {
+ const k = dayjs(r.date).format('YYYY-MM');
+ const pid = String(r.projectId);
+ const mapKey = `${k}|${pid}`;
+ totals.set(mapKey, (totals.get(mapKey) || 0) + (Number(r.count) || 0));
+ });
+
+ return monthKeys.map(k => {
+ const label = dayjs(k + '-01').format('MMM YYYY');
+ const row = { month: label, _k: k };
+ visibleProjects.forEach(({ id }) => {
+ const v = totals.get(`${k}|${id}`) || 0;
+ row[id] = v > 0 ? v : null;
+ });
+ return row;
+ });
+ }, [filtered, visibleProjects]);
+
+ const { yTicks, yDomain } = useMemo(() => {
+ let dataMax = 0;
+ for (const row of chartData) {
+ for (const { id } of visibleProjects) {
+ const v = row[id];
+ if (typeof v === 'number' && v > dataMax) dataMax = v;
+ }
+ }
+
+ const divisions = 5;
+ if (dataMax <= 0) {
+ return {
+ yTicks: Array.from({ length: divisions + 1 }, (_, i) => i),
+ yDomain: [0, divisions],
+ };
+ }
+
+ const rawStep = Math.ceil(dataMax / divisions);
+ const step = Math.max(1, rawStep);
+ const niceMax = step * divisions;
+ const ticks = Array.from({ length: divisions + 1 }, (_, i) => i * step);
+ return { yTicks: ticks, yDomain: [0, niceMax] };
+ }, [chartData, visibleProjects]);
+
+ const lineColors = useMemo(() => generateColors(visibleProjects.length || 1), [
+ visibleProjects.length,
+ ]);
+
+ return (
+
+
Injuries over time (Jan 1 2024 - Dec 31 2024)
+
+
+
+
+ setDateRange(dates || [null, null])}
+ popupClassName={darkMode ? 'wrapperDark-dropdown' : ''}
+ />
+
+
+
+
+
+
+
+
+ {error &&
{error}
}
+ {loading ? (
+
+
+
+ ) : visibleProjects?.length > 0 ? (
+
+
+
+
+
+
+ } />
+
+ {visibleProjects.map((proj, idx) => (
+
+ {
+ const { x, y, value } = props;
+ if (!value || value <= 0) return null;
+ return (
+
+ {value}
+
+ );
+ }}
+ />
+
+ ))}
+
+
+
+ ) : (
+
No Data Available
+ )}
+
+ );
+}
+
+const mapStateToProps = state => ({
+ darkMode: state?.theme?.darkMode,
+});
+
+export default connect(mapStateToProps)(InjuriesOverTimeLine);
diff --git a/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.module.css b/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.module.css
new file mode 100644
index 0000000000..e18738ac4a
--- /dev/null
+++ b/src/components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart.module.css
@@ -0,0 +1,323 @@
+.wrapper {
+ padding: 0 24px;
+ background-color: #ffffff;
+ min-height: 100%;
+}
+
+.wrapperDark {
+ background-color: #1b2a41;
+ color: #f5f7fa;
+}
+
+.title {
+ margin: 0;
+ padding: 16px 0 8px;
+ font-size: 25px;
+ font-weight: 700;
+ line-height: 1.3;
+}
+
+.filters {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ margin-bottom: 20px;
+ padding-left: 20px;
+}
+
+.filterSelect {
+ flex: 1 1 180px;
+ min-width: 180px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.filterSelect :global(.ant-select-selector),
+.filterSelect :global(.ant-picker) {
+ height: 40px;
+ border-color: #d9d9d9;
+}
+.filterSelect :global(.ant-select-selector .ant-select-selection-search-input) {
+ height: 38px;
+}
+
+.filterSelect :global(.ant-select-selection-placeholder){
+ color: rgba(0, 0, 0, 0.7) !important;
+}
+
+/* Light mode RangePicker placeholder color */
+.filterSelect :global(.ant-picker-input input::placeholder) {
+ color: rgba(0, 0, 0, 0.7) !important;
+}
+
+.filterSelect :global(.ant-picker-input input) {
+ text-align: center;
+}
+/* Dark Mode Styles for Select */
+.wrapperDark .filterSelect :global(.ant-select-selector) {
+ background-color: #0f172a !important;
+ color: #f5f7fa !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-select-selection-item) {
+ background-color: rgba(59, 130, 246, 0.3) !important;
+ border-color: rgba(59, 130, 246, 0.5) !important;
+ color: #f5f7fa !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-select-selection-placeholder),
+.wrapperDark .filterSelect :global(.ant-picker-input input::placeholder) {
+ color: rgba(245, 247, 250, 0.5) !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-select-arrow),
+.wrapperDark .filterSelect :global(.ant-select-clear) {
+ color: rgba(245, 247, 250, 0.7) !important;
+}
+
+
+
+/* Dark Mode Styles for RangePicker */
+.wrapperDark .filterSelect :global(.ant-picker) {
+ background-color: #0f172a !important;
+ border-color: rgba(255, 255, 255, 0.2) !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-picker input) {
+ color: #f5f7fa !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-picker input::placeholder) {
+ color: rgba(245, 247, 250, 0.5) !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-picker-suffix),
+.wrapperDark .filterSelect :global(.ant-picker-clear) {
+ color: rgba(245, 247, 250, 0.7) !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-picker-separator) {
+ color: rgba(245, 247, 250, 0.5) !important;
+}
+
+.wrapperDark .filterSelect :global(.ant-picker-active-bar) {
+ background: rgba(59, 130, 246, 0.5) !important;
+}
+
+/* Dark Mode Dropdown Styles (appears in portal, so needs global styles) */
+:global(.ant-select-dropdown.wrapperDark-dropdown) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-select-dropdown.wrapperDark-dropdown .ant-select-item) {
+ color: #f5f7fa !important;
+}
+
+:global(.ant-select-dropdown.wrapperDark-dropdown .ant-select-item-option-selected) {
+ background-color: rgba(59, 130, 246, 0.2) !important;
+}
+
+:global(.ant-select-dropdown.wrapperDark-dropdown .ant-select-item-option-active) {
+ background-color: rgba(59, 130, 246, 0.1) !important;
+}
+
+/* Dark Mode RangePicker Dropdown */
+:global(.ant-picker-dropdown.wrapperDark-dropdown) {
+ background-color: #1b2a41 !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-panel-container) {
+ background-color: #0f172a !important;
+ border-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-panel) {
+ background-color: #0f172a !important;
+ border-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-date-panel),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-body) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-header) {
+ color: #f5f7fa !important;
+ background-color: #0f172a !important;
+ border-bottom-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-header button) {
+ color: #f5f7fa !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-header button:hover) {
+ color: #3b82f6 !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-header-view button) {
+ color: #f5f7fa !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-header-view button:hover) {
+ color: #3b82f6 !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-content) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-content thead tr) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-content th),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-content td) {
+ color: #f5f7fa !important;
+ background-color: transparent !important;
+}
+
+.wrapperDark :global(.ant-picker) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell) {
+ color: rgba(245, 247, 250, 0.4) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-in-view) {
+ color: #f5f7fa !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-disabled) {
+ color: rgba(245, 247, 250, 0.25) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-disabled::before) {
+ background-color: transparent !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell .ant-picker-cell-inner) {
+ background-color: transparent !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end):not(.ant-picker-cell-disabled) .ant-picker-cell-inner) {
+ background-color: rgba(59, 130, 246, 0.15) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-selected .ant-picker-cell-inner),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-range-start .ant-picker-cell-inner),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-range-end .ant-picker-cell-inner) {
+ background-color: #3b82f6 !important;
+ color: #ffffff !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-in-range::before) {
+ background-color: rgba(59, 130, 246, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single)::before),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single)::before) {
+ background-color: rgba(59, 130, 246, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-cell-today .ant-picker-cell-inner::before) {
+ border-color: #3b82f6 !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-footer) {
+ background-color: #0f172a !important;
+ border-top-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-today-btn) {
+ color: #3b82f6 !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-year-panel),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-month-panel),
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-decade-panel) {
+ background-color: #0f172a !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-time-panel) {
+ background-color: #0f172a !important;
+ border-left-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-time-panel-column) {
+ border-left-color: rgba(255, 255, 255, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-time-panel-cell) {
+ color: #f5f7fa !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-time-panel-cell:hover) {
+ background-color: rgba(59, 130, 246, 0.1) !important;
+}
+
+:global(.ant-picker-dropdown.wrapperDark-dropdown .ant-picker-time-panel-cell-selected) {
+ background-color: rgba(59, 130, 246, 0.2) !important;
+}
+
+.chartCard {
+ width: 100%;
+ height: 420px;
+ background: #f8fafc;
+ border: 1px solid rgba(0,0,0,0.06);
+ border-radius: 10px;
+ padding: 8px 4px;
+}
+
+.chartCardDark {
+ background: #0f172a;
+ border-color: rgba(255,255,255,0.08);
+}
+
+.chartCard :global(svg) {
+ border-radius: 8px;
+}
+
+.legendSpacer {
+ padding-top: 20px;
+}
+
+.tooltip {
+ background: #ffffff;
+ color: #111827;
+ border: 1px solid rgba(0,0,0,0.15);
+ padding: 10px;
+ border-radius: 6px;
+ box-shadow:
+ 0 1px 2px rgba(0,0,0,0.06),
+ 0 4px 12px rgba(0,0,0,0.08);
+}
+.tooltipDark {
+ background: #1f2937;
+ color: #f9fafb;
+ border-color: rgba(255,255,255,0.18);
+}
+
+.noData {
+ text-align: center;
+ padding: 24px 0;
+ color: #6b7280;
+ font-size: 14px;
+}
+.noDataDark {
+ color: #cbd5e1;
+}
+
+@media (max-width: 768px) {
+ .title {
+ font-size: 18px;
+ }
+ .filters {
+ padding-left: 0;
+ gap: 10px;
+ }
+ .chartCard {
+ height: 360px;
+ }
+}
\ No newline at end of file
diff --git a/src/reducers/bmdashboard/injuryReducer.js b/src/reducers/bmdashboard/injuryReducer.js
index 55e4468c4e..d6fd50e3ed 100644
--- a/src/reducers/bmdashboard/injuryReducer.js
+++ b/src/reducers/bmdashboard/injuryReducer.js
@@ -12,6 +12,7 @@ import {
FETCH_BM_INJURY_TYPES,
FETCH_BM_INJURY_PROJECTS,
RESET_BM_INJURY_DATA,
+ FETCH_BM_INJURY_OVER_TIME,
GET_INJURY_SEVERITY,
} from '../../actions/bmdashboard/injuryActions';
@@ -25,6 +26,7 @@ const initialState = {
severities: [],
injuryTypes: [],
projects: [], // [{ _id, name }]
+ injuryOverTimeData: [],
severityData: [], // Legacy field for backward compatibility
};
@@ -85,6 +87,11 @@ function bmInjuryReducer(state = initialState, action) {
return { ...state, projects };
}
+ case FETCH_BM_INJURY_OVER_TIME: {
+ const injuryOverTimeData = Array.isArray(action.payload) ? action.payload : [];
+ return { ...state, injuryOverTimeData };
+ }
+
case RESET_BM_INJURY_DATA:
return { ...state, data: [], error: null, loading: false };
diff --git a/src/routes.jsx b/src/routes.jsx
index bae3755620..4e6692d492 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -246,6 +246,11 @@ const PurchaseTools = lazy(() => import('./components/BMDashboard/ToolPurchaseRe
const PurchaseEquipment = lazy(() => import('./components/BMDashboard/EquipmentPurchaseRequest'));
const AddMaterial = lazy(() => import('./components/BMDashboard/AddMaterial/AddMaterial'));
const AddConsumable = lazy(() => import('./components/BMDashboard/AddConsumable/AddConsumable'));
+
+const InjuriesOverTimeChart = lazy(() =>
+ import('./components/BMDashboard/InjuriesOverTime/InjuriesOverTimeChart'),
+);
+
// Code-Splitting
const Projects = lazy(() => import('./components/Projects'));
const WeeklySummariesReport = lazy(() => import('./components/WeeklySummariesReport'));
@@ -779,6 +784,12 @@ export default (
exact
component={ToolsAvailabilityPage}
/>
+
{/* PR Analytics Dashboard */}
{/* Community Portal Routes */}
diff --git a/src/utils/URL.js b/src/utils/URL.js
index 1ffcbea76b..b3616b2a37 100644
--- a/src/utils/URL.js
+++ b/src/utils/URL.js
@@ -375,6 +375,7 @@ export const ENDPOINTS = {
BM_INJURY_SEVERITIES: `${APIEndpoint}/bm/injuries/injury-severities`,
BM_INJURY_TYPES: `${APIEndpoint}/bm/injuries/injury-types`,
BM_INJURY_PROJECTS: `${APIEndpoint}/bm/injuries/project-injury`,
+ BM_INJURY_OVER_TIME: `${APIEndpoint}/bm/injuries/over-time`,
BM_INJURY_ISSUE: `${APIEndpoint}/bm/issues`,
BM_INJURY_SEVERITY: `${APIEndpoint}/bm/injuries/severity-by-project`,
BM_RENTAL_CHART: `${APIEndpoint}/bm/rentalChart`,