From a69613a2324384212eb6fe9299dc0fd23ba30cda Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Wed, 10 Dec 2025 05:45:25 +0100 Subject: [PATCH] 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. --- app/api/admin/events/[id]/route.ts | 20 +-- app/api/admin/events/route.ts | 41 ++---- app/events/page.tsx | 5 +- components/EventManagement.tsx | 43 +----- components/EventsPageSection.tsx | 139 ++++++++++-------- lib/eventStatus.ts | 28 ++++ prisma/generated/prisma/commonInputTypes.ts | 34 ----- prisma/generated/prisma/enums.ts | 9 -- prisma/generated/prisma/internal/class.ts | 4 +- .../prisma/internal/prismaNamespace.ts | 8 - .../prisma/internal/prismaNamespaceBrowser.ts | 1 - prisma/generated/prisma/models/Event.ts | 38 +---- .../migration.sql | 20 +++ prisma/schema.prisma | 8 - prisma/seed.ts | 67 +-------- 15 files changed, 167 insertions(+), 298 deletions(-) create mode 100644 lib/eventStatus.ts create mode 100644 prisma/migrations/20251210044500_remove_event_status_field/migration.sql diff --git a/app/api/admin/events/[id]/route.ts b/app/api/admin/events/[id]/route.ts index edd6864..586736e 100644 --- a/app/api/admin/events/[id]/route.ts +++ b/app/api/admin/events/[id]/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; -import { Role, EventType, EventStatus } from "@/prisma/generated/prisma/client"; +import { Role, EventType } from "@/prisma/generated/prisma/client"; export async function PUT( request: Request, @@ -16,7 +16,8 @@ export async function PUT( const { id } = await params; const body = await request.json(); - const { date, name, description, type, status, room, time, maxPlaces } = body; + const { date, name, description, type, room, time, maxPlaces } = body; + // Le statut est ignoré s'il est fourni, il sera calculé automatiquement // Vérifier que l'événement existe const existingEvent = await prisma.event.findUnique({ @@ -35,7 +36,6 @@ export async function PUT( name?: string; description?: string; type?: EventType; - status?: EventStatus; room?: string | null; time?: string | null; maxPlaces?: number | null; @@ -62,18 +62,11 @@ export async function PUT( } updateData.type = type as EventType; } - if (status !== undefined) { - if (!Object.values(EventStatus).includes(status)) { - return NextResponse.json( - { error: "Statut d'événement invalide" }, - { status: 400 } - ); - } - updateData.status = status as EventStatus; - } + // Le statut est toujours calculé automatiquement, on ignore s'il est fourni if (room !== undefined) updateData.room = room || null; if (time !== undefined) updateData.time = time || null; - if (maxPlaces !== undefined) updateData.maxPlaces = maxPlaces ? parseInt(maxPlaces) : null; + if (maxPlaces !== undefined) + updateData.maxPlaces = maxPlaces ? parseInt(maxPlaces) : null; const event = await prisma.event.update({ where: { id }, @@ -128,4 +121,3 @@ export async function DELETE( ); } } - diff --git a/app/api/admin/events/route.ts b/app/api/admin/events/route.ts index 05ae0e0..9828e34 100644 --- a/app/api/admin/events/route.ts +++ b/app/api/admin/events/route.ts @@ -1,7 +1,8 @@ import { NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; -import { Role, EventType, EventStatus } from "@/prisma/generated/prisma/client"; +import { Role, EventType } from "@/prisma/generated/prisma/client"; +import { calculateEventStatus } from "@/lib/eventStatus"; export async function GET() { try { @@ -15,18 +16,7 @@ export async function GET() { orderBy: { date: "desc", }, - select: { - id: true, - date: true, - name: true, - description: true, - type: true, - status: true, - room: true, - time: true, - maxPlaces: true, - createdAt: true, - updatedAt: true, + include: { _count: { select: { registrations: true, @@ -36,13 +26,14 @@ export async function GET() { }); // Transformer les données pour inclure le nombre d'inscriptions + // Le statut est calculé automatiquement en fonction de la date const eventsWithCount = events.map((event) => ({ id: event.id, date: event.date.toISOString(), name: event.name, description: event.description, type: event.type, - status: event.status, + status: calculateEventStatus(event.date), room: event.room, time: event.time, maxPlaces: event.maxPlaces, @@ -70,15 +61,24 @@ export async function POST(request: Request) { } const body = await request.json(); - const { date, name, description, type, status, room, time, maxPlaces } = body; + const { date, name, description, type, room, time, maxPlaces } = body; - if (!date || !name || !description || !type || !status) { + if (!date || !name || !description || !type) { return NextResponse.json( { error: "Tous les champs sont requis" }, { status: 400 } ); } + // Convertir la date string en Date object + const eventDate = new Date(date); + if (isNaN(eventDate.getTime())) { + return NextResponse.json( + { error: "Format de date invalide" }, + { status: 400 } + ); + } + // Valider les enums if (!Object.values(EventType).includes(type)) { return NextResponse.json( @@ -87,20 +87,12 @@ export async function POST(request: Request) { ); } - if (!Object.values(EventStatus).includes(status)) { - return NextResponse.json( - { error: "Statut d'événement invalide" }, - { status: 400 } - ); - } - const event = await prisma.event.create({ data: { date: eventDate, name, description, type: type as EventType, - status: status as EventStatus, room: room || null, time: time || null, maxPlaces: maxPlaces ? parseInt(maxPlaces) : null, @@ -116,4 +108,3 @@ export async function POST(request: Request) { ); } } - diff --git a/app/events/page.tsx b/app/events/page.tsx index 6a44c79..824e88d 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -3,6 +3,7 @@ import EventsPageSection from "@/components/EventsPageSection"; import { prisma } from "@/lib/prisma"; import { getBackgroundImage } from "@/lib/preferences"; import { auth } from "@/lib/auth"; +import { calculateEventStatus } from "@/lib/eventStatus"; export default async function EventsPage() { const events = await prisma.event.findMany({ @@ -26,7 +27,9 @@ export default async function EventsPage() { const initialRegistrations: Record = {}; if (session?.user?.id) { - const upcomingEvents = events.filter((e) => e.status === "UPCOMING"); + const upcomingEvents = events.filter( + (e) => calculateEventStatus(e.date) === "UPCOMING" + ); const eventIds = upcomingEvents.map((e) => e.id); if (eventIds.length > 0) { diff --git a/components/EventManagement.tsx b/components/EventManagement.tsx index 3a5fd78..8876a00 100644 --- a/components/EventManagement.tsx +++ b/components/EventManagement.tsx @@ -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() { ))} -
- - -
@@ -411,15 +383,16 @@ export default function EventManagement() { {getEventTypeLabel(event.type)} { + 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))}

diff --git a/components/EventsPageSection.tsx b/components/EventsPageSection.tsx index 202b2e0..98b656e 100644 --- a/components/EventsPageSection.tsx +++ b/components/EventsPageSection.tsx @@ -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(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({

{hasEvents && (
- {dayEvents.slice(0, 3).map((event) => ( -
- ))} + {dayEvents.slice(0, 3).map((event) => { + const status = getEventStatus(event); + return ( +
+ ); + })} {dayEvents.length > 3 && (
)} @@ -393,7 +407,7 @@ export default function EventsPageSection({
{/* Status Badge */}
- {getStatusBadge(event.status)} + {getStatusBadge(getEventStatus(event))} {getEventTypeLabel(event.type)} @@ -457,7 +471,7 @@ export default function EventsPageSection({ )} {/* Action Button */} - {event.status === "UPCOMING" && ( + {getEventStatus(event) === "UPCOMING" && ( <> {registrations[event.id] ? ( )} - {event.status === "PAST" && ( + {getEventStatus(event) === "PAST" && ( @@ -662,7 +676,9 @@ export default function EventsPageSection({
- {getStatusBadge(selectedEvent.status)} + {getStatusBadge( + selectedEvent ? getEventStatus(selectedEvent) : "UPCOMING" + )} {getEventTypeLabel(selectedEvent.type)} @@ -759,47 +775,48 @@ export default function EventsPageSection({
{/* Action Button */} - {selectedEvent.status === "UPCOMING" && ( -
- {registrations[selectedEvent.id] ? ( - - ) : ( - - )} -
- )} - {selectedEvent.status === "LIVE" && ( + {selectedEvent && + getEventStatus(selectedEvent) === "UPCOMING" && ( +
+ {registrations[selectedEvent.id] ? ( + + ) : ( + + )} +
+ )} + {selectedEvent && getEventStatus(selectedEvent) === "LIVE" && (
)} - {selectedEvent.status === "PAST" && ( + {selectedEvent && getEventStatus(selectedEvent) === "PAST" && (