diff --git a/components/Table.tsx b/components/Table.tsx
index 78039b12..67281dc9 100644
--- a/components/Table.tsx
+++ b/components/Table.tsx
@@ -3,7 +3,7 @@ import { FaTrash } from "react-icons/fa";
import React from "react";
import { useRouter } from "next/router";
-type ColumnType = {
+export type ColumnType = {
label: string;
field: string;
};
@@ -52,9 +52,8 @@ const Table = ({ rows, columns, onDelete }: TableProps) => {
{columns.map((column) => (
{column.field === "delete" && onDelete ? (
diff --git a/components/admin/RoomOverview.tsx b/components/admin/RoomOverview.tsx
new file mode 100644
index 00000000..406a6fc7
--- /dev/null
+++ b/components/admin/RoomOverview.tsx
@@ -0,0 +1,124 @@
+import { useState } from "react";
+import { periodType, committeeInterviewType, RoomBooking } from "../../lib/types/types";
+import Button from "../Button";
+import Table, { RowType } from "../Table"
+import { ColumnType } from "../Table";
+import TextInput from "../form/TextInput";
+import DateInput from "../form/DateInput";
+import TimeRangeInput from "../form/TimeRangeInput";
+
+import toast from "react-hot-toast";
+
+interface Props {
+ period: periodType | null;
+}
+
+const RoomInterview = ({
+ period
+}: Props) => {
+
+ // TODO: Fix correct tabbing
+
+ const [roomBookings, setRoomBookings] = useState ([])
+
+ const [date, setDate] = useState("");
+ const [startTime, setStartTime] = useState("");
+ const [endTime, setEndTime] = useState("");
+ const [room, setRoom] = useState("");
+
+ const isValidBooking = () => {
+ if (!room) {
+ toast.error("Vennligst fyll inn rom")
+ return false;
+ }
+ if (!date) {
+ toast.error("Vennligst velg dato")
+ return false;
+ }
+ if (!startTime || !endTime) {
+ toast.error("Vennligst velg tidspunkt")
+ return false;
+ }
+ if (Date.parse("2003-07-26T" + startTime) - Date.parse("2003-07-26T" + endTime) > 0) {
+ toast.error("Starttid må være før sluttid")
+ return false;
+ }
+
+ return true;
+ }
+
+ const handleAddBooking = () => {
+ if (!isValidBooking()) return;
+ addBooking()
+ setRoom("")
+ }
+
+ const addBooking = () => {
+ const booking: RoomBooking = {
+ room: room,
+ startDate: date.split("T")[0] + "T" + startTime,
+ endDate: date.split("T")[0] + "T" + endTime
+ }
+
+ roomBookings.push(booking)
+ setRoomBookings(roomBookings)
+ }
+
+ const columns: ColumnType[] = [
+ { label: "Rom", field: "room" },
+ { label: "Dato", field: "date" },
+ { label: "Fra", field: "from" },
+ { label: "Til", field: "to" },
+ { label: "Slett", field: "delete" },
+ ]
+
+ return
+ Legg inn romvalg
+
+
+ {
+ setRoom(input)
+ }}
+ label="Romnavn"
+ className="mx-0"
+ />
+ {
+ setDate(date)
+ }}
+ label="Test"
+ />
+ {
+ setStartTime(times.start)
+ setEndTime(times.end)
+ }}
+ className="mx-0"
+ />
+
+
+
+ Alle tilgjengelige romvalg
+ {roomBookings.length ? {
+ return {
+ id: roomBooking.room + "]" + roomBooking.startDate + "]" + roomBooking.endDate,
+ room: roomBooking.room,
+ date: roomBooking.startDate.split("T")[0],
+ from: roomBooking.startDate.split("T")[1],
+ to: roomBooking.endDate.split("T")[1]
+ }
+ })} onDelete={(id: string, name: string) => {
+ const [room, startDate, endDate] = id.split("]")
+ setRoomBookings(roomBookings.filter((booking, index, array) => {
+ return !(booking.room == room
+ && booking.startDate == startDate
+ && booking.endDate == endDate)
+ }))
+ }} />
+ : Legg inn et rom, så dukker det opp her. }
+
+};
+
+export default RoomInterview;
diff --git a/components/form/DateInput.tsx b/components/form/DateInput.tsx
new file mode 100644
index 00000000..eaff3c9b
--- /dev/null
+++ b/components/form/DateInput.tsx
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+interface Props {
+ label?: string;
+ updateDate: (date: string) => void;
+}
+
+const DateRangeInput = (props: Props) => {
+ const [date, setDate] = useState("");
+
+ useEffect(() => {
+ const dateString = date ? `${date}T00:00` : "";
+ props.updateDate(dateString);
+ }, [date]);
+
+ return (
+
+ setDate(e.target.value)}
+ className="border text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 border-gray-300 text-gray-900 dark:border-gray-600 dark:bg-online-darkBlue dark:text-gray-200"
+ />
+
+ );
+};
+
+export default DateRangeInput;
diff --git a/components/form/DatePickerInput.tsx b/components/form/DateRangeInput.tsx
similarity index 95%
rename from components/form/DatePickerInput.tsx
rename to components/form/DateRangeInput.tsx
index 6ef4474c..e3182dbf 100644
--- a/components/form/DatePickerInput.tsx
+++ b/components/form/DateRangeInput.tsx
@@ -4,7 +4,7 @@ interface Props {
updateDates: (dates: { start: string; end: string }) => void;
}
-const DatePickerInput = (props: Props) => {
+const DateRangeInput = (props: Props) => {
const [fromDate, setFromDate] = useState("");
const [toDate, setToDate] = useState("");
@@ -42,4 +42,4 @@ const DatePickerInput = (props: Props) => {
);
};
-export default DatePickerInput;
+export default DateRangeInput;
diff --git a/components/form/TextInput.tsx b/components/form/TextInput.tsx
index d54d130b..aa4e2d48 100644
--- a/components/form/TextInput.tsx
+++ b/components/form/TextInput.tsx
@@ -4,6 +4,7 @@ interface Props {
disabled?: boolean;
placeholder?: string;
defaultValue?: string;
+ className?: string;
}
const TextInput = (props: Props) => {
@@ -12,7 +13,7 @@ const TextInput = (props: Props) => {
};
return (
-
+
void;
+ className?: string;
+}
+
+const TimeRangeInput = (props: Props) => {
+ const [fromTime, setFromTime] = useState("");
+ const [toTime, setToTime] = useState("");
+ new Date()
+
+ useEffect(() => {
+ const startTime = fromTime ? `${fromTime}` : "";
+ const endTime = toTime ? `${toTime}` : "";
+ props.updateTimes({ start: startTime, end: endTime });
+ }, [fromTime, toTime]);
+
+ return (
+
+ );
+};
+
+export default TimeRangeInput;
diff --git a/lib/api/periodApi.ts b/lib/api/periodApi.ts
index a5662f53..36b4dfa4 100644
--- a/lib/api/periodApi.ts
+++ b/lib/api/periodApi.ts
@@ -1,5 +1,5 @@
import { QueryFunctionContext } from "@tanstack/react-query";
-import { periodType } from "../types/types";
+import { periodType, RoomBooking } from "../types/types";
export const fetchPeriodById = async (context: QueryFunctionContext) => {
const id = context.queryKey[1];
@@ -25,3 +25,16 @@ export const createPeriod = async (period: periodType) => {
},
});
};
+
+export const updateRoomsForPeriod = async (
+ id: string,
+ rooms: RoomBooking[]
+) => {
+ return fetch(`/api/periods${id}`, {
+ method: "PATCH",
+ body: JSON.stringify(rooms),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+};
diff --git a/lib/mongo/periods.ts b/lib/mongo/periods.ts
index dccc87d8..28c88734 100644
--- a/lib/mongo/periods.ts
+++ b/lib/mongo/periods.ts
@@ -1,6 +1,6 @@
import { Collection, Db, MongoClient, ObjectId } from "mongodb";
import clientPromise from "./mongodb";
-import { periodType } from "../types/types";
+import { periodType, RoomBooking } from "../types/types";
let client: MongoClient;
let db: Db;
@@ -85,6 +85,32 @@ export const getCurrentPeriods = async () => {
}
};
+export const updateRoomsForPeriod = async (
+ id: string | ObjectId,
+ rooms: RoomBooking[]
+) => {
+ try {
+ if (!periods) await init();
+
+ // Checks if period exists
+ const period = await getPeriodById(id);
+ if (!period.exists) {
+ return { error: "Period not found" };
+ }
+
+ const response = await periods.updateOne(
+ { _id: new ObjectId(id) },
+ { $set: { rooms: rooms } }
+ );
+
+ return response.modifiedCount >= 1
+ ? { message: "Period updated with rooms" }
+ : { message: "Period not updated" };
+ } catch (error) {
+ return { error: "Failed to update rooms for period" };
+ }
+};
+
export const getPeriodById = async (id: string | ObjectId) => {
try {
if (!periods) await init();
diff --git a/lib/types/types.ts b/lib/types/types.ts
index 708214ec..b0c03a3b 100644
--- a/lib/types/types.ts
+++ b/lib/types/types.ts
@@ -66,6 +66,7 @@ export type periodType = {
committees: string[];
optionalCommittees: string[];
hasSentInterviewTimes: boolean;
+ rooms?: RoomBooking[];
};
export type AvailableTime = {
@@ -141,3 +142,9 @@ export type emailApplicantInterviewType = {
};
}[];
};
+
+export interface RoomBooking {
+ room: String;
+ startDate: String;
+ endDate: String;
+}
diff --git a/lib/utils/validators.ts b/lib/utils/validators.ts
index e4433c4f..21352459 100644
--- a/lib/utils/validators.ts
+++ b/lib/utils/validators.ts
@@ -3,6 +3,7 @@ import {
committeeInterviewType,
periodType,
preferencesType,
+ RoomBooking,
} from "../types/types";
export const isApplicantType = (
@@ -191,3 +192,8 @@ export const isPeriodType = (data: any): data is periodType => {
return hasBasicFields;
};
+
+export const isRoomBookings = (data: any): data is RoomBooking => {
+ // TODO: Implement
+ return true;
+};
diff --git a/package-lock.json b/package-lock.json
index 6bf93c5d..d5a5825b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"mongodb": "^6.1.0",
"next": "^12.3.4",
"next-auth": "^4.24.5",
+ "online-opptak": "file:",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-toast": "^2.4.1",
@@ -5182,6 +5183,10 @@
"wrappy": "1"
}
},
+ "node_modules/online-opptak": {
+ "resolved": "",
+ "link": true
+ },
"node_modules/openid-client": {
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
diff --git a/package.json b/package.json
index 9fedecce..4b5e32b9 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"mongodb": "^6.1.0",
"next": "^12.3.4",
"next-auth": "^4.24.5",
+ "online-opptak": "file:",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-toast": "^2.4.1",
diff --git a/pages/admin/[period-id]/index.tsx b/pages/admin/[period-id]/index.tsx
index d025954d..081bc142 100644
--- a/pages/admin/[period-id]/index.tsx
+++ b/pages/admin/[period-id]/index.tsx
@@ -4,8 +4,9 @@ import router from "next/router";
import { periodType } from "../../../lib/types/types";
import NotFound from "../../404";
import ApplicantsOverview from "../../../components/applicantoverview/ApplicantsOverview";
+import RoomOverview from "../../../components/admin/RoomOverview";
import { Tabs } from "../../../components/Tabs";
-import { CalendarIcon, InboxIcon } from "@heroicons/react/24/solid";
+import { CalendarIcon, InboxIcon, BuildingOffice2Icon } from "@heroicons/react/24/solid";
import Button from "../../../components/Button";
import { useQuery } from "@tanstack/react-query";
import { fetchPeriodById } from "../../../lib/api/periodApi";
@@ -87,26 +88,35 @@ const Admin = () => {
/>
),
},
+ {
+ title: "Romoppsett",
+ icon: ,
+ content: (
+
+ )
+ },
//Super admin :)
...(session?.user?.email &&
- ["fhansteen@gmail.com", "jotto0214@gmail.com"].includes(
- session.user.email
- )
+ ["fhansteen@gmail.com", "jotto0214@gmail.com"].includes(
+ session.user.email
+ )
? [
- {
- title: "Send ut",
- icon: ,
- content: (
-
-
- ),
- },
- ]
+ {
+ title: "Send ut",
+ icon: ,
+ content: (
+
+
+ ),
+ },
+ ]
: []),
]}
/>
diff --git a/pages/admin/new-period.tsx b/pages/admin/new-period.tsx
index ee9035d1..90a4f935 100644
--- a/pages/admin/new-period.tsx
+++ b/pages/admin/new-period.tsx
@@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import Button from "../../components/Button";
import ApplicationForm from "../../components/form/ApplicationForm";
import CheckboxInput from "../../components/form/CheckboxInput";
-import DatePickerInput from "../../components/form/DatePickerInput";
+import DateRangeInput from "../../components/form/DateRangeInput";
import TextAreaInput from "../../components/form/TextAreaInput";
import TextInput from "../../components/form/TextInput";
import { DeepPartial, periodType } from "../../lib/types/types";
@@ -154,11 +154,11 @@ const NewPeriod = () => {
/>
-
-
@@ -214,7 +214,7 @@ const NewPeriod = () => {
{}}
+ setApplicationData={() => { }}
availableCommittees={
(periodData.committees?.filter(Boolean) as string[]) || []
}
diff --git a/pages/api/periods/[id].ts b/pages/api/periods/[id].ts
index 3f51d27e..ecc2d8cf 100644
--- a/pages/api/periods/[id].ts
+++ b/pages/api/periods/[id].ts
@@ -3,6 +3,9 @@ import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]";
import { deletePeriodById, getPeriodById } from "../../../lib/mongo/periods";
import { hasSession, isAdmin } from "../../../lib/utils/apiChecks";
+import { updateRoomsForPeriod } from "../../../lib/api/periodApi";
+import { isRoomBookings } from "../../../lib/utils/validators";
+import { RoomBooking } from "../../../lib/types/types";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -28,7 +31,22 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
return res.status(200).json({ exists, period });
+ } else if (req.method === "PATCH") {
+ if (!isAdmin(res, session)) {
+ return res.status(403).json({ error: "Unauthorized" });
+ }
+
+ if (!isRoomBookings(req.body)) {
+ return res.status(400).json({ error: "Invalid data format" });
+ }
+
+ const bookings: RoomBooking[] = req.body as RoomBooking[];
+
+ const { error } = await updateRoomsForPeriod(id, bookings);
+ if (error) throw new Error(error);
+ return res.status(200).json({ message: updated });
} else if (req.method === "DELETE") {
+ // TODO: The next line is probably supposed to be !isAdmin(res, session)?
if (!isAdmin) return res.status(403).json({ error: "Unauthorized" });
const { error } = await deletePeriodById(id);
|