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}:
`;
- applicant.committees.sort((a, b) => {
- return (
- new Date(a.interviewTime.start).getTime() -
- new Date(b.interviewTime.start).getTime()
- );
- });
+ // Sort committees by interview start time
+ applicant.committees.sort((a, b) =>
+ compareAsc(a.interviewTime.start, b.interviewTime.start)
+ );
applicant.committees.forEach((committee) => {
emailBody += `- Komité: ${changeDisplayName(
committee.committeeName
)}
`;
- if (committee.interviewTime.start !== "Ikke satt") {
+ if (committee.interviewTime.start != null) {
emailBody += `Tid: ${formatDateHours(
committee.interviewTime.start,
committee.interviewTime.end
)}
`;
- }
- if (committee.interviewTime.start === "Ikke satt") {
+ } else {
emailBody += `Tid: Ikke satt. Komitéen vil ta kontakt med deg for å avtale tidspunkt.
`;
}
@@ -50,25 +48,21 @@ export const formatCommitteeInterviewEmail = (
committee.applicants.length
} søkere:
`;
- committee.applicants.sort((a, b) => {
- return (
- new Date(a.interviewTime.start).getTime() -
- new Date(b.interviewTime.start).getTime()
- );
- });
+ // Sort applicants by interview start time
+ committee.applicants.sort((a, b) =>
+ compareAsc(a.interviewTime.start, b.interviewTime.start)
+ );
committee.applicants.forEach((applicant) => {
emailBody += `- Navn: ${applicant.applicantName}
`;
emailBody += `Telefon: ${applicant.applicantPhone}
`;
- if (applicant.interviewTime.start !== "Ikke satt") {
+ if (applicant.interviewTime.start != null) {
emailBody += `Tid: ${formatDateHours(
applicant.interviewTime.start,
applicant.interviewTime.end
)}
`;
- }
-
- if (applicant.interviewTime.start === "Ikke satt") {
+ } else {
emailBody += `Tid: Ikke satt. Ta kontakt med søker for å avtale tidspunkt.`;
}
emailBody += `Rom: ${applicant.interviewTime.room}
`;
diff --git a/lib/sendInterviewTimes/formatInterviewSMS.ts b/lib/sendInterviewTimes/formatInterviewSMS.ts
index e9b42822..08e5de91 100644
--- a/lib/sendInterviewTimes/formatInterviewSMS.ts
+++ b/lib/sendInterviewTimes/formatInterviewSMS.ts
@@ -1,3 +1,4 @@
+import { compareAsc } from "date-fns";
import { emailApplicantInterviewType } from "../types/types";
import { formatDateHours } from "../utils/dateUtils";
import { changeDisplayName } from "../utils/toString";
@@ -5,24 +6,20 @@ import { changeDisplayName } from "../utils/toString";
export const formatInterviewSMS = (applicant: emailApplicantInterviewType) => {
let phoneBody = `Hei ${applicant.applicantName}, her er dine intervjutider for ${applicant.period_name}: \n \n`;
- applicant.committees.sort((a, b) => {
- return (
- new Date(a.interviewTime.start).getTime() -
- new Date(b.interviewTime.start).getTime()
- );
- });
+ // Sort committees by interview start time
+ applicant.committees.sort((a, b) =>
+ compareAsc(a.interviewTime.start, b.interviewTime.start)
+ );
applicant.committees.forEach((committee) => {
phoneBody += `Komité: ${changeDisplayName(committee.committeeName)} \n`;
- if (committee.interviewTime.start !== "Ikke satt") {
+ if (committee.interviewTime.start != null) {
phoneBody += `Tid: ${formatDateHours(
committee.interviewTime.start,
committee.interviewTime.end
)}\n`;
- }
-
- if (committee.interviewTime.start === "Ikke satt") {
+ } else {
phoneBody += `Tid: Ikke satt. Komitéen vil ta kontakt for å avtale tidspunkt. \n`;
}
diff --git a/lib/types/types.ts b/lib/types/types.ts
index dced56e7..897012c0 100644
--- a/lib/types/types.ts
+++ b/lib/types/types.ts
@@ -4,6 +4,10 @@ export type DeepPartial = {
[P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
};
+export type Nullable = {
+ [K in keyof T]: T[K] | null;
+}
+
export type preferencesType = {
first: string;
second: string;
@@ -27,8 +31,8 @@ export type applicantType = {
optionalCommittees: string[];
selectedTimes: [
{
- start: string;
- end: string;
+ start: Date;
+ end: Date;
},
];
date: Date;
@@ -69,8 +73,8 @@ export type periodType = {
};
export type AvailableTime = {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
room: string;
};
@@ -80,7 +84,7 @@ export type committeeInterviewType = {
committee: string;
committeeEmail: string;
availabletimes: AvailableTime[];
- timeslot: string;
+ timeslot: string; // duration of each interview in minutes
message: string;
};
@@ -110,8 +114,8 @@ export interface OwGroup {
export type algorithmType = {
applicantId: string;
interviews: {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
committeeName: string;
room: string;
}[];
@@ -132,8 +136,8 @@ export type emailCommitteeInterviewType = {
applicantPhone: string;
applicantEmail: string;
interviewTime: {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
room: string;
};
}[];
@@ -149,8 +153,8 @@ export type emailApplicantInterviewType = {
committeeName: string;
committeeEmail: string;
interviewTime: {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
room: string;
};
}[];
diff --git a/lib/utils/dateUtils.ts b/lib/utils/dateUtils.ts
index 8ab5de14..ab9f1cfd 100644
--- a/lib/utils/dateUtils.ts
+++ b/lib/utils/dateUtils.ts
@@ -1,55 +1,32 @@
-export const formatDate = (inputDate: undefined | Date) => {
- const date = new Date(inputDate || "");
+import { isBefore, isAfter, isEqual } from 'date-fns';
+import { formatInTimeZone } from 'date-fns-tz';
+import { nb } from 'date-fns/locale';
- const day = date.getDate().toString().padStart(2, "0");
- const month = (date.getMonth() + 1).toString().padStart(2, "0");
- const year = date.getFullYear();
- const hours = date.getHours().toString().padStart(2, "0");
- const minutes = date.getMinutes().toString().padStart(2, "0");
+export const timezone = 'Europe/Oslo'; // Norwegian Time Zone
- return `${day}.${month}.${year}`; // - ${hours}:${minutes}
+export const formatDate = (inputDate: undefined | Date) => {
+ if (!inputDate) return "";
+
+ return formatInTimeZone(inputDate, timezone, 'dd.MM.yyyy'); // - HH:mm
};
-export const formatDateHours = (
- start: string,
- end: string
-) => {
- const startDate = new Date(Date.parse(start));
-
- const startTime = start.split("T")[1].slice(0, 5);
- const endTime = end.split("T")[1].slice(0, 5);
+export const formatDateHours = (start: Date, end: Date): string => {
- return `${formatDateNorwegian(
- startDate
- )}, ${startTime} til ${endTime}`;
+ const dateStr = formatInTimeZone(start, timezone, 'd. MMMM yyyy', { locale: nb });
+ const startTime = formatInTimeZone(start, timezone, 'HH:mm');
+ const endTime = formatInTimeZone(end, timezone, 'HH:mm');
+
+ return `${dateStr}, ${startTime} til ${endTime}`;
};
-export const formatDateNorwegian = (inputDate?: Date | string) => {
+export const formatDateNorwegian = (inputDate: Date): string => {
if (!inputDate) return "";
+
+ return formatInTimeZone(inputDate, timezone, 'd. MMM', { locale: nb });
+};
- let date: Date;
- if (inputDate instanceof Date) {
- date = inputDate;
- } else {
- date = new Date(inputDate);
- }
-
- const day = date.getUTCDate().toString().padStart(2, "0");
- const monthsNorwegian = [
- "jan",
- "feb",
- "mar",
- "apr",
- "mai",
- "jun",
- "jul",
- "aug",
- "sep",
- "okt",
- "nov",
- "des",
- ];
- const month = monthsNorwegian[date.getUTCMonth()];
+export const isBeforeOrEqual = (date: Date, dateToCompare: Date) =>
+ isBefore(date, dateToCompare) || isEqual(date, dateToCompare);
- return `${day}. ${month}`;
-};
+export const isAfterOrEqual = (date: Date, dateToCompare: Date) =>
+ isAfter(date, dateToCompare) || isEqual(date, dateToCompare);
\ No newline at end of file
diff --git a/lib/utils/validateApplication.ts b/lib/utils/validateApplication.ts
index eada7a89..0fc6e012 100644
--- a/lib/utils/validateApplication.ts
+++ b/lib/utils/validateApplication.ts
@@ -71,8 +71,8 @@ export const validateApplication = (applicationData: any) => {
}
for (const time of selectedTimes) {
- const startTime = new Date(time.start);
- const endTime = new Date(time.end);
+ const startTime = time.start;
+ const endTime = time.end;
if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
toast.error("Ugyldig start- eller sluttid");
return false;
diff --git a/lib/utils/validators.ts b/lib/utils/validators.ts
index e4433c4f..1eefcd56 100644
--- a/lib/utils/validators.ts
+++ b/lib/utils/validators.ts
@@ -4,6 +4,7 @@ import {
periodType,
preferencesType,
} from "../types/types";
+import { isBeforeOrEqual } from "./dateUtils";
export const isApplicantType = (
applicant: applicantType,
@@ -59,14 +60,12 @@ export const isApplicantType = (
const hasSelectedTimes =
Array.isArray(applicant.selectedTimes) &&
applicant.selectedTimes.every(
- (time: { start: string; end: string }) =>
- typeof time.start === "string" &&
- typeof time.end === "string" &&
- new Date(time.start) >= interviewPeriodStart &&
- new Date(time.start) <= interviewPeriodEnd &&
- new Date(time.end) <= interviewPeriodEnd &&
- new Date(time.end) >= interviewPeriodStart &&
- new Date(time.start) < new Date(time.end)
+ (time: { start: Date; end: Date }) =>
+ time.start >= interviewPeriodStart &&
+ time.start <= interviewPeriodEnd &&
+ time.end <= interviewPeriodEnd &&
+ time.end >= interviewPeriodStart &&
+ time.start < time.end
);
const periodOptionalCommittees = period.optionalCommittees.map((committee) =>
@@ -117,7 +116,8 @@ export const validateCommittee = (data: any, period: periodType): boolean => {
const isPeriodNameValid = data.periodId === String(period._id);
- const isBeforeDeadline = new Date() <= new Date(period.applicationPeriod.end);
+ const now = new Date();
+ const isBeforeDeadline = isBeforeOrEqual(now, period.applicationPeriod.end);
const committeeExists =
period.committees.some((committee) => {
@@ -128,15 +128,15 @@ export const validateCommittee = (data: any, period: periodType): boolean => {
});
const isWithinInterviewPeriod = data.availabletimes.every(
- (time: { start: string; end: string }) => {
- const startTime = new Date(time.start);
- const endTime = new Date(time.end);
+ (time: { start: Date; end: Date }) => {
+ const startTime = time.start;
+ const endTime = time.end;
return (
- startTime >= new Date(period.interviewPeriod.start) &&
- startTime <= new Date(period.interviewPeriod.end) &&
- endTime <= new Date(period.interviewPeriod.end) &&
- endTime >= new Date(period.interviewPeriod.start) &&
+ startTime >= period.interviewPeriod.start &&
+ startTime <= period.interviewPeriod.end &&
+ endTime <= period.interviewPeriod.end &&
+ endTime >= period.interviewPeriod.start &&
startTime < endTime
);
}
@@ -152,31 +152,27 @@ export const validateCommittee = (data: any, period: periodType): boolean => {
};
export const isPeriodType = (data: any): data is periodType => {
- const isDateString = (str: any): boolean => {
- return typeof str === "string" && !isNaN(Date.parse(str));
- };
+ const isDateDate = (date: any): boolean => {
+ return date instanceof Date && !isNaN(date.getTime());
+ }
const isValidPeriod = (period: any): boolean => {
return (
typeof period === "object" &&
period !== null &&
- isDateString(period.start) &&
- isDateString(period.end)
+ isDateDate(period.start) &&
+ isDateDate(period.end)
);
};
- const isChronological = (start: string, end: string): boolean => {
- return new Date(start) <= new Date(end);
- };
-
const arePeriodsValid = (
applicationPeriod: any,
interviewPeriod: any
): boolean => {
return (
- isChronological(applicationPeriod.start, applicationPeriod.end) &&
- isChronological(interviewPeriod.start, interviewPeriod.end) &&
- new Date(applicationPeriod.end) <= new Date(interviewPeriod.start)
+ isBeforeOrEqual(applicationPeriod.start, applicationPeriod.end) &&
+ isBeforeOrEqual(interviewPeriod.start, interviewPeriod.end) &&
+ isBeforeOrEqual(applicationPeriod.end, interviewPeriod.start)
);
};
diff --git a/package-lock.json b/package-lock.json
index 973ad124..18f8226f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,8 @@
"@trpc/tanstack-react-query": "^11.6.0",
"@types/mongodb": "^4.0.7",
"@vercel/analytics": "^1.1.2",
+ "date-fns": "^4.1.0",
+ "date-fns-tz": "^3.2.0",
"dompurify": "^3.3.1",
"lucide-react": "^0.441.0",
"mongodb": "^6.1.0",
@@ -2705,6 +2707,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-tz": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
+ "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "date-fns": "^3.0.0 || ^4.0.0"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
diff --git a/package.json b/package.json
index b3a00bfc..e6fdb1d3 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,8 @@
"@trpc/tanstack-react-query": "^11.6.0",
"@types/mongodb": "^4.0.7",
"@vercel/analytics": "^1.1.2",
+ "date-fns": "^4.1.0",
+ "date-fns-tz": "^3.2.0",
"dompurify": "^3.3.1",
"lucide-react": "^0.441.0",
"mongodb": "^6.1.0",
diff --git a/pages/admin/new-period.tsx b/pages/admin/new-period.tsx
index 48833a38..d8244684 100644
--- a/pages/admin/new-period.tsx
+++ b/pages/admin/new-period.tsx
@@ -83,14 +83,14 @@ const NewPeriod = () => {
start,
end,
}: {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
}) => {
setPeriodData((prevData) => ({
...prevData,
applicationPeriod: {
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
+ start: start,
+ end: end,
},
}));
};
@@ -99,14 +99,14 @@ const NewPeriod = () => {
start,
end,
}: {
- start: string;
- end: string;
+ start: Date;
+ end: Date;
}) => {
setPeriodData((prevData) => ({
...prevData,
interviewPeriod: {
- start: start ? new Date(start) : undefined,
- end: end ? new Date(end) : undefined,
+ start: start,
+ end: end,
},
}));
};
diff --git a/pages/api/applicants/[period-id]/[id].ts b/pages/api/applicants/[period-id]/[id].ts
index 3c391e1b..e2799a5f 100644
--- a/pages/api/applicants/[period-id]/[id].ts
+++ b/pages/api/applicants/[period-id]/[id].ts
@@ -7,10 +7,10 @@ import { getServerSession } from "next-auth";
import { authOptions } from "../../auth/[...nextauth]";
import {
hasSession,
- isAdmin,
checkOwId,
} from "../../../../lib/utils/apiChecks";
import { getPeriodById } from "../../../../lib/mongo/periods";
+import { isBefore } from "date-fns";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -40,9 +40,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
}
return res.status(200).json({ exists, application });
} else if (req.method === "DELETE") {
- const currentDate = new Date().toISOString();
+ const now = new Date();
- if (new Date(period.applicationPeriod.end) < new Date(currentDate)) {
+ if (isBefore(period.applicationPeriod.end, now)) {
return res.status(403).json({ error: "Application period is over" });
}
diff --git a/pages/api/applicants/index.ts b/pages/api/applicants/index.ts
index ee6440cb..20d06878 100644
--- a/pages/api/applicants/index.ts
+++ b/pages/api/applicants/index.ts
@@ -8,6 +8,7 @@ import { isApplicantType } from "../../../lib/utils/validators";
import { isAdmin, hasSession, checkOwId } from "../../../lib/utils/apiChecks";
import { sendConfirmationSMS } from "../../../lib/sms/sendConfirmationSMS";
import { sendConfirmationEmail } from "../../../lib/email/sendConfirmationEmail";
+import { isAfter, isBefore } from "date-fns";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -25,7 +26,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "POST") {
const requestBody: applicantType = req.body;
- requestBody.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegain time (GMT+2)
const { period } = await getPeriodById(String(requestBody.periodId));
@@ -44,7 +44,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const applicationEnd = period.applicationPeriod.end;
// Check if the current time is within the application period
- if (now < applicationStart || now > applicationEnd) {
+ if (isBefore(now, applicationStart) || isAfter(now, applicationEnd)) {
return res
.status(400)
.json({ error: "Not within the application period" });
diff --git a/pages/api/committees/times/[period-id]/[committee].ts b/pages/api/committees/times/[period-id]/[committee].ts
index 573b31ed..4f62f3ab 100644
--- a/pages/api/committees/times/[period-id]/[committee].ts
+++ b/pages/api/committees/times/[period-id]/[committee].ts
@@ -7,6 +7,7 @@ import { getServerSession } from "next-auth";
import { authOptions } from "../../../auth/[...nextauth]";
import { hasSession, isInCommitee } from "../../../../../lib/utils/apiChecks";
import { getPeriodById } from "../../../../../lib/mongo/periods";
+import { isAfter } from "date-fns";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -50,7 +51,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(400).json({ error: "Invalid periodId" });
}
- if (new Date() > new Date(period.applicationPeriod.end)) {
+ const now = new Date();
+
+ if (isAfter(now, period.interviewPeriod.end)) {
return res.status(400).json({ error: "Application period has ended" });
}
diff --git a/pages/api/committees/times/[period-id]/index.ts b/pages/api/committees/times/[period-id]/index.ts
index 297a0016..5ffeb089 100644
--- a/pages/api/committees/times/[period-id]/index.ts
+++ b/pages/api/committees/times/[period-id]/index.ts
@@ -9,6 +9,7 @@ import {
} from "../../../../../lib/utils/validators";
import { getPeriodById } from "../../../../../lib/mongo/periods";
import { committeeInterviewType } from "../../../../../lib/types/types";
+import { isAfter } from "date-fns";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getServerSession(req, res, authOptions);
@@ -35,7 +36,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(400).json({ error: "Invalid periodId" });
}
- if (new Date() > new Date(period.applicationPeriod.end)) {
+ const now = new Date();
+
+ if (isAfter(now, period.interviewPeriod.end)) {
return res.status(400).json({ error: "Application period has ended" });
}
diff --git a/pages/apply/[period-id].tsx b/pages/apply/[period-id].tsx
index 2f6836e3..be66a641 100644
--- a/pages/apply/[period-id].tsx
+++ b/pages/apply/[period-id].tsx
@@ -25,6 +25,7 @@ import {
import ErrorPage from "../../components/ErrorPage";
import { MainTitle, SimpleTitle } from "../../components/Typography";
import { getCommitteeDisplayNameFactory } from "../../lib/utils/getCommitteeDisplayNameFactory";
+import { isBefore } from "date-fns";
const Application: NextPage = () => {
const queryClient = useQueryClient();
@@ -116,9 +117,9 @@ const Application: NextPage = () => {
setPeriod(periodData.period);
- const currentDate = new Date().toISOString();
+ const now = new Date();
if (
- new Date(periodData.period.applicationPeriod.end) < new Date(currentDate)
+ isBefore(periodData.period.applicationPeriod.end, now)
) {
setIsApplicationPeriodOver(true);
}
@@ -160,7 +161,7 @@ const Application: NextPage = () => {
return ;
if (periodIsError || applicantIsError) return ;
- if (!periodData?.exists)
+ if (!periodData?.exists || !period)
return ;
if (fetchedApplicationData?.exists)
@@ -173,7 +174,7 @@ const Application: NextPage = () => {
Du vil få enda en e-post med intervjutider når søknadsperioden er over
- (rundt {formatDateNorwegian(period?.applicationPeriod?.end)}).
+ (rundt {formatDateNorwegian(period?.applicationPeriod.end)}).
(Hvis du ikke finner e-posten din, sjekk søppelpost- eller
diff --git a/pages/apply/index.tsx b/pages/apply/index.tsx
index 4a7867bf..78851fd9 100644
--- a/pages/apply/index.tsx
+++ b/pages/apply/index.tsx
@@ -7,6 +7,7 @@ import { fetchPeriods } from "../../lib/api/periodApi";
import { periodType } from "../../lib/types/types";
import { PeriodSkeletonPage } from "../../components/PeriodSkeleton";
import { SimpleTitle } from "../../components/Typography";
+import { isAfterOrEqual, isBeforeOrEqual } from "../../lib/utils/dateUtils";
const Apply = () => {
const [currentPeriods, setCurrentPeriods] = useState([]);
@@ -24,23 +25,19 @@ const Apply = () => {
useEffect(() => {
if (!periodsData) return;
- const today = new Date();
+ const now = new Date();
setCurrentPeriods(
periodsData.periods.filter((period: periodType) => {
- const startDate = new Date(period.applicationPeriod.start || "");
- const endDate = new Date(period.applicationPeriod.end || "");
+ const startDate = period.applicationPeriod.start;
+ const endDate = period.applicationPeriod.end;
- return startDate <= today && endDate >= today;
+ return isBeforeOrEqual(startDate, now) && isAfterOrEqual(endDate, now);
})
);
setUpcomingPeriods(
- periodsData.periods.filter((period: periodType) => {
- const startDate = new Date(period.applicationPeriod.start || "");
-
- return startDate >= today
- })
+ periodsData.periods.filter((period: periodType) => isAfterOrEqual(period.applicationPeriod.start, now))
)
}, [periodsData]);
diff --git a/pages/committee/[period-id]/[committee]/index.tsx b/pages/committee/[period-id]/[committee]/index.tsx
index b5f51989..510147d0 100644
--- a/pages/committee/[period-id]/[committee]/index.tsx
+++ b/pages/committee/[period-id]/[committee]/index.tsx
@@ -9,7 +9,6 @@ import { useRouter } from "next/router";
import ApplicantsOverview from "../../../../components/applicantoverview/ApplicantsOverview";
import {
CalendarIcon,
- InboxIcon,
UserGroupIcon,
} from "@heroicons/react/24/solid";
import { Tabs } from "../../../../components/Tabs";
@@ -23,6 +22,7 @@ import { fetchPeriodById } from "../../../../lib/api/periodApi";
import ErrorPage from "../../../../components/ErrorPage";
import { fetchCommitteeTimes } from "../../../../lib/api/committeesApi";
import { MainTitle, SimpleTitle } from "../../../../components/Typography";
+import { addDays, isBefore } from "date-fns";
const CommitteeApplicantOverview: NextPage = () => {
const { data: session } = useSession();
@@ -82,15 +82,11 @@ const CommitteeApplicantOverview: NextPage = () => {
if (periodIsError || interviewTimesIsError) return ;
if (!hasAccess) return ;
- const interviewPeriodEnd = period?.interviewPeriod.end
- ? new Date(period.interviewPeriod.end)
- : null;
-
// Satt frist til 14 dager etter intervju perioden, så får man ikke tilgang
- const interviewAccessExpired =
- interviewPeriodEnd &&
- interviewPeriodEnd.getTime() + 14 * 24 * 60 * 60 * 1000 <
- new Date().getTime();
+
+ const now = new Date();
+ const fourteenDaysAfterInterview = addDays(period.interviewPeriod.end, 14);
+ const interviewAccessExpired = isBefore(fourteenDaysAfterInterview, now);
if (interviewAccessExpired) {
return (
diff --git a/pages/committees.tsx b/pages/committees.tsx
index b658841c..5c9f14e9 100644
--- a/pages/committees.tsx
+++ b/pages/committees.tsx
@@ -11,6 +11,7 @@ import { UsersIcon } from "@heroicons/react/24/outline";
import { Tabs } from "../components/Tabs";
import { UserIcon, BellAlertIcon } from "@heroicons/react/24/solid";
import { shuffleList, partition } from "../lib/utils/arrays";
+import { isAfterOrEqual, isBeforeOrEqual } from "../lib/utils/dateUtils";
// Page Component
export default function Committees() {
@@ -103,17 +104,17 @@ export default function Committees() {
},
...(committeesInActivePeriod.length > 0
? [
- {
- title: "Har opptak",
- icon: ,
- content: (
-
- ),
- },
- ]
+ {
+ title: "Har opptak",
+ icon: ,
+ content: (
+
+ ),
+ },
+ ]
: []),
]}
/>
@@ -165,12 +166,12 @@ const committeeIsInActivePeriod = (
) => {
if (!Array.isArray(periods)) return false;
- const today = new Date();
+ const now = new Date();
const activePeriods = periods.filter((period) => {
- const applicationStart = new Date(period.applicationPeriod.start);
- const applicationEnd = new Date(period.applicationPeriod.end);
- return applicationStart <= today && applicationEnd >= today;
+ const applicationStart = period.applicationPeriod.start;
+ const applicationEnd = period.applicationPeriod.end;
+ return isBeforeOrEqual(applicationStart, now) && isAfterOrEqual(applicationEnd, now);
});
// Bankom is always active, since you can be a representative of bankom from each committee
@@ -191,12 +192,12 @@ const committeeIsCurrentlyInterviewing = (
) => {
if (!Array.isArray(periods)) return false;
- const today = new Date();
+ const now = new Date();
const periodsWithInterviewsCurrently = periods.filter((period) => {
- const interviewStart = new Date(period.interviewPeriod.start);
- const interviewEnd = new Date(period.interviewPeriod.end);
- return interviewStart <= today && interviewEnd >= today;
+ const interviewStart = period.interviewPeriod.start;
+ const interviewEnd = period.interviewPeriod.end;
+ return isBeforeOrEqual(interviewStart, now) && isAfterOrEqual(interviewEnd, now);
});
// Bankom is always active, since you can be a representative of bankom from each committee