Skip to content

Commit

Permalink
ability to fetch number of processed applications (#1591)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Snorre98 authored Nov 15, 2024
1 parent cc46c23 commit e475996
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 18 deletions.
2 changes: 2 additions & 0 deletions backend/root/utils/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 = ''
1 change: 1 addition & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,5 @@
path('recruitment/<int:id>/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/<int:recruitment_id>/gang/<int:gang_id>/stats/', views.GangApplicationCountView.as_view(), name='gang-application-stats'),
]
19 changes: 19 additions & 0 deletions backend/samfundet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
Recruitment,
InterviewRoom,
OccupiedTimeslot,
RecruitmentGangStat,
RecruitmentPosition,
RecruitmentStatistics,
RecruitmentApplication,
Expand Down Expand Up @@ -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,
}
)
4 changes: 2 additions & 2 deletions frontend/src/Components/RejectionMail/RejectionMail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -18,6 +19,7 @@ export function RecruitmentUsersWithoutInterviewGangPage() {
const { recruitmentId, gangId } = useParams();
const [users, setUsers] = useState<RecruitmentUserDto[]>([]);
const [recruitment, setRecruitment] = useState<RecruitmentDto>();
const [withoutInterviewCount, setWithoutInterviewCount] = useState<number>();
const [gang, setGang] = useState<GangDto>();
const [showSpinner, setShowSpinner] = useState<boolean>(true);
const { t } = useTranslation();
Expand All @@ -29,6 +31,7 @@ export function RecruitmentUsersWithoutInterviewGangPage() {
getApplicantsWithoutInterviews(recruitmentId, gangId)
.then((response) => {
setUsers(response.data);
setWithoutInterviewCount(response.data.length);
setShowSpinner(false);
})
.catch((error) => {
Expand Down Expand Up @@ -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 = (
Expand All @@ -88,6 +96,19 @@ export function RecruitmentUsersWithoutInterviewGangPage() {
);
return (
<AdminPageLayout title={title} backendUrl={ROUTES.backend.samfundet__user} header={header} loading={showSpinner}>
{gangStats && (
<Text size="l">
{[
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(' ')}
</Text>
)}
<RecruitmentWithoutInterviewTable applicants={users} />
</AdminPageLayout>
);
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type {
InterviewDto,
InterviewRoomDto,
KeyValueDto,
MailDto,
MenuDto,
MenuItemDto,
OccupiedTimeslotDto,
Expand All @@ -29,6 +28,7 @@ import type {
RecruitmentAvailabilityDto,
RecruitmentDto,
RecruitmentGangDto,
RecruitmentGangStatDto,
RecruitmentPositionDto,
RecruitmentPositionPostDto,
RecruitmentPositionPutDto,
Expand Down Expand Up @@ -1088,16 +1088,18 @@ export async function postFeedback(feedbackData: FeedbackDto): Promise<AxiosResp
return response;
}

export async function postRejectionMail(recruitmentId: string, rejectionMail: MailDto): Promise<AxiosResponse> {
export async function getRecruitmentGangStats(
recruitmentId: string,
gangId: string,
): Promise<AxiosResponse<RecruitmentGangStatDto>> {
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 });
}
5 changes: 2 additions & 3 deletions frontend/src/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/routes/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
} as const;

0 comments on commit e475996

Please sign in to comment.