EventTypeFilter UI working

This commit is contained in:
sean-brydon 2022-12-07 10:51:14 +00:00
parent 1a26a254ab
commit d078378303
9 changed files with 183 additions and 19 deletions

View File

@ -5,9 +5,10 @@ import { Fragment } from "react";
import { z } from "zod";
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
import BookingLayout from "@calcom/features/bookings/layout/BookingLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterInputs, RouterOutputs, trpc } from "@calcom/trpc/react";
import { Alert, BookingLayout, Button, EmptyScreen, Icon } from "@calcom/ui";
import { Alert, Button, EmptyScreen, Icon } from "@calcom/ui";
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
@ -163,7 +164,7 @@ export default function Bookings() {
</>
)}
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:mx-6 xl:pt-0">
<div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen
Icon={Icon.FiCalendar}
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}

View File

@ -1392,5 +1392,6 @@
"options": "Options",
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio"
"radio": "Radio",
"individual":"Individual"
}

View File

@ -0,0 +1,93 @@
import { useSession } from "next-auth/react";
import { Fragment, useState, useEffect } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc, RouterOutputs } from "@calcom/trpc/react";
import { Avatar, AnimatedPopover } from "@calcom/ui";
type EventTypes = RouterOutputs["viewer"]["eventTypes"]["listWithTeam"];
type EventType = EventTypes[0];
type KeySelector<T> = (item: T) => string;
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
);
}
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 eventTypes = trpc.viewer.eventTypes.listWithTeam.useQuery();
const [groupedEventTypes, setGroupedEventTypes] = useState<GroupedEventTypeState>();
// Will be handled up the tree to redirect
useEffect(() => {
if (eventTypes.data) {
// Group event types by team
const grouped = groupBy<EventType>(
eventTypes.data.filter((el) => el.team),
(item) => item?.team?.name || ""
); // Add the team name
const individualEvents = eventTypes.data.filter((el) => !el.team);
// push indivdual events to the start of grouped array
setGroupedEventTypes({ user_own_event_types: individualEvents, ...grouped });
}
}, [eventTypes.data, user]);
if (!user) return null;
return (
<AnimatedPopover text={t("event_type")}>
<div className="">
{groupedEventTypes &&
Object.keys(groupedEventTypes).map((teamName) => (
<Fragment key={teamName}>
<div className="p-4 text-xs font-medium uppercase leading-none text-gray-500">
{teamName === "user_own_event_types" ? t("individual") : teamName}
</div>
{groupedEventTypes[teamName].map((eventType) => (
<Fragment key={eventType.id}>
<div className="item-center flex px-4 py-[6px]">
<p className="block self-center text-sm font-medium text-gray-700">{eventType.title}</p>
<div className="ml-auto">
<input
type="checkbox"
name=""
id=""
className="text-primary-600 focus:ring-primary-500 mr-2 h-4 w-4 rounded border-gray-300 "
/>
</div>
</div>
</Fragment>
))}
</Fragment>
))}
</div>
</AnimatedPopover>
);
};

View File

