Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
73d7870
fix(bm-tools): handle new structured API response format
Aditya-gam Nov 8, 2025
b9ffc1c
refactor(bm-tools): replace direct axios with httpService wrapper
Aditya-gam Nov 8, 2025
0a6d759
refactor(bm-tools): split useEffect into auto-select and data-fetch e…
Aditya-gam Nov 8, 2025
3f72f15
fix(bm-tools): enhance error handling with specific categorized messages
Aditya-gam Nov 8, 2025
3fc9fde
refactor(bm-tools): replace console.error with logService for proper …
Aditya-gam Nov 8, 2025
c970f70
fix: prevent card stretching in Tools and Equipment Tracking section
Aditya-gam Nov 11, 2025
8dc3992
fix: prevent chart containers from forcing excessive height
Aditya-gam Nov 11, 2025
b2bdd6c
feat: standardize chart heights and improve bar spacing
Aditya-gam Nov 11, 2025
246a321
feat: make Tools and Equipment Tracking section responsive for mobile…
Aditya-gam Nov 12, 2025
2340a6e
feat: add responsive sizing to ToolsHorizontalBarChart for mobile/tablet
Aditya-gam Nov 12, 2025
7cef2c5
feat: add responsive sizing to ToolsStoppageHorizontalBarChart for mo…
Aditya-gam Nov 12, 2025
5a53b08
feat: add responsive sizing to ToolStatusDonutChart for mobile/tablet
Aditya-gam Nov 12, 2025
781af06
fix: improve ToolsStoppageHorizontalBarChart background and axis visi…
Aditya-gam Dec 5, 2025
b35adef
fix(tools-charts): improve responsive design and height matching in T…
Aditya-gam Dec 6, 2025
7171580
fix(tools-charts): implement gradient responsive system for mobile de…
Aditya-gam Dec 6, 2025
3b74916
fix(tools-charts): improve filter layout and dark mode text colors
Aditya-gam Dec 6, 2025
e6e9c89
fix(tools-charts): ensure consistent date picker sizing across light …
Aditya-gam Dec 6, 2025
e22157c
fix(tools-charts): populate dropdown options in ToolStatusDonutChart
Aditya-gam Dec 6, 2025
35caf31
refactor(tools-charts): reduce cognitive complexity and eliminate cod…
Aditya-gam Dec 6, 2025
0eee7be
fix(tools-charts): resolve SonarQube issues and reduce code duplication
Aditya-gam Dec 6, 2025
3a6cf99
fix(tools): prevent chart clipping in stoppage and horizontal bar charts
Aditya-gam Feb 19, 2026
3314eab
chore(utils): add chartResponsiveUtils for shared chart breakpoint logic
Aditya-gam Feb 19, 2026
cb49d2b
refactor(tools): use chartResponsiveUtils in both tools charts
Aditya-gam Feb 19, 2026
054e8ff
fix: update yarn.lock
rithika-paii May 11, 2026
884c3b9
fix: add PropTypes validation and fix contrast and chartResponsiveUti…
rithika-paii May 13, 2026
c1359a9
fix: use .at(-1) instead of array length index in chartResponsiveUtils
rithika-paii May 13, 2026
2af7fe2
Fix duplicate import and JSX structure in WeeklyProjectSummary tools …
rithika-paii May 21, 2026
8a05674
Remove unused imports SupplierPerformanceGraph and EmbedInteractiveMap
rithika-paii May 21, 2026
50a2d35
fix: resolve duplicate PropTypes imports and JSX fragment mismatch
rithika-paii Jun 6, 2026
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
380 changes: 380 additions & 0 deletions src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
import { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import Select from 'react-select';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import { Row, Col, Button } from 'react-bootstrap';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip as ChartTooltip,
Legend as ChartLegend,
} from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { Bar } from 'react-chartjs-2';
import httpService from '../../../services/httpService';
import logService from '../../../services/logService';
import { ENDPOINTS } from '../../../utils/URL';
import { getStandardSelectStyles } from '../../../utils/reactSelectUtils';
import {
getChartHeight,
getMaxBarThickness,
getCategoryPercentage,
getBarPercentage,
getChartFontSize,
getChartTitleFontSize,
} from '../../../utils/chartResponsiveUtils';
import styles from './ToolsStoppageHorizontalBarChart.module.css';

// Register Chart.js components
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
ChartTooltip,
ChartLegend,
ChartDataLabels,
);

