Skip to content

Commit a0ccf72

Browse files
Merge pull request #4448 from OneCommunityGlobal/fix/applicant-volunteer-tooltip-dark-mode
Mani shashank fix: dark mode tooltip styling for ApplicantVolunteerRatio chart
2 parents 38e1319 + 54a4dd5 commit a0ccf72

2 files changed

Lines changed: 324 additions & 230 deletions

File tree

src/components/ApplicantVolunteerRatio/ApplicantVolunteerRatio.jsx

Lines changed: 101 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,45 @@ import { BarChart, Bar, XAxis, YAxis, Tooltip, LabelList, ResponsiveContainer }
44
import DatePicker from 'react-datepicker';
55
import Select from 'react-select';
66
import { getAllApplicantVolunteerRatios } from '../../services/applicantVolunteerRatioService';
7-
import 'react-datepicker/dist/react-datepicker.css';
87
import styles from './ApplicantVolunteerRatio.module.css';
8+
import 'react-datepicker/dist/react-datepicker.css';
99

1010
function ApplicantVolunteerRatio() {
1111
const darkMode = useSelector(state => state.theme.darkMode);
1212
const [data, setData] = useState([]);
13-
const [allRoles, setAllRoles] = useState([]);
13+
const [allRoles, setAllRoles] = useState([]); // Store all available roles
1414
const [loading, setLoading] = useState(true);
1515
const [error, setError] = useState(null);
1616
const [selectedRoles, setSelectedRoles] = useState([]);
1717
const [startDate, setStartDate] = useState(null);
1818
const [endDate, setEndDate] = useState(null);
1919
const [validationError, setValidationError] = useState('');
2020

21+
// Fetch all available roles (without filtering)
2122
useEffect(() => {
2223
const fetchAllRoles = async () => {
2324
try {
2425
const response = await getAllApplicantVolunteerRatios({});
25-
const apiData = response.data || [];
26+
const apiData = response.data;
27+
28+
// Get all unique roles
2629
const uniqueRoles = [...new Set(apiData.map(item => item.role))];
2730
const roleOptions = uniqueRoles.map(role => ({ label: role, value: role }));
31+
2832
setAllRoles(roleOptions);
33+
34+
// Set all roles as selected by default
2935
setSelectedRoles(roleOptions);
3036
} catch (err) {
31-
// eslint-disable-next-line no-console
32-
console.error('Error fetching all roles:', err);
37+
// Error fetching all roles
3338
setError('Failed to load roles. Please try again.');
3439
}
3540
};
3641

3742
fetchAllRoles();
3843
}, []);
3944

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

5864
try {
5965
setLoading(true);
66+
67+
// Prepare filters
6068
const filters = {};
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(',');
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+
}
6478

6579
const response = await getAllApplicantVolunteerRatios(filters);
66-
const apiData = response?.data || [];
80+
const apiData = response.data;
81+
82+
// Transform API data to match chart format
6783
const transformedData = apiData.map(item => ({
6884
role: item.role,
6985
applicants: item.totalApplicants,
@@ -72,194 +88,94 @@ function ApplicantVolunteerRatio() {
7288

7389
setData(transformedData);
7490
} catch (err) {
75-
// eslint-disable-next-line no-console
76-
console.error('Error fetching applicant volunteer ratio data:', err);
91+
// Error fetching applicant volunteer ratio data
7792
setError('Failed to load data. Please try again.');
7893
} finally {
7994
setLoading(false);
8095
}
8196
};
8297

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

101+
// Filter and transform data for chart
86102
const chartData = useMemo(
87103
() => data.filter(d => selectedRoles.map(r => r.value).includes(d.role)),
88104
[data, selectedRoles],
89105
);
90106

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-
109-
// Inline styles for react-select to guarantee contrast in dark mode (overrides other CSS)
110-
const selectStyles = useMemo(() => {
111-
if (!darkMode) return undefined;
112-
113-
return {
114-
control: provided => ({
115-
...provided,
116-
backgroundColor: '#0b2434',
117-
borderColor: '#2b4a6b',
118-
boxShadow: 'none',
119-
color: '#ffffff',
120-
}),
121-
valueContainer: provided => ({ ...provided, color: '#ffffff' }),
122-
singleValue: provided => ({ ...provided, color: '#ffffff' }),
123-
placeholder: provided => ({ ...provided, color: 'rgba(224,224,224,0.9)' }),
124-
menu: provided => ({ ...provided, backgroundColor: '#0b2434', color: '#ffffff' }),
125-
menuPortal: provided => ({ ...provided, zIndex: 9999 }),
126-
option: (provided, state) => ({
127-
...provided,
128-
backgroundColor: state.isSelected
129-
? 'rgba(67,160,71,0.22)'
130-
: state.isFocused
131-
? 'rgba(255,255,255,0.06)'
132-
: 'transparent',
133-
color: '#ffffff',
134-
}),
135-
multiValue: provided => ({
136-
...provided,
137-
backgroundColor: 'rgba(255,255,255,0.06)',
138-
color: '#ffffff',
139-
}),
140-
multiValueLabel: provided => ({ ...provided, color: '#ffffff' }),
141-
dropdownIndicator: provided => ({ ...provided, color: '#ffffff' }),
142-
indicatorSeparator: provided => ({ ...provided, backgroundColor: 'rgba(255,255,255,0.06)' }),
143-
};
144-
}, [darkMode]);
145-
146-
// Apply dark mode to document body and inject page-specific dark styles
107+
// Apply dark mode to document body
147108
useEffect(() => {
148109
if (darkMode) {
149110
document.body.classList.add('dark-mode-body');
150111
} else {
151112
document.body.classList.remove('dark-mode-body');
152113
}
153114

154-
if (!document.getElementById('applicant-volunteer-dark-styles')) {
155-
const styleElement = document.createElement('style');
156-
styleElement.id = 'applicant-volunteer-dark-styles';
157-
styleElement.innerHTML = `
158-
/* Page-level dark background to cover gutters and root */
159-
.dark-mode-body, .dark-mode-body body, .dark-mode-body #root, .dark-mode-body .App {
160-
background-color: #1B2A41 !important;
161-
color: #e0e0e0 !important;
162-
}
163-
/* Common layout wrappers that might enforce white background */
164-
.dark-mode-body .header-wrapper,
165-
.dark-mode-body .content-wrapper,
166-
.dark-mode-body .page,
167-
.dark-mode-body .container,
168-
.dark-mode-body .container-fluid {
169-
background-color: #1B2A41 !important;
170-
color: #e0e0e0 !important;
171-
}
172-
.dark-mode-body .applicant-volunteer-page {
173-
background-color: #1B2A41 !important;
174-
color: #e0e0e0 !important;
175-
}
176-
.dark-mode-body .applicant-volunteer-content {
177-
background-color: #1B2A41 !important;
178-
color: #e0e0e0 !important;
179-
}
180-
.dark-mode-body .recharts-wrapper,
181-
.dark-mode-body .recharts-surface {
182-
background-color: #1B2A41 !important;
183-
}
184-
`;
185-
document.head.appendChild(styleElement);
186-
}
187-
188115
return () => {
189116
document.body.classList.remove('dark-mode-body');
190117
};
191118
}, [darkMode]);
192119

193-
const containerClass = `${styles.container} ${darkMode ? styles.containerDark : ''}`;
194-
const headerClass = `${styles.header} ${darkMode ? styles.headerDark : ''}`;
195-
196120
if (loading) {
197121
return (
198-
<div className={containerClass}>
199-
<h2 className={headerClass}>Number of People Hired vs. Total Applications</h2>
200-
<div className={styles.loading}>Loading...</div>
122+
<div className={`${styles.page} ${darkMode ? styles.dark : ''}`}>
123+
<h2 className={styles.heading}>Number of People Hired vs. Total Applications</h2>
124+
<div className={styles.statusMessage}>Loading...</div>
201125
</div>
202126
);
203127
}
204128

205129
if (error) {
206130
return (
207-
<div className={containerClass}>
208-
<h2 className={headerClass}>Number of People Hired vs. Total Applications</h2>
209-
<div className={styles.error}>{error}</div>
131+
<div className={`${styles.page} ${darkMode ? styles.dark : ''}`}>
132+
<h2 className={styles.heading}>Number of People Hired vs. Total Applications</h2>
133+
<div className={`${styles.statusMessage} ${styles.errorMessage}`}>{error}</div>
210134
</div>
211135
);
212136
}
213137

214138
return (
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:
139+
<div className={`${styles.page} ${darkMode ? styles.dark : ''}`}>
140+
<h2 className={styles.heading}>Number of People Hired vs. Total Applications</h2>
141+
<div className={styles.filters}>
142+
<div className={styles.filterGroup}>
143+
<label htmlFor="start-date" className={styles.label}>
144+
Date Range:{' '}
225145
</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-
/>
146+
<div className={styles.dateInputWrapper}>
147+
<DatePicker
148+
id="start-date"
149+
selected={startDate}
150+
onChange={date => setStartDate(date)}
151+
selectsStart
152+
startDate={startDate}
153+
endDate={endDate}
154+
placeholderText="Start Date"
155+
dateFormat="yyyy/MM/dd"
156+
/>
157+
<span>to</span>
158+
<DatePicker
159+
id="end-date"
160+
selected={endDate}
161+
onChange={date => setEndDate(date)}
162+
selectsEnd
163+
startDate={startDate}
164+
endDate={endDate}
165+
minDate={startDate}
166+
placeholderText="End Date"
167+
dateFormat="yyyy/MM/dd"
168+
/>
169+
</div>
250170
{validationError && (
251171
<div className={styles.validationError} role="alert">
252172
{validationError}
253173
</div>
254174
)}
255175
</div>
256-
257-
<div className={styles.selectWrapper}>
258-
<label
259-
htmlFor="role-select"
260-
className={`${styles.label} ${darkMode ? styles.labelDark : ''}`}
261-
>
262-
Role:
176+
<div className={styles.filterGroupInline}>
177+
<label htmlFor="role-select" className={styles.label}>
178+
Role:{' '}
263179
</label>
264180
<Select
265181
id="role-select"
@@ -268,46 +184,45 @@ function ApplicantVolunteerRatio() {
268184
value={selectedRoles}
269185
onChange={setSelectedRoles}
270186
placeholder="Select roles..."
271-
className={styles.select}
272187
classNamePrefix="custom-select"
273-
styles={selectStyles}
274188
menuPortalTarget={typeof document !== 'undefined' ? document.body : undefined}
275189
/>
276190
</div>
277191
</div>
278-
279192
{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}
295-
/>
296-
<YAxis
297-
dataKey="role"
298-
type="category"
299-
width={180}
300-
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
301-
/>
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>
193+
<div className={styles.chartContainer}>
194+
<ResponsiveContainer width="100%" height={400}>
195+
<BarChart
196+
data={chartData}
197+
layout="vertical"
198+
margin={{ top: 20, right: 40, left: 80, bottom: 20 }}
199+
barCategoryGap={24}
200+
>
201+
<XAxis
202+
type="number"
203+
label={{
204+
value: 'Percentage of People Hired vs. Total Applications',
205+
position: 'insideBottom',
206+
offset: -5,
207+
}}
208+
allowDecimals={false}
209+
/>
210+
<YAxis
211+
dataKey="role"
212+
type="category"
213+
width={180}
214+
label={{ value: 'Name of Role', angle: -90, position: 'insideLeft' }}
215+
/>
216+
<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>
223+
</BarChart>
224+
</ResponsiveContainer>
225+
</div>
311226
) : (
312227
<div className={styles.noData}>
313228
No data available. Please add some applicant volunteer ratio data.

0 commit comments

Comments
 (0)