Skip to content
Merged
67 changes: 67 additions & 0 deletions clients/web/src/components/guests/ActiveBookingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { CalendarDays, UsersRound } from "lucide-react";
import type { Stay } from "@shared";
import { cn } from "@/lib/utils";
import { formatDate } from "@/utils/dates";

type ActiveBookingCardProps = {
stay: Stay;
compact?: boolean;
};

export function ActiveBookingCard({ stay, compact }: ActiveBookingCardProps) {
return (
<div
className={cn(
"flex flex-col gap-2 rounded-lg border border-primary bg-bg-selected p-4",
compact && "w-[231px] shrink-0",
)}
>
<div className="flex items-start justify-between">
<span className="text-xl font-bold text-primary">
Suite {stay.room_number}
</span>
{stay.group_size != null && (
<div className="flex items-center gap-1 text-primary">
<UsersRound className="size-[19px]" strokeWidth={1.5} />
<span className="text-xl font-bold">{stay.group_size}</span>
</div>
)}
</div>
{compact ? (
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1">
<span className="text-sm font-medium text-primary">Arrival:</span>
<div className="flex items-center gap-2 text-sm text-primary">
<CalendarDays className="size-3 shrink-0" strokeWidth={1.5} />
<span>{formatDate(stay.arrival_date)}</span>
</div>
</div>
<div className="flex flex-col gap-1">
<span className="text-sm font-medium text-primary">Departure:</span>
<div className="flex items-center gap-2 text-sm text-primary">
<CalendarDays className="size-3 shrink-0" strokeWidth={1.5} />
<span>{formatDate(stay.departure_date)}</span>
</div>
</div>
</div>
) : (
<div className="flex gap-[72px]">
<div className="flex flex-1 flex-col gap-1">
<span className="text-sm font-medium text-primary">Arrival:</span>
<div className="flex items-center gap-2 text-sm text-primary">
<CalendarDays className="size-3 shrink-0" strokeWidth={1.5} />
<span>{formatDate(stay.arrival_date)}</span>
</div>
</div>
<div className="flex flex-1 flex-col gap-1">
<span className="text-sm font-medium text-primary">Departure:</span>
<div className="flex items-center gap-2 text-sm text-primary">
<CalendarDays className="size-3 shrink-0" strokeWidth={1.5} />
<span>{formatDate(stay.departure_date)}</span>
</div>
</div>
</div>
)}
</div>
);
}
74 changes: 74 additions & 0 deletions clients/web/src/components/guests/GuestBookingHistoryView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ChevronRight } from "lucide-react";
import { ActiveBookingCard } from "./ActiveBookingCard";
import { PastBookingCard } from "./PastBookingCard";
import type { Stay } from "@shared";

type GuestBookingHistoryViewProps = {
currentStays: Array<Stay>;
pastStays: Array<Stay>;
onBack: () => void;
};

export function GuestBookingHistoryView({
currentStays,
pastStays,
onBack,
}: GuestBookingHistoryViewProps) {
const byYear = pastStays.reduce<Record<string, Array<Stay>>>((acc, stay) => {
const year = stay.arrival_date.slice(0, 4);
(acc[year] ??= []).push(stay);
return acc;
}, {});
const years = Object.keys(byYear).sort((a, b) => Number(b) - Number(a));

return (
<div className="flex flex-col gap-6 p-6">
{/* Breadcrumb */}
<div className="flex items-center gap-1 text-base">
<button
type="button"
aria-label="Visit Activity"
onClick={onBack}
className="text-text-default hover:underline"
>
Visit Activity
</button>
<ChevronRight className="size-3.5 text-text-default" strokeWidth={2} />
<span className="text-text-default">Booking History</span>
</div>

{/* Active stays */}
{currentStays.length > 0 && (
<section className="flex flex-col gap-4">
<h3 className="text-base font-medium text-text-default">
Active Bookings ({currentStays.length})
</h3>
<div className="flex flex-col gap-3">
{currentStays.map((stay) => (
<ActiveBookingCard
key={`active-${stay.room_number}`}
stay={stay}
/>
))}
</div>
</section>
)}

{/* Past stays by year */}
{years.length > 0 ? (
years.map((year) => (
<section key={year} className="flex flex-col gap-2">
<h3 className="text-sm font-medium text-text-default">{year}</h3>
<div className="flex flex-col gap-2">
{byYear[year].map((stay, i) => (
<PastBookingCard key={`${year}-${i}`} stay={stay} />
))}
</div>
</section>
))
) : (
<p className="text-sm text-text-subtle">No booking history.</p>
)}
</div>
);
}
15 changes: 13 additions & 2 deletions clients/web/src/components/guests/GuestDetailsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { X } from "lucide-react";
import { useGetGuestsStaysId, usePutGuestsId } from "@shared";
import {
useGetGuestsStaysId,
useGetRequestGuestId,
usePutGuestsId,
} from "@shared";
import { GuestProfileTab } from "./GuestProfileTab";
import { GuestVisitActivityTab } from "./GuestVisitActivityTab";
import { cn } from "@/lib/utils";
import { Skeleton } from "@/components/ui/skeleton";

Expand Down Expand Up @@ -33,6 +38,8 @@ export function GuestDetailsDrawer({
isError,
refetch,
} = useGetGuestsStaysId(guestId);
const { data: requestsData } = useGetRequestGuestId(guestId);
const requests = (requestsData as any)?.items ?? requestsData ?? [];
const updateGuest = usePutGuestsId();

const handleSaveNotes = async (notes: string) => {
Expand Down Expand Up @@ -112,7 +119,11 @@ export function GuestDetailsDrawer({
/>
)}
{activeTab === GuestDrawerTab.Activity && (
<div className="p-6 text-sm text-text-subtle">Coming soon.</div>
<GuestVisitActivityTab
currentStays={guest.current_stays}
Comment thread
Dao-Ho marked this conversation as resolved.
pastStays={guest.past_stays}
requests={requests}
/>
)}
</>
)}
Expand Down
93 changes: 93 additions & 0 deletions clients/web/src/components/guests/GuestVisitActivityTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState } from "react";
import { ChevronRight } from "lucide-react";
import { ActiveBookingCard } from "./ActiveBookingCard";
import { GuestBookingHistoryView } from "./GuestBookingHistoryView";
import { PastBookingCard } from "./PastBookingCard";
import { RequestCard } from "./RequestCard";
import type { GuestRequest, Stay } from "@shared";

type GuestVisitActivityTabProps = {
currentStays: Array<Stay>;
pastStays: Array<Stay>;
requests: Array<GuestRequest>;
};

export function GuestVisitActivityTab({
currentStays,
pastStays,
requests,
}: GuestVisitActivityTabProps) {
const [showHistory, setShowHistory] = useState(false);

if (showHistory) {
return (
<GuestBookingHistoryView
currentStays={currentStays}
pastStays={pastStays}
onBack={() => setShowHistory(false)}
/>
);
}

const hasActiveBookings = currentStays.length > 0;

return (
<div className="flex flex-col gap-6 p-6">
{/* Active Bookings or Previous Bookings */}
<section className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h3 className="text-base font-medium text-text-default">
{hasActiveBookings
? `Active Bookings (${currentStays.length})`
: "Previous Bookings"}
</h3>
<button
type="button"
aria-label="View all bookings"
onClick={() => setShowHistory(true)}
className="flex items-center gap-1 text-base text-text-default hover:underline"
>
View All Bookings
<ChevronRight className="size-3.5" strokeWidth={2} />
</button>
</div>

{hasActiveBookings ? (
currentStays.length === 1 ? (
<ActiveBookingCard stay={currentStays[0]} />
) : (
<div className="flex gap-5">
{currentStays.slice(0, 2).map((stay) => (
<ActiveBookingCard key={stay.room_number} stay={stay} compact />
))}
</div>
)
) : pastStays.length > 0 ? (
<div className="flex flex-col gap-2">
{pastStays.slice(0, 3).map((stay, i) => (
<PastBookingCard key={`past-${i}`} stay={stay} />
))}
</div>
) : (
<p className="text-sm text-text-subtle">No bookings.</p>
)}
</section>

{/* Requests */}
<section className="flex flex-col gap-4">
<h3 className="text-base font-medium text-text-default">
{requests.length > 0 ? `Requests (${requests.length})` : "Requests"}
</h3>
{requests.length > 0 ? (
<div className="flex flex-col gap-4">
{requests.map((req) => (
<RequestCard key={req.id ?? req.name} req={req} />
))}
</div>
) : (
<p className="text-base text-text-secondary">You're all caught up!</p>
)}
</section>
</div>
);
}
30 changes: 30 additions & 0 deletions clients/web/src/components/guests/PastBookingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { UsersRound } from "lucide-react";
import type { Stay } from "@shared";
import { formatDate } from "@/utils/dates";

type PastBookingCardProps = {
stay: Stay;
};

export function PastBookingCard({ stay }: PastBookingCardProps) {
return (
<div className="flex items-center rounded-lg border border-text-subtle bg-bg-container p-4">
<div className="flex flex-1 flex-col gap-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-text-subtle">
Suite {stay.room_number}
</span>
{stay.group_size != null && (
<div className="flex items-center gap-0.5 text-text-subtle">
<UsersRound className="size-3" strokeWidth={1.5} />
<span className="text-sm font-medium">{stay.group_size}</span>
</div>
)}
</div>
<span className="text-sm text-text-subtle">
{formatDate(stay.arrival_date)} - {formatDate(stay.departure_date)}
</span>
</div>
</div>
);
}
Loading
Loading