From b24115722a0adf5b30b2eb96eda907c7b73efd9c Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Thu, 19 Sep 2024 19:08:32 +0200 Subject: [PATCH 1/7] Create page for room overview --- .../RecruitmentGangOverviewPage.tsx | 12 ++++++++++++ .../src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx | 3 +++ frontend/src/PagesAdmin/RoomAdminPage/index.ts | 1 + frontend/src/PagesAdmin/index.ts | 3 ++- frontend/src/i18n/constants.ts | 1 + frontend/src/i18n/translations.ts | 3 +++ frontend/src/router/router.tsx | 3 +++ frontend/src/routes/frontend.ts | 2 ++ 8 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx create mode 100644 frontend/src/PagesAdmin/RoomAdminPage/index.ts diff --git a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx index f16d4ddb1..cd547a9a1 100644 --- a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx @@ -102,6 +102,18 @@ export function RecruitmentGangOverviewPage() { > {t(KEY.common_edit)} + + {recruitmentId && } ); diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx new file mode 100644 index 000000000..54abd2178 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -0,0 +1,3 @@ +export function RoomAdminPage() { + return
Gotcha bitch
; +} diff --git a/frontend/src/PagesAdmin/RoomAdminPage/index.ts b/frontend/src/PagesAdmin/RoomAdminPage/index.ts new file mode 100644 index 000000000..f56127c25 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/index.ts @@ -0,0 +1 @@ +export { RoomAdminPage } from './RoomAdminPage'; diff --git a/frontend/src/PagesAdmin/index.ts b/frontend/src/PagesAdmin/index.ts index e084ff490..9a39d6a41 100644 --- a/frontend/src/PagesAdmin/index.ts +++ b/frontend/src/PagesAdmin/index.ts @@ -22,13 +22,14 @@ export { RecruitmentGangOverviewPage } from './RecruitmentGangOverviewPage'; export { RecruitmentOverviewPage } from './RecruitmentOverviewPage'; export { RecruitmentPositionFormAdminPage } from './RecruitmentPositionFormAdminPage'; export { RecruitmentPositionOverviewPage } from './RecruitmentPositionOverviewPage'; +export { RecruitmentRecruiterDashboardPage } from './RecruitmentRecruiterDashboardPage'; export { RecruitmentUnprocessedApplicantsPage } from './RecruitmentUnprocessedApplicantsPage'; export { RecruitmentUsersWithoutInterviewGangPage } from './RecruitmentUsersWithoutInterviewGangPage'; export { RecruitmentUsersWithoutThreeInterviewCriteriaPage } from './RecruitmentUsersWithoutThreeInterviewCriteriaPage'; +export { RoomAdminPage } from './RoomAdminPage'; export { SaksdokumentAdminPage } from './SaksdokumentAdminPage'; export { SaksdokumentFormAdminPage } from './SaksdokumentFormAdminPage'; export { SultenMenuAdminPage } from './SultenMenuAdminPage'; export { SultenMenuItemFormAdminPage } from './SultenMenuItemFormAdminPage'; export { SultenReservationAdminPage } from './SultenReservationAdminPage'; export { UsersAdminPage } from './UsersAdminPage'; -export { RecruitmentRecruiterDashboardPage } from './RecruitmentRecruiterDashboardPage'; diff --git a/frontend/src/i18n/constants.ts b/frontend/src/i18n/constants.ts index 319af276e..c910e291c 100644 --- a/frontend/src/i18n/constants.ts +++ b/frontend/src/i18n/constants.ts @@ -293,6 +293,7 @@ export const KEY = { recruitment_all_applications: 'recruitment_all_applications', recruitment_not_applied: 'recruitment_not_applied', recruitment_will_be_anonymized: 'recruitment_will_be_anonymized', + recruitment_create_room: 'recruitment_create_room', shown_application_deadline: 'shown_application_deadline', actual_application_deadline: 'actual_application_deadline', recruitment_number_of_applications: 'recruitment_number_of_applications', diff --git a/frontend/src/i18n/translations.ts b/frontend/src/i18n/translations.ts index e2896f8e2..8b9c7dccb 100644 --- a/frontend/src/i18n/translations.ts +++ b/frontend/src/i18n/translations.ts @@ -357,6 +357,7 @@ export const nb = prepareTranslations({ [KEY.admin_information_confirm_delete]: 'Er du sikker du vil slette denne informasjonssiden?', [KEY.admin_information_confirm_cancel]: 'Er du sikker på at du vil gå tilbake uten å lagre?', [KEY.admin_gangsadminpage_abbreviation]: 'Forkortelse', + [KEY.recruitment_create_room]: 'Opprett rom', // CommandMenu: [KEY.command_menu_label]: 'Global kommando meny', @@ -757,6 +758,8 @@ export const en = prepareTranslations({ [KEY.error_recruitment_form_4]: 'Group reprioritization deadline cannot be before the reprioritization deadline', [KEY.recruitment_dashboard_description]: 'Here you have an overview of your job as a recruiter for the recruitment, here you can see your upcomming interviews, the positions you have a responsibility for, and setting the time you are available to host an interview', + [KEY.recruitment_create_room]: 'Create room', + // Admin: [KEY.admin_organizer]: 'Organizer', [KEY.admin_saksdokument]: 'Case document', diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 0f86b5696..85afc2145 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -70,6 +70,7 @@ import { ROUTES } from '~/routes'; import { t } from 'i18next'; import { App } from '~/App'; import { RecruitmentRecruiterDashboardPage } from '~/PagesAdmin/RecruitmentRecruiterDashboardPage/RecruitmentRecruiterDashboardPage'; +import { RoomAdminPage } from '~/PagesAdmin/RoomAdminPage/RoomAdminPage'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { @@ -433,6 +434,7 @@ export const router = createBrowserRouter( }, }} /> + } @@ -453,6 +455,7 @@ export const router = createBrowserRouter( }, }} /> + } /> } />} diff --git a/frontend/src/routes/frontend.ts b/frontend/src/routes/frontend.ts index 0e1363341..9fda40fb7 100644 --- a/frontend/src/routes/frontend.ts +++ b/frontend/src/routes/frontend.ts @@ -80,6 +80,8 @@ export const ROUTES_FRONTEND = { admin_recruitment_gang_position_create: '/control-panel/recruitment/:recruitmentId/gang/:gangId/create/', admin_recruitment_gang_position_edit: '/control-panel/recruitment/:recruitmentId/gang/:gangId/edit/:positionId', admin_recruitment_recruiter_dashboard: '/control-panel/recruitment/:recruitmentId/recruiter/dashboard/', + admin_recruitment_room_create: '/control-panel/recruitment/:recruitmentId/room/create/', + admin_recruitment_room_edit: '/control-panel/recruitment/:recruitmentId/room/edit/:roomId/', admin_recruitment_gang_position_applicants_overview: '/control-panel/recruitment/:recruitmentId/gang/:gangId/position/:positionId', From 9c0b523282b391ee73b771527f128716f6a45648 Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Thu, 19 Sep 2024 19:23:40 +0200 Subject: [PATCH 2/7] Add table of interview rooms --- .../RoomAdminPage/RoomAdminPage.tsx | 42 ++++++++++++++++++- frontend/src/api.ts | 13 ++++++ frontend/src/dto.ts | 9 ++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx index 54abd2178..34ac9d186 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -1,3 +1,43 @@ +import { useEffect, useState } from 'react'; +import { useRouteLoaderData } from 'react-router-dom'; +import { Table } from '~/Components'; +import { getInterviewRoomsForRecruitment } from '~/api'; +import type { InterviewRoomDto } from '~/dto'; +import type { RecruitmentLoader } from '~/router/loaders'; + export function RoomAdminPage() { - return
Gotcha bitch
; + const [interviewRooms, setInterviewRooms] = useState(); + const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; + + useEffect(() => { + if (data?.recruitment?.id) { + getInterviewRoomsForRecruitment(data.recruitment.id.toString()).then((response) => + setInterviewRooms(response.data), + ); + } + }, [data?.recruitment?.id]); + + if (!interviewRooms) { + return

No rooms found

; + } + + const columns = [ + { content: 'Room Name', sortable: true }, + { content: 'Location', sortable: true }, + { content: 'Start Time', sortable: true }, + { content: 'End Time', sortable: true }, + { content: 'Recruitment', sortable: true }, + { content: 'Gang', sortable: true }, + ]; + + const tableData = interviewRooms.map((room) => [ + room.name, + room.location, + new Date(room.start_time), + new Date(room.end_time), + room.recruitment, + room.gang !== undefined ? room.gang : 'N/A', + ]); + + return ; } diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 0723bf81e..bce7d8efc 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -13,6 +13,7 @@ import type { ImagePostDto, InformationPageDto, InterviewDto, + InterviewRoomDto, KeyValueDto, MenuDto, MenuItemDto, @@ -893,6 +894,18 @@ export async function putRecruitmentApplicationInterview( const response = await axios.put(url, interview, { withCredentials: true }); return response; } + + +export async function getInterviewRoomsForRecruitment(recruitmentId: string): Promise> { + const url = + BACKEND_DOMAIN + + reverse({ + pattern: ROUTES.backend.samfundet__interview_rooms_list, + queryParams: { recruitment: recruitmentId }, + }); + return await axios.get(url, { withCredentials: true }); +} + // ############################################################ // Purchase Feedback // ############################################################ diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 4e164a43d..3ba934c98 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -485,6 +485,15 @@ export type RecruitmentStatsDto = { campus_stats: RecruitmentCampusStatDto[]; }; +export type InterviewRoomDto = { + name: string; + location: string; + start_time: string; + end_time: string; + recruitment: number; + gang?: number; +} + // ############################################################ // Purchase Feedback // ############################################################ From da10257c1b15a70fd72b64d1c36f680a66005872 Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Thu, 19 Sep 2024 20:24:13 +0200 Subject: [PATCH 3/7] Create page for creating rooms --- .../CreateInterviewRoomPage.tsx | 41 +++++++++++++++++++ .../CreateInterviewRoomPage/index.ts | 1 + .../RoomAdminPage/RoomAdminPage.tsx | 14 ++++++- .../src/PagesAdmin/RoomAdminPage/index.ts | 1 + frontend/src/PagesAdmin/index.ts | 2 +- frontend/src/api.ts | 12 +++++- frontend/src/router/router.tsx | 6 ++- frontend/src/routes/frontend.ts | 1 + 8 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx create mode 100644 frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts diff --git a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx new file mode 100644 index 000000000..1f9d4e67e --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx @@ -0,0 +1,41 @@ +import { useRouteLoaderData } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { SamfForm } from '~/Forms/SamfForm'; +import { SamfFormField } from '~/Forms/SamfFormField'; +import { postInterviewRoom } from '~/api'; +import type { InterviewRoomDto } from '~/dto'; +import { useCustomNavigate } from '~/hooks'; +import type { RecruitmentLoader } from '~/router/loaders'; +import { ROUTES } from '~/routes'; + +export function CreateInterviewRoomPage() { + const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; + const navigation = useCustomNavigate(); + if (!data?.recruitment?.id) { + return

No recruitment id found

; + } + + const initialData: Partial = { + recruitment: Number(data.recruitment.id), + }; + // ROUTES.frontend.admin_recruitment_room_overview, { recruitmentId: data.recruitment } + function handleSubmit(data: InterviewRoomDto) { + try { + postInterviewRoom(data).then(() => { + toast.success('Interview room created'); + navigation({ url: ROUTES.frontend.admin_recruitment_room_overview }); + }); + } catch (error) { + toast.error('Failed to create interview room'); + } + } + + return ( + initialData={initialData} onSubmit={handleSubmit}> + + + + + + ); +} diff --git a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts new file mode 100644 index 000000000..884880e21 --- /dev/null +++ b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/index.ts @@ -0,0 +1 @@ +export { CreateInterviewRoomPage } from './CreateInterviewRoomPage'; diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx index 34ac9d186..249829b5e 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -1,13 +1,16 @@ import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useRouteLoaderData } from 'react-router-dom'; -import { Table } from '~/Components'; +import { Button, Table } from '~/Components'; import { getInterviewRoomsForRecruitment } from '~/api'; import type { InterviewRoomDto } from '~/dto'; +import { KEY } from '~/i18n/constants'; import type { RecruitmentLoader } from '~/router/loaders'; export function RoomAdminPage() { const [interviewRooms, setInterviewRooms] = useState(); const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; + const { t } = useTranslation(); useEffect(() => { if (data?.recruitment?.id) { @@ -39,5 +42,12 @@ export function RoomAdminPage() { room.gang !== undefined ? room.gang : 'N/A', ]); - return
; + return ( + <> + +
+ + ); } diff --git a/frontend/src/PagesAdmin/RoomAdminPage/index.ts b/frontend/src/PagesAdmin/RoomAdminPage/index.ts index f56127c25..6ee984d91 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/index.ts +++ b/frontend/src/PagesAdmin/RoomAdminPage/index.ts @@ -1 +1,2 @@ +export { CreateInterviewRoomPage } from './CreateInterviewRoomPage'; export { RoomAdminPage } from './RoomAdminPage'; diff --git a/frontend/src/PagesAdmin/index.ts b/frontend/src/PagesAdmin/index.ts index 9a39d6a41..9cb5f8bda 100644 --- a/frontend/src/PagesAdmin/index.ts +++ b/frontend/src/PagesAdmin/index.ts @@ -26,7 +26,7 @@ export { RecruitmentRecruiterDashboardPage } from './RecruitmentRecruiterDashboa export { RecruitmentUnprocessedApplicantsPage } from './RecruitmentUnprocessedApplicantsPage'; export { RecruitmentUsersWithoutInterviewGangPage } from './RecruitmentUsersWithoutInterviewGangPage'; export { RecruitmentUsersWithoutThreeInterviewCriteriaPage } from './RecruitmentUsersWithoutThreeInterviewCriteriaPage'; -export { RoomAdminPage } from './RoomAdminPage'; +export { CreateInterviewRoomPage, RoomAdminPage } from './RoomAdminPage'; export { SaksdokumentAdminPage } from './SaksdokumentAdminPage'; export { SaksdokumentFormAdminPage } from './SaksdokumentFormAdminPage'; export { SultenMenuAdminPage } from './SultenMenuAdminPage'; diff --git a/frontend/src/api.ts b/frontend/src/api.ts index bce7d8efc..aa5ad992a 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -895,8 +895,13 @@ export async function putRecruitmentApplicationInterview( return response; } +// ############################################################ +// Interview rooms +// ############################################################ -export async function getInterviewRoomsForRecruitment(recruitmentId: string): Promise> { +export async function getInterviewRoomsForRecruitment( + recruitmentId: string, +): Promise> { const url = BACKEND_DOMAIN + reverse({ @@ -906,6 +911,11 @@ export async function getInterviewRoomsForRecruitment(recruitmentId: string): Pr return await axios.get(url, { withCredentials: true }); } +export async function postInterviewRoom(data: Partial): Promise { + const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__interview_rooms_list; + return await axios.post(url, data, { withCredentials: true }); +} + // ############################################################ // Purchase Feedback // ############################################################ diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 85afc2145..1c6491339 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -34,6 +34,7 @@ import { AdminLayout, ClosedPeriodAdminPage, ClosedPeriodFormAdminPage, + CreateInterviewRoomPage, EventCreatorAdminPage, EventsAdminPage, GangsAdminPage, @@ -57,6 +58,7 @@ import { RecruitmentUnprocessedApplicantsPage, RecruitmentUsersWithoutInterviewGangPage, RecruitmentUsersWithoutThreeInterviewCriteriaPage, + RoomAdminPage, SaksdokumentAdminPage, SaksdokumentFormAdminPage, SultenMenuAdminPage, @@ -70,7 +72,6 @@ import { ROUTES } from '~/routes'; import { t } from 'i18next'; import { App } from '~/App'; import { RecruitmentRecruiterDashboardPage } from '~/PagesAdmin/RecruitmentRecruiterDashboardPage/RecruitmentRecruiterDashboardPage'; -import { RoomAdminPage } from '~/PagesAdmin/RoomAdminPage/RoomAdminPage'; import { KEY } from '~/i18n/constants'; import { reverse } from '~/named-urls'; import { @@ -455,7 +456,8 @@ export const router = createBrowserRouter( }, }} /> - } /> + } /> + } /> } />} diff --git a/frontend/src/routes/frontend.ts b/frontend/src/routes/frontend.ts index 9fda40fb7..2ca74f0fb 100644 --- a/frontend/src/routes/frontend.ts +++ b/frontend/src/routes/frontend.ts @@ -80,6 +80,7 @@ export const ROUTES_FRONTEND = { admin_recruitment_gang_position_create: '/control-panel/recruitment/:recruitmentId/gang/:gangId/create/', admin_recruitment_gang_position_edit: '/control-panel/recruitment/:recruitmentId/gang/:gangId/edit/:positionId', admin_recruitment_recruiter_dashboard: '/control-panel/recruitment/:recruitmentId/recruiter/dashboard/', + admin_recruitment_room_overview: '/control-panel/recruitment/:recruitmentId/room-overview/', admin_recruitment_room_create: '/control-panel/recruitment/:recruitmentId/room/create/', admin_recruitment_room_edit: '/control-panel/recruitment/:recruitmentId/room/edit/:roomId/', From 465a37fc32ec290f343936d0db45581ae464ecdb Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Thu, 19 Sep 2024 22:23:31 +0200 Subject: [PATCH 4/7] Add edit and delete room functionality --- .../RecruitmentGangOverviewPage.tsx | 17 +- .../CreateInterviewRoomPage.tsx | 155 +++++++++++++++--- .../RoomAdminPage/RoomAdminPage.tsx | 41 ++++- frontend/src/api.ts | 18 ++ frontend/src/dto.ts | 5 +- frontend/src/router/router.tsx | 1 + 6 files changed, 197 insertions(+), 40 deletions(-) diff --git a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx index cd547a9a1..b3824d6a0 100644 --- a/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx +++ b/frontend/src/PagesAdmin/RecruitmentGangOverviewPage/RecruitmentGangOverviewPage.tsx @@ -102,18 +102,17 @@ export function RecruitmentGangOverviewPage() { > {t(KEY.common_edit)} - - + {recruitmentId && } ); diff --git a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx index 1f9d4e67e..f2c90feda 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx +++ b/frontend/src/PagesAdmin/RoomAdminPage/CreateInterviewRoomPage/CreateInterviewRoomPage.tsx @@ -1,41 +1,146 @@ -import { useRouteLoaderData } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useParams } from 'react-router-dom'; import { toast } from 'react-toastify'; +import { SamfundetLogoSpinner } from '~/Components'; import { SamfForm } from '~/Forms/SamfForm'; import { SamfFormField } from '~/Forms/SamfFormField'; -import { postInterviewRoom } from '~/api'; +import { AdminPageLayout } from '~/PagesAdmin/AdminPageLayout/AdminPageLayout'; +import { getInterviewRoom, postInterviewRoom, putInterviewRoom } from '~/api'; import type { InterviewRoomDto } from '~/dto'; -import { useCustomNavigate } from '~/hooks'; -import type { RecruitmentLoader } from '~/router/loaders'; +import { STATUS } from '~/http_status_codes'; +import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; import { ROUTES } from '~/routes'; +type FormType = { + name: string; + location: string; + start_time: string; + end_time: string; +}; + export function CreateInterviewRoomPage() { - const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; - const navigation = useCustomNavigate(); - if (!data?.recruitment?.id) { - return

No recruitment id found

; - } + const { t } = useTranslation(); + const navigate = useNavigate(); + + const { recruitmentId, roomId } = useParams(); + const [showSpinner, setShowSpinner] = useState(true); + const [room, setRoom] = useState>(); + + useEffect(() => { + if (roomId) { + getInterviewRoom(roomId) + .then((data) => { + setRoom(data.data); + setShowSpinner(false); + }) + .catch((data) => { + if (data.request.status === STATUS.HTTP_404_NOT_FOUND) { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + { replace: true }, + ); + } + toast.error(t(KEY.common_something_went_wrong)); + }); + } else { + setShowSpinner(false); + } + }, [roomId, recruitmentId, navigate, t]); const initialData: Partial = { - recruitment: Number(data.recruitment.id), + name: room?.name, + location: room?.location, + start_time: room?.start_time, + end_time: room?.end_time, }; - // ROUTES.frontend.admin_recruitment_room_overview, { recruitmentId: data.recruitment } - function handleSubmit(data: InterviewRoomDto) { - try { - postInterviewRoom(data).then(() => { - toast.success('Interview room created'); - navigation({ url: ROUTES.frontend.admin_recruitment_room_overview }); - }); - } catch (error) { - toast.error('Failed to create interview room'); + + const submitText = roomId ? t(KEY.common_save) : t(KEY.common_create); + + if (showSpinner) { + return ( +
+ +
+ ); + } + + function handleOnSubmit(data: InterviewRoomDto) { + const updatedRoom = { + ...data, + recruitment: recruitmentId, + }; + + if (roomId) { + putInterviewRoom(roomId, updatedRoom) + .then(() => { + toast.success(t(KEY.common_update_successful)); + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); + } else { + postInterviewRoom(updatedRoom) + .then(() => { + navigate( + reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_overview, + urlParams: { recruitmentId: recruitmentId }, + }), + ); + toast.success(t(KEY.common_creation_successful)); + }) + .catch((error) => { + toast.error(t(KEY.common_something_went_wrong)); + console.error(error); + }); } } return ( - initialData={initialData} onSubmit={handleSubmit}> - - - - - + +
+ onSubmit={handleOnSubmit} initialData={initialData} submitText={submitText}> +
+ field="name" type="text" label={t(KEY.common_name)} required={true} /> +
+
+ + field="location" + type="text" + label={t(KEY.recruitment_interview_location)} + required={true} + /> +
+
+ + field="start_time" + type="date_time" + label={t(KEY.start_time)} + required={true} + /> +
+
+ + field="end_time" + type="date_time" + label={t(KEY.end_time)} + required={true} + /> +
+ +
+
); } diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx index 249829b5e..e3773ad99 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -1,15 +1,20 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useRouteLoaderData } from 'react-router-dom'; -import { Button, Table } from '~/Components'; -import { getInterviewRoomsForRecruitment } from '~/api'; +import { toast } from 'react-toastify'; +import { Button, CrudButtons, Table } from '~/Components'; +import { deleteInterviewRoom, getInterviewRoomsForRecruitment } from '~/api'; import type { InterviewRoomDto } from '~/dto'; +import { useCustomNavigate } from '~/hooks'; import { KEY } from '~/i18n/constants'; +import { reverse } from '~/named-urls'; import type { RecruitmentLoader } from '~/router/loaders'; +import { ROUTES } from '~/routes'; export function RoomAdminPage() { const [interviewRooms, setInterviewRooms] = useState(); const data = useRouteLoaderData('recruitment') as RecruitmentLoader | undefined; + const navigate = useCustomNavigate(); const { t } = useTranslation(); useEffect(() => { @@ -31,6 +36,7 @@ export function RoomAdminPage() { { content: 'End Time', sortable: true }, { content: 'Recruitment', sortable: true }, { content: 'Gang', sortable: true }, + { content: 'Actions', sortable: false }, ]; const tableData = interviewRooms.map((room) => [ @@ -40,12 +46,39 @@ export function RoomAdminPage() { new Date(room.end_time), room.recruitment, room.gang !== undefined ? room.gang : 'N/A', + { + content: ( + + navigate({ + url: reverse({ + pattern: ROUTES.frontend.admin_recruitment_room_edit, + urlParams: { recruitmentId: data?.recruitment?.id, roomId: room.id.toString() }, + }), + }) + } + onDelete={() => { + deleteInterviewRoom(room.id.toString()).then(() => { + toast.success('Interview room deleted'); + setInterviewRooms(interviewRooms.filter((r) => r.id !== room.id)); + }); + }} + /> + ), + }, ]); return ( <> -
diff --git a/frontend/src/api.ts b/frontend/src/api.ts index aa5ad992a..0438eb4f9 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -911,11 +911,29 @@ export async function getInterviewRoomsForRecruitment( return await axios.get(url, { withCredentials: true }); } +export async function getInterviewRoom(id: string): Promise> { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.get(url, { withCredentials: true }); +} + export async function postInterviewRoom(data: Partial): Promise { const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__interview_rooms_list; return await axios.post(url, data, { withCredentials: true }); } +export async function putInterviewRoom(id: string, data: Partial): Promise { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.put(url, data, { withCredentials: true }); +} + +export async function deleteInterviewRoom(id: string): Promise { + const url = + BACKEND_DOMAIN + reverse({ pattern: ROUTES.backend.samfundet__interview_rooms_detail, urlParams: { pk: id } }); + return await axios.delete(url, { withCredentials: true }); +} + // ############################################################ // Purchase Feedback // ############################################################ diff --git a/frontend/src/dto.ts b/frontend/src/dto.ts index 3ba934c98..94deeeef7 100644 --- a/frontend/src/dto.ts +++ b/frontend/src/dto.ts @@ -486,13 +486,14 @@ export type RecruitmentStatsDto = { }; export type InterviewRoomDto = { + id: number; name: string; location: string; start_time: string; end_time: string; - recruitment: number; + recruitment: string; gang?: number; -} +}; // ############################################################ // Purchase Feedback diff --git a/frontend/src/router/router.tsx b/frontend/src/router/router.tsx index 1c6491339..a01b90cb9 100644 --- a/frontend/src/router/router.tsx +++ b/frontend/src/router/router.tsx @@ -458,6 +458,7 @@ export const router = createBrowserRouter( /> } /> } /> + } /> } />} From 5374d8009886c98520a8b53b1daebf376365a7c4 Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Thu, 19 Sep 2024 22:23:49 +0200 Subject: [PATCH 5/7] update caniuse-lite --- frontend/package.json | 2 -- frontend/yarn.lock | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 1667aeb02..196165efc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,6 @@ "build-storybook-dev": "storybook build", "cypress:open": "cypress open", "cypress:run": "yarn run cypress run", - "biome//": "echo Biome is configured for entire repo.", "biome:check": "biome check", "biome:ci": "biome ci", @@ -34,7 +33,6 @@ "lint:fix-unsafe": "biome lint --write --unsafe", "format:check": "biome format", "format:fix": "biome format --write", - "stylelint:check": "stylelint --config .stylelintrc src/**/*.{css,scss}", "tsc:check": "tsc", "tsc:watch": "tsc --watch", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c81f3bc3b..4ccc701cf 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7349,9 +7349,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001538, caniuse-lite@npm:^1.0.30001541": - version: 1.0.30001547 - resolution: "caniuse-lite@npm:1.0.30001547" - checksum: 10c0/bd8ef400fdd6a76aa5a4bc490a5b9b8adffbff1657d36ee1516b4be30315f1a3cfaa51ab872a46d5e7db17424eaa335593cd27e640248b4df3897b113650a7d3 + version: 1.0.30001662 + resolution: "caniuse-lite@npm:1.0.30001662" + checksum: 10c0/4af44610db30b9e63443d984be9d48ab93eef584609b3e87201c10972b9daff0b52674e3ed01f7b7b240460763428a3aa8ef7328d14b76ed31ed464203677007 languageName: node linkType: hard From 84dee05ee9cc6588a5c0306985a2fed3430e29b5 Mon Sep 17 00:00:00 2001 From: Mathias Aas Date: Tue, 24 Sep 2024 20:35:18 +0200 Subject: [PATCH 6/7] run biome fixes --- frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx index e3773ad99..1618e6bc6 100644 --- a/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx +++ b/frontend/src/PagesAdmin/RoomAdminPage/RoomAdminPage.tsx @@ -71,11 +71,11 @@ export function RoomAdminPage() { return ( <> -