source: jobvista-frontend/src/views/applications/ApplicationsByJobAd.js@ 4d97b63

main
Last change on this file since 4d97b63 was 4d97b63, checked in by 223021 <daniel.ilievski.2@…>, 4 weeks ago

Implemented Google login, additional file uploads, response messages and email notifications

  • Property mode set to 100644
File size: 10.4 KB
Line 
1import {useDispatch, useSelector} from "react-redux";
2import {useEffect, useState} from "react";
3import {useParams} from "react-router";
4import {ApplicationActions} from "../../redux/actions/applicationActions";
5import {ApplicationDetailsModal} from "./ApplicationDetailsModal";
6import "./Applications.css"
7import Select from "react-select";
8import {sortElementsBy} from "../../utils/utils";
9import {JobSeekerActions} from "../../redux/actions/JobSeekerActions";
10import {notifyAppStatusUpdate} from "../../utils/toastUtils";
11
12export const ApplicationsByJobAd = () => {
13
14 const dispatch = useDispatch();
15 let {advertisement_id} = useParams();
16
17 const [applicationsByJobAd, setApplicationsByJobAd] = useState([]);
18 let applicationsByJobAdState = useSelector(state => state.appl.applicationsByJobAdId)
19 const [dispatched, setDispatched] = useState(false);
20
21 const [profilePics, setProfilePics] = useState({});
22 let profilePicsState = useSelector(state => state.images.profilePictures)
23 const [profilePicsDispatched, setProfilePicsDispatched] = useState(false);
24
25 const [jobAdTitle, setJobAdTitle] = useState("");
26
27 const [changedApplications, setChangedApplications] = useState({});
28
29 useEffect(() => {
30 if(!dispatched) {
31 dispatch(ApplicationActions.fetchApplicationsByJobAdId(advertisement_id, (success, reponse) => {
32 if (success && reponse.data.length > 0) {
33 setApplicationsByJobAd(sortElementsBy(reponse.data, "submittedOn"))
34 setJobAdTitle(reponse.data[0].jobAdTitle)
35 }
36 setDispatched(true)
37 console.log("Fetch applications by job ad GET")
38 }))
39 } else {
40 setApplicationsByJobAd(sortElementsBy(applicationsByJobAdState, "submittedOn"));
41 if(applicationsByJobAdState.length > 0) {
42 setJobAdTitle(applicationsByJobAdState[0].jobAdTitle)
43 }
44
45 }
46
47 }, [applicationsByJobAdState])
48
49 useEffect(() => {
50 if(dispatched && !profilePicsDispatched) {
51 applicationsByJobAd.forEach(app => {
52 if(app.jobSeekerId && !profilePics[app.jobSeekerId]) {
53 fetchProfilePic(app.jobSeekerId)
54 }
55 })
56 setProfilePicsDispatched(true);
57 console.log("Fetch all profile pics GET")
58 } else if(profilePicsDispatched) {
59 setProfilePics(profilePicsState)
60 console.log("Fetch all profile pics STATE")
61 }
62 }, [dispatched])
63
64
65 const fetchProfilePic = (jobSeekerId) => {
66 dispatch(JobSeekerActions.downloadProfilePic(jobSeekerId, (success, response) => {
67 if(success) {
68 setProfilePics(prevState => ({...prevState, [jobSeekerId]: response}))
69 }
70 }))
71 }
72
73 const options = [
74 {value: 'PROPOSED', label: <span><i className="fa-solid fa-paper-plane"></i> Proposed</span>},
75 {value: 'UNDER_REVIEW', label: <span><i className="fa-solid fa-file-pen"></i> Under Review</span>},
76 {value: 'ACCEPTED', label: <span><i className="fa-solid fa-user-check"></i> Accepted</span>},
77 {value: 'DENIED', label: <span><i className="fa-solid fa-user-slash"></i> Denied</span>}
78 ];
79
80 const [selectedFilter, setSelectedFilter] = useState('All');
81
82 const filters = [
83 { value: 'ALL', label: 'All', icon: 'fa-folder-open' },
84 { value: 'PROPOSED', label: 'Proposed', icon: 'fa-paper-plane' },
85 { value: 'UNDER_REVIEW', label: 'Under Review', icon: 'fa-file-pen' },
86 { value: 'ACCEPTED', label: 'Accepted', icon: 'fa-user-check' },
87 { value: 'DENIED', label: 'Denied', icon: 'fa-user-slash' },
88 ];
89
90 const filterApplicationsByJobAdvertisement = (filter) => {
91 dispatch(ApplicationActions.filterApplicationsByJobAdId(advertisement_id, filter, (success, response) => {
92 if(success) {
93 //notify
94 }
95 }))
96 }
97
98 let handleDefaultStatus = (status) => {
99 return options.find(option => option.value === status);
100 }
101
102 let handleStatusChange = (selectedOption, id) => {
103
104 const currentApplication = applicationsByJobAd.find(app => app.id === id);
105
106 setChangedApplications(prevState => ({
107 ...prevState,
108 [id]: {
109 ...prevState[id],
110 response: (selectedOption.value === "ACCEPTED" || selectedOption.value === "DENIED") ? (prevState[id]?.response || currentApplication.response || "") : "",
111 status: selectedOption.value
112 }
113 }))
114
115 setApplicationsByJobAd(prevState => (
116 prevState.map(application =>
117 application.id === id ? {...application, status: selectedOption.value} : application)
118 ))
119
120 const responseTextarea = document.getElementById(`response-${id}`);
121 if (responseTextarea) {
122 responseTextarea.value = "";
123 }
124
125 /* dispatch(ApplicationActions.updateApplicationStatus(id, selectedOption.value, (success, response) => {
126 if(success) {
127 notifyAppStatusUpdate()
128 }
129 }))*/
130 }
131
132 let handleResponseChange = (e, id) => {
133
134 const currentApplication = applicationsByJobAd.find(app => app.id === id);
135
136 setChangedApplications(prevState => ({
137 ...prevState,
138 [id]: {
139 ...prevState[id],
140 response: e.target.value,
141 status: prevState[id]?.status || currentApplication.status,
142 }
143 }
144 ))
145 }
146
147 const handleSaveChanges = () => {
148 console.log(changedApplications)
149 const changes = Object.entries(changedApplications).map(
150 ([applicationId, change]) => ({
151 id: applicationId,
152 status: change.status,
153 response: change.response,
154 })
155 );
156 console.log(changes)
157
158 if(changes.length === 0) {
159 return;
160 }
161
162 dispatch(ApplicationActions.updateApplications(changes, (success, response) => {
163 if(success) {
164 setChangedApplications({});
165 notifyAppStatusUpdate()
166 //notify change success
167 }
168 }))
169
170
171 }
172
173 const isChangedApplication = (id) => {
174 return changedApplications && Object.keys(changedApplications).includes(id.toString());
175 };
176
177
178 return (<div className="custom-container">
179 <div className="application-title">
180 {jobAdTitle ?
181 <h3>Applications for <b>{jobAdTitle}</b></h3> :
182 <h1></h1>
183 }
184 </div>
185
186 <div className="row">
187 <div className="col-md-6 application-filters-wrap">
188 <div className="application-filters d-inline-flex flex-row justify-content-start">
189 {
190 filters.map(filter => (
191 <span
192 key={filter.label}
193 className={selectedFilter === filter.label ? "selected" : ""}
194 onClick={() => {
195 setSelectedFilter(filter.label)
196 filterApplicationsByJobAdvertisement(filter.value)
197 setChangedApplications({});
198 }}
199 ><i className={`fa-solid ${filter.icon}`}></i> {filter.label}</span>
200 ))
201 }
202 </div>
203
204 </div>
205 <div className="col-md-6 d-inline-flex flex-row justify-content-end">
206 <button onClick={handleSaveChanges}
207 className={`blue-submit-button ${Object.keys(changedApplications).length === 0 ? 'disabled' : ''}`}
208 disabled={Object.keys(changedApplications).length === 0}
209 >Submit Changes</button>
210 </div>
211 </div>
212
213
214
215 {applicationsByJobAd && applicationsByJobAd.map((application, index) => (
216 <div
217 key={application.id}
218 className={`application-card-wrapper ${(application.status !== "PROPOSED" ) ? 'expanded' : ''}`}
219 >
220
221 <div className={`application-card ${changedApplications[application.id] ? 'changed' : ''}`}>
222 <div className="app-job-seeker-pic">
223 <img
224 src={profilePicsState[application.jobSeekerId]}
225 alt=""
226 width={75} height={75}
227 />
228 </div>
229 <div className="app-info">
230 <span>Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
231 day: 'numeric',
232 month: 'long',
233 year: 'numeric'
234 })}</b></span>
235 <div className="contact-info">
236 <div className="contact-item">
237 <i className="fa-solid fa-user"></i> <span>{application.jobSeekerName}</span>
238 </div>
239 <div className="contact-item">
240 <i className="fa-solid fa-envelope"></i> <span>{application.jobSeekerEmail}</span>
241 </div>
242 <div className="contact-item">
243 <i className="fa-solid fa-phone"></i> <span>{application.jobSeekerPhoneNumber}</span>
244 </div>
245 </div>
246 </div>
247
248 <div className="app-status">
249 <ApplicationDetailsModal application={application} />
250 <div className="select">
251 <Select options={options} onChange={(selectedOption) => handleStatusChange(selectedOption, application.id)} defaultValue={handleDefaultStatus(application.status)} />
252 </div>
253 </div>
254 </div>
255
256 <div className="expand-section">
257 <textarea
258 id={`response-${application.id}`}
259 placeholder={application.status === "UNDER_REVIEW" ? "Request additional documents..." :"Write your response..."}
260 defaultValue={application.response}
261 onChange={(e) => handleResponseChange(e, application.id)}
262 />
263 </div>
264 </div>
265 ))}
266
267
268 </div>)
269}
Note: See TracBrowser for help on using the repository browser.