Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3790e50
Created the Tools Stoppage Horizontal Bar Chart page.
vamsikrishna1704 Jun 13, 2025
d8a90dd
Fixed issues rose on connecting frontend to backend api
vamsikrishna1704 Jul 12, 2025
6326100
Fixed the BG of recharts tooltip cursor of darkmode
vamsikrishna1704 Jul 19, 2025
c2139e0
Updated the styles for tooltip content
vamsikrishna1704 Jul 19, 2025
11e88e2
Node version upgrade issues fixed.
vamsikrishna1704 Sep 19, 2025
8229b17
Node version upgrade issues fixed.
vamsikrishna1704 Sep 19, 2025
1e92b6d
Resolved the merge conflicts with latest yarn.lock
vamsikrishna1704 Sep 27, 2025
8251216
fix(bm-tools): handle new structured API response format
Aditya-gam Nov 8, 2025
1851fe1
refactor(bm-tools): replace direct axios with httpService wrapper
Aditya-gam Nov 8, 2025
0935c8b
refactor(bm-tools): split useEffect into auto-select and data-fetch e…
Aditya-gam Nov 8, 2025
71a5a3e
fix(bm-tools): enhance error handling with specific categorized messages
Aditya-gam Nov 8, 2025
5220e7a
refactor(bm-tools): replace console.error with logService for proper …
Aditya-gam Nov 8, 2025
18a8932
fix: prevent card stretching in Tools and Equipment Tracking section
Aditya-gam Nov 11, 2025
c51448e
fix: prevent chart containers from forcing excessive height
Aditya-gam Nov 11, 2025
e631c94
feat: standardize chart heights and improve bar spacing
Aditya-gam Nov 11, 2025
29681c7
feat: make Tools and Equipment Tracking section responsive for mobile…
Aditya-gam Nov 12, 2025
004fb43
feat: add responsive sizing to ToolsHorizontalBarChart for mobile/tablet
Aditya-gam Nov 12, 2025
3d425ce
feat: add responsive sizing to ToolsStoppageHorizontalBarChart for mo…
Aditya-gam Nov 12, 2025
2546a0b
feat: add responsive sizing to ToolStatusDonutChart for mobile/tablet
Aditya-gam Nov 12, 2025
9349d4e
fix: improve ToolsStoppageHorizontalBarChart background and axis visi…
Aditya-gam Dec 5, 2025
e5e35ea
fix(tools-charts): improve responsive design and height matching in T…
Aditya-gam Dec 6, 2025
5e2a22f
fix(tools-charts): implement gradient responsive system for mobile de…
Aditya-gam Dec 6, 2025
a0c12ff
fix(tools-charts): improve filter layout and dark mode text colors
Aditya-gam Dec 6, 2025
c2b0fde
fix(tools-charts): ensure consistent date picker sizing across light …
Aditya-gam Dec 6, 2025
9de347e
fix(tools-charts): populate dropdown options in ToolStatusDonutChart
Aditya-gam Dec 6, 2025
0e078c4
refactor(tools-charts): reduce cognitive complexity and eliminate cod…
Aditya-gam Dec 6, 2025
5216f1f
fix(tools-charts): resolve SonarQube issues and reduce code duplication
Aditya-gam Dec 6, 2025
a71493e
fix(tools): use explicit height for ToolsHorizontalBarChart on mobile
Aditya-gam Feb 19, 2026
034e136
fix(tools): prevent chart clipping in stoppage and horizontal bar charts
Aditya-gam Feb 19, 2026
13fb49a
Merge origin/development into Aditya-fix/reason-tool-stoppage-chart-fix
Aditya-gam Feb 19, 2026
c9d7e8f
chore: sync yarn.lock with package.json for CI frozen-lockfile
Aditya-gam Feb 19, 2026
ae4e376
chore(utils): add chartResponsiveUtils for shared chart breakpoint logic
Aditya-gam Feb 19, 2026
26fc272
refactor(tools): use chartResponsiveUtils in both tools charts
Aditya-gam Feb 19, 2026
352d7bb
Merge branch 'development' into Aditya-fix/reason-tool-stoppage-chart…
Aditya-gam Feb 19, 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"diff": "^8.0.3",
"dompurify": "^3.2.5",
"elliptic": "^6.6.1",
"fast-png": "^7.0.1",
"font-awesome": "^4.7.0",
"fs-extra": "^11.3.0",
"history": "^4.10.1",
Expand Down
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