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`,