Skip to content

Commit 96bd716

Browse files
committed
Created chart for job roles hit and applications, added all the relevant files
1 parent 39828dc commit 96bd716

File tree

9 files changed

+9565
-9094
lines changed

9 files changed

+9565
-9094
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { JOBS_APPS_HITS_REQUEST, JOBS_APPS_HITS_REQUEST_SUCCESS, JOBS_APPS_HITS_REQUEST_FAILURE } from '../../constants/jobAnalytics/JobsApplicationsHitsConstants';
2+
import { ENDPOINTS } from '../../utils/URL';
3+
4+
export const fetchJobsHitsApplications = (queryParams, token) => async (dispatch) => {
5+
dispatch({ type: JOBS_APPS_HITS_REQUEST });
6+
7+
try {
8+
const response = await fetch(`${ENDPOINTS.JOB_HITS_AND_APPLICATIONS}?${queryParams}`, {
9+
method: 'GET',
10+
headers: {
11+
'Content-Type': 'application/json',
12+
'Authorization': token
13+
}
14+
});
15+
16+
const data = await response.json();
17+
18+
if(!response.ok) {
19+
throw new Error(data.error || 'Failed to fetch data');
20+
}
21+
22+
dispatch({ type: JOBS_APPS_HITS_REQUEST_SUCCESS, payload: data });
23+
} catch (error) {
24+
dispatch({ type: JOBS_APPS_HITS_REQUEST_FAILURE, payload: error.message });
25+
}
26+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { Fragment, useEffect, useState } from 'react';
2+
import { fetchJobsHitsApplications } from '../../../actions/jobAnalytics/JobsHitsApplicationsActions';
3+
import { useDispatch, useSelector } from 'react-redux';
4+
import { BarChart, XAxis, YAxis, Tooltip, Legend, Bar, ResponsiveContainer, Brush } from 'recharts';
5+
import styles from './JobsHitsApplicationsChart.module.css';
6+
import Select from 'react-select';
7+
import DatePicker from 'react-datepicker';
8+
9+
export const JobsHitsApplicationsChart = () => {
10+
const [startDate, setStartDate] = useState(null);
11+
const [endDate, setEndDate] = useState(null);
12+
const [selectedRoles, setSelectedRoles] = useState([]);
13+
const [roleOptions, setRoleOptions] = useState([]);
14+
const [roleAssigned, setRoleAssigned] = useState(false);
15+
16+
const { loading, data, error } = useSelector(state => state.jobsHitsApplications);
17+
const darkMode = useSelector(state => state.theme.darkMode);
18+
const dispatch = useDispatch();
19+
const token = localStorage.getItem('token');
20+
21+
useEffect(() => {
22+
const queryParams = new URLSearchParams();
23+
if (startDate) queryParams.append('startDate', startDate.toISOString());
24+
if (endDate) queryParams.append('endDate', endDate.toISOString());
25+
if (selectedRoles.length > 0) {
26+
const roles = selectedRoles.map(role => role.value).join(',');
27+
queryParams.append('roles', roles);
28+
}
29+
dispatch(fetchJobsHitsApplications(queryParams.toString(), token));
30+
}, [startDate, endDate, selectedRoles, dispatch, token]);
31+
32+
useEffect(() => {
33+
if (data && data.length > 0 && !roleAssigned) {
34+
setRoleOptions(
35+
data.map(item => {
36+
return {
37+
value: item.role,
38+
label: item.role,
39+
};
40+
}, setRoleAssigned(true)),
41+
);
42+
}
43+
}, [loading, error, data]);
44+
45+
const CustomYAxisNames = ({ x, y, payload }) => {
46+
const text = payload.value;
47+
const truncated = text.split(' ').slice(0, 2);
48+
49+
return (
50+
<g transform={`translate(${x},${y})`}>
51+
{truncated.map((line, index) => (
52+
<text
53+
key={index}
54+
x={0}
55+
y={0}
56+
dy={index * 14 - (truncated.length - 1) * 7}
57+
textAnchor="end"
58+
fill="#666"
59+
fontSize={12}
60+
>
61+
<title>{text}</title>
62+
{line}
63+
</text>
64+
))}
65+
</g>
66+
);
67+
};
68+
69+
const handleStartDateChange = date => {
70+
if (endDate && date > endDate) {
71+
setEndDate(date);
72+
}
73+
setStartDate(date);
74+
};
75+
76+
const handleEndDateChange = date => {
77+
if (startDate && date < startDate) {
78+
setStartDate(date);
79+
}
80+
setEndDate(date);
81+
};
82+
83+
const handleResetDates = () => {
84+
setStartDate(null);
85+
setEndDate(null);
86+
};
87+
88+
return (
89+
<Fragment>
90+
<div className={`${styles.mainContainer} ${darkMode ? styles.bgOxfordBlue : ''}`}>
91+
<h4 className={darkMode ? styles.colorWhite : ''}>Role-wise Hits and Applications</h4>
92+
<div className={styles.filterContainer}>
93+
<div className={styles.dateFilter}>
94+
<div className={styles.dateReset}>
95+
{startDate || endDate ? (
96+
<button
97+
onClick={handleResetDates}
98+
className={`${styles.resetBtn} ${darkMode ? styles.resetBtnDark : ''}`}
99+
>
100+
Reset Dates
101+
</button>
102+
) : (
103+
<button
104+
className={`${styles.resetBtn} ${darkMode ? styles.resetBtnDark : ''}`}
105+
disabled
106+
>
107+
Reset Dates
108+
</button>
109+
)}
110+
</div>
111+
<div className={styles.startDate}>
112+
<label
113+
htmlFor="start-date"
114+
className={`${styles.dateName} ${darkMode ? styles.colorWhite : ''}`}
115+
>
116+
Start Date:
117+
</label>
118+
<DatePicker
119+
id="start-date"
120+
selected={startDate}
121+
onChange={handleStartDateChange}
122+
selectsStart
123+
startDate={startDate}
124+
endDate={endDate}
125+
placeholderText="Start Date"
126+
className={`${styles.datePicker} ${darkMode ? styles.bgYinmnBlue : ''}`}
127+
/>
128+
</div>
129+
<div className={styles.endDate}>
130+
<label
131+
htmlFor="end-date"
132+
className={`${styles.dateName} ${darkMode ? styles.colorWhite : ''}`}
133+
>
134+
End Date:
135+
</label>
136+
<DatePicker
137+
id="end-date"
138+
selected={endDate}
139+
onChange={handleEndDateChange}
140+
selectsEnd
141+
startDate={startDate}
142+
endDate={endDate}
143+
placeholderText="End Date"
144+
className={styles.datePicker}
145+
/>
146+
</div>
147+
</div>
148+
149+
<div className={`${styles.roleFilter}`}>
150+
<div className={styles.roleFilterContainer}>
151+
<label htmlFor="role-select" className={darkMode ? styles.colorWhite : ''}>
152+
Roles:
153+
</label>
154+
<Select
155+
id="role-select"
156+
isMulti
157+
options={roleOptions}
158+
onChange={setSelectedRoles}
159+
placeholder="Select Roles"
160+
className={styles.roleSelector}
161+
/>
162+
</div>
163+
</div>
164+
</div>
165+
<div className={styles.chartContainer}>
166+
{loading && <div className={`${styles.spinner}`}>Loading...</div>}
167+
{error && <div className={`${styles.errorMessage}`}>Issue getting the data</div>}
168+
{!loading && !error && data.length === 0 && (
169+
<div className={`${styles.emptyMessage}`}>
170+
No data available for the selected filters.
171+
</div>
172+
)}
173+
{!loading && !error && data.length > 0 && (
174+
<ResponsiveContainer className={styles.chart} width="70%" height="100%">
175+
<BarChart
176+
layout="vertical"
177+
data={data}
178+
margin={{ top: 20, right: 30, left: 65, bottom: 20 }}
179+
>
180+
<XAxis
181+
type="number"
182+
label={{
183+
value: 'Number of Hits/Applications',
184+
position: 'bottom',
185+
style: {
186+
fill: darkMode ? styles.colorWhite : '',
187+
},
188+
}}
189+
/>
190+
<YAxis
191+
type="category"
192+
dataKey="role"
193+
label={{
194+
value: 'Roles',
195+
position: 'top',
196+
style: {
197+
fill: darkMode ? styles.colorWhite : '',
198+
},
199+
}}
200+
tick={<CustomYAxisNames />}
201+
/>
202+
<Bar dataKey="hits" fill="#8884d8" activeBar={false} isAnimationActive={false} />
203+
<Bar
204+
dataKey="applications"
205+
fill="#82ca9d"
206+
activeBar={false}
207+
isAnimationActive={false}
208+
/>
209+
<Tooltip cursor={{ fill: 'transparent' }} />
210+
<Legend verticalAlign="top" align="center" />
211+
212+
{data.length > 7 && (
213+
<Brush
214+
dataKey="role"
215+
height={20}
216+
stroke="#8884d8"
217+
startIndex={0}
218+
endIndex={7}
219+
y={480}
220+
/>
221+
)}
222+
</BarChart>
223+
</ResponsiveContainer>
224+
)}
225+
</div>
226+
</div>
227+
</Fragment>
228+
);
229+
};

0 commit comments

Comments
 (0)