@servicetitan/dte-pdf-editor 1.95.0 → 1.96.1
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/README.md +87 -2
- package/dist/components/field-config-panel/advanced-settings.d.ts +2 -0
- package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -1
- package/dist/components/field-config-panel/advanced-settings.js +5 -4
- package/dist/components/field-config-panel/advanced-settings.js.map +1 -1
- package/dist/components/field-config-panel/e-sign-options-modal.d.ts +3 -0
- package/dist/components/field-config-panel/e-sign-options-modal.d.ts.map +1 -1
- package/dist/components/field-config-panel/e-sign-options-modal.js +6 -4
- package/dist/components/field-config-panel/e-sign-options-modal.js.map +1 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +14 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel-overlay.js +41 -2
- package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts +6 -0
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +17 -3
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-config-panel/formula-generator.d.ts +4 -0
- package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-generator.js +2 -2
- package/dist/components/field-config-panel/formula-generator.js.map +1 -1
- package/dist/components/field-config-panel/formula-modal.d.ts +5 -0
- package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-modal.js +13 -6
- package/dist/components/field-config-panel/formula-modal.js.map +1 -1
- package/dist/components/field-config-panel/formula-workspace.d.ts +2 -0
- package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-workspace.js +6 -4
- package/dist/components/field-config-panel/formula-workspace.js.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts +52 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.js +2 -2
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts +3 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.js +14 -4
- package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts +18 -4
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +2 -2
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/calculated.constants.d.ts +6 -0
- package/dist/constants/calculated.constants.d.ts.map +1 -1
- package/dist/constants/calculated.constants.js +6 -0
- package/dist/constants/calculated.constants.js.map +1 -1
- package/dist/hooks/usePinchZoom.d.ts.map +1 -1
- package/dist/hooks/usePinchZoom.js +1 -1
- package/dist/hooks/usePinchZoom.js.map +1 -1
- package/dist/interface/types.d.ts +37 -1
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/formula/business-days.utils.d.ts +32 -0
- package/dist/utils/formula/business-days.utils.d.ts.map +1 -0
- package/dist/utils/formula/business-days.utils.js +148 -0
- package/dist/utils/formula/business-days.utils.js.map +1 -0
- package/dist/utils/formula/caret.utils.js +1 -1
- package/dist/utils/formula/caret.utils.js.map +1 -1
- package/dist/utils/formula/evaluate-formula.utils.d.ts +25 -6
- package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -1
- package/dist/utils/formula/evaluate-formula.utils.js +61 -101
- package/dist/utils/formula/evaluate-formula.utils.js.map +1 -1
- package/dist/utils/formula/index.d.ts +1 -0
- package/dist/utils/formula/index.d.ts.map +1 -1
- package/dist/utils/formula/index.js +1 -0
- package/dist/utils/formula/index.js.map +1 -1
- package/dist/utils/formula/schedule-bindings.utils.d.ts +12 -0
- package/dist/utils/formula/schedule-bindings.utils.d.ts.map +1 -0
- package/dist/utils/formula/schedule-bindings.utils.js +17 -0
- package/dist/utils/formula/schedule-bindings.utils.js.map +1 -0
- package/dist/vendor/dte-shared/business-calendar-validation.utils.d.ts +22 -0
- package/dist/vendor/dte-shared/business-calendar-validation.utils.js +76 -0
- package/dist/vendor/dte-shared/components/business-calendar-section.d.ts +15 -0
- package/dist/vendor/dte-shared/components/business-calendar-section.js +13 -0
- package/dist/vendor/dte-shared/components/schedule-drawer.d.ts +16 -0
- package/dist/vendor/dte-shared/components/schedule-drawer.js +112 -0
- package/dist/vendor/dte-shared/components/schedule-picker.d.ts +8 -0
- package/dist/vendor/dte-shared/components/schedule-picker.js +88 -0
- package/dist/vendor/dte-shared/constants.d.ts +10 -0
- package/dist/vendor/dte-shared/constants.js +10 -0
- package/dist/vendor/dte-shared/index.d.ts +6 -0
- package/dist/vendor/dte-shared/index.js +5 -0
- package/dist/vendor/dte-shared/styles/schedule.css +19 -0
- package/dist/vendor/dte-shared/types.d.ts +67 -0
- package/dist/vendor/dte-shared/types.js +1 -0
- package/package.json +5 -2
- package/src/__tests__/field-types/date-fields.test.ts +29 -3
- package/src/__tests__/field-types/e-sign-fields.test.ts +2 -2
- package/src/__tests__/field-types/fillable-fields.test.ts +2 -2
- package/src/components/field-config-panel/advanced-settings.tsx +16 -3
- package/src/components/field-config-panel/e-sign-options-modal.tsx +13 -2
- package/src/components/field-config-panel/field-config-panel-overlay.tsx +97 -1
- package/src/components/field-config-panel/field-config-panel.tsx +28 -0
- package/src/components/field-config-panel/formula-generator.tsx +8 -1
- package/src/components/field-config-panel/formula-modal.tsx +19 -4
- package/src/components/field-config-panel/formula-workspace.tsx +31 -6
- package/src/components/pdf-editor/pdf-editor.tsx +82 -0
- package/src/components/pdf-view/pdf-view-calculated.tsx +28 -3
- package/src/components/pdf-view/pdf-view.tsx +23 -3
- package/src/constants/calculated.constants.ts +7 -0
- package/src/hooks/usePinchZoom.ts +1 -6
- package/src/interface/types.ts +47 -0
- package/src/utils/field/__tests__/field-background-color.utils.test.ts +8 -8
- package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +16 -0
- package/src/utils/formula/__tests__/evaluate-formula.utils.test.ts +198 -16
- package/src/utils/formula/__tests__/schedule-bindings.utils.test.ts +73 -0
- package/src/utils/formula/business-days.utils.ts +188 -0
- package/src/utils/formula/caret.utils.ts +1 -1
- package/src/utils/formula/evaluate-formula.utils.ts +94 -114
- package/src/utils/formula/index.ts +1 -0
- package/src/utils/formula/schedule-bindings.utils.ts +27 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/dte-pdf-editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"typings": "./dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"registry": "https://registry.npmjs.org"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tsc --build && startup prepare-package"
|
|
16
|
+
"build": "npm run build --prefix ../shared && tsc --build && startup prepare-package && node scripts/bundle-shared.mjs"
|
|
17
17
|
},
|
|
18
18
|
"cli": {
|
|
19
19
|
"webpack": false
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"react-pdf": "^10.3.0",
|
|
24
24
|
"uuid": "^9.0.0"
|
|
25
25
|
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@servicetitan/dte-shared": "^0.0.1"
|
|
28
|
+
},
|
|
26
29
|
"peerDependencies": {
|
|
27
30
|
"@servicetitan/anvil2": "^3.7.1",
|
|
28
31
|
"react": "~18.3.1",
|
|
@@ -111,6 +111,15 @@ describe('date field scenarios', () => {
|
|
|
111
111
|
const subject = (formula: StructuredFormula, data: Record<string, string>) =>
|
|
112
112
|
evaluateFormula(formula, data);
|
|
113
113
|
|
|
114
|
+
// The default calendar covers the current year ± 1.
|
|
115
|
+
beforeAll(() => {
|
|
116
|
+
jest.useFakeTimers({ now: new Date('2024-06-01T00:00:00Z') });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterAll(() => {
|
|
120
|
+
jest.useRealTimers();
|
|
121
|
+
});
|
|
122
|
+
|
|
114
123
|
test('adds business days to a date and returns epoch ms of the result', () => {
|
|
115
124
|
// Monday 2024-06-10 + 3 business days = Thursday 2024-06-13
|
|
116
125
|
const formula: StructuredFormula = {
|
|
@@ -120,7 +129,7 @@ describe('date field scenarios', () => {
|
|
|
120
129
|
{ type: 'number', value: '3' },
|
|
121
130
|
],
|
|
122
131
|
};
|
|
123
|
-
const result =
|
|
132
|
+
const result = evaluateFormula(formula, { start: '2024-06-10' }, { enabled: true });
|
|
124
133
|
expect(result).not.toBeNull();
|
|
125
134
|
const resolved = new Date(result as number);
|
|
126
135
|
expect(resolved.getUTCFullYear()).toBe(2024);
|
|
@@ -137,7 +146,7 @@ describe('date field scenarios', () => {
|
|
|
137
146
|
{ type: 'number', value: '1' },
|
|
138
147
|
],
|
|
139
148
|
};
|
|
140
|
-
const result =
|
|
149
|
+
const result = evaluateFormula(formula, { start: '2024-06-14' }, { enabled: true });
|
|
141
150
|
expect(new Date(result as number).getUTCDate()).toBe(17);
|
|
142
151
|
});
|
|
143
152
|
|
|
@@ -150,10 +159,27 @@ describe('date field scenarios', () => {
|
|
|
150
159
|
],
|
|
151
160
|
};
|
|
152
161
|
// Mon 2024-06-10 + 1 day, but 2024-06-11 is a holiday => Wed 2024-06-12
|
|
153
|
-
const result = evaluateFormula(
|
|
162
|
+
const result = evaluateFormula(
|
|
163
|
+
formula,
|
|
164
|
+
{ start: '2024-06-10' },
|
|
165
|
+
{ enabled: true, holidays: ['2024-06-11'] },
|
|
166
|
+
);
|
|
154
167
|
expect(new Date(result as number).getUTCDate()).toBe(12);
|
|
155
168
|
});
|
|
156
169
|
|
|
170
|
+
test('adds plain calendar days (no weekend/holiday skipping) when the exclusion toggle is off', () => {
|
|
171
|
+
// The toggle defaults to off: Friday 2024-06-14 + 1 day = Saturday 2024-06-15, unskipped.
|
|
172
|
+
const formula: StructuredFormula = {
|
|
173
|
+
tokens: [
|
|
174
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
175
|
+
{ type: 'operator', value: '+' },
|
|
176
|
+
{ type: 'number', value: '1' },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
const result = subject(formula, { start: '2024-06-14' });
|
|
180
|
+
expect(new Date(result as number).getUTCDate()).toBe(15);
|
|
181
|
+
});
|
|
182
|
+
|
|
157
183
|
test('returns null when date field has invalid value', () => {
|
|
158
184
|
const formula: StructuredFormula = {
|
|
159
185
|
tokens: [
|
|
@@ -65,8 +65,8 @@ describe('e-sign field scenarios', () => {
|
|
|
65
65
|
expect(getFieldColor(field.recipient, colors)).toBeDefined();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
test('returns
|
|
69
|
-
expect(getFieldColor('unknown', colors)).
|
|
68
|
+
test('returns undefined for an unknown recipient', () => {
|
|
69
|
+
expect(getFieldColor('unknown', colors)).toBeUndefined();
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -296,8 +296,8 @@ describe('fillable field scenarios', () => {
|
|
|
296
296
|
);
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
test('returns
|
|
300
|
-
expect(getFieldColor(undefined, colors)).
|
|
299
|
+
test('returns undefined when field has no recipient', () => {
|
|
300
|
+
expect(getFieldColor(undefined, colors)).toBeUndefined();
|
|
301
301
|
});
|
|
302
302
|
});
|
|
303
303
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Flex, SegmentedControl, Switch, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import { BusinessCalendarSection, type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
2
3
|
import { Dispatch, FC, SetStateAction } from 'react';
|
|
3
4
|
import { DEFAULT_DATE_FORMAT, ROUNDING_OPTIONS } from '../../constants';
|
|
4
5
|
import { type CalculatedFieldFormat, type CalculatedFormatForNumber } from '../../interface/types';
|
|
@@ -6,9 +7,10 @@ import { type CalculatedFieldFormat, type CalculatedFormatForNumber } from '../.
|
|
|
6
7
|
interface AdvancedSettingsProps {
|
|
7
8
|
format: CalculatedFieldFormat;
|
|
8
9
|
setFormat: Dispatch<SetStateAction<CalculatedFieldFormat>>;
|
|
10
|
+
businessCalendar?: ScheduleEditorContext;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
const DateFormatSettings: FC<AdvancedSettingsProps> = ({ format, setFormat }) => (
|
|
13
|
+
const DateFormatSettings: FC<AdvancedSettingsProps> = ({ businessCalendar, format, setFormat }) => (
|
|
12
14
|
<div className="dte-formula-advanced">
|
|
13
15
|
<Text variant="body" size="small">
|
|
14
16
|
Date format
|
|
@@ -24,12 +26,23 @@ const DateFormatSettings: FC<AdvancedSettingsProps> = ({ format, setFormat }) =>
|
|
|
24
26
|
<Text variant="body" size="small" color="tertiary">
|
|
25
27
|
Tokens: YYYY, YY, MMMM, MMM, MM, M, DD, D
|
|
26
28
|
</Text>
|
|
29
|
+
{businessCalendar && <BusinessCalendarSection businessCalendar={businessCalendar} />}
|
|
27
30
|
</div>
|
|
28
31
|
);
|
|
29
32
|
|
|
30
|
-
export const AdvancedSettings: FC<AdvancedSettingsProps> = ({
|
|
33
|
+
export const AdvancedSettings: FC<AdvancedSettingsProps> = ({
|
|
34
|
+
businessCalendar,
|
|
35
|
+
format,
|
|
36
|
+
setFormat,
|
|
37
|
+
}) => {
|
|
31
38
|
if (format.resultType === 'date') {
|
|
32
|
-
return
|
|
39
|
+
return (
|
|
40
|
+
<DateFormatSettings
|
|
41
|
+
format={format}
|
|
42
|
+
setFormat={setFormat}
|
|
43
|
+
businessCalendar={businessCalendar}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
return (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Button, Flex, NumberField } from '@servicetitan/anvil2';
|
|
2
2
|
import AddIcon from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
3
|
+
import { BusinessCalendarSection, type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
3
4
|
import { FC, Fragment, useState } from 'react';
|
|
4
5
|
import {
|
|
5
6
|
ESignDateSignedOffsetSettings,
|
|
@@ -23,9 +24,12 @@ interface ESignOptionsModalProps {
|
|
|
23
24
|
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
24
25
|
onRequestFormFields?: (formId: number) => void;
|
|
25
26
|
formFieldsLoadingFormId?: number | null;
|
|
27
|
+
/** The business-day exclusion toggle/picker/drawer state and callbacks for this field. */
|
|
28
|
+
businessCalendar?: ScheduleEditorContext;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export const ESignOptionsModal: FC<ESignOptionsModalProps> = ({
|
|
32
|
+
businessCalendar,
|
|
29
33
|
documentFields,
|
|
30
34
|
formFieldsByFormId,
|
|
31
35
|
formFieldsLoadingFormId,
|
|
@@ -51,6 +55,7 @@ export const ESignOptionsModal: FC<ESignOptionsModalProps> = ({
|
|
|
51
55
|
|
|
52
56
|
const handleDaysChange = (days: number | null) => {
|
|
53
57
|
onSave({
|
|
58
|
+
...initialState,
|
|
54
59
|
rules: [...rulesWithConditions, { days: Math.max(Math.min(365, days ?? 0), 0) }],
|
|
55
60
|
});
|
|
56
61
|
};
|
|
@@ -59,11 +64,11 @@ export const ESignOptionsModal: FC<ESignOptionsModalProps> = ({
|
|
|
59
64
|
rules: ESignDateSignedOffsetSettingsRule[],
|
|
60
65
|
finalRule: ESignDateSignedOffsetSettingsFinalRule,
|
|
61
66
|
) => {
|
|
62
|
-
onSave({ rules: [...rules, finalRule] });
|
|
67
|
+
onSave({ ...initialState, rules: [...rules, finalRule] });
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
const handleClearRules = () => {
|
|
66
|
-
onSave(createClearedESignOffsetSettings());
|
|
71
|
+
onSave({ ...initialState, ...createClearedESignOffsetSettings() });
|
|
67
72
|
};
|
|
68
73
|
|
|
69
74
|
return (
|
|
@@ -113,6 +118,12 @@ export const ESignOptionsModal: FC<ESignOptionsModalProps> = ({
|
|
|
113
118
|
schema={schema}
|
|
114
119
|
/>
|
|
115
120
|
)}
|
|
121
|
+
{businessCalendar && (
|
|
122
|
+
<BusinessCalendarSection
|
|
123
|
+
businessCalendar={businessCalendar}
|
|
124
|
+
label="Exclude weekends & holidays from the calculation"
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
116
127
|
</Fragment>
|
|
117
128
|
);
|
|
118
129
|
};
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { Button, Flex, Text } from '@servicetitan/anvil2';
|
|
2
2
|
import IconClose from '@servicetitan/anvil2/assets/icons/material/round/close.svg';
|
|
3
|
-
import {
|
|
3
|
+
import { type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
4
|
+
import { FC, useCallback, useMemo } from 'react';
|
|
4
5
|
import {
|
|
6
|
+
type BusinessCalendar,
|
|
7
|
+
type BusinessDaySchedule,
|
|
8
|
+
type FieldScheduleBinding,
|
|
5
9
|
type FormFieldsByFormIdI,
|
|
6
10
|
type FormInfo,
|
|
11
|
+
type HolidayCatalog,
|
|
7
12
|
type PdfField,
|
|
8
13
|
type RecipientInfo,
|
|
14
|
+
type SaveScheduleRequest,
|
|
9
15
|
type SchemaObject,
|
|
10
16
|
} from '../../interface/types';
|
|
17
|
+
import { getFieldDateCalcOptions, setFieldSchedule, setFieldScheduleEnabled } from '../../utils';
|
|
11
18
|
import { FieldConfigPanel } from './field-config-panel';
|
|
12
19
|
|
|
13
20
|
interface FieldConfigPanelOverlayProps {
|
|
@@ -19,6 +26,19 @@ interface FieldConfigPanelOverlayProps {
|
|
|
19
26
|
selectedField: PdfField;
|
|
20
27
|
recipients: RecipientInfo[];
|
|
21
28
|
hideConditionalLogic?: boolean;
|
|
29
|
+
holidays?: string[];
|
|
30
|
+
fieldCalendars?: Record<string, BusinessCalendar>;
|
|
31
|
+
scheduleBindings?: FieldScheduleBinding[];
|
|
32
|
+
holidayCatalog?: HolidayCatalog;
|
|
33
|
+
holidayCatalogLoading?: boolean;
|
|
34
|
+
holidayCatalogError?: string;
|
|
35
|
+
schedules?: BusinessDaySchedule[];
|
|
36
|
+
schedulesLoading?: boolean;
|
|
37
|
+
schedulesError?: string;
|
|
38
|
+
onScheduleBindingsChange?(bindings: FieldScheduleBinding[]): void;
|
|
39
|
+
onCreateSchedule?(request: SaveScheduleRequest): Promise<BusinessDaySchedule>;
|
|
40
|
+
onUpdateSchedule?(id: number, request: SaveScheduleRequest): Promise<BusinessDaySchedule>;
|
|
41
|
+
onDeleteSchedule?(id: number): Promise<void>;
|
|
22
42
|
onFieldConfigChange(updates: Partial<PdfField>): void;
|
|
23
43
|
onMatchingFieldsConfigChange?(
|
|
24
44
|
predicate: (field: PdfField) => boolean,
|
|
@@ -32,18 +52,92 @@ interface FieldConfigPanelOverlayProps {
|
|
|
32
52
|
export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
|
|
33
53
|
dataModel,
|
|
34
54
|
documentFields = [],
|
|
55
|
+
fieldCalendars,
|
|
35
56
|
formFieldsByFormId,
|
|
36
57
|
forms,
|
|
37
58
|
formulaLoadingFormId,
|
|
38
59
|
hideConditionalLogic,
|
|
60
|
+
holidayCatalog,
|
|
61
|
+
holidayCatalogError,
|
|
62
|
+
holidayCatalogLoading,
|
|
63
|
+
holidays,
|
|
64
|
+
onCreateSchedule,
|
|
39
65
|
onDeleteField,
|
|
66
|
+
onDeleteSchedule,
|
|
40
67
|
onDeselectField,
|
|
41
68
|
onFieldConfigChange,
|
|
42
69
|
onMatchingFieldsConfigChange,
|
|
43
70
|
onRequestFormFields,
|
|
71
|
+
onScheduleBindingsChange,
|
|
72
|
+
onUpdateSchedule,
|
|
44
73
|
recipients,
|
|
74
|
+
scheduleBindings,
|
|
75
|
+
schedules,
|
|
76
|
+
schedulesError,
|
|
77
|
+
schedulesLoading,
|
|
45
78
|
selectedField,
|
|
46
79
|
}) => {
|
|
80
|
+
const dateCalcOptions = useMemo(
|
|
81
|
+
() => getFieldDateCalcOptions(selectedField.id, scheduleBindings, holidays, fieldCalendars),
|
|
82
|
+
[selectedField.id, scheduleBindings, holidays, fieldCalendars],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const selectedFieldId = selectedField.id;
|
|
86
|
+
const selectedBinding = useMemo(
|
|
87
|
+
() => scheduleBindings?.find(b => b.fieldKey === selectedFieldId),
|
|
88
|
+
[scheduleBindings, selectedFieldId],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const handleToggle = useCallback(
|
|
92
|
+
(enabled: boolean) => {
|
|
93
|
+
onScheduleBindingsChange?.(
|
|
94
|
+
setFieldScheduleEnabled(scheduleBindings ?? [], selectedFieldId, enabled),
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
[scheduleBindings, selectedFieldId, onScheduleBindingsChange],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const handleSelectSchedule = useCallback(
|
|
101
|
+
(scheduleId: number | undefined) => {
|
|
102
|
+
onScheduleBindingsChange?.(
|
|
103
|
+
setFieldSchedule(scheduleBindings ?? [], selectedFieldId, scheduleId),
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
[scheduleBindings, selectedFieldId, onScheduleBindingsChange],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const businessCalendar: ScheduleEditorContext = useMemo(
|
|
110
|
+
() => ({
|
|
111
|
+
enabled: !!selectedBinding,
|
|
112
|
+
selectedScheduleId: selectedBinding?.scheduleId,
|
|
113
|
+
onToggle: handleToggle,
|
|
114
|
+
onSelectSchedule: handleSelectSchedule,
|
|
115
|
+
holidayCatalog,
|
|
116
|
+
holidayCatalogLoading,
|
|
117
|
+
holidayCatalogError,
|
|
118
|
+
schedules,
|
|
119
|
+
schedulesLoading,
|
|
120
|
+
schedulesError,
|
|
121
|
+
onCreateSchedule,
|
|
122
|
+
onUpdateSchedule,
|
|
123
|
+
onDeleteSchedule,
|
|
124
|
+
}),
|
|
125
|
+
[
|
|
126
|
+
selectedBinding,
|
|
127
|
+
handleToggle,
|
|
128
|
+
handleSelectSchedule,
|
|
129
|
+
holidayCatalog,
|
|
130
|
+
holidayCatalogLoading,
|
|
131
|
+
holidayCatalogError,
|
|
132
|
+
schedules,
|
|
133
|
+
schedulesLoading,
|
|
134
|
+
schedulesError,
|
|
135
|
+
onCreateSchedule,
|
|
136
|
+
onUpdateSchedule,
|
|
137
|
+
onDeleteSchedule,
|
|
138
|
+
],
|
|
139
|
+
);
|
|
140
|
+
|
|
47
141
|
return (
|
|
48
142
|
<Flex className="dte-field-config-panel-overlay" onClick={e => e.stopPropagation()}>
|
|
49
143
|
<Flex
|
|
@@ -75,6 +169,8 @@ export const FieldConfigPanelOverlay: FC<FieldConfigPanelOverlayProps> = ({
|
|
|
75
169
|
onMatchingFieldsConfigChange={onMatchingFieldsConfigChange}
|
|
76
170
|
onDeleteField={onDeleteField}
|
|
77
171
|
hideConditionalLogic={hideConditionalLogic}
|
|
172
|
+
dateCalcOptions={dateCalcOptions}
|
|
173
|
+
businessCalendar={businessCalendar}
|
|
78
174
|
onRequestFormFields={onRequestFormFields}
|
|
79
175
|
/>
|
|
80
176
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button, Checkbox, Flex, SelectFieldSync, Textarea, TextField } from '@servicetitan/anvil2';
|
|
2
|
+
import { type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
2
3
|
import { FC, useMemo } from 'react';
|
|
3
4
|
import { FIELD_FONT_SIZE_OPTIONS } from '../../constants';
|
|
4
5
|
import {
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
type SchemaObject,
|
|
13
14
|
} from '../../interface/types';
|
|
14
15
|
import {
|
|
16
|
+
type DateCalcOptions,
|
|
15
17
|
generateESignPath,
|
|
16
18
|
generateFillablePath,
|
|
17
19
|
getFieldFontSize,
|
|
@@ -35,6 +37,10 @@ interface FieldConfigPanelProps {
|
|
|
35
37
|
formulaLoadingFormId?: number | null;
|
|
36
38
|
recipients: RecipientInfo[];
|
|
37
39
|
hideConditionalLogic?: boolean;
|
|
40
|
+
/** The calendar of this field's formula preview. */
|
|
41
|
+
dateCalcOptions?: DateCalcOptions;
|
|
42
|
+
/** The business-calendar toggle/picker/drawer state and callbacks for this field. */
|
|
43
|
+
businessCalendar?: ScheduleEditorContext;
|
|
38
44
|
onDeleteField(): void;
|
|
39
45
|
onFieldConfigChange(updates: Partial<PdfField>): void;
|
|
40
46
|
onMatchingFieldsConfigChange?(
|
|
@@ -45,7 +51,9 @@ interface FieldConfigPanelProps {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
|
|
54
|
+
businessCalendar,
|
|
48
55
|
dataModel,
|
|
56
|
+
dateCalcOptions,
|
|
49
57
|
documentFields = [],
|
|
50
58
|
field,
|
|
51
59
|
formFieldsByFormId,
|
|
@@ -88,6 +96,23 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
|
|
|
88
96
|
onFieldConfigChange({ data });
|
|
89
97
|
};
|
|
90
98
|
|
|
99
|
+
/*
|
|
100
|
+
* Reuses the catalog/CRUD half of `businessCalendar` (shared tenant schedules, same as a
|
|
101
|
+
* calculated field's), but overrides the enabled/selected-schedule half to read and write
|
|
102
|
+
* `ESignDateSignedOffsetSettings` instead of a `scheduleBindings` entry, since the toggle here
|
|
103
|
+
* applies to every `dateSignedOffset` field in the document, same as `rules` does.
|
|
104
|
+
*/
|
|
105
|
+
const dateSignedOffsetData = field.data as ESignDateSignedOffsetSettings;
|
|
106
|
+
const eSignBusinessCalendar: ScheduleEditorContext | undefined = businessCalendar && {
|
|
107
|
+
...businessCalendar,
|
|
108
|
+
enabled: !!dateSignedOffsetData?.businessCalendarEnabled,
|
|
109
|
+
selectedScheduleId: dateSignedOffsetData?.scheduleId,
|
|
110
|
+
onToggle: businessCalendarEnabled =>
|
|
111
|
+
handleDateSignedOffsetSave({ ...dateSignedOffsetData, businessCalendarEnabled }),
|
|
112
|
+
onSelectSchedule: scheduleId =>
|
|
113
|
+
handleDateSignedOffsetSave({ ...dateSignedOffsetData, scheduleId }),
|
|
114
|
+
};
|
|
115
|
+
|
|
91
116
|
const handleRecipientChange = (item: { id: string; label: string } | null) => {
|
|
92
117
|
if (!item) {
|
|
93
118
|
return;
|
|
@@ -245,7 +270,9 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
|
|
|
245
270
|
)}
|
|
246
271
|
{field.type === FieldTypeEnum.calculated && (
|
|
247
272
|
<FormulaGenerator
|
|
273
|
+
businessCalendar={businessCalendar}
|
|
248
274
|
dataModel={dataModel}
|
|
275
|
+
dateCalcOptions={dateCalcOptions}
|
|
249
276
|
documentFields={documentFields}
|
|
250
277
|
formFieldsByFormId={formFieldsByFormId}
|
|
251
278
|
forms={forms}
|
|
@@ -281,6 +308,7 @@ export const FieldConfigPanel: FC<FieldConfigPanelProps> = ({
|
|
|
281
308
|
<ESignOptionsModal
|
|
282
309
|
initialState={field.data as ESignDateSignedOffsetSettings}
|
|
283
310
|
onSave={handleDateSignedOffsetSave}
|
|
311
|
+
businessCalendar={eSignBusinessCalendar}
|
|
284
312
|
documentFields={documentFields}
|
|
285
313
|
formFieldsByFormId={formFieldsByFormId}
|
|
286
314
|
formFieldsLoadingFormId={formulaLoadingFormId}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Button } from '@servicetitan/anvil2';
|
|
2
2
|
import EditIcon from '@servicetitan/anvil2/assets/icons/material/round/edit.svg';
|
|
3
|
+
import { type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
3
4
|
import { FC, Fragment, useMemo, useState } from 'react';
|
|
4
5
|
import {
|
|
5
6
|
type CalculatedFieldFormat,
|
|
@@ -12,11 +13,13 @@ import {
|
|
|
12
13
|
type SchemaObject,
|
|
13
14
|
type StructuredFormula,
|
|
14
15
|
} from '../../interface/types';
|
|
15
|
-
import { extractGroupedFieldsFromDataModel } from '../../utils';
|
|
16
|
+
import { type DateCalcOptions, extractGroupedFieldsFromDataModel } from '../../utils';
|
|
16
17
|
import { FormulaModal } from './formula-modal';
|
|
17
18
|
|
|
18
19
|
export interface FormulaGeneratorProps {
|
|
20
|
+
businessCalendar?: ScheduleEditorContext;
|
|
19
21
|
dataModel?: SchemaObject;
|
|
22
|
+
dateCalcOptions?: DateCalcOptions;
|
|
20
23
|
documentFields?: PdfField[];
|
|
21
24
|
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
22
25
|
forms: FormInfo[];
|
|
@@ -43,7 +46,9 @@ function getFillableFilteredBuySubTypeFields(
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
export const FormulaGenerator: FC<FormulaGeneratorProps> = ({
|
|
49
|
+
businessCalendar,
|
|
46
50
|
dataModel,
|
|
51
|
+
dateCalcOptions,
|
|
47
52
|
documentFields = [],
|
|
48
53
|
formFieldsByFormId,
|
|
49
54
|
forms,
|
|
@@ -87,7 +92,9 @@ export const FormulaGenerator: FC<FormulaGeneratorProps> = ({
|
|
|
87
92
|
<FormulaModal
|
|
88
93
|
initialFormula={formula}
|
|
89
94
|
initialFormat={formulaFormat}
|
|
95
|
+
businessCalendar={businessCalendar}
|
|
90
96
|
dataModelGroups={dataModelGroups}
|
|
97
|
+
dateCalcOptions={dateCalcOptions}
|
|
91
98
|
fillableFieldsFromDocument={fillableFieldsFromDocument}
|
|
92
99
|
formFieldsByFormId={formFieldsByFormId}
|
|
93
100
|
forms={forms}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Button, Dialog, Divider, Flex } from '@servicetitan/anvil2';
|
|
2
2
|
import IconRedo from '@servicetitan/anvil2/assets/icons/material/round/redo.svg';
|
|
3
3
|
import IconUndo from '@servicetitan/anvil2/assets/icons/material/round/undo.svg';
|
|
4
|
+
import { type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
4
5
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_DATE_CALCULATED_FORMAT,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
type StructuredFormula,
|
|
19
20
|
} from '../../interface/types';
|
|
20
21
|
import {
|
|
22
|
+
type DateCalcOptions,
|
|
21
23
|
evaluateFormula,
|
|
22
24
|
formatCalculatedResult,
|
|
23
25
|
formFieldInfosToCalculationOptions,
|
|
@@ -68,7 +70,10 @@ function setSampleValueAtPath(target: DataModelValues, path: string, value: unkn
|
|
|
68
70
|
export interface FormulaModalProps {
|
|
69
71
|
initialFormula: StructuredFormula | undefined;
|
|
70
72
|
initialFormat?: CalculatedFieldFormat;
|
|
73
|
+
businessCalendar?: ScheduleEditorContext;
|
|
71
74
|
dataModelGroups: DataModelFieldGroup[];
|
|
75
|
+
/** The calendar the preview uses: the field's own, or the default one. */
|
|
76
|
+
dateCalcOptions?: DateCalcOptions;
|
|
72
77
|
fillableFieldsFromDocument?: FieldTypeOption[];
|
|
73
78
|
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
74
79
|
forms: FormInfo[];
|
|
@@ -79,7 +84,9 @@ export interface FormulaModalProps {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
export const FormulaModal: FC<FormulaModalProps> = ({
|
|
87
|
+
businessCalendar,
|
|
82
88
|
dataModelGroups,
|
|
89
|
+
dateCalcOptions,
|
|
83
90
|
fillableFieldsFromDocument = [],
|
|
84
91
|
formFieldsByFormId = {},
|
|
85
92
|
forms,
|
|
@@ -255,21 +262,28 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
255
262
|
if (parsedTokens.length === 0 || !formulaValidation.valid) {
|
|
256
263
|
return '';
|
|
257
264
|
}
|
|
258
|
-
return hasDivisionByZero({ tokens: parsedTokens }, sampleData)
|
|
265
|
+
return hasDivisionByZero({ tokens: parsedTokens }, sampleData, dateCalcOptions)
|
|
259
266
|
? 'Division by zero is not allowed'
|
|
260
267
|
: '';
|
|
261
|
-
}, [parsedTokens, formulaValidation.valid, sampleData]);
|
|
268
|
+
}, [parsedTokens, formulaValidation.valid, sampleData, dateCalcOptions]);
|
|
262
269
|
|
|
263
270
|
const previewValue = useMemo(() => {
|
|
264
271
|
if (parsedTokens.length === 0 || !formulaValidation.valid || divisionByZeroError) {
|
|
265
272
|
return '';
|
|
266
273
|
}
|
|
267
|
-
const computed = evaluateFormula({ tokens: parsedTokens }, sampleData);
|
|
274
|
+
const computed = evaluateFormula({ tokens: parsedTokens }, sampleData, dateCalcOptions);
|
|
268
275
|
if (computed === null) {
|
|
269
276
|
return '';
|
|
270
277
|
}
|
|
271
278
|
return formatCalculatedResult(computed, format);
|
|
272
|
-
}, [
|
|
279
|
+
}, [
|
|
280
|
+
parsedTokens,
|
|
281
|
+
formulaValidation.valid,
|
|
282
|
+
divisionByZeroError,
|
|
283
|
+
sampleData,
|
|
284
|
+
format,
|
|
285
|
+
dateCalcOptions,
|
|
286
|
+
]);
|
|
273
287
|
|
|
274
288
|
const handleClose = useCallback(() => {
|
|
275
289
|
onClose();
|
|
@@ -317,6 +331,7 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
317
331
|
selectedPaths={formulaEditor.selectedFieldPaths}
|
|
318
332
|
/>
|
|
319
333
|
<FormulaWorkspace
|
|
334
|
+
businessCalendar={businessCalendar}
|
|
320
335
|
disabledOperators={formulaEditor.disabledOperators}
|
|
321
336
|
editorRef={formulaEditor.editorRef}
|
|
322
337
|
isInvalid={isInvalid && formulaEditor.isDirty}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { Alert, Button, Flex,
|
|
1
|
+
import { Alert, Button, Flex, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import IconExpandMore from '@servicetitan/anvil2/assets/icons/material/round/expand_more.svg';
|
|
3
|
+
import IconArrowForward from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_right.svg';
|
|
4
|
+
|
|
5
|
+
import { type ScheduleEditorContext } from '@servicetitan/dte-shared';
|
|
2
6
|
import {
|
|
3
7
|
Dispatch,
|
|
4
8
|
FC,
|
|
@@ -17,6 +21,7 @@ import { ResultTypeSelector } from './result-type-selector';
|
|
|
17
21
|
interface FormulaWorkspaceProps {
|
|
18
22
|
actions: ReactNode;
|
|
19
23
|
advancedOpen: boolean;
|
|
24
|
+
businessCalendar?: ScheduleEditorContext;
|
|
20
25
|
disabledOperators?: Set<string>;
|
|
21
26
|
editorRef: RefObject<HTMLDivElement | null>;
|
|
22
27
|
format: CalculatedFieldFormat;
|
|
@@ -38,6 +43,7 @@ interface FormulaWorkspaceProps {
|
|
|
38
43
|
export const FormulaWorkspace: FC<FormulaWorkspaceProps> = ({
|
|
39
44
|
actions,
|
|
40
45
|
advancedOpen,
|
|
46
|
+
businessCalendar,
|
|
41
47
|
disabledOperators,
|
|
42
48
|
editorRef,
|
|
43
49
|
format,
|
|
@@ -75,7 +81,7 @@ export const FormulaWorkspace: FC<FormulaWorkspaceProps> = ({
|
|
|
75
81
|
);
|
|
76
82
|
|
|
77
83
|
return (
|
|
78
|
-
<Flex direction="column" flex={1} gap={2} style={{ padding: '12px' }}>
|
|
84
|
+
<Flex direction="column" flex={1} gap={2} style={{ padding: '12px', overflowY: 'auto' }}>
|
|
79
85
|
<Flex direction="column" gap={1}>
|
|
80
86
|
<Text variant="body" size="small">
|
|
81
87
|
Preview (uses demo sample data)
|
|
@@ -132,10 +138,29 @@ export const FormulaWorkspace: FC<FormulaWorkspaceProps> = ({
|
|
|
132
138
|
})}
|
|
133
139
|
</Flex>
|
|
134
140
|
<ResultTypeSelector format={format} onChange={onResultTypeChange} />
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
<Flex
|
|
142
|
+
role="button"
|
|
143
|
+
onClick={onToggleAdvanced}
|
|
144
|
+
alignItems="center"
|
|
145
|
+
gap={1}
|
|
146
|
+
style={{ cursor: 'pointer', marginTop: 16 }}
|
|
147
|
+
>
|
|
148
|
+
<Text variant="eyebrow" size="small">
|
|
149
|
+
Advanced settings
|
|
150
|
+
</Text>
|
|
151
|
+
{advancedOpen ? (
|
|
152
|
+
<IconExpandMore fontSize="small" />
|
|
153
|
+
) : (
|
|
154
|
+
<IconArrowForward fontSize="small" />
|
|
155
|
+
)}
|
|
156
|
+
</Flex>
|
|
157
|
+
{advancedOpen && (
|
|
158
|
+
<AdvancedSettings
|
|
159
|
+
format={format}
|
|
160
|
+
setFormat={setFormat}
|
|
161
|
+
businessCalendar={businessCalendar}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
139
164
|
</Flex>
|
|
140
165
|
);
|
|
141
166
|
};
|