Skip to content
Merged
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: 73 additions & 0 deletions clients/web/src/components/guests/GuestNotesCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useState } from 'react'

type GuestNotesCardProps = {
initialNotes: string
}

export function GuestNotesCard({ initialNotes }: GuestNotesCardProps) {
const [isEditing, setIsEditing] = useState(false)
const [notes, setNotes] = useState(initialNotes)
const [draft, setDraft] = useState(initialNotes)

const startEditing = () => {
setDraft(notes)
setIsEditing(true)
}

const cancelEditing = () => {
setDraft(notes)
setIsEditing(false)
}

const saveNotes = () => {
setNotes(draft)
setIsEditing(false)
}

return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
{!isEditing ? (
<div>
<div className="mb-[1vh] flex items-center justify-between">
<h2 className="text-[2vw] font-medium text-black">Notes</h2>
<button
type="button"
onClick={startEditing}
className="h-[3vh] min-h-[3vh] bg-[#004fc5] px-[1vw] text-[1vw] text-white"
>
Edit
</button>
</div>
<p className="whitespace-pre-wrap text-[1vw] leading-normal text-black">
{notes}
</p>
</div>
) : (
<div>
<h2 className="mb-[1vh] text-[2vw] font-medium text-black">Notes</h2>
<textarea
value={draft}
onChange={(event) => setDraft(event.target.value)}
className="min-h-[20vh] w-full resize-none bg-neutral-100 p-[1vw] text-[1vw] leading-normal text-black outline-none"
/>
<div className="mt-[2vh] flex justify-end gap-[0.9vw]">
<button
type="button"
onClick={cancelEditing}
className="h-[3vh] min-h-[3vh] px-[1vw] text-[1vw] text-black"
>
Cancel
</button>
<button
type="button"
onClick={saveNotes}
className="h-[3vh] min-h-[3vh] bg-[#004fc5] px-[1vw] text-[1vw] text-white"
>
Save
</button>
</div>
</div>
)}
</section>
)
}
51 changes: 51 additions & 0 deletions clients/web/src/components/guests/GuestProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { UserRound } from 'lucide-react'
import type { GuestProfile } from './guest-mocks'

type GuestProfileCardProps = {
guest: GuestProfile
}

function DetailRow({ label, value }: { label: string; value: string }) {
return (
<div className="grid grid-cols-[38%_1fr] items-center py-[1vh] text-[1vw]">
<p className="text-[#b6bac3]">{label}</p>
<p className="text-black">{value}</p>
</div>
)
}

