Skip to content

Commit 3c41b2f

Browse files
Merge pull request #4983 from OneCommunityGlobal/development
Frontend Release to Main [4.81]
2 parents 6550764 + 48ad605 commit 3c41b2f

67 files changed

Lines changed: 7604 additions & 4668 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.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"date-fns-tz": "^2.0.1",
5656
"dayjs": "^1.11.13",
5757
"diff": "^8.0.3",
58-
"dompurify": "^3.2.5",
58+
"dompurify": "^3.3.2",
5959
"elliptic": "^6.6.1",
6060
"font-awesome": "^4.7.0",
6161
"fs-extra": "^11.3.0",
@@ -129,7 +129,9 @@
129129
},
130130
"resolutions": {
131131
"react": "18.3.1",
132-
"react-dom": "18.3.1"
132+
"react-dom": "18.3.1",
133+
"mdn-data": "2.12.2",
134+
"ansi-escapes": "7.1.1"
133135
},
134136
"packageManager": "yarn@1.22.22",
135137
"scripts": {

src/components/ApplicantVolunteerRatio/ApplicantVolunteerRatio.jsx

Lines changed: 126 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,47 @@ import 'react-datepicker/dist/react-datepicker.css';
99

1010
function ApplicantVolunteerRatio() {
1111
const darkMode = useSelector(state => state.theme.darkMode);
12+
1213
const [data, setData] = useState([]);
13-
const [allRoles, setAllRoles] = useState([]); // Store all available roles
14+
const [allRoles, setAllRoles] = useState([]);
1415
const [loading, setLoading] = useState(true);
1516
const [error, setError] = useState(null);
1617
const [selectedRoles, setSelectedRoles] = useState([]);
1718
const [startDate, setStartDate] = useState(null);
1819
const [endDate, setEndDate] = useState(null);
1920
const [validationError, setValidationError] = useState('');
21+
const [viewMode, setViewMode] = useState('count');
2022

21-
// Fetch all available roles (without filtering)
23+
// Fetch all available roles
2224
useEffect(() => {
2325
const fetchAllRoles = async () => {
2426
try {
2527
const response = await getAllApplicantVolunteerRatios({});
2628
const apiData = response.data;
2729

28-
// Get all unique roles
2930
const uniqueRoles = [...new Set(apiData.map(item => item.role))];
3031
const roleOptions = uniqueRoles.map(role => ({ label: role, value: role }));
3132

3233
setAllRoles(roleOptions);
33-
34-
// Set all roles as selected by default
3534
setSelectedRoles(roleOptions);
3635
} catch (err) {
37-
// Error fetching all roles
3836
setError('Failed to load roles. Please try again.');
3937
}
4038
};
4139

4240
fetchAllRoles();
4341
}, []);
4442

45-
// Fetch filtered data based on selected roles and date range
43+
// Fetch filtered data
4644
useEffect(() => {
4745
const fetchFilteredData = async () => {
48-
// Validate date range: start must be before or equal to end
4946
if (startDate && endDate && startDate > endDate) {
5047
setValidationError('Start date must be earlier than or equal to End date.');
5148
setData([]);
5249
setLoading(false);
5350
return;
5451
}
5552

56-
// clear previous validation error when dates are valid
5753
if (validationError) setValidationError('');
5854

5955
if (selectedRoles.length === 0) {
@@ -64,22 +60,16 @@ function ApplicantVolunteerRatio() {
6460
try {
6561
setLoading(true);
6662

67-
// Prepare filters
6863
const filters = {};
69-
if (startDate) {
70-
filters.startDate = startDate.toISOString().split('T')[0]; // Format as YYYY-MM-DD
71-
}
72-
if (endDate) {
73-
filters.endDate = endDate.toISOString().split('T')[0]; // Format as YYYY-MM-DD
74-
}
64+
if (startDate) filters.startDate = startDate.toISOString().split('T')[0];
65+
if (endDate) filters.endDate = endDate.toISOString().split('T')[0];
7566
if (selectedRoles.length > 0) {
7667
filters.roles = selectedRoles.map(role => role.value).join(',');
7768
}
7869

7970
const response = await getAllApplicantVolunteerRatios(filters);
8071
const apiData = response.data;
8172

82-
// Transform API data to match chart format
8373
const transformedData = apiData.map(item => ({
8474
role: item.role,
8575
applicants: item.totalApplicants,
@@ -88,23 +78,35 @@ function ApplicantVolunteerRatio() {
8878

8979
setData(transformedData);
9080
} catch (err) {
91-
// Error fetching applicant volunteer ratio data
9281
setError('Failed to load data. Please try again.');
9382
} finally {
9483
setLoading(false);
9584
}
9685
};
9786

9887
fetchFilteredData();
99-
}, [startDate, endDate, selectedRoles]); // Re-fetch when date range or selected roles change
88+
}, [startDate, endDate, selectedRoles]);
10089

101-
// Filter and transform data for chart
102-
const chartData = useMemo(
103-
() => data.filter(d => selectedRoles.map(r => r.value).includes(d.role)),
104-
[data, selectedRoles],
105-
);
90+
// Prepare chart data
91+
const chartData = useMemo(() => {
92+
const filtered = data.filter(d => selectedRoles.map(r => r.value).includes(d.role));
93+
94+
if (viewMode === 'percentage') {
95+
return filtered.map(item => {
96+
const percentage =
97+
item.applicants > 0 ? Number(((item.hired / item.applicants) * 100).toFixed(1)) : 0;
10698

107-
// Apply dark mode to document body
99+
return {
100+
...item,
101+
hiredPercentage: percentage,
102+
};
103+
});
104+
}
105+
106+
return filtered;
107+
}, [data, selectedRoles, viewMode]);
108+
109+
// Apply dark mode
108110
useEffect(() => {
109111
if (darkMode) {
110112
document.body.classList.add('dark-mode-body');
@@ -138,10 +140,12 @@ function ApplicantVolunteerRatio() {
138140
return (
139141
<div className={`${styles.page} ${darkMode ? styles.dark : ''}`}>
140142
<h2 className={styles.heading}>Number of People Hired vs. Total Applications</h2>
143+
144+
{/* Filters */}
141145
<div className={styles.filters}>
142146
<div className={styles.filterGroup}>
143147
<label htmlFor="start-date" className={styles.label}>
144-
Date Range:{' '}
148+
Date Range:
145149
</label>
146150
<div className={styles.dateInputWrapper}>
147151
<DatePicker
@@ -173,9 +177,10 @@ function ApplicantVolunteerRatio() {
173177
</div>
174178
)}
175179
</div>
180+
176181
<div className={styles.filterGroupInline}>
177182
<label htmlFor="role-select" className={styles.label}>
178-
Role:{' '}
183+
Role:
179184
</label>
180185
<Select
181186
id="role-select"
@@ -189,39 +194,119 @@ function ApplicantVolunteerRatio() {
189194
/>
190195
</div>
191196
</div>
197+
198+
{/* Toggle Buttons */}
199+
<div style={{ marginBottom: '12px', display: 'flex', gap: '8px' }}>
200+
<button
201+
type="button"
202+
onClick={() => setViewMode('count')}
203+
style={{
204+
padding: '6px 12px',
205+
cursor: 'pointer',
206+
backgroundColor: viewMode === 'count' ? '#1976d2' : '#e0e0e0',
207+
color: viewMode === 'count' ? '#fff' : '#000',
208+
border: 'none',
209+
borderRadius: '4px',
210+
}}
211+
>
212+
Count View
213+
</button>
214+
215+
<button
216+
type="button"
217+
onClick={() => setViewMode('percentage')}
218+
style={{
219+
padding: '6px 12px',
220+
cursor: 'pointer',
221+
backgroundColor: viewMode === 'percentage' ? '#1976d2' : '#e0e0e0',
222+
color: viewMode === 'percentage' ? '#fff' : '#000',
223+
border: 'none',
224+
borderRadius: '4px',
225+
}}
226+
>
227+
Percentage View
228+
</button>
229+
</div>
230+
192231
{chartData.length > 0 ? (
193232
<div className={styles.chartContainer}>
194-
<ResponsiveContainer width="100%" height={400}>
233+
<ResponsiveContainer width="100%" height={350}>
195234
<BarChart
196235
data={chartData}
197236
layout="vertical"
198237
margin={{ top: 20, right: 40, left: 80, bottom: 20 }}
199238
barCategoryGap={24}
239+
barSize={16}
200240
>
201241
<XAxis
202242
type="number"
203-
label={{
204-
value: 'Percentage of People Hired vs. Total Applications',
205-
position: 'insideBottom',
206-
offset: -5,
207-
}}
208-
allowDecimals={false}
243+
domain={viewMode === 'percentage' ? [0, 100] : ['auto', 'auto']}
244+
allowDecimals={viewMode === 'percentage'}
209245
/>
246+
210247
<YAxis
211248
dataKey="role"
212249
type="category"
213250
width={180}
214-
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
251+
label={{ value: 'Role', angle: -90, position: 'insideLeft' }}
215252
/>
253+
216254
<Tooltip />
217-
<Bar dataKey="applicants" fill="#1976d2" name="Total Applicants">
218-
<LabelList dataKey="applicants" position="right" />
219-
</Bar>
220-
<Bar dataKey="hired" fill="#43a047" name="Total Hired">
221-
<LabelList dataKey="hired" position="right" />
222-
</Bar>
255+
256+
{viewMode === 'count' ? (
257+
<>
258+
<Bar dataKey="applicants" fill="#1976d2">
259+
<LabelList dataKey="applicants" position="right" />
260+
</Bar>
261+
262+
<Bar dataKey="hired" fill="#43a047">
263+
<LabelList dataKey="hired" position="right" />
264+
</Bar>
265+
</>
266+
) : (
267+
<Bar dataKey="hiredPercentage" fill="#43a047">
268+
<LabelList
269+
dataKey="hiredPercentage"
270+
position="right"
271+
formatter={value => `${value}%`}
272+
/>
273+
</Bar>
274+
)}
223275
</BarChart>
224276
</ResponsiveContainer>
277+
278+
{/* Manual Legend */}
279+
<div
280+
style={{
281+
display: 'flex',
282+
justifyContent: 'center',
283+
gap: '16px',
284+
marginTop: '12px',
285+
fontWeight: 500,
286+
}}
287+
>
288+
{viewMode === 'count' ? (
289+
<>
290+
<span style={{ color: '#1976d2' }}>■ Total Applications</span>
291+
<span style={{ color: '#43a047' }}>■ People Hired</span>
292+
</>
293+
) : (
294+
<span style={{ color: '#43a047' }}>■ People Hired (%)</span>
295+
)}
296+
</div>
297+
298+
{/* Axis Title */}
299+
<div
300+
style={{
301+
textAlign: 'center',
302+
marginTop: '10px',
303+
fontWeight: 500,
304+
}}
305+
>
306+
{viewMode === 'percentage'
307+
? 'Percentage of People Hired (%)'
308+
: 'Number of Applications / Hires'}
309+
</div>
225310
</div>
226311
) : (
227312
<div className={styles.noData}>

src/components/ApplicantVolunteerRatio/ApplicantVolunteerRatio.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Container */
22
.page {
3-
max-width: 900px;
3+
max-width: 1200px;
44
margin: 0 auto;
55
padding: 24px;
66
}
@@ -74,7 +74,7 @@
7474
/* Chart Container */
7575
.chartContainer {
7676
width: 100%;
77-
height: 400px;
77+
height: auto;
7878
}
7979

8080
/* No Data */

0 commit comments

Comments
 (0)