Skip to content

Commit eae641d

Browse files
Merge branch 'development' into chirag-pr-team-analytics-dashboard-data-view-toggle-addition
2 parents e7c5489 + 52f0558 commit eae641d

66 files changed

Lines changed: 5930 additions & 3354 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ module.exports = [
3131
// Ignore test files inside /src/components
3232
'src/components/Reports/PeopleReport/components/PeopleTasksPieChart.test.jsx',
3333
// Ignore entire component folders
34-
'src/components/Badge/**',
3534
],
3635
},
3736

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"html-react-parser": "^1.4.14",
6060
"html-to-pdfmake": "^2.0.6",
6161
"html2canvas": "^1.4.1",
62+
"jest": "^30.2.0",
6263
"jquery": "^3.7.1",
6364
"jspdf": "^3.0.3",
6465
"jwt-decode": "^2.2.0",

src/components/Auth/PermissionWatcher.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function PermissionWatcher() {
3131

3232
if (!userProfile || !userProfile._id) {
3333
// eslint-disable-next-line no-console
34-
console.error('User profile not available');
34+
//console.error('User profile not available');
3535
setIsAckLoading(false);
3636
return;
3737
}
@@ -53,12 +53,12 @@ function PermissionWatcher() {
5353
})
5454
.catch(error => {
5555
// eslint-disable-next-line no-console
56-
console.error('Error updating user profile:', error);
56+
// console.error('Error updating user profile:', error);
5757
setIsAckLoading(false);
5858
});
5959
} catch (error) {
6060
// eslint-disable-next-line no-console
61-
console.error('Error acknowledging permission changes:', error);
61+
// console.error('Error acknowledging permission changes:', error);
6262
setIsAckLoading(false);
6363
}
6464
};
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import React, { useEffect, useMemo, useRef, useState } from 'react';
2+
import axios from 'axios';
3+
import { ENDPOINTS } from '~/utils/URL';
4+
import { Bar } from 'react-chartjs-2';
5+
import DatePicker from 'react-datepicker';
6+
import { MultiSelect } from 'react-multi-select-component';
7+
import 'react-datepicker/dist/react-datepicker.css';
8+
import styles from './ReturnedLateChart.module.css';
9+
import {
10+
Chart as ChartJS,
11+
CategoryScale,
12+
LinearScale,
13+
BarElement,
14+
Title,
15+
Tooltip,
16+
Legend,
17+
} from 'chart.js';
18+
import { useSelector } from 'react-redux';
19+
import ChartDataLabels from 'chartjs-plugin-datalabels';
20+
21+
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels);
22+
23+
export default function ReturnedLateChart() {
24+
const chartRef = useRef(null);
25+
const [loading, setLoading] = useState(true);
26+
const [error, setError] = useState(null);
27+
const [availableProjects, setAvailableProjects] = useState([]);
28+
const [availableTools, setAvailableTools] = useState([]);
29+
const [selectedProject, setSelectedProject] = useState('All');
30+
const [selectedTools, setSelectedTools] = useState([]);
31+
const [dateRange, setDateRange] = useState({
32+
startDate: new Date(2020, 0, 1),
33+
endDate: new Date(2025, 11, 31),
34+
});
35+
const [chartData, setChartData] = useState({ labels: [], datasets: [] });
36+
const [rawToolsData, setRawToolsData] = useState([]);
37+
const darkMode = useSelector(state => state.theme.darkMode);
38+
39+
useEffect(() => {
40+
const fetchInitial = async () => {
41+
try {
42+
setLoading(true);
43+
try {
44+
const projectsRes = await axios.get(ENDPOINTS.BM_TOOLS_RETURNED_LATE_PROJECTS, {
45+
headers: {
46+
Authorization: localStorage.getItem('token'),
47+
},
48+
});
49+
if (projectsRes.data && projectsRes.data.success) {
50+
const projects = projectsRes.data.data || [];
51+
setAvailableProjects(projects);
52+
}
53+
const toolsRes = await axios.get(ENDPOINTS.BM_TOOLS_RETURNED_LATE, {
54+
headers: {
55+
Authorization: localStorage.getItem('token'),
56+
},
57+
});
58+
if (toolsRes.data && toolsRes.data.success && toolsRes.data.data) {
59+
const data = toolsRes.data.data || [];
60+
setRawToolsData(data);
61+
const tools = Array.from(new Set(data.map(d => d.toolName))).filter(Boolean);
62+
setAvailableTools(tools.map(t => ({ label: t, value: t })));
63+
}
64+
} catch (e) {
65+
setError('Failed to fetch initial data');
66+
}
67+
} catch (e) {
68+
setError('Error loading dashboard data');
69+
} finally {
70+
setLoading(false);
71+
}
72+
};
73+
74+
fetchInitial();
75+
}, []);
76+
77+
const buildUrl = () => {
78+
const params = [];
79+
if (selectedProject && selectedProject !== 'All') params.push(`projectId=${selectedProject}`);
80+
if (dateRange.startDate) params.push(`startDate=${dateRange.startDate.toISOString()}`);
81+
if (dateRange.endDate) params.push(`endDate=${dateRange.endDate.toISOString()}`);
82+
if (selectedTools && selectedTools.length > 0)
83+
params.push(`tools=${selectedTools.map(t => t.value).join(',')}`);
84+
85+
return `${ENDPOINTS.BM_TOOLS_RETURNED_LATE}${params.length ? `?${params.join('&')}` : ''}`;
86+
};
87+
88+
useEffect(() => {
89+
const fetchData = async () => {
90+
try {
91+
setLoading(true);
92+
setError(null);
93+
const url = buildUrl();
94+
const res = await axios.get(url, {
95+
headers: {
96+
Authorization: localStorage.getItem('token'),
97+
},
98+
});
99+
const data = (res.data && (res.data.data || res.data)) || [];
100+
const normalized = data.map(item => ({
101+
toolName: item.toolName || item.toolNameName || item.name || '',
102+
percentLate: Number(item.percentLate || item.percent || item.value || 0),
103+
}));
104+
normalized.sort((a, b) => b.percentLate - a.percentLate);
105+
setChartData({
106+
labels: normalized.map(i => i.toolName),
107+
datasets: [
108+
{
109+
label: '% Returned Late',
110+
data: normalized.map(i => i.percentLate),
111+
backgroundColor: 'rgba(53,162,235,0.7)',
112+
},
113+
],
114+
});
115+
} catch (err) {
116+
const errorMessage =
117+
err.response?.data?.error ||
118+
err.response?.data?.message ||
119+
err.message ||
120+
'Failed to fetch returned-late data';
121+
setError(errorMessage);
122+
} finally {
123+
setLoading(false);
124+
}
125+
};
126+
fetchData();
127+
}, [selectedProject, dateRange, selectedTools]);
128+
129+
const options = useMemo(() => {
130+
const textColor = darkMode ? '#fff' : '#333';
131+
const datalabelCOlor = darkMode ? '#fff' : '#111';
132+
return {
133+
responsive: true,
134+
maintainAspectRatio: false,
135+
plugins: {
136+
legend: { display: false },
137+
title: {
138+
display: false,
139+
},
140+
datalabels: {
141+
anchor: 'end',
142+
align: 'top',
143+
offset: 4,
144+
formatter: value => `${Number(value).toFixed(0)}%`,
145+
color: datalabelCOlor,
146+
font: { weight: 'bold' },
147+
},
148+
tooltip: {
149+
callbacks: {
150+
label(context) {
151+
const v = context.parsed.y;
152+
return `${v}%`;
153+
},
154+
},
155+
},
156+
},
157+
scales: {
158+
x: {
159+
title: {
160+
display: true,
161+
text: 'Tool Name',
162+
font: { size: 16, weight: 'bold' },
163+
color: textColor,
164+
},
165+
ticks: {
166+
color: textColor,
167+
},
168+
},
169+
y: {
170+
beginAtZero: true,
171+
title: {
172+
display: true,
173+
text: 'Percent of tools returned late',
174+
font: { size: 16, weight: 'bold' },
175+
color: textColor,
176+
},
177+
ticks: {
178+
color: textColor,
179+
callback: v => `${v}%`,
180+
},
181+
max: Math.max(...(chartData.datasets[0]?.data || [0])) * 1.15,
182+
},
183+
},
184+
};
185+
}, [chartData, darkMode]);
186+
187+
const handleProjectChange = e => setSelectedProject(e.target.value);
188+
const handleStartDateChange = date =>
189+
setDateRange(prev => ({ startDate: date, endDate: prev.endDate < date ? date : prev.endDate }));
190+
const handleEndDateChange = date =>
191+
setDateRange(prev => ({
192+
startDate: prev.startDate > date ? date : prev.startDate,
193+
endDate: date,
194+
}));
195+
const isOxfordBlue = darkMode ? 'bg-oxford-blue' : '';
196+
197+
return (
198+
<div className={`${styles['returned-late-chart']} ${isOxfordBlue}`}>
199+
<h1 className={darkMode ? 'text-white' : ''}>Percent of Tools Returned Late</h1>
200+
<div className={styles['returned-late-filters']}>
201+
<div className={styles['returned-late-filter-group']}>
202+
<label
203+
htmlFor="project-select"
204+
className={`${styles['returned-late-filter-label']} ${darkMode ? 'text-white' : ''}`}
205+
>
206+
Project:
207+
</label>
208+
<select
209+
id="project-select"
210+
value={selectedProject}
211+
onChange={handleProjectChange}
212+
className={styles['returned-late-project-select']}
213+
>
214+
<option value="All">All Projects</option>
215+
{availableProjects.map(p => (
216+
<option key={p.projectId} value={p.projectId}>
217+
{p.projectName}
218+
</option>
219+
))}
220+
</select>
221+
</div>
222+
<div className={styles['returned-late-filter-group']}>
223+
<label
224+
htmlFor="tools-select"
225+
className={`${styles['returned-late-filter-label']} ${darkMode ? 'text-white' : ''}`}
226+
>
227+
Tools:
228+
</label>
229+
<div id="tools-select" className={styles['returned-late-tools-select']}>
230+
<MultiSelect
231+
options={availableTools}
232+
value={selectedTools}
233+
onChange={setSelectedTools}
234+
labelledBy="tools-select"
235+
/>
236+
</div>
237+
</div>
238+
<div className={styles['returned-late-filter-group']}>
239+
<label
240+
htmlFor="start-date-picker"
241+
className={`${styles['returned-late-filter-label']} ${darkMode ? 'text-white' : ''}`}
242+
>
243+
From:
244+
</label>
245+
<DatePicker
246+
id="start-date-picker"
247+
selected={dateRange.startDate}
248+
onChange={handleStartDateChange}
249+
className={styles['returned-late-date-picker']}
250+
/>
251+
</div>
252+
<div className={styles['returned-late-filter-group']}>
253+
<label
254+
htmlFor="end-date-picker"
255+
className={`${styles['returned-late-filter-label']} ${darkMode ? 'text-white' : ''}`}
256+
>
257+
To:
258+
</label>
259+
<DatePicker
260+
id="end-date-picker"
261+
selected={dateRange.endDate}
262+
onChange={handleEndDateChange}
263+
className={styles['returned-late-date-picker']}
264+
/>
265+
</div>
266+
</div>
267+
<div className={`${styles['returned-late-chart-container']} text-white`}>
268+
{loading && (
269+
<div className={`${styles['returned-late-loading']} ${darkMode ? 'text-white' : ''}`}>
270+
Loading...
271+
</div>
272+
)}
273+
{error && (
274+
<div className={`${styles['returned-late-error']} ${darkMode ? 'text-white' : ''}`}>
275+
{error}
276+
</div>
277+
)}
278+
{!loading && !error && chartData.labels.length === 0 && (
279+
<div className={`${styles['returned-late-no-data']} ${darkMode ? 'text-white' : ''}`}>
280+
No data for selected filters
281+
</div>
282+
)}
283+
{!loading && !error && chartData.labels.length > 0 && (
284+
<Bar ref={chartRef} data={chartData} options={options} />
285+
)}
286+
</div>
287+
</div>
288+
);
289+
}

0 commit comments

Comments
 (0)