Skip to content

Commit

Permalink
refactor: use DataTable for /bookings
Browse files Browse the repository at this point in the history
  • Loading branch information
eunjae-lee committed Jan 10, 2025
1 parent e7df1ef commit 737dfe2
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 124 deletions.
28 changes: 14 additions & 14 deletions apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,9 @@ function BookingListItem(booking: BookingItemProps) {
</DialogFooter>
</DialogContent>
</Dialog>
<tr data-testid="booking-item" className="hover:bg-muted group transition ">
<div data-testid="booking-item" className="hover:bg-muted group w-full">
<div className="flex flex-col sm:flex-row">
<td className="hidden align-top ltr:pl-3 rtl:pr-6 sm:table-cell sm:min-w-[12rem]">
<div className="hidden align-top ltr:pl-3 rtl:pr-6 sm:table-cell sm:min-w-[12rem]">
<div className="flex h-full items-center">
{eventTypeColor && (
<div className="h-[70%] w-0.5" style={{ backgroundColor: eventTypeColor }} />
Expand Down Expand Up @@ -562,8 +562,10 @@ function BookingListItem(booking: BookingItemProps) {
</div>
</Link>
</div>
</td>
<td data-testid="title-and-attendees" className={`w-full px-4${isRejected ? " line-through" : ""}`}>
</div>
<div
data-testid="title-and-attendees"
className={`w-full px-4${isRejected ? " line-through" : ""}`}>
<Link href={bookingLink}>
{/* Time and Badges for mobile */}
<div className="w-full pb-2 pt-4 sm:hidden">
Expand Down Expand Up @@ -648,8 +650,8 @@ function BookingListItem(booking: BookingItemProps) {
)}
</div>
</Link>
</td>
<td className="flex w-full flex-col flex-wrap items-end justify-end space-x-2 space-y-2 py-4 pl-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4 sm:flex-row sm:flex-nowrap sm:items-start sm:space-y-0 sm:pl-0">
</div>
<div className="flex w-full flex-col flex-wrap items-end justify-end space-x-2 space-y-2 py-4 pl-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4 sm:flex-row sm:flex-nowrap sm:items-start sm:space-y-0 sm:pl-0">
{isUpcoming && !isCancelled ? (
<>
{isPending && <TableActions actions={pendingActions} />}
Expand All @@ -674,7 +676,7 @@ function BookingListItem(booking: BookingItemProps) {
<TableActions actions={chargeCardActions} />
</div>
)}
</td>
</div>
</div>
<BookingItemBadges
booking={booking}
Expand All @@ -683,7 +685,7 @@ function BookingListItem(booking: BookingItemProps) {
userTimeFormat={userTimeFormat}
userTimeZone={userTimeZone}
/>
</tr>
</div>

{isBookingReroutable(parsedBooking) && (
<RerouteDialog
Expand Down Expand Up @@ -712,7 +714,7 @@ const BookingItemBadges = ({
const { t } = useLocale();

return (
<div className="hidden h-9 flex-row pb-4 pl-6 sm:flex">
<div className="hidden h-9 flex-row items-center pb-4 pl-6 sm:flex">
{isPending && (
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
{t("unconfirmed")}
Expand Down Expand Up @@ -1251,11 +1253,9 @@ const AssignmentReasonTooltip = ({ assignmentReason }: { assignmentReason: Assig
const badgeTitle = assignmentReasonBadgeTitleMap(assignmentReason.reasonEnum);
return (
<Tooltip content={<p>{assignmentReason.reasonString}</p>}>
<div className="-mt-1">
<Badge className="ltr:mr-2 rtl:ml-2" variant="gray">
{t(badgeTitle)}
</Badge>
</div>
<Badge className="ltr:mr-2 rtl:ml-2" variant="gray">
{t(badgeTitle)}
</Badge>
</Tooltip>
);
};
Expand Down
140 changes: 88 additions & 52 deletions apps/web/modules/bookings/views/bookings-listing-view.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
"use client";

import { useAutoAnimate } from "@formkit/auto-animate/react";
import { Fragment, ReactElement, useState } from "react";
import { z } from "zod";
import {
useReactTable,
getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
createColumnHelper,
} from "@tanstack/react-table";
import type { ReactElement } from "react";
import { Fragment, useMemo, useState } from "react";
import type { z } from "zod";

import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
import dayjs from "@calcom/dayjs";
import { FilterToggle } from "@calcom/features/bookings/components/FilterToggle";
import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer";
import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { DataTableProvider, DataTableWrapper } from "@calcom/features/data-table";
import Shell from "@calcom/features/shell/Shell";
import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
import { Alert, Button, EmptyScreen, HorizontalTabs } from "@calcom/ui";
import { Alert, EmptyScreen, HorizontalTabs } from "@calcom/ui";

import useMeQuery from "@lib/hooks/useMeQuery";

import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";

import { validStatuses } from "~/bookings/lib/validStatuses";
import type { validStatuses } from "~/bookings/lib/validStatuses";

type BookingListingStatus = z.infer<NonNullable<typeof filterQuerySchema>>["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
Expand Down Expand Up @@ -66,7 +74,24 @@ const descriptionByStatus: Record<NonNullable<BookingListingStatus>, string> = {
unconfirmed: "unconfirmed_bookings",
};

export default function Bookings({ status }: { status: (typeof validStatuses)[number] }) {
type BookingsProps = {
status: (typeof validStatuses)[number];
};

export default function Bookings(props: BookingsProps) {
return (
<DataTableProvider>
<BookingsContent {...props} />
</DataTableProvider>
);
}

type RowData = {
booking: BookingOutput;
recurringInfo?: RecurringInfo;
};

function BookingsContent({ status }: BookingsProps) {
const { data: filterQuery } = useFilterQuery();

const { t } = useLocale();
Expand All @@ -87,13 +112,32 @@ export default function Bookings({ status }: { status: (typeof validStatuses)[nu
}
);

// Animate page (tab) transitions to look smoothing
const columns = useMemo(() => {
const columnHelper = createColumnHelper();

const buttonInView = useInViewObserver(() => {
if (!query.isFetching && query.hasNextPage && query.status === "success") {
query.fetchNextPage();
}
});
return [
columnHelper.display({
id: "custom-view",
cell: (props) => {
const { booking, recurringInfo } = props.row.original;
return (
<BookingListItem
key={booking.id}
loggedInUser={{
userId: user?.id,
userTimeZone: user?.timeZone,
userTimeFormat: user?.timeFormat,
userEmail: user?.email,
}}
listingStatus={status}
recurringInfo={recurringInfo}
{...booking}
/>
);
},
}),
];
}, [user, status]);

const isEmpty = !query.data?.pages[0]?.bookings.length;

Expand All @@ -120,6 +164,27 @@ export default function Bookings({ status }: { status: (typeof validStatuses)[nu
return true;
};

const flatData = useMemo(() => {
return (
query.data?.pages.flatMap((page) =>
page.bookings.filter(filterBookings).map((booking) => ({
booking,
recurringInfo: page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
),
}))
) || []
);
}, [query.data]);

const table = useReactTable<RowData>({
data: flatData,
columns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
});

let recurringInfoToday: RecurringInfo | undefined;

const bookingsToday =
Expand Down Expand Up @@ -181,46 +246,17 @@ export default function Bookings({ status }: { status: (typeof validStatuses)[nu
</div>
</div>
)}
<div className="pt-2 xl:pt-0">
<div className="border-subtle overflow-hidden rounded-md border">
<table data-testid={`${status}-bookings`} className="w-full max-w-full table-fixed">
<tbody className="bg-default divide-subtle divide-y" data-testid="bookings">
{query.data.pages.map((page, index) => (
<Fragment key={index}>
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
const recurringInfo = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return (
<BookingListItem
key={booking.id}
loggedInUser={{
userId: user?.id,
userTimeZone: user?.timeZone,
userTimeFormat: user?.timeFormat,
userEmail: user?.email,
}}
listingStatus={status}
recurringInfo={recurringInfo}
{...booking}
/>
);
})}
</Fragment>
))}
</tbody>
</table>
</div>
<div className="text-default p-4 text-center" ref={buttonInView.ref}>
<Button
color="minimal"
loading={query.isFetchingNextPage}
disabled={!query.hasNextPage}
onClick={() => query.fetchNextPage()}>
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>

<DataTableWrapper
table={table}
testId={`${status}-bookings`}
hideHeader={true}
isPending={query.isFetching && !flatData}
hasNextPage={query.hasNextPage}
fetchNextPage={query.fetchNextPage}
isFetching={query.isFetching}
variant="compact"
/>
</>
)}
{query.status === "success" && isEmpty && (
Expand Down
Loading

0 comments on commit 737dfe2

Please sign in to comment.