Refactor AdminPage and remove AdminPanel component: Simplify admin navigation by redirecting to preferences page and eliminating the AdminPanel component, streamlining the admin interface.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m21s

This commit is contained in:
Julien Froidefond
2025-12-19 14:02:06 +01:00
parent 82069c74bc
commit 14c767cfc0
16 changed files with 440 additions and 252 deletions

View File

@@ -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 (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Défis
</h2>
<ChallengeManagement initialChallenges={serializedChallenges} />
</Card>
);
}

34
app/admin/events/page.tsx Normal file
View File

@@ -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 (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Événements
</h2>
<EventManagement initialEvents={serializedEvents} />
</Card>
);
}

View File

@@ -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 (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Feedbacks
</h2>
<FeedbackManagement
initialFeedbacks={serializedFeedbacks}
initialStatistics={serializedStatistics}
/>
</Card>
);
}

90
app/admin/houses/page.tsx Normal file
View File

@@ -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 (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Maisons
</h2>
<HouseManagement initialHouses={serializedHouses} />
</Card>
);
}

52
app/admin/layout.tsx Normal file
View File

@@ -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 (
<main className="min-h-screen bg-black relative">
{/* Background Image */}
<div
className="fixed inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `url('/got-light.jpg')`,
}}
>
{/* Dark overlay for readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
</div>
<NavigationWrapper />
<section className="relative w-full min-h-screen flex flex-col items-center overflow-hidden pt-24 pb-16">
<div className="relative z-10 w-full max-w-6xl mx-auto px-8 py-16">
<SectionTitle variant="gradient" size="md" className="mb-16 text-center">
ADMIN
</SectionTitle>
<AdminNavigation />
{children}
</div>
</section>
</main>
);
}

View File

@@ -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 (
<main className="min-h-screen bg-black relative">
{/* Background Image */}
<div
className="fixed inset-0 bg-cover bg-center bg-no-repeat"
style={{
backgroundImage: `url('/got-light.jpg')`,
}}
>
{/* Dark overlay for readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/60 to-black/80"></div>
</div>
<NavigationWrapper />
<AdminPanel initialPreferences={sitePreferences} />
</main>
);
redirect("/admin/preferences");
}

View File

@@ -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 (
<Card variant="dark" className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<h2 className="text-xl sm:text-2xl font-gaming font-bold text-pixel-gold break-words">
Préférences UI Globales
</h2>
</div>
<div className="space-y-4">
<BackgroundPreferences initialPreferences={sitePreferences} />
<EventPointsPreferences initialPreferences={sitePreferences} />
<EventFeedbackPointsPreferences initialPreferences={sitePreferences} />
<HousePointsPreferences initialPreferences={sitePreferences} />
</div>
</Card>
);
}

42
app/admin/users/page.tsx Normal file
View File

@@ -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 (
<Card variant="dark" className="p-6">
<h2 className="text-2xl font-gaming font-bold mb-6 text-pixel-gold">
Gestion des Utilisateurs
</h2>
<UserManagement initialUsers={serializedUsers} />
</Card>
);
}