From 98ae7ad1009108a8cc5f688b75314c27eeb9664f Mon Sep 17 00:00:00 2001 From: nemisis84 Date: Sat, 21 Dec 2024 17:08:07 +0100 Subject: [PATCH 1/3] Removed unneccary linter changes --- backend/root/utils/routes.py | 1 + backend/samfundet/serializers.py | 3 ++ backend/samfundet/urls.py | 5 +++ backend/samfundet/views.py | 14 +++++++ .../RecruitmentApplicantAdminPage.tsx | 12 ++++-- .../RecruitmentInterviewNotesForm.tsx | 38 +++++++++++++++---- frontend/src/api.ts | 23 +++++------ 7 files changed, 73 insertions(+), 23 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index fc1b27776..bb5718847 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -596,6 +596,7 @@ samfundet__recruitment_application_update_state_gang = 'samfundet:recruitment_application_update_state_gang' samfundet__recruitment_application_update_state_position = 'samfundet:recruitment_application_update_state_position' samfundet__recruitment_applications_recruiter = 'samfundet:recruitment_applications_recruiter' +samfundet__recruitment_application_interview_notes = 'samfundet:recruitment_application_interview_notes' samfundet__recruitment_withdraw_application = 'samfundet:recruitment_withdraw_application' samfundet__recruitment_user_priority_update = 'samfundet:recruitment_user_priority_update' samfundet__recruitment_withdraw_application_recruiter = 'samfundet:recruitment_withdraw_application_recruiter' diff --git a/backend/samfundet/serializers.py b/backend/samfundet/serializers.py index d19f08ce7..6c4767952 100644 --- a/backend/samfundet/serializers.py +++ b/backend/samfundet/serializers.py @@ -1060,6 +1060,7 @@ class RecruitmentApplicationForRecruiterSerializer(serializers.ModelSerializer): recruitment_position = RecruitmentPositionForApplicantSerializer() recruiter_priority = serializers.CharField(source='get_recruiter_priority_display') interview_time = serializers.SerializerMethodField(method_name='get_interview_time', read_only=True) + interview = InterviewSerializer(read_only=True) class Meta: model = RecruitmentApplication @@ -1075,6 +1076,7 @@ class Meta: 'recruiter_priority', 'withdrawn', 'interview_time', + 'interview', 'created_at', ] read_only_fields = [ @@ -1089,6 +1091,7 @@ class Meta: 'applicant_state', 'interview_time', 'withdrawn', + 'interview', 'created_at', ] diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index 940ac346f..546dedc9b 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -116,6 +116,11 @@ views.RecruitmentApplicationForRecruitersView.as_view(), name='recruitment_applications_recruiter', ), + path( + 'recruitment-application-interview-notes//', + views.RecruitmentApplicationInterviewNotesView.as_view(), + name='recruitment_application_interview_notes', + ), path('recruitment-withdraw-application//', views.RecruitmentApplicationWithdrawApplicantView.as_view(), name='recruitment_withdraw_application'), path('recruitment-user-priority-update//', views.RecruitmentApplicationApplicantPriorityView.as_view(), name='recruitment_user_priority_update'), path( diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 59666f8aa..938014829 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -933,6 +933,20 @@ def list(self, request: Request) -> Response: return Response(serializer.data) +class RecruitmentApplicationInterviewNotesView(APIView): + permission_classes = [IsAuthenticated] + serializer_class = InterviewSerializer + + def put(self, request: Request, interview_id: str) -> Response: + interview = get_object_or_404(Interview, pk=interview_id) + update_serializer = self.serializer_class(interview, data=request.data, partial=True) + if update_serializer.is_valid() and 'notes' in update_serializer.validated_data: + interview.notes = update_serializer.validated_data['notes'] + interview.save() + return Response(status=status.HTTP_200_OK) + return Response(update_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + class RecruitmentApplicationWithdrawApplicantView(APIView): permission_classes = [IsAuthenticated] diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx index 1585bb699..f5a93ee23 100644 --- a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx @@ -37,6 +37,9 @@ export function RecruitmentApplicantAdminPage() { const applicant = data?.data.user; const otherRecruitmentApplications = data?.data.other_applications; const interviewNotes = recruitmentApplication?.interview?.notes; + const interviewId = recruitmentApplication?.interview?.id; + + console.log(recruitmentApplication); const adminWithdraw = useMutation({ mutationFn: (id: string) => { @@ -84,6 +87,11 @@ export function RecruitmentApplicantAdminPage() { {recruitmentApplication?.application_text} + {interviewId && ( +
+ +
+ )}
{recruitmentApplication?.withdrawn ? ( @@ -102,10 +110,6 @@ export function RecruitmentApplicantAdminPage() { )}
-
- -
-
{t(KEY.recruitment_all_applications)} diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx index 39b9de9d4..00c1a4497 100644 --- a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx @@ -1,32 +1,54 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; import { z } from 'zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Textarea } from '~/Components'; import { KEY } from '~/i18n/constants'; +import { putRecrutmentInterviewNotes } from '~/api'; +import { useMutation } from '@tanstack/react-query'; const recruitmentNotesSchema = z.object({ notes: z.string(), + interviewId: z.number(), }); type RecruitmentInterviewNotesFormType = z.infer; interface RecruitmentInterviewNotesFormProps { initialData: Partial; + interviewId?: number; } -export function RecruitmentInterviewNotesForm({ initialData }: RecruitmentInterviewNotesFormProps) { +export function RecruitmentInterviewNotesForm({ initialData, interviewId }: RecruitmentInterviewNotesFormProps) { const { t } = useTranslation(); - + const [currentNotes, setCurrentNotes] = useState(initialData.notes || ''); const form = useForm({ resolver: zodResolver(recruitmentNotesSchema), - defaultValues: initialData, + defaultValues: { + notes: initialData.notes || '', + interviewId: interviewId || 0, + }, + }); + + const handleUpdateNotes = useMutation({ + mutationFn: ({ notes, interviewId }: { notes: string; interviewId: number }) => + putRecrutmentInterviewNotes(notes, interviewId), + onSuccess: () => { + toast.success(t(KEY.common_update_successful)); + }, + onError: (error) => { + toast.error(t(KEY.common_something_went_wrong)); + }, }); - function handleUpdateNotes(value: string) { - // TODO: Update notes using a put request - console.log(value); - } + const handleNotesChange = (newNotes: string) => { + if (newNotes !== currentNotes && interviewId) { + setCurrentNotes(newNotes); + handleUpdateNotes.mutate({ notes: newNotes, interviewId }); + } + }; return (
@@ -43,7 +65,7 @@ export function RecruitmentInterviewNotesForm({ initialData }: RecruitmentInterv {...field} onBlur={(newNotes) => { field.onBlur(); // Call the default onBlur handler from react-hook-form - handleUpdateNotes(newNotes.target.value); // Call your custom function on blur + handleNotesChange(newNotes.target.value); // Call your custom function on blur }} /> diff --git a/frontend/src/api.ts b/frontend/src/api.ts index ed62fcd69..ca7668c80 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -37,10 +37,8 @@ import type { RecruitmentStatsDto, RecruitmentUnprocessedApplicationsDto, RecruitmentUserDto, - RecruitmentWriteDto, RegistrationDto, RoleDto, - RoleUsersDto, SaksdokumentDto, TextItemDto, UserDto, @@ -550,14 +548,14 @@ export async function getRecruitment(id: string): Promise { +export async function postRecruitment(recruitmentData: RecruitmentDto): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__recruitment_list; const response = await axios.post(url, recruitmentData, { withCredentials: true }); return response; } -export async function putRecruitment(id: string, recruitment: Partial): Promise { +export async function putRecruitment(id: string, recruitment: Partial): Promise { const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__recruitment_detail, urlParams: { pk: id } }); const response = await axios.put(url, recruitment, { withCredentials: true }); @@ -576,13 +574,6 @@ export async function getRecruitmentPositions(recruitmentId: string): Promise { - const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__role_users, urlParams: { pk: id } }); - const response = await axios.get(url, { withCredentials: true }); - - return response.data; -} - export async function getRecruitmentPositionsGangForApplicant( recruitmentId: number | string, gangId: number | string | undefined, @@ -978,6 +969,16 @@ export async function putRecruitmentApplication( return response; } +export async function putRecrutmentInterviewNotes(notes: string, interviewId: number): Promise { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__recruitment_application_interview_notes, + urlParams: { interviewId: interviewId }, + }); + return await axios.put(url, { notes: notes }, { withCredentials: true }); +} + export async function getRecruitmentApplicationForPosition( positionId: string, ): Promise> { From 2e33cbbd57da90ad2366c192bb2fc05582052803 Mon Sep 17 00:00:00 2001 From: nemisis84 Date: Sat, 21 Dec 2024 17:56:32 +0100 Subject: [PATCH 2/3] added final changes --- frontend/src/api.ts | 15 ++++++++++++--- frontend/src/routes/backend.ts | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index ca7668c80..49deb2a84 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -37,8 +37,10 @@ import type { RecruitmentStatsDto, RecruitmentUnprocessedApplicationsDto, RecruitmentUserDto, + RecruitmentWriteDto, RegistrationDto, RoleDto, + RoleUsersDto, SaksdokumentDto, TextItemDto, UserDto, @@ -548,14 +550,14 @@ export async function getRecruitment(id: string): Promise { +export async function postRecruitment(recruitmentData: RecruitmentWriteDto): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__recruitment_list; const response = await axios.post(url, recruitmentData, { withCredentials: true }); return response; } -export async function putRecruitment(id: string, recruitment: Partial): Promise { +export async function putRecruitment(id: string, recruitment: Partial): Promise { const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__recruitment_detail, urlParams: { pk: id } }); const response = await axios.put(url, recruitment, { withCredentials: true }); @@ -574,6 +576,13 @@ export async function getRecruitmentPositions(recruitmentId: string): Promise { + const url = BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__role_users, urlParams: { pk: id } }); + const response = await axios.get(url, { withCredentials: true }); + + return response.data; +} + export async function getRecruitmentPositionsGangForApplicant( recruitmentId: number | string, gangId: number | string | undefined, @@ -1117,4 +1126,4 @@ export async function getRecruitmentGangStats( }, }); return await axios.get(url, { withCredentials: true }); -} +} \ No newline at end of file diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index 85145b038..dedac6665 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -594,6 +594,7 @@ export const ROUTES_BACKEND = { samfundet__recruitment_application_update_state_gang: '/recruitment-application-update-state-gang/:pk/', samfundet__recruitment_application_update_state_position: '/recruitment-application-update-state-position/:pk/', samfundet__recruitment_applications_recruiter: '/recruitment-application-recruiter/:applicationId/', + samfundet__recruitment_application_interview_notes: '/recruitment-application-interview-notes/:interviewId/', samfundet__recruitment_withdraw_application: '/recruitment-withdraw-application/:pk/', samfundet__recruitment_user_priority_update: '/recruitment-user-priority-update/:pk/', samfundet__recruitment_withdraw_application_recruiter: '/recruitment-withdraw-application-recruiter/:pk/', From 45da5499a6cd5ddfd995704d98b49a4355eda1ca Mon Sep 17 00:00:00 2001 From: nemisis84 Date: Sat, 21 Dec 2024 18:10:25 +0100 Subject: [PATCH 3/3] Ran biome and removed console.log --- .../RecruitmentApplicantAdminPage.tsx | 2 -- .../RecruitmentInterviewNotesForm.tsx | 4 ++-- frontend/src/api.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx index f5a93ee23..be8e8d6fc 100644 --- a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx @@ -39,8 +39,6 @@ export function RecruitmentApplicantAdminPage() { const interviewNotes = recruitmentApplication?.interview?.notes; const interviewId = recruitmentApplication?.interview?.id; - console.log(recruitmentApplication); - const adminWithdraw = useMutation({ mutationFn: (id: string) => { return withdrawRecruitmentApplicationRecruiter(id); diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx index 00c1a4497..c9bb0f72b 100644 --- a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx @@ -1,13 +1,13 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useMutation } from '@tanstack/react-query'; import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import { z } from 'zod'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Textarea } from '~/Components'; -import { KEY } from '~/i18n/constants'; import { putRecrutmentInterviewNotes } from '~/api'; -import { useMutation } from '@tanstack/react-query'; +import { KEY } from '~/i18n/constants'; const recruitmentNotesSchema = z.object({ notes: z.string(), diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 49deb2a84..7b12fb0d8 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1126,4 +1126,4 @@ export async function getRecruitmentGangStats( }, }); return await axios.get(url, { withCredentials: true }); -} \ No newline at end of file +}