diff --git a/src/components/dashboard/EventsSection/EventCheckIn.tsx b/src/components/dashboard/EventsSection/EventCheckIn.tsx index 6601933..23dafd9 100644 --- a/src/components/dashboard/EventsSection/EventCheckIn.tsx +++ b/src/components/dashboard/EventsSection/EventCheckIn.tsx @@ -11,6 +11,10 @@ interface ExtendedEvent extends Event { description?: string; // This component uses 'description' but schema has 'event_description' } +// Note: Date conversion is now handled automatically by the Get and Update classes. +// When fetching events, UTC dates are converted to local time. +// When saving events, local dates are converted back to UTC. + // Toast management system const createToast = ( message: string, @@ -115,8 +119,8 @@ const EventCheckIn = () => { // Check if the event is active (has started and hasn't ended yet) const currentTime = new Date(); - const eventStartDate = new Date(event.start_date); - const eventEndDate = new Date(event.end_date); + const eventStartDate = new Date(event.start_date); // Now properly converted to local time by Get + const eventEndDate = new Date(event.end_date); // Now properly converted to local time by Get if (eventStartDate > currentTime) { throw new Error("This event has not started yet"); @@ -175,7 +179,7 @@ const EventCheckIn = () => { // Create attendee entry with check-in details const attendeeEntry: AttendeeEntry = { user_id: currentUser.id, - time_checked_in: new Date().toISOString(), + time_checked_in: new Date().toISOString(), // Will be properly converted to UTC by Update food: foodSelection || "none", }; @@ -290,7 +294,7 @@ const EventCheckIn = () => { /> - - - - -
- -
- - - ); }; -export default EventCheckIn; +export default EventCheckIn; \ No newline at end of file diff --git a/src/components/dashboard/Officer_EventManagement/EventEditor.tsx b/src/components/dashboard/Officer_EventManagement/EventEditor.tsx index a2b2b6d..6f55899 100644 --- a/src/components/dashboard/Officer_EventManagement/EventEditor.tsx +++ b/src/components/dashboard/Officer_EventManagement/EventEditor.tsx @@ -8,6 +8,11 @@ import { SendLog } from "../../../scripts/pocketbase/SendLog"; import FilePreview from "../universal/FilePreview"; import type { Event as SchemaEvent, AttendeeEntry } from "../../../schemas/pocketbase"; +// Note: Date conversion is now handled automatically by the Get and Update classes. +// When fetching events, UTC dates are converted to local time by the Get class. +// When saving events, local dates are converted back to UTC by the Update class. +// For datetime-local inputs, we format dates without seconds (YYYY-MM-DDThh:mm). + // Extended Event interface with optional created and updated fields interface Event extends Omit { created?: string; @@ -151,7 +156,7 @@ const EventForm = memo(({ type="datetime-local" name="editEventStartDate" className="input input-bordered" - value={event?.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : ""} + value={event?.start_date ? event.start_date.slice(0, 16) : ""} onChange={(e) => handleChange('start_date', e.target.value)} required /> @@ -167,7 +172,7 @@ const EventForm = memo(({ type="datetime-local" name="editEventEndDate" className="input input-bordered" - value={event?.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : ""} + value={event?.end_date ? event.end_date.slice(0, 16) : ""} onChange={(e) => handleChange('end_date', e.target.value)} required /> @@ -510,8 +515,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { location: '', files: [], points_to_reward: 0, - start_date: new Date().toISOString(), - end_date: new Date().toISOString(), + start_date: Get.formatLocalDate(new Date(), false), + end_date: Get.formatLocalDate(new Date(), false), published: false, has_food: false, attendees: [] @@ -551,6 +556,20 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { try { if (eventId) { const eventData = await services.get.getOne("events", eventId); + + // Ensure dates are properly formatted for datetime-local input + if (eventData.start_date) { + // Convert to Date object first to ensure proper formatting + const startDate = new Date(eventData.start_date); + eventData.start_date = Get.formatLocalDate(startDate, false); + } + + if (eventData.end_date) { + // Convert to Date object first to ensure proper formatting + const endDate = new Date(eventData.end_date); + eventData.end_date = Get.formatLocalDate(endDate, false); + } + setEvent(eventData); } else { setEvent({ @@ -561,8 +580,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { location: '', files: [], points_to_reward: 0, - start_date: new Date().toISOString(), - end_date: new Date().toISOString(), + start_date: Get.formatLocalDate(new Date(), false), + end_date: Get.formatLocalDate(new Date(), false), published: false, has_food: false, attendees: [] @@ -623,8 +642,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { location: '', files: [], points_to_reward: 0, - start_date: new Date().toISOString(), - end_date: new Date().toISOString(), + start_date: Get.formatLocalDate(new Date(), false), + end_date: Get.formatLocalDate(new Date(), false), published: false, has_food: false, attendees: [] @@ -664,8 +683,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { event_description: formData.get("editEventDescription"), location: formData.get("editEventLocation"), points_to_reward: Number(formData.get("editEventPoints")), - start_date: new Date(formData.get("editEventStartDate") as string).toISOString(), - end_date: new Date(formData.get("editEventEndDate") as string).toISOString(), + start_date: formData.get("editEventStartDate") as string, + end_date: formData.get("editEventEndDate") as string, published: formData.get("editEventPublished") === "on", has_food: formData.get("editEventHasFood") === "on", attendees: event.attendees || [] @@ -758,8 +777,8 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) { location: '', files: [], points_to_reward: 0, - start_date: new Date().toISOString(), - end_date: new Date().toISOString(), + start_date: Get.formatLocalDate(new Date(), false), + end_date: Get.formatLocalDate(new Date(), false), published: false, has_food: false, attendees: [] diff --git a/src/scripts/pocketbase/Get.ts b/src/scripts/pocketbase/Get.ts index b206166..abccd66 100644 --- a/src/scripts/pocketbase/Get.ts +++ b/src/scripts/pocketbase/Get.ts @@ -20,14 +20,45 @@ function isUTCDateString(value: any): boolean { return isoDateRegex.test(value); } +// Utility function to format a date to local ISO-like string +function formatLocalDate(date: Date, includeSeconds: boolean = true): string { + const pad = (num: number) => num.toString().padStart(2, "0"); + + const year = date.getFullYear(); + const month = pad(date.getMonth() + 1); + const day = pad(date.getDate()); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + + // Format for datetime-local input (YYYY-MM-DDThh:mm) + if (!includeSeconds) { + return `${year}-${month}-${day}T${hours}:${minutes}`; + } + + const seconds = pad(date.getSeconds()); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; +} + // Utility function to convert UTC date strings to local time function convertUTCToLocal(data: T): T { if (!data || typeof data !== "object") return data; const converted = { ...data }; for (const [key, value] of Object.entries(converted)) { - if (isUTCDateString(value)) { - (converted as any)[key] = new Date(value).toISOString(); + // Special handling for event date fields + if ( + (key === "start_date" || + key === "end_date" || + key === "time_checked_in") && + isUTCDateString(value) + ) { + // Convert UTC date string to local date string + const date = new Date(value); + (converted as any)[key] = formatLocalDate(date, false); + } else if (isUTCDateString(value)) { + // Convert UTC date string to local date string + const date = new Date(value); + (converted as any)[key] = formatLocalDate(date); } else if (Array.isArray(value)) { (converted as any)[key] = value.map((item) => convertUTCToLocal(item)); } else if (typeof value === "object" && value !== null) { @@ -73,6 +104,19 @@ export class Get { return isUTCDateString(value); } + /** + * Format a date to local ISO-like string + * @param date The date to format + * @param includeSeconds Whether to include seconds in the formatted string + * @returns The formatted date string + */ + public static formatLocalDate( + date: Date, + includeSeconds: boolean = true, + ): string { + return formatLocalDate(date, includeSeconds); + } + /** * Get a single record by ID * @param collectionName The name of the collection diff --git a/src/scripts/pocketbase/Update.ts b/src/scripts/pocketbase/Update.ts index 62404a1..515dd07 100644 --- a/src/scripts/pocketbase/Update.ts +++ b/src/scripts/pocketbase/Update.ts @@ -3,7 +3,9 @@ import { Authentication } from "./Authentication"; // Utility function to check if a value is a date string function isLocalDateString(value: any): boolean { if (typeof value !== "string") return false; - const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/; + // Match ISO format without the Z suffix (local time) + const isoDateRegex = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:(?:-|\+)\d{2}:\d{2})?$/; return isoDateRegex.test(value); } @@ -13,8 +15,19 @@ function convertLocalToUTC(data: T): T { const converted = { ...data }; for (const [key, value] of Object.entries(converted)) { + // Special handling for event date fields to ensure proper UTC conversion + if ( + (key === "start_date" || + key === "end_date" || + key === "time_checked_in") && + typeof value === "string" + ) { + // Ensure we're converting to UTC + const date = new Date(value); + (converted as any)[key] = date.toISOString(); + } // Special handling for invoice_data to ensure it's a proper JSON object - if (key === "invoice_data") { + else if (key === "invoice_data") { if (typeof value === "string") { try { // If it's a string representation of JSON, parse it @@ -29,7 +42,9 @@ function convertLocalToUTC(data: T): T { (converted as any)[key] = value; } } else if (isLocalDateString(value)) { - (converted as any)[key] = new Date(value).toISOString(); + // Convert local date string to UTC + const date = new Date(value); + (converted as any)[key] = date.toISOString(); } else if (Array.isArray(value)) { (converted as any)[key] = value.map((item) => convertLocalToUTC(item)); } else if (typeof value === "object" && value !== null) { @@ -57,6 +72,15 @@ export class Update { return Update.instance; } + /** + * Convert local time to UTC + * @param data The data to convert + * @returns The converted data + */ + public static convertLocalToUTC(data: T): T { + return convertLocalToUTC(data); + } + /** * Create a new record * @param collectionName The name of the collection