diff --git a/app/houses/page.tsx b/app/houses/page.tsx index c33f898..74ab996 100644 --- a/app/houses/page.tsx +++ b/app/houses/page.tsx @@ -5,7 +5,11 @@ import NavigationWrapper from "@/components/navigation/NavigationWrapper"; import HousesSection from "@/components/houses/HousesSection"; import { houseService } from "@/services/houses/house.service"; import { prisma } from "@/services/database"; -import type { House, HouseMembership, HouseInvitation } from "@/prisma/generated/prisma/client"; +import type { + House, + HouseMembership, + HouseInvitation, +} from "@/prisma/generated/prisma/client"; export const dynamic = "force-dynamic"; @@ -17,15 +21,17 @@ type HouseWithRelations = House & { avatar: string | null; } | null; creatorId?: string; - memberships?: Array; + memberships?: Array< + HouseMembership & { + user: { + id: string; + username: string; + avatar: string | null; + score: number | null; + level: number | null; + }; + } + >; }; type InvitationWithRelations = HouseInvitation & { @@ -47,10 +53,39 @@ export default async function HousesPage() { redirect("/login"); } - const [housesData, myHouseData, invitationsData, users, backgroundImage] = await Promise.all([ - // Récupérer les maisons - houseService.getAllHouses({ - include: { + const [housesData, myHouseData, invitationsData, users, backgroundImage] = + await Promise.all([ + // Récupérer les maisons + houseService.getAllHouses({ + include: { + memberships: { + include: { + user: { + select: { + id: true, + username: true, + avatar: true, + score: true, + level: true, + }, + }, + }, + orderBy: [ + { role: "asc" }, // OWNER, ADMIN, MEMBER + { user: { score: "desc" } }, // Puis par score décroissant + ], + }, + creator: { + select: { + id: true, + username: true, + avatar: true, + }, + }, + }, + }), + // Récupérer la maison de l'utilisateur + houseService.getUserHouse(session.user.id, { memberships: { include: { user: { @@ -63,10 +98,6 @@ export default async function HousesPage() { }, }, }, - orderBy: [ - { role: "asc" }, // OWNER, ADMIN, MEMBER - { user: { score: "desc" } }, // Puis par score décroissant - ], }, creator: { select: { @@ -75,78 +106,66 @@ export default async function HousesPage() { avatar: true, }, }, - }, - }), - // Récupérer la maison de l'utilisateur - houseService.getUserHouse(session.user.id, { - memberships: { - include: { - user: { - select: { - id: true, - username: true, - avatar: true, - score: true, - level: true, - }, + }), + // Récupérer les invitations de l'utilisateur + houseService.getUserInvitations(session.user.id, "PENDING"), + // Récupérer tous les utilisateurs sans maison pour les invitations + prisma.user.findMany({ + where: { + houseMemberships: { + none: {}, }, }, - }, - creator: { select: { id: true, username: true, avatar: true, }, - }, - }), - // Récupérer les invitations de l'utilisateur - houseService.getUserInvitations(session.user.id, "PENDING"), - // Récupérer tous les utilisateurs sans maison pour les invitations - prisma.user.findMany({ - where: { - houseMemberships: { - none: {}, + orderBy: { + username: "asc", }, - }, - select: { - id: true, - username: true, - avatar: true, - }, - orderBy: { - username: "asc", - }, - }), - getBackgroundImage("challenges", "/got-2.jpg"), - ]); + }), + getBackgroundImage("challenges", "/got-2.jpg"), + ]); // Sérialiser les données pour le client - const houses = (housesData as HouseWithRelations[]).map((house: HouseWithRelations) => ({ - id: house.id, - name: house.name, - description: house.description, - creator: house.creator || { id: house.creatorId || "", username: "Unknown", avatar: null }, - memberships: (house.memberships || []).map((m) => ({ - id: m.id, - role: m.role, - user: { - id: m.user.id, - username: m.user.username, - avatar: m.user.avatar, - score: m.user.score ?? 0, - level: m.user.level ?? 1, + const houses = (housesData as HouseWithRelations[]).map( + (house: HouseWithRelations) => ({ + id: house.id, + name: house.name, + description: house.description, + creator: house.creator || { + id: house.creatorId || "", + username: "Unknown", + avatar: null, }, - })), - })); + memberships: (house.memberships || []).map((m) => ({ + id: m.id, + role: m.role, + user: { + id: m.user.id, + username: m.user.username, + avatar: m.user.avatar, + score: m.user.score ?? 0, + level: m.user.level ?? 1, + }, + })), + }) + ); const myHouse = myHouseData ? { id: myHouseData.id, name: myHouseData.name, description: myHouseData.description, - creator: (myHouseData as HouseWithRelations).creator || { id: (myHouseData as HouseWithRelations).creatorId || "", username: "Unknown", avatar: null }, - memberships: ((myHouseData as HouseWithRelations).memberships || []).map((m) => ({ + creator: (myHouseData as HouseWithRelations).creator || { + id: (myHouseData as HouseWithRelations).creatorId || "", + username: "Unknown", + avatar: null, + }, + memberships: ( + (myHouseData as HouseWithRelations).memberships || [] + ).map((m) => ({ id: m.id, role: m.role, user: { @@ -160,16 +179,18 @@ export default async function HousesPage() { } : null; - const invitations = (invitationsData as InvitationWithRelations[]).map((inv: InvitationWithRelations) => ({ - id: inv.id, - house: { - id: inv.house.id, - name: inv.house.name, - }, - inviter: inv.inviter, - status: inv.status, - createdAt: inv.createdAt.toISOString(), - })); + const invitations = (invitationsData as InvitationWithRelations[]).map( + (inv: InvitationWithRelations) => ({ + id: inv.id, + house: { + id: inv.house.id, + name: inv.house.name, + }, + inviter: inv.inviter, + status: inv.status, + createdAt: inv.createdAt.toISOString(), + }) + ); return (
diff --git a/components/houses/HouseManagement.tsx b/components/houses/HouseManagement.tsx index 1511634..6612596 100644 --- a/components/houses/HouseManagement.tsx +++ b/components/houses/HouseManagement.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useTransition } from "react"; +import { useState, useEffect, useTransition, useCallback } from "react"; import { useSession } from "next-auth/react"; import Card from "@/components/ui/Card"; import Button from "@/components/ui/Button"; @@ -8,7 +8,7 @@ import HouseForm from "./HouseForm"; import RequestList from "./RequestList"; import Alert from "@/components/ui/Alert"; import { deleteHouse, leaveHouse, removeMember } from "@/actions/houses/update"; -import { inviteUser } from "@/actions/houses/invitations"; +import { inviteUser, cancelInvitation } from "@/actions/houses/invitations"; interface House { id: string; @@ -38,6 +38,22 @@ interface User { avatar: string | null; } +interface HouseInvitation { + id: string; + invitee: { + id: string; + username: string; + avatar: string | null; + }; + inviter: { + id: string; + username: string; + avatar: string | null; + }; + status: string; + createdAt: string; +} + interface HouseManagementProps { house: House | null; users?: User[]; @@ -76,6 +92,7 @@ export default function HouseManagement({ const [showInviteForm, setShowInviteForm] = useState(false); const [selectedUserId, setSelectedUserId] = useState(""); const [requests, setRequests] = useState(initialRequests); + const [invitations, setInvitations] = useState([]); const [isPending, startTransition] = useTransition(); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); @@ -105,6 +122,34 @@ export default function HouseManagement({ fetchRequests(); }, [house, isAdmin]); + const fetchInvitations = useCallback(async () => { + if (!house || !isAdmin) return; + try { + const response = await fetch( + `/api/houses/${house.id}/invitations?status=PENDING` + ); + if (response.ok) { + const data = await response.json(); + setInvitations(data); + } + } catch (error) { + console.error("Error fetching invitations:", error); + } + }, [house, isAdmin]); + + useEffect(() => { + // Utiliser un timeout pour éviter l'appel synchrone de setState dans l'effect + const timeout = setTimeout(() => { + fetchInvitations(); + }, 0); + return () => clearTimeout(timeout); + }, [fetchInvitations]); + + const handleUpdate = useCallback(() => { + fetchInvitations(); + onUpdate?.(); + }, [fetchInvitations, onUpdate]); + const handleDelete = () => { if ( !house || @@ -119,7 +164,7 @@ export default function HouseManagement({ if (result.success) { // Rafraîchir le score dans le header (le créateur perd des points) window.dispatchEvent(new Event("refreshUserScore")); - onUpdate?.(); + handleUpdate(); } else { setError(result.error || "Erreur lors de la suppression"); } @@ -136,7 +181,7 @@ export default function HouseManagement({ const result = await leaveHouse(house.id); if (result.success) { window.dispatchEvent(new Event("refreshUserScore")); - onUpdate?.(); + handleUpdate(); } else { setError(result.error || "Erreur lors de la sortie"); } @@ -157,7 +202,9 @@ export default function HouseManagement({ setSuccess("Invitation envoyée"); setShowInviteForm(false); setSelectedUserId(""); - onUpdate?.(); + // Rafraîchir la liste des invitations + await fetchInvitations(); + handleUpdate(); } else { setError(result.error || "Erreur lors de l'envoi de l'invitation"); } @@ -271,7 +318,7 @@ export default function HouseManagement({ house={house} onSuccess={() => { setIsEditing(false); - onUpdate?.(); + handleUpdate(); }} onCancel={() => setIsEditing(false)} /> @@ -286,6 +333,11 @@ export default function HouseManagement({ }} > Membres ({house.memberships?.length ?? 0}) + {isAdmin && invitations.length > 0 && ( + + • {invitations.length} invitation{invitations.length > 1 ? "s" : ""} en cours + + )}
{(house.memberships || []).map((membership) => { @@ -379,7 +431,7 @@ export default function HouseManagement({ window.dispatchEvent( new Event("refreshUserScore") ); - onUpdate?.(); + handleUpdate(); } else { setError( result.error || @@ -402,6 +454,103 @@ export default function HouseManagement({ })}
+ {isAdmin && invitations.length > 0 && ( +
+
+ Invitations en cours +
+
+ {invitations + .filter((inv) => inv.status === "PENDING") + .map((invitation) => ( +
+
+ {invitation.invitee.avatar && ( + {invitation.invitee.username} + )} +
+ + {invitation.invitee.username} + + + Invité par {invitation.inviter.username} + +
+
+
+ + En attente + + +
+
+ ))} +
+
+ )} + {isAdmin && (
{showInviteForm ? ( @@ -471,7 +620,7 @@ export default function HouseManagement({ > Demandes d'adhésion - + )}