From 73d7870aea3de9925669abb86babed999b48f483 Mon Sep 17 00:00:00 2001 From: Aditya Gambhir <67105262+Aditya-gam@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:23:33 -0800 Subject: [PATCH 01/29] fix(bm-tools): handle new structured API response format - Extract data from structured response {success, data, message, ...} - Add validation for success field in responses - Display API message for empty results and errors - Enhance error handling with specific messages for auth, network, and server errors - Add console logging for debugging API failures - Support backward compatibility with raw array responses Fixes issue with component crashing when backend returns structured response format instead of raw arrays. --- .../Tools/ToolsStoppageHorizontalBarChart.jsx | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx diff --git a/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx b/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx new file mode 100644 index 0000000000..e9861e7979 --- /dev/null +++ b/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx @@ -0,0 +1,335 @@ +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 axios from 'axios'; +import { ENDPOINTS } from '../../../utils/URL'; +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 emptyData = []; + + useEffect(() => { + const fetchProjects = async () => { + setLoading(true); + setError(null); + try { + const response = await axios.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) { + console.error('Failed to load projects:', 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 { + setError('Failed to load projects. Please try again.'); + } + } finally { + setLoading(false); + } + }; + + fetchProjects(); + }, []); + + useEffect(() => { + const fetchToolsStoppageData = async () => { + setLoading(true); + setError(null); + const formattedStart = startDate ? new Date(startDate).toISOString() : null; + const formattedEnd = endDate ? new Date(endDate).toISOString() : null; + + try { + if (selectedProject) { + const url = ENDPOINTS.BM_TOOLS_STOPPAGE_BY_PROJECT( + selectedProject?.value, + formattedStart, + formattedEnd, + ); + const response = await axios.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); + } + } else if (projects.length > 0) { + const firstProject = projects[0]; + setSelectedProject({ value: firstProject.projectId, label: firstProject.projectName }); + const url = ENDPOINTS.BM_TOOLS_STOPPAGE_BY_PROJECT(firstProject.projectId, null, null); + const response = await axios.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); + } + } else { + setData(emptyData); + } + } catch (err) { + console.error('Failed to load tools stoppage data:', 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.'); + } + } finally { + setLoading(false); + } + }; + + fetchToolsStoppageData(); + }, [selectedProject, startDate, endDate, projects]); + + 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)}` : ''; + + const selectDarkStyles = { + control: base => ({ + ...base, + backgroundColor: '#2c3344', + borderColor: '#364156', + }), + menu: base => ({ + ...base, + backgroundColor: '#2c3344', + }), + option: (base, state) => ({ + ...base, + backgroundColor: state.isFocused ? '#364156' : '#2c3344', + color: '#e0e0e0', + }), + singleValue: base => ({ + ...base, + color: '#e0e0e0', + }), + placeholder: base => ({ + ...base, + color: '#aaaaaa', + }), + }; + + // ✅ Prepare Chart.js data + 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', + barThickness: 30, + }, + { + label: 'Damaged', + data: data.map(item => item.damaged || 0), + backgroundColor: '#FF0000', + barThickness: 30, + }, + { + label: 'Lost', + data: data.map(item => item.lost || 0), + backgroundColor: '#FFB800', + barThickness: 30, + }, + ], + }; + + // ✅ Chart.js options for horizontal stacked bars + const chartOptions = { + indexAxis: 'y', + maintainAspectRatio: false, + responsive: true, + plugins: { + legend: { + position: 'top', + labels: { color: darkMode ? '#e0e0e0' : '#000' }, + }, + tooltip: { enabled: true, color: darkMode ? '#FFFFFF' : '#000000' }, + datalabels: { + display: true, + color: '#fff', + font: { weight: 'bold' }, + }, + }, + scales: { + x: { + stacked: true, + grid: { color: darkMode ? '#364156' : '#e0e0e0' }, + ticks: { color: darkMode ? '#e0e0e0' : '#000' }, + }, + y: { + title: { display: true, text: 'Tools', color: darkMode ? '#FFFFFF' : '#000000' }, + stacked: true, + grid: { display: false }, + ticks: { color: darkMode ? '#e0e0e0' : '#000' }, + }, + }, + }; + + return ( +
+

+ Reason of Stoppage of Tools +

+ + +
+ { + setDateRange(update); + }} + placeholderText={dateRangeLabel || 'Filter by Date Range'} + className={`${styles.datePickerInput} form-control ${darkMode ? 'darkTheme' : ''}`} + calendarClassName={darkMode ? 'darkThemeCalendar' : 'customCalendar'} + /> + +
+ + + setToolId(e.target.value)}> - - {uniqueTools.map(tool => ( - - ))} - + setProjectId(e.target.value)} - > - - {uniqueProjects.map(project => ( - - ))} - + setSelectedProject(opt)} - options={projectOptions} - placeholder="Select a project ID to view data" - isClearable={false} - isDisabled={projects.length === 0} - styles={selectStyles} - /> +
+ +