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,
SelectValue,
} 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 { GiftModal } from "@/components/gifts/gift-modal";
import {
@@ -52,6 +52,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useGuests } from "@/context/guest-context";
export default function GiftRegistryPage() {
const { slug } = useParams<{ slug: string }>();
@@ -68,8 +69,10 @@ export default function GiftRegistryPage() {
currentEventId,
setCurrentEventId,
deleteItem,
fetchGuestsGiftPurchases,
} = useGifts();
const { guests } = useGuests();
// State for modals
const [isAddGiftModalOpen, setIsAddGiftModalOpen] = useState(false);
const [isEditGiftModalOpen, setIsEditGiftModalOpen] = useState(false);
@@ -84,6 +87,39 @@ export default function GiftRegistryPage() {
const [searchQuery, setSearchQuery] = useState("");
const [categoryFilter, setCategoryFilter] = 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
const filteredItems = items
@@ -436,20 +472,44 @@ export default function GiftRegistryPage() {
{getStatusBadge(item.status)}
</div>
</PopoverTrigger>
{/*<PopoverContent className="text-sm">*/}
{/* {item.reservations &&*/}
{/* item.reservations.length > 0 ? (*/}
{/* <ul className="list-disc">*/}
{/* {item.reservations.map((res) => (*/}
{/* <li key={res.guest_id}>*/}
{/* {res.guest_name}: {res.quantity}*/}
{/* </li>*/}
{/* ))}*/}
{/* </ul>*/}
{/* ) : (*/}
{/* <p>No reservations available.</p>*/}
{/* )}*/}
{/*</PopoverContent>*/}
<PopoverContent className="text-sm w-[220px]">
{loadingReservations ? (
<div className="flex items-center justify-center p-2">
<Loader2
className="animate-spin"
size={16}
/>
<span className="ml-2">
Loading reservations...
</span>
</div>
) : reservations[item.id] &&
reservations[item.id].length > 0 ? (
<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>
) : (
getStatusBadge(item.status)

View File

@@ -24,6 +24,7 @@ import {
readGiftPurchase,
readGiftPurchasesByGift,
readGiftPurchasesByGuest,
readGuestsGiftReservations,
} from "@/client/sdk.gen";
import {
GiftCategory,
@@ -48,6 +49,10 @@ interface GiftContextState {
refetchCategories: (eventId: string) => Promise<any>;
fetchCategoryById: (id: string, eventId?: string) => void;
fetchGuestsGiftPurchases: (
eventId: string,
) => Promise<GiftPurchase[] | undefined>;
createCategory: (
data: GiftCategoryCreate,
) => Promise<GiftCategory | undefined>;
@@ -147,6 +152,8 @@ const defaultGiftContextState: GiftContextState = {
},
fetchCategoryById: () => {},
fetchGuestsGiftPurchases: async () => undefined,
createCategory: async () => {
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
const createCategoryMutation = useMutation({
mutationFn: (data: GiftCategoryCreate) =>
@@ -771,7 +792,7 @@ export const GiftProvider: React.FC<GiftProviderProps> = ({ children }) => {
isLoadingCategories,
isLoadingCategory,
refetchCategories,
fetchGuestsGiftPurchases,
fetchCategoryById,
createCategory: createCategoryMutation.mutateAsync,
updateCategory: (id, data, eventId) =>