Files
mustaboard/src/app/components/EventCard.tsx
T

206 lines
6.3 KiB
TypeScript

import React, { useEffect, useState } from "react";
import eventItems from "../../public/eventItems.json";
interface RecurringEvent {
name: string;
day: string;
time?: string;
timeDiffMs?: number;
}
interface SpecialEvent {
name: string;
date: string;
time: string;
timeDiffMs?: number;
}
interface Place {
name: string;
closingHours: Map<string, string>;
timeDiffMs?: number;
}
const recurringEvents: RecurringEvent[] = eventItems.recurring_events;
const specialEvents: SpecialEvent[] = eventItems.special_events;
const places: Place[] = eventItems.places.map((place: any) => ({
name: place.name,
closingHours: new Map(place.closing_times.map((time: any) => [time.day, time.time])),
}));
const EventCard: React.FC = () => {
const [upcomingEvents, setUpcomingEvents] = useState<
(RecurringEvent | SpecialEvent | Place)[]
>([]);
useEffect(() => {
const updateEvents = () => {
const now = new Date();
// Handle recurring events
const eventsWithCountdown = recurringEvents.map((event) => {
const eventTime = getNextRecurringEvent(event.day, event.time || "00:00");
const timeDiffMs = eventTime.getTime() - now.getTime();
return { ...event, timeDiffMs };
});
// Handle places' closing times
const placesWithCountdown = places.map((place) => {
const closingTime = getNextClosingTime(place.closingHours);
const timeDiffMs = closingTime.getTime() - now.getTime();
return { ...place, timeDiffMs };
});
// Handle special events
const specialEventsWithCountdown = specialEvents.map((event) => {
const eventTime = new Date(`${event.date}T${event.time}`);
const timeDiffMs = eventTime.getTime() - now.getTime();
return { ...event, timeDiffMs };
});
const filteredEvents = [
...eventsWithCountdown.filter(
(event) => event.timeDiffMs! > 0 && event.timeDiffMs! <= 48 * 60 * 60 * 1000
),
...placesWithCountdown.filter(
(place) => place.timeDiffMs! > 0 && place.timeDiffMs! <= 4 * 60 * 60 * 1000
),
...specialEventsWithCountdown.filter(
(event) => event.timeDiffMs! > 0 && event.timeDiffMs! <= 48 * 60 * 60 * 1000
),
];
// Sort events by time difference
filteredEvents.sort((a, b) => a.timeDiffMs! - b.timeDiffMs!);
setUpcomingEvents(filteredEvents);
};
updateEvents();
const intervalId = setInterval(updateEvents, 60 * 1000);
return () => clearInterval(intervalId);
}, []);
return (
<div className="flex flex-col bg-foreground rounded-lg p-3">
<div className="flex flex-col gap-3">
{upcomingEvents.map((event) => (
<div className="flex flex-1 flex-row bg-primary rounded-lg p-3 justify-between space" key={event.name}>
<div className="flex flex-row gap-3">
<p className="event-type">{'closingHours' in event ? "Closing" : "Event"}</p>
<p>{event.name}</p>
</div>
<div className="flex flex-col gap-3">
<p>{getTimeDifference(event.timeDiffMs ?? 0)}</p>
</div>
</div>
))}
</div>
</div>
);
};
const getNextRecurringEvent = (day: string, time: string): Date => {
/**
* Returns the next occurrence of a recurring event.
*
* @param {string} day - The day of the week the event occurs on.
* @param {string} time - The time of day the event occurs at.
* @returns {Date} The next occurrence of the event.
*
*/
const daysOfWeek: { [key: string]: number } = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
};
const now = new Date();
const currentDay = now.getDay();
const targetDay = daysOfWeek[day];
if (!time) {
console.error(`Time is undefined for event on ${day}`);
return now;
}
const [eventHour, eventMinute] = time.split(":").map(Number);
const eventDate = new Date();
eventDate.setHours(eventHour, eventMinute, 0, 0);
const daysUntilEvent = (targetDay + 7 - currentDay) % 7;
eventDate.setDate(eventDate.getDate() + daysUntilEvent);
if (daysUntilEvent === 0 && now > eventDate) {
eventDate.setDate(eventDate.getDate() + 7);
}
return eventDate;
};
const getNextClosingTime = (closingHours: Map<string, string>): Date => {
/**
* Returns the next closing time for a place.
*
* @param {Map<string, string>} closingHours - A map of closing times for each day of the week.
*
* @returns {Date} The next closing time for the place.
*
*/
const now = new Date();
const currentDay = now.toLocaleDateString("en-US", { weekday: "long" });
const closingTime = closingHours.get(currentDay);
if (!closingTime) {
return now; // If no closing time for today, return now (or handle differently)
}
const [closingHour, closingMinute] = closingTime.split(":").map(Number);
const closingDate = new Date();
closingDate.setHours(closingHour, closingMinute, 0, 0);
if (now > closingDate) {
// If already past today's closing time, get the next day's closing time
const nextDay = new Date(now.getTime() + 24 * 60 * 60 * 1000); // add 1 day
const nextDayName = nextDay.toLocaleDateString("en-US", { weekday: "long" });
const nextClosingTime = closingHours.get(nextDayName);
if (nextClosingTime) {
const [nextClosingHour, nextClosingMinute] = nextClosingTime.split(":").map(Number);
closingDate.setDate(closingDate.getDate() + 1); // Move to the next day
closingDate.setHours(nextClosingHour, nextClosingMinute, 0, 0);
}
}
return closingDate;
};
const getTimeDifference = (timeDiff: number): string => {
/**
* Returns a human-readable string representing the time difference.
*
* @param {number} timeDiff - The time difference in milliseconds.
*
* @returns {string} A human-readable string representing the time difference.
*
*/
const msInHour = 1000 * 60 * 60;
const msInDay = msInHour * 24;
if (timeDiff < msInHour) {
const minutesLeft = Math.floor(timeDiff / (1000 * 60));
return `in ${minutesLeft} minutes`;
} else if (timeDiff < msInDay) {
const hoursLeft = Math.floor(timeDiff / msInHour);
return `in ${hoursLeft} hours`;
} else {
const daysLeft = Math.floor(timeDiff / msInDay);
return `in ${daysLeft} days`;
}
};
export default EventCard;