Compare commits
7 Commits
62ce98c80e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
678e1db0a3 | ||
|
|
a2c3f16dc7 | ||
|
|
44e6b2a6dc | ||
|
|
42508af610 | ||
|
|
2a1f13a5f0 | ||
|
|
c81e27c602 | ||
|
|
9fe5e60907 |
@@ -1,7 +1,7 @@
|
|||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any
|
||||||
from uuid import UUID
|
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 sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.api.dependencies.auth import get_current_active_user, get_current_user, get_optional_current_user
|
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)
|
return gift_purchase_crud.get_gift_reservations_by_guest(db, guest_id=guest_id)
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/purchases/guest/all",
|
"/reservations/guests",
|
||||||
response_model=List[GiftPurchase],
|
response_model=List[GiftPurchase],
|
||||||
operation_id="read_guests_gift_purchases"
|
operation_id="read_guests_gift_reservations",
|
||||||
)
|
)
|
||||||
def read_all_guest_gift_reservations(
|
def read_all_guest_gift_reservations(
|
||||||
*,
|
*,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
event_id: Optional[str] = Query(None, description="Optional event ID to filter reservations by event"),
|
||||||
) -> Any:
|
) -> 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:
|
if not reservations:
|
||||||
reservations = []
|
reservations = []
|
||||||
|
|
||||||
return reservations
|
return reservations
|
||||||
|
|||||||
@@ -387,6 +387,41 @@ class CRUDGiftPurchase(CRUDBase[GiftPurchase, GiftPurchaseCreate, GiftPurchaseUp
|
|||||||
|
|
||||||
return result
|
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
|
# Create CRUD instances
|
||||||
gift_item_crud = CRUDGiftItem(GiftItem)
|
gift_item_crud = CRUDGiftItem(GiftItem)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
from pydantic import BaseModel, EmailStr, ConfigDict
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Any, Dict
|
from typing import Optional, Any, Dict
|
||||||
from app.models.guest import GuestStatus
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import BaseModel, EmailStr, ConfigDict
|
||||||
|
|
||||||
|
from app.models.guest import GuestStatus
|
||||||
from app.schemas.rsvp import RSVPSchema, RSVPStatus
|
from app.schemas.rsvp import RSVPSchema, RSVPStatus
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +49,8 @@ class GuestRead(GuestBase):
|
|||||||
is_blocked: bool
|
is_blocked: bool
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
invitation_code: str
|
invitation_code: str
|
||||||
|
rsvp: Optional[RSVPSchema] = None
|
||||||
|
|
||||||
|
|
||||||
class GuestWithRSVPResponse(BaseModel):
|
class GuestWithRSVPResponse(BaseModel):
|
||||||
"""
|
"""
|
||||||
@@ -62,6 +63,7 @@ class GuestWithRSVPResponse(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
def map_rsvp_status_to_guest_status(rsvp_status: RSVPStatus) -> GuestStatus:
|
def map_rsvp_status_to_guest_status(rsvp_status: RSVPStatus) -> GuestStatus:
|
||||||
if rsvp_status == RSVPStatus.ATTENDING:
|
if rsvp_status == RSVPStatus.ATTENDING:
|
||||||
return GuestStatus.CONFIRMED
|
return GuestStatus.CONFIRMED
|
||||||
@@ -70,4 +72,4 @@ def map_rsvp_status_to_guest_status(rsvp_status: RSVPStatus) -> GuestStatus:
|
|||||||
elif rsvp_status == RSVPStatus.MAYBE:
|
elif rsvp_status == RSVPStatus.MAYBE:
|
||||||
return GuestStatus.PENDING
|
return GuestStatus.PENDING
|
||||||
else:
|
else:
|
||||||
return GuestStatus.INVITED
|
return GuestStatus.INVITED
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import {
|
|||||||
readGiftPurchase,
|
readGiftPurchase,
|
||||||
readGiftPurchasesByGift,
|
readGiftPurchasesByGift,
|
||||||
readGiftPurchasesByGuest,
|
readGiftPurchasesByGuest,
|
||||||
readGuestsGiftPurchases,
|
readGuestsGiftReservations,
|
||||||
createEvent,
|
createEvent,
|
||||||
getUserEvents,
|
getUserEvents,
|
||||||
getUpcomingEvents,
|
getUpcomingEvents,
|
||||||
@@ -166,7 +166,7 @@ import type {
|
|||||||
ReadGiftPurchaseData,
|
ReadGiftPurchaseData,
|
||||||
ReadGiftPurchasesByGiftData,
|
ReadGiftPurchasesByGiftData,
|
||||||
ReadGiftPurchasesByGuestData,
|
ReadGiftPurchasesByGuestData,
|
||||||
ReadGuestsGiftPurchasesData,
|
ReadGuestsGiftReservationsData,
|
||||||
CreateEventData,
|
CreateEventData,
|
||||||
CreateEventError,
|
CreateEventError,
|
||||||
CreateEventResponse,
|
CreateEventResponse,
|
||||||
@@ -1423,16 +1423,16 @@ export const readGiftPurchasesByGuestOptions = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readGuestsGiftPurchasesQueryKey = (
|
export const readGuestsGiftReservationsQueryKey = (
|
||||||
options?: Options<ReadGuestsGiftPurchasesData>,
|
options?: Options<ReadGuestsGiftReservationsData>,
|
||||||
) => createQueryKey("readGuestsGiftPurchases", options);
|
) => createQueryKey("readGuestsGiftReservations", options);
|
||||||
|
|
||||||
export const readGuestsGiftPurchasesOptions = (
|
export const readGuestsGiftReservationsOptions = (
|
||||||
options?: Options<ReadGuestsGiftPurchasesData>,
|
options?: Options<ReadGuestsGiftReservationsData>,
|
||||||
) => {
|
) => {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryFn: async ({ queryKey, signal }) => {
|
queryFn: async ({ queryKey, signal }) => {
|
||||||
const { data } = await readGuestsGiftPurchases({
|
const { data } = await readGuestsGiftReservations({
|
||||||
...options,
|
...options,
|
||||||
...queryKey[0],
|
...queryKey[0],
|
||||||
signal,
|
signal,
|
||||||
@@ -1440,7 +1440,7 @@ export const readGuestsGiftPurchasesOptions = (
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
queryKey: readGuestsGiftPurchasesQueryKey(options),
|
queryKey: readGuestsGiftReservationsQueryKey(options),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2427,6 +2427,16 @@ export const GuestReadSchema = {
|
|||||||
type: "string",
|
type: "string",
|
||||||
title: "Invitation Code",
|
title: "Invitation Code",
|
||||||
},
|
},
|
||||||
|
rsvp: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
$ref: "#/components/schemas/RSVPSchema",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "null",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
type: "object",
|
type: "object",
|
||||||
required: [
|
required: [
|
||||||
|
|||||||
@@ -142,8 +142,9 @@ import type {
|
|||||||
ReadGiftPurchasesByGuestData,
|
ReadGiftPurchasesByGuestData,
|
||||||
ReadGiftPurchasesByGuestResponse,
|
ReadGiftPurchasesByGuestResponse,
|
||||||
ReadGiftPurchasesByGuestError,
|
ReadGiftPurchasesByGuestError,
|
||||||
ReadGuestsGiftPurchasesData,
|
ReadGuestsGiftReservationsData,
|
||||||
ReadGuestsGiftPurchasesResponse,
|
ReadGuestsGiftReservationsResponse,
|
||||||
|
ReadGuestsGiftReservationsError,
|
||||||
CreateEventData,
|
CreateEventData,
|
||||||
CreateEventResponse,
|
CreateEventResponse,
|
||||||
CreateEventError,
|
CreateEventError,
|
||||||
@@ -1157,17 +1158,19 @@ export const readGiftPurchasesByGuest = <ThrowOnError extends boolean = false>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read All Guest Gift Reservations
|
* 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>(
|
export const readGuestsGiftReservations = <
|
||||||
options?: Options<ReadGuestsGiftPurchasesData, ThrowOnError>,
|
ThrowOnError extends boolean = false,
|
||||||
|
>(
|
||||||
|
options?: Options<ReadGuestsGiftReservationsData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).get<
|
return (options?.client ?? _heyApiClient).get<
|
||||||
ReadGuestsGiftPurchasesResponse,
|
ReadGuestsGiftReservationsResponse,
|
||||||
unknown,
|
ReadGuestsGiftReservationsError,
|
||||||
ThrowOnError
|
ThrowOnError
|
||||||
>({
|
>({
|
||||||
url: "/api/v1/events/gifts/purchases/guest/all",
|
url: "/api/v1/events/gifts/reservations/guests",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -370,6 +370,7 @@ export type GuestRead = {
|
|||||||
actual_additional_guests: number;
|
actual_additional_guests: number;
|
||||||
is_blocked: boolean;
|
is_blocked: boolean;
|
||||||
invitation_code: string;
|
invitation_code: string;
|
||||||
|
rsvp?: RsvpSchema | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GuestStatus =
|
export type GuestStatus =
|
||||||
@@ -1876,22 +1877,37 @@ export type ReadGiftPurchasesByGuestResponses = {
|
|||||||
export type ReadGiftPurchasesByGuestResponse =
|
export type ReadGiftPurchasesByGuestResponse =
|
||||||
ReadGiftPurchasesByGuestResponses[keyof ReadGiftPurchasesByGuestResponses];
|
ReadGiftPurchasesByGuestResponses[keyof ReadGiftPurchasesByGuestResponses];
|
||||||
|
|
||||||
export type ReadGuestsGiftPurchasesData = {
|
export type ReadGuestsGiftReservationsData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
query?: never;
|
query?: {
|
||||||
url: "/api/v1/events/gifts/purchases/guest/all";
|
/**
|
||||||
|
* 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
|
* Successful Response
|
||||||
*/
|
*/
|
||||||
200: Array<GiftPurchase>;
|
200: Array<GiftPurchase>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReadGuestsGiftPurchasesResponse =
|
export type ReadGuestsGiftReservationsResponse =
|
||||||
ReadGuestsGiftPurchasesResponses[keyof ReadGuestsGiftPurchasesResponses];
|
ReadGuestsGiftReservationsResponses[keyof ReadGuestsGiftReservationsResponses];
|
||||||
|
|
||||||
export type CreateEventData = {
|
export type CreateEventData = {
|
||||||
body: EventCreate;
|
body: EventCreate;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
full_name: "",
|
full_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
max_additional_guests: 0,
|
max_additional_guests: 10,
|
||||||
dietary_restrictions: "",
|
dietary_restrictions: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
can_bring_guests: true,
|
can_bring_guests: true,
|
||||||
@@ -293,7 +293,7 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
"Phone",
|
"Phone",
|
||||||
"Invitation Code",
|
"Invitation Code",
|
||||||
"Status",
|
"Status",
|
||||||
"Additional Guests",
|
"Max Additional Guests",
|
||||||
];
|
];
|
||||||
const csvContent = [
|
const csvContent = [
|
||||||
headers.join(","),
|
headers.join(","),
|
||||||
@@ -348,6 +348,12 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
}
|
}
|
||||||
}, [addGuestOpen, editGuestOpen, currentGuest]);
|
}, [addGuestOpen, editGuestOpen, currentGuest]);
|
||||||
|
|
||||||
|
const confirmedGuestCount =
|
||||||
|
guests?.filter((g) => g.status === GuestStatus.CONFIRMED).length || 0;
|
||||||
|
const confirmedAdditionalGuestsCount =
|
||||||
|
guests?.reduce((acc, g) => acc + (g.actual_additional_guests || 0), 0) || 0;
|
||||||
|
const totalConfirmedGuestsCount =
|
||||||
|
confirmedGuestCount + confirmedAdditionalGuestsCount;
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full">
|
||||||
{error && (
|
{error && (
|
||||||
@@ -414,14 +420,14 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<div className="grid grid-cols-4 items-center gap-4">
|
||||||
<Label htmlFor="max_additional_guests" className="text-right">
|
<Label htmlFor="max_additional_guests" className="text-right">
|
||||||
Additional Guests
|
Max Add. Guests
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="max_additional_guests"
|
id="max_additional_guests"
|
||||||
name="max_additional_guests"
|
name="max_additional_guests"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.max_additional_guests || 0}
|
value={formData.max_additional_guests || 10}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className="col-span-3"
|
className="col-span-3"
|
||||||
/>
|
/>
|
||||||
@@ -578,8 +584,8 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
|
|
||||||
{/* Dietary Restrictions Column */}
|
{/* Dietary Restrictions Column */}
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{guest.dietary_restrictions &&
|
{guest.rsvp?.dietary_requirements &&
|
||||||
guest.dietary_restrictions.length > 0 ? (
|
guest.rsvp.dietary_requirements.length > 0 ? (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -592,7 +598,7 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="max-w-xs">
|
<PopoverContent className="max-w-xs">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{guest.dietary_restrictions}
|
{guest.rsvp.dietary_requirements}
|
||||||
</p>
|
</p>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -603,7 +609,8 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
|
|
||||||
{/* Notes Column */}
|
{/* Notes Column */}
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{guest.notes && guest.notes.length > 0 ? (
|
{guest.rsvp?.response_message &&
|
||||||
|
guest.rsvp.response_message.length > 0 ? (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -615,7 +622,9 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="max-w-xs">
|
<PopoverContent className="max-w-xs">
|
||||||
<p className="text-sm">{guest.notes}</p>
|
<p className="text-sm">
|
||||||
|
{guest.rsvp.response_message}
|
||||||
|
</p>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
) : (
|
||||||
@@ -682,14 +691,9 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
Showing {filteredGuests.length} of {guests?.length || 0} guests
|
Showing {filteredGuests.length} of {guests?.length || 0} guests
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Total Confirmed:{" "}
|
Guests Confirmed: {confirmedGuestCount} | Additional Guests:{" "}
|
||||||
{guests?.filter((g) => g.status === GuestStatus.CONFIRMED).length ||
|
{confirmedAdditionalGuestsCount} | Total Guests:{" "}
|
||||||
0}{" "}
|
{totalConfirmedGuestsCount}
|
||||||
| Total Additional Guests:{" "}
|
|
||||||
{guests?.reduce(
|
|
||||||
(acc, g) => acc + (g.max_additional_guests || 0),
|
|
||||||
0,
|
|
||||||
) || 0}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -743,7 +747,7 @@ const GuestListTable = ({ event }: GuestListTableProps) => {
|
|||||||
htmlFor="edit_max_additional_guests"
|
htmlFor="edit_max_additional_guests"
|
||||||
className="text-right"
|
className="text-right"
|
||||||
>
|
>
|
||||||
Additional Guests
|
Max Add. Guests
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="edit_max_additional_guests"
|
id="edit_max_additional_guests"
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user