export default function ToolsStoppageHorizontalBarChart() {
const darkMode = useSelector(state => state.theme.darkMode);
const [projects, setProjects] = useState([]);
const [selectedProject, setSelectedProject] = useState(null);
const [dateRange, setDateRange] = useState([null, null]);
const [startDate, endDate] = dateRange;
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState([]);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const emptyData = [];

useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);

useEffect(() => {
const fetchProjects = async () => {
setLoading(true);
setError(null);
try {
const response = await httpService.get(ENDPOINTS.BM_TOOL_PROJECTS);
const responseData = response.data;

// Handle new structured response format
if (responseData.success === false) {
setError(responseData.message || 'Failed to load projects.');
setProjects([]);
return;
}

// Extract data array from structured response
const projectsData = responseData.data || responseData;
setProjects(Array.isArray(projectsData) ? projectsData : []);
} catch (err) {
logService.logError(err);
if (err.response?.data?.message) {
setError(err.response.data.message);
} else if (err.response?.status === 401 || err.response?.status === 403) {
setError('Session expired. Please log in again.');
} else if (!err.response) {
setError('Network error. Please check your connection.');
} else if (err.response?.status >= 500) {
setError('Server error. Please try again later.');
} else {
setError(
`Failed to load projects. Please try again. (Status: ${err.response?.status ||
'unknown'})`,
);
}
} finally {
setLoading(false);
}
};

fetchProjects();
}, []);

// Auto-select first project when projects load
useEffect(() => {
if (!selectedProject && projects.length > 0) {
const firstProject = projects[0];
setSelectedProject({
value: firstProject.projectId,
label: firstProject.projectName,
});
}
}, [projects, selectedProject]);

// Fetch tools stoppage data when project or date filters change
useEffect(() => {
const fetchToolsStoppageData = async () => {
// Early return if no project selected
if (!selectedProject) {
setData(emptyData);
return;
}

setLoading(true);
setError(null);
const formattedStart = startDate ? new Date(startDate).toISOString() : null;
const formattedEnd = endDate ? new Date(endDate).toISOString() : null;

try {
const url = ENDPOINTS.BM_TOOLS_STOPPAGE_BY_PROJECT(
selectedProject.value,
formattedStart,
formattedEnd,
);
const response = await httpService.get(url);
const responseData = response.data;

// Handle new structured response format
if (responseData.success === false) {
setError(responseData.message || 'Failed to load stoppage data.');
setData(emptyData);
return;
}

// Extract data array from structured response
const stoppageData = responseData.data || responseData;

if (stoppageData && Array.isArray(stoppageData) && stoppageData.length > 0) {
const sortedData = [...stoppageData].map(item => ({
...item,
name: item.toolName || item.name,
}));
setData(sortedData);
} else {
setData(emptyData);
// Use message from API if available
const message =
responseData.message || 'No tool stoppage reason data found for this project.';
setError(message);
}
} catch (err) {
logService.logError(err);
setData(emptyData);

// Enhanced error handling
if (err.response?.data?.message) {
setError(err.response.data.message);
} else if (err.response?.status === 401 || err.response?.status === 403) {
setError('Session expired. Please log in again.');
} else if (!err.response) {
setError('Network error. Please check your connection.');
} else if (err.response?.status >= 500) {
setError('Server error. Please try again later.');
} else {
setError(
`Failed to load tools stoppage reason data. Please try again. (Status: ${err.response
?.status || 'unknown'})`,
);
}
} finally {
setLoading(false);
}
};

fetchToolsStoppageData();
}, [selectedProject, startDate, endDate]);

const projectOptions = projects.map(project => ({
value: project.projectId,
label: project.projectName,
}));

// Format date for display
const formatDate = date => date?.toISOString().split('T')[0];
const dateRangeLabel =
startDate && endDate ? `${formatDate(startDate)} - ${formatDate(endDate)}` : '';

// Use shared react-select styles to reduce duplication
const selectStyles = getStandardSelectStyles(darkMode);

// Prepare Chart.js data with responsive bar thickness
const chartData = {
labels: data.map(item =>
item.name.length > 20 ? `${item.name.substring(0, 18)}...` : item.name,
),
datasets: [
{
label: 'Used its lifetime',
data: data.map(item => item.usedForLifetime || 0),
backgroundColor: '#4589FF',
maxBarThickness: getMaxBarThickness(windowWidth),
},
{
label: 'Damaged',
data: data.map(item => item.damaged || 0),
backgroundColor: '#FF0000',
maxBarThickness: getMaxBarThickness(windowWidth),
},
{
label: 'Lost',
data: data.map(item => item.lost || 0),
backgroundColor: '#FFB800',
maxBarThickness: getMaxBarThickness(windowWidth),
},
],
};

