From cbc9cd60d5b5595691dbbd364b40de3f59bbc6a9 Mon Sep 17 00:00:00 2001 From: Shane Maglangit Date: Sat, 11 Mar 2023 04:00:19 +0800 Subject: [PATCH] [CAL-988] Limit total appointment time per day/week/month/year (#7166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Peer Richelsen Co-authored-by: Omar López --- CONTRIBUTING.md | 3 +- README.md | 5 +- .../components/eventtype/EventLimitsTab.tsx | 278 +++++++++++------- apps/web/pages/event-types/[type]/index.tsx | 17 +- apps/web/public/static/locales/en/common.json | 6 +- apps/web/test/lib/checkBookingLimits.test.ts | 24 +- apps/web/test/lib/checkDurationLimits.test.ts | 128 ++++++++ .../app-store/routing-forms/DESCRIPTION.md | 2 +- packages/core/getUserAvailability.ts | 233 ++++++++++----- .../features/bookings/lib/handleNewBooking.ts | 8 +- packages/lib/defaultEvents.ts | 1 + packages/lib/getEventTypeById.ts | 4 +- packages/lib/index.ts | 2 + packages/lib/isBookingLimits.ts | 29 +- packages/lib/isDurationLimits.ts | 12 + packages/lib/server/checkBookingLimits.ts | 10 +- packages/lib/server/checkDurationLimits.ts | 60 ++++ packages/lib/server/index.ts | 3 +- packages/lib/server/queries/booking/index.ts | 23 ++ packages/lib/server/queries/index.ts | 1 + packages/lib/test/builder.ts | 1 + packages/lib/validateIntervalLimitOrder.ts | 15 + .../migration.sql | 2 + packages/prisma/schema.prisma | 4 +- packages/prisma/zod-utils.ts | 2 +- .../trpc/server/routers/viewer/eventTypes.ts | 14 +- packages/trpc/server/routers/viewer/slots.tsx | 2 + packages/types/Calendar.d.ts | 3 +- 28 files changed, 664 insertions(+), 228 deletions(-) create mode 100644 apps/web/test/lib/checkDurationLimits.test.ts create mode 100644 packages/lib/isDurationLimits.ts create mode 100644 packages/lib/server/checkDurationLimits.ts create mode 100644 packages/lib/server/queries/booking/index.ts create mode 100644 packages/lib/validateIntervalLimitOrder.ts create mode 100644 packages/prisma/migrations/20230214083325_add_duration_limits/migration.sql diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5590e6f019..80ca94703f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Contributions are what make the open source community such an amazing place to l - Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission. -## Priorities +## Priorities @@ -57,7 +57,6 @@ Contributions are what make the open source community such an amazing place to l
- ## Developing The development branch is `main`. This is the branch that all pull diff --git a/README.md b/README.md index f93f3f7483..239f4a3767 100644 --- a/README.md +++ b/README.md @@ -350,17 +350,16 @@ We have a list of [help wanted](https://github.com/calcom/cal.com/issues?q=is:is Bounties of cal - + ### Contributors - + - ### Translations diff --git a/apps/web/components/eventtype/EventLimitsTab.tsx b/apps/web/components/eventtype/EventLimitsTab.tsx index ffa7ba5e14..7a3328a547 100644 --- a/apps/web/components/eventtype/EventLimitsTab.tsx +++ b/apps/web/components/eventtype/EventLimitsTab.tsx @@ -1,9 +1,11 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import * as RadioGroup from "@radix-ui/react-radio-group"; import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; +import type { Key } from "react"; import React, { useEffect, useState } from "react"; import type { UseFormRegisterReturn } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form"; +import type { SingleValue } from "react-select"; import { classNames } from "@calcom/lib"; import type { DurationType } from "@calcom/lib/convertToNewDurationType"; @@ -11,8 +13,17 @@ import convertToNewDurationType from "@calcom/lib/convertToNewDurationType"; import findDurationType from "@calcom/lib/findDurationType"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { PeriodType } from "@calcom/prisma/client"; -import type { BookingLimit } from "@calcom/types/Calendar"; -import { Button, DateRangePicker, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui"; +import type { IntervalLimit } from "@calcom/types/Calendar"; +import { + Button, + DateRangePicker, + Input, + InputField, + Label, + Select, + SettingsToggle, + TextField, +} from "@calcom/ui"; import { FiPlus, FiTrash } from "@calcom/ui/components/icon"; const MinimumBookingNoticeInput = React.forwardRef< @@ -260,7 +271,34 @@ export const EventLimitsTab = ({ eventType }: Pick - + + + )} + /> +
+ ( + 0} + onCheckedChange={(active) => { + if (active) { + formMethods.setValue("durationLimits", { + PER_DAY: 60, + }); + } else { + formMethods.setValue("durationLimits", {}); + } + }}> + )} /> @@ -348,124 +386,158 @@ export const EventLimitsTab = ({ eventType }: Pick { +type IntervalLimitsKey = keyof IntervalLimit; + +const intervalOrderKeys = ["PER_DAY", "PER_WEEK", "PER_MONTH", "PER_YEAR"] as const; + +const INTERVAL_LIMIT_OPTIONS = intervalOrderKeys.map((key) => ({ + value: key as keyof IntervalLimit, + label: `Per ${key.split("_")[1].toLocaleLowerCase()}`, +})); + +type IntervalLimitItemProps = { + key: Key; + limitKey: IntervalLimitsKey; + step: number; + value: number; + textFieldSuffix?: string; + selectOptions: { value: keyof IntervalLimit; label: string }[]; + hasDeleteButton?: boolean; + onDelete: (intervalLimitsKey: IntervalLimitsKey) => void; + onLimitChange: (intervalLimitsKey: IntervalLimitsKey, limit: number) => void; + onIntervalSelect: (interval: SingleValue<{ value: keyof IntervalLimit; label: string }>) => void; +}; + +const IntervalLimitItem = ({ + limitKey, + step, + value, + textFieldSuffix, + selectOptions, + hasDeleteButton, + onDelete, + onLimitChange, + onIntervalSelect, +}: IntervalLimitItemProps) => { + return ( +
+ onLimitChange(limitKey, parseInt(e.target.value))} + /> + { - const val = e.target.value; - setValue(`bookingLimits.${bookingLimitKey}`, parseInt(val)); - }} - /> -