Refactor component imports and structure: Update import paths for various components to improve organization, moving them into appropriate subdirectories. Remove unused components related to user and event management, enhancing code clarity and maintainability across the application.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s

This commit is contained in:
Julien Froidefond
2025-12-12 16:48:41 +01:00
parent 880e96d6e4
commit 97db800c73
27 changed files with 23 additions and 23 deletions

View File

@@ -0,0 +1,422 @@
"use client";
import { useState, useEffect, useTransition } from "react";
import { calculateEventStatus } from "@/lib/eventStatus";
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
import { Input, Textarea, Button, Card, Badge } from "@/components/ui";
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}
variant="success"
size="sm"
className="whitespace-nowrap flex-shrink-0"
>
+ Nouvel événement
</Button>
)}
</div>
{(isCreating || editingEvent) && (
<Card variant="default" className="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">
<Input
type="date"
label="Date"
value={formData.date}
onChange={(e) =>
setFormData({ ...formData, date: e.target.value })
}
className="text-xs sm:text-sm px-3 py-2"
/>
<Input
type="text"
label="Nom"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
placeholder="Nom de l'événement"
className="text-xs sm:text-sm px-3 py-2"
/>
<Textarea
label="Description"
value={formData.description}
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
placeholder="Description de l'événement"
rows={4}
className="text-xs sm:text-sm px-3 py-2"
/>
<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">
<Input
type="text"
label="Salle"
value={formData.room || ""}
onChange={(e) =>
setFormData({ ...formData, room: e.target.value })
}
placeholder="Ex: Nautilus"
className="text-xs sm:text-sm px-3 py-2"
/>
<Input
type="text"
label="Heure"
value={formData.time || ""}
onChange={(e) =>
setFormData({ ...formData, time: e.target.value })
}
placeholder="Ex: 11h-12h"
className="text-xs sm:text-sm px-3 py-2"
/>
<Input
type="number"
label="Places max"
value={formData.maxPlaces || ""}
onChange={(e) =>
setFormData({
...formData,
maxPlaces: e.target.value
? parseInt(e.target.value)
: undefined,
})
}
placeholder="Ex: 25"
className="text-xs sm:text-sm px-3 py-2"
/>
</div>
<div className="flex flex-col sm:flex-row gap-2">
<Button
onClick={handleSave}
variant="success"
size="md"
disabled={saving}
>
{saving ? "Enregistrement..." : "Enregistrer"}
</Button>
<Button onClick={handleCancel} variant="secondary" size="md">
Annuler
</Button>
</div>
</div>
</Card>
)}
{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) => {
const status = calculateEventStatus(event.date);
const statusVariant =
status === "UPCOMING"
? "success"
: status === "LIVE"
? "warning"
: "default";
return (
<Card key={event.id} variant="default" className="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>
<Badge variant="default" size="sm">
{getEventTypeLabel(event.type)}
</Badge>
<Badge variant={statusVariant} size="sm">
{getStatusLabel(status)}
</Badge>
</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>
)}
<Badge variant="info" size="sm">
{event.registrationsCount || 0} inscrit
{event.registrationsCount !== 1 ? "s" : ""}
</Badge>
</div>
</div>
{!isCreating && !editingEvent && (
<div className="flex gap-2 sm:ml-4 flex-shrink-0">
<Button
onClick={() => handleEdit(event)}
variant="primary"
size="sm"
className="whitespace-nowrap"
>
Modifier
</Button>
<Button
onClick={() => handleDelete(event.id)}
variant="danger"
size="sm"
className="whitespace-nowrap"
>
Supprimer
</Button>
</div>
)}
</div>
</Card>
);
})}
</div>
)}
</div>
);
}