@@ -9,51 +9,47 @@ import 'react-datepicker/dist/react-datepicker.css';
99
1010function 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 } >
0 commit comments