@ -1,36 +1,30 @@
import React, { ComponentProps } from "react";
import type { VerticalTabItemProps } from "../";
import { HorizontalTabs, VerticalTabs } from "../";
import { Icon } from "../../../Icon";
import Shell from "../Shell";
import type { HorizontalTabItemProps } from "../navigation/tabs/HorizontalTabItem";
import { HorizontalTabs, Shell } from "@calcom/ui";
import { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui/v2";
import { EventTypeFilter } from "../components/EventTypeFilter";
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
{
name: "upcoming",
href: "/bookings/upcoming",
icon: Icon.FiCalendar,
},
{
name: "unconfirmed",
href: "/bookings/unconfirmed",
icon: Icon.FiInbox,
},
{
name: "recurring",
href: "/bookings/recurring",
icon: Icon.FiRotateCcw,
},
{
name: "past",
href: "/bookings/past",
icon: Icon.FiSunset,
},
{
name: "cancelled",
href: "/bookings/cancelled",
icon: Icon.FiSlash,
},
];
@ -40,12 +34,12 @@ export default function BookingLayout({
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
return (
<Shell {...rest}>
<div className="flex flex-col sm:space-x-2 xl:flex-row">
<div className="hidden xl:block">
<VerticalTabs tabs={tabs} sticky />
</div>
<div className="block xl:hidden">
<div className="flex max-w-6xl flex-col sm:space-x-2">
<div className="flex flex-col lg:flex-row">
<HorizontalTabs tabs={tabs} />
<div className="flex space-x-2">
<EventTypeFilter />
</div>
</div>
<main className="w-full max-w-6xl">{children}</main>
</div>

View File

@ -352,6 +352,35 @@ export const eventTypesRouter = router({
},
});
}),
listWithTeam: authedProcedure.query(async ({ ctx }) => {
return await ctx.prisma.eventType.findMany({
where: {
OR: [
{ userId: ctx.user.id },
{
team: {
members: {
some: {
userId: ctx.user.id,
},
},
},
},
],
},
select: {
id: true,
team: {
select: {
id: true,
name: true,
},
},
title: true,
slug: true,
},
});
}),
create: authedProcedure.input(createEventTypeInput).mutation(async ({ ctx, input }) => {
const { schedulingType, teamId, ...rest } = input;
const userId = ctx.user.id;

View File

@ -26,3 +26,4 @@ export {
} from "./form";
export { TopBanner } from "./top-banner";
export type { TopBannerProps } from "./top-banner";
export { AnimatedPopover } from "./popover/index";

View File

@ -0,0 +1,44 @@
import * as Popover from "@radix-ui/react-popover";
import React from "react";
import { classNames } from "@calcom/lib";
import { Icon } from "@calcom/ui";
export const AnimatedPopover = ({
text,
count,
children,
}: {
text: string;
count?: number;
children: React.ReactNode;
}) => {
const [open, setOpen] = React.useState(false);
return (
<Popover.Root onOpenChange={setOpen} modal={true}>
<Popover.Trigger asChild>
<div
className="item-center mb-2 flex h-9 justify-between whitespace-nowrap rounded-md border border-gray-300 py-2 px-3 text-sm
placeholder:text-gray-400 hover:cursor-pointer hover:border-gray-400 focus:border-neutral-300
focus:outline-none focus:ring-2 focus:ring-neutral-800 focus:ring-offset-1">
<div className="flex">
<div>
{text}
{count && count > 0 && (
<div className="flex h-4 w-4 items-center justify-center rounded-full">{count}</div>
)}
</div>
<Icon.FiChevronDown
className={classNames("mt-auto ml-2 transition-transform duration-150", open && "rotate-180")}
/>
</div>
</div>
</Popover.Trigger>
<Popover.Content side="bottom" align="center" asChild>
<div className="absolute z-50 mt-2 w-56 -translate-x-3/4 rounded-md bg-white ring-1 ring-black ring-opacity-5 focus-within:outline-none">
{children}
</div>
</Popover.Content>
</Popover.Root>
);
};

View File

@ -24,6 +24,7 @@ export {
TextAreaField,
TextField,
TopBanner,
AnimatedPopover,
} from "./components";
export type { AvatarProps, BadgeProps, ButtonBaseProps, ButtonProps, TopBannerProps } from "./components";
export { default as CheckboxField } from "./components/form/checkbox/Checkbox";
@ -97,7 +98,6 @@ export { ToggleGroup } from "./v2/core/form/ToggleGroup";
export { default as ImageUploader } from "./v2/core/ImageUploader";
export { default as AdminLayout, getLayout as getAdminLayout } from "./v2/core/layouts/AdminLayout";
export { default as AppsLayout } from "./v2/core/layouts/AppsLayout";
export { default as BookingLayout } from "./v2/core/layouts/BookingLayout";
export { default as InstalledAppsLayout } from "./v2/core/layouts/InstalledAppsLayout";
export { default as SettingsLayout, getLayout as getSettingsLayout } from "./v2/core/layouts/SettingsLayout";
export { default as WizardLayout, getLayout as getWizardLayout } from "./v2/core/layouts/WizardLayout";

View File

@ -17,6 +17,7 @@
"@calcom/trpc": "*",
"@formkit/auto-animate": "^1.0.0-beta.5",
"@radix-ui/react-dialog": "^1.0.0",
"@radix-ui/react-popover": "^1.0.2",
"@radix-ui/react-portal": "^1.0.0",
"@radix-ui/react-select": "^0.1.1",
"@tanstack/react-query": "^4.3.9",