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
parent
2a0a293f8c
commit
4fcdc7bf5c
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue