Compare commits

...

6 Commits

Author SHA1 Message Date
Julien Froidefond
5eddf36121 Refactor UserManagement component layout: Update user display to a grid format for improved responsiveness, enhance user information presentation with clearer stats and action buttons, and streamline the overall UI for better user experience.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m3s
2025-12-16 16:55:40 +01:00
Julien Froidefond
ec965cd59d Enhance ChallengeManagement and EventManagement components: Refactor layout for better readability, implement event registration viewing with score editing functionality, and improve user feedback handling in modals. Update EventRegistrationService to fetch event registrations with user details, ensuring a more interactive admin experience. 2025-12-16 16:52:50 +01:00
Julien Froidefond
79c21955e0 Refactor modal implementation across admin components: Replace Card components with a reusable Modal component in ChallengeManagement, EventManagement, and UserManagement, enhancing UI consistency and maintainability. Update Modal to use React portals for improved rendering. 2025-12-16 16:50:06 +01:00
Julien Froidefond
16e4b63ffd Add feedback management features: Implement functions to add bonus points and mark feedback as read in the FeedbackManagement component. Update EventFeedback model to include isRead property, enhancing user interaction and feedback tracking. 2025-12-16 16:43:53 +01:00
Julien Froidefond
3dd82c2bd4 Add event registration and feedback points to site preferences: Update SitePreferences model and related components to include eventRegistrationPoints and eventFeedbackPoints, ensuring proper handling of user scores during event interactions. 2025-12-16 16:38:01 +01:00
Julien Froidefond
f45cc1839e Add score property to UserData interface across navigation and profile components: Update Navigation.tsx, NavigationWrapper.tsx, and PlayerStats.tsx to include score, ensuring consistent user data handling and display. 2025-12-16 16:29:21 +01:00
32 changed files with 2250 additions and 971 deletions

127
actions/admin/feedback.ts Normal file
View File

@@ -0,0 +1,127 @@
"use server";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";
import { prisma } from "@/services/database";
import { Role } from "@/prisma/generated/prisma/client";
import { NotFoundError } from "@/services/errors";
function checkAdminAccess() {
return async () => {
const session = await auth();
if (!session?.user || session.user.role !== Role.ADMIN) {
throw new Error("Accès refusé");
}
return session;
};
}
export async function addFeedbackBonusPoints(
userId: string,
points: number
) {
try {
await checkAdminAccess()();
// Vérifier que l'utilisateur existe
const user = await prisma.user.findUnique({
where: { id: userId },
select: { id: true, score: true },
});
if (!user) {
throw new NotFoundError("Utilisateur");
}
// Ajouter les points
const updatedUser = await prisma.user.update({
where: { id: userId },
data: {
score: {
increment: points,
},
},
select: {
id: true,
username: true,
score: true,
},
});
revalidatePath("/admin");
revalidatePath("/leaderboard");
return {
success: true,
message: `${points} points ajoutés avec succès`,
data: updatedUser,
};
} catch (error) {
console.error("Error adding bonus points:", error);
if (error instanceof NotFoundError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message === "Accès refusé") {
return { success: false, error: "Accès refusé" };
}
return {
success: false,
error: "Erreur lors de l'ajout des points",
};
}
}
export async function markFeedbackAsRead(feedbackId: string, isRead: boolean) {
try {
await checkAdminAccess()();
// Vérifier que le feedback existe
const feedback = await prisma.eventFeedback.findUnique({
where: { id: feedbackId },
select: { id: true },
});
if (!feedback) {
throw new NotFoundError("Feedback");
}
// Mettre à jour le statut
const updatedFeedback = await prisma.eventFeedback.update({
where: { id: feedbackId },
data: {
isRead,
},
select: {
id: true,
isRead: true,
},
});
revalidatePath("/admin");
return {
success: true,
message: isRead
? "Feedback marqué comme lu"
: "Feedback marqué comme non lu",
data: updatedFeedback,
};
} catch (error) {
console.error("Error marking feedback as read:", error);
if (error instanceof NotFoundError) {
return { success: false, error: error.message };
}
if (error instanceof Error && error.message === "Accès refusé") {
return { success: false, error: "Accès refusé" };
}
return {
success: false,
error: "Erreur lors de la mise à jour du feedback",
};
}
}

View File

@@ -20,6 +20,8 @@ export async function updateSitePreferences(data: {
eventsBackground?: string | null; eventsBackground?: string | null;
leaderboardBackground?: string | null; leaderboardBackground?: string | null;
challengesBackground?: string | null; challengesBackground?: string | null;
eventRegistrationPoints?: number;
eventFeedbackPoints?: number;
}) { }) {
try { try {
await checkAdminAccess()(); await checkAdminAccess()();
@@ -29,6 +31,8 @@ export async function updateSitePreferences(data: {
eventsBackground: data.eventsBackground, eventsBackground: data.eventsBackground,
leaderboardBackground: data.leaderboardBackground, leaderboardBackground: data.leaderboardBackground,
challengesBackground: data.challengesBackground, challengesBackground: data.challengesBackground,
eventRegistrationPoints: data.eventRegistrationPoints,
eventFeedbackPoints: data.eventFeedbackPoints,
}); });
revalidatePath("/admin"); revalidatePath("/admin");

View File

@@ -0,0 +1,31 @@
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { eventRegistrationService } from "@/services/events/event-registration.service";
import { Role } from "@/prisma/generated/prisma/client";
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user || session.user.role !== Role.ADMIN) {
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
}
const { id: eventId } = await params;
const registrations = await eventRegistrationService.getEventRegistrations(
eventId
);
return NextResponse.json(registrations);
} catch (error) {
console.error("Error fetching event registrations:", error);
return NextResponse.json(
{ error: "Erreur lors de la récupération des inscrits" },
{ status: 500 }
);
}
}

View File

