Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27,383 changes: 27,383 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

39 changes: 32 additions & 7 deletions src/actions/bmdashboard/injuryActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ 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_TREND_SUCCESS = 'FETCH_BM_INJURY_TREND_SUCCESS';
export const CREATE_BM_INJURY_SUCCESS = 'CREATE_BM_INJURY_SUCCESS';
export const FETCH_BM_INJURY_OVER_TIME = 'FETCH_BM_INJURY_OVER_TIME';

// Legacy constants for backward compatibility
Expand Down Expand Up @@ -101,6 +103,33 @@ export const fetchInjuryProjects = (filters) => async dispatch => {
}
};

// Trend data: { months:[], serious:[], medium:[], low:[] }
export const fetchInjuryTrend = (filters) => async dispatch => {
dispatch(setInjuryDataLoading());
try {
const params = cleanParams(filters);
const res = await axios.get(ENDPOINTS.BM_INJURY_TREND, { params, paramsSerializer });
const data = res?.data && typeof res.data === 'object' ? res.data : { months: [], serious: [], medium: [], low: [] };
dispatch(setInjuryTrendSuccess(data));
} catch (error) {
const msg = error?.response?.data?.error || error?.message || 'Failed to fetch injury trend';
dispatch(setInjuryDataError(msg));
}
};

// Create injuries via API
export const createInjuries = (payload, { useDevSeed = false } = {}) => async dispatch => {
try {
const url = useDevSeed ? ENDPOINTS.BM_INJURY_DEV_SEED : ENDPOINTS.BM_INJURY_CREATE;
const res = await axios.post(url, payload);
dispatch(setCreateInjurySuccess(res?.data));
return res?.data;
} catch (error) {
const msg = error?.response?.data?.error || error?.message || 'Failed to create injuries';
throw new Error(msg);
}
};

// Legacy function for backward compatibility
export const fetchInjurySeverity = (filters = {}) => {
return async dispatch => {
Expand Down Expand Up @@ -137,7 +166,7 @@ export const fetchInjuries = (projectId, startDate, endDate) => async dispatch =
// Build query parameters
const params = {};
if (projectId && projectId !== 'all') {
params.projectId = projectId;
params.projectIds = projectId;
}
if (startDate) params.startDate = startDate;
if (endDate) params.endDate = endDate;
Expand All @@ -164,20 +193,16 @@ export const fetchInjuries = (projectId, startDate, endDate) => async dispatch =
}
};

// Function to get injury data (non-Redux version for direct component use)
// Function to get injury trend data (non-Redux version for direct component use)
export const getInjuryData = async (projectId, startDate, endDate) => {
// Build query parameters
const params = {};
if (projectId && projectId !== 'all') {
params.projectId = projectId;
}
if (startDate) params.startDate = startDate;
if (endDate) params.endDate = endDate;

// API call
const response = await axios.get(ENDPOINTS.INJURIES, { params });

// Return the data directly
const response = await axios.get(ENDPOINTS.BM_INJURY_TREND, { params });
return response.data;
};

Expand Down
53 changes: 53 additions & 0 deletions src/components/ApplicationTimeChart/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Helper function to generate dates relative to now
const generateTimestamp = (daysAgo, hoursAgo = 0) => {
const date = new Date();
date.setDate(date.getDate() - daysAgo);
date.setHours(date.getHours() - hoursAgo);
return date.toISOString();
};

