diff --git a/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx b/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx new file mode 100644 index 0000000000..aa24bb49ae --- /dev/null +++ b/src/components/BMDashboard/Tools/ToolsStoppageHorizontalBarChart.jsx @@ -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 ( +
+

+ Reason of Stoppage of Tools +

+ + +
+ +
+ { + setDateRange(update); + }} + placeholderText={dateRangeLabel || 'Filter by Date Range'} + className={styles.datePickerInput} + calendarClassName={darkMode ? 'darkThemeCalendar' : 'customCalendar'} + wrapperClassName={darkMode ? 'darkThemeDatePickerWrapper' : ''} + /> + +
+
+ + +
+ + setToolId(e.target.value)}> - - {uniqueTools.map(tool => ( - - ))} - + setProjectId(e.target.value)} - > - - {uniqueProjects.map(project => ( - - ))} - +