Files
calendar/apps/web/modules/ee/workflows/views/WorkflowsPage.tsx
T
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
404d46c991 refactor: Move trpc-dependent components from features to web [2] (#26420)
* fix

* move event types components to web

* update import paths

* mv apps components

* migrate form builder

* fix

* mv sso

* fix

* mv

* update import paths

* update import paths

* mv

* mv

* mv

* fix

* update Booker

* fix

* fix

* fix

* fix

* mv video

* mv embed components to web

* update import paths

* mv calendar weekly view components

* update import paths

* fix

* fixp

* fix

* fix

* fix

* fix: update FormBuilder imports to use @calcom/features/form-builder

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: update broken import paths after file migrations

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: correct import paths for platform atoms and moved components

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: apply CSS type fixes and add missing atoms exports

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: resolve type errors in test files after component migrations

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: resolve remaining type errors in test files

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix

* migrate

* fix: resolve type errors in test and mock files

- Add missing bookingForm, bookerFormErrorRef, instantConnectCooldownMs to Booker.test.tsx bookings prop
- Add all required BookerEvent properties to event.mock.ts
- Add vi import from vitest to all mock files
- Fix date parameter types in packages/dayjs/__mocks__/index.ts
- Add verificationCode and setVerificationCode to test-utils.tsx mock store
- Remove children.type access in Section.tsx mock to fix type error
- Fix lint issues: remove unused React imports, use import type where needed, add return types
- Add biome-ignore comments for pre-existing lint warnings in test files

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* migrate

* migrate

* migrate

* update import paths

* update import paths

* update import paths

* fix

* migrate data table

* migrate data table

* fix

* fix

* fix

* migrate insights components

* migrate insights components

* fix

* mv

* update import paths

* fix

* fix

* fix

* fix

* fix

* fix: resolve type errors in test mocks

- Booker.test.tsx: Add all required UseFormReturn methods to bookingForm mock
- event.mock.ts: Fix entity, subsetOfHosts, instantMeetingParameters, fieldTranslations, image types
- dayjs/__mocks__/index.ts: Use Object.assign for proper typing of mock properties
- Section.tsx: Change 'class' to 'className' in JSX with biome-ignore comment

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing hasDataErrors and dataErrors to bookings.errors mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing loadingStates properties to bookings mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing slots properties (setTentativeSelectedTimeslots, tentativeSelectedTimeslots, slotReservationId)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: update quickAvailabilityChecks to include utcEndIso and use valid status type

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing bookerForm properties (formName, beforeVerifyEmail, formErrors)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing UseFormReturn properties to bookerForm.bookingForm mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add hasFormErrors and formErrors to bookerForm.formErrors mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add hasFormErrors and formErrors to bookerForm.errors mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing isError property to mockEvent

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: use complete BookerEvent mock in Booker.test.tsx

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: use branded bookingFields type in event mock

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: add missing schedule mock properties (isError, isSuccess, isLoading, dataUpdatedAt)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* revert

* fix

* fix

* fix

* fix build

* fix

* fix

* fix

* fix: correct AddMembersWithSwitch test wrapper to use initial assignAllTeamMembers value

- Fixed test wrapper to initialize useState with componentProps.assignAllTeamMembers
  instead of hardcoded false, allowing tests to properly test different states
- Updated test expectations for ALL_TEAM_MEMBERS_ENABLED_AND_SEGMENT_NOT_APPLICABLE
  state to match actual component behavior (toggle should be present and checked)
- Fixed 'should show Segment when toggled on' test to start with assignAllTeamMembers: false
  to properly test the flow of enabling it
- Added explicit types to satisfy biome lint requirements

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* fix: use JSX.Element instead of React.JSX.Element for type compatibility

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-05 12:53:51 -03:00

235 lines
8.8 KiB
TypeScript

"use client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import type { Dispatch, SetStateAction } from "react";
import { useState } from "react";
import LicenseRequired from "~/ee/common/components/LicenseRequired";
import type { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
import { FilterResults } from "@calcom/features/filters/components/FilterResults";
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Avatar } from "@calcom/ui/components/avatar";
import { AnimatedPopover } from "@calcom/ui/components/popover";
import Shell, { ShellMain } from "@calcom/web/modules/shell/Shell";
import EmptyScreen from "@calcom/web/modules/ee/workflows/components/EmptyScreen";
import SkeletonLoader from "@calcom/web/modules/ee/workflows/components/SkeletonLoaderList";
import {
WorkflowCreationDialog,
useWorkflowCreation,
} from "@calcom/web/modules/ee/workflows/components/WorkflowCreationDialog";
import WorkflowList from "@calcom/web/modules/ee/workflows/components/WorkflowListPage";
import { CreateButtonWithTeamsList } from "~/ee/teams/components/createButton/CreateButtonWithTeamsList";
type PageProps = {
filteredList?: Awaited<ReturnType<typeof WorkflowRepository.getFilteredList>>;
};
function WorkflowsPage({ filteredList }: PageProps) {
const { t } = useLocale();
const session = useSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- router is kept for future use
const router = useRouter();
const routerQuery = useRouterQuery();
const filters = getTeamsFiltersFromQuery(routerQuery);
const { showDialog, setShowDialog, pendingTeamId, openDialog } = useWorkflowCreation();
const { data, isPending: _isPending } = trpc.viewer.workflows.filteredList.useQuery(
{
filters,
},
{
enabled: !filteredList,
}
);
const filteredWorkflows = filteredList ?? data;
const isPending = filteredList ? false : _isPending;
return (
<Shell withoutMain>
<LicenseRequired>
<ShellMain
heading={t("workflows")}
subtitle={t("workflows_to_automate_notifications")}
title={t("workflows")}
description={t("workflows_to_automate_notifications")}
CTA={
session.data?.hasValidLicense ? (
<CreateButtonWithTeamsList
subtitle={t("new_workflow_subtitle").toUpperCase()}
createFunction={openDialog}
onlyShowWithNoTeams={true}
includeOrg={true}
withPermission={{
permission: "workflow.create",
fallbackRoles: ["ADMIN", "OWNER"],
}}
/>
) : null
}>
<>
{filteredWorkflows?.totalCount ? (
<div className="mb-2 flex">
<TeamsFilter />
<div className="mb-4 ml-auto">
<CreateButtonWithTeamsList
subtitle={t("new_workflow_subtitle").toUpperCase()}
createFunction={openDialog}
onlyShowWithTeams={true}
includeOrg={true}
withPermission={{
permission: "workflow.create",
fallbackRoles: ["ADMIN", "OWNER"],
}}
/>
</div>
</div>
) : null}
<FilterResults
queryRes={{ isPending, data: filteredWorkflows }}
emptyScreen={<EmptyScreen isFilteredView={false} />}
noResultsScreen={<EmptyScreen isFilteredView={true} />}
SkeletonLoader={SkeletonLoader}>
<WorkflowList workflows={filteredWorkflows?.filtered} />
</FilterResults>
</>
</ShellMain>
<WorkflowCreationDialog open={showDialog} onOpenChange={setShowDialog} teamId={pendingTeamId} />
</LicenseRequired>
</Shell>
);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Filter component is kept for future use
const Filter = (props: {
profiles: {
readOnly?: boolean | undefined;
slug: string | null;
name: string | null;
teamId: number | null | undefined;
image?: string | undefined | null;
}[];
checked: {
userId: number | null;
teamIds: number[];
};
setChecked: Dispatch<
SetStateAction<{
userId: number | null;
teamIds: number[];
}>
>;
}) => {
const session = useSession();
const userId = session.data?.user.id || 0;
const user = session.data?.user.name || "";
const userName = session.data?.user.username;
const userAvatar = `${WEBAPP_URL}/${userName}/avatar.png`;
const teams = props.profiles.filter((profile) => !!profile.teamId);
const { checked, setChecked } = props;
const [noFilter, setNoFilter] = useState(true);
return (
<div className={classNames("-mb-2", noFilter ? "w-16" : "w-[100px]")}>
<AnimatedPopover text={noFilter ? "All" : "Filtered"}>
<div className="item-center focus-within:bg-subtle hover:bg-cal-muted flex px-4 py-[6px] transition hover:cursor-pointer">
<Avatar
imageSrc={userAvatar || ""}
size="sm"
alt={`${user} Avatar`}
className="self-center"
asChild
/>
<label
htmlFor="yourWorkflows"
className="text-default ml-2 mr-auto self-center truncate text-sm font-medium">
{user}
</label>
<input
id="yourWorkflows"
type="checkbox"
className="text-emphasis focus:ring-emphasis dark:text-muted border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded transition "
checked={!!checked.userId}
onChange={(e) => {
if (e.target.checked) {
setChecked({ userId: userId, teamIds: checked.teamIds });
if (checked.teamIds.length === teams.length) {
setNoFilter(true);
}
} else if (!e.target.checked) {
setChecked({ userId: null, teamIds: checked.teamIds });
setNoFilter(false);
}
}}
/>
</div>
{teams.map((profile) => (
<div
className="item-center focus-within:bg-subtle hover:bg-cal-muted flex px-4 py-[6px] transition hover:cursor-pointer"
key={`${profile.teamId || 0}`}>
<Avatar
imageSrc={profile.image || ""}
size="sm"
alt={`${profile.slug} Avatar`}
className="self-center"
asChild
/>
<label
htmlFor={profile.slug || ""}
className="text-default ml-2 mr-auto select-none self-center truncate text-sm font-medium hover:cursor-pointer">
{profile.slug}
</label>
<input
id={profile.slug || ""}
name={profile.slug || ""}
type="checkbox"
checked={checked.teamIds?.includes(profile.teamId || 0)}
onChange={(e) => {
if (e.target.checked) {
const updatedChecked = checked;
updatedChecked.teamIds.push(profile.teamId || 0);
setChecked({ userId: checked.userId, teamIds: [...updatedChecked.teamIds] });
if (checked.userId && updatedChecked.teamIds.length === teams.length) {
setNoFilter(true);
} else {
setNoFilter(false);
}
} else if (!e.target.checked) {
const index = checked.teamIds.indexOf(profile.teamId || 0);
if (index !== -1) {
const updatedChecked = checked;
updatedChecked.teamIds.splice(index, 1);
setChecked({ userId: checked.userId, teamIds: [...updatedChecked.teamIds] });
if (checked.userId && updatedChecked.teamIds.length === teams.length) {
setNoFilter(true);
} else {
setNoFilter(false);
}
}
}
}}
className="text-emphasis focus:ring-emphasis dark:text-muted border-default inline-flex h-4 w-4 place-self-center justify-self-end rounded transition "
/>
</div>
))}
</AnimatedPopover>
</div>
);
};
export default WorkflowsPage;