Compare commits
3 Commits
62ce98c80e
...
2a1f13a5f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a1f13a5f0 | ||
|
|
c81e27c602 | ||
|
|
9fe5e60907 |
@@ -1,7 +1,7 @@
|
||||
from typing import List, Optional, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.dependencies.auth import get_current_active_user, get_current_user, get_optional_current_user
|
||||
@@ -1050,19 +1050,25 @@ def read_gift_purchases_by_guest(
|
||||
return gift_purchase_crud.get_gift_reservations_by_guest(db, guest_id=guest_id)
|
||||
|
||||
@router.get(
|
||||
"/purchases/guest/all",
|
||||
"/reservations/guests",
|
||||
response_model=List[GiftPurchase],
|
||||
operation_id="read_guests_gift_purchases"
|
||||
operation_id="read_guests_gift_reservations",
|
||||
)
|
||||
def read_all_guest_gift_reservations(
|
||||
*,
|
||||
db: Session = Depends(get_db),
|
||||
event_id: Optional[str] = Query(None, description="Optional event ID to filter reservations by event"),
|
||||
) -> Any:
|
||||
"""
|
||||
Retrieve all guest gift reservations.
|
||||
Retrieve all guest gift reservations, optionally filtered by event ID.
|
||||
"""
|
||||
reservations = gift_purchase_crud.get_all_guest_gift_reservations(db=db)
|
||||
if event_id:
|
||||
event_id = UUID(event_id)
|
||||
reservations = gift_purchase_crud.get_event_guest_gift_reservations(db=db, event_id=event_id)
|
||||
else:
|
||||
reservations = gift_purchase_crud.get_all_guest_gift_reservations(db=db)
|
||||
|
||||
if not reservations:
|
||||
reservations = []
|
||||
|
||||
return reservations
|
||||
|
||||
@@ -387,6 +387,41 @@ class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUp
|
||||
|
||||
return result
|
||||
|
||||
def get_event_guest_gift_reservations(self, db: Session, event_id: UUID | str) -> List[GiftPurchase]:
|
||||
"""Retrieve all gift reservations for guests belonging to a specific event."""
|
||||
event_id = event_id if isinstance(event_id, UUID) else UUID(event_id)
|
||||
|
||||
stmt = (
|
||||
select(Guest)
|
||||
.where(Guest.event_id == event_id)
|
||||
.options(joinedload(Guest.gifts))
|
||||
)
|
||||
|
||||
# Correct: Call unique() on the RESULT, not on the stmt
|
||||
guests = db.execute(stmt).unique().scalars().all()
|
||||
|
||||
results = []
|
||||
for guest in guests:
|
||||
for gift in guest.gifts:
|
||||
reservation_stmt = select(GuestGifts).where(
|
||||
(GuestGifts.c.guest_id == guest.id) &
|
||||
(GuestGifts.c.gift_id == gift.id)
|
||||
)
|
||||
|
||||
reservation = db.execute(reservation_stmt).first()
|
||||
|
||||
if reservation:
|
||||
purchase = GiftPurchase(
|
||||
id=UUID('00000000-0000-0000-0000-000000000000'),
|
||||
gift_id=gift.id,
|
||||
guest_id=guest.id,
|
||||
quantity=1,
|
||||
purchased_at=reservation.reserved_at,
|
||||
notes=reservation.notes
|
||||
)
|
||||
results.append(purchase)
|
||||
|
||||
return results
|
||||
|
||||
# Create CRUD instances
|
||||
gift_item_crud = CRUDGiftItem(GiftItem)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
readGiftPurchase,
|
||||
readGiftPurchasesByGift,
|
||||
readGiftPurchasesByGuest,
|
||||
readGuestsGiftPurchases,
|
||||
readGuestsGiftReservations,
|
||||
createEvent,
|
||||
getUserEvents,
|
||||
getUpcomingEvents,
|
||||
@@ -166,7 +166,7 @@ import type {
|
||||
ReadGiftPurchaseData,
|
||||
ReadGiftPurchasesByGiftData,
|
||||
ReadGiftPurchasesByGuestData,
|
||||
ReadGuestsGiftPurchasesData,
|
||||
ReadGuestsGiftReservationsData,
|
||||
CreateEventData,
|
||||
CreateEventError,
|
||||
CreateEventResponse,
|
||||
@@ -1423,16 +1423,16 @@ export const readGiftPurchasesByGuestOptions = (
|
||||
});
|
||||
};
|
||||
|
||||
export const readGuestsGiftPurchasesQueryKey = (
|
||||
options?: Options<ReadGuestsGiftPurchasesData>,
|
||||
) => createQueryKey("readGuestsGiftPurchases", options);
|
||||
export const readGuestsGiftReservationsQueryKey = (
|
||||
options?: Options<ReadGuestsGiftReservationsData>,
|
||||
) => createQueryKey("readGuestsGiftReservations", options);
|
||||
|
||||
export const readGuestsGiftPurchasesOptions = (
|
||||
options?: Options<ReadGuestsGiftPurchasesData>,
|
||||
export const readGuestsGiftReservationsOptions = (
|
||||
options?: Options<ReadGuestsGiftReservationsData>,
|
||||
) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await readGuestsGiftPurchases({
|
||||
const { data } = await readGuestsGiftReservations({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
@@ -1440,7 +1440,7 @@ export const readGuestsGiftPurchasesOptions = (
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: readGuestsGiftPurchasesQueryKey(options),
|
||||
queryKey: readGuestsGiftReservationsQueryKey(options),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -142,8 +142,9 @@ import type {
|
||||
ReadGiftPurchasesByGuestData,
|
||||
ReadGiftPurchasesByGuestResponse,
|
||||
ReadGiftPurchasesByGuestError,
|
||||
ReadGuestsGiftPurchasesData,
|
||||
ReadGuestsGiftPurchasesResponse,
|
||||
ReadGuestsGiftReservationsData,
|
||||
ReadGuestsGiftReservationsResponse,
|
||||
ReadGuestsGiftReservationsError,
|
||||
CreateEventData,
|
||||
CreateEventResponse,
|
||||
CreateEventError,
|
||||
@@ -1157,17 +1158,19 @@ export const readGiftPurchasesByGuest = <ThrowOnError extends boolean = false>(
|
||||
|
||||
/**
|
||||
* Read All Guest Gift Reservations
|
||||
* Retrieve all guest gift reservations.
|
||||
* Retrieve all guest gift reservations, optionally filtered by event ID.
|
||||
*/
|
||||
export const readGuestsGiftPurchases = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<ReadGuestsGiftPurchasesData, ThrowOnError>,
|
||||
export const readGuestsGiftReservations = <
|
||||
ThrowOnError extends boolean = false,
|
||||
>(
|
||||
options?: Options<ReadGuestsGiftReservationsData, ThrowOnError>,
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
ReadGuestsGiftPurchasesResponse,
|
||||
unknown,
|
||||
ReadGuestsGiftReservationsResponse,
|
||||
ReadGuestsGiftReservationsError,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/v1/events/gifts/purchases/guest/all",
|
||||
url: "/api/v1/events/gifts/reservations/guests",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1876,22 +1876,37 @@ export type ReadGiftPurchasesByGuestResponses = {
|
||||
export type ReadGiftPurchasesByGuestResponse =
|
||||
ReadGiftPurchasesByGuestResponses[keyof ReadGiftPurchasesByGuestResponses];
|
||||
|
||||
export type ReadGuestsGiftPurchasesData = {
|
||||
export type ReadGuestsGiftReservationsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: "/api/v1/events/gifts/purchases/guest/all";
|
||||
query?: {
|
||||
/**
|
||||
* Optional event ID to filter reservations by event
|
||||
*/
|
||||
event_id?: string | null;
|
||||
};
|
||||
url: "/api/v1/events/gifts/reservations/guests";
|
||||
};
|
||||
|
||||
export type ReadGuestsGiftPurchasesResponses = {
|
||||
export type ReadGuestsGiftReservationsErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type ReadGuestsGiftReservationsError =
|
||||
ReadGuestsGiftReservationsErrors[keyof ReadGuestsGiftReservationsErrors];
|
||||
|
||||
export type ReadGuestsGiftReservationsResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: Array<GiftPurchase>;
|
||||
};
|
||||
|
||||
export type ReadGuestsGiftPurchasesResponse =
|
||||
ReadGuestsGiftPurchasesResponses[keyof ReadGuestsGiftPurchasesResponses];
|
||||
export type ReadGuestsGiftReservationsResponse =
|
||||
ReadGuestsGiftReservationsResponses[keyof ReadGuestsGiftReservationsResponses];
|
||||
|
||||
export type CreateEventData = {
|
||||
body: EventCreate;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
Reference in New Issue
Block a user