const sampleData = [
// Recent applications (last 7 days) - Always fresh dates
{ role: 'Software Developer', timeToApply: 8, timestamp: generateTimestamp(0, 2) }, // 2 hours ago
{ role: 'Software Developer', timeToApply: 12, timestamp: generateTimestamp(1, 1) }, // 1 day, 1 hour ago
{ role: 'Software Developer', timeToApply: 9, timestamp: generateTimestamp(2, 3) }, // 2 days, 3 hours ago
{ role: 'Architect', timeToApply: 15, timestamp: generateTimestamp(1, 5) }, // 1 day, 5 hours ago
{ role: 'Architect', timeToApply: 18, timestamp: generateTimestamp(3, 2) }, // 3 days, 2 hours ago
{ role: 'Master Electrician', timeToApply: 25, timestamp: generateTimestamp(2, 8) }, // 2 days, 8 hours ago
{ role: 'Master Electrician', timeToApply: 22, timestamp: generateTimestamp(4, 1) }, // 4 days, 1 hour ago
{ role: 'Product Manager', timeToApply: 6, timestamp: generateTimestamp(3, 4) }, // 3 days, 4 hours ago
{ role: 'Product Manager', timeToApply: 9, timestamp: generateTimestamp(5, 2) }, // 5 days, 2 hours ago
{ role: 'Data Scientist', timeToApply: 13, timestamp: generateTimestamp(4, 6) }, // 4 days, 6 hours ago
{ role: 'Data Scientist', timeToApply: 11, timestamp: generateTimestamp(6, 3) }, // 6 days, 3 hours ago
{ role: 'UX Designer', timeToApply: 14, timestamp: generateTimestamp(5, 7) }, // 5 days, 7 hours ago
{ role: 'UX Designer', timeToApply: 16, timestamp: generateTimestamp(6, 4) }, // 6 days, 4 hours ago

// Monthly data (8-30 days ago)
{ role: 'Project Manager', timeToApply: 7, timestamp: generateTimestamp(8) }, // 8 days ago
{ role: 'Project Manager', timeToApply: 11, timestamp: generateTimestamp(11) }, // 11 days ago
{ role: 'Plumber', timeToApply: 20, timestamp: generateTimestamp(12) }, // 12 days ago
{ role: 'Plumber', timeToApply: 24, timestamp: generateTimestamp(16) }, // 16 days ago
{ role: 'Software Developer', timeToApply: 10, timestamp: generateTimestamp(18) }, // 18 days ago
{ role: 'Architect', timeToApply: 16, timestamp: generateTimestamp(21) }, // 21 days ago
{ role: 'Master Electrician', timeToApply: 28, timestamp: generateTimestamp(24) }, // 24 days ago
{ role: 'Data Scientist', timeToApply: 15, timestamp: generateTimestamp(26) }, // 26 days ago
{ role: 'UX Designer', timeToApply: 12, timestamp: generateTimestamp(28) }, // 28 days ago
{ role: 'Product Manager', timeToApply: 8, timestamp: generateTimestamp(30) }, // 30 days ago

// Older data (31-365 days ago)
{ role: 'UX Designer', timeToApply: 12, timestamp: generateTimestamp(50) }, // ~50 days ago
{ role: 'Product Manager', timeToApply: 8, timestamp: generateTimestamp(75) }, // ~75 days ago
{ role: 'Plumber', timeToApply: 26, timestamp: generateTimestamp(115) }, // ~115 days ago
{ role: 'Software Developer', timeToApply: 7, timestamp: generateTimestamp(180) }, // ~180 days ago
{ role: 'Architect', timeToApply: 14, timestamp: generateTimestamp(205) }, // ~205 days ago
{ role: 'Master Electrician', timeToApply: 24, timestamp: generateTimestamp(230) }, // ~230 days ago
{ role: 'Data Scientist', timeToApply: 16, timestamp: generateTimestamp(255) }, // ~255 days ago
{ role: 'Project Manager', timeToApply: 9, timestamp: generateTimestamp(295) }, // ~295 days ago

// Outliers (these will be filtered out as they're > 30 minutes)
{ role: 'Software Developer', timeToApply: 45, timestamp: generateTimestamp(3) }, // Tab left open
{ role: 'Product Manager', timeToApply: 120, timestamp: generateTimestamp(8) }, // 2 hours - outlier
{ role: 'UX Designer', timeToApply: 90, timestamp: generateTimestamp(11) }, // 1.5 hours - outlier
];

export default sampleData;
80 changes: 51 additions & 29 deletions src/components/BMDashboard/InjuryChart/InjuryChartForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,35 @@
import 'react-datepicker/dist/react-datepicker.css';
import styles from './InjuryChartForm.module.css';

