diff --git a/components/form/ApplicationForm.tsx b/components/form/ApplicationForm.tsx index e32a968c..a50d5e9f 100644 --- a/components/form/ApplicationForm.tsx +++ b/components/form/ApplicationForm.tsx @@ -3,7 +3,11 @@ import RadioInput from "./RadioInput"; import TextAreaInput from "./TextAreaInput"; import SelectInput from "./SelectInput"; import Line from "./Line"; -import { DeepPartial, applicantType } from "../../lib/types/types"; +import { + DeepPartial, + applicantType, + preferencesType, +} from "../../lib/types/types"; import { changeDisplayName } from "../../lib/utils/toString"; import { useEffect, useState } from "react"; import CustomPhoneInput from "./CustomPhoneInput"; @@ -14,6 +18,7 @@ interface Props { setApplicationData: Function; availableCommittees: string[]; optionalCommittees: string[]; + isEditing?: boolean; } export const ApplicationForm = (props: Props) => { @@ -121,6 +126,7 @@ export const ApplicationForm = (props: Props) => { props.setApplicationData({ ...props.applicationData, about: value }) } @@ -136,6 +142,9 @@ export const ApplicationForm = (props: Props) => { 2 ? "Førstevalg" : "Velg komite"} updateInputValues={(value: string) => @@ -152,6 +161,10 @@ export const ApplicationForm = (props: Props) => { {availableCommittees.length > 2 && ( props.setApplicationData({ @@ -167,6 +180,10 @@ export const ApplicationForm = (props: Props) => { {availableCommittees.length > 3 && ( props.setApplicationData({ @@ -186,8 +203,17 @@ export const ApplicationForm = (props: Props) => { ["Nei", "nei"], ["Usikker (gjerne spør om mer info på intervjuet)", "kanskje"], ]} + defaultValue={ + props.isEditing + ? props.applicationData.bankom === "ja" + ? "ja" + : props.applicationData.bankom === "nei" + ? "nei" + : "kanskje" + : undefined + } label="Er du interessert i å være økonomiansvarlig i komiteen (tilleggsverv i Bankom)?" - updateInputValues={(value: boolean) => + updateInputValues={(value: string) => props.setApplicationData({ ...props.applicationData, bankom: value, @@ -197,6 +223,15 @@ export const ApplicationForm = (props: Props) => { {optionalCommittees.map((committee) => (
void; +} + +const ApplicationEditModal = ({ + availableCommittees, + optionalCommittees, + period, + originalApplicationData, + setIsEditing, +}: Props) => { + const queryClient = useQueryClient(); + const [activeTab, setActiveTab] = useState(0); + const [applicationData, setApplicationData] = useState< + DeepPartial + >({ + owId: originalApplicationData.owId, + name: originalApplicationData.name, + email: originalApplicationData.email, + phone: originalApplicationData.phone, + grade: originalApplicationData.grade, + about: originalApplicationData.about, + bankom: originalApplicationData.bankom, + + optionalCommittees: originalApplicationData.optionalCommittees, + preferences: originalApplicationData.preferences, + selectedTimes: originalApplicationData.selectedTimes, + }); + + const submitEdit = () => { + if (!validateApplication(applicationData)) return; + + editApplicationMutation.mutate({ + applicant: applicationData as applicantType, + periodId: period._id.toString(), + owId: originalApplicationData.owId, + }); + }; + + const editApplicationMutation = useMutation({ + mutationFn: editApplicant, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ + "applicant", + period._id.toString(), + originalApplicationData.owId, + ], + }); + toast.success("Søknad oppdatert successfully"); + setIsEditing(false); + }, + onError: () => toast.error("Det skjedde en feil, vennligst prøv igjen"), + }); + + return ( + <> +
+
+
+

+ Rediger søknad +

+
+

+ Du kan redigere søknaden din her. Husk å lagre endringene før du + lukker. +

+
+
+
+ , + content: ( + <> + +
+
+ + ), + }, + { + title: "Intervjutider", + icon: , + content: ( +
+ +
+ ), + }, + ]} + /> +
+
+
+
+
+ + ); +}; + +export default ApplicationEditModal; diff --git a/components/form/RadioInput.tsx b/components/form/RadioInput.tsx index ba237fc6..f462a7c9 100644 --- a/components/form/RadioInput.tsx +++ b/components/form/RadioInput.tsx @@ -1,37 +1,52 @@ interface Props { updateInputValues: Function; label: string; - values: any[][]; + values: [string, string][]; + defaultValue?: string; } -const RadioInput = (props: Props) => { - const handleInputChange = (e: React.BaseSyntheticEvent) => { - props.updateInputValues(e.target.value); +const RadioInput = ({ + updateInputValues, + label, + values, + defaultValue, +}: Props) => { + const handleInputChange = (e: React.ChangeEvent) => { + updateInputValues(e.target.value); }; return (
- -
handleInputChange(e)}> - {props.values.map((option, index) => { - return ( -
- - -
- ); - })} -
+
+ + {label} + +
+ {values.map(([displayText, value], index) => { + const inputId = `${label}-${value}-${index}`; + return ( +
+ + +
+ ); + })} +
+
); }; diff --git a/lib/api/applicantApi.ts b/lib/api/applicantApi.ts index ac0ef8c5..e4360f40 100644 --- a/lib/api/applicantApi.ts +++ b/lib/api/applicantApi.ts @@ -44,6 +44,30 @@ export const createApplicant = async (applicant: applicantType) => { return data; }; +export const editApplicant = async ({ + applicant, + periodId, + owId, +}: { + applicant: applicantType; + periodId: string; + owId: string; +}) => { + const response = await fetch(`/api/applicants/${periodId}/${owId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ application: applicant }), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || "Unknown error occurred"); + } + return data; +}; + export const deleteApplicant = async ({ periodId, owId, diff --git a/lib/mongo/applicants.ts b/lib/mongo/applicants.ts index 212aff32..10472eac 100644 --- a/lib/mongo/applicants.ts +++ b/lib/mongo/applicants.ts @@ -225,6 +225,31 @@ export const getApplicantsForCommittee = async ( } }; +export const editApplication = async ( + owId: string, + periodId: string | ObjectId, + newApplicationData: applicantType +) => { + try { + if (!applicants) await init(); + + const result = await applicants.findOneAndUpdate( + { owId: owId, periodId: periodId }, + { $set: newApplicationData }, + { returnDocument: "after" } + ); + + if (result) { + return { applicant: result }; + } else { + return { error: "Application not found" }; + } + } catch (error) { + console.error(error); + return { error: "Failed to edit application" }; + } +}; + export const deleteApplication = async ( owId: string, periodId: string | ObjectId diff --git a/pages/api/applicants/[period-id]/[id].ts b/pages/api/applicants/[period-id]/[id].ts index 7d77e73a..2297389c 100644 --- a/pages/api/applicants/[period-id]/[id].ts +++ b/pages/api/applicants/[period-id]/[id].ts @@ -1,6 +1,7 @@ import { NextApiRequest, NextApiResponse } from "next"; import { deleteApplication, + editApplication, getApplication, } from "../../../../lib/mongo/applicants"; import { getServerSession } from "next-auth"; @@ -11,6 +12,8 @@ import { checkOwId, } from "../../../../lib/utils/apiChecks"; import { getPeriodById } from "../../../../lib/mongo/periods"; +import { isApplicantType } from "../../../../lib/utils/validators"; +import { applicantType } from "../../../../lib/types/types"; const handler = async (req: NextApiRequest, res: NextApiResponse) => { const session = await getServerSession(req, res, authOptions); @@ -49,6 +52,45 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { error } = await deleteApplication(id, periodId); if (error) throw new Error(error); return res.status(204).end(); + } else if (req.method === "PUT") { + const { application } = req.body; + + if (!application) { + return res.status(400).json({ error: "Missing application data" }); + } + + const { period } = await getPeriodById(periodId); + + if (!period) { + return res.status(400).json({ error: "Invalid period id" }); + } + + application.periodId = periodId; + application.date = new Date(new Date().getTime() + 60 * 60 * 2000); // add date with norwegain time (GMT+2) + + if (!isApplicantType(application, period)) { + return res.status(400).json({ error: "Invalid data format" }); + } + + const now = new Date(); + const applicationStart = period.applicationPeriod.start; + const applicationEnd = period.applicationPeriod.end; + + if (now < applicationStart || now > applicationEnd) { + return res + .status(400) + .json({ error: "Not within the application period" }); + } + + if (!isApplicantType(application, period)) { + return res.status(400).json({ error: "Invalid data format" }); + } + + const { error } = await editApplication(id, periodId, application); + if (error) throw new Error(error); + return res + .status(200) + .json({ message: "Application updated successfully" }); } } catch (error) { if (error instanceof Error) { @@ -57,7 +99,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(500).json("Unexpected error occurred"); } - res.setHeader("Allow", ["GET", "DELETE"]); + res.setHeader("Allow", ["GET", "DELETE", "PUT"]); res.status(405).end(`Method ${req.method} is not allowed.`); }; diff --git a/pages/apply/[period-id].tsx b/pages/apply/[period-id].tsx index 73afadb8..ab5bea73 100644 --- a/pages/apply/[period-id].tsx +++ b/pages/apply/[period-id].tsx @@ -24,6 +24,7 @@ import { } from "../../lib/api/applicantApi"; import ErrorPage from "../../components/ErrorPage"; import { MainTitle, SimpleTitle } from "../../components/Typography"; +import ApplicationEditModal from "../../components/form/EditApplicationModal"; const Application: NextPage = () => { const queryClient = useQueryClient(); @@ -33,6 +34,7 @@ const Application: NextPage = () => { const applicantId = session?.user?.owId; const [activeTab, setActiveTab] = useState(0); + const [isEditing, setIsEditing] = useState(false); const [applicationData, setApplicationData] = useState< DeepPartial >({ @@ -150,6 +152,31 @@ const Application: NextPage = () => { if (!periodData?.exists) return ; + if (isEditing) { + return ( + <> + {fetchedApplicationData?.application && period && ( + + )} + {applicationData.phone && period && ( + + )} + + ); + } + if (fetchedApplicationData?.exists) return (
@@ -167,12 +194,20 @@ const Application: NextPage = () => { spam-mappen.)

{!isApplicationPeriodOver && ( -
)} + {fetchedApplicationData?.application && (
{
)} - {applicationData.phone && ( + {applicationData.phone && !fetchedApplicationData.application && (