diff --git a/components/form/CheckboxInput.tsx b/components/form/CheckboxInput.tsx index 9e03079a..58fec0df 100644 --- a/components/form/CheckboxInput.tsx +++ b/components/form/CheckboxInput.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { InformationCircleIcon } from "@heroicons/react/24/outline"; interface CheckboxOption { @@ -14,12 +14,18 @@ interface Props { required?: boolean; order: number; info?: string; + checkedItems?: string[]; } const CheckboxInput = (props: Props) => { const [checkedItems, setCheckedItems] = useState([]); const [showInfo, setShowInfo] = useState(false); + useEffect(() => { + if (!props.checkedItems) return; + setCheckedItems(props.checkedItems); + }, [props.checkedItems]); + const handleInputChange = (e: React.BaseSyntheticEvent) => { const value = e.target.value; const isChecked = e.target.checked; diff --git a/components/form/DatePickerInput.tsx b/components/form/DatePickerInput.tsx index 6ef4474c..b11a2392 100644 --- a/components/form/DatePickerInput.tsx +++ b/components/form/DatePickerInput.tsx @@ -1,18 +1,12 @@ import { useEffect, useState } from "react"; interface Props { label?: string; + fromDate?: string; + toDate?: string; updateDates: (dates: { start: string; end: string }) => void; } const DatePickerInput = (props: Props) => { - const [fromDate, setFromDate] = useState(""); - const [toDate, setToDate] = useState(""); - - useEffect(() => { - const startDate = fromDate ? `${fromDate}T00:00` : ""; - const endDate = toDate ? `${toDate}T23:59` : ""; - props.updateDates({ start: startDate, end: endDate }); - }, [fromDate, toDate]); return (
@@ -24,8 +18,8 @@ const DatePickerInput = (props: Props) => { type="date" id={`${props.label}-from`} name={`${props.label}-from`} - value={fromDate} - onChange={(e) => setFromDate(e.target.value)} + value={props.fromDate ?? ""} + onChange={(e) => props.updateDates({start: `${e.target.value}T00:00`, end: props.toDate ? `${props.toDate}T23:59` : ""})} 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" /> til @@ -33,8 +27,8 @@ const DatePickerInput = (props: Props) => { type="date" id={`${props.label}-to`} name={`${props.label}-to`} - value={toDate} - onChange={(e) => setToDate(e.target.value)} + value={props.toDate ?? ""} + onChange={(e) => props.updateDates({start: props.fromDate ? `${props.fromDate}T00:00` : "", end: `${e.target.value}T23:59`})} 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" />
diff --git a/lib/api/committeesApi.ts b/lib/api/committeesApi.ts index a4fbe4c5..0821c43f 100644 --- a/lib/api/committeesApi.ts +++ b/lib/api/committeesApi.ts @@ -13,3 +13,11 @@ export const fetchCommitteeTimes = async (context: QueryFunctionContext) => { res.json(), ); }; + +export const fetchCommitteesByPeriodId = async (context: QueryFunctionContext) => { + const periodId = context.queryKey[1]; + + return fetch(`/api/committees/times/${periodId}`).then((res) => + res.json(), + ); +} diff --git a/lib/api/periodApi.ts b/lib/api/periodApi.ts index a5662f53..4857aabb 100644 --- a/lib/api/periodApi.ts +++ b/lib/api/periodApi.ts @@ -25,3 +25,13 @@ export const createPeriod = async (period: periodType) => { }, }); }; + +export const editPeriod = async (updatedFields : Partial) => { + return fetch(`/api/periods/${updatedFields._id}`, { + method: "PATCH", + body: JSON.stringify(updatedFields), + headers: { + "Content-Type": "application/json", + }, + }); +}; \ No newline at end of file diff --git a/lib/mongo/periods.ts b/lib/mongo/periods.ts index dc651a61..6f930671 100644 --- a/lib/mongo/periods.ts +++ b/lib/mongo/periods.ts @@ -123,6 +123,29 @@ export const deletePeriodById = async (periodId: string | ObjectId) => { } }; +export const editPeriodById = async ( + periodId: string | ObjectId, + updatedFields: Partial +) => { + try { + if (!periods) await init(); + delete updatedFields._id; + + const result = await periods.updateOne( + { _id: new ObjectId(periodId) }, + { $set: updatedFields } + ); + + if (result.matchedCount === 0) { + return { error: "Period not found" }; + } + + return { message: "Period updated successfully" }; + } catch (error) { + return { error: "Failed to update period" }; + } +}; + export const markMatchedInterviewsByPeriodId = async (periodId: string) => { try { if (!periods) await init(); diff --git a/lib/utils/validateEditedPeriod.ts b/lib/utils/validateEditedPeriod.ts new file mode 100644 index 00000000..0cbc623f --- /dev/null +++ b/lib/utils/validateEditedPeriod.ts @@ -0,0 +1,114 @@ +import { DeepPartial, periodType } from "../../lib/types/types"; + +export const validateChangedInterviewPeriod = ( + changed: DeepPartial, + applicantsData: any, + owCommitteesData: any +): boolean => { + let earliest = new Date(8640000000000000); + let latest = new Date(0); + for (const application of applicantsData.applications) { + if (earliest > new Date(application.selectedTimes[0].start)) { + earliest = new Date(application.selectedTimes[0].start); + } + if (latest < new Date(application.selectedTimes.at(-1).end)) { + latest = new Date(application.selectedTimes.at(-1).end); + } + } + + if ((changed.interviewPeriod?.start && earliest < changed.interviewPeriod.start) || (changed.interviewPeriod?.end && latest > changed.interviewPeriod.end)) { + return window.confirm("Du har fjernet intervjutider som minst en søker har markert. Dette kan skape problemer. Er du sikker på at du vil fortsette?") + } + + earliest = new Date(8640000000000000); + latest = new Date(0); + for (const committee of owCommitteesData.result) { + if (earliest > new Date(committee.availabletimes.at(0).start)) { + earliest = new Date(committee.availabletimes.at(0).start); + } + if (latest < new Date(committee.availabletimes.at(-1).end)) { + latest = new Date(committee.availabletimes.at(-1).end); + } + } + + if ((changed.interviewPeriod?.start && earliest < changed.interviewPeriod.start) || (changed.interviewPeriod?.end && latest > changed.interviewPeriod.end)) { + return window.confirm("Du har fjernet intervjutider som minst en komite har markert. Dette kan skape problemer. Er du sikker på at du vil fortsette?") + } + + return true; +} + +export const validateChangedCommittees = ( + original: periodType, + changed: DeepPartial, + applicantsData: any +): boolean => { + const illegalRemovals: string[] = [] + const removedLowerCase: string[] = []; + original.committees.map((committee) => { + if (!changed.committees?.includes(committee)) { + removedLowerCase.push(committee.toLowerCase()); + } + }) + + for (const application of applicantsData.applications) { + if (removedLowerCase && application.preferences.first != '' && removedLowerCase.includes(application.preferences.first) && !illegalRemovals.includes(application.preferences.first)) { + illegalRemovals.push(application.preferences.first); + } + if (removedLowerCase && application.preferences.second != '' && removedLowerCase.includes(application.preferences.second) && !illegalRemovals.includes(application.preferences.second)) { + illegalRemovals.push(application.preferences.second); + } + if (removedLowerCase && application.preferences.third != '' && removedLowerCase.includes(application.preferences.third) && !illegalRemovals.includes(application.preferences.third)) { + illegalRemovals.push(application.preferences.third); + } + } + + if (illegalRemovals.length > 0) { + const formattedCommittees = illegalRemovals + .map(c => c.charAt(0).toUpperCase() + c.slice(1)) + .join("\n"); + + const confirmMessage = + "Følgende komiteer du har fjernet har minst en søker:\n" + + formattedCommittees + + "\n\nDette kan skape problemer. Ønsker du å fortsette?"; + + return window.confirm(confirmMessage); + } + return true; +} + +export const validateChangedOptionalCommittees = ( + original: periodType, + changed: DeepPartial, + applicantsData: any +): boolean => { + const illegalRemovals: string[] = [] + const removedLowerCase: string[] = []; + original.optionalCommittees.map((committee) => { + if (!changed.optionalCommittees?.includes(committee)) { + removedLowerCase.push(committee.toLowerCase()); + } + }); + for (const application of applicantsData.applications) { + for (const committee of application.optionalCommittees) { + if (removedLowerCase.includes(committee.toLowerCase()) && !illegalRemovals.includes(committee.toLowerCase())) { + illegalRemovals.push(committee.toLowerCase()); + } + } + } + + if (illegalRemovals.length > 0) { + const formattedCommittees = illegalRemovals + .map(c => c.charAt(0).toUpperCase() + c.slice(1)) + .join("\n"); + + const confirmMessage = + "Følgende valgfrie komiteer du har fjernet har minst en søker:\n" + + formattedCommittees + + "\n\nDette kan skape problemer. Ønsker du å fortsette?"; + + return window.confirm(confirmMessage); + } + return true; +} \ No newline at end of file diff --git a/pages/admin/[period-id]/index.tsx b/pages/admin/[period-id]/index.tsx index 51d24a7e..64b3be86 100644 --- a/pages/admin/[period-id]/index.tsx +++ b/pages/admin/[period-id]/index.tsx @@ -6,13 +6,14 @@ import { periodType } from "../../../lib/types/types"; import NotFound from "../../404"; import ApplicantsOverview from "../../../components/applicantoverview/ApplicantsOverview"; import { Tabs } from "../../../components/Tabs"; -import { CalendarIcon, InboxIcon } from "@heroicons/react/24/solid"; +import { CalendarIcon, CogIcon, InboxIcon } from "@heroicons/react/24/solid"; import Button from "../../../components/Button"; import { useQuery } from "@tanstack/react-query"; import { fetchPeriodById } from "../../../lib/api/periodApi"; import LoadingPage from "../../../components/LoadingPage"; import ErrorPage from "../../../components/ErrorPage"; import toast from "react-hot-toast"; +import PeriodSettings from "../period-settings"; const Admin = () => { const { data: session } = useSession(); @@ -119,6 +120,13 @@ const Admin = () => { /> ), }, + { + title: "Instillinger", + icon: , + content: ( + + ) + }, //Super admin :) ...(session?.user?.email && [ diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index e37b092d..40d94a5b 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -91,7 +91,7 @@ const Admin = () => {