From 81716d4a4f4cbc711715008d3ac3d9c603db0551 Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Thu, 27 Nov 2025 13:08:33 -0500 Subject: [PATCH] hooked up create event --- api/src/routes/calendar.ts | 17 +++++-- api/src/services/calendarService.ts | 7 +-- shared/schemas/calendarEventSchema.ts | 33 +++++++++++++ shared/types/calendar.ts | 10 ++-- ui/src/api/calendar.ts | 15 +++++- .../calendar/CreateCalendarEvent.vue | 49 +++++-------------- 6 files changed, 80 insertions(+), 51 deletions(-) create mode 100644 shared/schemas/calendarEventSchema.ts diff --git a/api/src/routes/calendar.ts b/api/src/routes/calendar.ts index cc6fa2f..6227df4 100644 --- a/api/src/routes/calendar.ts +++ b/api/src/routes/calendar.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus } from "../services/calendarService"; +import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus } from "../services/calendarService"; import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar"; const express = require('express'); @@ -63,9 +63,18 @@ r.get('/:id', async (req: Request, res: Response) => { }) -//post a new calendar event -r.post('/', async (req, res) => { - +//post a new calendar event +r.post('/', async (req: Request, res: Response) => { + try { + const member = req.user.id; + let event: CalendarEvent = req.body; + console.log(event); + createEvent(event); + res.sendStatus(200); + } catch (error) { + console.error('Failed to create event:', error); + res.status(500).json(error); + } }) module.exports.calendarRouter = r; \ No newline at end of file diff --git a/api/src/services/calendarService.ts b/api/src/services/calendarService.ts index 4e36206..d173722 100644 --- a/api/src/services/calendarService.ts +++ b/api/src/services/calendarService.ts @@ -1,11 +1,12 @@ import pool from '../db'; import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar" +import { toDateTime } from "@app/shared/utils/time" export async function createEvent(eventObject: Omit) { const sql = ` INSERT INTO calendar_events (name, start, end, location, color, description, creator) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) `; const params = [ eventObject.name, @@ -40,8 +41,8 @@ export async function updateEvent(eventObject: CalendarEvent) { const params = [ eventObject.name, - eventObject.start, - eventObject.end, + toDateTime(eventObject.start), + toDateTime(eventObject.end), eventObject.location, eventObject.color, eventObject.description ?? null, diff --git a/shared/schemas/calendarEventSchema.ts b/shared/schemas/calendarEventSchema.ts new file mode 100644 index 0000000..c0b4893 --- /dev/null +++ b/shared/schemas/calendarEventSchema.ts @@ -0,0 +1,33 @@ +import z from "zod"; + +const dateRe = /^\d{4}-\d{2}-\d{2}$/ // YYYY-MM-DD +const timeRe = /^(?:[01]\d|2[0-3]):[0-5]\d$/ // HH:mm (24h)\ + +export function parseLocalDateTime(dateStr: string, timeStr: string) { + // Construct a Date in the user's local timezone + const [y, m, d] = dateStr.split("-").map(Number) + const [hh, mm] = timeStr.split(":").map(Number) + return new Date(y, m - 1, d, hh, mm, 0, 0) +} + +export const calendarEventSchema = z.object({ + name: z.string().min(2, "Please enter at least 2 characters").max(100), + startDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), + startTime: z.string().regex(timeRe, "Use HH:mm (24h)"), + endDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), + endTime: z.string().regex(timeRe, "Use HH:mm (24h)"), + location: z.string().max(200).default(""), + color: z.string().regex(/^#([0-9A-Fa-f]{6})$/, "Use a hex color like #AABBCC"), + description: z.string().max(2000).default(""), + id: z.number().int().nonnegative().nullable().default(null), +}).superRefine((vals, ctx) => { + const start = parseLocalDateTime(vals.startDate, vals.startTime) + const end = parseLocalDateTime(vals.endDate, vals.endTime) + if (!(end > start)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "End must be after start", + path: ["endTime"], // attach to a visible field + }) + } +}) \ No newline at end of file diff --git a/shared/types/calendar.ts b/shared/types/calendar.ts index 6ff2695..2f9b7b7 100644 --- a/shared/types/calendar.ts +++ b/shared/types/calendar.ts @@ -1,15 +1,15 @@ export interface CalendarEvent { - id: number; + id?: number; name: string; start: Date; end: Date; location: string; color: string; description: string; - creator_id: number; - cancelled: boolean; - created_at: Date; - updated_at: Date; + creator_id?: number; + cancelled?: boolean; + created_at?: Date; + updated_at?: Date; creator_name?: string | null; eventSignups?: CalendarSignup[] | null; diff --git a/ui/src/api/calendar.ts b/ui/src/api/calendar.ts index 9b2ee88..35c10f0 100644 --- a/ui/src/api/calendar.ts +++ b/ui/src/api/calendar.ts @@ -68,7 +68,18 @@ export async function getCalendarEvent(id: number): Promise { } export async function createCalendarEvent(eventData: CalendarEvent) { + let res = await fetch(`${addr}/calendar`, { + method: "POST", + credentials: "include", + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(eventData) + }); + if (res.ok) { + return; + } else { + throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); + } } export async function editCalendarEvent(eventData: CalendarEvent) { @@ -84,14 +95,14 @@ export async function adminCancelCalendarEvent(eventID: number) { } export async function setCalendarEventAttendance(eventID: number, state: CalendarAttendance) { - let res = await fetch(`${addr}/calendar/ ${eventID}/attendance?state=${state}`, { + let res = await fetch(`${addr}/calendar/${eventID}/attendance?state=${state}`, { method: "POST", credentials: "include", }); if (res.ok) { return; - } else { + } else { throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`); } } \ No newline at end of file diff --git a/ui/src/components/calendar/CreateCalendarEvent.vue b/ui/src/components/calendar/CreateCalendarEvent.vue index d2e5355..78dfcad 100644 --- a/ui/src/components/calendar/CreateCalendarEvent.vue +++ b/ui/src/components/calendar/CreateCalendarEvent.vue @@ -21,11 +21,9 @@ import { } from "@/components/ui/form" import { Input } from "@/components/ui/input" import Textarea from "../ui/textarea/Textarea.vue" -import { CalendarEvent } from "@/api/calendar" +import { CalendarEvent } from "@shared/types/calendar" + -// ---------- helpers ---------- -const dateRe = /^\d{4}-\d{2}-\d{2}$/ // YYYY-MM-DD -const timeRe = /^(?:[01]\d|2[0-3]):[0-5]\d$/ // HH:mm (24h) function toLocalDateString(d: Date) { // yyyy-MM-dd with local time zone @@ -45,37 +43,11 @@ function roundToNextHour(d = new Date()) { t.setHours(t.getHours() + 1) return t } -function parseLocalDateTime(dateStr: string, timeStr: string) { - // Construct a Date in the user's local timezone - const [y, m, d] = dateStr.split("-").map(Number) - const [hh, mm] = timeStr.split(":").map(Number) - return new Date(y, m - 1, d, hh, mm, 0, 0) -} -// ---------- schema ---------- -const zEvent = z.object({ - name: z.string().min(2, "Please enter at least 2 characters").max(100), - startDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), - startTime: z.string().regex(timeRe, "Use HH:mm (24h)"), - endDate: z.string().regex(dateRe, "Use YYYY-MM-DD"), - endTime: z.string().regex(timeRe, "Use HH:mm (24h)"), - location: z.string().max(200).default(""), - color: z.string().regex(/^#([0-9A-Fa-f]{6})$/, "Use a hex color like #AABBCC"), - description: z.string().max(2000).default(""), - id: z.number().int().nonnegative().nullable().default(null), -}).superRefine((vals, ctx) => { - const start = parseLocalDateTime(vals.startDate, vals.startTime) - const end = parseLocalDateTime(vals.endDate, vals.endTime) - if (!(end > start)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "End must be after start", - path: ["endTime"], // attach to a visible field - }) - } -}) -const formSchema = toTypedSchema(zEvent) +import { calendarEventSchema, parseLocalDateTime } from '@shared/schemas/calendarEventSchema' +import { createCalendarEvent } from "@/api/calendar" +const formSchema = toTypedSchema(calendarEventSchema) // ---------- dialog state & defaults ---------- const dialogOpen = ref(false) @@ -107,7 +79,7 @@ watch(dialogOpen, (isOpen) => { } }) // ---------- submit ---------- -function onSubmit(vals: z.infer) { +function onSubmit(vals: z.infer) { const start = parseLocalDateTime(vals.startDate, vals.startTime) const end = parseLocalDateTime(vals.endDate, vals.endTime) @@ -118,11 +90,14 @@ function onSubmit(vals: z.infer) { location: vals.location, color: vals.color, description: vals.description, - id: null, - creator: null } - console.log("Submitting CalendarEvent:", event) + try { + console.log("Submitting CalendarEvent:", event) + createCalendarEvent(event); + } catch (error) { + console.error(error); + } // close after success dialogOpen.value = false