From 1af0eb131b2e169fa6edbdb351e34bd266551377 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 26 Mar 2026 20:53:40 -0400 Subject: [PATCH 01/22] 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 02/22] 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({ - +

Room

diff --git a/clients/web/src/routes/_protected/guests.index.tsx b/clients/web/src/routes/_protected/guests.index.tsx index 82fa243ef..abbd9da43 100644 --- a/clients/web/src/routes/_protected/guests.index.tsx +++ b/clients/web/src/routes/_protected/guests.index.tsx @@ -1,3 +1,7 @@ +import { + useGetGuestBookingsGroupSizes, + useGetRoomsFloors, +} from "@shared"; import { usePostApiV1GuestsSearchHook } from "@shared/api/generated/endpoints/guests/guests"; import { useInfiniteQuery } from "@tanstack/react-query"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; @@ -15,15 +19,6 @@ export const Route = createFileRoute("/_protected/guests/")({ component: GuestsQuickListPage, }); -function groupSizeFilter(filter: string): Array | undefined { - if (filter === "1-2") return [1, 2]; - if (filter === "3-4") return [3, 4]; - // Product decision: the largest group filter bucket is capped at 20. - 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(""); @@ -38,6 +33,11 @@ function GuestsQuickListPage() { const debouncedSearch = useDebounce(searchTerm, 300); const postGuests = usePostApiV1GuestsSearchHook(); + const { data: floorsData } = useGetRoomsFloors(); + const { data: groupSizesData } = useGetGuestBookingsGroupSizes(); + + const availableFloors = floorsData ?? []; + const availableGroupSizes = groupSizesData ?? []; const { data, fetchNextPage, hasNextPage, isFetching, isLoading, isError } = useInfiniteQuery({ @@ -46,7 +46,7 @@ function GuestsQuickListPage() { postGuests({ search: debouncedSearch || undefined, floors: floorFilter !== "all" ? [Number(floorFilter)] : undefined, - group_size: groupSizeFilter(groupFilter), + group_size: groupFilter !== "all" ? [Number(groupFilter)] : undefined, cursor: pageParam, limit: 20, }), @@ -68,6 +68,8 @@ function GuestsQuickListPage() { <> { render( { ); }); + it("renders backend-provided filter options", () => { + render( + {}} + onFloorFilterChange={() => {}} + onGuestClick={() => {}} + />, + ); + + expect(screen.getByRole("option", { name: "2" })).not.toBe(null); + expect(screen.getByRole("option", { name: "6" })).not.toBe(null); + }); + it("renders an em dash for a null group size", () => { const guest = { id: "guest-1", @@ -46,6 +66,8 @@ describe("guest UI helpers", () => { render( {}} From 3c59258d60523c5245d5273b35646e67a50b3daf Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 02:12:55 -0400 Subject: [PATCH 10/22] feat: Persist notes --- .../src/components/guests/GuestNotesCard.tsx | 20 ++++++++++------ .../src/routes/_protected/guests.$guestId.tsx | 24 ++++++++++++++++--- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/clients/web/src/components/guests/GuestNotesCard.tsx b/clients/web/src/components/guests/GuestNotesCard.tsx index 48d06b95c..7f55074f4 100644 --- a/clients/web/src/components/guests/GuestNotesCard.tsx +++ b/clients/web/src/components/guests/GuestNotesCard.tsx @@ -1,13 +1,19 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; type GuestNotesCardProps = { - initialNotes?: string; + notes?: string; + onSave: (nextNotes: string) => Promise | void; }; -export function GuestNotesCard({ initialNotes = "" }: GuestNotesCardProps) { +export function GuestNotesCard({ notes = "", onSave }: GuestNotesCardProps) { const [isEditing, setIsEditing] = useState(false); - const [notes, setNotes] = useState(initialNotes); - const [draft, setDraft] = useState(initialNotes); + const [draft, setDraft] = useState(notes); + + useEffect(() => { + if (!isEditing) { + setDraft(notes); + } + }, [isEditing, notes]); const startEditing = () => { setDraft(notes); @@ -19,8 +25,8 @@ export function GuestNotesCard({ initialNotes = "" }: GuestNotesCardProps) { setIsEditing(false); }; - const saveNotes = () => { - setNotes(draft); + const saveNotes = async () => { + await onSave(draft); setIsEditing(false); }; diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index f64c5b221..82711f883 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,4 +1,7 @@ -import { useGetApiV1GuestsStaysId } from "@shared/api/generated/endpoints/guests/guests"; +import { + useGetGuestsStaysId, + usePutApiV1GuestsId, +} 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"; @@ -12,7 +15,22 @@ export const Route = createFileRoute("/_protected/guests/$guestId")({ function GuestProfilePage() { const { guestId } = Route.useParams(); - const { data: guest, isLoading, isError } = useGetApiV1GuestsStaysId(guestId); + const { + data: guest, + isLoading, + isError, + refetch, + } = useGetGuestsStaysId(guestId); + const updateGuest = usePutApiV1GuestsId(); + + const handleSaveNotes = async (notes: string) => { + await updateGuest.mutateAsync({ + id: guestId, + data: { notes }, + }); + + await refetch(); + }; if (isLoading) { return ( @@ -43,7 +61,7 @@ function GuestProfilePage() {
- +
From cfd8367062fe236e6d019d71d5ad746b012c0699 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 02:46:07 -0400 Subject: [PATCH 11/22] feat: fix failed to get get guests bug --- backend/internal/handler/guests.go | 1 + backend/internal/models/guests.go | 4 +- backend/internal/repository/guests.go | 70 +++++++++++++++---- .../components/guests/GuestProfileCard.tsx | 14 ++++ .../guests/GuestSpecialNeedsCard.tsx | 5 -- .../src/routes/_protected/guests.$guestId.tsx | 28 +++++++- clients/web/src/tests/guests-ui.test.tsx | 41 +++++++++++ 7 files changed, 139 insertions(+), 24 deletions(-) diff --git a/backend/internal/handler/guests.go b/backend/internal/handler/guests.go index 513d4b598..01c18ede7 100644 --- a/backend/internal/handler/guests.go +++ b/backend/internal/handler/guests.go @@ -116,6 +116,7 @@ func (h *GuestsHandler) GetGuestWithStays(c *fiber.Ctx) error { return errs.NotFound("guest", "id", id) } + slog.Error("failed to get guest with stays", "id", id, "error", err) return errs.InternalServerError() } return c.JSON(guest) diff --git a/backend/internal/models/guests.go b/backend/internal/models/guests.go index 365675534..14809c9ac 100644 --- a/backend/internal/models/guests.go +++ b/backend/internal/models/guests.go @@ -83,8 +83,8 @@ type GuestWithStays struct { Preferences *string `json:"preferences,omitempty" example:"extra pillows"` Notes *string `json:"notes,omitempty" example:"VIP"` Pronouns *string `json:"pronouns,omitempty" example:"she/her"` - DoNotDisturbStart *time.Time `json:"do_not_disturb_start,omitempty" example:"17:00:00"` - DoNotDisturbEnd *time.Time `json:"do_not_disturb_end,omitempty" example:"07:00:00"` + DoNotDisturbStart *string `json:"do_not_disturb_start,omitempty" example:"17:00:00"` + DoNotDisturbEnd *string `json:"do_not_disturb_end,omitempty" example:"07:00:00"` HousekeepingCadence *string `json:"housekeeping_cadence,omitempty" example:"daily"` Assistance *Assistance `json:"assistance,omitempty"` CurrentStays []Stay `json:"current_stays" validate:"required"` diff --git a/backend/internal/repository/guests.go b/backend/internal/repository/guests.go index bdbcf9795..274a2a0f9 100644 --- a/backend/internal/repository/guests.go +++ b/backend/internal/repository/guests.go @@ -2,7 +2,9 @@ package repository import ( "context" + "encoding/json" "errors" + "fmt" "iter" "sort" "time" @@ -11,6 +13,7 @@ import ( "github.com/generate/selfserve/internal/models" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" ) @@ -89,6 +92,8 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri CurrentStays: []models.Stay{}, PastStays: []models.Stay{}, } + var doNotDisturbStart, doNotDisturbEnd pgtype.Time + var assistanceRaw []byte err := r.db.QueryRow(ctx, ` SELECT @@ -99,8 +104,8 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri WHERE g.id = $1 `, id).Scan( &guest.ID, &guest.FirstName, &guest.LastName, &guest.Phone, &guest.Email, - &guest.Preferences, &guest.Notes, &guest.Pronouns, &guest.DoNotDisturbStart, - &guest.DoNotDisturbEnd, &guest.HousekeepingCadence, &guest.Assistance, + &guest.Preferences, &guest.Notes, &guest.Pronouns, &doNotDisturbStart, + &doNotDisturbEnd, &guest.HousekeepingCadence, &assistanceRaw, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { @@ -109,6 +114,17 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri return nil, err } + guest.DoNotDisturbStart = formatPGTime(doNotDisturbStart) + guest.DoNotDisturbEnd = formatPGTime(doNotDisturbEnd) + + if len(assistanceRaw) > 0 && string(assistanceRaw) != "null" { + var assistance models.Assistance + if err := json.Unmarshal(assistanceRaw, &assistance); err != nil { + return nil, err + } + guest.Assistance = &assistance + } + rows, err := r.db.Query(ctx, ` SELECT gb.arrival_date, gb.departure_date, rm.room_number, gb.status, gb.group_size FROM guest_bookings gb @@ -131,34 +147,42 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri func loadGuestStayHistory(guest *models.GuestWithStays, rows pgx.Rows) error { for rows.Next() { - var arrivalDate, departureDate *time.Time - var roomNumber, groupSize *int - var status *models.BookingStatus + var arrivalDate, departureDate pgtype.Date + var roomNumber, groupSize pgtype.Int4 + var status string if err := rows.Scan(&arrivalDate, &departureDate, &roomNumber, &status, &groupSize); err != nil { return err } - if arrivalDate == nil || departureDate == nil || roomNumber == nil || status == nil { + if !arrivalDate.Valid || !departureDate.Valid || !roomNumber.Valid || status == "" { continue } - stay := buildStay(arrivalDate, departureDate, roomNumber, groupSize, status) - appendStay(guest, stay, *status) + stayStatus := models.BookingStatus(status) + stay := buildStay(arrivalDate, departureDate, roomNumber, groupSize, stayStatus) + appendStay(guest, stay, stayStatus) } return rows.Err() } -func buildStay(arrival, departure *time.Time, roomNumber, groupSize *int, status *models.BookingStatus) models.Stay { +func buildStay( + arrival pgtype.Date, + departure pgtype.Date, + roomNumber pgtype.Int4, + groupSize pgtype.Int4, + status models.BookingStatus, +) models.Stay { stay := models.Stay{ - ArrivalDate: *arrival, - DepartureDate: *departure, - RoomNumber: *roomNumber, - Status: *status, + ArrivalDate: arrival.Time, + DepartureDate: departure.Time, + RoomNumber: int(roomNumber.Int32), + Status: status, } - if groupSize != nil { - stay.GroupSize = groupSize + if groupSize.Valid { + value := int(groupSize.Int32) + stay.GroupSize = &value } return stay } @@ -183,6 +207,22 @@ func sortGuestStays(guest *models.GuestWithStays) { }) } +func formatPGTime(value pgtype.Time) *string { + if !value.Valid { + return nil + } + + duration := time.Duration(value.Microseconds) * time.Microsecond + hours := int(duration / time.Hour) + duration -= time.Duration(hours) * time.Hour + minutes := int(duration / time.Minute) + duration -= time.Duration(minutes) * time.Minute + seconds := int(duration / time.Second) + + formatted := fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) + return &formatted +} + func (r *GuestsRepository) UpdateGuest(ctx context.Context, id string, update *models.UpdateGuest) (*models.Guest, error) { var guest models.Guest diff --git a/clients/web/src/components/guests/GuestProfileCard.tsx b/clients/web/src/components/guests/GuestProfileCard.tsx index 169aa8e4c..ecf4f73fe 100644 --- a/clients/web/src/components/guests/GuestProfileCard.tsx +++ b/clients/web/src/components/guests/GuestProfileCard.tsx @@ -18,6 +18,9 @@ function DetailRow({ label, value }: { label: string; value: string }) { export function GuestProfileCard({ guest }: GuestProfileCardProps) { const hasCurrentStay = guest.current_stays.length > 0; const currentStay = guest.current_stays[0]; + const phone = guest.phone?.trim() || "-"; + const email = guest.email?.trim() || "-"; + const pronouns = guest.pronouns?.trim() || "-"; return (
@@ -33,9 +36,20 @@ export function GuestProfileCard({ guest }: GuestProfileCardProps) {
+ + + {hasCurrentStay ? ( <> + - @@ -44,7 +53,7 @@ function GuestProfilePage() { return (
-

Guest not found.

+

{detailErrorMessage}

@@ -64,6 +86,8 @@ function GuestProfilePage() {
+ +
diff --git a/clients/web/src/tests/guests-ui.test.tsx b/clients/web/src/tests/guests-ui.test.tsx index b679ba269..949af3889 100644 --- a/clients/web/src/tests/guests-ui.test.tsx +++ b/clients/web/src/tests/guests-ui.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import type { GuestWithBooking } from "@shared"; import { GuestProfilePageSkeleton } from "../components/guests/GuestProfilePageSkeleton"; +import { GuestProfileCard } from "../components/guests/GuestProfileCard"; import { GuestQuickListTable } from "../components/guests/GuestQuickListTable"; import { formatDate } from "../utils/dates"; @@ -80,6 +81,46 @@ describe("guest UI helpers", () => { }); }); + describe("GuestProfileCard", () => { + it("renders current backend guest fields", () => { + render( + , + ); + + expect(screen.getByText("Jane Doe")).not.toBe(null); + expect(screen.getByText("+1 555 111 2222")).not.toBe(null); + expect(screen.getByText("jane@example.com")).not.toBe(null); + expect(screen.getByText("she/her")).not.toBe(null); + expect(screen.getByText("301")).not.toBe(null); + expect(screen.getByText("2")).not.toBe(null); + }); + }); + describe("GuestProfilePageSkeleton", () => { it("renders multiple skeleton placeholders for the profile page", () => { const { container } = render(); From 9ad49e59308db4bab843c3dc7b059fe69a833e5b Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 03:02:53 -0400 Subject: [PATCH 12/22] style: fix styling --- clients/web/src/components/guests/GuestQuickListTable.tsx | 4 ++-- clients/web/src/routes/_protected/guests.$guestId.tsx | 6 +----- clients/web/src/routes/_protected/guests.index.tsx | 2 +- clients/web/src/tests/guests-ui.test.tsx | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/clients/web/src/components/guests/GuestQuickListTable.tsx b/clients/web/src/components/guests/GuestQuickListTable.tsx index e4ac0f5af..e7994e107 100644 --- a/clients/web/src/components/guests/GuestQuickListTable.tsx +++ b/clients/web/src/components/guests/GuestQuickListTable.tsx @@ -3,8 +3,8 @@ import type { GuestWithBooking } from "@shared"; type GuestQuickListTableProps = { guests: Array; - floorOptions: number[]; - groupSizeOptions: number[]; + floorOptions: Array; + groupSizeOptions: Array; groupFilter: string; floorFilter: string; isLoading?: boolean; diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index 242429c37..fdbf80cec 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,8 +1,4 @@ -import { - ApiError, - useGetGuestsStaysId, - usePutApiV1GuestsId, -} from "@shared"; +import { ApiError, useGetGuestsStaysId, usePutApiV1GuestsId } from "@shared"; import { Link, createFileRoute } from "@tanstack/react-router"; import { GuestNotesCard } from "../../components/guests/GuestNotesCard"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; diff --git a/clients/web/src/routes/_protected/guests.index.tsx b/clients/web/src/routes/_protected/guests.index.tsx index abbd9da43..73732ff48 100644 --- a/clients/web/src/routes/_protected/guests.index.tsx +++ b/clients/web/src/routes/_protected/guests.index.tsx @@ -1,4 +1,5 @@ import { + MakeRequestPriority, useGetGuestBookingsGroupSizes, useGetRoomsFloors, } from "@shared"; @@ -6,7 +7,6 @@ import { usePostApiV1GuestsSearchHook } from "@shared/api/generated/endpoints/gu import { useInfiniteQuery } from "@tanstack/react-query"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; -import { MakeRequestPriority } from "@shared"; import { GuestQuickListTable } from "../../components/guests/GuestQuickListTable"; import { GuestSearchBar } from "../../components/guests/GuestSearchBar"; import { useDebounce } from "../../hooks/use-debounce"; diff --git a/clients/web/src/tests/guests-ui.test.tsx b/clients/web/src/tests/guests-ui.test.tsx index 949af3889..077472bf9 100644 --- a/clients/web/src/tests/guests-ui.test.tsx +++ b/clients/web/src/tests/guests-ui.test.tsx @@ -1,10 +1,10 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; -import type { GuestWithBooking } from "@shared"; import { GuestProfilePageSkeleton } from "../components/guests/GuestProfilePageSkeleton"; import { GuestProfileCard } from "../components/guests/GuestProfileCard"; import { GuestQuickListTable } from "../components/guests/GuestQuickListTable"; import { formatDate } from "../utils/dates"; +import type { GuestWithBooking } from "@shared"; describe("guest UI helpers", () => { describe("formatDate", () => { From 4c599fe6f24db755d3d2df3b51e98430a97b2889 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 03:04:51 -0400 Subject: [PATCH 13/22] style: fix styling --- clients/web/src/components/guests/GuestQuickListTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/web/src/components/guests/GuestQuickListTable.tsx b/clients/web/src/components/guests/GuestQuickListTable.tsx index e7994e107..b55a7b7b9 100644 --- a/clients/web/src/components/guests/GuestQuickListTable.tsx +++ b/clients/web/src/components/guests/GuestQuickListTable.tsx @@ -81,7 +81,7 @@ export function GuestQuickListTable({

{guest.preferred_name}

-

{guest.group_size ?? "—"}

+

{String(guest.group_size)}

{guest.floor}

{guest.room_number}

From bf4c3ba71d7bb903c3645437e9ff1b9562ff83db Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 03:09:36 -0400 Subject: [PATCH 14/22] fix: fix failing test --- .../components/guests/GuestQuickListTable.tsx | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/clients/web/src/components/guests/GuestQuickListTable.tsx b/clients/web/src/components/guests/GuestQuickListTable.tsx index b55a7b7b9..cea67f81f 100644 --- a/clients/web/src/components/guests/GuestQuickListTable.tsx +++ b/clients/web/src/components/guests/GuestQuickListTable.tsx @@ -67,25 +67,31 @@ export function GuestQuickListTable({
- {guests.map((guest) => ( - - ))} + {guests.map((guest) => { + const groupSize = guest.group_size as number | null | undefined; + + return ( + + ); + })} {!isLoading && guests.length === 0 && (
No guests match your current filters. From 576138541aa957075c363f2be4b2d1e2b02fec24 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Thu, 9 Apr 2026 20:19:54 -0400 Subject: [PATCH 15/22] feat: Update param names and fix assistance marshalling --- backend/internal/repository/guests.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/internal/repository/guests.go b/backend/internal/repository/guests.go index 274a2a0f9..7d425682d 100644 --- a/backend/internal/repository/guests.go +++ b/backend/internal/repository/guests.go @@ -117,12 +117,12 @@ func (r *GuestsRepository) FindGuestWithStayHistory(ctx context.Context, id stri guest.DoNotDisturbStart = formatPGTime(doNotDisturbStart) guest.DoNotDisturbEnd = formatPGTime(doNotDisturbEnd) - if len(assistanceRaw) > 0 && string(assistanceRaw) != "null" { - var assistance models.Assistance + if len(assistanceRaw) > 0 { + var assistance *models.Assistance if err := json.Unmarshal(assistanceRaw, &assistance); err != nil { return nil, err } - guest.Assistance = &assistance + guest.Assistance = assistance } rows, err := r.db.Query(ctx, ` @@ -198,12 +198,16 @@ func appendStay(guest *models.GuestWithStays, stay models.Stay, status models.Bo } func sortGuestStays(guest *models.GuestWithStays) { - sort.Slice(guest.CurrentStays, func(i, j int) bool { - return guest.CurrentStays[i].ArrivalDate.After(guest.CurrentStays[j].ArrivalDate) + sort.Slice(guest.CurrentStays, func(currentStayIndex, otherCurrentStayIndex int) bool { + return guest.CurrentStays[currentStayIndex].ArrivalDate.After( + guest.CurrentStays[otherCurrentStayIndex].ArrivalDate, + ) }) - sort.Slice(guest.PastStays, func(i, j int) bool { - return guest.PastStays[i].DepartureDate.After(guest.PastStays[j].DepartureDate) + sort.Slice(guest.PastStays, func(pastStayIndex, otherPastStayIndex int) bool { + return guest.PastStays[pastStayIndex].DepartureDate.After( + guest.PastStays[otherPastStayIndex].DepartureDate, + ) }) } From 096b9618c07d478ce7bb50fd36db4b91e1b3eaff Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir <154794199+zaydaanjahangir@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:22:51 -0400 Subject: [PATCH 16/22] fix: remove duplicate hotelID --- backend/internal/models/guests.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/internal/models/guests.go b/backend/internal/models/guests.go index 29ed4df50..7280c5188 100644 --- a/backend/internal/models/guests.go +++ b/backend/internal/models/guests.go @@ -49,8 +49,7 @@ type Guest struct { } //@name Guest type GuestFilters struct { - HotelID string `json:"hotel_id" validate:"required,uuid" swaggerignore:"true"` - HotelID string `json:"hotel_id" validate:"required,startswith=org_"` + HotelID string `json:"hotel_id" validate:"required,startswith=org_" swaggerignore:"true"`` Floors []int `json:"floors"` GroupSize []int `json:"group_size"` Search string `json:"search"` From 06514b302819786e0ac05499ebb268fdf4b5b1c3 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir <154794199+zaydaanjahangir@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:24:32 -0400 Subject: [PATCH 17/22] fix: Remove extra tick --- backend/internal/models/guests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/models/guests.go b/backend/internal/models/guests.go index 7280c5188..591685b6c 100644 --- a/backend/internal/models/guests.go +++ b/backend/internal/models/guests.go @@ -49,7 +49,7 @@ type Guest struct { } //@name Guest type GuestFilters struct { - HotelID string `json:"hotel_id" validate:"required,startswith=org_" swaggerignore:"true"`` + HotelID string `json:"hotel_id" validate:"required,startswith=org_" swaggerignore:"true"` Floors []int `json:"floors"` GroupSize []int `json:"group_size"` Search string `json:"search"` From 4fe65e10ce203afea3a0934c97603f93ffbc4f42 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Fri, 10 Apr 2026 05:27:11 -0400 Subject: [PATCH 18/22] fix: Update swagger --- backend/docs/swagger.yaml | 51 +++++---------------------------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index dc04e7475..43ace5f39 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -42,7 +42,7 @@ definitions: example: John type: string hotel_id: - example: 550e8400-e29b-41d4-a716-446655440000 + example: org_550e8400-e29b-41d4-a716-446655440000 type: string id: example: user_123 @@ -93,7 +93,7 @@ definitions: GenerateRequestInput: properties: hotel_id: - example: 521e8400-e458-41d4-a716-446655440000 + example: org_521e8400-e458-41d4-a716-446655440000 type: string raw_text: example: Guest in room 504 needs extra towels urgently @@ -235,9 +235,9 @@ definitions: required: - first_name - floor - - group_size - id - last_name + - preferred_name - room_number type: object GuestWithStays: @@ -329,7 +329,7 @@ definitions: example: 521e8417-e458-41d4-a716-446655440990 type: string hotel_id: - example: 521e8400-e458-41d4-a716-446655440000 + example: org_521e8400-e458-41d4-a716-446655440000 type: string name: example: room cleaning @@ -423,7 +423,7 @@ definitions: example: 521e8417-e458-41d4-a716-446655440990 type: string hotel_id: - example: 521e8400-e458-41d4-a716-446655440000 + example: org_521e8400-e458-41d4-a716-446655440000 type: string id: example: 530e8400-e458-41d4-a716-446655440000 @@ -562,7 +562,7 @@ definitions: example: John type: string hotel_id: - example: 550e8400-e29b-41d4-a716-446655440000 + example: org_550e8400-e29b-41d4-a716-446655440000 type: string id: example: user_123 @@ -995,45 +995,6 @@ paths: summary: Get available group size options tags: - guest-bookings - /guests/stays/{id}: - get: - consumes: - - application/json - description: Retrieves a single guest with previous stays given an id - parameters: - - description: Guest ID (UUID) - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/GuestWithStays' - "400": - description: Invalid guest ID format - schema: - additionalProperties: - type: string - type: object - "404": - description: Guest not found - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal server error - schema: - additionalProperties: - type: string - type: object - security: - - BearerAuth: [] - summary: Gets a guest with previous stays - tags: - - guests /hello: get: consumes: From 6966c29f781779b5ceb22f84f0349149b009e92c Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Fri, 10 Apr 2026 05:36:43 -0400 Subject: [PATCH 19/22] fix: Update hook used to be compatible with orval --- clients/shared/src/index.ts | 4 ++-- clients/web/src/routes/_protected/guests.$guestId.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/clients/shared/src/index.ts b/clients/shared/src/index.ts index 86591f985..909125d23 100644 --- a/clients/shared/src/index.ts +++ b/clients/shared/src/index.ts @@ -52,7 +52,7 @@ export { useGetApiV1GuestsId, usePutApiV1GuestsId, usePostApiV1GuestsSearchHook, - useGetGuestsStaysId, + useGetApiV1GuestsStaysId, } from "./api/generated/endpoints/guests/guests"; export type { @@ -85,4 +85,4 @@ export { useMarkNotificationRead, useMarkAllNotificationsRead, usePostDeviceToken, -} from "./api/notifications"; \ No newline at end of file +} from "./api/notifications"; diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index fdbf80cec..50571d418 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,4 +1,8 @@ -import { ApiError, useGetGuestsStaysId, usePutApiV1GuestsId } from "@shared"; +import { + ApiError, + useGetApiV1GuestsStaysId, + usePutApiV1GuestsId, +} from "@shared"; import { Link, createFileRoute } from "@tanstack/react-router"; import { GuestNotesCard } from "../../components/guests/GuestNotesCard"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; @@ -20,7 +24,7 @@ function GuestProfilePage() { isError, error, refetch, - } = useGetGuestsStaysId(guestId); + } = useGetApiV1GuestsStaysId(guestId); const updateGuest = usePutApiV1GuestsId(); const handleSaveNotes = async (notes: string) => { From 64061dcd97c871dc3006f48e80c878881001e589 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Fri, 10 Apr 2026 05:50:07 -0400 Subject: [PATCH 20/22] fix: pls work --- backend/docs/swagger.yaml | 404 ++++++++++++++++-- backend/internal/handler/guests.go | 1 + backend/internal/models/guests.go | 2 +- clients/shared/src/index.ts | 2 +- .../src/routes/_protected/guests.$guestId.tsx | 4 +- 5 files changed, 369 insertions(+), 44 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index fa15e5d93..26643c1ad 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -66,6 +66,22 @@ definitions: example: America/New_York type: string type: object + Department: + properties: + created_at: + type: string + hotel_id: + example: org_2abc123 + type: string + id: + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + name: + example: Housekeeping + type: string + updated_at: + type: string + type: object Dev: properties: created_at: @@ -158,8 +174,24 @@ definitions: type: object GuestFilters: properties: + assistance: + items: + $ref: '#/definitions/github_com_generate_selfserve_internal_models.AssistanceFilter' + type: array + booking_sort: + allOf: + - $ref: '#/definitions/github_com_generate_selfserve_internal_models.GuestSortOrder' + enum: + - high_to_low + - low_to_high cursor: type: string + floor_sort: + allOf: + - $ref: '#/definitions/github_com_generate_selfserve_internal_models.FloorSortOrder' + enum: + - ascending + - descending floors: items: type: integer @@ -172,8 +204,19 @@ definitions: maximum: 100 minimum: 1 type: integer + request_sort: + allOf: + - $ref: '#/definitions/github_com_generate_selfserve_internal_models.RequestSortOrder' + enum: + - high_to_low + - low_to_high + - urgent search: type: string + status: + items: + $ref: '#/definitions/github_com_generate_selfserve_internal_models.BookingStatus' + type: array type: object GuestPage: properties: @@ -593,12 +636,31 @@ definitions: example: "2024-01-01T00:00:00Z" type: string type: object + UserPage: + properties: + next_cursor: + type: string + users: + items: + $ref: '#/definitions/User' + type: array + type: object github_com_generate_selfserve_internal_errs.HTTPError: properties: code: type: integer message: {} type: object + github_com_generate_selfserve_internal_models.AssistanceFilter: + enum: + - accessibility + - dietary + - medical + type: string + x-enum-varnames: + - AssistanceAccessibility + - AssistanceDietary + - AssistanceMedical github_com_generate_selfserve_internal_models.BookingStatus: enum: - active @@ -607,6 +669,27 @@ definitions: x-enum-varnames: - BookingStatusActive - BookingStatusInactive + github_com_generate_selfserve_internal_models.CreateDepartment: + properties: + name: + type: string + type: object + github_com_generate_selfserve_internal_models.FloorSortOrder: + enum: + - ascending + - descending + type: string + x-enum-varnames: + - FloorSortAscending + - FloorSortDescending + github_com_generate_selfserve_internal_models.GuestSortOrder: + enum: + - high_to_low + - low_to_high + type: string + x-enum-varnames: + - GuestSortHighToLow + - GuestSortLowToHigh github_com_generate_selfserve_internal_models.NotificationType: enum: - task_assigned @@ -615,6 +698,21 @@ definitions: x-enum-varnames: - TypeTaskAssigned - TypeHighPriorityTask + github_com_generate_selfserve_internal_models.RequestSortOrder: + enum: + - high_to_low + - low_to_high + - urgent + type: string + x-enum-varnames: + - RequestSortHighToLow + - RequestSortLowToHigh + - RequestSortUrgent + github_com_generate_selfserve_internal_models.UpdateDepartment: + properties: + name: + type: string + type: object github_com_generate_selfserve_internal_utils.CursorPage-RoomWithOptionalGuestBooking: properties: has_more: @@ -627,6 +725,20 @@ definitions: description: nil when no more pages type: string type: object + internal_handler.AddEmployeeDepartmentBody: + properties: + department_id: + type: string + type: object + internal_handler.SearchUsersBody: + properties: + cursor: + type: string + hotel_id: + type: string + q: + type: string + type: object internal_handler.UpdateProfilePictureRequest: description: Request body containing the S3 key after uploading properties: @@ -814,6 +926,7 @@ paths: consumes: - application/json description: Retrieves a single guest with previous stays given an id + operationId: getGuestsStaysId parameters: - description: Guest ID (UUID) in: path @@ -1039,6 +1152,154 @@ paths: summary: Get personalized hello message tags: - hello + /hotels/{id}/departments: + get: + description: Returns all departments for a hotel + parameters: + - description: Hotel ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/Department' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Get departments by hotel + tags: + - hotels + post: + consumes: + - application/json + description: Adds a new department to a hotel + parameters: + - description: Hotel ID + in: path + name: id + required: true + type: string + - description: Department data + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_models.CreateDepartment' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/Department' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Create department + tags: + - hotels + /hotels/{id}/departments/{deptId}: + delete: + description: Removes a department from a hotel + parameters: + - description: Hotel ID + in: path + name: id + required: true + type: string + - description: Department ID + in: path + name: deptId + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Delete department + tags: + - hotels + put: + consumes: + - application/json + description: Renames a department + parameters: + - description: Hotel ID + in: path + name: id + required: true + type: string + - description: Department ID + in: path + name: deptId + required: true + type: string + - description: Department data + in: body + name: request + required: true + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_models.UpdateDepartment' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/Department' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "404": + description: Not Found + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Update department + tags: + - hotels /hotels/{id}/users: get: description: Returns a paginated list of all users for a hotel @@ -1537,46 +1798,6 @@ paths: tags: - s3 /users: - get: - consumes: - - application/json - description: Returns a paginated list of users for a hotel, optionally filtered - by name - parameters: - - description: Hotel UUID - in: query - name: hotel_id - required: true - type: string - - description: Pagination cursor (last seen user ID) - in: query - name: cursor - type: string - - description: Name search query - in: query - name: q - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - additionalProperties: true - type: object - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Search users by hotel - tags: - - users post: consumes: - application/json @@ -1693,6 +1914,76 @@ paths: summary: Update user tags: - users + /users/{id}/departments: + post: + consumes: + - application/json + description: Creates a row in employee_departments linking the user to a department + parameters: + - description: User ID + in: path + name: id + required: true + type: string + - description: Department to add + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.AddEmployeeDepartmentBody' + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Add department to employee + tags: + - users + /users/{id}/departments/{deptId}: + delete: + consumes: + - application/json + description: Deletes a row from employee_departments unlinking the user from + a department + parameters: + - description: User ID + in: path + name: id + required: true + type: string + - description: Department ID + in: path + name: deptId + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Remove department from employee + tags: + - users /users/{userId}/profile-picture: delete: consumes: @@ -1810,6 +2101,39 @@ paths: summary: Update user's profile picture tags: - users + /users/search: + post: + consumes: + - application/json + description: Returns a paginated list of users for a hotel, optionally filtered + by name. Cursor is the last seen user ID. + parameters: + - description: Search params + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.SearchUsersBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/UserPage' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Search users by hotel + tags: + - users schemes: - http - https diff --git a/backend/internal/handler/guests.go b/backend/internal/handler/guests.go index 94a75a8d6..71d7334be 100644 --- a/backend/internal/handler/guests.go +++ b/backend/internal/handler/guests.go @@ -102,6 +102,7 @@ func (h *GuestsHandler) GetGuest(c *fiber.Ctx) error { // @Failure 400 {object} map[string]string "Invalid guest ID format" // @Failure 404 {object} errs.HTTPError "Guest not found" // @Failure 500 {object} map[string]string "Internal server error" +// @ID getGuestsStaysId // @Security BearerAuth // @Router /api/v1/guests/stays/{id} [get] func (h *GuestsHandler) GetGuestWithStays(c *fiber.Ctx) error { diff --git a/backend/internal/models/guests.go b/backend/internal/models/guests.go index 81da4fd1a..2b40d12ba 100644 --- a/backend/internal/models/guests.go +++ b/backend/internal/models/guests.go @@ -79,7 +79,7 @@ const ( ) type GuestFilters struct { - HotelID string `json:"hotel_id" validate:"required,startswith=org_"` + HotelID string `json:"hotel_id" validate:"required,startswith=org_" swaggerignore:"true"` Status []BookingStatus `json:"status" validate:"omitempty,dive,oneof=active inactive"` BookingSort GuestSortOrder `json:"booking_sort" validate:"omitempty,oneof=high_to_low low_to_high"` RequestSort RequestSortOrder `json:"request_sort" validate:"omitempty,oneof=high_to_low low_to_high urgent"` diff --git a/clients/shared/src/index.ts b/clients/shared/src/index.ts index ca873d536..e5b1e25a2 100644 --- a/clients/shared/src/index.ts +++ b/clients/shared/src/index.ts @@ -52,7 +52,7 @@ export { useGetApiV1GuestsId, usePutApiV1GuestsId, usePostApiV1GuestsSearchHook, - useGetApiV1GuestsStaysId, + useGetGuestsStaysId, } from "./api/generated/endpoints/guests/guests"; export type { diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index 50571d418..242429c37 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,6 +1,6 @@ import { ApiError, - useGetApiV1GuestsStaysId, + useGetGuestsStaysId, usePutApiV1GuestsId, } from "@shared"; import { Link, createFileRoute } from "@tanstack/react-router"; @@ -24,7 +24,7 @@ function GuestProfilePage() { isError, error, refetch, - } = useGetApiV1GuestsStaysId(guestId); + } = useGetGuestsStaysId(guestId); const updateGuest = usePutApiV1GuestsId(); const handleSaveNotes = async (notes: string) => { From 1072903d9d7f7ed81282f2e3d4591c34cefdce71 Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Fri, 10 Apr 2026 05:51:48 -0400 Subject: [PATCH 21/22] style: fix style --- clients/web/src/routes/_protected/guests.$guestId.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index 242429c37..fdbf80cec 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,8 +1,4 @@ -import { - ApiError, - useGetGuestsStaysId, - usePutApiV1GuestsId, -} from "@shared"; +import { ApiError, useGetGuestsStaysId, usePutApiV1GuestsId } from "@shared"; import { Link, createFileRoute } from "@tanstack/react-router"; import { GuestNotesCard } from "../../components/guests/GuestNotesCard"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; From 369ae3378d46e5603a3d4f260621df3aeb756c6c Mon Sep 17 00:00:00 2001 From: Zaydaan Jahangir Date: Mon, 13 Apr 2026 00:41:46 -0400 Subject: [PATCH 22/22] feat: Add swagger fixes --- backend/docs/swagger.yaml | 260 +++++++++--------- backend/internal/handler/guests.go | 10 +- backend/internal/handler/hotels.go | 4 +- clients/shared/src/index.ts | 12 +- .../src/routes/_protected/guests.$guestId.tsx | 4 +- .../src/routes/_protected/guests.index.tsx | 4 +- 6 files changed, 147 insertions(+), 147 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 26643c1ad..d8dad6405 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -759,7 +759,91 @@ info: title: SelfServe API version: "1.0" paths: - /api/v1/guests: + /device-tokens: + post: + consumes: + - application/json + description: Registers an Expo push token so the user receives mobile push notifications + parameters: + - description: Device token + in: body + name: request + required: true + schema: + $ref: '#/definitions/RegisterDeviceTokenInput' + responses: + "204": + description: No Content + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' + security: + - BearerAuth: [] + summary: Register device token + tags: + - notifications + /devs/{name}: + get: + consumes: + - application/json + description: Retrieves a developer member by name + parameters: + - description: Developer name + in: path + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/Dev' + "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 developer member + tags: + - devs + /guest_bookings/group_sizes: + get: + description: Retrieves all distinct group sizes across guest bookings + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + type: integer + type: array + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Get available group size options + tags: + - guest-bookings + /guests: post: consumes: - application/json @@ -795,7 +879,7 @@ paths: summary: Creates a guest tags: - guests - /api/v1/guests/{id}: + /guests/{id}: get: consumes: - application/json @@ -880,7 +964,7 @@ paths: summary: Updates a guest tags: - guests - /api/v1/guests/search: + /guests/search: post: consumes: - application/json @@ -921,7 +1005,7 @@ paths: summary: Get Guests tags: - guests - /api/v1/guests/stays/{id}: + /guests/stays/{id}: get: consumes: - application/json @@ -961,7 +1045,47 @@ paths: summary: Gets a guest with previous stays tags: - guests - /api/v1/hotels: + /hello: + get: + consumes: + - application/json + description: Returns a simple hello message + produces: + - text/plain + responses: + "200": + description: 'Yogurt. Gurt: Yo!' + schema: + type: string + security: + - BearerAuth: [] + summary: Get hello message + tags: + - hello + /hello/{name}: + get: + consumes: + - application/json + description: Returns a hello message with the provided name + parameters: + - description: Name to greet + in: path + name: name + required: true + type: string + produces: + - text/plain + responses: + "200": + description: Yo, {name}! + schema: + type: string + security: + - BearerAuth: [] + summary: Get personalized hello message + tags: + - hello + /hotels: post: consumes: - application/json @@ -997,7 +1121,7 @@ paths: summary: Create hotel tags: - hotels - /api/v1/hotels/{id}: + /hotels/{id}: get: description: Retrieve a hotel's details using its UUID parameters: @@ -1028,130 +1152,6 @@ paths: summary: Get hotel by ID tags: - hotels - /device-tokens: - post: - consumes: - - application/json - description: Registers an Expo push token so the user receives mobile push notifications - parameters: - - description: Device token - in: body - name: request - required: true - schema: - $ref: '#/definitions/RegisterDeviceTokenInput' - responses: - "204": - description: No Content - "400": - description: Bad Request - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError' - security: - - BearerAuth: [] - summary: Register device token - tags: - - notifications - /devs/{name}: - get: - consumes: - - application/json - description: Retrieves a developer member by name - parameters: - - description: Developer name - in: path - name: name - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/Dev' - "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 developer member - tags: - - devs - /guest_bookings/group_sizes: - get: - description: Retrieves all distinct group sizes across guest bookings - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - type: integer - type: array - "500": - description: Internal Server Error - schema: - additionalProperties: - type: string - type: object - summary: Get available group size options - tags: - - guest-bookings - /hello: - get: - consumes: - - application/json - description: Returns a simple hello message - produces: - - text/plain - responses: - "200": - description: 'Yogurt. Gurt: Yo!' - schema: - type: string - security: - - BearerAuth: [] - summary: Get hello message - tags: - - hello - /hello/{name}: - get: - consumes: - - application/json - description: Returns a hello message with the provided name - parameters: - - description: Name to greet - in: path - name: name - required: true - type: string - produces: - - text/plain - responses: - "200": - description: Yo, {name}! - schema: - type: string - security: - - BearerAuth: [] - summary: Get personalized hello message - tags: - - hello /hotels/{id}/departments: get: description: Returns all departments for a hotel diff --git a/backend/internal/handler/guests.go b/backend/internal/handler/guests.go index 71d7334be..dff10e50e 100644 --- a/backend/internal/handler/guests.go +++ b/backend/internal/handler/guests.go @@ -41,7 +41,7 @@ func NewGuestsHandler(repo storage.GuestsRepository, searchRepo storage.GuestsSe // @Failure 400 {object} map[string]string "Invalid guest body format" // @Failure 500 {object} map[string]string "Internal server error" // @Security BearerAuth -// @Router /api/v1/guests [post] +// @Router /guests [post] func (h *GuestsHandler) CreateGuest(c *fiber.Ctx) error { var CreateGuestRequest models.CreateGuest if err := httpx.BindAndValidate(c, &CreateGuestRequest); err != nil { @@ -71,7 +71,7 @@ func (h *GuestsHandler) CreateGuest(c *fiber.Ctx) error { // @Failure 404 {object} errs.HTTPError "Guest not found" // @Failure 500 {object} map[string]string "Internal server error" // @Security BearerAuth -// @Router /api/v1/guests/{id} [get] +// @Router /guests/{id} [get] func (h *GuestsHandler) GetGuest(c *fiber.Ctx) error { id := c.Params("id") _, err := uuid.Parse(id) @@ -104,7 +104,7 @@ func (h *GuestsHandler) GetGuest(c *fiber.Ctx) error { // @Failure 500 {object} map[string]string "Internal server error" // @ID getGuestsStaysId // @Security BearerAuth -// @Router /api/v1/guests/stays/{id} [get] +// @Router /guests/stays/{id} [get] func (h *GuestsHandler) GetGuestWithStays(c *fiber.Ctx) error { id := c.Params("id") if !validUUID(id) { @@ -136,7 +136,7 @@ func (h *GuestsHandler) GetGuestWithStays(c *fiber.Ctx) error { // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Security BearerAuth -// @Router /api/v1/guests/{id} [put] +// @Router /guests/{id} [put] func (h *GuestsHandler) UpdateGuest(c *fiber.Ctx) error { id := c.Params("id") @@ -179,7 +179,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/search [post] +// @Router /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/handler/hotels.go b/backend/internal/handler/hotels.go index 0ed0b1ee8..7ea36e6d1 100644 --- a/backend/internal/handler/hotels.go +++ b/backend/internal/handler/hotels.go @@ -42,7 +42,7 @@ func NewHotelsHandler(repo HotelsRepository, usersRepo storage.UsersRepository) // @Failure 404 {object} errs.HTTPError "Hotel not found" // @Failure 500 {object} errs.HTTPError "Internal server error" // @Security BearerAuth -// @Router /api/v1/hotels/{id} [get] +// @Router /hotels/{id} [get] func (h *HotelsHandler) GetHotelByID(c *fiber.Ctx) error { idParam := c.Params("id") @@ -241,7 +241,7 @@ func (h *HotelsHandler) DeleteDepartment(c *fiber.Ctx) error { // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Security BearerAuth -// @Router /api/v1/hotels [post] +// @Router /hotels [post] func (h *HotelsHandler) CreateHotel(c *fiber.Ctx) error { var hotelRequest models.CreateHotelRequest diff --git a/clients/shared/src/index.ts b/clients/shared/src/index.ts index 6b3b8a689..4037948e2 100644 --- a/clients/shared/src/index.ts +++ b/clients/shared/src/index.ts @@ -41,17 +41,17 @@ export { } from "./api/generated/endpoints/users/users"; export { - usePostApiV1Hotels, - useGetApiV1HotelsId, + usePostHotels, + useGetHotelsId, } from "./api/generated/endpoints/hotels/hotels"; export { useGetDevsName } from "./api/generated/endpoints/devs/devs"; export { - usePostApiV1Guests, - useGetApiV1GuestsId, - usePutApiV1GuestsId, - usePostApiV1GuestsSearchHook, + usePostGuests, + useGetGuestsId, + usePutGuestsId, + usePostGuestsSearchHook, useGetGuestsStaysId, } from "./api/generated/endpoints/guests/guests"; diff --git a/clients/web/src/routes/_protected/guests.$guestId.tsx b/clients/web/src/routes/_protected/guests.$guestId.tsx index fdbf80cec..900524c6a 100644 --- a/clients/web/src/routes/_protected/guests.$guestId.tsx +++ b/clients/web/src/routes/_protected/guests.$guestId.tsx @@ -1,4 +1,4 @@ -import { ApiError, useGetGuestsStaysId, usePutApiV1GuestsId } from "@shared"; +import { ApiError, useGetGuestsStaysId, usePutGuestsId } from "@shared"; import { Link, createFileRoute } from "@tanstack/react-router"; import { GuestNotesCard } from "../../components/guests/GuestNotesCard"; import { GuestPageShell } from "../../components/guests/GuestPageShell"; @@ -21,7 +21,7 @@ function GuestProfilePage() { error, refetch, } = useGetGuestsStaysId(guestId); - const updateGuest = usePutApiV1GuestsId(); + const updateGuest = usePutGuestsId(); const handleSaveNotes = async (notes: string) => { await updateGuest.mutateAsync({ diff --git a/clients/web/src/routes/_protected/guests.index.tsx b/clients/web/src/routes/_protected/guests.index.tsx index 73732ff48..a020dc917 100644 --- a/clients/web/src/routes/_protected/guests.index.tsx +++ b/clients/web/src/routes/_protected/guests.index.tsx @@ -3,7 +3,7 @@ import { useGetGuestBookingsGroupSizes, useGetRoomsFloors, } from "@shared"; -import { usePostApiV1GuestsSearchHook } from "@shared/api/generated/endpoints/guests/guests"; +import { usePostGuestsSearchHook } from "@shared/api/generated/endpoints/guests/guests"; import { useInfiniteQuery } from "@tanstack/react-query"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -32,7 +32,7 @@ function GuestsQuickListPage() { } | null>(null); const debouncedSearch = useDebounce(searchTerm, 300); - const postGuests = usePostApiV1GuestsSearchHook(); + const postGuests = usePostGuestsSearchHook(); const { data: floorsData } = useGetRoomsFloors(); const { data: groupSizesData } = useGetGuestBookingsGroupSizes();