added badge for unconfirmed bookings (#4912)

* added badge for unconfirmed bookings

* added link to unconfirmed bookings

* added rounded prop to badge, added unconfirmed badge to botto nav

* Supporting recurring events + bug fix

* Tooltip and hover style

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
pull/4931/head^2
Peer Richelsen 2022-10-10 18:00:09 +01:00 committed by GitHub
parent 2a0a293f8c
commit 4fcdc7bf5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 3 deletions

View File

@ -313,6 +313,7 @@
"past_bookings": "Your past bookings will show up here.",
"cancelled_bookings": "Your cancelled bookings will show up here.",
"unconfirmed_bookings": "Your unconfirmed bookings will show up here.",
"unconfirmed_bookings_tooltip": "Unconfirmed bookings",
"on": "on",
"and": "and",
"calendar_shows_busy_between": "Your calendar shows you as busy between",

View File

@ -0,0 +1,21 @@
import Link from "next/link";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import Badge from "@calcom/ui/v2/core/Badge";
export default function UnconfirmedBookingBadge() {
const { t } = useLocale();
const { data: unconfirmedBookingCount } = trpc.useQuery(["viewer.bookingUnconfirmedCount"]);
if (!unconfirmedBookingCount) return null;
else
return (
<Link href="/bookings/unconfirmed">
<a title={t("unconfirmed_bookings_tooltip")}>
<Badge rounded variant="orange" className="hover:bg-orange-800 hover:text-orange-100">
{unconfirmedBookingCount}
</Badge>
</a>
</Link>
);
}

View File

@ -587,6 +587,7 @@ const loggedInViewerRouter = createProtectedRouter()
status: {
notIn: [BookingStatus.CANCELLED],
},
userId: user.id,
},
});
@ -1437,6 +1438,34 @@ const loggedInViewerRouter = createProtectedRouter()
},
});
},
})
.query("bookingUnconfirmedCount", {
async resolve({ ctx }) {
const { prisma, user } = ctx;
const count = await prisma.booking.count({
where: {
status: BookingStatus.PENDING,
userId: user.id,
endTime: { gt: new Date() },
},
});
const recurringGrouping = await prisma.booking.groupBy({
by: ["recurringEventId"],
_count: {
recurringEventId: true,
},
where: {
recurringEventId: { not: { equals: null } },
status: { equals: "PENDING" },
userId: user.id,
},
});
return recurringGrouping.reduce((prev, current) => {
// recurringEventId is the total number of recurring instances for a booking
// we need to substract all but one, to represent a single recurring booking
return prev - (current._count?.recurringEventId - 1);
}, count);
},
});
export const viewerRouter = createRouter()

View File

@ -20,6 +20,7 @@ import getWebhooks from "@calcom/features/webhooks/utils/getWebhooks";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { bookingConfirmPatchBodySchema } from "@calcom/prisma/zod-utils";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";

View File

@ -24,17 +24,19 @@ export type BadgeProps = {
size?: keyof typeof classNameBySize;
StartIcon?: Icon;
bold?: boolean;
rounded?: boolean;
} & JSX.IntrinsicElements["div"];
export const Badge = function Badge(props: BadgeProps) {
const { variant, className, size = "default", StartIcon, bold, ...passThroughProps } = props;
const { variant, className, size = "default", rounded, StartIcon, bold, ...passThroughProps } = props;
return (
<div
{...passThroughProps}
className={classNames(
"inline-flex items-center justify-center rounded py-0.5 px-[6px] text-xs",
"inline-flex items-center justify-center py-0.5 px-[6px] text-xs",
bold ? "font-semibold" : "font-normal",
rounded ? "min-w-5 min-h-5 rounded-full pt-1" : "rounded-md",
!StartIcon ? classNameBySize[size] : "",
badgeClassNameByVariant[variant],
className

View File

@ -8,6 +8,7 @@ import { Toaster } from "react-hot-toast";
import dayjs from "@calcom/dayjs";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge";
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
import CustomBranding from "@calcom/lib/CustomBranding";
@ -388,6 +389,7 @@ function UserDropdown({ small }: { small?: boolean }) {
export type NavigationItemType = {
name: string;
href: string;
badge?: React.ReactNode;
icon?: SVGComponent;
child?: NavigationItemType[];
pro?: true;
@ -406,6 +408,7 @@ export type NavigationItemType = {
const requiredCredentialNavigationItems = ["Routing Forms"];
const MORE_SEPARATOR_NAME = "more";
const navigation: NavigationItemType[] = [
{
name: "event_types_page_title",
@ -416,6 +419,7 @@ const navigation: NavigationItemType[] = [
name: "bookings",
href: "/bookings/upcoming",
icon: Icon.FiCalendar,
badge: <UnconfirmedBookingBadge />,
},
{
name: "availability",
@ -563,7 +567,10 @@ const NavigationItem: React.FC<{
/>
)}
{isLocaleReady ? (
<span className="hidden lg:inline">{t(item.name)}</span>
<span className="hidden w-full justify-between lg:flex">
<div className="flex">{t(item.name)}</div>
{item.badge && item.badge}
</span>
) : (
<SkeletonText className="h-3 w-32" />
)}
@ -619,6 +626,7 @@ const MobileNavigationItem: React.FC<{
<a
className="relative my-2 min-w-0 flex-1 overflow-hidden rounded-md py-2 px-1 text-center text-xs font-medium text-neutral-400 hover:bg-gray-200 hover:text-gray-700 focus:z-10 sm:text-sm [&[aria-current='page']]:text-gray-900"
aria-current={current ? "page" : undefined}>
{item.badge && <div className="absolute right-1 top-1">{item.badge}</div>}
{item.icon && (
<item.icon
className="mx-auto mb-1 block h-5 w-5 flex-shrink-0 text-center text-inherit [&[aria-current='page']]:text-gray-900"