@@ -4,39 +4,45 @@ import { BarChart, Bar, XAxis, YAxis, Tooltip, LabelList, ResponsiveContainer }
44import DatePicker from 'react-datepicker' ;
55import Select from 'react-select' ;
66import { getAllApplicantVolunteerRatios } from '../../services/applicantVolunteerRatioService' ;
7- import 'react-datepicker/dist/react-datepicker.css' ;
87import styles from './ApplicantVolunteerRatio.module.css' ;
8+ import 'react-datepicker/dist/react-datepicker.css' ;
99
1010function 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