Skip to content

Commit 4f6b4e3

Browse files
Resolved package-lock.json merge conflict
2 parents 59118df + ccea523 commit 4f6b4e3

31 files changed

Lines changed: 5580 additions & 4170 deletions

package-lock.json

Lines changed: 1399 additions & 511 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ApplicantVolunteerRatio/ApplicantVolunteerRatio.jsx

Lines changed: 133 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,27 @@ import DatePicker from 'react-datepicker';
55
import Select from 'react-select';
66
import { getAllApplicantVolunteerRatios } from '../../services/applicantVolunteerRatioService';
77
import 'react-datepicker/dist/react-datepicker.css';
8+
import styles from './ApplicantVolunteerRatio.module.css';
89

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

20-
// Fetch all available roles (without filtering)
2121
useEffect(() => {
2222
const fetchAllRoles = async () => {
2323
try {
2424
const response = await getAllApplicantVolunteerRatios({});
25-
const apiData = response.data;
26-
27-
// Get all unique roles
25+
const apiData = response.data || [];
2826
const uniqueRoles = [...new Set(apiData.map(item => item.role))];
2927
const roleOptions = uniqueRoles.map(role => ({ label: role, value: role }));
30-
3128
setAllRoles(roleOptions);
32-
33-
// Set all roles as selected by default
3429
setSelectedRoles(roleOptions);
3530
} catch (err) {
3631
// eslint-disable-next-line no-console
@@ -42,7 +37,6 @@ function ApplicantVolunteerRatio() {
4237
fetchAllRoles();
4338
}, []);
4439

45-
// Fetch filtered data based on selected roles and date range
4640
useEffect(() => {
4741
const fetchFilteredData = async () => {
4842
// Validate date range: start must be before or equal to end
@@ -63,23 +57,13 @@ function ApplicantVolunteerRatio() {
6357

6458
try {
6559
setLoading(true);
66-
67-
// Prepare filters
6860
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-
}
75-
if (selectedRoles.length > 0) {
76-
filters.roles = selectedRoles.map(role => role.value).join(',');
77-
}
61+
if (startDate) filters.startDate = startDate.toISOString().split('T')[0];
62+
if (endDate) filters.endDate = endDate.toISOString().split('T')[0];
63+
if (selectedRoles.length > 0) filters.roles = selectedRoles.map(r => r.value).join(',');
7864

7965
const response = await getAllApplicantVolunteerRatios(filters);
80-
const apiData = response.data;
81-
82-
// Transform API data to match chart format
66+
const apiData = response?.data || [];
8367
const transformedData = apiData.map(item => ({
8468
role: item.role,
8569
applicants: item.totalApplicants,
@@ -97,14 +81,31 @@ function ApplicantVolunteerRatio() {
9781
};
9882

9983
fetchFilteredData();
100-
}, [startDate, endDate, selectedRoles]); // Re-fetch when date range or selected roles change
84+
}, [startDate, endDate, selectedRoles, validationError]);
10185

102-
// Filter and transform data for chart
10386
const chartData = useMemo(
10487
() => data.filter(d => selectedRoles.map(r => r.value).includes(d.role)),
10588
[data, selectedRoles],
10689
);
10790

91+
const handleStartDateChange = date => {
92+
setStartDate(date);
93+
if (endDate && date && date > endDate) {
94+
setValidationError('Start date must be earlier than or equal to End date.');
95+
} else {
96+
setValidationError('');
97+
}
98+
};
99+
100+
const handleEndDateChange = date => {
101+
setEndDate(date);
102+
if (startDate && date && startDate > date) {
103+
setValidationError('Start date must be earlier than or equal to End date.');
104+
} else {
105+
setValidationError('');
106+
}
107+
};
108+
108109
// Inline styles for react-select to guarantee contrast in dark mode (overrides other CSS)
109110
const selectStyles = useMemo(() => {
110111
if (!darkMode) return undefined;
@@ -189,138 +190,129 @@ function ApplicantVolunteerRatio() {
189190
};
190191
}, [darkMode]);
191192

193+
const containerClass = `${styles.container} ${darkMode ? styles.containerDark : ''}`;
194+
const headerClass = `${styles.header} ${darkMode ? styles.headerDark : ''}`;
195+
192196
if (loading) {
193197
return (
194-
<div style={{ maxWidth: 900, margin: '0 auto', padding: 24 }}>
195-
<h2>Number of People Hired vs. Total Applications</h2>
196-
<div>Loading...</div>
198+
<div className={containerClass}>
199+
<h2 className={headerClass}>Number of People Hired vs. Total Applications</h2>
200+
<div className={styles.loading}>Loading...</div>
197201
</div>
198202
);
199203
}
200204

201205
if (error) {
202206
return (
203-
<div style={{ maxWidth: 900, margin: '0 auto', padding: 24 }}>
204-
<h2>Number of People Hired vs. Total Applications</h2>
205-
<div style={{ color: 'red' }}>{error}</div>
207+
<div className={containerClass}>
208+
<h2 className={headerClass}>Number of People Hired vs. Total Applications</h2>
209+
<div className={styles.error}>{error}</div>
206210
</div>
207211
);
208212
}
209213

210-
const darkModeStyles = darkMode
211-
? {
212-
backgroundColor: '#1B2A41',
213-
color: '#e0e0e0',
214-
}
215-
: {};
216-
217214
return (
218-
<div
219-
className={`applicant-volunteer-page ${darkMode ? 'dark-mode' : ''}`}
220-
style={{ maxWidth: 900, margin: '0 auto', padding: 24 }}
221-
>
222-
<div className="applicant-volunteer-content" style={darkMode ? darkModeStyles : {}}>
223-
<h2 className={darkMode ? 'text-light' : ''}>
224-
Number of People Hired vs. Total Applications
225-
</h2>
226-
<div style={{ display: 'flex', gap: 16, marginBottom: 24, flexWrap: 'wrap' }}>
227-
<div>
228-
<label
229-
htmlFor="start-date"
230-
style={{ fontWeight: 500 }}
231-
className={darkMode ? 'text-light' : ''}
232-
>
233-
Date Range:{' '}
234-
</label>
235-
<DatePicker
236-
id="start-date"
237-
selected={startDate}
238-
onChange={date => setStartDate(date)}
239-
selectsStart
240-
startDate={startDate}
241-
endDate={endDate}
242-
placeholderText="Start Date"
243-
dateFormat="yyyy/MM/dd"
244-
style={{ marginRight: 8 }}
245-
/>
246-
<span> to </span>
247-
<DatePicker
248-
id="end-date"
249-
selected={endDate}
250-
onChange={date => setEndDate(date)}
251-
selectsEnd
252-
startDate={startDate}
253-
endDate={endDate}
254-
minDate={startDate}
255-
placeholderText="End Date"
256-
dateFormat="yyyy/MM/dd"
215+
<div className={containerClass}>
216+
<h2 className={headerClass}>Number of People Hired vs. Total Applications</h2>
217+
218+
<div className={styles.controls}>
219+
<div className={styles.dateGroup}>
220+
<label
221+
htmlFor="start-date"
222+
className={`${styles.label} ${darkMode ? styles.labelDark : ''}`}
223+
>
224+
Date Range:
225+
</label>
226+
<DatePicker
227+
id="start-date"
228+
selected={startDate}
229+
onChange={handleStartDateChange}
230+
selectsStart
231+
startDate={startDate}
232+
endDate={endDate}
233+
placeholderText="Start Date"
234+
dateFormat="yyyy/MM/dd"
235+
className={`${styles.dateInput} ${darkMode ? styles.dateInputDark : ''}`}
236+
/>
237+
<span className={darkMode ? styles.labelDark : ''}>to</span>
238+
<DatePicker
239+
id="end-date"
240+
selected={endDate}
241+
onChange={handleEndDateChange}
242+
selectsEnd
243+
startDate={startDate}
244+
endDate={endDate}
245+
minDate={startDate}
246+
placeholderText="End Date"
247+
dateFormat="yyyy/MM/dd"
248+
className={`${styles.dateInput} ${darkMode ? styles.dateInputDark : ''}`}
249+
/>
250+
{validationError && (
251+
<div className={styles.validationError} role="alert">
252+
{validationError}
253+
</div>
254+
)}
255+
</div>
256+
257+
<div className={styles.selectWrapper}>
258+
<label
259+
htmlFor="role-select"
260+
className={`${styles.label} ${darkMode ? styles.labelDark : ''}`}
261+
>
262+
Role:
263+
</label>
264+
<Select
265+
id="role-select"
266+
isMulti
267+
options={allRoles}
268+
value={selectedRoles}
269+
onChange={setSelectedRoles}
270+
placeholder="Select roles..."
271+
className={styles.select}
272+
classNamePrefix="custom-select"
273+
styles={selectStyles}
274+
menuPortalTarget={typeof document !== 'undefined' ? document.body : undefined}
275+
/>
276+
</div>
277+
</div>
278+
279+
{chartData.length > 0 ? (
280+
<ResponsiveContainer width="100%" height={400}>
281+
<BarChart
282+
data={chartData}
283+
layout="vertical"
284+
margin={{ top: 20, right: 40, left: 80, bottom: 20 }}
285+
barCategoryGap={24}
286+
>
287+
<XAxis
288+
type="number"
289+
label={{
290+
value: 'Percentage of People Hired vs. Total Applications',
291+
position: 'insideBottom',
292+
offset: -5,
293+
}}
294+
allowDecimals={false}
257295
/>
258-
{validationError && (
259-
<div style={{ color: '#ffcc00', marginTop: 8, fontWeight: 'bold' }} role="alert">
260-
{validationError}
261-
</div>
262-
)}
263-
</div>
264-
<div style={{ minWidth: 220 }}>
265-
<label
266-
htmlFor="role-select"
267-
style={{ fontWeight: 500 }}
268-
className={darkMode ? 'text-light' : ''}
269-
>
270-
Role:{' '}
271-
</label>
272-
<Select
273-
id="role-select"
274-
isMulti
275-
options={allRoles} // Use allRoles for the dropdown
276-
value={selectedRoles}
277-
onChange={setSelectedRoles}
278-
placeholder="Select roles..."
279-
className={darkMode ? 'dark-select' : ''}
280-
classNamePrefix="custom-select"
281-
styles={selectStyles}
282-
menuPortalTarget={typeof document !== 'undefined' ? document.body : undefined}
296+
<YAxis
297+
dataKey="role"
298+
type="category"
299+
width={180}
300+
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
283301
/>
284-
</div>
302+
<Tooltip />
303+
<Bar dataKey="applicants" fill="#1976d2" name="Total Applicants">
304+
<LabelList dataKey="applicants" position="right" />
305+
</Bar>
306+
<Bar dataKey="hired" fill="#43a047" name="Total Hired">
307+
<LabelList dataKey="hired" position="right" />
308+
</Bar>
309+
</BarChart>
310+
</ResponsiveContainer>
311+
) : (
312+
<div className={styles.noData}>
313+
No data available. Please add some applicant volunteer ratio data.
285314
</div>
286-
{chartData.length > 0 ? (
287-
<ResponsiveContainer width="100%" height={400}>
288-
<BarChart
289-
data={chartData}
290-
layout="vertical"
291-
margin={{ top: 20, right: 40, left: 80, bottom: 20 }}
292-
barCategoryGap={24}
293-
>
294-
<XAxis
295-
type="number"
296-
label={{
297-
value: 'Percentage of People Hired vs. Total Applications',
298-
position: 'insideBottom',
299-
offset: -5,
300-
}}
301-
allowDecimals={false}
302-
/>
303-
<YAxis
304-
dataKey="role"
305-
type="category"
306-
width={180}
307-
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
308-
/>
309-
<Tooltip />
310-
<Bar dataKey="applicants" fill="#1976d2" name="Total Applicants">
311-
<LabelList dataKey="applicants" position="right" />
312-
</Bar>
313-
<Bar dataKey="hired" fill="#43a047" name="Total Hired">
314-
<LabelList dataKey="hired" position="right" />
315-
</Bar>
316-
</BarChart>
317-
</ResponsiveContainer>
318-
) : (
319-
<div style={{ textAlign: 'center', padding: '40px 0' }}>
320-
No data available. Please add some applicant volunteer ratio data.
321-
</div>
322-
)}
323-
</div>
315+
)}
324316
</div>
325317
);
326318
}

0 commit comments

Comments
 (0)