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
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s
This commit is contained in:
422
components/admin/EventManagement.tsx
Normal file
422
components/admin/EventManagement.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user