// Chart.js options for horizontal stacked bars with responsive settings
const chartOptions = {
indexAxis: 'y',
maintainAspectRatio: false,
responsive: true,
plugins: {
legend: {
position: 'top',
labels: {
color: darkMode ? '#e0e0e0' : '#000',
font: { size: getChartFontSize(windowWidth) },
},
},
tooltip: { enabled: true, color: darkMode ? '#FFFFFF' : '#000000' },
datalabels: {
display: true,
color: '#fff',
font: { weight: 'bold', size: getChartFontSize(windowWidth) },
},
},
scales: {
x: {
stacked: true,
grid: { color: darkMode ? '#364156' : '#e0e0e0' },
border: {
color: darkMode ? '#ffffff' : '#000000', // Make axis border visible in dark mode
width: 1,
},
ticks: {
color: darkMode ? '#ffffff' : '#000', // Brighter color in dark mode for better visibility
font: { size: getChartFontSize(windowWidth) },
maxRotation: 0,
},
},
y: {
title: {
display: true,
text: 'Tools',
color: darkMode ? '#FFFFFF' : '#000000',
font: { size: getChartTitleFontSize(windowWidth) },
},
stacked: true,
grid: { display: false },
border: {
color: darkMode ? '#ffffff' : '#000000', // Make axis border visible in dark mode
width: 1,
},
ticks: {
color: darkMode ? '#ffffff' : '#000', // Brighter color in dark mode for better visibility
font: { size: getChartFontSize(windowWidth) },
},
categoryPercentage: getCategoryPercentage(windowWidth),
barPercentage: getBarPercentage(windowWidth),
},
},
};

return (
<div className={`tools-availability-page ${darkMode ? 'dark-mode' : ''}`}>
<h3 className={`tools-chart-title ${darkMode ? 'dark-mode' : ''}`}>
Reason of Stoppage of Tools
</h3>
<Row className={`mb-3 ${styles.filtersRow}`}>
<Col xs={12} md={5}>
<div className={styles.datepickerWrapper}>
<label htmlFor="date-range-picker" className={styles.filterLabel}>
Filter by Date Range
</label>
<div className={styles.datePickerInputGroup}>
<DatePicker
id="date-range-picker"
selectsRange
startDate={startDate}
endDate={endDate}
onChange={update => {
setDateRange(update);
}}
placeholderText={dateRangeLabel || 'Filter by Date Range'}
className={styles.datePickerInput}
calendarClassName={darkMode ? 'darkThemeCalendar' : 'customCalendar'}
wrapperClassName={darkMode ? 'darkThemeDatePickerWrapper' : ''}
/>
<Button variant="outline-danger" size="sm" onClick={() => setDateRange([null, null])}>
</Button>
</div>
</div>
</Col>
<Col xs={12} md={4}>
<div className={styles.filterWrapper}>
<label htmlFor="project-select" className={styles.filterLabel}>
Project
</label>
<Select
id="project-select"
className="w-100"
classNamePrefix="customSelect"
value={selectedProject}
onChange={opt => setSelectedProject(opt)}
options={projectOptions}
placeholder="Select a project ID to view data"
isClearable={false}
isDisabled={projects.length === 0}
styles={selectStyles}
/>
</div>
</Col>
<Col xs={12} md={3}>
<div className={styles.resetWrapper}>
<div className={styles.filterLabel} style={{ visibility: 'hidden', height: '19px' }}>
&nbsp;
</div>
<Button
variant="danger"
size="sm"
className={styles.resetButton}
onClick={() => {
setSelectedProject(null);
setDateRange([null, null]);
}}
>
Reset
</Button>
</div>
</Col>
</Row>

<div className="tools-horizontal-chart-container">
{error && <div className="tools-chart-error">{error}</div>}
{loading && <div className="tools-chart-loading">Loading tool availability data...</div>}

{!loading && selectedProject && data.length > 0 && (
<div
style={{
width: '100%',
position: 'relative',
height: `${getChartHeight(windowWidth)}px`,
backgroundColor: darkMode ? '#2c3344' : '#ffffff',
borderRadius: '4px',
}}
>
<Bar data={chartData} options={chartOptions} />
</div>
)}

{!loading && selectedProject && data.length === 0 && (
<div className="tools-chart-empty">
<p>No data available for the selected filters.</p>
</div>
)}
</div>
</div>
);
}
Loading
Loading