From 60b1a6d1793dfc7b9c3470d26651c7269182561d Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Sat, 2 Nov 2024 05:58:54 +0100 Subject: [PATCH 1/6] adds view and api call for fetching recruitment gang stat --- backend/root/utils/routes.py | 2 ++ backend/samfundet/urls.py | 1 + backend/samfundet/views.py | 19 +++++++++++++++++++ frontend/src/api.ts | 18 ++++++++++-------- frontend/src/dto.ts | 5 ++--- frontend/src/routes/backend.ts | 3 ++- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/backend/root/utils/routes.py b/backend/root/utils/routes.py index 74cc0fc7e..a270e88ee 100644 --- a/backend/root/utils/routes.py +++ b/backend/root/utils/routes.py @@ -564,6 +564,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' @@ -607,5 +608,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 875f80229..47d2bf40c 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -133,6 +133,7 @@ Recruitment, InterviewRoom, OccupiedTimeslot, + RecruitmentGangStat, RecruitmentPosition, RecruitmentStatistics, RecruitmentApplication, @@ -1342,3 +1343,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, 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/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 4cd54cb1c..3b6bce135 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/routes/backend.ts b/frontend/src/routes/backend.ts index f1b8b9b81..33b17ec67 100644 --- a/frontend/src/routes/backend.ts +++ b/frontend/src/routes/backend.ts @@ -606,6 +606,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 a52a4cef915cd6d54f4aea23e4e5c5195269e90a Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Sat, 2 Nov 2024 05:59:30 +0100 Subject: [PATCH 2/6] fetches recruitment gang stat on page for users without interview --- ...cruitmentUsersWithoutInterviewGangPage.tsx | 28 +++++++++++++++---- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 ++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx index 3098a460e..2f2012790 100644 --- a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx @@ -4,8 +4,8 @@ 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 type { GangDto, RecruitmentDto, RecruitmentUserDto } from '~/dto'; +import { getApplicantsWithoutInterviews, getGang, getRecruitment, getRecruitmentGangStats } from '~/api'; +import type { GangDto, RecruitmentDto, RecruitmentGangStatDto, RecruitmentUserDto } from '~/dto'; import { useCustomNavigate, useTitle } from '~/hooks'; import { STATUS } from '~/http_status_codes'; import { KEY } from '~/i18n/constants'; @@ -18,7 +18,9 @@ export function RecruitmentUsersWithoutInterviewGangPage() { const { recruitmentId, gangId } = useParams(); const [users, setUsers] = useState([]); const [recruitment, setRecruitment] = useState(); + const [withoutInterviewCount, setWithoutInterviewCount] = useState(); const [gang, setGang] = useState(); + const [recruitmentGangStat, setRecruitmentGangStat] = useState(); const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); const navigate = useCustomNavigate(); @@ -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,26 @@ 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]); + useEffect(() => { + if (!recruitmentId || !gangId) return; + getRecruitmentGangStats(recruitmentId, gangId) + .then((response) => { + setRecruitmentGangStat(response.data); + }) + .catch((error) => { + console.error(error); + }); + }, [recruitmentId, gangId]); + const title = t(KEY.recruitment_applicants_without_interview); useTitle(title); const header = ( @@ -88,6 +101,11 @@ export function RecruitmentUsersWithoutInterviewGangPage() { ); return ( + {recruitmentGangStat && ( + + {`${withoutInterviewCount} ${t(KEY.common_out_of)} ${t(KEY.common_total).toLowerCase()} ${recruitmentGangStat.total_applicants} ${t(KEY.recruitment_applicants_without_interview).toLowerCase()}`} + + )} ); diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index 6f8a67974..dec220820 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', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index 1e3a3b20e..5c7784d6a 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', @@ -636,6 +637,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', From 39a96242559485add3569b995ef66225f805476d Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Sat, 2 Nov 2024 06:10:11 +0100 Subject: [PATCH 3/6] comments out for tsc --- frontend/src/Components/RejectionMail/RejectionMail.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)); From b7e0fe6f36ef74a8ad400518597a7ea71aef5e11 Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Sat, 2 Nov 2024 06:53:13 +0100 Subject: [PATCH 4/6] fixed view, and corrected translation --- backend/samfundet/views.py | 2 +- .../RecruitmentUsersWithoutInterviewGangPage.tsx | 9 ++++++++- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/samfundet/views.py b/backend/samfundet/views.py index 47d2bf40c..514b4431c 100644 --- a/backend/samfundet/views.py +++ b/backend/samfundet/views.py @@ -1348,7 +1348,7 @@ def post(self, request: Request) -> Response: class GangApplicationCountView(APIView): permission_classes = [IsAuthenticated] - def get(self, recruitment_id: int, gang_id: int) -> Response: + 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) diff --git a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx index 2f2012790..503d36277 100644 --- a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx @@ -103,7 +103,14 @@ export function RecruitmentUsersWithoutInterviewGangPage() { {recruitmentGangStat && ( - {`${withoutInterviewCount} ${t(KEY.common_out_of)} ${t(KEY.common_total).toLowerCase()} ${recruitmentGangStat.total_applicants} ${t(KEY.recruitment_applicants_without_interview).toLowerCase()}`} + {[ + withoutInterviewCount, + t(KEY.common_out_of), + `${t(KEY.common_total).toLowerCase()} ${recruitmentGangStat.total_applicants}`, + t(KEY.recruitment_applications), + t(KEY.common_have), + t(KEY.recruitment_interview), + ].join(' ')} )} diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index dec220820..075df6b93 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -170,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 5c7784d6a..752e38960 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -262,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_interviewer]: 'Intervjuer', [KEY.recruitment_interviewers]: 'Intervjuere', @@ -748,6 +749,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_interviewer]: 'Interviewer', [KEY.recruitment_interviewers]: 'Interviewers', From 03693ee2dfecd7649419460d11ccc437c16b4ee0 Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Thu, 7 Nov 2024 22:55:02 +0100 Subject: [PATCH 5/6] react query that b --- ...cruitmentUsersWithoutInterviewGangPage.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx index 503d36277..2ec16e5c3 100644 --- a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx @@ -1,3 +1,4 @@ +import { useQuery } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; @@ -5,7 +6,7 @@ import { toast } from 'react-toastify'; import { RecruitmentWithoutInterviewTable } from '~/Components'; import { Text } from '~/Components/Text/Text'; import { getApplicantsWithoutInterviews, getGang, getRecruitment, getRecruitmentGangStats } from '~/api'; -import type { GangDto, RecruitmentDto, RecruitmentGangStatDto, RecruitmentUserDto } from '~/dto'; +import type { GangDto, RecruitmentDto, RecruitmentUserDto } from '~/dto'; import { useCustomNavigate, useTitle } from '~/hooks'; import { STATUS } from '~/http_status_codes'; import { KEY } from '~/i18n/constants'; @@ -18,9 +19,9 @@ export function RecruitmentUsersWithoutInterviewGangPage() { const { recruitmentId, gangId } = useParams(); const [users, setUsers] = useState([]); const [recruitment, setRecruitment] = useState(); + //const [gangStats, setGangStats] = useState(); const [withoutInterviewCount, setWithoutInterviewCount] = useState(); const [gang, setGang] = useState(); - const [recruitmentGangStat, setRecruitmentGangStat] = useState(); const [showSpinner, setShowSpinner] = useState(true); const { t } = useTranslation(); const navigate = useCustomNavigate(); @@ -74,16 +75,11 @@ export function RecruitmentUsersWithoutInterviewGangPage() { } }, [recruitmentId]); - useEffect(() => { - if (!recruitmentId || !gangId) return; - getRecruitmentGangStats(recruitmentId, gangId) - .then((response) => { - setRecruitmentGangStat(response.data); - }) - .catch((error) => { - console.error(error); - }); - }, [recruitmentId, gangId]); + 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); @@ -101,12 +97,13 @@ export function RecruitmentUsersWithoutInterviewGangPage() { ); return ( - {recruitmentGangStat && ( + {gangStats && ( {[ withoutInterviewCount, t(KEY.common_out_of), - `${t(KEY.common_total).toLowerCase()} ${recruitmentGangStat.total_applicants}`, + t(KEY.common_total).toLowerCase(), + gangStats.data.total_applicants, t(KEY.recruitment_applications), t(KEY.common_have), t(KEY.recruitment_interview), From d43b5b7e1027384e6a8fe2b029e2979e1ab2291a Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Thu, 7 Nov 2024 23:04:39 +0100 Subject: [PATCH 6/6] removed comment --- .../RecruitmentUsersWithoutInterviewGangPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx index 2ec16e5c3..719029554 100644 --- a/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentUsersWithoutInterviewGangPage/RecruitmentUsersWithoutInterviewGangPage.tsx @@ -19,7 +19,6 @@ export function RecruitmentUsersWithoutInterviewGangPage() { const { recruitmentId, gangId } = useParams(); const [users, setUsers] = useState([]); const [recruitment, setRecruitment] = useState(); - //const [gangStats, setGangStats] = useState(); const [withoutInterviewCount, setWithoutInterviewCount] = useState(); const [gang, setGang] = useState(); const [showSpinner, setShowSpinner] = useState(true);