Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use client";

import { useQueryState } from "nuqs";
import { useEffect, useState } from "react";

import { activeFiltersParser } from "@calcom/features/data-table/lib/parsers";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { ToggleGroup } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";

import { viewParser, type BookingView } from "~/bookings/lib/viewParser";

export function ViewToggleButton() {
const { t } = useLocale();
const [view, setView] = useQueryState(
"view",
viewParser.withDefault("list").withOptions({ clearOnDefault: true })
);
const [, setActiveFilters] = useQueryState("activeFilters", activeFiltersParser);
const isMobile = !useMediaQuery("(min-width: 640px)");
const [hasMounted, setHasMounted] = useState(false);

useEffect(() => {
setHasMounted(true);
}, []);

useEffect(() => {
// Force list view on mobile, but only after media query has initialized on client
if (hasMounted && isMobile && view !== "list") {
setView("list");
}
}, [hasMounted, isMobile, view, setView]);

return (
<div className="hidden sm:block">
<ToggleGroup
value={view}
onValueChange={(value) => {
if (!value) return;
const newView = value as BookingView;

// When switching from calendar to list view, remove the dateRange filter
if (view === "calendar" && newView === "list") {
setActiveFilters((prev) => prev?.filter((filter) => filter.f !== "dateRange") ?? []);
}

setView(newView);
}}
options={[
{
value: "list",
label: "",
tooltip: t("list_view"),
iconLeft: <Icon name="menu" className="h-4 w-4" />,
},
{
value: "calendar",
label: "",
tooltip: t("calendar_view"),
iconLeft: <Icon name="calendar" className="h-4 w-4" />,
},
]}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx";
import { validStatuses } from "~/bookings/lib/validStatuses";
import BookingsList from "~/bookings/views/bookings-view";

import { ViewToggleButton } from "./ViewToggleButton";

const querySchema = z.object({
status: z.enum(validStatuses),
});
Expand Down Expand Up @@ -54,18 +56,23 @@ const Page = async ({ params }: PageProps) => {
canReadOthersBookings = teamIdsWithPermission.length > 0;
}

const userProfile = session?.user?.profile;
const orgId = userProfile?.organizationId ?? session?.user.org?.id;
const featuresRepository = new FeaturesRepository(prisma);
const isCalendarViewEnabled = await featuresRepository.checkIfFeatureIsEnabledGlobally(
"booking-calendar-view"
);
const bookingsV3Enabled = orgId
? await featuresRepository.checkIfTeamHasFeature(orgId, "bookings-v3")
: false;

return (
<ShellMainAppDir heading={t("bookings")} subtitle={t("bookings_description")}>
<ShellMainAppDir
heading={t("bookings")}
subtitle={t("bookings_description")}
CTA={bookingsV3Enabled ? <ViewToggleButton /> : null}>
<BookingsList
status={parsed.data.status}
userId={session?.user?.id}
permissions={{ canReadOthersBookings }}
isCalendarViewEnabled={isCalendarViewEnabled}
bookingsV3Enabled={bookingsV3Enabled}
/>
</ShellMainAppDir>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ function BookingListItem(booking: BookingItemProps) {
</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">
{shouldShowPendingActions(actionContext) && <TableActions actions={pendingActions} />}
<BookingActionsDropdown booking={booking} context="booking-list-item" />
<BookingActionsDropdown booking={booking} context="list" />
{shouldShowRecurringCancelAction(actionContext) && <TableActions actions={[cancelEventAction]} />}
{shouldShowIndividualReportButton(actionContext) && (
<div className="flex items-center space-x-2">
Expand Down
56 changes: 44 additions & 12 deletions apps/web/components/booking/SkeletonLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@ import { SkeletonText } from "@calcom/ui/components/skeleton";
function SkeletonLoader() {
return (
<ul className="divide-subtle border-subtle bg-default animate-pulse divide-y rounded-md border sm:overflow-hidden">
{/* TODAY header */}
<li className="bg-muted flex items-center px-6 py-4">
<SkeletonText className="h-4 w-16" />
</li>
<SkeletonItem />
<SkeletonItem />
<SkeletonItem />

{/* NEXT header */}
<li className="bg-muted px-4 py-2">
<SkeletonText className="h-4 w-16" />
</li>
<SkeletonItem />
<SkeletonItem />
</ul>
);
}
Expand All @@ -16,21 +27,42 @@ export default SkeletonLoader;

function SkeletonItem() {
return (
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
<div className="flex-grow truncate text-sm">
<div className="flex">
<div className="flex flex-col space-y-2">
<SkeletonText className="h-5 w-16" />
<SkeletonText className="h-4 w-32" />
</div>
<li className="group flex w-full items-start justify-between px-4 py-4 sm:px-6">
{/* Left side - Date and time info */}
<div className="flex min-w-0 flex-shrink-0 flex-col gap-2 pr-4">
<SkeletonText className="h-4 w-24" />
<SkeletonText className="h-4 w-28" />
<div className="mt-1 flex items-center gap-2">
<SkeletonText className="h-4 w-4 rounded" />
<SkeletonText className="h-3 w-20" />
</div>
</div>
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
<SkeletonText className="h-6 w-16" />
<SkeletonText className="h-6 w-32" />
{/* Badges */}
<div className="mt-3 flex gap-2">
<SkeletonText className="h-5 w-14 rounded-md" />
</div>
</div>

{/* Right side - Event details */}
<div className="flex min-w-0 flex-1 flex-col space-y-2">
{/* Event title */}
<SkeletonText className="h-5 w-36" />
{/* Event description */}
<SkeletonText className="h-4 w-48" />
{/* Attendees */}
<SkeletonText className="h-4 w-56" />
</div>

{/* Action buttons - only visible on larger screens */}
<div className="ml-4 hidden flex-shrink-0 gap-2 sm:flex">
<SkeletonText className="h-9 w-20 rounded-md" />
<SkeletonText className="h-9 w-20 rounded-md" />
<SkeletonText className="h-9 w-9 rounded-md" />
</div>

{/* Mobile menu button */}
<div className="ml-4 flex flex-shrink-0 sm:hidden">
<SkeletonText className="h-9 w-9 rounded-md" />
</div>
</li>
);
}
Loading
Loading