Skip to content

Commit

Permalink
display the booking history to admin on front end (#740)
Browse files Browse the repository at this point in the history
* set up components

* working implementation

* fix tests
  • Loading branch information
choden-dev authored Aug 4, 2024
1 parent d29dde6 commit f5cb95f
Show file tree
Hide file tree
Showing 18 changed files with 504 additions and 90 deletions.
13 changes: 13 additions & 0 deletions client/src/app/admin/booking-history/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client"

import WrappedAdminBookingHistoryView from "@/components/composite/Admin/AdminBookingHistoryView/WrappedAdminBookingHistoryView"
import { AdminHeading } from "../AdminHeading"

export default function AdminBookingHistoryPage() {
return (
<>
<AdminHeading title="History" />
<WrappedAdminBookingHistoryView />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Meta, StoryObj } from "@storybook/react"
import { BookingHistoryEvent } from "@/models/History"
import { Timestamp } from "firebase/firestore"
import AdminBookingHistoryItem from "./AdminBookingHistoryItem"

const meta: Meta<typeof AdminBookingHistoryItem> = {
component: AdminBookingHistoryItem
}

export default meta
type Story = StoryObj<typeof meta>

const bookingAdditionItem: BookingHistoryEvent = {
event_type: "added_user_to_booking",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
uid: "jack sun"
}

const bookingDeletionItem: BookingHistoryEvent = {
event_type: "removed_user_from_booking",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
uid: "stephen zhang"
}

const availabilityChangeItem: BookingHistoryEvent = {
event_type: "changed_date_availability",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
change: -32
}

export const AdditionItem: Story = {
args: {
item: bookingAdditionItem,
name: "stephen zhang"
}
}

export const DeletionItem: Story = {
args: {
item: bookingDeletionItem,
name: "Jack sun"
}
}

export const AvailabilityChangeItem: Story = {
args: {
item: availabilityChangeItem
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { DateUtils } from "@/components/utils/DateUtils"
import { BookingHistoryEvent } from "@/models/History"
import { useMemo } from "react"

interface IAdminBookingHistoryItem {
/**
* The event that is to be parsed and displayed in list view
*/
item: BookingHistoryEvent
/**
* The name of the user associated with the event (**NOT** the admin performing it)
*/
name?: string
/**
* The email of the user associated with the event (**NOT** the admin performing it)
*/
email?: string
}

const AdminBookingHistoryItem = ({
item,
name,
email
}: IAdminBookingHistoryItem) => {
/**
* Used for parsing the history event and presenting it
*/
const InnerContent = useMemo(() => {
const UserInformation = () => {
return (
<h5 className="border-dark-blue-100 w-fit border-2 px-3">
User: <strong>{name}</strong> | Email: <strong>{email}</strong>
</h5>
)
}
const SharedContent = () => {
return (
<>
<p>
At{" "}
<strong>
{new Date(
DateUtils.timestampMilliseconds(item.timestamp)
).toLocaleString()}
</strong>{" "}
for the date range{" "}
<strong>
{DateUtils.formattedNzDate(
new Date(DateUtils.timestampMilliseconds(item.start_date))
)}
</strong>{" "}
to{" "}
<strong>
{DateUtils.formattedNzDate(
new Date(DateUtils.timestampMilliseconds(item.end_date))
)}
</strong>
</p>
</>
)
}
switch (item.event_type) {
case "added_user_to_booking":
return (
<>
<h5 className="font-bold uppercase underline">Added to booking</h5>
<UserInformation />
<SharedContent />
</>
)
case "removed_user_from_booking":
return (
<>
<h5 className="font-bold uppercase underline">
Removed from booking
</h5>
<UserInformation />
<SharedContent />
</>
)
case "changed_date_availability":
return (
<>
<h5 className="font-bold uppercase underline">
Availability Changed
</h5>
<div className="flex gap-1">
<SharedContent />
<p>
for <strong>{item.change}</strong> slots
</p>
</div>
</>
)
}
}, [item, name, email])

return (
<>
<div className="border-gray-3 flex w-full flex-col gap-1 rounded-md border bg-white p-4">
{InnerContent}
</div>
</>
)
}

export default AdminBookingHistoryItem
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Meta, StoryObj } from "@storybook/react"
import { BookingHistoryEvent } from "@/models/History"
import AdminBookingHistoryView from "./AdminBookingHistoryView"
import { Timestamp } from "firebase/firestore"
import { CombinedUserData } from "@/models/User"

const meta: Meta<typeof AdminBookingHistoryView> = {
component: AdminBookingHistoryView
}

export default meta
type Story = StoryObj<typeof meta>

const bookingAdditionItem: BookingHistoryEvent = {
event_type: "added_user_to_booking",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
uid: "jack sun"
}

const bookingDeletionItem: BookingHistoryEvent = {
event_type: "removed_user_from_booking",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
uid: "stephen zhang"
}

const availabilityChangeItem: BookingHistoryEvent = {
event_type: "changed_date_availability",
timestamp: Timestamp.fromMillis(69),
start_date: Timestamp.now(),
end_date: Timestamp.now(),
change: -32
}

const users: CombinedUserData[] = [
{
uid: "stephen zhang",
first_name: "Stephen",
last_name: "Zhang",
dietary_requirements: "nothing",
email: "[email protected]",
phone_number: 696969,
date_of_birth: Timestamp.now(),
membership: "guest"
},
{
uid: "jack sun",
first_name: "Jack",
last_name: "Sun",
dietary_requirements: "nothing",
email: "[email protected]",
phone_number: 696969,
date_of_birth: Timestamp.now(),
membership: "guest"
}
]

const mockDataArray: BookingHistoryEvent[] = []

for (let i = 0; i < 100; ++i) {
mockDataArray.push(bookingAdditionItem)
mockDataArray.push(bookingDeletionItem)
mockDataArray.push(availabilityChangeItem)
}

export const DefaultAdminBookingHistoryView: Story = {
args: {
historyItems: mockDataArray,
users
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { BookingHistoryEvent } from "@/models/History"
import AdminBookingHistoryItem from "./AdminBookingHistoryItem"
import { CombinedUserData } from "@/models/User"
import { useMemo } from "react"
import Button from "@/components/generic/FigmaButtons/FigmaButton"

interface IAdminBookingHistoryView {
/**
* The list of history items to display
*/
historyItems?: BookingHistoryEvent[]

/**
* The list of all users for querying names and emails
*/
users?: CombinedUserData[]

/**
* If not all history items have been loaded
*/
hasMore?: boolean

/**
* callback to fetch more history items (if there *is* more)
*/
loadMore?: () => void
}

/**
* @deprecated do not use directly, use {@link WrappedAdminBookingHistoryView} instead
*/
const AdminBookingHistoryView = ({
historyItems = [],
users = [],
hasMore,
loadMore
}: IAdminBookingHistoryView) => {
const content = useMemo(() => {
return (
<>
{historyItems.map((item) => {
let matchingUser
if (
item.event_type === "added_user_to_booking" ||
item.event_type === "removed_user_from_booking"
) {
matchingUser = users.find((user) => user.uid === item.uid)
}
return (
<AdminBookingHistoryItem
item={item}
key={item.timestamp.seconds}
name={`${matchingUser?.first_name} ${matchingUser?.last_name}`}
email={matchingUser?.email}
/>
)
})}
</>
)
}, [historyItems, users])

return (
<div className="flex w-full flex-col gap-2 py-4">
<h5 className="font-bold uppercase">Newest</h5>
{content}
<h5 className="font-bold uppercase">Oldest</h5>
<Button disabled={!hasMore || !loadMore}>Load More</Button>
</div>
)
}

export default AdminBookingHistoryView
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useBookingHistoryQuery } from "@/services/Admin/AdminQueries"
import AdminBookingHistoryView from "./AdminBookingHistoryView"
import { useCallback, useMemo } from "react"
import useAllUsers from "@/hooks/useAllUsers"

/**
* To be used for consumption of {@link AdminBookingHistoryView}
*/
const WrappedAdminBookingHistoryView = () => {
const {
data: bookingHistoryData,
fetchNextPage,
hasNextPage,
isFetchingNextPage
} = useBookingHistoryQuery()

const historyItems = useMemo(() => {
const history = bookingHistoryData?.pages.flatMap(
(page) => page.historyEvents || []
)
return history?.reverse()
}, [bookingHistoryData])

const loadMoreHistory = useCallback(() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage])

const { users } = useAllUsers()

return (
<>
<AdminBookingHistoryView
historyItems={historyItems}
users={users}
loadMore={loadMoreHistory}
/>
</>
)
}

export default WrappedAdminBookingHistoryView
Loading

0 comments on commit f5cb95f

Please sign in to comment.