chore: remove not used code related to filters on /bookings (#19450)

* chore: remove filter-related code that is no longer in use

* remove groupBy.ts
This commit is contained in:
Eunjae Lee
2025-02-26 09:42:19 +00:00
committed by GitHub
parent 3d3acba82a
commit 271d9a5f61
8 changed files with 1 additions and 399 deletions
@@ -1,113 +0,0 @@
import { useSession } from "next-auth/react";
import { Fragment, useMemo, useState } from "react";
import {
FilterCheckboxField,
FilterCheckboxFieldsContainer,
} from "@calcom/features/filters/components/TeamsFilter";
import type { IEventTypeFilter } from "@calcom/features/filters/types/filter";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { FilterSearchField } from "@calcom/ui";
import { AnimatedPopover, Divider, Icon } from "@calcom/ui";
import { groupBy } from "../groupBy";
import { useFilterQuery } from "../lib/useFilterQuery";
type GroupedEventTypeState = Record<
string,
{
team: {
id: number;
name: string;
} | null;
id: number;
title: string;
slug: string;
}[]
>;
export const EventTypeFilter = () => {
const { t } = useLocale();
const { data: user } = useSession();
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery();
const [search, setSearch] = useState("");
const eventTypes = trpc.viewer.eventTypes.listWithTeam.useQuery(undefined, {
enabled: !!user,
});
const groupedEventTypes: GroupedEventTypeState | null = useMemo(() => {
const data = eventTypes.data;
if (!data) {
return null;
}
// Will be handled up the tree to redirect
// Group event types by team
const grouped = groupBy<IEventTypeFilter>(
data.filter((el) => el.team),
(item) => item?.team?.name || ""
); // Add the team name
const individualEvents = data.filter((el) => !el.team);
// push individual events to the start of grouped array
return individualEvents.length > 0 ? { user_own_event_types: individualEvents, ...grouped } : grouped;
}, [eventTypes.data]);
if (!eventTypes.data) return null;
const isEmpty = eventTypes.data.length === 0;
const getTextForPopover = () => {
const eventTypeIds = query.eventTypeIds;
if (eventTypeIds) {
return `${t("number_selected", { count: eventTypeIds.length })}`;
}
return `${t("all")}`;
};
return (
<AnimatedPopover text={getTextForPopover()} prefix={`${t("event_type")}: `}>
{!isEmpty ? (
<FilterCheckboxFieldsContainer>
<FilterSearchField
placeholder={t("search")}
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<FilterCheckboxField
id="all"
icon={<Icon name="link" className="h-4 w-4" />}
checked={!query.eventTypeIds?.length}
onChange={removeAllQueryParams}
label={t("all_event_types_filter_label")}
/>
<Divider />
{groupedEventTypes &&
Object.keys(groupedEventTypes).map((teamName) => (
<Fragment key={teamName}>
<div className="text-subtle px-4 py-2 text-xs font-medium uppercase leading-none">
{teamName === "user_own_event_types" ? t("individual") : teamName}
</div>
{groupedEventTypes[teamName]
.filter((eventType) => eventType.title.toLowerCase().includes(search.toLowerCase()))
.map((eventType) => (
<FilterCheckboxField
key={eventType.id}
checked={query.eventTypeIds?.includes(eventType.id)}
onChange={(e) => {
if (e.target.checked) {
pushItemToKey("eventTypeIds", eventType.id);
} else if (!e.target.checked) {
removeItemByKeyAndValue("eventTypeIds", eventType.id);
}
}}
label={eventType.title}
/>
))}
</Fragment>
))}
</FilterCheckboxFieldsContainer>
) : (
<h2 className="text-default px-4 py-2 text-sm font-medium">{t("no_options_available")}</h2>
)}
</AnimatedPopover>
);
};
@@ -1,35 +0,0 @@
import type { Dispatch, SetStateAction } from "react";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Badge, Button, Tooltip } from "@calcom/ui";
export interface FilterToggleProps {
setIsFiltersVisible: Dispatch<SetStateAction<boolean>>;
}
export function FilterToggle({ setIsFiltersVisible }: FilterToggleProps) {
const {
data: { teamIds, userIds, eventTypeIds },
} = useFilterQuery();
const { t } = useLocale();
function toggleFiltersVisibility() {
setIsFiltersVisible((prev) => !prev);
}
return (
<Button color="secondary" onClick={toggleFiltersVisibility} className="mb-4" StartIcon="filter">
<div className="flex items-center">
<Tooltip content={t("filters")}>
<div className="mx-2">{t("filters")}</div>
</Tooltip>
{(teamIds || userIds || eventTypeIds) && (
<Badge variant="gray" rounded>
{(teamIds ? 1 : 0) + (userIds ? 1 : 0) + (eventTypeIds ? 1 : 0)}
</Badge>
)}
</div>
</Button>
);
}
@@ -1,49 +0,0 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useSearchParams } from "next/navigation";
import { PeopleFilter } from "@calcom/features/bookings/components/PeopleFilter";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { StartTimeFilters } from "@calcom/features/filters/components/StartTimeFilters";
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip, Button } from "@calcom/ui";
import { EventTypeFilter } from "./EventTypeFilter";
export interface FiltersContainerProps {
isFiltersVisible: boolean;
}
export function FiltersContainer({ isFiltersVisible }: FiltersContainerProps) {
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
const { removeAllQueryParams } = useFilterQuery();
const { t } = useLocale();
const searchParams = useSearchParams();
const validFilterKeys = ["userIds", "eventTypeIds", "upIds", "teamIds", "afterStartDate", "beforeEndDate"];
const hasValidQueryParams = Array.from(searchParams?.keys() ?? []).some((key) =>
validFilterKeys.includes(key)
);
return (
<div ref={animationParentRef}>
{isFiltersVisible ? (
<div className="no-scrollbar mb-2 flex w-full space-x-2 overflow-x-scroll rtl:space-x-reverse">
<PeopleFilter />
<EventTypeFilter />
<TeamsFilter />
<StartTimeFilters />
<Tooltip content={t("remove_filters")}>
<Button
disabled={!hasValidQueryParams}
color="secondary"
type="button"
onClick={removeAllQueryParams}>
{t("remove_filters")}
</Button>
</Tooltip>
</div>
) : null}
</div>
);
}
@@ -1,97 +0,0 @@
import { useState } from "react";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import {
FilterCheckboxField,
FilterCheckboxFieldsContainer,
} from "@calcom/features/filters/components/TeamsFilter";
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { AnimatedPopover, Avatar, Divider, FilterSearchField, Icon, Button } from "@calcom/ui";
export const PeopleFilter = () => {
const { t } = useLocale();
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery();
const isAdmin = currentOrg?.user.role === "ADMIN" || currentOrg?.user.role === "OWNER";
const hasPermToView = !currentOrg?.isPrivate || isAdmin;
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeAllQueryParams } = useFilterQuery();
const [searchText, setSearchText] = useState("");
const debouncedSearch = useDebounce(searchText, 500);
const queryMembers = trpc.viewer.teams.legacyListMembers.useInfiniteQuery(
{ limit: 10, searchText: debouncedSearch, includeEmail: true },
{
enabled: true,
getNextPageParam: (lastPage) => lastPage.nextCursor,
}
);
const { ref: observerRef } = useInViewObserver(() => {
if (queryMembers.hasNextPage && !queryMembers.isFetching) {
queryMembers.fetchNextPage();
}
}, document.querySelector('[role="dialog"]'));
const filteredMembers = queryMembers?.data?.pages.flatMap((page) => page.members);
const getTextForPopover = () => {
const userIds = query.userIds;
if (userIds) {
return `${t("number_selected", { count: userIds.length })}`;
}
return `${t("all")}`;
};
if (!hasPermToView) {
return null;
}
return (
<AnimatedPopover text={getTextForPopover()} prefix={`${t("people")}: `}>
<FilterCheckboxFieldsContainer>
<FilterSearchField onChange={(e) => setSearchText(e.target.value)} placeholder={t("search")} />
<FilterCheckboxField
id="all"
icon={<Icon name="user" className="h-4 w-4" />}
checked={!query.userIds?.length}
onChange={removeAllQueryParams}
label={t("all_users_filter_label")}
/>
<Divider />
{filteredMembers?.map((member) => (
<FilterCheckboxField
key={member.id}
id={member.id.toString()}
label={member?.name ?? member.username ?? t("no_name")}
checked={!!query.userIds?.includes(member.id)}
onChange={(e) => {
if (e.target.checked) {
pushItemToKey("userIds", member.id);
} else if (!e.target.checked) {
removeItemByKeyAndValue("userIds", member.id);
}
}}
icon={<Avatar alt={`${member?.id} avatar`} imageSrc={member.avatarUrl} size="xs" />}
/>
))}
<div className="text-default text-center" ref={observerRef} data-testid="people-filter">
<Button
color="minimal"
loading={queryMembers.isFetchingNextPage}
disabled={!queryMembers.hasNextPage}
onClick={() => queryMembers.fetchNextPage()}
data-testid="people-filter">
{queryMembers.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</FilterCheckboxFieldsContainer>
</AnimatedPopover>
);
};
-18
View File
@@ -1,18 +0,0 @@
type KeySelector<T> = (item: T) => string;
export function groupBy<T>(array: Iterable<T>, keySelector: KeySelector<T>): Record<string, T[]> {
return Array.from(array).reduce(
(acc: Record<string, T[]>, item: T) => {
const key = keySelector(item);
if (key in acc) {
// found key, push new item into existing array
acc[key].push(item);
} else {
// did not find key, create new array
acc[key] = [item];
}
return acc;
},
{} // start with empty object
);
}
@@ -1,80 +0,0 @@
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import type { Dayjs } from "@calcom/dayjs";
import dayjs from "@calcom/dayjs";
import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import { DateRangePicker } from "@calcom/ui";
export const StartTimeFilters = () => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const { data: query } = useFilterQuery();
const getQueryDate = (param: string) =>
searchParams?.get(param) ? dayjs(searchParams?.get(param)) : undefined;
const [afterStartDate, setAfterStartDate] = useState<Dayjs | undefined>(() =>
getQueryDate("afterStartDate")
);
const [beforeEndDate, setBeforeEndDate] = useState<Dayjs | undefined>(() => getQueryDate("beforeEndDate"));
const startValue = afterStartDate?.toDate();
const endValue = beforeEndDate?.toDate();
const updateUrlParams = (newStartDate: Dayjs, newEndDate: Dayjs) => {
const search = new URLSearchParams(searchParams?.toString());
Object.entries(query).forEach(([key, value]) => {
if (key !== "afterStartDate" && key !== "beforeEndDate") {
search.set(key, String(value));
}
});
if (newStartDate) {
search.set("afterStartDate", newStartDate.startOf("day").format("YYYY-MM-DDTHH:mm:ss"));
}
if (newEndDate) {
search.set("beforeEndDate", newEndDate.endOf("day").format("YYYY-MM-DDTHH:mm:ss"));
}
router.replace(`${pathname}?${search.toString()}`);
};
useEffect(() => {
//if params has date range
if (searchParams?.has("afterStartDate") && searchParams?.has("beforeEndDate")) {
setAfterStartDate(getQueryDate("afterStartDate"));
setBeforeEndDate(getQueryDate("beforeEndDate"));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
useEffect(() => {
if (Object.keys(query).length === 1 && "status" in query && afterStartDate && beforeEndDate) {
setAfterStartDate(undefined);
setBeforeEndDate(undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query]);
return (
<DateRangePicker
minDate={null}
dates={{ startDate: startValue, endDate: endValue }}
onDatesChange={(values) => {
const newAfterStartDate = values.startDate ? dayjs(values.startDate) : undefined;
const newBeforeEndDate = values.endDate ? dayjs(values.endDate) : undefined;
setAfterStartDate(newAfterStartDate);
setBeforeEndDate(newBeforeEndDate);
if (newAfterStartDate && newBeforeEndDate) {
updateUrlParams(newAfterStartDate, newBeforeEndDate);
}
}}
/>
);
};
@@ -64,10 +64,6 @@ export const legacyListMembers = async ({ ctx, input }: ListMembersOptions) => {
{ username: { contains: input.searchText, mode: "insensitive" } },
];
if (input.includeEmail) {
searchTextClauses.push({ email: { contains: input.searchText, mode: "insensitive" } });
}
// Fetch unique users through memberships
const memberships = await prisma.membership.findMany({
where: {
@@ -7,9 +7,7 @@ export const ZListMembersInputSchema = z.object({
cursor: z.number().nullish(),
});
export const ZLegacyListMembersInputSchema = ZListMembersInputSchema.extend({
includeEmail: z.boolean().optional(),
});
export const ZLegacyListMembersInputSchema = ZListMembersInputSchema.extend({});
export type TListMembersInputSchema = z.infer<typeof ZListMembersInputSchema>;
export type TLegacyListMembersInputSchema = z.infer<typeof ZLegacyListMembersInputSchema>;