hooked up create event
This commit is contained in:
@@ -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;
|
||||||
@@ -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,
|
||||||
|
|||||||
33
shared/schemas/calendarEventSchema.ts
Normal file
33
shared/schemas/calendarEventSchema.ts
Normal 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user