Skip to content

Commit 8923047

Browse files
rodrigoehlerseunjae-leedevin-ai-integration[bot]
authored
feat: bookings page redesign v3 with calendar view (calcom#24664)
* bookings page redesign work in progress fix: duplicate translation key chore: use newly supported separator type remove outdated BookingDetailsSheet remove dropdown and related code revert unncessary changes * fix wrong rebase * fix type error * refactor: separate bookings columns into filter and display columns (calcom#24959) * refactor: separate bookings columns into filter and display columns - Extract filter-only columns into shared filterColumns.ts - Extract list display columns into listColumns.tsx - Create BookingsListContainer for list view with both column sets - Create BookingsCalendarContainer for calendar view with filter columns only - Refactor bookings-view.tsx to use dynamic imports for containers - Remove column/table creation logic from bookings-view.tsx This ensures calendar view doesn't import list-specific UI components (AvatarGroup, Badge, etc.) Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: return null instead of false for separator rows in filter accessors The filter accessor functions were returning false for separator rows instead of null, which would pollute the multi-select filters with bogus 'false' values. This fix ensures that separator rows return null so they are properly excluded from filters. Addresses cubic AI reviewer feedback on PR calcom#24959 Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * clean up filter column visibility * feat: integrate booking calendar view with re-designed list (calcom#24973) * add toggle button * remove the dateRange filter when switching from calendar to list view * move "view" to the action dropdown * add close button the details sheet * move close button * fix more button behavior --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix type error * update the test case * fix e2e util * fix actions dropdown * revert e2e tests * fix type error on BookingActionsDropdown.tsx * fix: include today's bookings in flatData for past status Previously, flatData excluded groupedBookings.today, which caused past bookings that happened today to not show up when status === 'past'. This fix includes today's bookings in flatData for all statuses. Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * improve attendee cell * fix e2e tests * change max * fix e2e * add reschedule requested message * fix e2e * update e2e * remove flaky checks --------- Co-authored-by: Eunjae Lee <hey@eunjae.dev> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent d7fc11d commit 8923047

34 files changed

Lines changed: 1860 additions & 615 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"use client";
2+
3+
import { useQueryState } from "nuqs";
4+
import { useEffect } from "react";
5+
6+
import { activeFiltersParser } from "@calcom/features/data-table/lib/parsers";
7+
import { useLocale } from "@calcom/lib/hooks/useLocale";
8+
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
9+
import { ToggleGroup } from "@calcom/ui/components/form";
10+
import { Icon } from "@calcom/ui/components/icon";
11+
12+
import { viewParser, type BookingView } from "~/bookings/lib/viewParser";
13+
14+
export function ViewToggleButton() {
15+
const { t } = useLocale();
16+
const [view, setView] = useQueryState(
17+
"view",
18+
viewParser.withDefault("list").withOptions({ clearOnDefault: true })
19+
);
20+
const [, setActiveFilters] = useQueryState("activeFilters", activeFiltersParser);
21+
const isMobile = !useMediaQuery("(min-width: 640px)");
22+
23+
useEffect(() => {
24+
// Force list view on mobile
25+
if (isMobile && view !== "list") {
26+
setView("list");
27+
}
28+
}, [isMobile, view, setView]);
29+
30+
return (
31+
<div className="hidden sm:block">
32+
<ToggleGroup
33+
value={view}
34+
onValueChange={(value) => {
35+
if (!value) return;
36+
const newView = value as BookingView;
37+
38+
// When switching from calendar to list view, remove the dateRange filter
39+
if (view === "calendar" && newView === "list") {
40+
setActiveFilters((prev) => prev?.filter((filter) => filter.f !== "dateRange") ?? []);
41+
}
42+
43+
setView(newView);
44+
}}
45+
options={[
46+
{
47+
value: "list",
48+
label: "",
49+
tooltip: t("list_view"),
50+
iconLeft: <Icon name="menu" className="h-4 w-4" />,
51+
},
52+
{
53+
value: "calendar",
54+
label: "",
55+
tooltip: t("calendar_view"),
56+
iconLeft: <Icon name="calendar" className="h-4 w-4" />,
57+
},
58+
]}
59+
/>
60+
</div>
61+
);
62+
}

apps/web/app/(use-page-wrapper)/(main-nav)/bookings/[status]/page.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import { redirect } from "next/navigation";
66
import { z } from "zod";
77

88
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
9-
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
109
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
11-
import { prisma } from "@calcom/prisma";
1210
import { MembershipRole } from "@calcom/prisma/enums";
1311

1412
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
1513

1614
import { validStatuses } from "~/bookings/lib/validStatuses";
1715
import BookingsList from "~/bookings/views/bookings-view";
1816

17+
import { ViewToggleButton } from "./ViewToggleButton";
18+
1919
const querySchema = z.object({
2020
status: z.enum(validStatuses),
2121
});
@@ -54,18 +54,12 @@ const Page = async ({ params }: PageProps) => {
5454
canReadOthersBookings = teamIdsWithPermission.length > 0;
5555
}
5656

57-
const featuresRepository = new FeaturesRepository(prisma);
58-
const isCalendarViewEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally(
59-
"booking-calendar-view"
60-
);
61-
6257
return (
63-
<ShellMainAppDir heading={t("bookings")} subtitle={t("bookings_description")}>
58+
<ShellMainAppDir heading={t("bookings")} CTA={<ViewToggleButton />}>
6459
<BookingsList
6560
status={parsed.data.status}
6661
userId={session?.user?.id}
6762
permissions={{ canReadOthersBookings }}
68-
isCalendarViewEnabled={isCalendarViewEnabled}
6963
/>
7064
</ShellMainAppDir>
7165
);
Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,83 @@
11
import React from "react";
22

3-
import { SkeletonText } from "@calcom/ui/components/skeleton";
3+
import classNames from "@calcom/ui/classNames";
4+
import { SkeletonAvatar, SkeletonText } from "@calcom/ui/components/skeleton";
45

56
function SkeletonLoader() {
67
return (
7-
<ul className="divide-subtle border-subtle bg-default animate-pulse divide-y rounded-md border sm:overflow-hidden">
8-
<SkeletonItem />
9-
<SkeletonItem />
10-
<SkeletonItem />
11-
</ul>
8+
<div className="animate-pulse">
9+
{/* Table rows with separator at the beginning */}
10+
<div className="divide-subtle divide-y">
11+
{/* Month separator skeleton */}
12+
<div className="bg-muted rounded-t py-2">
13+
<SkeletonItem isHeader={true} />
14+
</div>
15+
<div className="bg-muted">
16+
<SkeletonText className="ml-2 mt-3 h-4 w-28 rounded" />
17+
</div>
18+
19+
<SkeletonItem />
20+
<SkeletonItem />
21+
<SkeletonItem />
22+
<SkeletonItem />
23+
<SkeletonItem />
24+
</div>
25+
</div>
1226
);
1327
}
1428

1529
export default SkeletonLoader;
1630

17-
function SkeletonItem() {
31+
function SkeletonItem({ isHeader = false }: { isHeader?: boolean }) {
1832
return (
19-
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
20-
<div className="flex-grow truncate text-sm">
21-
<div className="flex">
22-
<div className="flex flex-col space-y-2">
23-
<SkeletonText className="h-5 w-16" />
24-
<SkeletonText className="h-4 w-32" />
25-
</div>
26-
</div>
33+
<div
34+
className={classNames(
35+
"grid grid-cols-[132px_130px_185px_150px_140px_280px] gap-6 px-2",
36+
isHeader ? "py-2" : "py-2.5"
37+
)}>
38+
{/* Date column - 140px */}
39+
<div className="flex items-center">
40+
<SkeletonText className={classNames("h-4 rounded", isHeader ? "w-12" : "w-20")} />
2741
</div>
28-
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
29-
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
30-
<SkeletonText className="h-6 w-16" />
31-
<SkeletonText className="h-6 w-32" />
32-
</div>
42+
43+
{/* Time column - 140px */}
44+
<div className="flex items-center">
45+
<SkeletonText className={classNames("h-4 rounded", isHeader ? "w-12" : "w-28")} />
46+
</div>
47+
48+
{/* Event column - 200px */}
49+
<div className="flex items-center">
50+
<SkeletonText className={classNames("h-4 rounded", isHeader ? "w-16" : "w-full")} />
51+
</div>
52+
53+
{/* Who column - 160px, Avatar group */}
54+
<div className="flex items-center -space-x-1">
55+
{isHeader && <SkeletonText className="h-4 w-20 rounded" />}
56+
{!isHeader && (
57+
<>
58+
<SkeletonAvatar className="h-6 w-6 rounded-full" />
59+
<SkeletonAvatar className="h-6 w-6 rounded-full" />
60+
<SkeletonAvatar className="h-6 w-6 rounded-full" />
61+
</>
62+
)}
63+
</div>
64+
65+
{/* Team column - 140px */}
66+
<div className="flex items-center">
67+
<SkeletonText className="h-4 w-20 rounded-md" />
68+
</div>
69+
70+
{/* Actions column - 280px */}
71+
<div className="mr-2 flex items-center justify-end gap-2">
72+
{isHeader && <SkeletonText className="h-4 w-16 rounded-md" />}
73+
{!isHeader && (
74+
<>
75+
<SkeletonText className="h-4 w-16 rounded-md" />
76+
<SkeletonText className="h-4 w-20 rounded-md" />
77+
<SkeletonText className="h-4 w-8 rounded-md" />
78+
</>
79+
)}
3380
</div>
34-
</li>
81+
</div>
3582
);
3683
}

apps/web/components/booking/BookingListItem.tsx renamed to apps/web/components/booking/_BookingListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ function BookingListItem(booking: BookingItemProps) {
499499
</div>
500500
<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">
501501
{shouldShowPendingActions(actionContext) && <TableActions actions={pendingActions} />}
502-
<BookingActionsDropdown booking={booking} context="booking-list-item" />
502+
<BookingActionsDropdown booking={booking} />
503503
{shouldShowRecurringCancelAction(actionContext) && <TableActions actions={[cancelEventAction]} />}
504504
{shouldShowIndividualReportButton(actionContext) && (
505505
<div className="flex items-center space-x-2">

0 commit comments

Comments
 (0)