function InjuryChartForm({ dark }) {

Check failure on line 26 in src/components/BMDashboard/InjuryChart/InjuryChartForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ4TOEmDQ47wmykST63m&open=AZ4TOEmDQ47wmykST63m&pullRequest=4063
// Chart type toggle state
const [chartType, setChartType] = useState('line'); // 'bar' or 'line'
const wrapperClass = dark ? styles.wrapperDark : 'bg-white';
const labelClass = dark ? styles.wrapperDark : '';
const gridStroke = dark ? '#374151' : '#eee';
const tickStyle = { fill: dark ? '#d1d5db' : '#666' };
const xLabelStyle = {
value: 'Month',
position: 'insideBottom',
offset: -10,
fill: tickStyle.fill,
};
const yLabelStyle = {
value: 'Number of Injuries',
angle: -90,
position: 'insideLeft',
fill: tickStyle.fill,
};
const tooltipStyle = {
backgroundColor: dark ? '#1e293b' : '#fff',
border: `1px solid ${dark ? '#475569' : '#ddd'}`,
borderRadius: 8,
color: dark ? '#e2e8f0' : '#333',
};
const tooltipLabelStyle = { color: dark ? '#f1f5f9' : '#333', fontWeight: 600 };
const tooltipItemStyle = { color: dark ? '#e2e8f0' : '#555' };
const noDataClass = dark ? 'bg-dark text-light' : 'bg-white';
const noDataText = dark ? 'text-light' : 'text-muted';

const [chartType, setChartType] = useState('line');
const dispatch = useDispatch();
const bmProjects = useSelector(state => state.bmProjects || []);
// Form state
Expand Down Expand Up @@ -118,15 +144,11 @@
return (
<div className={`${styles.injuryChartContainer} p-4`}>
{/* Filter Form */}
<div
className={`${styles.filterForm} mb-4 p-3 ${
dark ? styles.wrapperDark : 'bg-white'
} rounded shadow-sm`}
>
<div className={`${styles.filterForm} mb-4 p-3 ${wrapperClass} rounded shadow-sm`}>
<div className="row g-3">
<div className="col-md-4">
<FormGroup>
<Label for="project" className={dark ? styles.wrapperDark : ''}>
<Label for="project" className={labelClass}>
Project
</Label>
<Input id="project" type="select" value={projectId} onChange={handleProjectChange}>
Expand All @@ -142,7 +164,7 @@

<div className="ol-md-4">
<FormGroup>
<Label className={dark ? styles.wrapperDark : ''}>Start Date</Label>
<Label className={labelClass}>Start Date</Label>
<DatePicker
selected={startDate}
onChange={handleStartDateChange}
Expand All @@ -157,7 +179,7 @@

<div className="col-md-4">
<FormGroup>
<Label className={dark ? styles.wrapperDark : ''}>End Date</Label>
<Label className={labelClass}>End Date</Label>
<DatePicker
selected={endDate}
onChange={handleEndDateChange}
Expand All @@ -182,11 +204,7 @@

{/* Chart Display with Toggle */}
{!error && chartData && chartData.length > 0 && (
<div
className={`${styles.injuryChartContainer} ${
dark ? styles.wrapperDark : 'bg-white'
} p-4 rounded shadow-sm`}
>
<div className={`${styles.injuryChartContainer} ${wrapperClass} p-4 rounded shadow-sm`}>
<div className="d-flex justify-content-end mb-2">
<button
className={`btn btn-sm ${
Expand Down Expand Up @@ -214,35 +232,39 @@
<ResponsiveContainer width="100%" height={400}>
{chartType === 'bar' ? (
<BarChart data={chartData} margin={{ top: 10, right: 30, left: 10, bottom: 30 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#eee" />
<CartesianGrid strokeDasharray="3 3" stroke={gridStroke} />
<XAxis
dataKey="month"
padding={{ left: 20, right: 20 }}
label={{ value: 'Month', position: 'insideBottom', offset: -10 }}
tick={tickStyle}
label={xLabelStyle}
/>
<YAxis
allowDecimals={false}
label={{ value: 'Number of Injuries', angle: -90, position: 'insideLeft' }}
<YAxis allowDecimals={false} tick={tickStyle} label={yLabelStyle} />
<Tooltip
contentStyle={tooltipStyle}
labelStyle={tooltipLabelStyle}
itemStyle={tooltipItemStyle}
/>
<Tooltip />
<Legend verticalAlign="top" align="center" />
<Bar dataKey="Serious" fill="#dc3545" name="Serious" barSize={20} />
<Bar dataKey="Medium" fill="#fd7e14" name="Medium" barSize={20} />
<Bar dataKey="Low" fill="#198754" name="Low" barSize={20} />
</BarChart>
) : (
<LineChart data={chartData} margin={{ top: 10, right: 30, left: 10, bottom: 30 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#eee" />
<CartesianGrid strokeDasharray="3 3" stroke={gridStroke} />
<XAxis
dataKey="month"
padding={{ left: 20, right: 20 }}
label={{ value: 'Month', position: 'insideBottom', offset: -10 }}
tick={tickStyle}
label={xLabelStyle}
/>
<YAxis
allowDecimals={false}
label={{ value: 'Number of Injuries', angle: -90, position: 'insideLeft' }}
<YAxis allowDecimals={false} tick={tickStyle} label={yLabelStyle} />
<Tooltip
contentStyle={tooltipStyle}
labelStyle={tooltipLabelStyle}
itemStyle={tooltipItemStyle}
/>
<Tooltip />
<Legend verticalAlign="top" align="center" />
<Line
type="monotone"
Expand Down Expand Up @@ -276,8 +298,8 @@

{/* No Data Display */}
{!error && !loading && (!chartData || chartData.length === 0) && (
<div className="text-center p-5 bg-white rounded shadow-sm">
<p className="text-muted">No injury data available for the selected criteria.</p>
<div className={`text-center p-5 rounded shadow-sm ${noDataClass}`}>
<p className={noDataText}>No injury data available for the selected criteria.</p>
</div>
)}
</div>
Expand Down
Loading
Loading