Skip to content

Commit

Permalink
1470 interview notes on recruitmentpositionoverviewpage (#1568)
Browse files Browse the repository at this point in the history
* Refactor to use useQuery instead of useState

* Added form for interview form

* Translation change

* Removed unused file

* Biome:fix and removed imports

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx

Co-authored-by: Snorre Sæther <[email protected]>

* Made changes requested from review

---------

Co-authored-by: Snorre Sæther <[email protected]>
  • Loading branch information
nemisis84 and Snorre98 authored Nov 15, 2024
1 parent e475996 commit ba566b0
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -1,71 +1,64 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
import { BackButton, Button, Link, SamfundetLogoSpinner } from '~/Components';
import { Table } from '~/Components/Table';
import { Text } from '~/Components/Text/Text';
import { getRecruitmentApplicationsForRecruiter, withdrawRecruitmentApplicationRecruiter } from '~/api';
import type { RecruitmentApplicationDto, RecruitmentUserDto } from '~/dto';
import type { InterviewDto } from '~/dto';
import { STATUS } from '~/http_status_codes';
import { KEY } from '~/i18n/constants';
import { reverse } from '~/named-urls';
import { ROUTES } from '~/routes';
import { dbT } from '~/utils';
import { AdminPage } from '../AdminPageLayout';
import styles from './RecruitmentApplicantAdminPage.module.scss';
import { RecruitmentInterviewNotesForm } from './RecruitmentInterviewNotesForm';

export function RecruitmentApplicantAdminPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const [recruitmentApplication, setRecruitmentApplication] = useState<RecruitmentApplicationDto>();
const [otherRecruitmentApplication, setOtherRecruitmentApplication] = useState<RecruitmentApplicationDto[]>([]);
const [applicant, setApplicant] = useState<RecruitmentUserDto>();

const [loading, setLoading] = useState(true);

const { applicationID } = useParams();
const { data, isLoading, error } = useQuery({
queryKey: ['recruitmentapplicationpage', applicationID],
queryFn: () => getRecruitmentApplicationsForRecruiter(applicationID as string),
});

useEffect(() => {
getRecruitmentApplicationsForRecruiter(applicationID as string)
.then((res) => {
setRecruitmentApplication(res.data.application);
setApplicant(res.data.user);
setOtherRecruitmentApplication(res.data.other_applications);
setLoading(false);
})
.catch((data) => {
if (data.request.status === STATUS.HTTP_404_NOT_FOUND) {
navigate(ROUTES.frontend.not_found, { replace: true });
}
toast.error(t(KEY.common_something_went_wrong));
});
}, [applicationID, t, navigate]);

const adminWithdraw = () => {
if (recruitmentApplication) {
if (window.confirm(t(KEY.recruitment_confirm_withdraw_application))) {
withdrawRecruitmentApplicationRecruiter(recruitmentApplication.id)
.then((response) => {
setRecruitmentApplication(response.data);
toast.success(t(KEY.common_update_successful));
})
.catch(() => {
toast.error(t(KEY.common_something_went_wrong));
});
}
if (error) {
if (data?.request.status === STATUS.HTTP_404_NOT_FOUND) {
navigate(ROUTES.frontend.not_found, { replace: true });
}
};
toast.error(t(KEY.common_something_went_wrong));
}

const recruitmentApplication = data?.data.application;
const applicant = data?.data.user;
const otherRecruitmentApplications = data?.data.other_applications;
const interviewNotes = recruitmentApplication?.interview?.notes;

const adminWithdraw = useMutation({
mutationFn: (id: string) => {
return withdrawRecruitmentApplicationRecruiter(id);
},
onSuccess: () => {
toast.success(t(KEY.common_update_successful));
},
});

if (loading) {
if (isLoading) {
return (
<div>
<SamfundetLogoSpinner />
</div>
);
}

const initialData: Partial<InterviewDto> = {
notes: interviewNotes || '',
};

return (
<AdminPage title={`${applicant?.first_name} ${applicant?.last_name}`}>
<div className={classNames(styles.infoContainer)}>
Expand Down Expand Up @@ -97,11 +90,22 @@ export function RecruitmentApplicantAdminPage() {
{t(KEY.recruitment_withdrawn)}
</Text>
) : (
<Button theme="samf" onClick={adminWithdraw}>
<Button
theme="samf"
onClick={() => {
if (recruitmentApplication?.id) {
adminWithdraw.mutate(recruitmentApplication.id);
}
}}
>
{t(KEY.recruitment_withdraw_application)}
</Button>
)}
</div>
<div className={classNames(styles.infoContainer)}>
<RecruitmentInterviewNotesForm initialData={initialData} />
</div>

<div className={classNames(styles.infoContainer)}>
<Text size="l" as="strong" className={styles.textBottom}>
{t(KEY.recruitment_all_applications)}
Expand All @@ -114,57 +118,61 @@ export function RecruitmentApplicantAdminPage() {
t(KEY.recruitment_recruiter_status),
t(KEY.recruitment_interview_time),
]}
data={otherRecruitmentApplication.map((element) => {
return {
cells: [
{
sortable: true,
content: (
<Link
target={'frontend'}
url={reverse({
pattern: ROUTES.frontend.admin_recruitment_applicant,
urlParams: {
applicationID: element.id,
},
})}
>
{element.applicant_priority}
</Link>
),
},
{
content: (
<Link
target={'frontend'}
url={reverse({
pattern: ROUTES.frontend.admin_recruitment_applicant,
urlParams: {
applicationID: element.id,
},
})}
>
{dbT(element.recruitment_position, 'name')}
</Link>
),
},
{
content: (
<Link
url={reverse({
pattern: ROUTES.frontend.information_page_detail,
urlParams: { slugField: element.recruitment_position.gang.name_nb.toLowerCase() },
})}
>
{dbT(element.recruitment_position.gang, 'name')}
</Link>
),
},
element.recruiter_priority ? element.recruiter_priority : t(KEY.common_not_set),
element.interview_time ? element.interview_time : t(KEY.common_not_set),
],
};
})}
data={
otherRecruitmentApplications
? otherRecruitmentApplications.map((element) => {
return {
cells: [
{
sortable: true,
content: (
<Link
target={'frontend'}
url={reverse({
pattern: ROUTES.frontend.admin_recruitment_applicant,
urlParams: {
applicationID: element.id,
},
})}
>
{element.applicant_priority}
</Link>
),
},
{
content: (
<Link
target={'frontend'}
url={reverse({
pattern: ROUTES.frontend.admin_recruitment_applicant,
urlParams: {
applicationID: element.id,
},
})}
>
{dbT(element.recruitment_position, 'name')}
</Link>
),
},
{
content: (
<Link
url={reverse({
pattern: ROUTES.frontend.information_page_detail,
urlParams: { slugField: element.recruitment_position.gang.name_nb.toLowerCase() },
})}
>
{dbT(element.recruitment_position.gang, 'name')}
</Link>
),
},
element.recruiter_priority ? element.recruiter_priority : t(KEY.common_not_set),
element.interview_time ? element.interview_time : t(KEY.common_not_set),
],
};
})
: []
}
/>
</div>
</AdminPage>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Textarea } from '~/Components';
import { KEY } from '~/i18n/constants';

const recruitmentNotesSchema = z.object({
notes: z.string(),
});

type RecruitmentInterviewNotesFormType = z.infer<typeof recruitmentNotesSchema>;

interface RecruitmentInterviewNotesFormProps {
initialData: Partial<RecruitmentInterviewNotesFormType>;
}

export function RecruitmentInterviewNotesForm({ initialData }: RecruitmentInterviewNotesFormProps) {
const { t } = useTranslation();

const form = useForm<RecruitmentInterviewNotesFormType>({
resolver: zodResolver(recruitmentNotesSchema),
defaultValues: initialData,
});

function handleUpdateNotes(value: string) {
// TODO: Update notes using a put request
console.log(value);
}

return (
<Form {...form}>
<form>
<div>
<FormField
control={form.control}
name="notes"
render={({ field }) => (
<FormItem>
<FormLabel>{t(KEY.recruitment_interview_notes)}</FormLabel>
<FormControl>
<Textarea
{...field}
onBlur={(newNotes) => {
field.onBlur(); // Call the default onBlur handler from react-hook-form
handleUpdateNotes(newNotes.target.value); // Call your custom function on blur
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</form>
</Form>
);
}
2 changes: 1 addition & 1 deletion frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ export async function getRecruitmentApplicationsForRecruiter(
pattern: ROUTES.backend.samfundet__recruitment_applications_recruiter,
urlParams: { applicationId: applicationID },
});
const response = await axios.get(url, { withCredentials: true });
const response = await axios.get<RecruitmentApplicationRecruiterDto>(url, { withCredentials: true });

return response;
}
Expand Down

0 comments on commit ba566b0

Please sign in to comment.