Refactor event status handling: Remove EventStatus enum from the Prisma schema and update related API routes and UI components to calculate event status dynamically based on event date. This change simplifies event management and enhances data integrity by ensuring status is always derived from the date.

This commit is contained in:
Julien Froidefond
2025-12-10 05:45:25 +01:00
parent fb830c6fcc
commit a69613a232
15 changed files with 167 additions and 298 deletions

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useMemo, useRef } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { calculateEventStatus } from "@/lib/eventStatus";
interface Event {
id: string;
@@ -10,7 +11,6 @@ interface Event {
name: string;
description: string;
type: "SUMMIT" | "LAUNCH" | "FESTIVAL" | "COMPETITION" | "CODE_KATA";
status: "UPCOMING" | "LIVE" | "PAST";
room?: string | null;
time?: string | null;
maxPlaces?: number | null;
@@ -56,7 +56,7 @@ const getEventTypeLabel = (type: Event["type"]) => {
}
};
const getStatusBadge = (status: Event["status"]) => {
const getStatusBadge = (status: "UPCOMING" | "LIVE" | "PAST") => {
switch (status) {
case "UPCOMING":
return (
@@ -93,6 +93,9 @@ export default function EventsPageSection({
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
// Helper function pour obtenir le statut d'un événement
const getEventStatus = (event: Event) => calculateEventStatus(event.date);
// Déterminer si on a des données initiales valides
const hasInitialData = useMemo(
() => Object.keys(initialRegistrations).length > 0,
@@ -103,8 +106,12 @@ export default function EventsPageSection({
const hasUsedInitialData = useRef(hasInitialData);
// Séparer et trier les événements (du plus récent au plus ancien)
// Le statut est calculé automatiquement en fonction de la date
const upcomingEvents = events
.filter((e) => e.status === "UPCOMING" || e.status === "LIVE")
.filter((e) => {
const status = calculateEventStatus(e.date);
return status === "UPCOMING" || status === "LIVE";
})
.sort((a, b) => {
// Trier par date décroissante (du plus récent au plus ancien)
const dateA = typeof a.date === "string" ? new Date(a.date) : a.date;
@@ -112,7 +119,7 @@ export default function EventsPageSection({
return dateB.getTime() - dateA.getTime();
});
const pastEvents = events
.filter((e) => e.status === "PAST")
.filter((e) => calculateEventStatus(e.date) === "PAST")
.sort((a, b) => {
// Trier par date décroissante (du plus récent au plus ancien)
const dateA = typeof a.date === "string" ? new Date(a.date) : a.date;
@@ -168,7 +175,9 @@ export default function EventsPageSection({
// Charger les inscriptions depuis l'API seulement si on n'a pas de données initiales
const checkRegistrations = async () => {
const upcomingOnlyEvents = events.filter((e) => e.status === "UPCOMING");
const upcomingOnlyEvents = events.filter(
(e) => getEventStatus(e) === "UPCOMING"
);
const registrationChecks = upcomingOnlyEvents.map(async (event) => {
try {
const response = await fetch(`/api/events/${event.id}/register`);
@@ -298,9 +307,11 @@ export default function EventsPageSection({
const hasEvents = dayEvents.length > 0;
// Déterminer la couleur principale selon le type d'événement
const hasUpcoming = dayEvents.some((e) => e.status === "UPCOMING");
const hasLive = dayEvents.some((e) => e.status === "LIVE");
const hasPast = dayEvents.some((e) => e.status === "PAST");
const hasUpcoming = dayEvents.some(
(e) => getEventStatus(e) === "UPCOMING"
);
const hasLive = dayEvents.some((e) => getEventStatus(e) === "LIVE");
const hasPast = dayEvents.some((e) => getEventStatus(e) === "PAST");
let eventBorderColor = "";
let eventBgColor = "";
@@ -347,19 +358,22 @@ export default function EventsPageSection({
</div>
{hasEvents && (
<div className="absolute bottom-0.5 left-0 right-0 flex justify-center gap-0.5">
{dayEvents.slice(0, 3).map((event) => (
<div
key={event.id}
className={`w-1 h-1 rounded-full ${
event.status === "UPCOMING"
? "bg-green-400"
: event.status === "LIVE"
? "bg-red-400 animate-pulse"
: "bg-gray-400"
}`}
title={event.name}
/>
))}
{dayEvents.slice(0, 3).map((event) => {
const status = getEventStatus(event);
return (
<div
key={event.id}
className={`w-1 h-1 rounded-full ${
status === "UPCOMING"
? "bg-green-400"
: status === "LIVE"
? "bg-red-400 animate-pulse"
: "bg-gray-400"
}`}
title={event.name}
/>
);
})}
{dayEvents.length > 3 && (
<div className="w-1 h-1 rounded-full bg-gray-400" />
)}
@@ -393,7 +407,7 @@ export default function EventsPageSection({
<div className="p-6">
{/* Status Badge */}
<div className="flex justify-between items-start mb-4">
{getStatusBadge(event.status)}
{getStatusBadge(getEventStatus(event))}
<span className="text-pixel-gold text-xs uppercase tracking-widest">
{getEventTypeLabel(event.type)}
</span>
@@ -457,7 +471,7 @@ export default function EventsPageSection({
)}
{/* Action Button */}
{event.status === "UPCOMING" && (
{getEventStatus(event) === "UPCOMING" && (
<>
{registrations[event.id] ? (
<button
@@ -484,12 +498,12 @@ export default function EventsPageSection({
)}
</>
)}
{event.status === "LIVE" && (
{getEventStatus(event) === "LIVE" && (
<button className="w-full px-4 py-2 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-xs tracking-widest rounded hover:bg-red-900/30 transition animate-pulse">
Rejoindre en direct
</button>
)}
{event.status === "PAST" && (
{getEventStatus(event) === "PAST" && (
<button className="w-full px-4 py-2 border border-gray-600/50 bg-gray-900/20 text-gray-500 uppercase text-xs tracking-widest rounded cursor-not-allowed">
Événement terminé
</button>
@@ -662,7 +676,9 @@ export default function EventsPageSection({
<div className="flex items-center justify-between mb-6">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
{getStatusBadge(selectedEvent.status)}
{getStatusBadge(
selectedEvent ? getEventStatus(selectedEvent) : "UPCOMING"
)}
<span className="px-3 py-1 bg-pixel-gold/20 border border-pixel-gold/50 text-pixel-gold text-xs uppercase rounded">
{getEventTypeLabel(selectedEvent.type)}
</span>
@@ -759,47 +775,48 @@ export default function EventsPageSection({
</div>
{/* Action Button */}
{selectedEvent.status === "UPCOMING" && (
<div className="pt-4 border-t border-pixel-gold/20">
{registrations[selectedEvent.id] ? (
<button
onClick={(e) => {
e.stopPropagation();
handleUnregister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-sm tracking-widest rounded hover:bg-green-900/30 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Annulation..."
: "Se désinscrire"}
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
handleRegister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-pixel-gold/50 bg-pixel-gold/10 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/20 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Inscription..."
: "S'inscrire maintenant"}
</button>
)}
</div>
)}
{selectedEvent.status === "LIVE" && (
{selectedEvent &&
getEventStatus(selectedEvent) === "UPCOMING" && (
<div className="pt-4 border-t border-pixel-gold/20">
{registrations[selectedEvent.id] ? (
<button
onClick={(e) => {
e.stopPropagation();
handleUnregister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-sm tracking-widest rounded hover:bg-green-900/30 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Annulation..."
: "Se désinscrire"}
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
handleRegister(selectedEvent.id);
setSelectedEvent(null);
}}
disabled={loading[selectedEvent.id]}
className="w-full px-4 py-3 border border-pixel-gold/50 bg-pixel-gold/10 text-white uppercase text-sm tracking-widest rounded hover:bg-pixel-gold/20 hover:border-pixel-gold transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading[selectedEvent.id]
? "Inscription..."
: "S'inscrire maintenant"}
</button>
)}
</div>
)}
{selectedEvent && getEventStatus(selectedEvent) === "LIVE" && (
<div className="pt-4 border-t border-pixel-gold/20">
<button className="w-full px-4 py-3 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-sm tracking-widest rounded hover:bg-red-900/30 transition animate-pulse">
Rejoindre en direct
</button>
</div>
)}
{selectedEvent.status === "PAST" && (
{selectedEvent && getEventStatus(selectedEvent) === "PAST" && (
<div className="pt-4 border-t border-pixel-gold/20">
<button className="w-full px-4 py-3 border border-gray-600/50 bg-gray-900/20 text-gray-500 uppercase text-sm tracking-widest rounded cursor-not-allowed">
Événement terminé