@@ -128,6 +128,9 @@ export default function FeedbackPageClient({
}); });
} }
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
// Rediriger après 2 secondes // Rediriger après 2 secondes
setTimeout(() => { setTimeout(() => {
router.push("/events"); router.push("/events");

View File

@@ -6,6 +6,8 @@ import EventManagement from "@/components/admin/EventManagement";
import FeedbackManagement from "@/components/admin/FeedbackManagement"; import FeedbackManagement from "@/components/admin/FeedbackManagement";
import ChallengeManagement from "@/components/admin/ChallengeManagement"; import ChallengeManagement from "@/components/admin/ChallengeManagement";
import BackgroundPreferences from "@/components/admin/BackgroundPreferences"; import BackgroundPreferences from "@/components/admin/BackgroundPreferences";
import EventPointsPreferences from "@/components/admin/EventPointsPreferences";
import EventFeedbackPointsPreferences from "@/components/admin/EventFeedbackPointsPreferences";
import { Button, Card, SectionTitle } from "@/components/ui"; import { Button, Card, SectionTitle } from "@/components/ui";
interface SitePreferences { interface SitePreferences {
@@ -14,6 +16,8 @@ interface SitePreferences {
eventsBackground: string | null; eventsBackground: string | null;
leaderboardBackground: string | null; leaderboardBackground: string | null;
challengesBackground: string | null; challengesBackground: string | null;
eventRegistrationPoints: number;
eventFeedbackPoints: number;
} }
interface AdminPanelProps { interface AdminPanelProps {
@@ -93,6 +97,8 @@ export default function AdminPanel({ initialPreferences }: AdminPanelProps) {
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<BackgroundPreferences initialPreferences={initialPreferences} /> <BackgroundPreferences initialPreferences={initialPreferences} />
<EventPointsPreferences initialPreferences={initialPreferences} />
<EventFeedbackPointsPreferences initialPreferences={initialPreferences} />
</div> </div>
</Card> </Card>
)} )}

View File

@@ -11,6 +11,7 @@ interface SitePreferences {
eventsBackground: string | null; eventsBackground: string | null;
leaderboardBackground: string | null; leaderboardBackground: string | null;
challengesBackground: string | null; challengesBackground: string | null;
eventRegistrationPoints?: number;
} }
interface BackgroundPreferencesProps { interface BackgroundPreferencesProps {

View File

@@ -9,7 +9,15 @@ import {
adminCancelChallenge, adminCancelChallenge,
reactivateChallenge, reactivateChallenge,
} from "@/actions/admin/challenges"; } from "@/actions/admin/challenges";
import { Button, Card, Input, Textarea, Alert } from "@/components/ui"; import {
Button,
Card,
Input,
Textarea,
Alert,
Modal,
CloseButton,
} from "@/components/ui";
import { Avatar } from "@/components/ui"; import { Avatar } from "@/components/ui";
interface Challenge { interface Challenge {
@@ -417,23 +425,29 @@ export default function ChallengeManagement() {
{/* Modal de validation */} {/* Modal de validation */}
{selectedChallenge && ( {selectedChallenge && (
<div <Modal
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" isOpen={!!selectedChallenge}
onClose={() => {
setSelectedChallenge(null);
setWinnerId("");
setAdminComment("");
}}
size="lg"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold text-pixel-gold">
Désigner le gagnant
</h2>
<CloseButton
onClick={() => { onClick={() => {
setSelectedChallenge(null); setSelectedChallenge(null);
setWinnerId(""); setWinnerId("");
setAdminComment(""); setAdminComment("");
}} }}
> size="lg"
<Card />
variant="dark" </div>
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6">
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
Désigner le gagnant
</h2>
<div className="mb-6"> <div className="mb-6">
<h3 className="text-lg font-bold text-gray-300 mb-2"> <h3 className="text-lg font-bold text-gray-300 mb-2">
@@ -545,30 +559,36 @@ export default function ChallengeManagement() {
</Button> </Button>
</div> </div>
</div> </div>
</Card> </Modal>
</div>
)} )}
{/* Modal d'édition */} {/* Modal d'édition */}
{editingChallenge && ( {editingChallenge && (
<div <Modal
className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" isOpen={!!editingChallenge}
onClose={() => {
setEditingChallenge(null);
setEditTitle("");
setEditDescription("");
setEditPointsReward(0);
}}
size="lg"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold text-pixel-gold">
Modifier le défi
</h2>
<CloseButton
onClick={() => { onClick={() => {
setEditingChallenge(null); setEditingChallenge(null);
setEditTitle(""); setEditTitle("");
setEditDescription(""); setEditDescription("");
setEditPointsReward(0); setEditPointsReward(0);
}} }}
> size="lg"
<Card />
variant="dark" </div>
className="max-w-2xl w-full max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="p-6">
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
Modifier le défi
</h2>
<div className="space-y-4"> <div className="space-y-4">
<Input <Input
@@ -632,8 +652,7 @@ export default function ChallengeManagement() {
</div> </div>
</div> </div>
</div> </div>
</Card> </Modal>
</div>
)} )}
</div> </div>
); );

View File

@@ -0,0 +1,166 @@
"use client";
import { useState, useEffect } from "react";
import { updateSitePreferences } from "@/actions/admin/preferences";
import { Button, Card, Input } from "@/components/ui";
interface SitePreferences {
id: string;
eventFeedbackPoints: number;
}
interface EventFeedbackPointsPreferencesProps {
initialPreferences: SitePreferences;
}
export default function EventFeedbackPointsPreferences({
initialPreferences,
}: EventFeedbackPointsPreferencesProps) {
const [preferences, setPreferences] = useState<SitePreferences | null>(
initialPreferences
);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState({
eventFeedbackPoints: initialPreferences.eventFeedbackPoints.toString(),
});
const [isSaving, setIsSaving] = useState(false);
// Synchroniser les préférences quand initialPreferences change
useEffect(() => {
setPreferences(initialPreferences);
setFormData({
eventFeedbackPoints: initialPreferences.eventFeedbackPoints.toString(),
});
}, [initialPreferences]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = async () => {
const points = parseInt(formData.eventFeedbackPoints, 10);
if (isNaN(points) || points < 0) {
alert("Le nombre de points doit être un nombre positif");
return;
}
setIsSaving(true);
try {
const result = await updateSitePreferences({
eventFeedbackPoints: points,
});
if (result.success && result.data) {
setPreferences(result.data);
setFormData({
eventFeedbackPoints: result.data.eventFeedbackPoints.toString(),
});
setIsEditing(false);
} else {
console.error("Error updating preferences:", result.error);
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating preferences:", error);
alert("Erreur lors de la mise à jour");
} finally {
setIsSaving(false);
}
};
const handleCancel = () => {
setIsEditing(false);
if (preferences) {
setFormData({
eventFeedbackPoints: preferences.eventFeedbackPoints.toString(),
});
}
};
return (
<Card variant="default" className="p-3 sm:p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3 mb-4">
<div className="min-w-0 flex-1">
<h3 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Points de feedback sur les événements
</h3>
<p className="text-gray-400 text-xs sm:text-sm">
Nombre de points attribués lorsqu&apos;un utilisateur donne un feedback à un événement (première fois uniquement)
</p>
</div>
{!isEditing && (
<Button
onClick={handleEdit}
variant="primary"
size="sm"
className="whitespace-nowrap flex-shrink-0"
>
Modifier
</Button>
)}
</div>
{isEditing ? (
<div className="space-y-4">
<div>
<label
htmlFor="eventFeedbackPoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points de feedback
</label>
<Input
id="eventFeedbackPoints"
type="number"
min="0"
value={formData.eventFeedbackPoints}
onChange={(e) =>
setFormData({
...formData,
eventFeedbackPoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lors de leur premier feedback sur un événement
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button
onClick={handleSave}
variant="success"
size="md"
disabled={isSaving}
>
{isSaving ? "Enregistrement..." : "Enregistrer"}
</Button>
<Button
onClick={handleCancel}
variant="secondary"
size="md"
disabled={isSaving}
>
Annuler
</Button>
</div>
</div>
) : (
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[200px] flex-shrink-0">
Points actuels:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.eventFeedbackPoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
)}
</Card>
);
}

View File

@@ -3,7 +3,17 @@
import { useState, useEffect, useTransition } from "react"; import { useState, useEffect, useTransition } from "react";
import { calculateEventStatus } from "@/lib/eventStatus"; import { calculateEventStatus } from "@/lib/eventStatus";
import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events"; import { createEvent, updateEvent, deleteEvent } from "@/actions/admin/events";
import { Input, Textarea, Button, Card, Badge } from "@/components/ui"; import {
Input,
Textarea,
Button,
Card,
Badge,
Modal,
CloseButton,
Avatar,
} from "@/components/ui";
import { updateUser } from "@/actions/admin/users";
interface Event { interface Event {
id: string; id: string;
@@ -20,6 +30,24 @@ interface Event {
registrationsCount?: number; registrationsCount?: number;
} }
interface EventRegistration {
id: string;
userId: string;
eventId: string;
createdAt: string;
user: {
id: string;
username: string;
avatar: string | null;
score: number;
level: number;
hp: number;
maxHp: number;
xp: number;
maxXp: number;
};
}
interface EventFormData { interface EventFormData {
date: string; date: string;
name: string; name: string;
@@ -70,6 +98,14 @@ export default function EventManagement() {
const [editingEvent, setEditingEvent] = useState<Event | null>(null); const [editingEvent, setEditingEvent] = useState<Event | null>(null);
const [isCreating, setIsCreating] = useState(false); const [isCreating, setIsCreating] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [viewingRegistrations, setViewingRegistrations] =
useState<Event | null>(null);
const [registrations, setRegistrations] = useState<EventRegistration[]>([]);
const [loadingRegistrations, setLoadingRegistrations] = useState(false);
const [editingScores, setEditingScores] = useState<Record<string, number>>(
{}
);
const [savingScore, setSavingScore] = useState<string | null>(null);
const [formData, setFormData] = useState<EventFormData>({ const [formData, setFormData] = useState<EventFormData>({
date: "", date: "",
name: "", name: "",
@@ -199,6 +235,75 @@ export default function EventManagement() {
}); });
}; };
const handleViewRegistrations = async (event: Event) => {
setViewingRegistrations(event);
setLoadingRegistrations(true);
try {
const response = await fetch(
`/api/admin/events/${event.id}/registrations`
);
if (response.ok) {
const data = await response.json();
setRegistrations(data);
// Initialiser les scores d'édition avec les scores actuels
const scoresMap: Record<string, number> = {};
data.forEach((reg: EventRegistration) => {
scoresMap[reg.user.id] = reg.user.score;
});
setEditingScores(scoresMap);
} else {
alert("Erreur lors de la récupération des inscrits");
}
} catch (error) {
console.error("Error fetching registrations:", error);
alert("Erreur lors de la récupération des inscrits");
} finally {
setLoadingRegistrations(false);
}
};
const handleCloseRegistrations = () => {
setViewingRegistrations(null);
setRegistrations([]);
setEditingScores({});
};
const handleScoreChange = (userId: string, newScore: number) => {
setEditingScores({
...editingScores,
[userId]: newScore,
});
};
const handleSaveScore = async (userId: string) => {
const newScore = editingScores[userId];
if (newScore === undefined) return;
setSavingScore(userId);
startTransition(async () => {
try {
const result = await updateUser(userId, { score: newScore });
if (result.success) {
// Mettre à jour le score dans la liste locale
setRegistrations((prev) =>
prev.map((reg) =>
reg.user.id === userId
? { ...reg, user: { ...reg.user, score: newScore } }
: reg
)
);
} else {
alert(result.error || "Erreur lors de la mise à jour du score");
}
} catch (error) {
console.error("Error updating score:", error);
alert("Erreur lors de la mise à jour du score");
} finally {
setSavingScore(null);
}
});
};
if (loading) { if (loading) {
return <div className="text-center text-gray-400 py-8">Chargement...</div>; return <div className="text-center text-gray-400 py-8">Chargement...</div>;
} }
@@ -221,11 +326,20 @@ export default function EventManagement() {
)} )}
</div> </div>
{/* Modal de création/édition */}
{(isCreating || editingEvent) && ( {(isCreating || editingEvent) && (
<Card variant="default" className="p-3 sm:p-4 mb-4"> <Modal
<h4 className="text-pixel-gold font-bold mb-4 text-base sm:text-lg break-words"> isOpen={isCreating || !!editingEvent}
onClose={handleCancel}
size="lg"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
{isCreating ? "Créer un événement" : "Modifier l'événement"} {isCreating ? "Créer un événement" : "Modifier l'événement"}
</h4> </h4>
<CloseButton onClick={handleCancel} size="lg" />
</div>
<div className="space-y-4"> <div className="space-y-4">
<Input <Input
type="date" type="date"
@@ -330,7 +444,8 @@ export default function EventManagement() {
</Button> </Button>
</div> </div>
</div> </div>
</Card> </div>
</Modal>
)} )}
{events.length === 0 ? ( {events.length === 0 ? (
@@ -392,7 +507,15 @@ export default function EventManagement() {
</div> </div>
</div> </div>
{!isCreating && !editingEvent && ( {!isCreating && !editingEvent && (
<div className="flex gap-2 sm:ml-4 flex-shrink-0"> <div className="flex gap-2 sm:ml-4 flex-shrink-0 flex-wrap">
<Button
onClick={() => handleViewRegistrations(event)}
variant="primary"
size="sm"
className="whitespace-nowrap"
>
Inscrits ({event.registrationsCount || 0})
</Button>
<Button <Button
onClick={() => handleEdit(event)} onClick={() => handleEdit(event)}
variant="primary" variant="primary"
@@ -417,6 +540,116 @@ export default function EventManagement() {
})} })}
</div> </div>
)} )}
{/* Modal des inscrits */}
{viewingRegistrations && (
<Modal
isOpen={!!viewingRegistrations}
onClose={handleCloseRegistrations}
size="lg"
>
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Inscrits à &quot;{viewingRegistrations.name}&quot;
</h4>
<CloseButton onClick={handleCloseRegistrations} size="lg" />
</div>
{loadingRegistrations ? (
<div className="text-center text-gray-400 py-8">
Chargement...
</div>
) : registrations.length === 0 ? (
<div className="text-center text-gray-400 py-8">
Aucun inscrit pour cet événement
</div>
) : (
<div className="space-y-3 max-h-[60vh] overflow-y-auto">
{registrations.map((registration) => {
const user = registration.user;
const currentScore = editingScores[user.id] ?? user.score;
const isSaving = savingScore === user.id;
return (
<Card
key={registration.id}
variant="default"
className="p-3 sm:p-4"
>
<div className="flex flex-col sm:flex-row sm:items-center gap-3">
<div className="flex items-center gap-3 flex-1 min-w-0">
<Avatar
src={user.avatar}
username={user.username}
size="md"
borderClassName="border-2 border-pixel-gold/50"
/>
<div className="flex-1 min-w-0">
<h5 className="text-pixel-gold font-bold text-sm sm:text-base break-words">
{user.username}
</h5>
<p className="text-gray-400 text-xs sm:text-sm">
Niveau {user.level} HP: {user.hp}/{user.maxHp}
XP: {user.xp}/{user.maxXp}
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-3 flex-shrink-0">
<div className="flex items-center gap-2">
<label className="text-xs sm:text-sm text-gray-300 whitespace-nowrap">
Score:
</label>
<input
type="number"
value={currentScore}
onChange={(e) =>
handleScoreChange(
user.id,
parseInt(e.target.value) || 0
)
}
disabled={isSaving}
className="w-24 px-2 sm:px-3 py-1 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm text-center disabled:opacity-50"
/>
</div>
<div className="flex gap-1 sm:gap-2">
<button
onClick={() =>
handleScoreChange(user.id, currentScore - 100)
}
disabled={isSaving}
className="px-2 sm:px-3 py-1 border border-red-500/50 bg-red-900/20 text-red-400 text-[10px] sm:text-xs rounded hover:bg-red-900/30 transition flex-shrink-0 disabled:opacity-50"
>
-100
</button>
<button
onClick={() =>
handleScoreChange(user.id, currentScore + 100)
}
disabled={isSaving}
className="px-2 sm:px-3 py-1 border border-green-500/50 bg-green-900/20 text-green-400 text-[10px] sm:text-xs rounded hover:bg-green-900/30 transition flex-shrink-0 disabled:opacity-50"
>
+100
</button>
<button
onClick={() => handleSaveScore(user.id)}
disabled={isSaving || currentScore === user.score}
className="px-2 sm:px-3 py-1 border border-pixel-gold/50 bg-pixel-gold/20 text-pixel-gold text-[10px] sm:text-xs rounded hover:bg-pixel-gold/30 transition flex-shrink-0 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSaving ? "..." : "Sauver"}
</button>
</div>
</div>
</div>
</Card>
);
})}
</div>
)}
</div>
</Modal>
)}
</div> </div>
); );
} }

View File

@@ -0,0 +1,166 @@
"use client";
import { useState, useEffect } from "react";
import { updateSitePreferences } from "@/actions/admin/preferences";
import { Button, Card, Input } from "@/components/ui";
interface SitePreferences {
id: string;
eventRegistrationPoints: number;
}
interface EventPointsPreferencesProps {
initialPreferences: SitePreferences;
}
export default function EventPointsPreferences({
initialPreferences,
}: EventPointsPreferencesProps) {
const [preferences, setPreferences] = useState<SitePreferences | null>(
initialPreferences
);
const [isEditing, setIsEditing] = useState(false);
const [formData, setFormData] = useState({
eventRegistrationPoints: initialPreferences.eventRegistrationPoints.toString(),
});
const [isSaving, setIsSaving] = useState(false);
// Synchroniser les préférences quand initialPreferences change
useEffect(() => {
setPreferences(initialPreferences);
setFormData({
eventRegistrationPoints: initialPreferences.eventRegistrationPoints.toString(),
});
}, [initialPreferences]);
const handleEdit = () => {
setIsEditing(true);
};
const handleSave = async () => {
const points = parseInt(formData.eventRegistrationPoints, 10);
if (isNaN(points) || points < 0) {
alert("Le nombre de points doit être un nombre positif");
return;
}
setIsSaving(true);
try {
const result = await updateSitePreferences({
eventRegistrationPoints: points,
});
if (result.success && result.data) {
setPreferences(result.data);
setFormData({
eventRegistrationPoints: result.data.eventRegistrationPoints.toString(),
});
setIsEditing(false);
} else {
console.error("Error updating preferences:", result.error);
alert(result.error || "Erreur lors de la mise à jour");
}
} catch (error) {
console.error("Error updating preferences:", error);
alert("Erreur lors de la mise à jour");
} finally {
setIsSaving(false);
}
};
const handleCancel = () => {
setIsEditing(false);
if (preferences) {
setFormData({
eventRegistrationPoints: preferences.eventRegistrationPoints.toString(),
});
}
};
return (
<Card variant="default" className="p-3 sm:p-4">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-3 mb-4">
<div className="min-w-0 flex-1">
<h3 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Points d&apos;inscription aux événements
</h3>
<p className="text-gray-400 text-xs sm:text-sm">
Nombre de points attribués lorsqu&apos;un utilisateur s&apos;inscrit à un événement
</p>
</div>
{!isEditing && (
<Button
onClick={handleEdit}
variant="primary"
size="sm"
className="whitespace-nowrap flex-shrink-0"
>
Modifier
</Button>
)}
</div>
{isEditing ? (
<div className="space-y-4">
<div>
<label
htmlFor="eventRegistrationPoints"
className="block text-sm font-medium text-pixel-gold mb-2"
>
Points d&apos;inscription
</label>
<Input
id="eventRegistrationPoints"
type="number"
min="0"
value={formData.eventRegistrationPoints}
onChange={(e) =>
setFormData({
...formData,
eventRegistrationPoints: e.target.value,
})
}
placeholder="100"
className="w-full"
/>
<p className="text-xs text-gray-400 mt-1">
Les utilisateurs gagneront ce nombre de points lors de leur inscription à un événement
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2 pt-4">
<Button
onClick={handleSave}
variant="success"
size="md"
disabled={isSaving}
>
{isSaving ? "Enregistrement..." : "Enregistrer"}
</Button>
<Button
onClick={handleCancel}
variant="secondary"
size="md"
disabled={isSaving}
>
Annuler
</Button>
</div>
</div>
) : (
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
<span className="text-pixel-gold font-bold text-sm sm:text-base min-w-0 sm:min-w-[200px] flex-shrink-0">
Points actuels:
</span>
<div className="flex items-center gap-2">
<span className="text-lg sm:text-xl font-bold text-white">
{preferences?.eventRegistrationPoints ?? 100}
</span>
<span className="text-xs sm:text-sm text-gray-400">points</span>
</div>
</div>
)}
</Card>
);
}

View File

@@ -1,11 +1,18 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import {
addFeedbackBonusPoints,
markFeedbackAsRead,
} from "@/actions/admin/feedback";
import { Button } from "@/components/ui";
import Avatar from "@/components/ui/Avatar";
interface Feedback { interface Feedback {
id: string; id: string;
rating: number; rating: number;
comment: string | null; comment: string | null;
isRead: boolean;
createdAt: string; createdAt: string;
event: { event: {
id: string; id: string;
@@ -17,6 +24,8 @@ interface Feedback {
id: string; id: string;
username: string; username: string;
email: string; email: string;
avatar: string | null;
score: number;
}; };
} }
@@ -35,6 +44,10 @@ export default function FeedbackManagement() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [selectedEvent, setSelectedEvent] = useState<string | null>(null); const [selectedEvent, setSelectedEvent] = useState<string | null>(null);
const [addingPoints, setAddingPoints] = useState<Record<string, boolean>>(
{}
);
const [markingRead, setMarkingRead] = useState<Record<string, boolean>>({});
useEffect(() => { useEffect(() => {
fetchFeedbacks(); fetchFeedbacks();
@@ -92,9 +105,59 @@ export default function FeedbackManagement() {
); );
}; };
const filteredFeedbacks = selectedEvent const handleAddPoints = async (userId: string, points: number) => {
const key = `${userId}-${points}`;
setAddingPoints((prev) => ({ ...prev, [key]: true }));
setError("");
try {
const result = await addFeedbackBonusPoints(userId, points);
if (result.success) {
// Rafraîchir les données pour voir les nouveaux scores
await fetchFeedbacks();
// Rafraîchir le score dans le header si l'utilisateur est connecté
window.dispatchEvent(new Event("refreshUserScore"));
} else {
setError(result.error || "Erreur lors de l'ajout des points");
}
} catch {
setError("Erreur lors de l'ajout des points");
} finally {
setAddingPoints((prev) => ({ ...prev, [key]: false }));
}
};
const handleMarkAsRead = async (feedbackId: string, isRead: boolean) => {
setMarkingRead((prev) => ({ ...prev, [feedbackId]: true }));
setError("");
try {
const result = await markFeedbackAsRead(feedbackId, isRead);
if (result.success) {
// Rafraîchir les données pour voir le nouveau statut
await fetchFeedbacks();
} else {
setError(result.error || "Erreur lors de la mise à jour");
}
} catch {
setError("Erreur lors de la mise à jour");
} finally {
setMarkingRead((prev) => ({ ...prev, [feedbackId]: false }));
}
};
const filteredFeedbacks = (selectedEvent
? feedbacks.filter((f) => f.event.id === selectedEvent) ? feedbacks.filter((f) => f.event.id === selectedEvent)
: feedbacks; : feedbacks
).sort((a, b) => {
// Trier : non lus en premier, puis par date décroissante
if (a.isRead !== b.isRead) {
return a.isRead ? 1 : -1;
}
return (
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
});
if (loading) { if (loading) {
return ( return (
@@ -184,21 +247,46 @@ export default function FeedbackManagement() {
{filteredFeedbacks.map((feedback) => ( {filteredFeedbacks.map((feedback) => (
<div <div
key={feedback.id} key={feedback.id}
className="bg-black/40 border border-pixel-gold/20 rounded p-3 sm:p-4" className={`bg-black/40 border rounded p-3 sm:p-4 ${
feedback.isRead
? "border-pixel-gold/20 opacity-75"
: "border-pixel-gold/50 bg-pixel-gold/5"
}`}
> >
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-3"> <div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 mb-2"> {/* En-tête utilisateur avec avatar */}
<div className="flex items-center gap-2 sm:gap-3 mb-3">
<Avatar
src={feedback.user.avatar}
username={feedback.user.username}
size="md"
borderClassName="border-pixel-gold/30"
/>
<div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 mb-1">
<h4 className="text-white font-semibold text-sm sm:text-base break-words"> <h4 className="text-white font-semibold text-sm sm:text-base break-words">
{feedback.user.username} {feedback.user.username}
</h4> </h4>
<span className="text-pixel-gold font-bold text-xs sm:text-sm">
{feedback.user.score.toLocaleString("fr-FR")} pts
</span>
</div>
<span className="text-gray-500 text-[10px] sm:text-xs break-all"> <span className="text-gray-500 text-[10px] sm:text-xs break-all">
{feedback.user.email} {feedback.user.email}
</span> </span>
</div> </div>
<div className="text-pixel-gold text-xs sm:text-sm font-semibold mb-2 break-words"> </div>
<div className="flex items-center gap-2 mb-2">
<div className="text-pixel-gold text-xs sm:text-sm font-semibold break-words">
{feedback.event.name} {feedback.event.name}
</div> </div>
{!feedback.isRead && (
<span className="bg-pixel-gold/20 text-pixel-gold text-[10px] px-1.5 py-0.5 rounded uppercase font-semibold">
Non lu
</span>
)}
</div>
<div className="text-gray-500 text-[10px] sm:text-xs mb-2"> <div className="text-gray-500 text-[10px] sm:text-xs mb-2">
{new Date(feedback.createdAt).toLocaleDateString( {new Date(feedback.createdAt).toLocaleDateString(
"fr-FR", "fr-FR",
@@ -212,8 +300,23 @@ export default function FeedbackManagement() {
)} )}
</div> </div>
</div> </div>
<div className="flex-shrink-0"> <div className="flex flex-col items-end gap-2 flex-shrink-0">
{renderStars(feedback.rating)} {renderStars(feedback.rating)}
<Button
variant={feedback.isRead ? "secondary" : "success"}
size="sm"
onClick={() =>
handleMarkAsRead(feedback.id, !feedback.isRead)
}
disabled={markingRead[feedback.id]}
className="text-xs whitespace-nowrap"
>
{markingRead[feedback.id]
? "..."
: feedback.isRead
? "Marquer non lu"
: "Marquer lu"}
</Button>
</div> </div>
</div> </div>
{feedback.comment && ( {feedback.comment && (
@@ -223,6 +326,39 @@ export default function FeedbackManagement() {
</p> </p>
</div> </div>
)} )}
{/* Boutons pour ajouter des points bonus */}
<div className="mt-3 pt-3 border-t border-pixel-gold/20 flex flex-wrap gap-2">
<span className="text-gray-400 text-xs sm:text-sm mr-2">
Points bonus:
</span>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 10)}
disabled={addingPoints[`${feedback.user.id}-10`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-10`] ? "..." : "+10"}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 100)}
disabled={addingPoints[`${feedback.user.id}-100`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-100`] ? "..." : "+100"}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 1000)}
disabled={addingPoints[`${feedback.user.id}-1000`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-1000`] ? "..." : "+1000"}
</Button>
</div>
</div> </div>
))} ))}
</div> </div>

View File

@@ -1,7 +1,14 @@
"use client"; "use client";
import { useState, useEffect, useTransition } from "react"; import { useState, useEffect, useTransition } from "react";
import { Avatar, Input, Button, Card } from "@/components/ui"; import {
Avatar,
Input,
Button,
Card,
Modal,
CloseButton,
} from "@/components/ui";
import { updateUser, deleteUser } from "@/actions/admin/users"; import { updateUser, deleteUser } from "@/actions/admin/users";
interface User { interface User {
@@ -159,6 +166,25 @@ export default function UserManagement() {
return num.toLocaleString("en-US"); return num.toLocaleString("en-US");
}; };
// Trouver l'utilisateur en cours d'édition pour les previews
const currentEditingUserData = editingUser
? users.find((u) => u.id === editingUser.userId)
: null;
const previewHp =
currentEditingUserData && editingUser
? Math.max(
0,
Math.min(
currentEditingUserData.maxHp,
currentEditingUserData.hp + editingUser.hpDelta
)
)
: 0;
const previewXp =
currentEditingUserData && editingUser
? Math.max(0, currentEditingUserData.xp + editingUser.xpDelta)
: 0;
if (loading) { if (loading) {
return <div className="text-center text-gray-400 py-8">Chargement...</div>; return <div className="text-center text-gray-400 py-8">Chargement...</div>;
} }
@@ -170,79 +196,129 @@ export default function UserManagement() {
Aucun utilisateur trouvé Aucun utilisateur trouvé
</div> </div>
) : ( ) : (
users.map((user) => { <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4">
const isEditing = editingUser?.userId === user.id; {users.map((user) => {
const previewHp = isEditing
? Math.max(0, Math.min(user.maxHp, user.hp + editingUser.hpDelta))
: user.hp;
const previewXp = isEditing
? Math.max(0, user.xp + editingUser.xpDelta)
: user.xp;
const displayAvatar = isEditing ? editingUser.avatar : user.avatar;
const displayUsername = isEditing
? editingUser.username || user.username
: user.username;
return ( return (
<Card key={user.id} variant="default" className="p-3 sm:p-4"> <Card key={user.id} variant="default" className="p-3">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 mb-2"> <div className="flex flex-col gap-2">
<div className="flex gap-2 sm:gap-3 items-center flex-1 min-w-0"> {/* Header avec avatar et nom */}
{/* Avatar */} <div className="flex items-center gap-2">
<Avatar <Avatar
src={displayAvatar} src={user.avatar}
username={displayUsername} username={user.username}
size="sm" size="sm"
className="flex-shrink-0" className="flex-shrink-0"
borderClassName="border-2 border-pixel-gold/50" borderClassName="border-2 border-pixel-gold/50"
/> />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 sm:gap-2 flex-wrap"> <h3 className="text-pixel-gold font-bold text-sm truncate">
<h3 className="text-pixel-gold font-bold text-sm sm:text-base break-words"> {user.username}
{displayUsername}
</h3> </h3>
<span className="text-[10px] sm:text-xs text-gray-500 whitespace-nowrap"> <div className="flex items-center gap-1.5 mt-0.5">
Niveau {user.level} <span className="text-[10px] text-gray-500">
</span> Niv. {user.level}
<span className="text-[10px] sm:text-xs text-gray-500 whitespace-nowrap">
Score: {formatNumber(user.score)}
</span> </span>
<span <span
className={`text-[10px] sm:text-xs whitespace-nowrap ${ className={`text-[10px] font-bold px-1.5 py-0.5 rounded border ${
user.role === "ADMIN" user.role === "ADMIN"
? "text-pixel-gold" ? "text-pixel-gold border-pixel-gold/50 bg-pixel-gold/10"
: "text-gray-500" : "text-gray-500 border-gray-500/30 bg-gray-500/10"
}`} }`}
> >
{user.role} {user.role}
</span> </span>
</div> </div>
<p className="text-gray-400 text-[10px] sm:text-xs truncate"> </div>
</div>
{/* Score en évidence */}
<div className="flex items-baseline gap-1.5 px-1">
<span className="text-[10px] text-gray-400">Score:</span>
<span className="text-lg font-bold text-pixel-gold">
{formatNumber(user.score)}
</span>
</div>
{/* Stats HP et XP */}
<div className="space-y-1.5 text-[10px]">
<div>
<div className="flex justify-between items-center mb-0.5">
<span className="text-gray-400">HP</span>
<span className="text-gray-400">
{user.hp}/{user.maxHp}
</span>
</div>
<div className="h-1 bg-black/60 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-red-600 to-green-500"
style={{
width: `${Math.min(
100,
(user.hp / user.maxHp) * 100
)}%`,
}}
/>
</div>
</div>
<div>
<div className="flex justify-between items-center mb-0.5">
<span className="text-gray-400">XP</span>
<span className="text-gray-400">
{formatNumber(user.xp)}/{formatNumber(user.maxXp)}
</span>
</div>
<div className="h-1 bg-black/60 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-blue-600 to-purple-500"
style={{
width: `${Math.min(
100,
(user.xp / user.maxXp) * 100
)}%`,
}}
/>
</div>
</div>
</div>
{/* Email */}
<p className="text-gray-400 text-[10px] truncate px-1">
{user.email} {user.email}
</p> </p>
</div>
</div> {/* Boutons d'action */}
{!isEditing && ( <div className="flex gap-2 pt-1">
<div className="flex gap-2 flex-shrink-0 sm:ml-2">
<button <button
onClick={() => handleEdit(user)} onClick={() => handleEdit(user)}
className="px-2 sm:px-3 py-1.5 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" className="flex-1 px-2 py-1 border border-pixel-gold/50 bg-black/60 text-white uppercase text-[10px] tracking-widest rounded hover:bg-pixel-gold/10 transition"
> >
Modifier Modifier
</button> </button>
<button <button
onClick={() => handleDelete(user.id)} onClick={() => handleDelete(user.id)}
disabled={deletingUserId === user.id} disabled={deletingUserId === user.id}
className="px-2 sm:px-3 py-1.5 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 disabled:opacity-50 whitespace-nowrap" className="flex-1 px-2 py-1 border border-red-500/50 bg-red-900/20 text-red-400 uppercase text-[10px] tracking-widest rounded hover:bg-red-900/30 transition disabled:opacity-50"
> >
{deletingUserId === user.id {deletingUserId === user.id ? "..." : "Suppr."}
? "Suppression..."
: "Supprimer"}
</button> </button>
</div> </div>
)}
</div> </div>
</Card>
);
})}
</div>
)}
{isEditing ? ( {/* Modal d'édition */}
{editingUser && currentEditingUserData && (
<Modal isOpen={!!editingUser} onClose={handleCancel} size="lg">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h4 className="text-pixel-gold font-bold text-base sm:text-lg break-words">
Modifier l&apos;utilisateur
</h4>
<CloseButton onClick={handleCancel} size="lg" />
</div>
<div className="space-y-4"> <div className="space-y-4">
{/* Username Section */} {/* Username Section */}
<Input <Input
@@ -269,15 +345,15 @@ export default function UserManagement() {
<div className="relative"> <div className="relative">
<Avatar <Avatar
src={editingUser.avatar} src={editingUser.avatar}
username={editingUser.username || user.username} username={
editingUser.username || currentEditingUserData.username
}
size="lg" size="lg"
borderClassName="border-2 border-pixel-gold/50" borderClassName="border-2 border-pixel-gold/50"
/> />
{uploadingAvatar === user.id && ( {uploadingAvatar === editingUser.userId && (
<div className="absolute inset-0 bg-black/60 flex items-center justify-center rounded-full"> <div className="absolute inset-0 bg-black/60 flex items-center justify-center rounded-full">
<div className="text-pixel-gold text-xs"> <div className="text-pixel-gold text-xs">Upload...</div>
Upload...
</div>
</div> </div>
)} )}
</div> </div>
@@ -330,7 +406,7 @@ export default function UserManagement() {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
setUploadingAvatar(user.id); setUploadingAvatar(editingUser.userId);
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
@@ -363,16 +439,16 @@ export default function UserManagement() {
} }
}} }}
className="hidden" className="hidden"
id={`avatar-upload-${user.id}`} id={`avatar-upload-${editingUser.userId}`}
/> />
<label htmlFor={`avatar-upload-${user.id}`}> <label htmlFor={`avatar-upload-${editingUser.userId}`}>
<Button <Button
variant="primary" variant="primary"
size="sm" size="sm"
as="span" as="span"
className="cursor-pointer" className="cursor-pointer"
> >
{uploadingAvatar === user.id {uploadingAvatar === editingUser.userId
? "Upload en cours..." ? "Upload en cours..."
: "Upload un avatar custom"} : "Upload un avatar custom"}
</Button> </Button>
@@ -387,7 +463,7 @@ export default function UserManagement() {
Points de Vie (HP) Points de Vie (HP)
</label> </label>
<span className="text-[10px] sm:text-xs text-gray-400"> <span className="text-[10px] sm:text-xs text-gray-400">
{previewHp} / {user.maxHp} {previewHp} / {currentEditingUserData.maxHp}
</span> </span>
</div> </div>
<div className="flex gap-1 sm:gap-2 flex-wrap"> <div className="flex gap-1 sm:gap-2 flex-wrap">
@@ -453,7 +529,7 @@ export default function UserManagement() {
style={{ style={{
width: `${Math.min( width: `${Math.min(
100, 100,
(previewHp / user.maxHp) * 100 (previewHp / currentEditingUserData.maxHp) * 100
)}%`, )}%`,
}} }}
/> />
@@ -467,7 +543,8 @@ export default function UserManagement() {
Expérience (XP) Expérience (XP)
</label> </label>
<span className="text-[10px] sm:text-xs text-gray-400"> <span className="text-[10px] sm:text-xs text-gray-400">
{formatNumber(previewXp)} / {formatNumber(user.maxXp)} {formatNumber(previewXp)} /{" "}
{formatNumber(currentEditingUserData.maxXp)}
</span> </span>
</div> </div>
<div className="flex gap-1 sm:gap-2 flex-wrap"> <div className="flex gap-1 sm:gap-2 flex-wrap">
@@ -533,7 +610,7 @@ export default function UserManagement() {
style={{ style={{
width: `${Math.min( width: `${Math.min(
100, 100,
(previewXp / user.maxXp) * 100 (previewXp / currentEditingUserData.maxXp) * 100
)}%`, )}%`,
}} }}
/> />
@@ -695,60 +772,13 @@ export default function UserManagement() {
> >
{saving ? "Enregistrement..." : "Enregistrer"} {saving ? "Enregistrement..." : "Enregistrer"}
</Button> </Button>
<Button <Button onClick={handleCancel} variant="secondary" size="md">
onClick={handleCancel}
variant="secondary"
size="md"
>
Annuler Annuler
</Button> </Button>
</div> </div>
</div> </div>
) : (
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 text-[10px] sm:text-xs">
<div className="flex-1">
<div className="flex justify-between items-center mb-0.5">
<span className="text-gray-400">HP</span>
<span className="text-gray-400">
{user.hp}/{user.maxHp}
</span>
</div> </div>
<div className="h-1.5 bg-black/60 rounded-full overflow-hidden"> </Modal>
<div
className="h-full bg-gradient-to-r from-red-600 to-green-500"
style={{
width: `${Math.min(
100,
(user.hp / user.maxHp) * 100
)}%`,
}}
/>
</div>
</div>
<div className="flex-1">
<div className="flex justify-between items-center mb-0.5">
<span className="text-gray-400">XP</span>
<span className="text-gray-400">
{formatNumber(user.xp)}/{formatNumber(user.maxXp)}
</span>
</div>
<div className="h-1.5 bg-black/60 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-blue-600 to-purple-500"
style={{
width: `${Math.min(
100,
(user.xp / user.maxXp) * 100
)}%`,
}}
/>
</div>
</div>
</div>
)}
</Card>
);
})
)} )}
</div> </div>
); );

View File

@@ -564,6 +564,8 @@ export default function EventsPageSection({
...prev, ...prev,
[eventId]: true, [eventId]: true,
})); }));
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
} else { } else {
setError(result.error || "Une erreur est survenue"); setError(result.error || "Une erreur est survenue");
} }
@@ -583,6 +585,8 @@ export default function EventsPageSection({
...prev, ...prev,
[eventId]: false, [eventId]: false,
})); }));
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
} else { } else {
setError(result.error || "Une erreur est survenue"); setError(result.error || "Une erreur est survenue");
} }

View File

@@ -155,6 +155,9 @@ export default function FeedbackModal({
}); });
} }
// Rafraîchir le score dans le header
window.dispatchEvent(new Event("refreshUserScore"));
// Fermer la modale après 1.5 secondes // Fermer la modale après 1.5 secondes
setTimeout(() => { setTimeout(() => {
onClose(); onClose();

View File

@@ -16,6 +16,7 @@ interface UserData {
xp: number; xp: number;
maxXp: number; maxXp: number;
level: number; level: number;
score: number;
} }
interface NavigationProps { interface NavigationProps {

View File

@@ -11,6 +11,7 @@ interface UserData {
xp: number; xp: number;
maxXp: number; maxXp: number;
level: number; level: number;
score: number;
} }
export default async function NavigationWrapper() { export default async function NavigationWrapper() {
@@ -31,6 +32,7 @@ export default async function NavigationWrapper() {
xp: true, xp: true,
maxXp: true, maxXp: true,
level: true, level: true,
score: true,
}), }),
challengeService.getActiveChallengesCount(session.user.id), challengeService.getActiveChallengesCount(session.user.id),
]); ]);

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState, useCallback } from "react";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { Avatar } from "@/components/ui"; import { Avatar } from "@/components/ui";
@@ -13,6 +13,7 @@ interface UserData {
xp: number; xp: number;
maxXp: number; maxXp: number;
level: number; level: number;
score: number;
} }
interface PlayerStatsProps { interface PlayerStatsProps {
@@ -32,6 +33,7 @@ const defaultUserData: UserData = {
xp: 0, xp: 0,
maxXp: 5000, maxXp: 5000,
level: 1, level: 1,
score: 0,
}; };
export default function PlayerStats({ initialUserData }: PlayerStatsProps) { export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
@@ -40,6 +42,31 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
initialUserData || defaultUserData initialUserData || defaultUserData
); );
const refreshUserData = useCallback(async () => {
if (!session?.user?.id) return;
try {
const res = await fetch(`/api/users/${session.user.id}`);
const data = await res.json();
if (data) {
requestAnimationFrame(() => {
setUserData({
username: data.username || "Guest",
avatar: data.avatar,
hp: data.hp || 1000,
maxHp: data.maxHp || 1000,
xp: data.xp || 0,
maxXp: data.maxXp || 5000,
level: data.level || 1,
score: data.score || 0,
});
});
}
} catch (error) {
console.error("Error refreshing user data:", error);
}
}, [session]);
useEffect(() => { useEffect(() => {
// Si on a déjà des données initiales, ne rien faire (déjà initialisé dans useState) // Si on a déjà des données initiales, ne rien faire (déjà initialisé dans useState)
if (initialUserData) { if (initialUserData) {
@@ -62,6 +89,7 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
xp: data.xp || 0, xp: data.xp || 0,
maxXp: data.maxXp || 5000, maxXp: data.maxXp || 5000,
level: data.level || 1, level: data.level || 1,
score: data.score || 0,
}); });
}); });
} }
@@ -77,6 +105,7 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
xp: 0, xp: 0,
maxXp: 5000, maxXp: 5000,
level: 1, level: 1,
score: 0,
}); });
}); });
}); });
@@ -88,51 +117,19 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
} }
}, [session, initialUserData]); }, [session, initialUserData]);
const { username, avatar, hp, maxHp, xp, maxXp, level } = userData; // Écouter les événements de refresh du score
// Calculer les pourcentages cibles
const targetHpPercentage = (hp / maxHp) * 100;
const targetXpPercentage = (xp / maxXp) * 100;
// Initialiser les pourcentages à 0 si on a des données initiales (pour l'animation)
// Sinon utiliser directement les valeurs calculées
const [hpPercentage, setHpPercentage] = useState(
initialUserData ? 0 : targetHpPercentage
);
const [xpPercentage, setXpPercentage] = useState(
initialUserData ? 0 : targetXpPercentage
);
useEffect(() => { useEffect(() => {
// Si on a des données initiales, animer depuis 0 vers la valeur cible const handleRefreshScore = () => {
if (initialUserData) { refreshUserData();
const hpTimer = setTimeout(() => {
setHpPercentage(targetHpPercentage);
}, 100);
const xpTimer = setTimeout(() => {
setXpPercentage(targetXpPercentage);
}, 200);
return () => {
clearTimeout(hpTimer);
clearTimeout(xpTimer);
}; };
}
// Sinon, mettre à jour directement (pour les pages Client Components)
// Utiliser requestAnimationFrame pour éviter les cascades de rendu
requestAnimationFrame(() => {
setHpPercentage(targetHpPercentage);
setXpPercentage(targetXpPercentage);
});
}, [targetHpPercentage, targetXpPercentage, initialUserData]);
const hpColor = window.addEventListener("refreshUserScore", handleRefreshScore);
hpPercentage > 60 return () => {
? "from-green-600 to-green-700" window.removeEventListener("refreshUserScore", handleRefreshScore);
: hpPercentage > 30 };
? "from-yellow-600 to-orange-700" }, [refreshUserData]);
: "from-red-700 to-red-900";
const { username, avatar, level, score } = userData;
return ( return (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -150,7 +147,7 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
</Link> </Link>
{/* Stats */} {/* Stats */}
<div className="flex flex-col gap-1.5 min-w-[180px] sm:min-w-[200px]"> <div className="flex flex-col gap-1.5 min-w-[140px] sm:min-w-[160px]">
{/* Username & Level */} {/* Username & Level */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link <Link
@@ -166,57 +163,16 @@ export default function PlayerStats({ initialUserData }: PlayerStatsProps) {
</div> </div>
</div> </div>
{/* Bars side by side */} {/* Score Display */}
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* HP Bar */} <div className="text-gray-400 font-pixel text-xs uppercase">
<div className="relative h-2 flex-1 bg-gray-900 border border-gray-700 rounded overflow-hidden"> Score
<div
className={`absolute inset-0 bg-gradient-to-r ${hpColor} transition-all duration-1000 ease-out`}
style={{ width: `${hpPercentage}%` }}
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
</div> </div>
{hpPercentage < 30 && ( <div className="text-pixel-gold font-gaming font-bold text-sm">
<div className="absolute inset-0 border border-red-500 rounded animate-pulse"></div> {formatNumber(score)}
)}
</div>
{/* XP Bar */}
<div className="relative h-2 flex-1 bg-gray-900 border border-pixel-gold/30 rounded overflow-hidden">
<div
className="absolute inset-0 bg-gradient-to-r from-pixel-gold/80 via-pixel-gold/70 to-pixel-gold/80 transition-all duration-1000 ease-out"
style={{ width: `${xpPercentage}%` }}
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
</div> </div>
</div> </div>
</div> </div>
{/* Labels */}
<div className="flex items-center gap-2 text-[8px] font-pixel text-gray-400">
<div className="flex-1 text-left">
HP {hp} / {maxHp}
</div>
<div className="flex-1 text-right">
XP {formatNumber(xp)} / {formatNumber(maxXp)}
</div>
</div>
</div>
</div>
<style jsx>{`
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.animate-shimmer {
animation: shimmer 2s infinite;
}
`}</style>
</div> </div>
); );
} }

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { ReactNode, useEffect } from "react"; import { ReactNode, useEffect } from "react";
import { createPortal } from "react-dom";
interface ModalProps { interface ModalProps {
isOpen: boolean; isOpen: boolean;
@@ -37,7 +38,7 @@ export default function Modal({
if (!isOpen) return null; if (!isOpen) return null;
return ( const modalContent = (
<div <div
className="fixed inset-0 z-[200] flex items-center justify-center p-4 backdrop-blur-sm" className="fixed inset-0 z-[200] flex items-center justify-center p-4 backdrop-blur-sm"
style={{ style={{
@@ -59,4 +60,11 @@ export default function Modal({
</div> </div>
</div> </div>
); );
// Utiliser un portal pour rendre le modal directement dans le body
if (typeof window !== "undefined") {
return createPortal(modalContent, document.body);
}
return null;
} }

View File

@@ -211,6 +211,19 @@ export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedIntNullableFilter<$PrismaModel> _max?: Prisma.NestedIntNullableFilter<$PrismaModel>
} }
export type BoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type EnumChallengeStatusFilter<$PrismaModel = never> = { export type EnumChallengeStatusFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[]
@@ -467,6 +480,19 @@ export type NestedFloatNullableFilter<$PrismaModel = never> = {
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
} }
export type NestedBoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type NestedEnumChallengeStatusFilter<$PrismaModel = never> = { export type NestedEnumChallengeStatusFilter<$PrismaModel = never> = {
equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel> equals?: $Enums.ChallengeStatus | Prisma.EnumChallengeStatusFieldRefInput<$PrismaModel>
in?: $Enums.ChallengeStatus[] in?: $Enums.ChallengeStatus[]

File diff suppressed because one or more lines are too long

View File

@@ -1032,6 +1032,7 @@ export const EventFeedbackScalarFieldEnum = {
eventId: 'eventId', eventId: 'eventId',
rating: 'rating', rating: 'rating',
comment: 'comment', comment: 'comment',
isRead: 'isRead',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -1045,6 +1046,8 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground', eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground', leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground', challengesBackground: 'challengesBackground',
eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -1136,6 +1139,13 @@ export type EnumEventTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$Prisma
/**
* Reference to a field of type 'Boolean'
*/
export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'>
/** /**
* Reference to a field of type 'ChallengeStatus' * Reference to a field of type 'ChallengeStatus'
*/ */

View File

@@ -141,6 +141,7 @@ export const EventFeedbackScalarFieldEnum = {
eventId: 'eventId', eventId: 'eventId',
rating: 'rating', rating: 'rating',
comment: 'comment', comment: 'comment',
isRead: 'isRead',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const
@@ -154,6 +155,8 @@ export const SitePreferencesScalarFieldEnum = {
eventsBackground: 'eventsBackground', eventsBackground: 'eventsBackground',
leaderboardBackground: 'leaderboardBackground', leaderboardBackground: 'leaderboardBackground',
challengesBackground: 'challengesBackground', challengesBackground: 'challengesBackground',
eventRegistrationPoints: 'eventRegistrationPoints',
eventFeedbackPoints: 'eventFeedbackPoints',
createdAt: 'createdAt', createdAt: 'createdAt',
updatedAt: 'updatedAt' updatedAt: 'updatedAt'
} as const } as const

View File

@@ -40,6 +40,7 @@ export type EventFeedbackMinAggregateOutputType = {
eventId: string | null eventId: string | null
rating: number | null rating: number | null
comment: string | null comment: string | null
isRead: boolean | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -50,6 +51,7 @@ export type EventFeedbackMaxAggregateOutputType = {
eventId: string | null eventId: string | null
rating: number | null rating: number | null
comment: string | null comment: string | null
isRead: boolean | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -60,6 +62,7 @@ export type EventFeedbackCountAggregateOutputType = {
eventId: number eventId: number
rating: number rating: number
comment: number comment: number
isRead: number
createdAt: number createdAt: number
updatedAt: number updatedAt: number
_all: number _all: number
@@ -80,6 +83,7 @@ export type EventFeedbackMinAggregateInputType = {
eventId?: true eventId?: true
rating?: true rating?: true
comment?: true comment?: true
isRead?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -90,6 +94,7 @@ export type EventFeedbackMaxAggregateInputType = {
eventId?: true eventId?: true
rating?: true rating?: true
comment?: true comment?: true
isRead?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -100,6 +105,7 @@ export type EventFeedbackCountAggregateInputType = {
eventId?: true eventId?: true
rating?: true rating?: true
comment?: true comment?: true
isRead?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
_all?: true _all?: true
@@ -197,6 +203,7 @@ export type EventFeedbackGroupByOutputType = {
eventId: string eventId: string
rating: number rating: number
comment: string | null comment: string | null
isRead: boolean
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
_count: EventFeedbackCountAggregateOutputType | null _count: EventFeedbackCountAggregateOutputType | null
@@ -230,6 +237,7 @@ export type EventFeedbackWhereInput = {
eventId?: Prisma.StringFilter<"EventFeedback"> | string eventId?: Prisma.StringFilter<"EventFeedback"> | string
rating?: Prisma.IntFilter<"EventFeedback"> | number rating?: Prisma.IntFilter<"EventFeedback"> | number
comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null
isRead?: Prisma.BoolFilter<"EventFeedback"> | boolean
createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
event?: Prisma.XOR<Prisma.EventScalarRelationFilter, Prisma.EventWhereInput> event?: Prisma.XOR<Prisma.EventScalarRelationFilter, Prisma.EventWhereInput>
@@ -242,6 +250,7 @@ export type EventFeedbackOrderByWithRelationInput = {
eventId?: Prisma.SortOrder eventId?: Prisma.SortOrder
rating?: Prisma.SortOrder rating?: Prisma.SortOrder
comment?: Prisma.SortOrderInput | Prisma.SortOrder comment?: Prisma.SortOrderInput | Prisma.SortOrder
isRead?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
event?: Prisma.EventOrderByWithRelationInput event?: Prisma.EventOrderByWithRelationInput
@@ -258,6 +267,7 @@ export type EventFeedbackWhereUniqueInput = Prisma.AtLeast<{
eventId?: Prisma.StringFilter<"EventFeedback"> | string eventId?: Prisma.StringFilter<"EventFeedback"> | string
rating?: Prisma.IntFilter<"EventFeedback"> | number rating?: Prisma.IntFilter<"EventFeedback"> | number
comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null
isRead?: Prisma.BoolFilter<"EventFeedback"> | boolean
createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
event?: Prisma.XOR<Prisma.EventScalarRelationFilter, Prisma.EventWhereInput> event?: Prisma.XOR<Prisma.EventScalarRelationFilter, Prisma.EventWhereInput>
@@ -270,6 +280,7 @@ export type EventFeedbackOrderByWithAggregationInput = {
eventId?: Prisma.SortOrder eventId?: Prisma.SortOrder
rating?: Prisma.SortOrder rating?: Prisma.SortOrder
comment?: Prisma.SortOrderInput | Prisma.SortOrder comment?: Prisma.SortOrderInput | Prisma.SortOrder
isRead?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
_count?: Prisma.EventFeedbackCountOrderByAggregateInput _count?: Prisma.EventFeedbackCountOrderByAggregateInput
@@ -288,6 +299,7 @@ export type EventFeedbackScalarWhereWithAggregatesInput = {
eventId?: Prisma.StringWithAggregatesFilter<"EventFeedback"> | string eventId?: Prisma.StringWithAggregatesFilter<"EventFeedback"> | string
rating?: Prisma.IntWithAggregatesFilter<"EventFeedback"> | number rating?: Prisma.IntWithAggregatesFilter<"EventFeedback"> | number
comment?: Prisma.StringNullableWithAggregatesFilter<"EventFeedback"> | string | null comment?: Prisma.StringNullableWithAggregatesFilter<"EventFeedback"> | string | null
isRead?: Prisma.BoolWithAggregatesFilter<"EventFeedback"> | boolean
createdAt?: Prisma.DateTimeWithAggregatesFilter<"EventFeedback"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"EventFeedback"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"EventFeedback"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"EventFeedback"> | Date | string
} }
@@ -296,6 +308,7 @@ export type EventFeedbackCreateInput = {
id?: string id?: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
event: Prisma.EventCreateNestedOneWithoutFeedbacksInput event: Prisma.EventCreateNestedOneWithoutFeedbacksInput
@@ -308,6 +321,7 @@ export type EventFeedbackUncheckedCreateInput = {
eventId: string eventId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -316,6 +330,7 @@ export type EventFeedbackUpdateInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
event?: Prisma.EventUpdateOneRequiredWithoutFeedbacksNestedInput event?: Prisma.EventUpdateOneRequiredWithoutFeedbacksNestedInput
@@ -328,6 +343,7 @@ export type EventFeedbackUncheckedUpdateInput = {
eventId?: Prisma.StringFieldUpdateOperationsInput | string eventId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -338,6 +354,7 @@ export type EventFeedbackCreateManyInput = {
eventId: string eventId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -346,6 +363,7 @@ export type EventFeedbackUpdateManyMutationInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -356,6 +374,7 @@ export type EventFeedbackUncheckedUpdateManyInput = {
eventId?: Prisma.StringFieldUpdateOperationsInput | string eventId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -381,6 +400,7 @@ export type EventFeedbackCountOrderByAggregateInput = {
eventId?: Prisma.SortOrder eventId?: Prisma.SortOrder
rating?: Prisma.SortOrder rating?: Prisma.SortOrder
comment?: Prisma.SortOrder comment?: Prisma.SortOrder
isRead?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -395,6 +415,7 @@ export type EventFeedbackMaxOrderByAggregateInput = {
eventId?: Prisma.SortOrder eventId?: Prisma.SortOrder
rating?: Prisma.SortOrder rating?: Prisma.SortOrder
comment?: Prisma.SortOrder comment?: Prisma.SortOrder
isRead?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -405,6 +426,7 @@ export type EventFeedbackMinOrderByAggregateInput = {
eventId?: Prisma.SortOrder eventId?: Prisma.SortOrder
rating?: Prisma.SortOrder rating?: Prisma.SortOrder
comment?: Prisma.SortOrder comment?: Prisma.SortOrder
isRead?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -497,10 +519,15 @@ export type EventFeedbackUncheckedUpdateManyWithoutEventNestedInput = {
deleteMany?: Prisma.EventFeedbackScalarWhereInput | Prisma.EventFeedbackScalarWhereInput[] deleteMany?: Prisma.EventFeedbackScalarWhereInput | Prisma.EventFeedbackScalarWhereInput[]
} }
export type BoolFieldUpdateOperationsInput = {
set?: boolean
}
export type EventFeedbackCreateWithoutUserInput = { export type EventFeedbackCreateWithoutUserInput = {
id?: string id?: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
event: Prisma.EventCreateNestedOneWithoutFeedbacksInput event: Prisma.EventCreateNestedOneWithoutFeedbacksInput
@@ -511,6 +538,7 @@ export type EventFeedbackUncheckedCreateWithoutUserInput = {
eventId: string eventId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -549,6 +577,7 @@ export type EventFeedbackScalarWhereInput = {
eventId?: Prisma.StringFilter<"EventFeedback"> | string eventId?: Prisma.StringFilter<"EventFeedback"> | string
rating?: Prisma.IntFilter<"EventFeedback"> | number rating?: Prisma.IntFilter<"EventFeedback"> | number
comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null comment?: Prisma.StringNullableFilter<"EventFeedback"> | string | null
isRead?: Prisma.BoolFilter<"EventFeedback"> | boolean
createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string createdAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string updatedAt?: Prisma.DateTimeFilter<"EventFeedback"> | Date | string
} }
@@ -557,6 +586,7 @@ export type EventFeedbackCreateWithoutEventInput = {
id?: string id?: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
user: Prisma.UserCreateNestedOneWithoutEventFeedbacksInput user: Prisma.UserCreateNestedOneWithoutEventFeedbacksInput
@@ -567,6 +597,7 @@ export type EventFeedbackUncheckedCreateWithoutEventInput = {
userId: string userId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -601,6 +632,7 @@ export type EventFeedbackCreateManyUserInput = {
eventId: string eventId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -609,6 +641,7 @@ export type EventFeedbackUpdateWithoutUserInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
event?: Prisma.EventUpdateOneRequiredWithoutFeedbacksNestedInput event?: Prisma.EventUpdateOneRequiredWithoutFeedbacksNestedInput
@@ -619,6 +652,7 @@ export type EventFeedbackUncheckedUpdateWithoutUserInput = {
eventId?: Prisma.StringFieldUpdateOperationsInput | string eventId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -628,6 +662,7 @@ export type EventFeedbackUncheckedUpdateManyWithoutUserInput = {
eventId?: Prisma.StringFieldUpdateOperationsInput | string eventId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -637,6 +672,7 @@ export type EventFeedbackCreateManyEventInput = {
userId: string userId: string
rating: number rating: number
comment?: string | null comment?: string | null
isRead?: boolean
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -645,6 +681,7 @@ export type EventFeedbackUpdateWithoutEventInput = {
id?: Prisma.StringFieldUpdateOperationsInput | string id?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
user?: Prisma.UserUpdateOneRequiredWithoutEventFeedbacksNestedInput user?: Prisma.UserUpdateOneRequiredWithoutEventFeedbacksNestedInput
@@ -655,6 +692,7 @@ export type EventFeedbackUncheckedUpdateWithoutEventInput = {
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -664,6 +702,7 @@ export type EventFeedbackUncheckedUpdateManyWithoutEventInput = {
userId?: Prisma.StringFieldUpdateOperationsInput | string userId?: Prisma.StringFieldUpdateOperationsInput | string
rating?: Prisma.IntFieldUpdateOperationsInput | number rating?: Prisma.IntFieldUpdateOperationsInput | number
comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null comment?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
isRead?: Prisma.BoolFieldUpdateOperationsInput | boolean
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -676,6 +715,7 @@ export type EventFeedbackSelect<ExtArgs extends runtime.Types.Extensions.Interna
eventId?: boolean eventId?: boolean
rating?: boolean rating?: boolean
comment?: boolean comment?: boolean
isRead?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
event?: boolean | Prisma.EventDefaultArgs<ExtArgs> event?: boolean | Prisma.EventDefaultArgs<ExtArgs>
@@ -688,6 +728,7 @@ export type EventFeedbackSelectCreateManyAndReturn<ExtArgs extends runtime.Types
eventId?: boolean eventId?: boolean
rating?: boolean rating?: boolean
comment?: boolean comment?: boolean
isRead?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
event?: boolean | Prisma.EventDefaultArgs<ExtArgs> event?: boolean | Prisma.EventDefaultArgs<ExtArgs>
@@ -700,6 +741,7 @@ export type EventFeedbackSelectUpdateManyAndReturn<ExtArgs extends runtime.Types
eventId?: boolean eventId?: boolean
rating?: boolean rating?: boolean
comment?: boolean comment?: boolean
isRead?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
event?: boolean | Prisma.EventDefaultArgs<ExtArgs> event?: boolean | Prisma.EventDefaultArgs<ExtArgs>
@@ -712,11 +754,12 @@ export type EventFeedbackSelectScalar = {
eventId?: boolean eventId?: boolean
rating?: boolean rating?: boolean
comment?: boolean comment?: boolean
isRead?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
} }
export type EventFeedbackOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "eventId" | "rating" | "comment" | "createdAt" | "updatedAt", ExtArgs["result"]["eventFeedback"]> export type EventFeedbackOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "userId" | "eventId" | "rating" | "comment" | "isRead" | "createdAt" | "updatedAt", ExtArgs["result"]["eventFeedback"]>
export type EventFeedbackInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type EventFeedbackInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
event?: boolean | Prisma.EventDefaultArgs<ExtArgs> event?: boolean | Prisma.EventDefaultArgs<ExtArgs>
user?: boolean | Prisma.UserDefaultArgs<ExtArgs> user?: boolean | Prisma.UserDefaultArgs<ExtArgs>
@@ -742,6 +785,7 @@ export type $EventFeedbackPayload<ExtArgs extends runtime.Types.Extensions.Inter
eventId: string eventId: string
rating: number rating: number
comment: string | null comment: string | null
isRead: boolean
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
}, ExtArgs["result"]["eventFeedback"]> }, ExtArgs["result"]["eventFeedback"]>
@@ -1174,6 +1218,7 @@ export interface EventFeedbackFieldRefs {
readonly eventId: Prisma.FieldRef<"EventFeedback", 'String'> readonly eventId: Prisma.FieldRef<"EventFeedback", 'String'>
readonly rating: Prisma.FieldRef<"EventFeedback", 'Int'> readonly rating: Prisma.FieldRef<"EventFeedback", 'Int'>
readonly comment: Prisma.FieldRef<"EventFeedback", 'String'> readonly comment: Prisma.FieldRef<"EventFeedback", 'String'>
readonly isRead: Prisma.FieldRef<"EventFeedback", 'Boolean'>
readonly createdAt: Prisma.FieldRef<"EventFeedback", 'DateTime'> readonly createdAt: Prisma.FieldRef<"EventFeedback", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"EventFeedback", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"EventFeedback", 'DateTime'>
} }

View File

@@ -20,16 +20,30 @@ export type SitePreferencesModel = runtime.Types.Result.DefaultSelection<Prisma.
export type AggregateSitePreferences = { export type AggregateSitePreferences = {
_count: SitePreferencesCountAggregateOutputType | null _count: SitePreferencesCountAggregateOutputType | null
_avg: SitePreferencesAvgAggregateOutputType | null
_sum: SitePreferencesSumAggregateOutputType | null
_min: SitePreferencesMinAggregateOutputType | null _min: SitePreferencesMinAggregateOutputType | null
_max: SitePreferencesMaxAggregateOutputType | null _max: SitePreferencesMaxAggregateOutputType | null
} }
export type SitePreferencesAvgAggregateOutputType = {
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
}
export type SitePreferencesSumAggregateOutputType = {
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
}
export type SitePreferencesMinAggregateOutputType = { export type SitePreferencesMinAggregateOutputType = {
id: string | null id: string | null
homeBackground: string | null homeBackground: string | null
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -40,6 +54,8 @@ export type SitePreferencesMaxAggregateOutputType = {
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
eventRegistrationPoints: number | null
eventFeedbackPoints: number | null
createdAt: Date | null createdAt: Date | null
updatedAt: Date | null updatedAt: Date | null
} }
@@ -50,18 +66,32 @@ export type SitePreferencesCountAggregateOutputType = {
eventsBackground: number eventsBackground: number
leaderboardBackground: number leaderboardBackground: number
challengesBackground: number challengesBackground: number
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: number createdAt: number
updatedAt: number updatedAt: number
_all: number _all: number
} }
export type SitePreferencesAvgAggregateInputType = {
eventRegistrationPoints?: true
eventFeedbackPoints?: true
}
export type SitePreferencesSumAggregateInputType = {
eventRegistrationPoints?: true
eventFeedbackPoints?: true
}
export type SitePreferencesMinAggregateInputType = { export type SitePreferencesMinAggregateInputType = {
id?: true id?: true
homeBackground?: true homeBackground?: true
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -72,6 +102,8 @@ export type SitePreferencesMaxAggregateInputType = {
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
} }
@@ -82,6 +114,8 @@ export type SitePreferencesCountAggregateInputType = {
eventsBackground?: true eventsBackground?: true
leaderboardBackground?: true leaderboardBackground?: true
challengesBackground?: true challengesBackground?: true
eventRegistrationPoints?: true
eventFeedbackPoints?: true
createdAt?: true createdAt?: true
updatedAt?: true updatedAt?: true
_all?: true _all?: true
@@ -122,6 +156,18 @@ export type SitePreferencesAggregateArgs<ExtArgs extends runtime.Types.Extension
* Count returned SitePreferences * Count returned SitePreferences
**/ **/
_count?: true | SitePreferencesCountAggregateInputType _count?: true | SitePreferencesCountAggregateInputType
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
*
* Select which fields to average
**/
_avg?: SitePreferencesAvgAggregateInputType
/**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
*
* Select which fields to sum
**/
_sum?: SitePreferencesSumAggregateInputType
/** /**
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
* *
@@ -155,6 +201,8 @@ export type SitePreferencesGroupByArgs<ExtArgs extends runtime.Types.Extensions.
take?: number take?: number
skip?: number skip?: number
_count?: SitePreferencesCountAggregateInputType | true _count?: SitePreferencesCountAggregateInputType | true
_avg?: SitePreferencesAvgAggregateInputType
_sum?: SitePreferencesSumAggregateInputType
_min?: SitePreferencesMinAggregateInputType _min?: SitePreferencesMinAggregateInputType
_max?: SitePreferencesMaxAggregateInputType _max?: SitePreferencesMaxAggregateInputType
} }
@@ -165,9 +213,13 @@ export type SitePreferencesGroupByOutputType = {
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
_count: SitePreferencesCountAggregateOutputType | null _count: SitePreferencesCountAggregateOutputType | null
_avg: SitePreferencesAvgAggregateOutputType | null
_sum: SitePreferencesSumAggregateOutputType | null
_min: SitePreferencesMinAggregateOutputType | null _min: SitePreferencesMinAggregateOutputType | null
_max: SitePreferencesMaxAggregateOutputType | null _max: SitePreferencesMaxAggregateOutputType | null
} }
@@ -196,6 +248,8 @@ export type SitePreferencesWhereInput = {
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
} }
@@ -206,6 +260,8 @@ export type SitePreferencesOrderByWithRelationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -219,6 +275,8 @@ export type SitePreferencesWhereUniqueInput = Prisma.AtLeast<{
eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeFilter<"SitePreferences"> | Date | string
}, "id"> }, "id">
@@ -229,11 +287,15 @@ export type SitePreferencesOrderByWithAggregationInput = {
eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder eventsBackground?: Prisma.SortOrderInput | Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder leaderboardBackground?: Prisma.SortOrderInput | Prisma.SortOrder
challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder challengesBackground?: Prisma.SortOrderInput | Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
_count?: Prisma.SitePreferencesCountOrderByAggregateInput _count?: Prisma.SitePreferencesCountOrderByAggregateInput
_avg?: Prisma.SitePreferencesAvgOrderByAggregateInput
_max?: Prisma.SitePreferencesMaxOrderByAggregateInput _max?: Prisma.SitePreferencesMaxOrderByAggregateInput
_min?: Prisma.SitePreferencesMinOrderByAggregateInput _min?: Prisma.SitePreferencesMinOrderByAggregateInput
_sum?: Prisma.SitePreferencesSumOrderByAggregateInput
} }
export type SitePreferencesScalarWhereWithAggregatesInput = { export type SitePreferencesScalarWhereWithAggregatesInput = {
@@ -245,6 +307,8 @@ export type SitePreferencesScalarWhereWithAggregatesInput = {
eventsBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null eventsBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
leaderboardBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null leaderboardBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null challengesBackground?: Prisma.StringNullableWithAggregatesFilter<"SitePreferences"> | string | null
eventRegistrationPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
eventFeedbackPoints?: Prisma.IntWithAggregatesFilter<"SitePreferences"> | number
createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SitePreferences"> | Date | string
} }
@@ -255,6 +319,8 @@ export type SitePreferencesCreateInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -265,6 +331,8 @@ export type SitePreferencesUncheckedCreateInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -275,6 +343,8 @@ export type SitePreferencesUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -285,6 +355,8 @@ export type SitePreferencesUncheckedUpdateInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -295,6 +367,8 @@ export type SitePreferencesCreateManyInput = {
eventsBackground?: string | null eventsBackground?: string | null
leaderboardBackground?: string | null leaderboardBackground?: string | null
challengesBackground?: string | null challengesBackground?: string | null
eventRegistrationPoints?: number
eventFeedbackPoints?: number
createdAt?: Date | string createdAt?: Date | string
updatedAt?: Date | string updatedAt?: Date | string
} }
@@ -305,6 +379,8 @@ export type SitePreferencesUpdateManyMutationInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -315,6 +391,8 @@ export type SitePreferencesUncheckedUpdateManyInput = {
eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null eventsBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null leaderboardBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null challengesBackground?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
eventRegistrationPoints?: Prisma.IntFieldUpdateOperationsInput | number
eventFeedbackPoints?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
} }
@@ -325,16 +403,25 @@ export type SitePreferencesCountOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
export type SitePreferencesAvgOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
}
export type SitePreferencesMaxOrderByAggregateInput = { export type SitePreferencesMaxOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
homeBackground?: Prisma.SortOrder homeBackground?: Prisma.SortOrder
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
@@ -345,10 +432,17 @@ export type SitePreferencesMinOrderByAggregateInput = {
eventsBackground?: Prisma.SortOrder eventsBackground?: Prisma.SortOrder
leaderboardBackground?: Prisma.SortOrder leaderboardBackground?: Prisma.SortOrder
challengesBackground?: Prisma.SortOrder challengesBackground?: Prisma.SortOrder
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
updatedAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder
} }
export type SitePreferencesSumOrderByAggregateInput = {
eventRegistrationPoints?: Prisma.SortOrder
eventFeedbackPoints?: Prisma.SortOrder
}
export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{ export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
@@ -357,6 +451,8 @@ export type SitePreferencesSelect<ExtArgs extends runtime.Types.Extensions.Inter
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -367,6 +463,8 @@ export type SitePreferencesSelectCreateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -377,6 +475,8 @@ export type SitePreferencesSelectUpdateManyAndReturn<ExtArgs extends runtime.Typ
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -387,11 +487,13 @@ export type SitePreferencesSelectScalar = {
eventsBackground?: boolean eventsBackground?: boolean
leaderboardBackground?: boolean leaderboardBackground?: boolean
challengesBackground?: boolean challengesBackground?: boolean
eventRegistrationPoints?: boolean
eventFeedbackPoints?: boolean
createdAt?: boolean createdAt?: boolean
updatedAt?: boolean updatedAt?: boolean
} }
export type SitePreferencesOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]> export type SitePreferencesOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "homeBackground" | "eventsBackground" | "leaderboardBackground" | "challengesBackground" | "eventRegistrationPoints" | "eventFeedbackPoints" | "createdAt" | "updatedAt", ExtArgs["result"]["sitePreferences"]>
export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
name: "SitePreferences" name: "SitePreferences"
@@ -402,6 +504,8 @@ export type $SitePreferencesPayload<ExtArgs extends runtime.Types.Extensions.Int
eventsBackground: string | null eventsBackground: string | null
leaderboardBackground: string | null leaderboardBackground: string | null
challengesBackground: string | null challengesBackground: string | null
eventRegistrationPoints: number
eventFeedbackPoints: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
}, ExtArgs["result"]["sitePreferences"]> }, ExtArgs["result"]["sitePreferences"]>
@@ -832,6 +936,8 @@ export interface SitePreferencesFieldRefs {
readonly eventsBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly eventsBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly leaderboardBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly leaderboardBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'> readonly challengesBackground: Prisma.FieldRef<"SitePreferences", 'String'>
readonly eventRegistrationPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly eventFeedbackPoints: Prisma.FieldRef<"SitePreferences", 'Int'>
readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> readonly createdAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'> readonly updatedAt: Prisma.FieldRef<"SitePreferences", 'DateTime'>
} }

View File

@@ -0,0 +1,18 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"eventFeedbackPoints" INTEGER NOT NULL DEFAULT 50,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SitePreferences" (
"id" TEXT NOT NULL PRIMARY KEY DEFAULT 'global',
"homeBackground" TEXT,
"eventsBackground" TEXT,
"leaderboardBackground" TEXT,
"challengesBackground" TEXT,
"eventRegistrationPoints" INTEGER NOT NULL DEFAULT 100,
"eventFeedbackPoints" INTEGER NOT NULL DEFAULT 100,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_SitePreferences" ("challengesBackground", "createdAt", "eventFeedbackPoints", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt") SELECT "challengesBackground", "createdAt", "eventFeedbackPoints", "eventRegistrationPoints", "eventsBackground", "homeBackground", "id", "leaderboardBackground", "updatedAt" FROM "SitePreferences";
DROP TABLE "SitePreferences";
ALTER TABLE "new_SitePreferences" RENAME TO "SitePreferences";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,24 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_EventFeedback" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"eventId" TEXT NOT NULL,
"rating" INTEGER NOT NULL,
"comment" TEXT,
"isRead" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "EventFeedback_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "EventFeedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_EventFeedback" ("comment", "createdAt", "eventId", "id", "rating", "updatedAt", "userId") SELECT "comment", "createdAt", "eventId", "id", "rating", "updatedAt", "userId" FROM "EventFeedback";
DROP TABLE "EventFeedback";
ALTER TABLE "new_EventFeedback" RENAME TO "EventFeedback";
CREATE INDEX "EventFeedback_userId_idx" ON "EventFeedback"("userId");
CREATE INDEX "EventFeedback_eventId_idx" ON "EventFeedback"("eventId");
CREATE INDEX "EventFeedback_isRead_idx" ON "EventFeedback"("isRead");
CREATE UNIQUE INDEX "EventFeedback_userId_eventId_key" ON "EventFeedback"("userId", "eventId");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -84,6 +84,7 @@ model EventFeedback {
eventId String eventId String
rating Int rating Int
comment String? comment String?
isRead Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
@@ -92,6 +93,7 @@ model EventFeedback {
@@unique([userId, eventId]) @@unique([userId, eventId])
@@index([userId]) @@index([userId])
@@index([eventId]) @@index([eventId])
@@index([isRead])
} }
model SitePreferences { model SitePreferences {
@@ -100,6 +102,8 @@ model SitePreferences {
eventsBackground String? eventsBackground String?
leaderboardBackground String? leaderboardBackground String?
challengesBackground String? challengesBackground String?
eventRegistrationPoints Int @default(100)
eventFeedbackPoints Int @default(100)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View File

@@ -2,6 +2,7 @@ import { prisma } from "../database";
import type { EventFeedback, Prisma } from "@/prisma/generated/prisma/client"; import type { EventFeedback, Prisma } from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError } from "../errors"; import { ValidationError, NotFoundError } from "../errors";
import { eventService } from "./event.service"; import { eventService } from "./event.service";
import { sitePreferencesService } from "../preferences/site-preferences.service";
export interface CreateOrUpdateFeedbackInput { export interface CreateOrUpdateFeedbackInput {
rating: number; rating: number;
@@ -97,6 +98,8 @@ export class EventFeedbackService {
id: true, id: true,
username: true, username: true,
email: true, email: true,
avatar: true,
score: true,
}, },
}, },
}, },
@@ -168,11 +171,34 @@ export class EventFeedbackService {
throw new NotFoundError("Événement"); throw new NotFoundError("Événement");
} }
// Créer ou mettre à jour le feedback // Vérifier si c'est un nouveau feedback ou une mise à jour
return this.createOrUpdateFeedback(userId, eventId, { const existingFeedback = await this.getUserFeedback(userId, eventId);
const isNewFeedback = !existingFeedback;
// Récupérer les points à attribuer depuis les préférences du site
const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
const pointsToAward = sitePreferences.eventFeedbackPoints || 100;
// Créer ou mettre à jour le feedback et attribuer les points (seulement pour nouveau feedback)
const [feedback] = await Promise.all([
this.createOrUpdateFeedback(userId, eventId, {
rating: data.rating, rating: data.rating,
comment: data.comment || null, comment: data.comment || null,
}); }),
// Attribuer les points seulement si c'est un nouveau feedback
isNewFeedback
? prisma.user.update({
where: { id: userId },
data: {
score: {
increment: pointsToAward,
},
},
})
: Promise.resolve(null),
]);
return feedback;
} }
} }

View File

@@ -3,6 +3,7 @@ import type { EventRegistration } from "@/prisma/generated/prisma/client";
import { ValidationError, NotFoundError, ConflictError } from "../errors"; import { ValidationError, NotFoundError, ConflictError } from "../errors";
import { eventService } from "./event.service"; import { eventService } from "./event.service";
import { calculateEventStatus } from "@/lib/eventStatus"; import { calculateEventStatus } from "@/lib/eventStatus";
import { sitePreferencesService } from "../preferences/site-preferences.service";
/** /**
* Service de gestion des inscriptions aux événements * Service de gestion des inscriptions aux événements
@@ -24,18 +25,40 @@ export class EventRegistrationService {
} }
/** /**
* Désinscrit un utilisateur d'un événement * Désinscrit un utilisateur d'un événement et retire les points attribués
*/ */
async unregisterUserFromEvent( async unregisterUserFromEvent(
userId: string, userId: string,
eventId: string eventId: string
): Promise<void> { ): Promise<void> {
await prisma.eventRegistration.deleteMany({ // Vérifier que l'utilisateur est bien inscrit avant de retirer les points
const isRegistered = await this.checkUserRegistration(userId, eventId);
if (!isRegistered) {
return; // Pas d'inscription, rien à faire
}
// Récupérer les points à retirer depuis les préférences du site
const sitePreferences =
await sitePreferencesService.getOrCreateSitePreferences();
const pointsToRemove = sitePreferences.eventRegistrationPoints || 100;
// Supprimer l'inscription et retirer les points en parallèle
await Promise.all([
prisma.eventRegistration.deleteMany({
where: { where: {
userId, userId,
eventId, eventId,
}, },
}); }),
prisma.user.update({
where: { id: userId },
data: {
score: {
decrement: pointsToRemove,
},
},
}),
]);
} }
/** /**
@@ -96,6 +119,35 @@ export class EventRegistrationService {
return count; return count;
} }
/**
* Récupère tous les inscrits d'un événement avec leurs informations
*/
async getEventRegistrations(eventId: string) {
return prisma.eventRegistration.findMany({
where: {
eventId,
},
include: {
user: {
select: {
id: true,
username: true,
avatar: true,
score: true,
level: true,
hp: true,
maxHp: true,
xp: true,
maxXp: true,
},
},
},
orderBy: {
createdAt: "asc",
},
});
}
/** /**
* Valide et inscrit un utilisateur à un événement avec toutes les règles métier * Valide et inscrit un utilisateur à un événement avec toutes les règles métier
*/ */
@@ -123,8 +175,25 @@ export class EventRegistrationService {
throw new ConflictError("Vous êtes déjà inscrit à cet événement"); throw new ConflictError("Vous êtes déjà inscrit à cet événement");
} }
// Créer l'inscription // Récupérer les points à attribuer depuis les préférences du site
return this.registerUserToEvent(userId, eventId); const sitePreferences =
await sitePreferencesService.getOrCreateSitePreferences();
const pointsToAward = sitePreferences.eventRegistrationPoints || 100;
// Créer l'inscription et attribuer les points en parallèle
const [registration] = await Promise.all([
this.registerUserToEvent(userId, eventId),
prisma.user.update({
where: { id: userId },
data: {
score: {
increment: pointsToAward,
},
},
}),
]);
return registration;
} }
} }

View File

@@ -7,6 +7,8 @@ export interface UpdateSitePreferencesInput {
eventsBackground?: string | null; eventsBackground?: string | null;
leaderboardBackground?: string | null; leaderboardBackground?: string | null;
challengesBackground?: string | null; challengesBackground?: string | null;
eventRegistrationPoints?: number;
eventFeedbackPoints?: number;
} }
/** /**
@@ -38,6 +40,8 @@ export class SitePreferencesService {
eventsBackground: null, eventsBackground: null,
leaderboardBackground: null, leaderboardBackground: null,
challengesBackground: null, challengesBackground: null,
eventRegistrationPoints: 100,
eventFeedbackPoints: 100,
}, },
}); });
} }
@@ -70,6 +74,14 @@ export class SitePreferencesService {
data.challengesBackground === "" data.challengesBackground === ""
? null ? null
: (data.challengesBackground ?? undefined), : (data.challengesBackground ?? undefined),
eventRegistrationPoints:
data.eventRegistrationPoints !== undefined
? data.eventRegistrationPoints
: undefined,
eventFeedbackPoints:
data.eventFeedbackPoints !== undefined
? data.eventFeedbackPoints
: undefined,
}, },
create: { create: {
id: "global", id: "global",
@@ -85,6 +97,8 @@ export class SitePreferencesService {
data.challengesBackground === "" data.challengesBackground === ""
? null ? null
: (data.challengesBackground ?? null), : (data.challengesBackground ?? null),
eventRegistrationPoints: data.eventRegistrationPoints ?? 100,
eventFeedbackPoints: data.eventFeedbackPoints ?? 100,
}, },
}); });
} }