Add functionality to display gift reservations per guest
All checks were successful
Build and Push Docker Images / changes (push) Successful in 4s
Build and Push Docker Images / build-backend (push) Has been skipped
Build and Push Docker Images / build-frontend (push) Successful in 1m10s

Implemented fetching and grouping of guests' gift reservations for events. Added a popover UI to display reservation details, including guest names and quantities, with a loading state for pending data. This enhances the gift dashboard's interactivity and usability.
This commit is contained in:
2025-03-19 19:56:23 +01:00
parent c81e27c602
commit 2a1f13a5f0
2 changed files with 97 additions and 16 deletions

View File

@@ -44,7 +44,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { GiftPriority, GiftStatus } from "@/client/types.gen"; import { GiftPriority, GiftPurchase, GiftStatus } from "@/client/types.gen";
import { CategoryModal } from "@/components/gifts/category-modal"; import { CategoryModal } from "@/components/gifts/category-modal";
import { GiftModal } from "@/components/gifts/gift-modal"; import { GiftModal } from "@/components/gifts/gift-modal";
import { import {
@@ -52,6 +52,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { useGuests } from "@/context/guest-context";
export default function GiftRegistryPage() { export default function GiftRegistryPage() {
const { slug } = useParams<{ slug: string }>(); const { slug } = useParams<{ slug: string }>();
@@ -68,8 +69,10 @@ export default function GiftRegistryPage() {
currentEventId, currentEventId,
setCurrentEventId, setCurrentEventId,
deleteItem, deleteItem,
fetchGuestsGiftPurchases,
} = useGifts(); } = useGifts();
const { guests } = useGuests();
// State for modals // State for modals
const [isAddGiftModalOpen, setIsAddGiftModalOpen] = useState(false); const [isAddGiftModalOpen, setIsAddGiftModalOpen] = useState(false);
const [isEditGiftModalOpen, setIsEditGiftModalOpen] = useState(false); const [isEditGiftModalOpen, setIsEditGiftModalOpen] = useState(false);
@@ -84,6 +87,39 @@ export default function GiftRegistryPage() {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [categoryFilter, setCategoryFilter] = useState<string>("all"); const [categoryFilter, setCategoryFilter] = useState<string>("all");
const [statusFilter, setStatusFilter] = useState<string>("all"); const [statusFilter, setStatusFilter] = useState<string>("all");
const [reservations, setReservations] = useState<
Record<string, GiftPurchase[]>
>({});
const [loadingReservations, setLoadingReservations] = useState<boolean>(true);
useEffect(() => {
const loadReservations = async () => {
if (currentEventId) {
setLoadingReservations(true);
try {
const data = await fetchGuestsGiftPurchases(currentEventId);
if (data) {
const groupedReservations = data.reduce(
(acc, purchase) => {
const giftId = purchase.gift_id;
if (!acc[giftId]) acc[giftId] = [];
acc[giftId].push(purchase);
return acc;
},
{} as Record<string, GiftPurchase[]>,
);
setReservations(groupedReservations);
}
} catch (err) {
console.error("Unable to fetch reservations:", err);
} finally {
setLoadingReservations(false);
}
}
};
loadReservations();
}, [currentEventId, fetchGuestsGiftPurchases]);
// Filter items based on search query and filters // Filter items based on search query and filters
const filteredItems = items const filteredItems = items
@@ -436,20 +472,44 @@ export default function GiftRegistryPage() {
{getStatusBadge(item.status)} {getStatusBadge(item.status)}
</div> </div>
</PopoverTrigger> </PopoverTrigger>
{/*<PopoverContent className="text-sm">*/} <PopoverContent className="text-sm w-[220px]">
{/* {item.reservations &&*/} {loadingReservations ? (
{/* item.reservations.length > 0 ? (*/} <div className="flex items-center justify-center p-2">
{/* <ul className="list-disc">*/} <Loader2
{/* {item.reservations.map((res) => (*/} className="animate-spin"
{/* <li key={res.guest_id}>*/} size={16}
{/* {res.guest_name}: {res.quantity}*/} />
{/* </li>*/} <span className="ml-2">
{/* ))}*/} Loading reservations...
{/* </ul>*/} </span>
{/* ) : (*/} </div>
{/* <p>No reservations available.</p>*/} ) : reservations[item.id] &&
{/* )}*/} reservations[item.id].length > 0 ? (
{/*</PopoverContent>*/} <ul className="list-disc pl-4 py-2">
{reservations[item.id].map(
(purchase, index) => {
const guest = guests?.find(
(g) => g.id === purchase.guest_id,
);
return (
<li key={`${purchase.id}_${index}`}>
<strong>
{guest?.full_name ||
purchase.guest_id}
</strong>
: {purchase.quantity}
</li>
);
},
)}
</ul>
) : (
<p className="p-2 text-gray-500">
No reservations available.
</p>
)}
</PopoverContent>
</Popover> </Popover>
) : ( ) : (
getStatusBadge(item.status) getStatusBadge(item.status)

View File

@@ -24,6 +24,7 @@ import {
readGiftPurchase, readGiftPurchase,
readGiftPurchasesByGift, readGiftPurchasesByGift,
readGiftPurchasesByGuest, readGiftPurchasesByGuest,
readGuestsGiftReservations,
} from "@/client/sdk.gen"; } from "@/client/sdk.gen";
import { import {
GiftCategory, GiftCategory,
@@ -48,6 +49,10 @@ interface GiftContextState {
refetchCategories: (eventId: string) => Promise<any>; refetchCategories: (eventId: string) => Promise<any>;
fetchCategoryById: (id: string, eventId?: string) => void; fetchCategoryById: (id: string, eventId?: string) => void;
fetchGuestsGiftPurchases: (
eventId: string,
) => Promise<GiftPurchase[] | undefined>;
createCategory: ( createCategory: (
data: GiftCategoryCreate, data: GiftCategoryCreate,
) => Promise<GiftCategory | undefined>; ) => Promise<GiftCategory | undefined>;
@@ -147,6 +152,8 @@ const defaultGiftContextState: GiftContextState = {
}, },
fetchCategoryById: () => {}, fetchCategoryById: () => {},
fetchGuestsGiftPurchases: async () => undefined,
createCategory: async () => { createCategory: async () => {
throw new Error("GiftContext not initialized"); throw new Error("GiftContext not initialized");
}, },
@@ -310,6 +317,20 @@ export const GiftProvider: React.FC<GiftProviderProps> = ({ children }) => {
} }
}; };
const fetchGuestsGiftPurchases = async (
eventId: string,
): Promise<GiftPurchase[] | undefined> => {
try {
const result = await readGuestsGiftReservations({
query: { event_id: eventId },
});
return result.data;
} catch (error) {
console.error("Error fetching guests' gift purchases:", error);
throw error;
}
};
// Create Category Mutation // Create Category Mutation
const createCategoryMutation = useMutation({ const createCategoryMutation = useMutation({
mutationFn: (data: GiftCategoryCreate) => mutationFn: (data: GiftCategoryCreate) =>
@@ -771,7 +792,7 @@ export const GiftProvider: React.FC<GiftProviderProps> = ({ children }) => {
isLoadingCategories, isLoadingCategories,
isLoadingCategory, isLoadingCategory,
refetchCategories, refetchCategories,
fetchGuestsGiftPurchases,
fetchCategoryById, fetchCategoryById,
createCategory: createCategoryMutation.mutateAsync, createCategory: createCategoryMutation.mutateAsync,
updateCategory: (id, data, eventId) => updateCategory: (id, data, eventId) =>