Files
Rajiv SahalandGitHub 0757b00db7 feat: list schedules atom (#24205)
* feat: add api v2 endpoint to fetch all user schedules

* chore: update typing

* feat: custom hook to fetch all user schedules

* refactor: shift logic to transform schedules into function of its own

* init: list schedules atom

* feat: api endpoints for list schedules atom

* refactor: accept redirect url as prop

* fix: pass redirect url prop

* integrate list schedules atom with availability settings

* refactor: extract types to be reused in api v2 endpoints

* skip availability settings page for the time being until we have api v2 endpoints in prod

* feat: add docs for list schedules atom

* fixup

* export Schedule type

* make sure we always have a default schedule if user deletes his default schedule

* chore: implement code rabbit feedback

* chore: implement PR feedback

* fix: resolve merge conflicts

* fix: import path

* update platform libraries

* fix: type check

* resolve  merge conflicts

* update atoms export

* update platform libraries schedule

* chore: arrange atoms in alphabetical order

* update atoms controller to include endpoints for list schedules atom

* add create atom scheule atom

* fix: invalidate schedules on new schedule creation

* chore: add changesets
2025-10-15 17:57:22 +02:00

148 lines
5.1 KiB
TypeScript

"use client";
import Link from "next/link";
import { Fragment } from "react";
import { availabilityAsString } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { sortAvailabilityStrings } from "@calcom/lib/weekstart";
import type { RouterOutputs } from "@calcom/trpc/react";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import {
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@calcom/ui/components/dropdown";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
export function ScheduleListItem({
schedule,
deleteFunction,
displayOptions,
updateDefault,
isDeletable,
duplicateFunction,
redirectUrl,
}: {
schedule: RouterOutputs["viewer"]["availability"]["list"]["schedules"][number];
deleteFunction: ({ scheduleId }: { scheduleId: number }) => void;
displayOptions?: {
timeZone?: string;
hour12?: boolean;
weekStart?: string;
};
isDeletable: boolean;
updateDefault: ({ scheduleId, isDefault }: { scheduleId: number; isDefault: boolean }) => void;
duplicateFunction: ({ scheduleId }: { scheduleId: number }) => void;
redirectUrl: string;
}) {
const { t, i18n } = useLocale();
return (
<li key={schedule.id}>
<div className="hover:bg-muted flex items-center justify-between px-3 py-5 transition sm:px-4">
<div className="group flex w-full items-center justify-between ">
<Link href={redirectUrl} className="flex-grow truncate text-sm" title={schedule.name}>
<div className="space-x-2 rtl:space-x-reverse">
<span className="text-emphasis truncate font-medium">{schedule.name}</span>
{schedule.isDefault && (
<Badge variant="gray" className="text-xs">
{t("default")}
</Badge>
)}
</div>
<p className="text-subtle mt-1">
{schedule.availability
.filter((availability) => !!availability.days.length)
.map((availability) =>
availabilityAsString(availability, {
locale: i18n.language,
hour12: displayOptions?.hour12,
})
)
// sort the availability strings as per user's weekstart (settings)
.sort(sortAvailabilityStrings(i18n.language, displayOptions?.weekStart))
.map((availabilityString, index) => (
<Fragment key={index}>
{availabilityString}
<br />
</Fragment>
))}
{(schedule.timeZone || displayOptions?.timeZone) && (
<span className="my-1 flex items-center first-letter:text-xs">
<Icon name="globe" className="h-3.5 w-3.5" />
&nbsp;{schedule.timeZone ?? displayOptions?.timeZone}
</span>
)}
</p>
</Link>
</div>
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
data-testid="schedule-more"
type="button"
variant="icon"
color="secondary"
StartIcon="ellipsis"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
{!schedule.isDefault && (
<DropdownMenuItem className="min-w-40 focus:ring-muted">
<DropdownItem
type="button"
StartIcon="star"
onClick={() => {
updateDefault({
scheduleId: schedule.id,
isDefault: true,
});
}}>
{t("set_as_default")}
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuItem className="outline-none">
<DropdownItem
type="button"
data-testid={`schedule-duplicate${schedule.id}`}
StartIcon="copy"
onClick={() => {
duplicateFunction({
scheduleId: schedule.id,
});
}}>
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="min-w-40 focus:ring-muted">
<DropdownItem
type="button"
color="destructive"
StartIcon="trash"
data-testid="delete-schedule"
className="rounded-t-none"
onClick={() => {
if (!isDeletable) {
showToast(t("requires_at_least_one_schedule"), "error");
} else {
deleteFunction({
scheduleId: schedule.id,
});
}
}}>
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
</li>
);
}