From 1af0eb131b2e169fa6edbdb351e34bd266551377 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 26 Mar 2026 20:53:40 -0400 Subject: [PATCH 1/7] feat: link frontend to backend for guests --- backend/docs/swagger.yaml | 73 +++++++--- backend/internal/handler/guests.go | 2 +- backend/internal/models/guests.go | 16 +- backend/internal/repository/guests.go | 5 +- clients/shared/src/index.ts | 10 ++ .../src/components/guests/GuestNotesCard.tsx | 6 +- .../components/guests/GuestProfileCard.tsx | 40 ++--- .../components/guests/GuestQuickListTable.tsx | 12 +- .../guests/GuestSpecialNeedsCard.tsx | 9 +- .../guests/HousekeepingPreferencesCard.tsx | 7 +- .../components/guests/PreviousStaysCard.tsx | 18 ++- .../web/src/components/guests/guest-mocks.ts | 137 ------------------ clients/web/src/hooks/use-debounce.ts | 12 ++ .../src/routes/_protected/guests.$guestId.tsx | 40 +++-- .../src/routes/_protected/guests.index.tsx | 99 ++++++++----- clients/web/src/utils/dates.ts | 7 + 16 files changed, 245 insertions(+), 248 deletions(-) delete mode 100644 clients/web/src/components/guests/guest-mocks.ts create mode 100644 clients/web/src/hooks/use-debounce.ts create mode 100644 clients/web/src/utils/dates.ts diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 29b13e059..2d8cfdc56 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -139,16 +139,12 @@ definitions: items: type: integer type: array - hotel_id: - type: string limit: maximum: 100 minimum: 1 type: integer search: type: string - required: - - hotel_id type: object GuestPage: properties: @@ -162,18 +158,25 @@ definitions: GuestWithBooking: properties: first_name: + example: Jane type: string floor: + example: 3 type: integer group_size: + example: 2 type: integer id: + example: 530e8400-e458-41d4-a716-446655440000 type: string last_name: + example: Doe type: string preferred_name: + example: Jane type: string room_number: + example: 301 type: integer required: - first_name @@ -504,41 +507,36 @@ paths: post: consumes: - application/json - description: Retrieves guests optionally filtered by floor + description: Creates a guest with the given data parameters: - - description: Hotel ID (UUID) - in: header - name: X-Hotel-ID - required: true - type: string - - description: Guest filters + - description: Guest data in: body - name: body + name: request required: true schema: - $ref: '#/definitions/GuestFilters' + $ref: '#/definitions/CreateGuest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/GuestPage' + $ref: '#/definitions/Guest' "400": - description: Bad Request + description: Invalid guest body format schema: additionalProperties: type: string type: object "500": - description: Internal Server Error + description: Internal server error schema: additionalProperties: type: string type: object security: - BearerAuth: [] - summary: Get Guests + summary: Creates a guest tags: - guests /api/v1/guests/{id}: @@ -626,6 +624,47 @@ paths: summary: Updates a guest tags: - guests + /api/v1/guests/search: + post: + consumes: + - application/json + description: Retrieves guests optionally filtered by floor + parameters: + - description: Hotel ID (UUID) + in: header + name: X-Hotel-ID + required: true + type: string + - description: Guest filters + in: body + name: body + required: true + schema: + $ref: '#/definitions/GuestFilters' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/GuestPage' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Get Guests + tags: + - guests /api/v1/guests/stays/{id}: get: consumes: diff --git a/backend/internal/handler/guests.go b/backend/internal/handler/guests.go index ccaa99ec8..31cc6b538 100644 --- a/backend/internal/handler/guests.go +++ b/backend/internal/handler/guests.go @@ -168,7 +168,7 @@ func (h *GuestsHandler) UpdateGuest(c *fiber.Ctx) error { // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Security BearerAuth -// @Router /api/v1/guests [post] +// @Router /api/v1/guests/search [post] func (h *GuestsHandler) GetGuests(c *fiber.Ctx) error { hotelID := c.Get("X-Hotel-ID") var filters models.GuestFilters diff --git a/backend/internal/models/guests.go b/backend/internal/models/guests.go index e105336ee..a79c3b5a9 100644 --- a/backend/internal/models/guests.go +++ b/backend/internal/models/guests.go @@ -24,7 +24,7 @@ type Guest struct { } //@name Guest type GuestFilters struct { - HotelID string `json:"hotel_id" validate:"required,uuid"` + HotelID string `json:"hotel_id" validate:"required,uuid" swaggerignore:"true"` Floors []int `json:"floors"` GroupSize []int `json:"group_size"` Search string `json:"search"` @@ -40,13 +40,13 @@ type GuestPage struct { } // @name GuestPage type GuestWithBooking struct { - ID string `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - PreferredName string `json:"preferred_name"` - Floor int `json:"floor"` - RoomNumber int `json:"room_number"` - GroupSize *int `json:"group_size"` + ID string `json:"id" validate:"required" example:"530e8400-e458-41d4-a716-446655440000"` + FirstName string `json:"first_name" validate:"required" example:"Jane"` + LastName string `json:"last_name" validate:"required" example:"Doe"` + PreferredName string `json:"preferred_name" validate:"required" example:"Jane"` + Floor int `json:"floor" validate:"required" example:"3"` + RoomNumber int `json:"room_number" validate:"required" example:"301"` + GroupSize *int `json:"group_size" example:"2"` } // @name GuestWithBooking type GuestWithStays struct { diff --git a/backend/internal/repository/guests.go b/backend/internal/repository/guests.go index 7cdd65988..3588aca3b 100644 --- a/backend/internal/repository/guests.go +++ b/backend/internal/repository/guests.go @@ -108,7 +108,10 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri var status *models.BookingStatus if guest == nil { - guest = &models.GuestWithStays{} + guest = &models.GuestWithStays{ + CurrentStays: []models.Stay{}, + PastStays: []models.Stay{}, + } } err := rows.Scan( diff --git a/clients/shared/src/index.ts b/clients/shared/src/index.ts index 5f4f8a62a..b06ffef52 100644 --- a/clients/shared/src/index.ts +++ b/clients/shared/src/index.ts @@ -45,8 +45,18 @@ export { usePostApiV1Guests, useGetApiV1GuestsId, usePutApiV1GuestsId, + usePostApiV1GuestsSearchHook, + useGetApiV1GuestsStaysId, } from "./api/generated/endpoints/guests/guests"; +export type { + GuestWithBooking, + GuestWithStays, + GuestPage, + GuestFilters, + Stay, +} from "./api/generated/models"; + export { usePostRooms, useGetRoomsFloors } from "./api/generated/endpoints/rooms/rooms"; export type { diff --git a/clients/web/src/components/guests/GuestNotesCard.tsx b/clients/web/src/components/guests/GuestNotesCard.tsx index 1e4846c22..c71fec2af 100644 --- a/clients/web/src/components/guests/GuestNotesCard.tsx +++ b/clients/web/src/components/guests/GuestNotesCard.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; type GuestNotesCardProps = { - initialNotes: string; + initialNotes?: string; }; export function GuestNotesCard({ initialNotes }: GuestNotesCardProps) { const [isEditing, setIsEditing] = useState(false); - const [notes, setNotes] = useState(initialNotes); - const [draft, setDraft] = useState(initialNotes); + const [notes, setNotes] = useState(initialNotes ?? ""); + const [draft, setDraft] = useState(initialNotes ?? ""); const startEditing = () => { setDraft(notes); diff --git a/clients/web/src/components/guests/GuestProfileCard.tsx b/clients/web/src/components/guests/GuestProfileCard.tsx index b1460b32a..0f64a897c 100644 --- a/clients/web/src/components/guests/GuestProfileCard.tsx +++ b/clients/web/src/components/guests/GuestProfileCard.tsx @@ -1,8 +1,9 @@ +import type { GuestWithStays } from "@shared"; import { UserRound } from "lucide-react"; -import type { GuestProfile } from "./guest-mocks"; +import { formatDate } from "../../utils/dates"; type GuestProfileCardProps = { - guest: GuestProfile; + guest: GuestWithStays; }; function DetailRow({ label, value }: { label: string; value: string }) { @@ -15,6 +16,8 @@ function DetailRow({ label, value }: { label: string; value: string }) { } export function GuestProfileCard({ guest }: GuestProfileCardProps) { + const currentStay = (guest.current_stays ?? [])[0]; + return (
@@ -23,28 +26,27 @@ export function GuestProfileCard({ guest }: GuestProfileCardProps) {

- {guest.preferredName} + {guest.first_name} {guest.last_name}

-

{guest.pronouns}

-
- - -
-
- - - - + {currentStay ? ( + <> + + + + + ) : ( +

No active stay.

+ )}
); diff --git a/clients/web/src/components/guests/GuestQuickListTable.tsx b/clients/web/src/components/guests/GuestQuickListTable.tsx index 3157e94b4..a3f395ff5 100644 --- a/clients/web/src/components/guests/GuestQuickListTable.tsx +++ b/clients/web/src/components/guests/GuestQuickListTable.tsx @@ -1,8 +1,8 @@ +import type { GuestWithBooking } from "@shared"; import { UserRound } from "lucide-react"; -import type { GuestListItem } from "./guest-mocks"; type GuestQuickListTableProps = { - guests: Array; + guests: Array; groupFilter: string; floorFilter: string; onGroupFilterChange: (value: string) => void; @@ -68,14 +68,14 @@ export function GuestQuickListTable({ > {avatarPill()}

- {guest.governmentName} + {guest.first_name} {guest.last_name}

- {guest.preferredName} + {guest.preferred_name}

-

{guest.groupSize}

+

{guest.group_size ?? "—"}

{guest.floor}

-

{guest.room}

+

{guest.room_number}

))} {guests.length === 0 && ( diff --git a/clients/web/src/components/guests/GuestSpecialNeedsCard.tsx b/clients/web/src/components/guests/GuestSpecialNeedsCard.tsx index d76ff5cf8..a1478e60f 100644 --- a/clients/web/src/components/guests/GuestSpecialNeedsCard.tsx +++ b/clients/web/src/components/guests/GuestSpecialNeedsCard.tsx @@ -1,7 +1,12 @@ -import type { GuestProfile } from "./guest-mocks"; +type SpecialNeeds = { + dietaryRestrictions: string; + accessibilityNeeds: string; + sensorySensitivities: string; + medicalConditions: string; +}; type GuestSpecialNeedsCardProps = { - specialNeeds: GuestProfile["specialNeeds"]; + specialNeeds: SpecialNeeds; }; function SpecialNeedsRow({ label, value }: { label: string; value: string }) { diff --git a/clients/web/src/components/guests/HousekeepingPreferencesCard.tsx b/clients/web/src/components/guests/HousekeepingPreferencesCard.tsx index 96609874e..418f9c02f 100644 --- a/clients/web/src/components/guests/HousekeepingPreferencesCard.tsx +++ b/clients/web/src/components/guests/HousekeepingPreferencesCard.tsx @@ -1,7 +1,10 @@ -import type { GuestProfile } from "./guest-mocks"; +type HousekeepingPreferences = { + frequency: string; + doNotDisturb: string; +}; type HousekeepingPreferencesCardProps = { - housekeeping: GuestProfile["housekeeping"]; + housekeeping: HousekeepingPreferences; }; function PreferenceRow({ label, value }: { label: string; value: string }) { diff --git a/clients/web/src/components/guests/PreviousStaysCard.tsx b/clients/web/src/components/guests/PreviousStaysCard.tsx index 2a2c9766d..ea2f27929 100644 --- a/clients/web/src/components/guests/PreviousStaysCard.tsx +++ b/clients/web/src/components/guests/PreviousStaysCard.tsx @@ -1,7 +1,8 @@ -import type { PreviousStay } from "./guest-mocks"; +import type { Stay } from "@shared"; +import { formatDate } from "../../utils/dates"; type PreviousStaysCardProps = { - stays: Array; + stays: Array; }; export function PreviousStaysCard({ stays }: PreviousStaysCardProps) { @@ -11,19 +12,20 @@ export function PreviousStaysCard({ stays }: PreviousStaysCardProps) { Previous Stays
- {stays.map((stay) => ( + {stays.map((stay, index) => (

- {stay.startDate} - {stay.endDate} -

-

- {stay.room} | Group size: {stay.groupSize} + {formatDate(stay.arrival_date)} - {formatDate(stay.departure_date)}

+

Room {stay.room_number}

))} + {stays.length === 0 && ( +

No previous stays.

+ )}
); diff --git a/clients/web/src/components/guests/guest-mocks.ts b/clients/web/src/components/guests/guest-mocks.ts deleted file mode 100644 index ae928ce48..000000000 --- a/clients/web/src/components/guests/guest-mocks.ts +++ /dev/null @@ -1,137 +0,0 @@ -export type GuestListItem = { - id: string; - governmentName: string; - preferredName: string; - groupSize: number; - floor: number; - room: string; -}; - -export type PreviousStay = { - id: string; - startDate: string; - endDate: string; - room: string; - groupSize: number; -}; - -export type GuestProfile = { - id: string; - governmentName: string; - preferredName: string; - pronouns: string; - dateOfBirth: string; - room: string; - groupSize: number; - arrivalTime: string; - arrivalDate: string; - departureTime: string; - departureDate: string; - notes: string; - specialNeeds: { - dietaryRestrictions: string; - accessibilityNeeds: string; - sensorySensitivities: string; - medicalConditions: string; - }; - previousStays: Array; - housekeeping: { - frequency: string; - doNotDisturb: string; - }; -}; - -export const guestListItems: Array = [ - { - id: "monkey-d-luffy", - governmentName: "Monkey D. Luffy", - preferredName: "Luffy", - groupSize: 5, - floor: 3, - room: "Suite 300", - }, - { - id: "roronoa-zoro", - governmentName: "Roronoa Zoro", - preferredName: "Zoro", - groupSize: 4, - floor: 4, - room: "Suite 401", - }, - { - id: "nami", - governmentName: "Nami", - preferredName: "Nami", - groupSize: 2, - floor: 2, - room: "Suite 203", - }, - { - id: "nico-robin", - governmentName: "Nico Robin", - preferredName: "Robin", - groupSize: 3, - floor: 5, - room: "Suite 502", - }, - { - id: "usopp", - governmentName: "Usopp", - preferredName: "Usopp", - groupSize: 6, - floor: 3, - room: "Suite 318", - }, - { - id: "sanji", - governmentName: "Vinsmoke Sanji", - preferredName: "Sanji", - groupSize: 1, - floor: 1, - room: "Suite 102", - }, -]; - -export const guestProfilesById: Record = { - "monkey-d-luffy": { - id: "monkey-d-luffy", - governmentName: "Monkey D. Luffy", - preferredName: "Luffy", - pronouns: "he/him", - dateOfBirth: "03/21/2005", - room: "Suite 300", - groupSize: 5, - arrivalTime: "11:00 AM", - arrivalDate: "01/25/2026", - departureTime: "11:00 AM", - departureDate: "02/04/2026", - notes: - '"Wealth, fame, power... Gold Roger, the King of the Pirates, obtained this and everything else the world had to offer and his dying words drove countless souls to the seas.""You want my treasure? You can have it! I left everything I gathered together in one place! Now you just have to find it!""These words lured men to the Grand Line. In pursuit of dreams greather than theyve ever dared to imagine! This is the time known as the Great Pirate Era!"', - specialNeeds: { - dietaryRestrictions: "", - accessibilityNeeds: "", - sensorySensitivities: "", - medicalConditions: "", - }, - previousStays: [ - { - id: "stay-1", - startDate: "05/12/2024", - endDate: "05/20/2024", - room: "Suite 401", - groupSize: 5, - }, - { - id: "stay-2", - startDate: "12/04/2023", - endDate: "12/11/2023", - room: "Suite 318", - groupSize: 4, - }, - ], - housekeeping: { - frequency: "Daily", - doNotDisturb: "6:00 PM - 10:00 AM", - }, - }, -}; diff --git a/clients/web/src/hooks/use-debounce.ts b/clients/web/src/hooks/use-debounce.ts new file mode 100644 index 000000000..7bb0e18e7 --- /dev/null +++ b/clients/web/src/hooks/use-debounce.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay: number): T { + const [debounced, setDebounced] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => setDebounced(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return debounced; +} diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index 42dd4ae9c..1ed02d93c 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,3 +1,4 @@ +import { useGetApiV1GuestsStaysId } from "@shared/api/generated/endpoints/guests/guests"; import { Link, createFileRoute } from "@tanstack/react-router"; import { GuestNotesCard } from "../../components/guests/GuestNotesCard"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; @@ -5,17 +6,38 @@ import { GuestProfileCard } from "../../components/guests/GuestProfileCard"; import { GuestSpecialNeedsCard } from "../../components/guests/GuestSpecialNeedsCard"; import { HousekeepingPreferencesCard } from "../../components/guests/HousekeepingPreferencesCard"; import { PreviousStaysCard } from "../../components/guests/PreviousStaysCard"; -import { guestProfilesById } from "../../components/guests/guest-mocks"; export const Route = createFileRoute("/_protected/guests/$guestId")({ component: GuestProfilePage, }); +const emptySpecialNeeds = { + dietaryRestrictions: "", + accessibilityNeeds: "", + sensorySensitivities: "", + medicalConditions: "", +}; + +const emptyHousekeeping = { + frequency: "", + doNotDisturb: "", +}; + function GuestProfilePage() { const { guestId } = Route.useParams(); - const guestProfile = guestProfilesById[guestId]; + const { data: guest, isLoading, isError } = useGetApiV1GuestsStaysId(guestId); + + if (isLoading) { + return ( + +
+ Loading guest profile... +
+
+ ); + } - if (!guestProfile) { + if (isError || !guest) { return (
@@ -35,15 +57,13 @@ function GuestProfilePage() {
- - + +
- - - + + +
diff --git a/clients/web/src/routes/_protected/guests.index.tsx b/clients/web/src/routes/_protected/guests.index.tsx index 53a83aa81..ab7f05d44 100644 --- a/clients/web/src/routes/_protected/guests.index.tsx +++ b/clients/web/src/routes/_protected/guests.index.tsx @@ -1,58 +1,89 @@ +import { usePostApiV1GuestsSearchHook } from "@shared/api/generated/endpoints/guests/guests"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { useMemo, useState } from "react"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useState } from "react"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; import { GuestQuickListTable } from "../../components/guests/GuestQuickListTable"; import { GuestSearchBar } from "../../components/guests/GuestSearchBar"; -import { guestListItems } from "../../components/guests/guest-mocks"; +import { useDebounce } from "../../hooks/use-debounce"; export const Route = createFileRoute("/_protected/guests/")({ component: GuestsQuickListPage, }); +function groupSizeFilter(filter: string): number[] | undefined { + if (filter === "1-2") return [1, 2]; + if (filter === "3-4") return [3, 4]; + if (filter === "5+") + return [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + return undefined; +} + function GuestsQuickListPage() { const navigate = useNavigate(); const [searchTerm, setSearchTerm] = useState(""); const [groupFilter, setGroupFilter] = useState("all"); const [floorFilter, setFloorFilter] = useState("all"); - const filteredGuests = useMemo(() => { - const query = searchTerm.trim().toLowerCase(); - - return guestListItems.filter((guest) => { - const matchesSearch = - query.length === 0 || - guest.governmentName.toLowerCase().includes(query) || - guest.preferredName.toLowerCase().includes(query) || - guest.room.toLowerCase().includes(query); - - const matchesGroup = - groupFilter === "all" || - (groupFilter === "1-2" && guest.groupSize <= 2) || - (groupFilter === "3-4" && - guest.groupSize >= 3 && - guest.groupSize <= 4) || - (groupFilter === "5+" && guest.groupSize >= 5); - - const matchesFloor = - floorFilter === "all" || guest.floor === Number(floorFilter); + const debouncedSearch = useDebounce(searchTerm, 300); + const postGuests = usePostApiV1GuestsSearchHook(); - return matchesSearch && matchesGroup && matchesFloor; + const { data, fetchNextPage, hasNextPage, isFetching, isLoading, isError } = + useInfiniteQuery({ + queryKey: ["guests", debouncedSearch, floorFilter, groupFilter], + queryFn: ({ pageParam }: { pageParam: string | undefined }) => + postGuests({ + search: debouncedSearch || undefined, + floors: floorFilter !== "all" ? [Number(floorFilter)] : undefined, + group_size: groupSizeFilter(groupFilter), + cursor: pageParam, + limit: 20, + }), + initialPageParam: undefined as string | undefined, + getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined, }); - }, [floorFilter, groupFilter, searchTerm]); + + const allGuests = data?.pages.flatMap((page) => page.data ?? []) ?? []; return ( - - navigate({ to: "/guests/$guestId", params: { guestId } }) - } - /> + + {isError ? ( +
+ Failed to load guests. Please try again. +
+ ) : ( + <> + + navigate({ to: "/guests/$guestId", params: { guestId } }) + } + /> + + {isLoading && ( +
+ Loading guests... +
+ )} + + {hasNextPage && !isLoading && ( + + )} + + )}
); } diff --git a/clients/web/src/utils/dates.ts b/clients/web/src/utils/dates.ts new file mode 100644 index 000000000..dbe474414 --- /dev/null +++ b/clients/web/src/utils/dates.ts @@ -0,0 +1,7 @@ +export function formatDate(iso: string): string { + return new Date(iso).toLocaleDateString("en-US", { + month: "2-digit", + day: "2-digit", + year: "numeric", + }); +} From ec72610feec29d673e09c5537cb9d60db0fa0cfd Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Tue, 31 Mar 2026 17:58:29 -0400 Subject: [PATCH 2/7] fix: Update group size filtering, date rendering, added tests --- .../components/guests/GuestProfileCard.tsx | 7 +-- .../components/guests/GuestQuickListTable.tsx | 8 +-- .../components/guests/PreviousStaysCard.tsx | 2 +- .../src/routes/_protected/guests.$guestId.tsx | 4 +- .../src/routes/_protected/guests.index.tsx | 4 +- clients/web/src/tests/guests-ui.test.tsx | 53 +++++++++++++++++++ clients/web/src/utils/dates.ts | 13 +++-- 7 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 clients/web/src/tests/guests-ui.test.tsx diff --git a/clients/web/src/components/guests/GuestProfileCard.tsx b/clients/web/src/components/guests/GuestProfileCard.tsx index 0f64a897c..ba5d4b6aa 100644 --- a/clients/web/src/components/guests/GuestProfileCard.tsx +++ b/clients/web/src/components/guests/GuestProfileCard.tsx @@ -1,6 +1,6 @@ -import type { GuestWithStays } from "@shared"; import { UserRound } from "lucide-react"; import { formatDate } from "../../utils/dates"; +import type { GuestWithStays } from "@shared"; type GuestProfileCardProps = { guest: GuestWithStays; @@ -16,7 +16,8 @@ function DetailRow({ label, value }: { label: string; value: string }) { } export function GuestProfileCard({ guest }: GuestProfileCardProps) { - const currentStay = (guest.current_stays ?? [])[0]; + const hasCurrentStay = guest.current_stays.length > 0; + const currentStay = guest.current_stays[0]; return (
@@ -32,7 +33,7 @@ export function GuestProfileCard({ guest }: GuestProfileCardProps) {
- {currentStay ? ( + {hasCurrentStay ? ( <> ; groupFilter: string; floorFilter: string; + isLoading?: boolean; onGroupFilterChange: (value: string) => void; onFloorFilterChange: (value: string) => void; onGuestClick: (guestId: string) => void; @@ -22,6 +23,7 @@ export function GuestQuickListTable({ guests, groupFilter, floorFilter, + isLoading = false, onGroupFilterChange, onFloorFilterChange, onGuestClick, @@ -40,7 +42,7 @@ export function GuestQuickListTable({ - + onGroupFilterChange(event.target.value)} - className="h-[3vh] min-h-[3vh] border border-black bg-white px-[1vw] text-[1vw]" - aria-label="Group filter" - > - - - - - - -

Room

+
+

Guest

+

Active Bookings

+

Group Size

+

Specific Assistance

-
+
{guests.map((guest) => ( ))} {!isLoading && guests.length === 0 && ( -
+
No guests match your current filters.
)} diff --git a/clients/web/src/components/guests/GuestSearchBar.tsx b/clients/web/src/components/guests/GuestSearchBar.tsx index edd35a1cf..7187073eb 100644 --- a/clients/web/src/components/guests/GuestSearchBar.tsx +++ b/clients/web/src/components/guests/GuestSearchBar.tsx @@ -7,15 +7,15 @@ type GuestSearchBarProps = { export function GuestSearchBar({ value, onChange }: GuestSearchBarProps) { return ( -