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:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { calculateEventStatus } from "@/lib/eventStatus";
|
||||
|
||||
interface Event {
|
||||
id: string;
|
||||
@@ -22,7 +23,6 @@ interface EventFormData {
|
||||
name: string;
|
||||
description: string;
|
||||
type: "SUMMIT" | "LAUNCH" | "FESTIVAL" | "COMPETITION" | "CODE_KATA";
|
||||
status: "UPCOMING" | "LIVE" | "PAST";
|
||||
room?: string;
|
||||
time?: string;
|
||||
maxPlaces?: number;
|
||||
@@ -35,8 +35,6 @@ const eventTypes: Event["type"][] = [
|
||||
"COMPETITION",
|
||||
"CODE_KATA",
|
||||
];
|
||||
const eventStatuses: Event["status"][] = ["UPCOMING", "LIVE", "PAST"];
|
||||
|
||||
const getEventTypeLabel = (type: Event["type"]) => {
|
||||
switch (type) {
|
||||
case "SUMMIT":
|
||||
@@ -78,7 +76,6 @@ export default function EventManagement() {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "SUMMIT",
|
||||
status: "UPCOMING",
|
||||
room: "",
|
||||
time: "",
|
||||
maxPlaces: undefined,
|
||||
@@ -110,7 +107,6 @@ export default function EventManagement() {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "SUMMIT",
|
||||
status: "UPCOMING",
|
||||
room: "",
|
||||
time: "",
|
||||
maxPlaces: undefined,
|
||||
@@ -125,7 +121,6 @@ export default function EventManagement() {
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
type: event.type,
|
||||
status: event.status,
|
||||
room: event.room || "",
|
||||
time: event.time || "",
|
||||
maxPlaces: event.maxPlaces || undefined,
|
||||
@@ -163,7 +158,6 @@ export default function EventManagement() {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "SUMMIT",
|
||||
status: "UPCOMING",
|
||||
room: "",
|
||||
time: "",
|
||||
maxPlaces: undefined,
|
||||
@@ -210,7 +204,6 @@ export default function EventManagement() {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "SUMMIT",
|
||||
status: "UPCOMING",
|
||||
room: "",
|
||||
time: "",
|
||||
maxPlaces: undefined,
|
||||
@@ -300,27 +293,6 @@ export default function EventManagement() {
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-gray-300 mb-1">
|
||||
Statut
|
||||
</label>
|
||||
<select
|
||||
value={formData.status}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
status: e.target.value as Event["status"],
|
||||
})
|
||||
}
|
||||
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-sm"
|
||||
>
|
||||
{eventStatuses.map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{getStatusLabel(status)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
@@ -411,15 +383,16 @@ export default function EventManagement() {
|
||||
{getEventTypeLabel(event.type)}
|
||||
</span>
|
||||
<span
|
||||
className={`px-2 py-1 text-xs uppercase rounded ${
|
||||
event.status === "UPCOMING"
|
||||
className={`px-2 py-1 text-xs uppercase rounded ${(() => {
|
||||
const status = calculateEventStatus(event.date);
|
||||
return status === "UPCOMING"
|
||||
? "bg-green-900/50 border border-green-500/50 text-green-400"
|
||||
: event.status === "LIVE"
|
||||
: status === "LIVE"
|
||||
? "bg-yellow-900/50 border border-yellow-500/50 text-yellow-400"
|
||||
: "bg-gray-900/50 border border-gray-500/50 text-gray-400"
|
||||
}`}
|
||||
: "bg-gray-900/50 border border-gray-500/50 text-gray-400";
|
||||
})()}`}
|
||||
>
|
||||
{getStatusLabel(event.status)}
|
||||
{getStatusLabel(calculateEventStatus(event.date))}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm mb-2">
|
||||
|
||||
@@ -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é
|
||||
|
||||
Reference in New Issue
Block a user