hooked up create event

This commit is contained in:
2025-11-27 13:08:33 -05:00
parent 4dc121c018
commit 81716d4a4f
6 changed files with 80 additions and 51 deletions

View File

@@ -1,5 +1,5 @@
import { Request, Response } from "express"; 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"; import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
const express = require('express'); const express = require('express');
@@ -64,8 +64,17 @@ r.get('/:id', async (req: Request, res: Response) => {
//post a new calendar event //post a new calendar event
r.post('/', async (req, res) => { 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; module.exports.calendarRouter = r;

View File

@@ -1,11 +1,12 @@
import pool from '../db'; import pool from '../db';
import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar" import { CalendarEventShort, CalendarSignup, CalendarEvent, CalendarAttendance } from "@app/shared/types/calendar"
import { toDateTime } from "@app/shared/utils/time"
export async function createEvent(eventObject: Omit<CalendarEvent, 'id' | 'created_at' | 'updated_at' | 'cancelled'>) { export async function createEvent(eventObject: Omit<CalendarEvent, 'id' | 'created_at' | 'updated_at' | 'cancelled'>) {
const sql = ` const sql = `
INSERT INTO calendar_events INSERT INTO calendar_events
(name, start, end, location, color, description, creator) (name, start, end, location, color, description, creator)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
`; `;
const params = [ const params = [
eventObject.name, eventObject.name,
@@ -40,8 +41,8 @@ export async function updateEvent(eventObject: CalendarEvent) {
const params = [ const params = [
eventObject.name, eventObject.name,
eventObject.start, toDateTime(eventObject.start),
eventObject.end, toDateTime(eventObject.end),
eventObject.location, eventObject.location,
eventObject.color, eventObject.color,
eventObject.description ?? null, eventObject.description ?? null,

View File

@@ -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
})
}
})

View File

@@ -1,15 +1,15 @@
export interface CalendarEvent { export interface CalendarEvent {
id: number; id?: number;
name: string; name: string;
start: Date; start: Date;
end: Date; end: Date;
location: string; location: string;
color: string; color: string;
description: string; description: string;
creator_id: number; creator_id?: number;
cancelled: boolean; cancelled?: boolean;
created_at: Date; created_at?: Date;
updated_at: Date; updated_at?: Date;
creator_name?: string | null; creator_name?: string | null;
eventSignups?: CalendarSignup[] | null; eventSignups?: CalendarSignup[] | null;

View File

@@ -68,7 +68,18 @@ export async function getCalendarEvent(id: number): Promise<CalendarEvent> {
} }
export async function createCalendarEvent(eventData: CalendarEvent) { 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) { export async function editCalendarEvent(eventData: CalendarEvent) {
@@ -84,7 +95,7 @@ export async function adminCancelCalendarEvent(eventID: number) {
} }
export async function setCalendarEventAttendance(eventID: number, state: CalendarAttendance) { 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", method: "POST",
credentials: "include", credentials: "include",
}); });

View File

@@ -21,11 +21,9 @@ import {
} from "@/components/ui/form" } from "@/components/ui/form"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import Textarea from "../ui/textarea/Textarea.vue" 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) { function toLocalDateString(d: Date) {
// yyyy-MM-dd with local time zone // yyyy-MM-dd with local time zone
@@ -45,37 +43,11 @@ function roundToNextHour(d = new Date()) {
t.setHours(t.getHours() + 1) t.setHours(t.getHours() + 1)
return t 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 ---------- // ---------- dialog state & defaults ----------
const dialogOpen = ref(false) const dialogOpen = ref(false)
@@ -107,7 +79,7 @@ watch(dialogOpen, (isOpen) => {
} }
}) })
// ---------- submit ---------- // ---------- submit ----------
function onSubmit(vals: z.infer<typeof zEvent>) { function onSubmit(vals: z.infer<typeof calendarEventSchema>) {
const start = parseLocalDateTime(vals.startDate, vals.startTime) const start = parseLocalDateTime(vals.startDate, vals.startTime)
const end = parseLocalDateTime(vals.endDate, vals.endTime) const end = parseLocalDateTime(vals.endDate, vals.endTime)
@@ -118,11 +90,14 @@ function onSubmit(vals: z.infer<typeof zEvent>) {
location: vals.location, location: vals.location,
color: vals.color, color: vals.color,
description: vals.description, description: vals.description,
id: null,
creator: null
} }
try {
console.log("Submitting CalendarEvent:", event) console.log("Submitting CalendarEvent:", event)
createCalendarEvent(event);
} catch (error) {
console.error(error);
}
// close after success // close after success
dialogOpen.value = false dialogOpen.value = false