Files
got-gaming/components/EventManagement.tsx

444 lines
15 KiB
TypeScript

"use client";
import { useState, useEffect, useTransition } from "react";
import { calculateEventStatus } from "@/lib/eventStatus";
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
interface Event {
id: string;
date: string;
name: string;
description: string;
type: "ATELIER" | "KATA" | "PRESENTATION" | "LEARNING_HOUR";
status: "UPCOMING" | "LIVE" | "PAST";
room?: string | null;
time?: string | null;
maxPlaces?: number | null;
createdAt: string;
updatedAt: string;
registrationsCount?: number;
}
interface EventFormData {
date: string;
name: string;
description: string;
type: "ATELIER" | "KATA" | "PRESENTATION" | "LEARNING_HOUR";
room?: string;
time?: string;
maxPlaces?: number;
}
const eventTypes: Event["type"][] = [
"ATELIER",
"KATA",
"PRESENTATION",
"LEARNING_HOUR",
];
const getEventTypeLabel = (type: Event["type"]) => {
switch (type) {
case "ATELIER":
return "Atelier";
case "KATA":
return "Kata";
case "PRESENTATION":
return "Présentation";
case "LEARNING_HOUR":
return "Learning Hour";
default:
return type;
}
};
const getStatusLabel = (status: Event["status"]) => {
switch (status) {
case "UPCOMING":
return "À venir";
case "LIVE":
return "En cours";
case "PAST":
return "Passé";
default:
return status;
}
};
export default function EventManagement() {
const [events, setEvents] = useState<Event[]>([]);
const [loading, setLoading] = useState(true);
const [editingEvent, setEditingEvent] = useState<Event | null>(null);
const [isCreating, setIsCreating] = useState(false);
const [saving, setSaving] = useState(false);
const [formData, setFormData] = useState<EventFormData>({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
useEffect(() => {
fetchEvents();
}, []);
const fetchEvents = async () => {
try {
const response = await fetch("/api/admin/events");
if (response.ok) {
const data = await response.json();
setEvents(data);
}
} catch (error) {
console.error("Error fetching events:", error);
} finally {
setLoading(false);
}
};
const handleCreate = () => {
setIsCreating(true);
setEditingEvent(null);
setFormData({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
};
const handleEdit = (event: Event) => {
setEditingEvent(event);
setIsCreating(false);
setFormData({
date: event.date,
name: event.name,
description: event.description,
type: event.type,
room: event.room || "",
time: event.time || "",
maxPlaces: event.maxPlaces || undefined,
});
};
const [, startTransition] = useTransition();
const handleSave = async () => {
setSaving(true);
startTransition(async () => {
try {
let result;
if (isCreating) {
result = await createEvent(formData);
} else if (editingEvent) {
result = await updateEvent(editingEvent.id, formData);
}
if (result?.success) {
await fetchEvents();
setEditingEvent(null);
setIsCreating(false);
setFormData({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
} else {
alert(result?.error || "Erreur lors de la sauvegarde");
}
} catch (error) {
console.error("Error saving event:", error);
alert("Erreur lors de la sauvegarde");
} finally {
setSaving(false);
}
});
};
const handleDelete = async (eventId: string) => {
if (!confirm("Êtes-vous sûr de vouloir supprimer cet événement ?")) {
return;
}
startTransition(async () => {
try {
const result = await deleteEvent(eventId);
if (result.success) {
await fetchEvents();
} else {
alert(result.error || "Erreur lors de la suppression");
}
} catch (error) {
console.error("Error deleting event:", error);
alert("Erreur lors de la suppression");
}
});
};
const handleCancel = () => {
setEditingEvent(null);
setIsCreating(false);
setFormData({
date: "",
name: "",
description: "",
type: "ATELIER",
room: "",
time: "",
maxPlaces: undefined,
});
};
if (loading) {
return <div className="text-center text-gray-400 py-8">Chargement...</div>;
}
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 mb-4">
<h3 className="text-lg sm:text-xl font-gaming font-bold text-pixel-gold break-words">
Événements ({events.length})
</h3>
{!isCreating && !editingEvent && (
<button
onClick={handleCreate}
className="px-3 sm:px-4 py-2 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-[10px] sm:text-xs tracking-widest rounded hover:bg-green-900/30 transition whitespace-nowrap flex-shrink-0"
>
+ Nouvel événement
</button>
)}
</div>
{(isCreating || editingEvent) && (
<div className="bg-black/60 border border-pixel-gold/20 rounded p-3 sm:p-4 mb-4">
<h4 className="text-pixel-gold font-bold mb-4 text-base sm:text-lg break-words">
{isCreating ? "Créer un événement" : "Modifier l'événement"}
</h4>
<div className="space-y-4">
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Date
</label>
<input
type="date"
value={formData.date}
onChange={(e) =>
setFormData({ ...formData, date: e.target.value })
}
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Nom
</label>
<input
type="text"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
placeholder="Nom de l'événement"
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Description
</label>
<textarea
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
placeholder="Description de l'événement"
rows={4}
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Type
</label>
<select
value={formData.type}
onChange={(e) =>
setFormData({
...formData,
type: e.target.value as Event["type"],
})
}
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
>
{eventTypes.map((type) => (
<option key={type} value={type}>
{getEventTypeLabel(type)}
</option>
))}
</select>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Salle
</label>
<input
type="text"
value={formData.room || ""}
onChange={(e) =>
setFormData({ ...formData, room: e.target.value })
}
placeholder="Ex: Nautilus"
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Heure
</label>
<input
type="text"
value={formData.time || ""}
onChange={(e) =>
setFormData({ ...formData, time: e.target.value })
}
placeholder="Ex: 11h-12h"
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
<div>
<label className="block text-xs sm:text-sm text-gray-300 mb-1">
Places max
</label>
<input
type="number"
value={formData.maxPlaces || ""}
onChange={(e) =>
setFormData({
...formData,
maxPlaces: e.target.value
? parseInt(e.target.value)
: undefined,
})
}
placeholder="Ex: 25"
className="w-full px-3 py-2 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm"
/>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-2">
<button
onClick={handleSave}
disabled={saving}
className="px-4 py-2 border border-green-500/50 bg-green-900/20 text-green-400 uppercase text-xs tracking-widest rounded hover:bg-green-900/30 transition disabled:opacity-50"
>
{saving ? "Enregistrement..." : "Enregistrer"}
</button>
<button
onClick={handleCancel}
className="px-4 py-2 border border-gray-600/50 bg-gray-900/20 text-gray-400 uppercase text-xs tracking-widest rounded hover:bg-gray-900/30 transition"
>
Annuler
</button>
</div>
</div>
</div>
)}
{events.length === 0 ? (
<div className="text-center text-gray-400 py-8">
Aucun événement trouvé
</div>
) : (
<div className="space-y-3">
{events.map((event) => (
<div
key={event.id}
className="bg-black/60 border border-pixel-gold/20 rounded p-3 sm:p-4"
>
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3">
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2 sm:gap-3 mb-2">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
{event.name}
</h4>
<span className="px-2 py-1 bg-pixel-gold/20 border border-pixel-gold/50 text-pixel-gold text-[10px] sm:text-xs uppercase rounded whitespace-nowrap flex-shrink-0">
{getEventTypeLabel(event.type)}
</span>
<span
className={`px-2 py-1 text-[10px] sm:text-xs uppercase rounded whitespace-nowrap flex-shrink-0 ${(() => {
const status = calculateEventStatus(event.date);
return status === "UPCOMING"
? "bg-green-900/50 border border-green-500/50 text-green-400"
: 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";
})()}`}
>
{getStatusLabel(calculateEventStatus(event.date))}
</span>
</div>
<p className="text-gray-400 text-xs sm:text-sm mb-2 break-words">
{event.description}
</p>
<div className="flex flex-wrap items-center gap-2 sm:gap-4 mt-2">
<p className="text-gray-500 text-[10px] sm:text-xs whitespace-nowrap">
Date: {new Date(event.date).toLocaleDateString("fr-FR")}
</p>
{event.room && (
<p className="text-gray-500 text-[10px] sm:text-xs whitespace-nowrap">
📍 Salle: {event.room}
</p>
)}
{event.time && (
<p className="text-gray-500 text-[10px] sm:text-xs whitespace-nowrap">
🕐 Heure: {event.time}
</p>
)}
{event.maxPlaces && (
<p className="text-gray-500 text-[10px] sm:text-xs whitespace-nowrap">
👥 Places: {event.maxPlaces}
</p>
)}
<span className="px-2 py-1 bg-blue-900/30 border border-blue-500/50 text-blue-400 text-[10px] sm:text-xs rounded whitespace-nowrap flex-shrink-0">
{event.registrationsCount || 0} inscrit
{event.registrationsCount !== 1 ? "s" : ""}
</span>
</div>
</div>
{!isCreating && !editingEvent && (
<div className="flex gap-2 sm:ml-4 flex-shrink-0">
<button
onClick={() => handleEdit(event)}
className="px-2 sm:px-3 py-1 border border-pixel-gold/50 bg-black/60 text-white uppercase text-[10px] sm:text-xs tracking-widest rounded hover:bg-pixel-gold/10 transition whitespace-nowrap"
>
Modifier
</button>
<button
onClick={() => handleDelete(event.id)}
className="px-2 sm:px-3 py-1 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-[10px] sm:text-xs tracking-widest rounded hover:bg-red-900/30 transition whitespace-nowrap"
>
Supprimer
</button>
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
);
}