From afa4fa559c1b46f8ea70ca3226141d45f54bcf38 Mon Sep 17 00:00:00 2001 From: Lidavic <148764396+Lidavic@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:15:24 +0100 Subject: [PATCH 1/5] 1493 add search to users endpoint (#1609) * add ability to search for users at endpoint localhost:8000/users * reverted permissions --- backend/samfundet/utils.py | 9 +++++++++ backend/samfundet/views.py | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/samfundet/utils.py b/backend/samfundet/utils.py index 6755ad78d..6964f4003 100644 --- a/backend/samfundet/utils.py +++ b/backend/samfundet/utils.py @@ -42,6 +42,15 @@ def event_query(*, query: QueryDict, events: QuerySet[Event] = None) -> QuerySet return events +def user_query(*, query: QueryDict, users: QuerySet[User] = None) -> QuerySet[User]: + if not users: + users = User.objects.all() + search = query.get('search', None) + if search: + users = users.filter(Q(username__icontains=search) | Q(first_name__icontains=search) | Q(last_name__icontains=search)) + return users + + def generate_timeslots(start_time: datetime.time, end_time: datetime.time, interval_minutes: int) -> list[str]: # Convert from datetime.time objects to datetime.datetime start_datetime = datetime.datetime.combine(datetime.datetime.today(), start_time) diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index e4099a47c..4f3f8c509 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -39,7 +39,7 @@ REQUESTED_IMPERSONATE_USER, ) -from .utils import event_query, generate_timeslots, get_occupied_timeslots_from_request +from .utils import user_query, event_query, generate_timeslots, get_occupied_timeslots_from_request from .homepage import homepage from .models.role import Role, UserOrgRole, UserGangRole, UserGangSectionRole from .serializers import ( @@ -492,6 +492,10 @@ class AllUsersView(ListAPIView): serializer_class = UserSerializer queryset = User.objects.all() + def get(self, request: Request) -> Response: + users = user_query(query=request.query_params) + return Response(data=UserSerializer(users, many=True).data) + class ImpersonateView(APIView): permission_classes = [IsAuthenticated] # TODO: Permission check. From cc46c23548772b22f20e91e0722eaf625254d970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Snorre=20S=C3=A6ther?= <112980079+Snorre98@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:34:03 +0100 Subject: [PATCH 2/5] adds remote server for wsl in vscode (#1612) --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c1843a2c0..d93c29e5e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -14,6 +14,7 @@ "yzhang.markdown-all-in-one", "stylelint.vscode-stylelint", "ms-python.mypy-type-checker", - "visualstudioexptteam.vscodeintellicode" + "visualstudioexptteam.vscodeintellicode", + "ms-vscode-remote.vscode-remote-extensionpack" ] } From e47599636d54174f31f48360f589d53861cbfafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Snorre=20S=C3=A6ther?= <112980079+Snorre98@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:24:20 +0100 Subject: [PATCH 3/5] ability to fetch number of processed applications (#1591) * adds view and api call for fetching recruitment gang stat * fetches recruitment gang stat on page for users without interview * comments out for tsc * fixed view, and corrected translation * react query that b * removed comment --- backend/root/utils/routes.py | 2 ++ backend/samfundet/urls.py | 1 + backend/samfundet/views.py | 19 ++++++++++++ .../RejectionMail/RejectionMail.tsx | 4 +-- ...cruitmentUsersWithoutInterviewGangPage.tsx | 29 ++++++++++++++++--- frontend/src/api.ts | 18 +++++++----- frontend/src/dto.ts | 5 ++-- frontend/src/i18n/constants.ts | 2 ++ frontend/src/i18n/translations.ts | 4 +++ frontend/src/routes/backend.ts | 3 +- 10 files changed, 69 insertions(+), 18 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 2d1132ab8..30079e0fb 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -565,6 +565,7 @@ samfundet__interview_list = 'samfundet:interview-list' samfundet__interview_detail = 'samfundet:interview-detail' samfundet__api_root = 'samfundet:api-root' +samfundet__api_root = 'samfundet:api-root' samfundet__schema = 'samfundet:schema' samfundet__swagger_ui = 'samfundet:swagger_ui' samfundet__redoc = 'samfundet:redoc' @@ -608,5 +609,6 @@ samfundet__recruitment_availability = 'samfundet:recruitment_availability' samfundet__feedback = 'samfundet:feedback' samfundet__purchase_feedback = 'samfundet:purchase_feedback' +samfundet__gang_application_stats = 'samfundet:gang-application-stats' static__path = '' media__path = '' diff --git a/backend/samfundet/urls.py b/backend/samfundet/urls.py index 3761d75c3..0fab9f6d5 100644 --- a/backend/samfundet/urls.py +++ b/backend/samfundet/urls.py @@ -145,4 +145,5 @@ path('recruitment//availability/', views.RecruitmentAvailabilityView.as_view(), name='recruitment_availability'), path('feedback/', views.UserFeedbackView.as_view(), name='feedback'), path('purchase-feedback/', views.PurchaseFeedbackView.as_view(), name='purchase_feedback'), + path('recruitment//gang//stats/', views.GangApplicationCountView.as_view(), name='gang-application-stats'), ] diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 4f3f8c509..a566688b0 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -137,6 +137,7 @@ Recruitment, InterviewRoom, OccupiedTimeslot, + RecruitmentGangStat, RecruitmentPosition, RecruitmentStatistics, RecruitmentApplication, @@ -1366,3 +1367,21 @@ def post(self, request: Request) -> Response: form=purchase_model, ) return Response(status=status.HTTP_201_CREATED, data={'message': 'Feedback submitted successfully!'}) + + +class GangApplicationCountView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request: Request, recruitment_id: int, gang_id: int) -> Response: + # Get total applications from RecruitmentGangStat + gang_stat = get_object_or_404(RecruitmentGangStat, gang_id=gang_id, recruitment_stats__recruitment_id=recruitment_id) + + return Response( + { + 'total_applications': gang_stat.application_count, + 'total_applicants': gang_stat.applicant_count, + 'average_priority': gang_stat.average_priority, + 'total_accepted': gang_stat.total_accepted, + 'total_rejected': gang_stat.total_rejected, + } + ) diff --git a/frontend/src/Components/RejectionMail/RejectionMail.tsx b/frontend/src/Components/RejectionMail/RejectionMail.tsx index 7109290e1..a97215347 100644 --- a/frontend/src/Components/RejectionMail/RejectionMail.tsx +++ b/frontend/src/Components/RejectionMail/RejectionMail.tsx @@ -2,7 +2,7 @@ import { t } from 'i18next'; import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; -import { postRejectionMail } from '~/api'; +//import { postRejectionMail } from '~/api'; import { KEY } from '~/i18n/constants'; import { Button } from '../Button'; import { InputField } from '../InputField'; @@ -15,7 +15,7 @@ export function RejectionMail() { function handleSubmit() { if (recruitmentId) { - postRejectionMail(recruitmentId, { subject, text }); + //postRejectionMail(recruitmentId, { subject, text }); toast.success(t(KEY.common_save_successful)); } else { toast.error(t(KEY.common_something_went_wrong)); diff --git a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx index 3098a460e..719029554 100644 --- a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx @@ -1,10 +1,11 @@ +import { useQuery } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; import { RecruitmentWithoutInterviewTable } from '~/Components'; import { Text } from '~/Components/Text/Text'; -import { getApplicantsWithoutInterviews, getGang, getRecruitment } from '~/api'; +import { getApplicantsWithoutInterviews, getGang, getRecruitment, getRecruitmentGangStats } from '~/api'; import type { GangDto, RecruitmentDto, RecruitmentUserDto } from '~/dto'; import { useCustomNavigate, useTitle } from '~/hooks'; import { STATUS } from '~/http_status_codes'; @@ -18,6 +19,7 @@ export function RecruitmentUsersWithoutInterviewGangPage() { const { recruitmentId, gangId } = useParams(); const [users, setUsers] = useState([]); const [recruitment, setRecruitment] = useState(); + const [withoutInterviewCount, setWithoutInterviewCount] = useState(); const [gang, setGang] = useState(); const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); @@ -29,6 +31,7 @@ export function RecruitmentUsersWithoutInterviewGangPage() { getApplicantsWithoutInterviews(recruitmentId, gangId) .then((response) => { setUsers(response.data); + setWithoutInterviewCount(response.data.length); setShowSpinner(false); }) .catch((error) => { @@ -62,16 +65,21 @@ export function RecruitmentUsersWithoutInterviewGangPage() { setRecruitment(resp.data); }) .catch((data) => { - // TODO add error pop up message? if (data.request.status === STATUS.HTTP_404_NOT_FOUND) { + toast.error(t(KEY.common_something_went_wrong)); + console.error(data); navigate({ url: ROUTES.frontend.not_found, replace: true }); } - toast.error(t(KEY.common_something_went_wrong)); - console.error(data); }); } }, [recruitmentId]); + const { data: gangStats } = useQuery({ + queryKey: ['recruitmentGangStats', recruitmentId, gangId], + queryFn: () => getRecruitmentGangStats(recruitmentId as string, gangId as string), + enabled: Boolean(typeof recruitmentId === 'string' && typeof gangId === 'string'), + }); + const title = t(KEY.recruitment_applicants_without_interview); useTitle(title); const header = ( @@ -88,6 +96,19 @@ export function RecruitmentUsersWithoutInterviewGangPage() { ); return ( + {gangStats && ( + + {[ + withoutInterviewCount, + t(KEY.common_out_of), + t(KEY.common_total).toLowerCase(), + gangStats.data.total_applicants, + t(KEY.recruitment_applications), + t(KEY.common_have), + t(KEY.recruitment_interview), + ].join(' ')} + + )} ); diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 9a7997519..f959486cf 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -15,7 +15,6 @@ import type { InterviewDto, InterviewRoomDto, KeyValueDto, - MailDto, MenuDto, MenuItemDto, OccupiedTimeslotDto, @@ -29,6 +28,7 @@ import type { RecruitmentAvailabilityDto, RecruitmentDto, RecruitmentGangDto, + RecruitmentGangStatDto, RecruitmentPositionDto, RecruitmentPositionPostDto, RecruitmentPositionPutDto, @@ -1088,16 +1088,18 @@ export async function postFeedback(feedbackData: FeedbackDto): Promise { +export async function getRecruitmentGangStats( + recruitmentId: string, + gangId: string, +): Promise> { const url = BACKEND_DOMAIN + reverse({ - pattern: ROUTES.backend.samfundet__rejected_applicants, - queryParams: { - recruitment: recruitmentId, + pattern: ROUTES.backend.samfundet__gang_application_stats, + urlParams: { + recruitmentId: recruitmentId, + gangId: gangId, }, }); - const response = await axios.post(url, rejectionMail, { withCredentials: true }); - - return response; + return await axios.get(url, { withCredentials: true }); } diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 3a538bd24..c7e5fd1f4 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -556,9 +556,8 @@ export type RecruitmentCampusStatDto = { }; export type RecruitmentGangStatDto = { - gang: string; - application_count: number; - applicant_count: number; + total_applications: number; + total_applicants: number; average_priority: number; total_accepted: number; total_rejected: number; diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index a237bf674..e8642e9c8 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -124,6 +124,7 @@ export const KEY = { common_email: 'common_email', common_email_subject: 'common_email_subject', common_total: 'common_total', + common_out_of: 'common_out_of', common_roles: 'common_roles', common_role: 'common_role', common_guests: 'common_guests', @@ -169,6 +170,7 @@ export const KEY = { common_something_went_wrong: 'common_something_went_wrong', common_click_here: 'common_click_here', common_have: 'common_have', + recruitment_interview: 'recruitment_interview', common_been: 'common_been', common_processed: 'common_processed', common_rejected: 'common_rejected', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index 5e693cd43..7fb9b2861 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -150,6 +150,7 @@ export const nb = prepareTranslations({ [KEY.common_something_went_wrong]: 'Noe gikk galt', [KEY.common_click_here]: 'klikk her', [KEY.common_total]: 'Totalt', + [KEY.common_out_of]: 'av', [KEY.common_count]: 'Antall', [KEY.common_guests]: 'Gjester', [KEY.common_occasion]: 'Annledning', @@ -261,6 +262,7 @@ export const nb = prepareTranslations({ [KEY.recruitment_all_applications]: 'Alle søknader', [KEY.recruitment_not_applied]: 'Du har ikke sendt søknader til noen stillinger ennå', [KEY.recruitment_will_be_anonymized]: 'All info relatert til dine søknader vil bli slettet 3 uker etter opptaket', + [KEY.recruitment_interview]: 'intervju', [KEY.recruitment_interviews]: 'Intervjuer', [KEY.recruitment_interview_planned]: 'Intervjuer planlagt', [KEY.recruitment_interviewer]: 'Intervjuer', @@ -637,6 +639,7 @@ export const en = prepareTranslations({ [KEY.common_click_here]: 'click here', [KEY.common_total]: 'In total', [KEY.common_count]: 'Number of', + [KEY.common_out_of]: 'out of', [KEY.common_guests]: 'Guests', [KEY.common_occasion]: 'Occasion', [KEY.common_have]: 'have', @@ -747,6 +750,7 @@ export const en = prepareTranslations({ [KEY.recruitment_not_applied]: 'You have not applied to any positions yet', [KEY.recruitment_will_be_anonymized]: 'All info related to the applications will be anonymized three weeks after the recruitment is over', + [KEY.recruitment_interview]: 'interview', [KEY.recruitment_interviews]: 'Interviews', [KEY.recruitment_interview_planned]: 'Interviews planned', [KEY.recruitment_interviewer]: 'Interviewer', diff --git a/frontend/src/routes/backend.ts b/frontend/src/routes/backend.ts index cea34268e..b10b02f78 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -607,6 +607,7 @@ export const ROUTES_BACKEND = { samfundet__recruitment_availability: '/recruitment/:id/availability/', samfundet__feedback: '/feedback/', samfundet__purchase_feedback: '/purchase-feedback/', + samfundet__gang_application_stats: '/recruitment/:recruitmentId/gang/:gangId/stats/', static__path: '/static/:path', media__path: '/media/:path', -} as const; \ No newline at end of file +} as const; From ba566b071993dda43dc963a2b91477742cc2dc93 Mon Sep 17 00:00:00 2001 From: Simen Myrrusten <76472217+nemisis84@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:19:47 +0100 Subject: [PATCH 4/5] 1470 interview notes on recruitmentpositionoverviewpage (#1568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Update frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> * Made changes requested from review --------- Co-authored-by: Snorre Sæther <112980079+Snorre98@users.noreply.github.com> --- .../RecruitmentApplicantAdminPage.tsx | 188 +++++++++--------- .../RecruitmentInterviewNotesForm.tsx | 58 ++++++ frontend/src/api.ts | 2 +- 3 files changed, 157 insertions(+), 91 deletions(-) create mode 100644 frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx index 07b7abd8f..1585bb699 100644 --- a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentApplicantAdminPage.tsx @@ -1,5 +1,5 @@ +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'; @@ -7,7 +7,7 @@ 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'; @@ -15,50 +15,39 @@ 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(); - const [otherRecruitmentApplication, setOtherRecruitmentApplication] = useState([]); - const [applicant, setApplicant] = useState(); - - 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 (
@@ -66,6 +55,10 @@ export function RecruitmentApplicantAdminPage() { ); } + const initialData: Partial = { + notes: interviewNotes || '', + }; + return (
@@ -97,11 +90,22 @@ export function RecruitmentApplicantAdminPage() { {t(KEY.recruitment_withdrawn)} ) : ( - )}
+
+ +
+
{t(KEY.recruitment_all_applications)} @@ -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: ( - - {element.applicant_priority} - - ), - }, - { - content: ( - - {dbT(element.recruitment_position, 'name')} - - ), - }, - { - content: ( - - {dbT(element.recruitment_position.gang, 'name')} - - ), - }, - 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: ( + + {element.applicant_priority} + + ), + }, + { + content: ( + + {dbT(element.recruitment_position, 'name')} + + ), + }, + { + content: ( + + {dbT(element.recruitment_position.gang, 'name')} + + ), + }, + element.recruiter_priority ? element.recruiter_priority : t(KEY.common_not_set), + element.interview_time ? element.interview_time : t(KEY.common_not_set), + ], + }; + }) + : [] + } />
diff --git a/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx new file mode 100644 index 000000000..39b9de9d4 --- /dev/null +++ b/frontend/src/PagesAdmin/RecruitmentApplicantAdminPage/RecruitmentInterviewNotesForm.tsx @@ -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; + +interface RecruitmentInterviewNotesFormProps { + initialData: Partial; +} + +export function RecruitmentInterviewNotesForm({ initialData }: RecruitmentInterviewNotesFormProps) { + const { t } = useTranslation(); + + const form = useForm({ + resolver: zodResolver(recruitmentNotesSchema), + defaultValues: initialData, + }); + + function handleUpdateNotes(value: string) { + // TODO: Update notes using a put request + console.log(value); + } + + return ( +
+ +
+ ( + + {t(KEY.recruitment_interview_notes)} + +