export function GuestProfileCard({ guest }: GuestProfileCardProps) {
return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<div className="mb-[2vh] flex items-start gap-[1.1vw]">
<div className="flex h-[3vw] w-[3vw] items-center justify-center rounded-full border-2 border-black">
<UserRound className="h-[2vw] w-[2vw] text-black" />
</div>
<div>
<p className="text-[2vw] font-medium leading-tight text-black">
{guest.preferredName}
</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}`}
/>
</div>
</section>
)
}
45 changes: 45 additions & 0 deletions clients/web/src/components/guests/GuestSpecialNeedsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { GuestProfile } from './guest-mocks'

type GuestSpecialNeedsCardProps = {
specialNeeds: GuestProfile['specialNeeds']
}

function SpecialNeedsRow({ label, value }: { label: string; value: string }) {
const displayValue = value.trim().length > 0 ? value : '-'
return (
<div className="grid grid-cols-[48%_1fr] py-[1vh] text-[1vw]">
<p className="text-[#b6bac3]">{label}</p>
<p className={displayValue === '-' ? 'text-[#b6bac3]' : 'text-black'}>
{displayValue}
</p>
</div>
)
}

export function GuestSpecialNeedsCard({
specialNeeds,
}: GuestSpecialNeedsCardProps) {
return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<h2 className="mb-[1vh] text-[2vw] font-medium text-black">
Special Needs
</h2>
<SpecialNeedsRow
label="Dietary Restrictions"
value={specialNeeds.dietaryRestrictions}
/>
<SpecialNeedsRow
label="Accessibility Needs"
value={specialNeeds.accessibilityNeeds}
/>
<SpecialNeedsRow
label="Sensory Sensitivities"
value={specialNeeds.sensorySensitivities}
/>
<SpecialNeedsRow
label="Medical Conditions"
value={specialNeeds.medicalConditions}
/>
</section>
)
}
28 changes: 28 additions & 0 deletions clients/web/src/components/guests/HousekeepingPreferencesCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { GuestProfile } from './guest-mocks'

type HousekeepingPreferencesCardProps = {
housekeeping: GuestProfile['housekeeping']
}

function PreferenceRow({ label, value }: { label: string; value: string }) {
return (
<div className="grid grid-cols-[42%_1fr] py-[1vh] text-[1vw]">
<p className="text-[#b6bac3]">{label}</p>
<p className="text-black">{value}</p>
</div>
)
}

export function HousekeepingPreferencesCard({
housekeeping,
}: HousekeepingPreferencesCardProps) {
return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<h2 className="mb-[1vh] text-[2vw] font-medium text-black">
Housekeeping Preferences
</h2>
<PreferenceRow label="Frequency" value={housekeeping.frequency} />
<PreferenceRow label="Do Not Disturb" value={housekeeping.doNotDisturb} />
</section>
)
}
30 changes: 30 additions & 0 deletions clients/web/src/components/guests/PreviousStaysCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { PreviousStay } from './guest-mocks'

type PreviousStaysCardProps = {
stays: Array<PreviousStay>
}

export function PreviousStaysCard({ stays }: PreviousStaysCardProps) {
return (
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<h2 className="mb-[2vh] text-[2vw] font-medium text-black">
Previous Stays
</h2>
<div className="flex flex-col gap-[1vh]">
{stays.map((stay) => (
<article
key={stay.id}
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}
</p>
</article>
))}
</div>
</section>
)
}
35 changes: 33 additions & 2 deletions clients/web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Route as ProtectedRouteImport } from './routes/_protected'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ProtectedTestApiRouteImport } from './routes/_protected/test-api'
import { Route as ProtectedGuestsIndexRouteImport } from './routes/_protected/guests.index'
import { Route as ProtectedGuestsGuestIdRouteImport } from './routes/_protected/guests.$guestId'

const SignUpRoute = SignUpRouteImport.update({
id: '/sign-up',
Expand Down Expand Up @@ -45,19 +46,26 @@ const ProtectedGuestsIndexRoute = ProtectedGuestsIndexRouteImport.update({
path: '/guests/',
getParentRoute: () => ProtectedRoute,
} as any)
const ProtectedGuestsGuestIdRoute = ProtectedGuestsGuestIdRouteImport.update({
id: '/guests/$guestId',
path: '/guests/$guestId',
getParentRoute: () => ProtectedRoute,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/test-api': typeof ProtectedTestApiRoute
'/guests/$guestId': typeof ProtectedGuestsGuestIdRoute
'/guests/': typeof ProtectedGuestsIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/test-api': typeof ProtectedTestApiRoute
'/guests/$guestId': typeof ProtectedGuestsGuestIdRoute
'/guests': typeof ProtectedGuestsIndexRoute
}
export interface FileRoutesById {
Expand All @@ -67,20 +75,34 @@ export interface FileRoutesById {
'/sign-in': typeof SignInRoute
'/sign-up': typeof SignUpRoute
'/_protected/test-api': typeof ProtectedTestApiRoute
'/_protected/guests/$guestId': typeof ProtectedGuestsGuestIdRoute
'/_protected/guests/': typeof ProtectedGuestsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/sign-in' | '/sign-up' | '/test-api' | '/guests/'
fullPaths:
| '/'
| '/sign-in'
| '/sign-up'
| '/test-api'
| '/guests/$guestId'
| '/guests/'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/sign-in' | '/sign-up' | '/test-api' | '/guests'
to:
| '/'
| '/sign-in'
| '/sign-up'
| '/test-api'
| '/guests/$guestId'
| '/guests'
id:
| '__root__'
| '/'
| '/_protected'
| '/sign-in'
| '/sign-up'
| '/_protected/test-api'
| '/_protected/guests/$guestId'
| '/_protected/guests/'
fileRoutesById: FileRoutesById
}
Expand Down Expand Up @@ -135,16 +157,25 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ProtectedGuestsIndexRouteImport
parentRoute: typeof ProtectedRoute
}
'/_protected/guests/$guestId': {
id: '/_protected/guests/$guestId'
path: '/guests/$guestId'
fullPath: '/guests/$guestId'
preLoaderRoute: typeof ProtectedGuestsGuestIdRouteImport
parentRoute: typeof ProtectedRoute
}
}
}

interface ProtectedRouteChildren {
ProtectedTestApiRoute: typeof ProtectedTestApiRoute
ProtectedGuestsGuestIdRoute: typeof ProtectedGuestsGuestIdRoute
ProtectedGuestsIndexRoute: typeof ProtectedGuestsIndexRoute
}

const ProtectedRouteChildren: ProtectedRouteChildren = {
ProtectedTestApiRoute: ProtectedTestApiRoute,
ProtectedGuestsGuestIdRoute: ProtectedGuestsGuestIdRoute,
ProtectedGuestsIndexRoute: ProtectedGuestsIndexRoute,
}

Expand Down
51 changes: 51 additions & 0 deletions clients/web/src/routes/_protected/guests.$guestId.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import { GuestNotesCard } from '../../components/guests/GuestNotesCard'
import { GuestPageShell } from '../../components/guests/GuestPageShell'
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,
})

function GuestProfilePage() {
const { guestId } = Route.useParams()
const guestProfile = guestProfilesById[guestId]

if (!guestProfile) {
return (
<GuestPageShell title="Guests / Guest Profile">
<section className="border border-black bg-white px-[1vw] py-[2vh]">
<p className="text-[1vw] text-black">Guest not found.</p>
<Link
to="/guests"
className="mt-[1vh] inline-block text-[1vw] text-[#004fc5] underline"
>
Return to guest list
</Link>
</section>
</GuestPageShell>
)
}

return (
<GuestPageShell title="Guests / Guest Profile">
<div className="grid gap-[2vh] xl:grid-cols-[minmax(0,45vw)_minmax(0,32vw)]">
<div className="flex flex-col gap-[2vh]">
<GuestProfileCard guest={guestProfile} />
<GuestNotesCard initialNotes={guestProfile.notes} />
</div>
<div className="flex flex-col gap-[2vh]">
<GuestSpecialNeedsCard specialNeeds={guestProfile.specialNeeds} />
<PreviousStaysCard stays={guestProfile.previousStays} />
<HousekeepingPreferencesCard
housekeeping={guestProfile.housekeeping}
/>
</div>
</div>
</GuestPageShell>
)
}
8 changes: 2 additions & 6 deletions clients/web/src/routes/_protected/guests.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,8 @@ function GuestsQuickListPage() {
floorFilter={floorFilter}
onGroupFilterChange={setGroupFilter}
onFloorFilterChange={setFloorFilter}
onGuestClick={
(guestId) =>
navigate({
to: '/guests/$guestId' as any,
params: { guestId } as any,
}) // ^ Will be updated in next PR, very temporary
onGuestClick={(guestId) =>
navigate({ to: '/guests/$guestId', params: { guestId } })
}
/>
</GuestPageShell>
Expand Down
Loading