Skip to content

Commit 8cd16db

Browse files
committed
fix: merge conflicts resolved in yarn.lock
2 parents 3ac121f + 0eeb45d commit 8cd16db

42 files changed

Lines changed: 3080 additions & 1353 deletions

Some content is hidden

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

src/components/ApplicationTimeChart/ApplicationTimeChart.jsx

Lines changed: 222 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,203 @@
1-
import { useState, useMemo } from 'react';
1+
import { useState, useMemo, useEffect } from 'react';
2+
import { useSelector } from 'react-redux';
23
import { v4 as uuidv4 } from 'uuid';
3-
import getApplicationData from './api';
4+
import { ENDPOINTS } from '../../utils/URL';
5+
import httpService from '../../services/httpService';
6+
import { getAggregatedMockForChart } from './api';
47
import styles from './ApplicationTimeChart.module.css';
58

9+
function uniqueRolesFromRows(rows) {
10+
return [...new Set((rows || []).map(r => r?.role).filter(Boolean))].sort((a, b) =>
11+
a.localeCompare(b),
12+
);
13+
}
14+
15+
function mergeRoleOptions(prev, rows) {
16+
const fromData = uniqueRolesFromRows(rows);
17+
const fromPrev = prev.filter(r => r !== 'all');
18+
const combined = new Set([...fromPrev, ...fromData]);
19+
return ['all', ...Array.from(combined).sort((a, b) => a.localeCompare(b))];
20+
}
21+
622
function ApplicationTimeChart() {
723
const [dateFilter, setDateFilter] = useState('all');
824
const [selectedRole, setSelectedRole] = useState('all');
25+
const [data, setData] = useState([]);
26+
const [availableRoles, setAvailableRoles] = useState(['all']);
27+
const [loading, setLoading] = useState(true);
28+
const [error, setError] = useState(null);
929

10-
const rawData = getApplicationData();
30+
// Get dark mode state from Redux
31+
const darkMode = useSelector(state => state.theme?.darkMode || false);
1132

12-
const processedData = useMemo(() => {
13-
let filtered = [...rawData];
14-
15-
filtered = filtered.filter(item => item.timeToApply <= 30);
16-
17-
if (dateFilter !== 'all') {
18-
const now = new Date();
19-
filtered = filtered.filter(item => {
20-
const itemDate = new Date(item.timestamp);
21-
const daysAgo = Math.floor((now.getTime() - itemDate.getTime()) / (1000 * 60 * 60 * 24));
22-
23-
switch (dateFilter) {
24-
case 'weekly':
25-
return daysAgo <= 7;
26-
case 'monthly':
27-
return daysAgo <= 30;
28-
case 'yearly':
29-
return daysAgo <= 365;
30-
default:
31-
return true;
33+
// Fetch available roles from backend
34+
useEffect(() => {
35+
const fetchRoles = async () => {
36+
try {
37+
// Construct roles endpoint URL: /api/analytics/application-time/roles
38+
const baseUrl = ENDPOINTS.APPLICATION_TIME_DATA('', '', []);
39+
const rolesUrl = baseUrl.split('?')[0] + '/roles';
40+
const response = await httpService.get(rolesUrl);
41+
if (response.data && response.data.data && Array.isArray(response.data.data)) {
42+
const apiRoles = response.data.data.filter(Boolean).sort((a, b) => a.localeCompare(b));
43+
setAvailableRoles(['all', ...apiRoles]);
44+
} else if (response.data && response.data.success && Array.isArray(response.data.data)) {
45+
const apiRoles = response.data.data.filter(Boolean).sort((a, b) => a.localeCompare(b));
46+
setAvailableRoles(['all', ...apiRoles]);
47+
} else {
48+
setAvailableRoles(mergeRoleOptions(['all'], getAggregatedMockForChart()));
3249
}
33-
});
50+
} catch (err) {
51+
console.error('Error fetching available roles:', err);
52+
setAvailableRoles(mergeRoleOptions(['all'], getAggregatedMockForChart()));
53+
}
54+
};
55+
56+
fetchRoles();
57+
}, []);
58+
59+
// Fetch data from backend
60+
useEffect(() => {
61+
const fetchData = async () => {
62+
try {
63+
setLoading(true);
64+
setError(null);
65+
66+
// Prepare query parameters
67+
let startDate = null;
68+
let endDate = null;
69+
70+
if (dateFilter !== 'all') {
71+
const now = new Date();
72+
73+
switch (dateFilter) {
74+
case 'weekly':
75+
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
76+
break;
77+
case 'monthly':
78+
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
79+
break;
80+
case 'yearly':
81+
startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
82+
break;
83+
default:
84+
break;
85+
}
86+
87+
if (startDate) {
88+
endDate = now;
89+
}
90+
}
91+
92+
const url = ENDPOINTS.APPLICATION_TIME_DATA(
93+
startDate ? startDate.toISOString() : null,
94+
endDate ? endDate.toISOString() : null,
95+
selectedRole !== 'all' ? [selectedRole] : [],
96+
);
97+
98+
const response = await httpService.get(url);
99+
100+
// Backend returns { data: [], message: "", summary: {} }
101+
if (response.data && response.data.data && Array.isArray(response.data.data)) {
102+
const rows = response.data.data;
103+
setData(rows);
104+
setAvailableRoles(prev => mergeRoleOptions(prev, rows));
105+
} else if (response.data && Array.isArray(response.data)) {
106+
const rows = response.data;
107+
setData(rows);
108+
setAvailableRoles(prev => mergeRoleOptions(prev, rows));
109+
} else {
110+
console.error('Backend returned unexpected data format:', response.data);
111+
setError('Unexpected data format from server');
112+
setData([]);
113+
}
114+
} catch (err) {
115+
console.error('Error fetching application time data:', err);
116+
const status = err?.response?.status;
117+
if (status === 404) {
118+
const rows = getAggregatedMockForChart();
119+
setData(rows);
120+
setAvailableRoles(prev => mergeRoleOptions(prev, rows));
121+
setError(null);
122+
} else {
123+
setError(err.message || 'Failed to fetch data from server');
124+
setData([]);
125+
}
126+
} finally {
127+
setLoading(false);
128+
}
129+
};
130+
131+
fetchData();
132+
}, [dateFilter, selectedRole]);
133+
134+
const processedData = useMemo(() => {
135+
// Backend may return all roles even when `roles` query is set; mock data is always full set.
136+
// Apply Role filter here so the chart always matches the dropdown.
137+
if (!Array.isArray(data) || data.length === 0) {
138+
return [];
34139
}
35140

141+
let rows = data;
36142
if (selectedRole !== 'all') {
37-
filtered = filtered.filter(item => item.role === selectedRole);
143+
rows = data.filter(item => item && item.role === selectedRole);
38144
}
39145

40-
const roleGroups = {};
41-
filtered.forEach(item => {
42-
if (!roleGroups[item.role]) roleGroups[item.role] = [];
43-
roleGroups[item.role].push(item.timeToApply);
44-
});
45-
46-
const chartData = Object.entries(roleGroups)
47-
.map(([role, times]) => {
48-
const avg = times.reduce((a, b) => a + b, 0) / times.length;
49-
return {
50-
role,
51-
avgTime: Math.round(avg * 10) / 10,
52-
count: times.length,
53-
};
54-
})
55-
.sort((a, b) => b.avgTime - a.avgTime);
146+
// Map backend response to chart data format
147+
// Backend returns timeToApplyMinutes (average time in minutes)
148+
const chartData = rows
149+
.map(item => ({
150+
role: item.role,
151+
avgTime: item.timeToApplyMinutes || (item.timeToApply ? item.timeToApply / 60 : 0),
152+
count: item.totalApplications || 1,
153+
formattedTime:
154+
item.timeToApplyFormatted ||
155+
`${Math.round((item.timeToApplyMinutes || 0) * 10) / 10} min`,
156+
}))
157+
.sort((a, b) => b.avgTime - a.avgTime); // Sort highest to lowest (most time-consuming first)
56158

57159
return chartData;
58-
}, [rawData, dateFilter, selectedRole]);
59-
60-
const roles = useMemo(() => {
61-
const uniqueRoles = [...new Set(rawData.map(item => item.role))];
62-
return ['all', ...uniqueRoles];
63-
}, [rawData]);
160+
}, [data, selectedRole]);
64161

65162
const maxTime = Math.max(...processedData.map(item => item.avgTime), 10);
66163

164+
// Show loading state
165+
if (loading) {
166+
return (
167+
<div className={`${styles.container} ${darkMode ? styles.darkMode : ''}`}>
168+
<div className={`${styles.chartCard} ${darkMode ? styles.darkMode : ''}`}>
169+
<h2 className={`${styles.title} ${darkMode ? styles.darkMode : ''}`}>
170+
Comparing the Average Time Taken to Fill an Application by Role
171+
</h2>
172+
<div className={`${styles.noData} ${darkMode ? styles.darkMode : ''}`}>
173+
Loading application time data...
174+
</div>
175+
</div>
176+
</div>
177+
);
178+
}
179+
180+
// Show error state
181+
if (error) {
182+
return (
183+
<div className={`${styles.container} ${darkMode ? styles.darkMode : ''}`}>
184+
<div className={`${styles.chartCard} ${darkMode ? styles.darkMode : ''}`}>
185+
<h2 className={`${styles.title} ${darkMode ? styles.darkMode : ''}`}>
186+
Comparing the Average Time Taken to Fill an Application by Role
187+
</h2>
188+
<div className={`${styles.noData} ${darkMode ? styles.darkMode : ''}`}>
189+
Error loading data: {error}. Please try again later.
190+
</div>
191+
</div>
192+
</div>
193+
);
194+
}
195+
67196
return (
68-
<div className={styles.container}>
197+
<div className={`${styles.container} ${darkMode ? styles.darkMode : ''}`}>
69198
{/* Chart Container */}
70-
<div className={styles.chartCard}>
71-
<h2 className={styles.title}>
199+
<div className={`${styles.chartCard} ${darkMode ? styles.darkMode : ''}`}>
200+
<h2 className={`${styles.title} ${darkMode ? styles.darkMode : ''}`}>
72201
Comparing the Average Time Taken to Fill an Application by Role
73202
</h2>
74203

@@ -78,7 +207,7 @@ function ApplicationTimeChart() {
78207
<>
79208
{/* Grid Lines */}
80209
<div
81-
className={styles.grid}
210+
className={`${styles.grid} ${darkMode ? styles.darkMode : ''}`}
82211
style={{
83212
backgroundSize: `${100 / 6}% ${100 / processedData.length}%`,
84213
}}
@@ -89,7 +218,7 @@ function ApplicationTimeChart() {
89218
{processedData.map(item => (
90219
<div
91220
key={uuidv4()}
92-
className={styles.yAxisItem}
221+
className={`${styles.yAxisItem} ${darkMode ? styles.darkMode : ''}`}
93222
style={{ height: `${100 / processedData.length}%` }}
94223
>
95224
{item.role}
@@ -98,21 +227,31 @@ function ApplicationTimeChart() {
98227
</div>
99228

100229
{/* X-axis */}
101-
<div className={styles.xAxis}>
102-
{[0, 5, 10, 15, 20, 25, 30].map(tick => (
103-
<div
104-
key={tick}
105-
style={{
106-
position: 'absolute',
107-
left: `${(tick / maxTime) * 100}%`,
108-
fontSize: '12px',
109-
color: '#5f6368',
110-
transform: 'translateX(-50%)',
111-
}}
112-
>
113-
{tick <= maxTime ? tick : ''}
114-
</div>
115-
))}
230+
<div className={`${styles.xAxis} ${darkMode ? styles.darkMode : ''}`}>
231+
{(() => {
232+
// Generate dynamic ticks based on maxTime
233+
const tickCount = 6;
234+
const ticks = [];
235+
for (let i = 0; i <= tickCount; i++) {
236+
const tickValue = Math.round(((maxTime * i) / tickCount) * 10) / 10;
237+
ticks.push(tickValue);
238+
}
239+
return ticks.map(tick => (
240+
<div
241+
key={tick}
242+
className={darkMode ? styles.darkMode : ''}
243+
style={{
244+
position: 'absolute',
245+
left: `${(tick / maxTime) * 100}%`,
246+
fontSize: '12px',
247+
color: darkMode ? '#e0e0e0' : '#5f6368',
248+
transform: 'translateX(-50%)',
249+
}}
250+
>
251+
{tick}
252+
</div>
253+
));
254+
})()}
116255
</div>
117256

118257
{/* Bars */}
@@ -124,28 +263,32 @@ function ApplicationTimeChart() {
124263
style={{ height: `${100 / processedData.length}%` }}
125264
>
126265
<div
127-
className={styles.bar}
266+
className={`${styles.bar} ${darkMode ? styles.darkMode : ''}`}
128267
style={{ width: `${(item.avgTime / maxTime) * 100}%` }}
129268
>
130-
<div className={styles.dataLabel}>{item.avgTime} min</div>
269+
<div className={`${styles.dataLabel} ${darkMode ? styles.darkMode : ''}`}>
270+
{item.formattedTime || `${Math.round(item.avgTime * 10) / 10} min`}
271+
</div>
131272
</div>
132273
</div>
133274
))}
134275
</div>
135276

136277
{/* X-axis Label */}
137-
<div className={styles.xAxisLabel}>
278+
<div className={`${styles.xAxisLabel} ${darkMode ? styles.darkMode : ''}`}>
138279
Average Time taken to fill application (in minutes)
139280
</div>
140281
</>
141282
) : (
142-
<div className={styles.noData}>No data available for the selected filters</div>
283+
<div className={`${styles.noData} ${darkMode ? styles.darkMode : ''}`}>
284+
No data available for the selected filters
285+
</div>
143286
)}
144287
</div>
145288

146289
{/* Summary Info */}
147290
{processedData.length > 0 && (
148-
<div className={styles.summary}>
291+
<div className={`${styles.summary} ${darkMode ? styles.darkMode : ''}`}>
149292
<div>
150293
<strong>Showing:</strong> {processedData.length} role(s)
151294
</div>
@@ -163,12 +306,12 @@ function ApplicationTimeChart() {
163306
{/* Filters Panel */}
164307
<div className={styles.filters}>
165308
{/* Dates Filter */}
166-
<div className={styles.filterCard}>
167-
<div className={styles.filterTitle}>Dates</div>
309+
<div className={`${styles.filterCard} ${darkMode ? styles.darkMode : ''}`}>
310+
<div className={`${styles.filterTitle} ${darkMode ? styles.darkMode : ''}`}>Dates</div>
168311
<select
169312
value={dateFilter}
170313
onChange={e => setDateFilter(e.target.value)}
171-
className={styles.select}
314+
className={`${styles.select} ${darkMode ? styles.darkMode : ''}`}
172315
>
173316
<option value="all">ALL</option>
174317
<option value="weekly">Last 7 Days</option>
@@ -178,14 +321,14 @@ function ApplicationTimeChart() {
178321
</div>
179322

180323
{/* Role Filter */}
181-
<div className={styles.filterCard}>
182-
<div className={styles.filterTitle}>Role</div>
324+
<div className={`${styles.filterCard} ${darkMode ? styles.darkMode : ''}`}>
325+
<div className={`${styles.filterTitle} ${darkMode ? styles.darkMode : ''}`}>Role</div>
183326
<select
184327
value={selectedRole}
185328
onChange={e => setSelectedRole(e.target.value)}
186-
className={styles.select}
329+
className={`${styles.select} ${darkMode ? styles.darkMode : ''}`}
187330
>
188-
{roles.map(role => (
331+
{availableRoles.map(role => (
189332
<option key={role} value={role}>
190333
{role === 'all' ? 'ALL' : role}
191334
</option>

0 commit comments

Comments
 (0)