diff --git a/app/admin/challenges/page.tsx b/app/admin/challenges/page.tsx
new file mode 100644
index 0000000..1f0bb8e
--- /dev/null
+++ b/app/admin/challenges/page.tsx
@@ -0,0 +1,26 @@
+import ChallengeManagement from "@/components/admin/ChallengeManagement";
+import { Card } from "@/components/ui";
+import { challengeService } from "@/services/challenges/challenge.service";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminChallengesPage() {
+ const challenges = await challengeService.getAllChallenges();
+
+ // Sérialiser les dates pour le client
+ const serializedChallenges = challenges.map((challenge) => ({
+ ...challenge,
+ createdAt: challenge.createdAt.toISOString(),
+ acceptedAt: challenge.acceptedAt?.toISOString() ?? null,
+ completedAt: challenge.completedAt?.toISOString() ?? null,
+ }));
+
+ return (
+
+
+ Gestion des Défis
+
+
+
+ );
+}
diff --git a/app/admin/events/page.tsx b/app/admin/events/page.tsx
new file mode 100644
index 0000000..3f99f2f
--- /dev/null
+++ b/app/admin/events/page.tsx
@@ -0,0 +1,34 @@
+import EventManagement from "@/components/admin/EventManagement";
+import { Card } from "@/components/ui";
+import { eventService } from "@/services/events/event.service";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminEventsPage() {
+ const events = await eventService.getEventsWithStatus();
+
+ // Transformer les données pour la sérialisation
+ const serializedEvents = events.map((event) => ({
+ id: event.id,
+ date: event.date.toISOString(),
+ name: event.name,
+ description: event.description,
+ type: event.type,
+ status: event.status,
+ room: event.room,
+ time: event.time,
+ maxPlaces: event.maxPlaces,
+ createdAt: event.createdAt.toISOString(),
+ updatedAt: event.updatedAt.toISOString(),
+ registrationsCount: event.registrationsCount,
+ }));
+
+ return (
+
+
+ Gestion des Événements
+
+
+
+ );
+}
diff --git a/app/admin/feedbacks/page.tsx b/app/admin/feedbacks/page.tsx
new file mode 100644
index 0000000..5bfe6c3
--- /dev/null
+++ b/app/admin/feedbacks/page.tsx
@@ -0,0 +1,77 @@
+import FeedbackManagement from "@/components/admin/FeedbackManagement";
+import { Card } from "@/components/ui";
+import { eventFeedbackService } from "@/services/events/event-feedback.service";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminFeedbacksPage() {
+ const [feedbacksRaw, statistics] = await Promise.all([
+ eventFeedbackService.getAllFeedbacks(),
+ eventFeedbackService.getFeedbackStatistics(),
+ ]);
+
+ // Type assertion car getAllFeedbacks inclut event et user par défaut
+ const feedbacks = feedbacksRaw as unknown as Array<{
+ id: string;
+ rating: number;
+ comment: string | null;
+ isRead: boolean;
+ createdAt: Date;
+ event: {
+ id: string;
+ name: string;
+ date: Date;
+ type: string;
+ };
+ user: {
+ id: string;
+ username: string;
+ email: string;
+ avatar: string | null;
+ score: number;
+ };
+ }>;
+
+ // Sérialiser les dates pour le client
+ const serializedFeedbacks = feedbacks.map((feedback) => ({
+ id: feedback.id,
+ rating: feedback.rating,
+ comment: feedback.comment,
+ isRead: feedback.isRead,
+ createdAt: feedback.createdAt.toISOString(),
+ event: {
+ id: feedback.event.id,
+ name: feedback.event.name,
+ date: feedback.event.date.toISOString(),
+ type: feedback.event.type,
+ },
+ user: {
+ id: feedback.user.id,
+ username: feedback.user.username,
+ email: feedback.user.email,
+ avatar: feedback.user.avatar,
+ score: feedback.user.score,
+ },
+ }));
+
+ const serializedStatistics = statistics.map((stat) => ({
+ eventId: stat.eventId,
+ eventName: stat.eventName,
+ eventDate: stat.eventDate?.toISOString() ?? null,
+ eventType: stat.eventType,
+ averageRating: stat.averageRating,
+ feedbackCount: stat.feedbackCount,
+ }));
+
+ return (
+
+
+ Gestion des Feedbacks
+
+
+
+ );
+}
diff --git a/app/admin/houses/page.tsx b/app/admin/houses/page.tsx
new file mode 100644
index 0000000..d70a99a
--- /dev/null
+++ b/app/admin/houses/page.tsx
@@ -0,0 +1,90 @@
+import HouseManagement from "@/components/admin/HouseManagement";
+import { Card } from "@/components/ui";
+import { houseService } from "@/services/houses/house.service";
+import { Prisma } from "@/prisma/generated/prisma/client";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminHousesPage() {
+ type HouseWithIncludes = Prisma.HouseGetPayload<{
+ include: {
+ creator: {
+ select: {
+ id: true;
+ username: true;
+ avatar: true;
+ };
+ };
+ memberships: {
+ include: {
+ user: {
+ select: {
+ id: true;
+ username: true;
+ avatar: true;
+ score: true;
+ level: true;
+ };
+ };
+ };
+ };
+ };
+ }>;
+
+ const houses = (await houseService.getAllHouses({
+ include: {
+ creator: {
+ select: {
+ id: true,
+ username: true,
+ avatar: true,
+ },
+ },
+ memberships: {
+ include: {
+ user: {
+ select: {
+ id: true,
+ username: true,
+ avatar: true,
+ score: true,
+ level: true,
+ },
+ },
+ },
+ orderBy: [{ role: "asc" }, { joinedAt: "asc" }],
+ },
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+ })) as unknown as HouseWithIncludes[];
+
+ // Transformer les données pour la sérialisation
+ const serializedHouses = houses.map((house) => ({
+ id: house.id,
+ name: house.name,
+ description: house.description,
+ creatorId: house.creatorId,
+ creator: house.creator,
+ createdAt: house.createdAt.toISOString(),
+ updatedAt: house.updatedAt.toISOString(),
+ membersCount: house.memberships?.length || 0,
+ memberships:
+ house.memberships?.map((membership) => ({
+ id: membership.id,
+ role: membership.role,
+ joinedAt: membership.joinedAt.toISOString(),
+ user: membership.user,
+ })) || [],
+ }));
+
+ return (
+
+
+ Gestion des Maisons
+
+
+
+ );
+}
diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx
new file mode 100644
index 0000000..e481931
--- /dev/null
+++ b/app/admin/layout.tsx
@@ -0,0 +1,52 @@
+import { redirect } from "next/navigation";
+import { auth } from "@/lib/auth";
+import { Role } from "@/prisma/generated/prisma/client";
+import NavigationWrapper from "@/components/navigation/NavigationWrapper";
+import AdminNavigation from "@/components/admin/AdminNavigation";
+import { SectionTitle } from "@/components/ui";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const session = await auth();
+
+ if (!session?.user) {
+ redirect("/login");
+ }
+
+ if (session.user.role !== Role.ADMIN) {
+ redirect("/");
+ }
+
+ return (
+
+ {/* Background Image */}
+
+ {/* Dark overlay for readability */}
+
+
+
+
+
+
+ ADMIN
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
index 83d27d4..b81e586 100644
--- a/app/admin/page.tsx
+++ b/app/admin/page.tsx
@@ -1,41 +1,7 @@
import { redirect } from "next/navigation";
-import { auth } from "@/lib/auth";
-import { sitePreferencesService } from "@/services/preferences/site-preferences.service";
-import { Role } from "@/prisma/generated/prisma/client";
-import NavigationWrapper from "@/components/navigation/NavigationWrapper";
-import AdminPanel from "@/components/admin/AdminPanel";
export const dynamic = "force-dynamic";
export default async function AdminPage() {
- const session = await auth();
-
- if (!session?.user) {
- redirect("/login");
- }
-
- if (session.user.role !== Role.ADMIN) {
- redirect("/");
- }
-
- // Récupérer les préférences globales du site (ou créer si elles n'existent pas)
- const sitePreferences =
- await sitePreferencesService.getOrCreateSitePreferences();
-
- return (
-
- {/* Background Image */}
-
- {/* Dark overlay for readability */}
-
-
-
-
-
- );
+ redirect("/admin/preferences");
}
diff --git a/app/admin/preferences/page.tsx b/app/admin/preferences/page.tsx
new file mode 100644
index 0000000..a88be75
--- /dev/null
+++ b/app/admin/preferences/page.tsx
@@ -0,0 +1,30 @@
+import { sitePreferencesService } from "@/services/preferences/site-preferences.service";
+import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
+import EventPointsPreferences from "@/components/admin/EventPointsPreferences";
+import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences";
+import HousePointsPreferences from "@/components/admin/HousePointsPreferences";
+import { Card } from "@/components/ui";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminPreferencesPage() {
+ const sitePreferences =
+ await sitePreferencesService.getOrCreateSitePreferences();
+
+ return (
+
+
+
+ Préférences UI Globales
+
+
+
+
+
+
+
+
+
+ );
+}
+
diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx
new file mode 100644
index 0000000..9a42154
--- /dev/null
+++ b/app/admin/users/page.tsx
@@ -0,0 +1,42 @@
+import UserManagement from "@/components/admin/UserManagement";
+import { Card } from "@/components/ui";
+import { userService } from "@/services/users/user.service";
+
+export const dynamic = "force-dynamic";
+
+export default async function AdminUsersPage() {
+ const users = await userService.getAllUsers({
+ orderBy: {
+ score: "desc",
+ },
+ select: {
+ id: true,
+ username: true,
+ email: true,
+ role: true,
+ score: true,
+ level: true,
+ hp: true,
+ maxHp: true,
+ xp: true,
+ maxXp: true,
+ avatar: true,
+ createdAt: true,
+ },
+ });
+
+ // Sérialiser les dates pour le client
+ const serializedUsers = users.map((user) => ({
+ ...user,
+ createdAt: user.createdAt.toISOString(),
+ }));
+
+ return (
+
+
+ Gestion des Utilisateurs
+
+
+
+ );
+}
diff --git a/components/admin/AdminNavigation.tsx b/components/admin/AdminNavigation.tsx
new file mode 100644
index 0000000..b53bb31
--- /dev/null
+++ b/components/admin/AdminNavigation.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { Button } from "@/components/ui";
+
+const adminSections = [
+ { id: "preferences", label: "Préférences UI", path: "/admin/preferences" },
+ { id: "users", label: "Utilisateurs", path: "/admin/users" },
+ { id: "events", label: "Événements", path: "/admin/events" },
+ { id: "feedbacks", label: "Feedbacks", path: "/admin/feedbacks" },
+ { id: "challenges", label: "Défis", path: "/admin/challenges" },
+ { id: "houses", label: "Maisons", path: "/admin/houses" },
+];
+
+export default function AdminNavigation() {
+ const pathname = usePathname();
+
+ return (
+
+ {adminSections.map((section) => {
+ const isActive = pathname === section.path ||
+ (section.path === "/admin/preferences" && pathname === "/admin");
+
+ return (
+
+ );
+ })}
+
+ );
+}
+
diff --git a/components/admin/AdminPanel.tsx b/components/admin/AdminPanel.tsx
deleted file mode 100644
index eb5ca4b..0000000
--- a/components/admin/AdminPanel.tsx
+++ /dev/null
@@ -1,168 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import UserManagement from "@/components/admin/UserManagement";
-import EventManagement from "@/components/admin/EventManagement";
-import FeedbackManagement from "@/components/admin/FeedbackManagement";
-import ChallengeManagement from "@/components/admin/ChallengeManagement";
-import HouseManagement from "@/components/admin/HouseManagement";
-import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
-import EventPointsPreferences from "@/components/admin/EventPointsPreferences";
-import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences";
-import HousePointsPreferences from "@/components/admin/HousePointsPreferences";
-import { Button, Card, SectionTitle } from "@/components/ui";
-
-interface SitePreferences {
- id: string;
- homeBackground: string | null;
- eventsBackground: string | null;
- leaderboardBackground: string | null;
- challengesBackground: string | null;
- eventRegistrationPoints: number;
- eventFeedbackPoints: number;
- houseJoinPoints: number;
- houseLeavePoints: number;
- houseCreatePoints: number;
-}
-
-interface AdminPanelProps {
- initialPreferences: SitePreferences;
-}
-
-type AdminSection =
- | "preferences"
- | "users"
- | "events"
- | "feedbacks"
- | "challenges"
- | "houses";
-
-export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
- const [activeSection, setActiveSection] =
- useState("preferences");
-
- return (
-
-
-
- ADMIN
-
-
- {/* Navigation Tabs */}
-
-
-
-
-
-
-
-
-
- {activeSection === "preferences" && (
-
-
-
- Préférences UI Globales
-
-
-
-
-
-
-
-
-
- )}
-
- {activeSection === "users" && (
-
-
- Gestion des Utilisateurs
-
-
-
- )}
-
- {activeSection === "events" && (
-
-
- Gestion des Événements
-
-
-
- )}
-
- {activeSection === "feedbacks" && (
-
-
- Gestion des Feedbacks
-
-
-
- )}
-
- {activeSection === "challenges" && (
-
-
- Gestion des Défis
-
-
-
- )}
-
- {activeSection === "houses" && (
-
-
- Gestion des Maisons
-
-
-
- )}
-
-
- );
-}
diff --git a/components/admin/ChallengeManagement.tsx b/components/admin/ChallengeManagement.tsx
index c02f7ec..b758cc3 100644
--- a/components/admin/ChallengeManagement.tsx
+++ b/components/admin/ChallengeManagement.tsx
@@ -42,9 +42,13 @@ interface Challenge {
acceptedAt: string | null;
}
-export default function ChallengeManagement() {
- const [challenges, setChallenges] = useState([]);
- const [loading, setLoading] = useState(true);
+interface ChallengeManagementProps {
+ initialChallenges: Challenge[];
+}
+
+export default function ChallengeManagement({ initialChallenges }: ChallengeManagementProps) {
+ const [challenges, setChallenges] = useState(initialChallenges);
+ const [loading, setLoading] = useState(false);
const [selectedChallenge, setSelectedChallenge] = useState(
null
);
@@ -60,10 +64,6 @@ export default function ChallengeManagement() {
const [successMessage, setSuccessMessage] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
- useEffect(() => {
- fetchChallenges();
- }, []);
-
const fetchChallenges = async () => {
try {
const response = await fetch("/api/admin/challenges");
@@ -73,8 +73,6 @@ export default function ChallengeManagement() {
}
} catch (error) {
console.error("Error fetching challenges:", error);
- } finally {
- setLoading(false);
}
};
diff --git a/components/admin/EventManagement.tsx b/components/admin/EventManagement.tsx
index 2794ef5..1ba9889 100644
--- a/components/admin/EventManagement.tsx
+++ b/components/admin/EventManagement.tsx
@@ -92,9 +92,13 @@ const getStatusLabel = (status: Event["status"]) => {
}
};
-export default function EventManagement() {
- const [events, setEvents] = useState([]);
- const [loading, setLoading] = useState(true);
+interface EventManagementProps {
+ initialEvents: Event[];
+}
+
+export default function EventManagement({ initialEvents }: EventManagementProps) {
+ const [events, setEvents] = useState(initialEvents);
+ const [loading, setLoading] = useState(false);
const [editingEvent, setEditingEvent] = useState(null);
const [isCreating, setIsCreating] = useState(false);
const [saving, setSaving] = useState(false);
@@ -116,10 +120,6 @@ export default function EventManagement() {
maxPlaces: undefined,
});
- useEffect(() => {
- fetchEvents();
- }, []);
-
const fetchEvents = async () => {
try {
const response = await fetch("/api/admin/events");
@@ -129,8 +129,6 @@ export default function EventManagement() {
}
} catch (error) {
console.error("Error fetching events:", error);
- } finally {
- setLoading(false);
}
};
diff --git a/components/admin/FeedbackManagement.tsx b/components/admin/FeedbackManagement.tsx
index 1c3728c..1be909f 100644
--- a/components/admin/FeedbackManagement.tsx
+++ b/components/admin/FeedbackManagement.tsx
@@ -38,10 +38,18 @@ interface EventStatistics {
feedbackCount: number;
}
-export default function FeedbackManagement() {
- const [feedbacks, setFeedbacks] = useState([]);
- const [statistics, setStatistics] = useState([]);
- const [loading, setLoading] = useState(true);
+interface FeedbackManagementProps {
+ initialFeedbacks: Feedback[];
+ initialStatistics: EventStatistics[];
+}
+
+export default function FeedbackManagement({
+ initialFeedbacks,
+ initialStatistics,
+}: FeedbackManagementProps) {
+ const [feedbacks, setFeedbacks] = useState(initialFeedbacks);
+ const [statistics, setStatistics] = useState(initialStatistics);
+ const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [selectedEvent, setSelectedEvent] = useState(null);
const [addingPoints, setAddingPoints] = useState>(
@@ -49,10 +57,6 @@ export default function FeedbackManagement() {
);
const [markingRead, setMarkingRead] = useState>({});
- useEffect(() => {
- fetchFeedbacks();
- }, []);
-
const fetchFeedbacks = async () => {
try {
const response = await fetch("/api/admin/feedback");
@@ -65,8 +69,6 @@ export default function FeedbackManagement() {
setStatistics(data.statistics || []);
} catch {
setError("Erreur lors du chargement des feedbacks");
- } finally {
- setLoading(false);
}
};
diff --git a/components/admin/HouseManagement.tsx b/components/admin/HouseManagement.tsx
index d398e88..c0ad810 100644
--- a/components/admin/HouseManagement.tsx
+++ b/components/admin/HouseManagement.tsx
@@ -71,9 +71,13 @@ const getRoleColor = (role: string) => {
}
};
-export default function HouseManagement() {
- const [houses, setHouses] = useState([]);
- const [loading, setLoading] = useState(true);
+interface HouseManagementProps {
+ initialHouses: House[];
+}
+
+export default function HouseManagement({ initialHouses }: HouseManagementProps) {
+ const [houses, setHouses] = useState(initialHouses);
+ const [loading, setLoading] = useState(false);
const [editingHouse, setEditingHouse] = useState(null);
const [saving, setSaving] = useState(false);
const [deletingHouseId, setDeletingHouseId] = useState(null);
@@ -85,10 +89,6 @@ export default function HouseManagement() {
});
const [, startTransition] = useTransition();
- useEffect(() => {
- fetchHouses();
- }, []);
-
const fetchHouses = async () => {
try {
const response = await fetch("/api/admin/houses");
@@ -98,8 +98,6 @@ export default function HouseManagement() {
}
} catch (error) {
console.error("Error fetching houses:", error);
- } finally {
- setLoading(false);
}
};
diff --git a/components/admin/UserManagement.tsx b/components/admin/UserManagement.tsx
index 00a9061..612ad26 100644
--- a/components/admin/UserManagement.tsx
+++ b/components/admin/UserManagement.tsx
@@ -37,19 +37,19 @@ interface EditingUser {
role: string | null;
}
-export default function UserManagement() {
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
+interface UserManagementProps {
+ initialUsers: User[];
+}
+
+export default function UserManagement({ initialUsers }: UserManagementProps) {
+ const [users, setUsers] = useState(initialUsers);
+ const [loading, setLoading] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const [saving, setSaving] = useState(false);
const [deletingUserId, setDeletingUserId] = useState(null);
const [, startTransition] = useTransition();
const [uploadingAvatar, setUploadingAvatar] = useState(null);
- useEffect(() => {
- fetchUsers();
- }, []);
-
const fetchUsers = async () => {
try {
const response = await fetch("/api/admin/users");
@@ -59,8 +59,6 @@ export default function UserManagement() {
}
} catch (error) {
console.error("Error fetching users:", error);
- } finally {
- setLoading(false);
}
};
diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx
index 492d416..70269b3 100644
--- a/components/ui/Button.tsx
+++ b/components/ui/Button.tsx
@@ -1,13 +1,17 @@
"use client";
-import { ButtonHTMLAttributes, ReactNode, ElementType } from "react";
+import { ButtonHTMLAttributes, ReactNode, ElementType, AnchorHTMLAttributes } from "react";
+import Link from "next/link";
-interface ButtonProps extends ButtonHTMLAttributes {
+type ButtonProps = ButtonHTMLAttributes & {
variant?: "primary" | "secondary" | "success" | "danger" | "ghost";
size?: "sm" | "md" | "lg";
children: ReactNode;
as?: ElementType;
-}
+} & (
+ | { as?: Exclude }
+ | { as: typeof Link; href: string }
+);
const variantClasses = {
primary: "btn-primary border transition-colors",