@licklist/design 0.63.0 → 0.63.1-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bitbucket-pipelines.yml +0 -8
- package/dist/date-time-button/DateTimeButton.js +1 -1
- package/dist/events/edit-event-modal/component/EditEventForm/EditEventForm.d.ts +3 -1
- package/dist/events/edit-event-modal/component/EditEventForm/EditEventForm.d.ts.map +1 -1
- package/dist/events/edit-event-modal/component/EditEventForm/EditEventForm.js +1 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/SelectEventProductSet.d.ts +3 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/SelectEventProductSet.d.ts.map +1 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/SelectEventProductSet.js +1 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/component/EditEventProductSet.d.ts +3 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/component/EditEventProductSet.d.ts.map +1 -1
- package/dist/events/edit-event-modal/component/SelectEventProductSet/component/EditEventProductSet.js +1 -1
- package/dist/iframe/payment/order-items-table/utils/paymentSummary.js +1 -1
- package/dist/product-set/control/DateAndRecurrenceInput.d.ts +1 -5
- package/dist/product-set/control/DateAndRecurrenceInput.d.ts.map +1 -1
- package/dist/product-set/control/DateAndRecurrenceInput.js +1 -1
- package/dist/product-set/control/DateInput.d.ts.map +1 -1
- package/dist/product-set/control/DateInput.js +1 -1
- package/dist/product-set/control/ProductSetControl.d.ts +4 -3
- package/dist/product-set/control/ProductSetControl.d.ts.map +1 -1
- package/dist/product-set/control/ProductSetControl.js +1 -1
- package/dist/product-set/control/utils.d.ts +5 -0
- package/dist/product-set/control/utils.d.ts.map +1 -0
- package/dist/product-set/control/utils.js +1 -0
- package/dist/product-set/form/ProductCategoriesControl.d.ts +2 -1
- package/dist/product-set/form/ProductCategoriesControl.d.ts.map +1 -1
- package/dist/product-set/form/ProductCategoriesControl.js +1 -1
- package/dist/product-set/form/ProductSetForm.d.ts +4 -1
- package/dist/product-set/form/ProductSetForm.d.ts.map +1 -1
- package/dist/product-set/form/ProductsControl.d.ts +2 -1
- package/dist/product-set/form/ProductsControl.d.ts.map +1 -1
- package/dist/product-set/form/ProductsControl.js +1 -1
- package/dist/product-set/form/StepsControl.d.ts +2 -1
- package/dist/product-set/form/StepsControl.d.ts.map +1 -1
- package/dist/product-set/form/StepsControl.js +1 -1
- package/dist/product-set/hooks/useSortableTreeFunctions.d.ts.map +1 -1
- package/dist/product-set/product/ProductControl.d.ts +1 -0
- package/dist/product-set/product/ProductControl.d.ts.map +1 -1
- package/dist/product-set/product/ProductControl.js +1 -1
- package/dist/product-set/product/fixed-duration-fields/FixedDurationOptions.d.ts.map +1 -1
- package/dist/product-set/step/StepControl.d.ts +2 -1
- package/dist/product-set/step/StepControl.d.ts.map +1 -1
- package/dist/product-set/step/StepControl.js +1 -1
- package/dist/provider/working-hours-input/WorkingHoursInputDescription.d.ts.map +1 -1
- package/dist/provider/working-hours-input/WorkingHoursInputDescription.js +1 -1
- package/dist/recurring-date-picker-input/RecurrenceAndFrequencyInput.d.ts.map +1 -1
- package/dist/recurring-date-picker-input/RecurringDatePickerInput.d.ts +3 -1
- package/dist/recurring-date-picker-input/RecurringDatePickerInput.d.ts.map +1 -1
- package/dist/recurring-date-picker-input/RecurringDatePickerInput.js +1 -1
- package/dist/recurring-date-picker-input/utils.d.ts +6 -0
- package/dist/recurring-date-picker-input/utils.d.ts.map +1 -1
- package/dist/recurring-date-picker-input/utils.js +1 -1
- package/dist/sales/booking/results/components/ResultCard.d.ts.map +1 -1
- package/dist/sales/booking/results/components/ResultCard.js +1 -1
- package/dist/sales/coupon/control/CouponFormControl.d.ts +2 -1
- package/dist/sales/coupon/control/CouponFormControl.d.ts.map +1 -1
- package/dist/sales/coupon/control/CouponFormControl.js +1 -1
- package/dist/sales/coupon/form/CouponFrom.d.ts +2 -2
- package/dist/sales/coupon/form/CouponFrom.d.ts.map +1 -1
- package/dist/sales/coupon/utils/index.d.ts +7 -0
- package/dist/sales/coupon/utils/index.d.ts.map +1 -0
- package/dist/sales/coupon/utils/index.js +1 -0
- package/dist/setting/admin/AdminSettingForm.d.ts +2 -2
- package/dist/setting/admin/AdminSettingForm.d.ts.map +1 -1
- package/dist/setting/dashboard/DashboardSettingForm.d.ts +1 -0
- package/dist/setting/dashboard/DashboardSettingForm.d.ts.map +1 -1
- package/dist/setting/dashboard/DashboardSettingForm.js +1 -1
- package/dist/sortable-tree/SortableTreeItem.d.ts +1 -2
- package/dist/sortable-tree/SortableTreeItem.d.ts.map +1 -1
- package/dist/sortable-tree/SortableTreeItem.js +1 -1
- package/dist/styles/sales/BookingResults.scss +1 -1
- package/dist/typeahead/Typeahead.d.ts +2 -1
- package/dist/typeahead/Typeahead.d.ts.map +1 -1
- package/dist/typeahead/Typeahead.js +1 -1
- package/dist/zone/form/ZoneForm.d.ts +2 -2
- package/dist/zone/form/ZoneForm.d.ts.map +1 -1
- package/dist/zone/form/ZoneForm.js +1 -1
- package/dist/zone/form/components/AvailableTimesControl.d.ts +0 -2
- package/dist/zone/form/components/AvailableTimesControl.d.ts.map +1 -1
- package/dist/zone/form/components/ZoneControl.d.ts +2 -2
- package/dist/zone/form/components/ZoneControl.d.ts.map +1 -1
- package/dist/zone/form/components/ZoneControl.js +1 -1
- package/dist/zone/form/components/ZoneRecurrencesControl.d.ts +4 -4
- package/dist/zone/form/components/ZoneRecurrencesControl.d.ts.map +1 -1
- package/dist/zone/form/components/ZoneRecurrencesControl.js +1 -1
- package/dist/zone/form/utils/dates.d.ts.map +1 -1
- package/package.json +10 -35
- package/src/date-time-button/DateTimeButton.stories.tsx +2 -1
- package/src/date-time-button/DateTimeButton.tsx +7 -5
- package/src/events/edit-event-modal/component/EditEventForm/EditEventForm.tsx +4 -0
- package/src/events/edit-event-modal/component/SelectEventProductSet/SelectEventProductSet.tsx +18 -9
- package/src/events/edit-event-modal/component/SelectEventProductSet/component/EditEventProductSet.tsx +5 -0
- package/src/iframe/payment/order-items-table/utils/paymentSummary.tsx +6 -6
- package/src/product-set/control/DateAndRecurrenceInput.tsx +48 -14
- package/src/product-set/control/DateInput.tsx +2 -4
- package/src/product-set/control/ProductSetControl.stories.tsx +1 -1
- package/src/product-set/control/ProductSetControl.tsx +12 -11
- package/src/product-set/control/utils.ts +25 -0
- package/src/product-set/form/ProductCategoriesControl.tsx +8 -4
- package/src/product-set/form/ProductSetForm.stories.tsx +1 -2
- package/src/product-set/form/ProductSetForm.tsx +5 -1
- package/src/product-set/form/ProductsControl.tsx +87 -77
- package/src/product-set/form/StepsControl.tsx +11 -6
- package/src/product-set/hooks/useSortableTreeFunctions.ts +2 -0
- package/src/product-set/product/ProductControl.tsx +39 -39
- package/src/product-set/product/fixed-duration-fields/FixedDurationOptions.tsx +0 -2
- package/src/product-set/step/StepControl.tsx +4 -3
- package/src/provider/working-hours-input/WorkingHoursInputDescription.tsx +4 -18
- package/src/recurring-date-picker-input/RecurrenceAndFrequencyInput.tsx +0 -1
- package/src/recurring-date-picker-input/RecurringDatePickerInput.stories.tsx +13 -7
- package/src/recurring-date-picker-input/RecurringDatePickerInput.tsx +12 -1
- package/src/recurring-date-picker-input/utils.ts +82 -1
- package/src/sales/booking/results/BookingResults.stories.tsx +3 -2
- package/src/sales/booking/results/components/ResultCard.tsx +2 -5
- package/src/sales/coupon/control/CouponFormControl.tsx +28 -51
- package/src/sales/coupon/form/CouponFrom.tsx +5 -15
- package/src/sales/coupon/utils/index.ts +13 -0
- package/src/setting/admin/AdminSettingForm.tsx +2 -2
- package/src/setting/dashboard/DashboardSettingForm.tsx +13 -0
- package/src/sortable-tree/SortableTreeItem.tsx +1 -4
- package/src/static/switch/BooleanSwitch.tsx +1 -1
- package/src/styles/sales/BookingResults.scss +1 -1
- package/src/typeahead/Typeahead.tsx +16 -3
- package/src/zone/form/ZoneForm.tsx +3 -2
- package/src/zone/form/components/AvailableTimesControl.tsx +0 -2
- package/src/zone/form/components/ZoneControl.tsx +3 -3
- package/src/zone/form/components/ZoneRecurrencesControl.tsx +7 -5
- package/src/zone/form/utils/dates.ts +9 -10
- package/jest.config.js +0 -29
- package/tests/Auth/Authorizer.test.tsx +0 -194
- package/tests/Auth/Layout/UserNavDropDown.test.tsx +0 -43
- package/tests/Auth/Layout/UserNavDropDownToggle.test.tsx +0 -33
- package/tests/Auth/Login/LoginComponent.test.tsx +0 -246
- package/tests/Auth/Login/LoginFormComponent.test.tsx +0 -182
- package/tests/Auth/Register/RegisterComponent.test.tsx +0 -285
- package/tests/Auth/Register/RegisterFormComponent.test.tsx +0 -170
- package/tests/Auth/Settings/Dashboard/IpInput.test.tsx +0 -130
- package/tests/Auth/Social/SocialCallbackComponent.test.tsx +0 -133
- package/tests/Auth/Social/SocialFormComponent.test.tsx +0 -118
- package/tests/FileUpload/FileUpload.test.tsx +0 -42
- package/tests/Notification/EmailTemplate.test.tsx +0 -82
- package/tests/ProductSet/ProductSetPopover.test.tsx +0 -40
- package/tests/Report/Report.test.tsx +0 -48
- package/tests/Sales/Coupon.test.tsx +0 -51
- package/tests/Sales/SalesAndVIews.test.tsx +0 -63
- package/tests/SnippetTemplates/SnippetTemplates.test.tsx +0 -56
- package/tests/Table/FilterHelperComponent.test.tsx +0 -88
- package/tests/Table/PaginationHelperComponent.test.tsx +0 -109
- package/tests/Table/PerPageHelperComponent.test.tsx +0 -34
- package/tests/Table/TableHelperComponent.test.tsx +0 -295
- package/tests/TipTapEditor/TipTapEditor.test.tsx +0 -28
- package/tests/__mock__/hooks/useAuthApi.ts +0 -13
- package/tests/__mock__/hooks/useAuthMock.ts +0 -13
- package/tests/__mock__/hooks/useFormMock.ts +0 -27
- package/tests/__mock__/hooks/useNotificationMock.ts +0 -13
- package/tests/__mock__/hooks/useQueryMock.ts +0 -16
- package/tests/__mock__/hooks/useSocialApiMock.ts +0 -20
- package/tests/__mock__/hooks/useTranslationMock.ts +0 -17
- package/tests/__mock__/hooks/useUserApiMock.ts +0 -18
- package/tests/__mock__/hooks/useUserMock.ts +0 -13
- package/tests/__mock__/styleMock.js +0 -1
- package/tests/__mock__/windowMock.ts +0 -5
- package/tests/packages/react-query.tsx +0 -28
- package/tests/setupTests.ts +0 -10
|
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { Modifier } from "@popperjs/core";
|
|
3
3
|
import { useId } from "@react-aria/utils";
|
|
4
4
|
import { Form, OverlayTrigger, Popover } from "react-bootstrap";
|
|
5
|
-
import { useFieldArray, useFormContext } from "react-hook-form";
|
|
5
|
+
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
|
|
6
6
|
import { useTranslation } from "react-i18next";
|
|
7
7
|
import { useClickAway } from "react-use";
|
|
8
8
|
import { DateTime } from "luxon";
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
SortableContext,
|
|
15
15
|
verticalListSortingStrategy,
|
|
16
16
|
} from "@dnd-kit/sortable";
|
|
17
|
-
|
|
18
17
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
19
18
|
import { CONFIRM_MODAL_ACTIONS } from "../../modals/confirmation/ConfirmModal";
|
|
20
19
|
import { Icon } from "../../static";
|
|
@@ -27,16 +26,15 @@ import {
|
|
|
27
26
|
AvailableTimesControl,
|
|
28
27
|
AvailableTimesControlRef,
|
|
29
28
|
} from "../../zone/form/components/AvailableTimesControl";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
29
|
+
import { ProductSetControlValues } from "..";
|
|
30
|
+
import {
|
|
31
|
+
getProductWithSmallestDuration,
|
|
32
|
+
getLatestAvailableDateTime,
|
|
33
|
+
} from "./utils";
|
|
36
34
|
|
|
37
35
|
interface DateAndRecurrenceInputProps {
|
|
38
36
|
isEventEditProductSet?: boolean;
|
|
39
|
-
workHours
|
|
37
|
+
workHours: WorkHour[] | undefined;
|
|
40
38
|
providerHasBookingManagement: boolean;
|
|
41
39
|
isLoading?: boolean;
|
|
42
40
|
}
|
|
@@ -54,7 +52,9 @@ export const DateAndRecurrenceInput = ({
|
|
|
54
52
|
getValues,
|
|
55
53
|
setValue,
|
|
56
54
|
clearErrors,
|
|
57
|
-
} = useFormContext<
|
|
55
|
+
} = useFormContext<ProductSetControlValues>();
|
|
56
|
+
|
|
57
|
+
const steps = useWatch({ control, name: "steps" });
|
|
58
58
|
|
|
59
59
|
const [availableTimes, setAvailableTimes] = useState<string[]>([]);
|
|
60
60
|
const availableTimesFormRef = useRef<AvailableTimesControlRef>();
|
|
@@ -90,7 +90,7 @@ export const DateAndRecurrenceInput = ({
|
|
|
90
90
|
clearEditState();
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
const popoverId = useId()
|
|
93
|
+
const popoverId = useId();
|
|
94
94
|
|
|
95
95
|
const { t } = useTranslation("Design");
|
|
96
96
|
|
|
@@ -101,11 +101,13 @@ export const DateAndRecurrenceInput = ({
|
|
|
101
101
|
const isAvailableTimesFormValid =
|
|
102
102
|
await availableTimesFormRef.current?.trigger();
|
|
103
103
|
|
|
104
|
+
const { availableTimes } = availableTimesFormRef.current.getValues();
|
|
105
|
+
|
|
104
106
|
if (next?.startTime && next?.endTime && isAvailableTimesFormValid) {
|
|
105
107
|
const startTime = DateTime.fromISO(next.startTime);
|
|
106
108
|
const endTime = DateTime.fromISO(next.endTime);
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
+
|
|
110
|
+
const areAvailableTimesValid = availableTimes.every((time) => {
|
|
109
111
|
const currentAvailableTime = DateTime.fromFormat(time, TIME_FORMAT);
|
|
110
112
|
|
|
111
113
|
return (
|
|
@@ -113,7 +115,8 @@ export const DateAndRecurrenceInput = ({
|
|
|
113
115
|
currentAvailableTime.diff(endTime, "minutes").minutes <= 0
|
|
114
116
|
);
|
|
115
117
|
});
|
|
116
|
-
|
|
118
|
+
|
|
119
|
+
if (!areAvailableTimesValid) {
|
|
117
120
|
availableTimesFormRef.current.setError("availableTimes", {
|
|
118
121
|
message: t("Validation:fieldTimeBetween", {
|
|
119
122
|
attribute: t("Design:startTimesSmall"),
|
|
@@ -126,6 +129,36 @@ export const DateAndRecurrenceInput = ({
|
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
// this check is required to prevent user from adding available time
|
|
133
|
+
// less than the lowest duration product, e.g. if lowest duration product
|
|
134
|
+
// is 1 hour and user tries to add time 15 minutes before end time,
|
|
135
|
+
// such time can never be booked
|
|
136
|
+
const lowestDurationProduct = getProductWithSmallestDuration(steps);
|
|
137
|
+
|
|
138
|
+
if (lowestDurationProduct) {
|
|
139
|
+
const endTime = next?.endTime
|
|
140
|
+
? DateTime.fromFormat(next?.endTime, TIME_FORMAT)
|
|
141
|
+
: // this is to allow user to add 23:00 if end is at 23:59
|
|
142
|
+
DateTime.fromFormat("23:59", TIME_FORMAT).plus({ minutes: 1 });
|
|
143
|
+
|
|
144
|
+
const latestAvailableTime = getLatestAvailableDateTime(availableTimes);
|
|
145
|
+
|
|
146
|
+
const latestAllowedTime = endTime.minus({
|
|
147
|
+
minutes: lowestDurationProduct.duration,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (latestAvailableTime > latestAllowedTime) {
|
|
151
|
+
availableTimesFormRef.current.setError("availableTimes", {
|
|
152
|
+
message: t("Validation:fieldTimeBefore", {
|
|
153
|
+
attribute: t("Design:timeLowercase"),
|
|
154
|
+
time: latestAllowedTime.toFormat(TIME_FORMAT),
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
129
162
|
const availableTime =
|
|
130
163
|
availableTimesFormRef?.current?.getValues()?.availableTimes || null;
|
|
131
164
|
|
|
@@ -228,6 +261,7 @@ export const DateAndRecurrenceInput = ({
|
|
|
228
261
|
defaultValues={editState.values}
|
|
229
262
|
onChange={handleRecurringDateChange}
|
|
230
263
|
onDelete={handleDelete}
|
|
264
|
+
workHours={workHours}
|
|
231
265
|
>
|
|
232
266
|
{providerHasBookingManagement && (
|
|
233
267
|
<AvailableTimesControl
|
|
@@ -5,9 +5,8 @@ import { Form, OverlayTrigger, Popover } from "react-bootstrap";
|
|
|
5
5
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
|
6
6
|
import { useTranslation } from "react-i18next";
|
|
7
7
|
import { useClickAway } from "react-use";
|
|
8
|
-
import {
|
|
8
|
+
import { UTC_TIMEZONE, TIME_FORMAT } from "@licklist/core/dist/Config/Date";
|
|
9
9
|
import { ProductSetRecurrence } from "@licklist/core/dist/DataMapper/Product/ProductSetRecurrenceDataMapper";
|
|
10
|
-
import { TIME_FORMAT } from "@licklist/core/dist/Config/Date";
|
|
11
10
|
import { DateTime } from "luxon";
|
|
12
11
|
import RRule, { Frequency } from "rrule";
|
|
13
12
|
import { WorkHour } from "@licklist/core/dist/DataMapper/Provider/WorkHourDataMapper";
|
|
@@ -27,7 +26,6 @@ import {
|
|
|
27
26
|
import { ProductSetRecurrenceOverridesControl } from "./ProductSetRecurrenceOverridesControl";
|
|
28
27
|
import { MAX_QUANTITY_RECURRENCE_DATE_IN_OVERRIDE } from "../product/constants";
|
|
29
28
|
|
|
30
|
-
|
|
31
29
|
export interface DateAndRecurrenceInputValues {
|
|
32
30
|
menuRecurrences?: Partial<ProductSetRecurrence>[];
|
|
33
31
|
}
|
|
@@ -138,7 +136,7 @@ export const DateInput = ({
|
|
|
138
136
|
until: (start as DateTime).toJSDate(),
|
|
139
137
|
byweekday: [],
|
|
140
138
|
freq: Frequency.DAILY,
|
|
141
|
-
tzid:
|
|
139
|
+
tzid: UTC_TIMEZONE,
|
|
142
140
|
interval: 1,
|
|
143
141
|
}).replace("RRULE:", "");
|
|
144
142
|
|
|
@@ -2,12 +2,12 @@ import { Meta, Story } from "@storybook/react";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import Form from "react-bootstrap/Form";
|
|
4
4
|
import { FormProvider, useForm } from "react-hook-form";
|
|
5
|
+
import { OPERATIONAL_COST_CUSTOMER } from "@licklist/core/dist/DataMapper/Product/ProductSetDataMapper";
|
|
5
6
|
import {
|
|
6
7
|
ProductSetControl,
|
|
7
8
|
ProductSetControlProps,
|
|
8
9
|
ProductSetControlValues,
|
|
9
10
|
} from "./ProductSetControl";
|
|
10
|
-
import { OPERATIONAL_COST_CUSTOMER } from "@licklist/core/dist/DataMapper/Product/ProductSetDataMapper";
|
|
11
11
|
|
|
12
12
|
export default {
|
|
13
13
|
title: "Product Set/Control",
|
|
@@ -22,14 +22,10 @@ import { SmsTemplate } from "@licklist/core/dist/DataMapper/Notification/SmsTemp
|
|
|
22
22
|
import HookFormService from "@licklist/plugins/dist/services/Form/HookFormService";
|
|
23
23
|
import { ruleForUrlWithProtocol } from "@licklist/plugins/dist/validation/Rules/urlRule";
|
|
24
24
|
import { WorkHour } from "@licklist/core/dist/DataMapper/Provider/WorkHourDataMapper";
|
|
25
|
+
import { ProductSetRecurrence } from "@licklist/core/dist/DataMapper/Product/ProductSetRecurrenceDataMapper";
|
|
25
26
|
import { WarningMessage } from "../../static";
|
|
26
27
|
import { SelectItem } from "../../types/generic/SelectItem";
|
|
27
|
-
import {
|
|
28
|
-
DateAndRecurrenceInput,
|
|
29
|
-
// TODO: Show Date Component, when reccurent date bugs are fixed
|
|
30
|
-
// DateAndRecurrenceInput,
|
|
31
|
-
DateAndRecurrenceInputValues,
|
|
32
|
-
} from "./DateAndRecurrenceInput";
|
|
28
|
+
import { DateAndRecurrenceInput } from "./DateAndRecurrenceInput";
|
|
33
29
|
import TutorialGifCard from "./TutorialGifCard";
|
|
34
30
|
import { Step } from "../types";
|
|
35
31
|
import { StepsControl } from "../form/StepsControl";
|
|
@@ -53,7 +49,7 @@ export interface TemplateItem {
|
|
|
53
49
|
label: string;
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
export interface ProductSetControlValues
|
|
52
|
+
export interface ProductSetControlValues {
|
|
57
53
|
name: string;
|
|
58
54
|
type: ProductSetType;
|
|
59
55
|
termsAndConditions: string;
|
|
@@ -65,6 +61,7 @@ export interface ProductSetControlValues extends DateAndRecurrenceInputValues {
|
|
|
65
61
|
steps: Step[];
|
|
66
62
|
emailTemplates?: TemplateItem[];
|
|
67
63
|
smsTemplates?: TemplateItem[];
|
|
64
|
+
menuRecurrences?: Partial<ProductSetRecurrence>[];
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
export interface ProductSetControlShared {
|
|
@@ -85,7 +82,7 @@ export interface ProductSetControlProps {
|
|
|
85
82
|
fieldSets?: FieldSet[];
|
|
86
83
|
showEmailTemplate?: boolean;
|
|
87
84
|
showSmsTemplate?: boolean;
|
|
88
|
-
workHours
|
|
85
|
+
workHours: WorkHour[] | undefined;
|
|
89
86
|
providerHasBookingManagement?: boolean;
|
|
90
87
|
isOverrides?: boolean;
|
|
91
88
|
}
|
|
@@ -123,7 +120,7 @@ export function ProductSetControl({
|
|
|
123
120
|
|
|
124
121
|
const showSmsTemplateSelector = showSmsTemplate && !isOverrides;
|
|
125
122
|
const showEmailTemplateSelector = showEmailTemplate && !isOverrides;
|
|
126
|
-
|
|
123
|
+
const showDateSelector = !isEventEditProductSet && isOverrides;
|
|
127
124
|
return (
|
|
128
125
|
<Row
|
|
129
126
|
className={`product-set-form ${
|
|
@@ -165,7 +162,7 @@ export function ProductSetControl({
|
|
|
165
162
|
</Form.Control.Feedback>
|
|
166
163
|
</Form.Group>
|
|
167
164
|
|
|
168
|
-
{
|
|
165
|
+
{showDateSelector ? (
|
|
169
166
|
<DateInput
|
|
170
167
|
workHours={workHours}
|
|
171
168
|
isLoading={isLoading}
|
|
@@ -188,7 +185,11 @@ export function ProductSetControl({
|
|
|
188
185
|
|
|
189
186
|
<TutorialGifCard isOverrides={isOverrides} />
|
|
190
187
|
|
|
191
|
-
<StepsControl
|
|
188
|
+
<StepsControl
|
|
189
|
+
isLoading={isLoading}
|
|
190
|
+
isEventEditProductSet={isEventEditProductSet}
|
|
191
|
+
isOverrides={isOverrides}
|
|
192
|
+
/>
|
|
192
193
|
</Col>
|
|
193
194
|
<Col md={6} sm={12}>
|
|
194
195
|
<div className="second-column">
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TIME_FORMAT } from "@licklist/core/dist/Config";
|
|
2
|
+
import { dateTimesSortFn } from "@licklist/plugins/dist/utils/dateTime";
|
|
3
|
+
import { extractObjectWithLowestKey } from "@licklist/plugins/dist/utils/extractObjectWithLowestKey";
|
|
4
|
+
import { DateTime } from "luxon";
|
|
5
|
+
import { Product, Step } from "../types";
|
|
6
|
+
|
|
7
|
+
const getProductsFromSteps = (steps: Step[]): Product[] =>
|
|
8
|
+
steps.reduce(
|
|
9
|
+
(acc, step) =>
|
|
10
|
+
acc.concat(
|
|
11
|
+
step.productCategories.reduce(
|
|
12
|
+
(acc, productCategory) => acc.concat(productCategory.products),
|
|
13
|
+
[]
|
|
14
|
+
)
|
|
15
|
+
),
|
|
16
|
+
[]
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const getProductWithSmallestDuration = (steps: Step[]): Product =>
|
|
20
|
+
extractObjectWithLowestKey(getProductsFromSteps(steps), "duration");
|
|
21
|
+
|
|
22
|
+
export const getLatestAvailableDateTime = (array: string[]): DateTime =>
|
|
23
|
+
array
|
|
24
|
+
.map((time) => DateTime.fromFormat(time, TIME_FORMAT))
|
|
25
|
+
.sort(dateTimesSortFn("desc"))[0];
|
|
@@ -36,6 +36,7 @@ import { useSortableTreeFunctions } from "../hooks/useSortableTreeFunctions";
|
|
|
36
36
|
interface ProductCategoriesControlProps extends WithIsLoading {
|
|
37
37
|
stepIndex: number;
|
|
38
38
|
isOverrides?: boolean;
|
|
39
|
+
isEventEditProductSet?: boolean;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const getCategoryDefaultValue = (
|
|
@@ -67,6 +68,7 @@ export function ProductCategoriesControl({
|
|
|
67
68
|
isLoading,
|
|
68
69
|
stepIndex,
|
|
69
70
|
isOverrides,
|
|
71
|
+
isEventEditProductSet,
|
|
70
72
|
}: ProductCategoriesControlProps) {
|
|
71
73
|
const { t } = useTranslation("Design");
|
|
72
74
|
const {
|
|
@@ -214,6 +216,10 @@ export function ProductCategoriesControl({
|
|
|
214
216
|
? String(productCategory.id)
|
|
215
217
|
: productCategory.uniqueId;
|
|
216
218
|
|
|
219
|
+
const isOverridesCategory = productCategory.products.some((product) =>
|
|
220
|
+
isEventEditProductSet ? !!product.id : !!product.originalProductId
|
|
221
|
+
);
|
|
222
|
+
|
|
217
223
|
const onSetIsExpanded = () => {
|
|
218
224
|
setIsExpanded(productCategoryId);
|
|
219
225
|
};
|
|
@@ -269,7 +275,7 @@ export function ProductCategoriesControl({
|
|
|
269
275
|
categoryName={value}
|
|
270
276
|
stepIndex={stepIndex}
|
|
271
277
|
productCategoryIndex={index}
|
|
272
|
-
isOverride={isOverrides}
|
|
278
|
+
isOverride={isOverrides && isOverridesCategory}
|
|
273
279
|
/>
|
|
274
280
|
}
|
|
275
281
|
isOverride={isOverrides}
|
|
@@ -296,6 +302,7 @@ export function ProductCategoriesControl({
|
|
|
296
302
|
productCategoryIndex={index}
|
|
297
303
|
categoryType={productCategory.type}
|
|
298
304
|
isOverrides={isOverrides}
|
|
305
|
+
isEventEditProductSet={isEventEditProductSet}
|
|
299
306
|
/>
|
|
300
307
|
</SortableTree.Item>
|
|
301
308
|
)}
|
|
@@ -305,9 +312,7 @@ export function ProductCategoriesControl({
|
|
|
305
312
|
</SortableTree>
|
|
306
313
|
<CreateProductSetItem
|
|
307
314
|
title={t("addCategory")}
|
|
308
|
-
isOverride={isOverrides}
|
|
309
315
|
onClick={() => {
|
|
310
|
-
if (isOverrides) return;
|
|
311
316
|
setIsSelectCategoryVisible(true);
|
|
312
317
|
}}
|
|
313
318
|
/>
|
|
@@ -316,7 +321,6 @@ export function ProductCategoriesControl({
|
|
|
316
321
|
isVisible={isSelectCategoryVisible}
|
|
317
322
|
onHide={() => setIsSelectCategoryVisible(false)}
|
|
318
323
|
onCategorySelect={(categoryType) => {
|
|
319
|
-
if (isOverrides) return;
|
|
320
324
|
append(getCategoryDefaultValue(categoryType, fields.length));
|
|
321
325
|
setShowCategoryModal(true);
|
|
322
326
|
if (
|
|
@@ -63,9 +63,9 @@ Default.args = {
|
|
|
63
63
|
sort: 100,
|
|
64
64
|
type: "standard",
|
|
65
65
|
updatedAt: "2023-05-08T15:55:28.000+03:00",
|
|
66
|
+
defaultDuration: 30,
|
|
66
67
|
zoneRecurrences: [
|
|
67
68
|
{
|
|
68
|
-
availableTimes: ["10:05", "10:25", "10:45"],
|
|
69
69
|
endDate: "2022-02-22",
|
|
70
70
|
endTime: "22:00:00",
|
|
71
71
|
id: 1,
|
|
@@ -90,7 +90,6 @@ Default.args = {
|
|
|
90
90
|
updatedAt: "2023-05-08T15:55:28.000+03:00",
|
|
91
91
|
zoneRecurrences: [
|
|
92
92
|
{
|
|
93
|
-
availableTimes: ["10:05", "10:25", "10:45"],
|
|
94
93
|
endDate: "2022-02-22",
|
|
95
94
|
endTime: "22:00:00",
|
|
96
95
|
id: 1,
|
|
@@ -26,12 +26,16 @@ import { checkAvailableTimesErrors, getFilteredTemplates } from "../utils";
|
|
|
26
26
|
export interface WithIsLoading {
|
|
27
27
|
isLoading: boolean;
|
|
28
28
|
}
|
|
29
|
+
export interface WithIdOptional {
|
|
30
|
+
id?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
export interface WithId {
|
|
30
34
|
id: number;
|
|
31
35
|
}
|
|
32
36
|
export interface ProductSetFormValues
|
|
33
37
|
extends FormValues,
|
|
34
|
-
|
|
38
|
+
WithIdOptional,
|
|
35
39
|
ProductSetControlValues {
|
|
36
40
|
steps: Step[];
|
|
37
41
|
isOverrides?: boolean;
|
|
@@ -35,6 +35,7 @@ interface ProductsControlProps extends WithIsLoading {
|
|
|
35
35
|
categoryType: CategoryType;
|
|
36
36
|
zones?: Zone[];
|
|
37
37
|
isOverrides?: boolean;
|
|
38
|
+
isEventEditProductSet?: boolean;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const getDefaultProductValue = (sort: number): Product => ({
|
|
@@ -57,6 +58,7 @@ const getDefaultProductValue = (sort: number): Product => ({
|
|
|
57
58
|
quantitySelector: 1,
|
|
58
59
|
hasSpecialNotes: false,
|
|
59
60
|
weight: 0,
|
|
61
|
+
originalProductId: null,
|
|
60
62
|
type: DEFAULT_PRODUCT_TYPE,
|
|
61
63
|
tierId: Number(uniqueId()),
|
|
62
64
|
zoneId: null,
|
|
@@ -79,6 +81,7 @@ export function ProductsControl({
|
|
|
79
81
|
categoryType,
|
|
80
82
|
zones,
|
|
81
83
|
isOverrides,
|
|
84
|
+
isEventEditProductSet,
|
|
82
85
|
}: ProductsControlProps) {
|
|
83
86
|
const { t } = useTranslation("Design");
|
|
84
87
|
const form = useFormContext<ProductSetFormValues>();
|
|
@@ -203,90 +206,97 @@ export function ProductsControl({
|
|
|
203
206
|
onDragEnd={handleDragEnd}
|
|
204
207
|
sensors={[mouseSensor]}
|
|
205
208
|
>
|
|
206
|
-
{fields.map((product, index) =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}`
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
209
|
+
{fields.map((product, index) => {
|
|
210
|
+
const isOverridesProductSet = isEventEditProductSet
|
|
211
|
+
? !!product.id
|
|
212
|
+
: isOverrides && !!product.originalProductId;
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Controller
|
|
216
|
+
key={product._id}
|
|
217
|
+
control={control}
|
|
218
|
+
name={`${productControlFieldName}.${index}.name` as const}
|
|
219
|
+
rules={{
|
|
220
|
+
required: t("Validation:fieldRequired", {
|
|
221
|
+
attribute: t("name"),
|
|
222
|
+
}) as string,
|
|
223
|
+
}}
|
|
224
|
+
render={({ field: { value, onChange } }) => (
|
|
225
|
+
<SortableTree.Item
|
|
226
|
+
key={`product-${product._id}`}
|
|
227
|
+
id={String(product._id)}
|
|
228
|
+
isExpanded={isFirstProductAdded}
|
|
229
|
+
isInvalid={!!categoryProductErrors}
|
|
230
|
+
title={value}
|
|
231
|
+
isOverride={isOverrides}
|
|
232
|
+
badge={
|
|
233
|
+
<Badge className="product-badge">{t("product")}</Badge>
|
|
234
|
+
}
|
|
235
|
+
isIconInHeader={false}
|
|
236
|
+
cancelChanges={() => cancelChanges(index)}
|
|
237
|
+
edit={() => edit(index)}
|
|
238
|
+
secondaryBadge={getBadgeConfig(categoryType, t(categoryType))}
|
|
239
|
+
subTitle={
|
|
240
|
+
<div className="product-set-badges-container">
|
|
241
|
+
<div className="product-set-subtitle-dot product-set-subtitle-product-dot" />
|
|
242
|
+
<span>
|
|
243
|
+
{`£${prices[index]} ${t("each")} - ${t("qty")}:${
|
|
244
|
+
!isUnlimitedQuantities[index]
|
|
245
|
+
? ` ${quantities[index]}`
|
|
246
|
+
: t(" unlimited")
|
|
247
|
+
} ${
|
|
248
|
+
!isUnlimitedQuantities[index]
|
|
249
|
+
? ` - ${t("possibleRevenue")} £${
|
|
250
|
+
prices[index] * quantities[index]
|
|
251
|
+
}`
|
|
252
|
+
: ""
|
|
253
|
+
} `}
|
|
254
|
+
</span>
|
|
255
|
+
</div>
|
|
256
|
+
}
|
|
257
|
+
modalLabel={t("addNewProduct")}
|
|
258
|
+
modalClass={ProductSetModalClasses.product}
|
|
259
|
+
isNewAdded={showProductModal}
|
|
260
|
+
body={
|
|
261
|
+
<ProductControl<ProductSetFormValues>
|
|
262
|
+
isLoading={isLoading}
|
|
263
|
+
quantityType={quantityType}
|
|
264
|
+
allowDeposits={allowDeposits}
|
|
265
|
+
hasBookingManagement={false}
|
|
266
|
+
fieldNamePrefix={
|
|
267
|
+
`${productControlFieldName}.${index}` as const
|
|
268
|
+
}
|
|
269
|
+
productName={value}
|
|
270
|
+
onProductNameChange={onChange}
|
|
271
|
+
hasTicket={hasTicket}
|
|
272
|
+
categoryType={categoryType}
|
|
273
|
+
zoneDuration={catergoryZone?.defaultDuration}
|
|
274
|
+
isOverrides={isOverridesProductSet}
|
|
275
|
+
/>
|
|
276
|
+
}
|
|
277
|
+
onDelete={() => onProductRemove(index)}
|
|
278
|
+
validate={() =>
|
|
279
|
+
trigger(`${productControlFieldName}.${index}`)
|
|
280
|
+
}
|
|
281
|
+
saveValidField={saveValidField}
|
|
282
|
+
// preItem={<Popover className="d-none d-sm-block" />}
|
|
283
|
+
>
|
|
284
|
+
<SubProductsControl
|
|
253
285
|
isLoading={isLoading}
|
|
254
286
|
quantityType={quantityType}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
`${productControlFieldName}.${index}` as const
|
|
259
|
-
}
|
|
260
|
-
productName={value}
|
|
261
|
-
onProductNameChange={onChange}
|
|
262
|
-
hasTicket={hasTicket}
|
|
263
|
-
categoryType={categoryType}
|
|
264
|
-
zoneDuration={catergoryZone?.defaultDuration}
|
|
265
|
-
isOverrides={isOverrides}
|
|
287
|
+
stepIndex={stepIndex}
|
|
288
|
+
productCategoryIndex={productCategoryIndex}
|
|
289
|
+
productIndex={index}
|
|
266
290
|
/>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
>
|
|
273
|
-
<SubProductsControl
|
|
274
|
-
isLoading={isLoading}
|
|
275
|
-
quantityType={quantityType}
|
|
276
|
-
stepIndex={stepIndex}
|
|
277
|
-
productCategoryIndex={productCategoryIndex}
|
|
278
|
-
productIndex={index}
|
|
279
|
-
/>
|
|
280
|
-
</SortableTree.Item>
|
|
281
|
-
)}
|
|
282
|
-
/>
|
|
283
|
-
))}
|
|
291
|
+
</SortableTree.Item>
|
|
292
|
+
)}
|
|
293
|
+
/>
|
|
294
|
+
);
|
|
295
|
+
})}
|
|
284
296
|
</SortableTree>
|
|
285
297
|
<CreateProductSetItem
|
|
286
298
|
title={t("addProduct")}
|
|
287
|
-
isOverride={isOverrides}
|
|
288
299
|
onClick={() => {
|
|
289
|
-
if (isOverrides) return;
|
|
290
300
|
append(getDefaultProductValue(fields.length));
|
|
291
301
|
setShowProductModal(true);
|
|
292
302
|
|
|
@@ -19,9 +19,14 @@ import { ProductSetFormValues } from "./ProductSetForm";
|
|
|
19
19
|
interface StepsControlProps {
|
|
20
20
|
isLoading: boolean;
|
|
21
21
|
isOverrides?: boolean;
|
|
22
|
+
isEventEditProductSet?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function StepsControl({
|
|
25
|
+
export function StepsControl({
|
|
26
|
+
isLoading,
|
|
27
|
+
isOverrides,
|
|
28
|
+
isEventEditProductSet,
|
|
29
|
+
}: StepsControlProps) {
|
|
25
30
|
const form = useFormContext<ProductSetFormValues>();
|
|
26
31
|
const { t } = useTranslation("Design");
|
|
27
32
|
const [showStepModal, setShowStepModal] = useState(false);
|
|
@@ -68,8 +73,8 @@ export function StepsControl({ isLoading, isOverrides }: StepsControlProps) {
|
|
|
68
73
|
<>
|
|
69
74
|
{fields.map((step, index) => {
|
|
70
75
|
const stepFieldName = `steps.${index}` as const;
|
|
71
|
-
|
|
72
76
|
const stepData = getValues(stepFieldName);
|
|
77
|
+
const isOverrideStep = !!step.id;
|
|
73
78
|
|
|
74
79
|
return (
|
|
75
80
|
<Controller
|
|
@@ -92,9 +97,9 @@ export function StepsControl({ isLoading, isOverrides }: StepsControlProps) {
|
|
|
92
97
|
modalLabel={t("addNewStep")}
|
|
93
98
|
modalClass={ProductSetModalClasses.step}
|
|
94
99
|
isNewAdded={showStepModal}
|
|
95
|
-
edit={() =>
|
|
100
|
+
edit={() => edit(index)}
|
|
96
101
|
cancelChanges={() => cancelChanges(index)}
|
|
97
|
-
isOverride={
|
|
102
|
+
isOverride={isOverrideStep}
|
|
98
103
|
subTitle={
|
|
99
104
|
<div className="product-set-badges-container">
|
|
100
105
|
<div className="product-set-subtitle-dot product-set-subtitle-step-dot" />
|
|
@@ -110,6 +115,7 @@ export function StepsControl({ isLoading, isOverrides }: StepsControlProps) {
|
|
|
110
115
|
stepIndex={index}
|
|
111
116
|
stepName={value}
|
|
112
117
|
onStepNameChange={onChange}
|
|
118
|
+
isOverrides={isOverrideStep}
|
|
113
119
|
/>
|
|
114
120
|
}
|
|
115
121
|
onDelete={() => remove(index)}
|
|
@@ -140,6 +146,7 @@ export function StepsControl({ isLoading, isOverrides }: StepsControlProps) {
|
|
|
140
146
|
isLoading={isLoading}
|
|
141
147
|
stepIndex={index}
|
|
142
148
|
isOverrides={isOverrides}
|
|
149
|
+
isEventEditProductSet={isEventEditProductSet}
|
|
143
150
|
/>
|
|
144
151
|
</SortableTree.Item>
|
|
145
152
|
)}
|
|
@@ -149,9 +156,7 @@ export function StepsControl({ isLoading, isOverrides }: StepsControlProps) {
|
|
|
149
156
|
|
|
150
157
|
<CreateProductSetItem
|
|
151
158
|
title={t("addStep")}
|
|
152
|
-
isOverride={isOverrides}
|
|
153
159
|
onClick={() => {
|
|
154
|
-
if (isOverrides) return;
|
|
155
160
|
append(getDefaultStepsValues());
|
|
156
161
|
setShowStepModal(true);
|
|
157
162
|
if (errors.steps?.type === HookFormService.manualErrorType) {
|
|
@@ -24,8 +24,10 @@ export const useSortableTreeFunctions = ({
|
|
|
24
24
|
const cancelChanges = (index: number) => {
|
|
25
25
|
if (isOverrides) return;
|
|
26
26
|
if (!previousValue) {
|
|
27
|
+
// eslint-disable-next-line consistent-return
|
|
27
28
|
return remove(index);
|
|
28
29
|
}
|
|
30
|
+
// eslint-disable-next-line consistent-return
|
|
29
31
|
return setValue(`${fieldName}.${index}` as const, previousValue);
|
|
30
32
|
};
|
|
31
33
|
|