From 76190c29c0e82adcc04cc7e67d248bf5971218d7 Mon Sep 17 00:00:00 2001 From: Julian Ammouche Ottosen Date: Sun, 18 Jan 2026 14:11:39 +0100 Subject: [PATCH 1/2] Add RoomBookingCalendar-component --- components/committee/RoomBookingCalendar.tsx | 257 +++++++++++++++++++ package-lock.json | 10 + package.json | 1 + 3 files changed, 268 insertions(+) create mode 100644 components/committee/RoomBookingCalendar.tsx diff --git a/components/committee/RoomBookingCalendar.tsx b/components/committee/RoomBookingCalendar.tsx new file mode 100644 index 00000000..c92598dc --- /dev/null +++ b/components/committee/RoomBookingCalendar.tsx @@ -0,0 +1,257 @@ +import clsx from "clsx"; +import { useState } from "react"; + +type RoomTime = { + date: string; + rooms: Room[]; +} + +type Room = { + room: string; + times: Time[]; +} + +type Time = { + time: string; + committee: string | undefined; +} + +const initialRoomTimes: RoomTime[] = [ + { + date: "29.03", + rooms: [ + { + room: "S9", + times: [ + { time: "8:00", committee: "appkom" }, + { time: "9:00", committee: "appkom" }, + { time: "10:00", committee: "appkom" }, + { time: "11:00", committee: "appkom" }, + { time: "12:00", committee: undefined }, + { time: "13:00", committee: undefined }, + { time: "14:00", committee: "prokom" }, + { time: "15:00", committee: "eeeeeeeeeeeee" }, + { time: "16:00", committee: "prokom" }, + { time: "17:00", committee: "prokom" }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "R3", + times: [ + { time: "8:00", committee: "dotkom" }, + { time: "9:00", committee: "dotkom" }, + { time: "10:00", committee: "dotkom" }, + { time: "11:00", committee: "dotkom" }, + { time: "12:00", committee: undefined }, + { time: "13:00", committee: "arrkom" }, + { time: "14:00", committee: "arrkom" }, + { time: "15:00", committee: "arrkom" }, + { time: "16:00", committee: "arrkom" }, + { time: "17:00", committee: undefined }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "A4-108", + times: [ + { time: "8:00", committee: "fagkom" }, + { time: "9:00", committee: "fagkom" }, + { time: "10:00", committee: undefined }, + { time: "11:00", committee: undefined }, + { time: "12:00", committee: "trikom" }, + { time: "13:00", committee: "trikom" }, + { time: "14:00", committee: "trikom" }, + { time: "15:00", committee: "appkom" }, + { time: "16:00", committee: "appkom" }, + { time: "17:00", committee: "appkom" }, + { time: "18:00", committee: undefined } + ] + } + ] + }, + { + date: "30.03", + rooms: [ + { + room: "S9", + times: [ + { time: "8:00", committee: "prokom" }, + { time: "9:00", committee: "prokom" }, + { time: "10:00", committee: "prokom" }, + { time: "11:00", committee: "prokom" }, + { time: "12:00", committee: undefined }, + { time: "13:00", committee: "dotkom" }, + { time: "14:00", committee: "dotkom" }, + { time: "15:00", committee: undefined }, + { time: "16:00", committee: "arrkom" }, + { time: "17:00", committee: "arrkom" }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "R3", + times: [ + { time: "8:00", committee: "fagkom" }, + { time: "9:00", committee: undefined }, + { time: "10:00", committee: undefined }, + { time: "11:00", committee: "trikom" }, + { time: "12:00", committee: "trikom" }, + { time: "13:00", committee: "trikom" }, + { time: "14:00", committee: "appkom" }, + { time: "15:00", committee: "appkom" }, + { time: "16:00", committee: undefined }, + { time: "17:00", committee: "prokom" }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "A4-108", + times: [ + { time: "8:00", committee: "arrkom" }, + { time: "9:00", committee: "arrkom" }, + { time: "10:00", committee: "arrkom" }, + { time: "11:00", committee: "arrkom" }, + { time: "12:00", committee: "dotkom" }, + { time: "13:00", committee: "dotkom" }, + { time: "14:00", committee: "fagkom" }, + { time: "15:00", committee: "fagkom" }, + { time: "16:00", committee: undefined }, + { time: "17:00", committee: undefined }, + { time: "18:00", committee: undefined } + ] + } + ] + }, + { + date: "31.03", + rooms: [ + { + room: "S9", + times: [ + { time: "8:00", committee: "trikom" }, + { time: "9:00", committee: "trikom" }, + { time: "10:00", committee: "trikom" }, + { time: "11:00", committee: undefined }, + { time: "12:00", committee: "fagkom" }, + { time: "13:00", committee: "fagkom" }, + { time: "14:00", committee: undefined }, + { time: "15:00", committee: "dotkom" }, + { time: "16:00", committee: "dotkom" }, + { time: "17:00", committee: "dotkom" }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "R3", + times: [ + { time: "8:00", committee: "appkom" }, + { time: "9:00", committee: "appkom" }, + { time: "10:00", committee: "appkom" }, + { time: "11:00", committee: undefined }, + { time: "12:00", committee: "arrkom" }, + { time: "13:00", committee: "arrkom" }, + { time: "14:00", committee: "arrkom" }, + { time: "15:00", committee: undefined }, + { time: "16:00", committee: "prokom" }, + { time: "17:00", committee: "prokom" }, + { time: "18:00", committee: undefined } + ] + }, + { + room: "A4-108", + times: [ + { time: "8:00", committee: "dotkom" }, + { time: "9:00", committee: undefined }, + { time: "10:00", committee: undefined }, + { time: "11:00", committee: "trikom" }, + { time: "12:00", committee: "trikom" }, + { time: "13:00", committee: "appkom" }, + { time: "14:00", committee: "appkom" }, + { time: "15:00", committee: undefined }, + { time: "16:00", committee: "fagkom" }, + { time: "17:00", committee: "fagkom" }, + { time: "18:00", committee: undefined } + ] + } + ] + } +]; + +const currentCommittee = "appkom"; + +export const RoomBookingCalendar = () => { + const [ roomTimes, setRoomTimes ] = useState(initialRoomTimes); + + + return ( +
+
+ + + +
+ +
+
Dato
+
Rom
+ {roomTimes[0].rooms[0].times.map((time, index) => ( +
{time.time}
+ ))} +
+ +
+ { + roomTimes.map((roomTime, index) => ( +
+ { + roomTime.rooms.map((room, index) => ( +
+
{index === 0 && roomTime.date}
+
{room.room}
+ { + room.times.map((time, index) => ( + + )) + } +
+ )) + } +
+ )) + } +
+
+ ); +} + +const Cell = ({committee}: {committee: string | undefined }) => { + const colorClass = committee === undefined ? "" : committee === currentCommittee ? "bg-green-300" : "bg-red-300"; + const cursorHoverClass = committee === undefined || committee === currentCommittee ? "cursor-pointer hover:bg-gray-100" : ""; + + return ( +
+ {committee} +
+ ); +} + +const AvailabilityIndicator = ({ colorClass, text }: { colorClass: string, text: string }) => { + return ( +
+
+
+ {text} +
+
+ ); +}; diff --git a/package-lock.json b/package-lock.json index c99b5b06..f094948b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@tanstack/react-query": "^5.51.11", "@types/mongodb": "^4.0.7", "@vercel/analytics": "^1.1.2", + "clsx": "^2.1.1", "lucide-react": "^0.441.0", "mongodb": "^6.1.0", "next": "^14.2.16", @@ -2477,6 +2478,15 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/package.json b/package.json index 336c3ed5..2aef0ccf 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tanstack/react-query": "^5.51.11", "@types/mongodb": "^4.0.7", "@vercel/analytics": "^1.1.2", + "clsx": "^2.1.1", "lucide-react": "^0.441.0", "mongodb": "^6.1.0", "next": "^14.2.16", From c6190b58d552a18ab614bdbdf2c2d7e0f4755239 Mon Sep 17 00:00:00 2001 From: Julian Ammouche Ottosen Date: Sun, 18 Jan 2026 14:18:15 +0100 Subject: [PATCH 2/2] Don't know if this works, but I found this code in my branch, so just pushing it --- .../committee/CommitteeInterviewTimes.tsx | 151 ++---------------- .../[period-id]/[committee]/index.tsx | 2 +- 2 files changed, 16 insertions(+), 137 deletions(-) diff --git a/components/committee/CommitteeInterviewTimes.tsx b/components/committee/CommitteeInterviewTimes.tsx index 88a98947..0e9f9a4f 100644 --- a/components/committee/CommitteeInterviewTimes.tsx +++ b/components/committee/CommitteeInterviewTimes.tsx @@ -2,8 +2,6 @@ import { BaseSyntheticEvent, useEffect, useRef } from "react"; import { useState } from "react"; import { useSession } from "next-auth/react"; import FullCalendar from "@fullcalendar/react"; -import timeGridPlugin from "@fullcalendar/timegrid"; -import interactionPlugin from "@fullcalendar/interaction"; import { periodType, committeeInterviewType } from "../../lib/types/types"; import toast from "react-hot-toast"; import NotFound from "../../pages/404"; @@ -13,8 +11,7 @@ import useUnsavedChangesWarning from "../../lib/utils/unSavedChangesWarning"; import { SimpleTitle } from "../Typography"; import { useQuery } from "@tanstack/react-query"; import { fetchApplicantsByPeriodIdAndCommittee } from "../../lib/api/applicantApi"; -import { CheckIcon } from "@heroicons/react/24/outline"; -import { XMarkIcon } from "@heroicons/react/24/solid"; +import { RoomBookingCalendar } from "./RoomBookingCalendar"; interface Interview { id: string; @@ -150,11 +147,15 @@ const CommitteeInterviewTimes = ({ } }, [calendarEvents, selectedTimeslot]); - const handleDateSelect = (selectionInfo: any) => { - setCurrentSelection(selectionInfo); - setIsModalOpen(true); - setUnsavedChanges(true); - }; + useEffect(() => { + if (period) { + setCountdown(getSubmissionDeadline()); + const intervalId = setInterval(() => { + setCountdown(getSubmissionDeadline()); + }, 1000); + return () => clearInterval(intervalId); + } + }, [period]); const handleRoomSubmit = () => { if (!roomInput) { @@ -217,51 +218,11 @@ const CommitteeInterviewTimes = ({ } }; - const removeCell = (event: Interview) => { - setCalendarEvents((prevEvents) => - prevEvents.filter((evt) => evt.id !== event.id) - ); - - setUnsavedChanges(true); - }; - const updateInterviewInterval = (e: BaseSyntheticEvent) => { setInterviewInterval(parseInt(e.target.value)); setUnsavedChanges(true); }; - const calendarEventStyle = (eventContent: { event: Interview }) => { - return ( -
- {!hasAlreadySubmitted && ( - - )} -

- {eventContent.event.title} -

-
- ); - }; - const formatEventsForExport = (events: Interview[]) => { return events.map((event) => { const startDateTime = new Date(event.start); @@ -306,16 +267,6 @@ const CommitteeInterviewTimes = ({ } }; - useEffect(() => { - if (period) { - setCountdown(getSubmissionDeadline()); - const intervalId = setInterval(() => { - setCountdown(getSubmissionDeadline()); - }, 1000); - return () => clearInterval(intervalId); - } - }, [period]); - const getSubmissionDeadline = (): string => { const deadlineIso = period!.applicationPeriod.end; @@ -373,7 +324,7 @@ const CommitteeInterviewTimes = ({ if (deadLineHasPassed) return ( ); @@ -381,11 +332,11 @@ const CommitteeInterviewTimes = ({ return (
- Legg inn ledige tider for intervjuer + Book rom for intervjuer

@@ -418,49 +369,8 @@ const CommitteeInterviewTimes = ({

)} -

{`${interviewsPlanned} / ${numberOfApplications} intervjuer planlagt`}

-
- { - const start = selectInfo.start; - const end = selectInfo.end; - const startHour = start.getHours(); - const endHour = end.getHours(); - const isSameDay = start.toDateString() === end.toDateString(); - return isSameDay && startHour >= 8 && endHour <= 18; - }} - slotLabelFormat={{ - hour: "2-digit", - minute: "2-digit", - hour12: false, - }} - handleWindowResize={true} - longPressDelay={0} - /> -
+ + {!hasAlreadySubmitted && (
- - {isModalOpen && ( -
-
-

- Skriv inn navn på rom: -

- setRoomInput(e.target.value)} - /> -
- - -
-
-
- )} ); }; diff --git a/pages/committee/[period-id]/[committee]/index.tsx b/pages/committee/[period-id]/[committee]/index.tsx index b5f51989..bc22703c 100644 --- a/pages/committee/[period-id]/[committee]/index.tsx +++ b/pages/committee/[period-id]/[committee]/index.tsx @@ -143,7 +143,7 @@ const CommitteeApplicantOverview: NextPage = () => { }} content={[ { - title: "Intervjutider", + title: "Rombooking", icon: , content: (