Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions backend/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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}:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/handler/guests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions backend/internal/models/guests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion backend/internal/repository/guests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 10 additions & 0 deletions clients/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions clients/web/src/components/guests/GuestNotesCard.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
40 changes: 21 additions & 19 deletions clients/web/src/components/guests/GuestProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { GuestWithStays } from "@shared";

Check failure on line 1 in clients/web/src/components/guests/GuestProfileCard.tsx

View workflow job for this annotation

GitHub Actions / Lint & Format Check

`@shared` type import should occur after import of `../../utils/dates`
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 }) {
Expand All @@ -15,6 +16,8 @@
}

export function GuestProfileCard({ guest }: GuestProfileCardProps) {
const currentStay = (guest.current_stays ?? [])[0];

Check failure on line 19 in clients/web/src/components/guests/GuestProfileCard.tsx

View workflow job for this annotation

GitHub Actions / Lint & Format Check

Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined

return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<div className="mb-[2vh] flex items-start gap-[1.1vw]">
Expand All @@ -23,28 +26,27 @@
</div>
<div>
<p className="text-[2vw] font-medium leading-tight text-black">
{guest.preferredName}
{guest.first_name} {guest.last_name}
</p>
<p className="text-[1vw] text-black">{guest.pronouns}</p>
</div>
</div>

<div className="border-b border-[#d3d8df] pb-[1vh]">
<DetailRow label="Government Name" value={guest.governmentName} />
<DetailRow label="Date of Birth" value={guest.dateOfBirth} />
</div>

<div className="pt-[1vh]">
<DetailRow label="Room" value={guest.room} />
<DetailRow label="Group Size" value={String(guest.groupSize)} />
<DetailRow
label="Arrival"
value={`${guest.arrivalTime} ${guest.arrivalDate}`}
/>
<DetailRow
label="Departure"
value={`${guest.departureTime} ${guest.departureDate}`}
/>
{currentStay ? (

Check failure on line 35 in clients/web/src/components/guests/GuestProfileCard.tsx

View workflow job for this annotation

GitHub Actions / Lint & Format Check

Unnecessary conditional, value is always truthy
<>
<DetailRow label="Room" value={String(currentStay.room_number)} />
<DetailRow
label="Arrival"
value={formatDate(currentStay.arrival_date)}
/>
<DetailRow
label="Departure"
value={formatDate(currentStay.departure_date)}
/>
</>
) : (
<p className="text-[1vw] text-[#b6bac3]">No active stay.</p>
)}
</div>
</section>
);
Expand Down
12 changes: 6 additions & 6 deletions clients/web/src/components/guests/GuestQuickListTable.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { GuestWithBooking } from "@shared";
import { UserRound } from "lucide-react";

Check failure on line 2 in clients/web/src/components/guests/GuestQuickListTable.tsx

View workflow job for this annotation

GitHub Actions / Lint & Format Check

`lucide-react` import should occur before type import of `@shared`
import type { GuestListItem } from "./guest-mocks";

type GuestQuickListTableProps = {
guests: Array<GuestListItem>;
guests: Array<GuestWithBooking>;
groupFilter: string;
floorFilter: string;
onGroupFilterChange: (value: string) => void;
Expand Down Expand Up @@ -68,14 +68,14 @@
>
{avatarPill()}
<p className="truncate text-[1vw] text-black">
{guest.governmentName}
{guest.first_name} {guest.last_name}
</p>
<p className="truncate text-[1vw] text-black">
{guest.preferredName}
{guest.preferred_name}
</p>
<p className="text-[1vw] text-black">{guest.groupSize}</p>
<p className="text-[1vw] text-black">{guest.group_size ?? "—"}</p>
<p className="text-[1vw] text-black">{guest.floor}</p>
<p className="text-[1vw] text-black">{guest.room}</p>
<p className="text-[1vw] text-black">{guest.room_number}</p>
</button>
))}
{guests.length === 0 && (
Expand Down
9 changes: 7 additions & 2 deletions clients/web/src/components/guests/GuestSpecialNeedsCard.tsx
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) {
Expand Down
18 changes: 10 additions & 8 deletions clients/web/src/components/guests/PreviousStaysCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { PreviousStay } from "./guest-mocks";
import type { Stay } from "@shared";
import { formatDate } from "../../utils/dates";

Check failure on line 2 in clients/web/src/components/guests/PreviousStaysCard.tsx

View workflow job for this annotation

GitHub Actions / Lint & Format Check

`../../utils/dates` import should occur before type import of `@shared`

type PreviousStaysCardProps = {
stays: Array<PreviousStay>;
stays: Array<Stay>;
};

export function PreviousStaysCard({ stays }: PreviousStaysCardProps) {
Expand All @@ -11,19 +12,20 @@
Previous Stays
</h2>
<div className="flex flex-col gap-[1vh]">
{stays.map((stay) => (
{stays.map((stay, index) => (
<article
key={stay.id}
key={`${stay.arrival_date}-${stay.room_number}-${index}`}
className="rounded-[1vh] border border-black px-[1vw] py-[1vh]"
>
<p className="text-[1vw] text-black">
{stay.startDate} - {stay.endDate}
</p>
<p className="text-[1vw] text-black">
{stay.room} | Group size: {stay.groupSize}
{formatDate(stay.arrival_date)} - {formatDate(stay.departure_date)}
</p>
<p className="text-[1vw] text-black">Room {stay.room_number}</p>
</article>
))}
{stays.length === 0 && (
<p className="text-[1vw] text-[#b6bac3]">No previous stays.</p>
)}
</div>
</section>
);
Expand Down
Loading
Loading