fix datetime issues
This commit is contained in:
parent
72ea5ba8df
commit
c5925bd275
4 changed files with 113 additions and 99 deletions
|
@ -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 = () => {
|
|||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className={`btn btn-primary h-10 min-h-[2.5rem] text-sm sm:text-base w-full sm:w-auto`}
|
||||
className="btn btn-primary h-10 min-h-[2.5rem] text-sm sm:text-base w-full sm:w-auto"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
|
@ -308,83 +312,6 @@ const EventCheckIn = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="foodSelectionModal" className="modal">
|
||||
<div className="modal-box max-w-[90vw] sm:max-w-lg p-4 sm:p-6">
|
||||
<h3 className="font-bold text-base sm:text-lg mb-3 sm:mb-4">Food Selection</h3>
|
||||
<form onSubmit={handleSubmit} className="space-y-3 sm:space-y-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text text-sm sm:text-base">What food would you like?</span>
|
||||
<span className="label-text-alt text-error">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={foodInput}
|
||||
onChange={(e) => setFoodInput(e.target.value)}
|
||||
className="input input-bordered text-sm sm:text-base h-10 min-h-[2.5rem] w-full"
|
||||
placeholder="Enter your food choice or 'none'"
|
||||
required
|
||||
/>
|
||||
<label className="label">
|
||||
<span className="label-text-alt text-info text-xs sm:text-sm">
|
||||
Enter 'none' if you don't want any food
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="modal-action flex flex-col sm:flex-row gap-2 sm:gap-3">
|
||||
<button type="submit" className="btn btn-primary text-sm sm:text-base h-10 min-h-[2.5rem] w-full sm:w-auto">
|
||||
Submit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn text-sm sm:text-base h-10 min-h-[2.5rem] w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
const modal = document.getElementById("foodSelectionModal") as HTMLDialogElement;
|
||||
modal.close();
|
||||
setCurrentCheckInEvent(null);
|
||||
setFoodInput("");
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<style>{`
|
||||
.toast-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast {
|
||||
pointer-events: auto;
|
||||
transform: translateX(0);
|
||||
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
.toast-exit {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
.toast.translate-x-full {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.toast-container .toast {
|
||||
transform: translateY(calc((1 - attr(data-index number)) * -0.25rem));
|
||||
}
|
||||
.toast-container .toast[data-index="0"] {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.toast-container .toast[data-index="1"] {
|
||||
transform: translateY(-0.025rem);
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<SchemaEvent, 'created' | 'updated'> {
|
||||
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<Event>("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: []
|
||||
|
|
|
@ -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<T>(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
|
||||
|
|
|
@ -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<T>(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<T>(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<T>(data: T): T {
|
||||
return convertLocalToUTC(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new record
|
||||
* @param collectionName The name of the collection
|
||||
|
|
Loading…
Reference in a new issue