diff --git a/components/committee/CommitteeInterviewTimes.tsx b/components/committee/CommitteeInterviewTimes.tsx index 88a98947..8bfd12f2 100644 --- a/components/committee/CommitteeInterviewTimes.tsx +++ b/components/committee/CommitteeInterviewTimes.tsx @@ -4,7 +4,7 @@ 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 { periodType, committeeInterviewType, AvailableTime } from "../../lib/types/types"; import toast from "react-hot-toast"; import NotFound from "../../pages/404"; import Button from "../Button"; @@ -19,8 +19,8 @@ import { XMarkIcon } from "@heroicons/react/24/solid"; interface Interview { id: string; title: string; - start: string; - end: string; + start: Date; + end: Date; } interface Props { @@ -63,8 +63,8 @@ const CommitteeInterviewTimes = ({ useEffect(() => { if (period) { setVisibleRange({ - start: new Date(period!.interviewPeriod.start).toISOString(), - end: new Date(period!.interviewPeriod.end).toISOString(), + start: period.interviewPeriod.start.toISOString(), + end: period.interviewPeriod.end.toISOString(), }); } }, [period]); @@ -98,11 +98,11 @@ const CommitteeInterviewTimes = ({ if (cleanCommittee === cleanSelectedCommittee) { setHasAlreadySubmitted(true); const events = committeeInterviewTimes.availabletimes.map( - (at: any) => ({ + (availableTime: AvailableTime) => ({ id: crypto.getRandomValues(new Uint32Array(1))[0].toString(), - title: at.room, - start: new Date(at.start).toISOString(), - end: new Date(at.end).toISOString(), + title: availableTime.room, + start: availableTime.start.toISOString(), + end: availableTime.end.toISOString(), }) ); @@ -262,17 +262,12 @@ const CommitteeInterviewTimes = ({ ); }; - const formatEventsForExport = (events: Interview[]) => { - return events.map((event) => { - const startDateTime = new Date(event.start); - const endDateTime = new Date(event.end); - return { + const formatEventsForExport = (events: Interview[]) => + events.map((event) => ({ room: event.title, - start: startDateTime.toISOString(), - end: endDateTime.toISOString(), - }; - }); - }; + start: event.start.toISOString(), + end: event.end.toISOString(), + })); const handleTimeslotSelection = (e: React.ChangeEvent) => { setSelectedTimeslot(e.target.value); diff --git a/components/form/DatePickerInput.tsx b/components/form/DatePickerInput.tsx index 6ef4474c..9cd0f3a1 100644 --- a/components/form/DatePickerInput.tsx +++ b/components/form/DatePickerInput.tsx @@ -1,7 +1,10 @@ import { useEffect, useState } from "react"; +import { fromZonedTime } from 'date-fns-tz'; +import { timezone } from "../../lib/utils/dateUtils"; + interface Props { label?: string; - updateDates: (dates: { start: string; end: string }) => void; + updateDates: (dates: { start: Date; end: Date }) => void; } const DatePickerInput = (props: Props) => { @@ -9,8 +12,19 @@ const DatePickerInput = (props: Props) => { const [toDate, setToDate] = useState(""); useEffect(() => { - const startDate = fromDate ? `${fromDate}T00:00` : ""; - const endDate = toDate ? `${toDate}T23:59` : ""; + if (!fromDate || !toDate) return; + + + // Convert to Date objects in correct timezone + const startDate = fromZonedTime( + `${fromDate}T00:00:00`, + timezone + ); + const endDate = fromZonedTime( + `${toDate}T23:59:59`, + timezone + ); + props.updateDates({ start: startDate, end: endDate }); }, [fromDate, toDate]); @@ -38,8 +52,11 @@ const DatePickerInput = (props: Props) => { 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" /> +

+ NB: Alle tider er i norsk tidssone (GMT+1) +

); }; -export default DatePickerInput; +export default DatePickerInput; \ No newline at end of file diff --git a/lib/mongo/applicants.ts b/lib/mongo/applicants.ts index 212aff32..b5522b25 100644 --- a/lib/mongo/applicants.ts +++ b/lib/mongo/applicants.ts @@ -1,7 +1,8 @@ import { Collection, Db, MongoClient, ObjectId } from "mongodb"; import clientPromise from "./mongodb"; -import { applicantType, periodType, preferencesType } from "../types/types"; +import { applicantType, Nullable, periodType, preferencesType } from "../types/types"; import { getPeriodById } from "./periods"; +import { addDays, isAfter } from 'date-fns'; let client: MongoClient; let db: Db; @@ -153,7 +154,7 @@ export const getApplicantsForCommittee = async ( // Filtrerer søkerne slik at kun brukere som er i komiteen som har blitt søkt på ser søkeren // Fjerner prioriterings informasjon const filteredApplicants = result - .map((applicant) => { + .map((applicant: Nullable) => { let preferencesArray: string[] = []; if (isPreferencesType(applicant.preferences)) { preferencesArray = [ @@ -182,24 +183,22 @@ export const getApplicantsForCommittee = async ( applicant.optionalCommittees = []; - const today = new Date(); - const sevenDaysAfterInterviewEnd = new Date(period.interviewPeriod.end); - sevenDaysAfterInterviewEnd.setDate( - sevenDaysAfterInterviewEnd.getDate() + 5 - ); + const now = new Date(); + const sevenDaysAfterInterviewEnd = addDays(period.interviewPeriod.end, 7); + // Sletter sensitiv informasjon etter intervju perioden + 7 dager, for å forhindre snoking i tidligere søknader if ( - new Date(period.applicationPeriod.end) > today || - today > sevenDaysAfterInterviewEnd + isAfter(now, period.applicationPeriod.end) || + isAfter(now, sevenDaysAfterInterviewEnd) ) { - applicant.owId = "Skjult"; - applicant.name = "Skjult"; - applicant.date = today; - applicant.phone = "Skjult"; - applicant.email = "Skjult"; - applicant.about = "Skjult"; - applicant.grade = "-"; - applicant.selectedTimes = [{ start: "Skjult", end: "Skjult" }]; + applicant.owId = null; + applicant.name = null; + applicant.phone = null; + applicant.grade = null; + applicant.email = null; + applicant.about = null; + applicant.selectedTimes = null; + applicant.date = null; } const isSelectedCommitteePresent = diff --git a/lib/mongo/committees.ts b/lib/mongo/committees.ts index e978fef6..aa83707b 100644 --- a/lib/mongo/committees.ts +++ b/lib/mongo/committees.ts @@ -1,4 +1,4 @@ -import { Collection, Db, MongoClient, ObjectId, UpdateResult } from "mongodb"; +import { Collection, Db, MongoClient, ObjectId } from "mongodb"; import clientPromise from "./mongodb"; import { committeeInterviewType } from "../types/types"; diff --git a/lib/mongo/periods.ts b/lib/mongo/periods.ts index dccc87d8..e40814b3 100644 --- a/lib/mongo/periods.ts +++ b/lib/mongo/periods.ts @@ -59,19 +59,19 @@ export const getCurrentPeriods = async () => { try { if (!periods) await init(); - const currentDate = new Date().toISOString(); + const now = new Date(); const filter = { $or: [ { - // Check if current ISO date string is within the application period - "applicationPeriod.start": { $lte: currentDate }, - "applicationPeriod.end": { $gte: currentDate }, + // Check if current date is within the application period + "applicationPeriod.start": { $lte: now }, + "applicationPeriod.end": { $gte: now }, }, { - // Check if current ISO date string is within the interview period - "interviewPeriod.start": { $lte: currentDate }, - "interviewPeriod.end": { $gte: currentDate }, + // Check if current date is within the interview period + "interviewPeriod.start": { $lte: now }, + "interviewPeriod.end": { $gte: now }, }, ], }; diff --git a/lib/sendInterviewTimes/formatInterviewEmail.ts b/lib/sendInterviewTimes/formatInterviewEmail.ts index b6b91d9f..80282cf1 100644 --- a/lib/sendInterviewTimes/formatInterviewEmail.ts +++ b/lib/sendInterviewTimes/formatInterviewEmail.ts @@ -1,3 +1,4 @@ +import { compareAsc } from "date-fns"; import { emailApplicantInterviewType, emailCommitteeInterviewType, @@ -11,25 +12,22 @@ export const formatApplicantInterviewEmail = ( ) => { let emailBody = `

Hei ${applicant.applicantName},

Her er dine intervjutider for ${applicant.period_name}: