@servicetitan/dte-pdf-editor 1.87.0 → 1.88.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/README.md +37 -30
- package/dist/components/display-conditions/condition-row.d.ts.map +1 -1
- package/dist/components/display-conditions/condition-row.js +51 -22
- package/dist/components/display-conditions/condition-row.js.map +1 -1
- package/dist/components/display-conditions/condition-value-control.d.ts.map +1 -1
- package/dist/components/display-conditions/condition-value-control.js +1 -2
- package/dist/components/display-conditions/condition-value-control.js.map +1 -1
- package/dist/components/display-conditions/display-condition-modal.d.ts.map +1 -1
- package/dist/components/display-conditions/display-condition-modal.js +6 -49
- package/dist/components/display-conditions/display-condition-modal.js.map +1 -1
- package/dist/components/field-config-panel/e-sign-date-rules-summary.d.ts +11 -0
- package/dist/components/field-config-panel/e-sign-date-rules-summary.d.ts.map +1 -0
- package/dist/components/field-config-panel/e-sign-date-rules-summary.js +36 -0
- package/dist/components/field-config-panel/e-sign-date-rules-summary.js.map +1 -0
- package/dist/components/field-config-panel/e-sign-date-signed-offset-modal.d.ts +16 -0
- package/dist/components/field-config-panel/e-sign-date-signed-offset-modal.d.ts.map +1 -0
- package/dist/components/field-config-panel/e-sign-date-signed-offset-modal.js +62 -0
- package/dist/components/field-config-panel/e-sign-date-signed-offset-modal.js.map +1 -0
- package/dist/components/field-config-panel/e-sign-offset-rule-row.d.ts +16 -0
- package/dist/components/field-config-panel/e-sign-offset-rule-row.d.ts.map +1 -0
- package/dist/components/field-config-panel/e-sign-offset-rule-row.js +25 -0
- package/dist/components/field-config-panel/e-sign-offset-rule-row.js.map +1 -0
- package/dist/components/field-config-panel/e-sign-options-modal.d.ts +15 -0
- package/dist/components/field-config-panel/e-sign-options-modal.d.ts.map +1 -0
- package/dist/components/field-config-panel/e-sign-options-modal.js +26 -0
- package/dist/components/field-config-panel/e-sign-options-modal.js.map +1 -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 +30 -28
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.js +1 -6
- package/dist/components/field-sidebar/e-sign-field-type-list.js.map +1 -1
- package/dist/constants/field.constants.d.ts +1 -5
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +21 -7
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
- package/dist/hooks/usePdfFieldDnD.js +13 -5
- package/dist/hooks/usePdfFieldDnD.js.map +1 -1
- package/dist/interface/types.d.ts +26 -1
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js +1 -0
- package/dist/interface/types.js.map +1 -1
- package/dist/styles/field-config-panel.css +11 -0
- package/dist/styles/field-type.css +12 -0
- package/dist/styles/index.css +1 -0
- package/dist/styles/variables.css +2 -0
- package/dist/utils/conditions/condition-match-type.utils.d.ts +14 -0
- package/dist/utils/conditions/condition-match-type.utils.d.ts.map +1 -0
- package/dist/utils/conditions/condition-match-type.utils.js +50 -0
- package/dist/utils/conditions/condition-match-type.utils.js.map +1 -0
- package/dist/utils/conditions/e-sign-offset-rules.utils.d.ts +12 -0
- package/dist/utils/conditions/e-sign-offset-rules.utils.d.ts.map +1 -0
- package/dist/utils/conditions/e-sign-offset-rules.utils.js +19 -0
- package/dist/utils/conditions/e-sign-offset-rules.utils.js.map +1 -0
- package/dist/utils/conditions/index.d.ts +2 -0
- package/dist/utils/conditions/index.d.ts.map +1 -1
- package/dist/utils/conditions/index.js +2 -0
- package/dist/utils/conditions/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/display-conditions/condition-row.tsx +114 -131
- package/src/components/display-conditions/condition-value-control.tsx +1 -2
- package/src/components/display-conditions/display-condition-modal.tsx +13 -63
- package/src/components/field-config-panel/e-sign-date-rules-summary.tsx +114 -0
- package/src/components/field-config-panel/e-sign-date-signed-offset-modal.tsx +197 -0
- package/src/components/field-config-panel/e-sign-offset-rule-row.tsx +91 -0
- package/src/components/field-config-panel/e-sign-options-modal.tsx +112 -0
- package/src/components/field-config-panel/field-config-panel.tsx +70 -91
- package/src/components/field-sidebar/e-sign-field-type-list.tsx +11 -14
- package/src/constants/field.constants.ts +21 -8
- package/src/hooks/usePdfFieldDnD.ts +17 -2
- package/src/interface/types.ts +34 -1
- package/src/styles/field-config-panel.css +11 -0
- package/src/styles/field-type.css +12 -0
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +2 -0
- package/src/utils/conditions/__tests__/condition-match-type.utils.test.ts +104 -0
- package/src/utils/conditions/__tests__/e-sign-offset-rules.utils.test.ts +51 -0
- package/src/utils/conditions/condition-match-type.utils.ts +80 -0
- package/src/utils/conditions/e-sign-offset-rules.utils.ts +32 -0
- package/src/utils/conditions/index.ts +2 -0
|
@@ -9,48 +9,15 @@ import type {
|
|
|
9
9
|
SchemaObject,
|
|
10
10
|
} from '../../interface/types';
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
applyConditionMatchType,
|
|
13
|
+
type ConditionMatchType,
|
|
14
|
+
deriveConditionMatchType,
|
|
13
15
|
getDocumentFieldsDataPointOptions,
|
|
14
16
|
getSchemaDataPointOptions,
|
|
15
|
-
|
|
17
|
+
isValidConditionGroups,
|
|
16
18
|
} from '../../utils';
|
|
17
19
|
import { ConditionGroupsSection } from './condition-groups-section';
|
|
18
20
|
|
|
19
|
-
type ConditionMatchType = 'and' | 'or';
|
|
20
|
-
|
|
21
|
-
const deriveConditionMatchType = (conditionState: DisplayConditionState): ConditionMatchType => {
|
|
22
|
-
for (let g = 0; g < conditionState.groups.length; g++) {
|
|
23
|
-
const group = conditionState.groups[g];
|
|
24
|
-
if (g > 0 && group.logicalOperator === 'or') {
|
|
25
|
-
return 'or';
|
|
26
|
-
}
|
|
27
|
-
for (let i = 1; i < group.conditions.length; i++) {
|
|
28
|
-
if (group.conditions[i]?.logicalOperator === 'or') {
|
|
29
|
-
return 'or';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return 'and';
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const applyConditionMatchType = (
|
|
37
|
-
conditionState: DisplayConditionState,
|
|
38
|
-
matchType: ConditionMatchType,
|
|
39
|
-
): DisplayConditionState => {
|
|
40
|
-
return {
|
|
41
|
-
...conditionState,
|
|
42
|
-
groups: conditionState.groups.map((group, groupIndex) => ({
|
|
43
|
-
...group,
|
|
44
|
-
...(groupIndex > 0 ? { logicalOperator: matchType } : {}),
|
|
45
|
-
conditions: group.conditions.map((c, i) =>
|
|
46
|
-
i === 0
|
|
47
|
-
? { ...c, logicalOperator: undefined }
|
|
48
|
-
: { ...c, logicalOperator: matchType },
|
|
49
|
-
),
|
|
50
|
-
})),
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
|
|
54
21
|
const BEHAVIOR_OPTIONS = [
|
|
55
22
|
{ label: 'Show', value: 'show' },
|
|
56
23
|
{ label: 'Hide', value: 'hide' },
|
|
@@ -159,32 +126,15 @@ export function DisplayConditionModal({
|
|
|
159
126
|
setState(prev => applyConditionMatchType(prev, value));
|
|
160
127
|
}, []);
|
|
161
128
|
|
|
162
|
-
const canSave = useMemo(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const trimmedValue = condition.value.trim();
|
|
172
|
-
if (!trimmedValue) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
const fieldType = getDisplayConditionFieldTypeForKey(
|
|
176
|
-
condition.dataPointKey,
|
|
177
|
-
mergeTagOptions,
|
|
178
|
-
fillableOptions,
|
|
179
|
-
formFieldsByFormId ?? {},
|
|
180
|
-
);
|
|
181
|
-
if (fieldType === 'number' && !isValidNumber(trimmedValue)) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return state.groups.length > 0;
|
|
187
|
-
}, [fillableOptions, formFieldsByFormId, mergeTagOptions, state.groups]);
|
|
129
|
+
const canSave = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
isValidConditionGroups(state.groups, {
|
|
132
|
+
fillableOptions,
|
|
133
|
+
formFieldsByFormId,
|
|
134
|
+
mergeTagOptions,
|
|
135
|
+
}),
|
|
136
|
+
[fillableOptions, formFieldsByFormId, mergeTagOptions, state.groups],
|
|
137
|
+
);
|
|
188
138
|
|
|
189
139
|
return (
|
|
190
140
|
<Dialog open onClose={handleClose} size="xlarge">
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Button, Flex, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import EditIcon from '@servicetitan/anvil2/assets/icons/material/round/edit.svg';
|
|
3
|
+
import { FC, useMemo } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
CHECKBOX_OPERATORS,
|
|
6
|
+
type ConditionOperator,
|
|
7
|
+
type DisplayConditionSingle,
|
|
8
|
+
type ESignDateSignedOffsetSettingsRule,
|
|
9
|
+
NUMBER_OPERATORS,
|
|
10
|
+
type PdfField,
|
|
11
|
+
RADIO_OPERATORS,
|
|
12
|
+
type SchemaObject,
|
|
13
|
+
STRING_OPERATORS,
|
|
14
|
+
VALUE_LESS_OPERATORS,
|
|
15
|
+
} from '../../interface/types';
|
|
16
|
+
import { getDataPointOptions } from '../../utils';
|
|
17
|
+
|
|
18
|
+
const ALL_OPERATORS = [
|
|
19
|
+
...NUMBER_OPERATORS,
|
|
20
|
+
...STRING_OPERATORS,
|
|
21
|
+
...RADIO_OPERATORS,
|
|
22
|
+
...CHECKBOX_OPERATORS,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const getOperatorSymbol = (operator: string): string =>
|
|
26
|
+
ALL_OPERATORS.find(op => op.value === operator)?.label.split(' (')[0] ?? operator;
|
|
27
|
+
|
|
28
|
+
export interface ESignDateRulesSummaryProps {
|
|
29
|
+
elseDays: number;
|
|
30
|
+
rulesWithConditions: ESignDateSignedOffsetSettingsRule[];
|
|
31
|
+
schema?: SchemaObject;
|
|
32
|
+
documentFields?: PdfField[];
|
|
33
|
+
onEdit: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const ESignDateRulesSummary: FC<ESignDateRulesSummaryProps> = ({
|
|
37
|
+
documentFields,
|
|
38
|
+
elseDays,
|
|
39
|
+
onEdit,
|
|
40
|
+
rulesWithConditions,
|
|
41
|
+
schema,
|
|
42
|
+
}) => {
|
|
43
|
+
const dataPointOptions = useMemo(
|
|
44
|
+
() => getDataPointOptions(schema, documentFields),
|
|
45
|
+
[schema, documentFields],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const getConditionText = (condition: DisplayConditionSingle) => {
|
|
49
|
+
const dataPoint = dataPointOptions.find(
|
|
50
|
+
option => option.fullKey === condition.dataPointKey,
|
|
51
|
+
);
|
|
52
|
+
const label = dataPoint?.title ?? condition.dataPointKey;
|
|
53
|
+
const symbol = getOperatorSymbol(condition.operator).replace('==', '=');
|
|
54
|
+
const isValueLess =
|
|
55
|
+
dataPoint?.isCheckbox === true ||
|
|
56
|
+
VALUE_LESS_OPERATORS.includes(condition.operator as ConditionOperator);
|
|
57
|
+
const value = condition.valueLabel ?? condition.value;
|
|
58
|
+
|
|
59
|
+
return `${label} ${symbol}${isValueLess ? '' : ` ${value}`}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Flex direction="column" gap="1">
|
|
64
|
+
<Flex justifyContent="space-between" alignItems="center">
|
|
65
|
+
<Text variant="body" size="medium" style={{ fontWeight: 500 }}>
|
|
66
|
+
Calculated date rules
|
|
67
|
+
</Text>
|
|
68
|
+
<Button
|
|
69
|
+
appearance="ghost"
|
|
70
|
+
size="small"
|
|
71
|
+
icon={{ before: EditIcon }}
|
|
72
|
+
onClick={onEdit}
|
|
73
|
+
>
|
|
74
|
+
Edit
|
|
75
|
+
</Button>
|
|
76
|
+
</Flex>
|
|
77
|
+
<Flex direction="column" gap="2" className="dte-rule-card --active">
|
|
78
|
+
{rulesWithConditions.map((rule, index) => {
|
|
79
|
+
const condition = rule.condition.groups[0]?.conditions[0];
|
|
80
|
+
return (
|
|
81
|
+
<Flex key={rule.condition.groups[0]?.id ?? index} direction="column">
|
|
82
|
+
<Text variant="body">{rule.days} After Signing When:</Text>
|
|
83
|
+
{condition && (
|
|
84
|
+
<Text
|
|
85
|
+
variant="body"
|
|
86
|
+
size="small"
|
|
87
|
+
style={{
|
|
88
|
+
color: '#422799',
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{getConditionText(condition)}
|
|
92
|
+
</Text>
|
|
93
|
+
)}
|
|
94
|
+
</Flex>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
<Flex direction="column">
|
|
98
|
+
<Text variant="body" size="medium">
|
|
99
|
+
{elseDays} After Signing When:
|
|
100
|
+
</Text>
|
|
101
|
+
<Text
|
|
102
|
+
variant="body"
|
|
103
|
+
size="small"
|
|
104
|
+
style={{
|
|
105
|
+
color: '#422799',
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
ELSE
|
|
109
|
+
</Text>
|
|
110
|
+
</Flex>
|
|
111
|
+
</Flex>
|
|
112
|
+
</Flex>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why not `DisplayConditionModal`:
|
|
3
|
+
*
|
|
4
|
+
* `DisplayConditionModal` manages exactly one `DisplayConditionState` (a show/hide behavior
|
|
5
|
+
* toggle plus a list of condition groups) for a single field. `ESignDateSignedOffsetSettings` is
|
|
6
|
+
* a different shape entirely: a *list* of independent `{ days, condition }` rules, with no
|
|
7
|
+
* show/hide concept at all (`ESignDateSignedOffsetSettingsRule.condition` is "only show" by
|
|
8
|
+
* contract). The trailing, condition-less "else" row required by that settings type is owned by
|
|
9
|
+
* `ESignOptionsModal` (its own always-visible "Day offset" field) — this modal only ever manages
|
|
10
|
+
* the conditioned rules that precede it.
|
|
11
|
+
*
|
|
12
|
+
* Instead, this modal follows the same Dialog/Header/Content/Footer structure and reuses the same
|
|
13
|
+
* building blocks `DisplayConditionModal` is built from: `ConditionGroup`/`ConditionRow` for the
|
|
14
|
+
* condition-editing UI, `getSchemaDataPointOptions`/`getDocumentFieldsDataPointOptions` for
|
|
15
|
+
* data-point resolution, and `isValidConditionGroups` extracted from `DisplayConditionModal`.
|
|
16
|
+
* Nothing ESign-specific was added to any of those shared pieces.
|
|
17
|
+
*/
|
|
18
|
+
import { Button, Chip, Dialog, Flex, NumberField, Text } from '@servicetitan/anvil2';
|
|
19
|
+
import PlusCircleIcon from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
20
|
+
import { FC, useCallback, useMemo, useState } from 'react';
|
|
21
|
+
import { MODAL_CONTENT_MAX_HEIGHT } from '../../constants';
|
|
22
|
+
import type {
|
|
23
|
+
ESignDateSignedOffsetSettingsFinalRule,
|
|
24
|
+
ESignDateSignedOffsetSettingsRule,
|
|
25
|
+
FormFieldsByFormIdI,
|
|
26
|
+
FormInfo,
|
|
27
|
+
PdfField,
|
|
28
|
+
SchemaObject,
|
|
29
|
+
} from '../../interface/types';
|
|
30
|
+
import {
|
|
31
|
+
addESignOffsetRule,
|
|
32
|
+
canAddESignOffsetRule,
|
|
33
|
+
createESignOffsetRule,
|
|
34
|
+
getDocumentFieldsDataPointOptions,
|
|
35
|
+
getSchemaDataPointOptions,
|
|
36
|
+
isValidConditionGroups,
|
|
37
|
+
updateESignOffsetRule,
|
|
38
|
+
} from '../../utils';
|
|
39
|
+
import { ESignOffsetRuleRow } from './e-sign-offset-rule-row';
|
|
40
|
+
|
|
41
|
+
export interface ESignDateSignedOffsetModalProps {
|
|
42
|
+
onClose: () => void;
|
|
43
|
+
onSave: (
|
|
44
|
+
state: ESignDateSignedOffsetSettingsRule[],
|
|
45
|
+
finalRule: ESignDateSignedOffsetSettingsFinalRule,
|
|
46
|
+
) => void;
|
|
47
|
+
initialState: ESignDateSignedOffsetSettingsRule[];
|
|
48
|
+
finalRule: ESignDateSignedOffsetSettingsFinalRule;
|
|
49
|
+
schema?: SchemaObject;
|
|
50
|
+
documentFields?: PdfField[];
|
|
51
|
+
forms?: FormInfo[];
|
|
52
|
+
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
53
|
+
onRequestFormFields?: (formId: number) => void;
|
|
54
|
+
formFieldsLoadingFormId?: number | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const ESignDateSignedOffsetModal: FC<ESignDateSignedOffsetModalProps> = ({
|
|
58
|
+
documentFields,
|
|
59
|
+
finalRule,
|
|
60
|
+
formFieldsByFormId,
|
|
61
|
+
formFieldsLoadingFormId,
|
|
62
|
+
forms,
|
|
63
|
+
initialState,
|
|
64
|
+
onClose,
|
|
65
|
+
onRequestFormFields,
|
|
66
|
+
onSave,
|
|
67
|
+
schema,
|
|
68
|
+
}) => {
|
|
69
|
+
const [rules, setRules] = useState<ESignDateSignedOffsetSettingsRule[]>(
|
|
70
|
+
initialState.length === 0 ? [createESignOffsetRule()] : initialState,
|
|
71
|
+
);
|
|
72
|
+
const [finalRuleDays, setFinalRuleDays] = useState<number>(finalRule.days);
|
|
73
|
+
|
|
74
|
+
const mergeTagOptions = useMemo(() => getSchemaDataPointOptions(schema), [schema]);
|
|
75
|
+
|
|
76
|
+
const fillableOptions = useMemo(
|
|
77
|
+
() => getDocumentFieldsDataPointOptions(documentFields),
|
|
78
|
+
[documentFields],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const handleClose = useCallback(() => onClose(), [onClose]);
|
|
82
|
+
|
|
83
|
+
const handleSave = useCallback(() => {
|
|
84
|
+
onSave(rules, { days: finalRuleDays });
|
|
85
|
+
onClose();
|
|
86
|
+
}, [finalRuleDays, onClose, onSave, rules]);
|
|
87
|
+
|
|
88
|
+
const handleFinalRuleDaysChange = useCallback((days: number | null) => {
|
|
89
|
+
setFinalRuleDays(Math.max(Math.min(365, days ?? 0), 0));
|
|
90
|
+
}, []);
|
|
91
|
+
|
|
92
|
+
const handleAddRule = useCallback(() => {
|
|
93
|
+
setRules(prev => addESignOffsetRule(prev));
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const handleRemoveRule = useCallback((index: number) => {
|
|
97
|
+
setRules(prev => prev.filter((_, i) => i !== index));
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const handleUpdateRule = useCallback(
|
|
101
|
+
(index: number, rule: ESignDateSignedOffsetSettingsRule) => {
|
|
102
|
+
setRules(prev => updateESignOffsetRule(prev, index, rule));
|
|
103
|
+
},
|
|
104
|
+
[],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const canSave = useMemo(
|
|
108
|
+
() =>
|
|
109
|
+
rules.every(rule =>
|
|
110
|
+
isValidConditionGroups(rule.condition.groups, {
|
|
111
|
+
fillableOptions,
|
|
112
|
+
formFieldsByFormId,
|
|
113
|
+
mergeTagOptions,
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
[fillableOptions, formFieldsByFormId, mergeTagOptions, rules],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Dialog open onClose={handleClose} size="xlarge">
|
|
121
|
+
<Dialog.Header>Date Signed Offset Rules</Dialog.Header>
|
|
122
|
+
<Dialog.Content>
|
|
123
|
+
<Flex
|
|
124
|
+
direction="column"
|
|
125
|
+
gap="4"
|
|
126
|
+
flex={1}
|
|
127
|
+
style={{
|
|
128
|
+
maxHeight: MODAL_CONTENT_MAX_HEIGHT,
|
|
129
|
+
overflowY: 'auto',
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{rules.length === 0 && (
|
|
133
|
+
<Flex justifyContent="center" alignItems="center">
|
|
134
|
+
<Text variant="body" size="xlarge" subdued>
|
|
135
|
+
Rule not configured yet.
|
|
136
|
+
</Text>
|
|
137
|
+
</Flex>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{rules.map((rule, index) => (
|
|
141
|
+
<ESignOffsetRuleRow
|
|
142
|
+
key={rule.condition.groups[0]?.id ?? index}
|
|
143
|
+
onDelete={() => handleRemoveRule(index)}
|
|
144
|
+
fillableOptions={fillableOptions}
|
|
145
|
+
formFieldsByFormId={formFieldsByFormId}
|
|
146
|
+
formFieldsLoadingFormId={formFieldsLoadingFormId}
|
|
147
|
+
forms={forms}
|
|
148
|
+
mergeTagOptions={mergeTagOptions}
|
|
149
|
+
onChange={next => handleUpdateRule(index, next)}
|
|
150
|
+
onRequestFormFields={onRequestFormFields}
|
|
151
|
+
rule={rule}
|
|
152
|
+
canRemove={rules.length > 1}
|
|
153
|
+
/>
|
|
154
|
+
))}
|
|
155
|
+
<Button
|
|
156
|
+
size="small"
|
|
157
|
+
appearance="ghost"
|
|
158
|
+
icon={{ before: PlusCircleIcon }}
|
|
159
|
+
onClick={handleAddRule}
|
|
160
|
+
disabled={!canAddESignOffsetRule(rules)}
|
|
161
|
+
>
|
|
162
|
+
Add Else Condition
|
|
163
|
+
</Button>
|
|
164
|
+
|
|
165
|
+
<Flex className="dte-rule-card">
|
|
166
|
+
<Flex
|
|
167
|
+
alignItems="center"
|
|
168
|
+
style={{
|
|
169
|
+
padding: '8px 12px',
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<Chip label="THEN" size="medium" />
|
|
173
|
+
</Flex>
|
|
174
|
+
<Flex direction="column" gap="1">
|
|
175
|
+
<NumberField
|
|
176
|
+
size="small"
|
|
177
|
+
label="Number of days"
|
|
178
|
+
value={finalRuleDays}
|
|
179
|
+
onChange={handleFinalRuleDaysChange}
|
|
180
|
+
suffix="days"
|
|
181
|
+
/>
|
|
182
|
+
<Text variant="body" size="small" subdued>
|
|
183
|
+
days range up to 365
|
|
184
|
+
</Text>
|
|
185
|
+
</Flex>
|
|
186
|
+
</Flex>
|
|
187
|
+
</Flex>
|
|
188
|
+
</Dialog.Content>
|
|
189
|
+
<Dialog.Footer sticky>
|
|
190
|
+
<Dialog.CancelButton onClick={handleClose}>Cancel</Dialog.CancelButton>
|
|
191
|
+
<Button appearance="primary" disabled={!canSave} onClick={handleSave}>
|
|
192
|
+
Save Changes
|
|
193
|
+
</Button>
|
|
194
|
+
</Dialog.Footer>
|
|
195
|
+
</Dialog>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Chip, Flex, NumberField, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import { defaultCondition, defaultGroup } from '../../constants';
|
|
4
|
+
import type {
|
|
5
|
+
DataPointOption,
|
|
6
|
+
ESignDateSignedOffsetSettingsRule,
|
|
7
|
+
FormFieldsByFormIdI,
|
|
8
|
+
FormInfo,
|
|
9
|
+
} from '../../interface/types';
|
|
10
|
+
import { ConditionRow } from '../display-conditions/condition-row';
|
|
11
|
+
|
|
12
|
+
export interface ESignOffsetRuleRowProps {
|
|
13
|
+
canRemove: boolean;
|
|
14
|
+
rule: ESignDateSignedOffsetSettingsRule;
|
|
15
|
+
onChange: (rule: ESignDateSignedOffsetSettingsRule) => void;
|
|
16
|
+
onDelete: () => void;
|
|
17
|
+
fillableOptions: DataPointOption[];
|
|
18
|
+
mergeTagOptions: DataPointOption[];
|
|
19
|
+
forms?: FormInfo[];
|
|
20
|
+
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
21
|
+
onRequestFormFields?: (formId: number) => void;
|
|
22
|
+
formFieldsLoadingFormId?: number | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const ESignOffsetRuleRow: FC<ESignOffsetRuleRowProps> = ({
|
|
26
|
+
canRemove,
|
|
27
|
+
fillableOptions,
|
|
28
|
+
formFieldsByFormId,
|
|
29
|
+
formFieldsLoadingFormId,
|
|
30
|
+
forms,
|
|
31
|
+
mergeTagOptions,
|
|
32
|
+
onChange,
|
|
33
|
+
onDelete,
|
|
34
|
+
onRequestFormFields,
|
|
35
|
+
rule,
|
|
36
|
+
}) => {
|
|
37
|
+
const handleDaysChange = (days: number | null) => {
|
|
38
|
+
onChange({ ...rule, days: days ?? 0 });
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const group = rule.condition.groups[0];
|
|
42
|
+
const condition = group?.conditions[0] ?? defaultCondition();
|
|
43
|
+
|
|
44
|
+
const handleConditionChange = (nextCondition: typeof condition) => {
|
|
45
|
+
onChange({
|
|
46
|
+
...rule,
|
|
47
|
+
condition: {
|
|
48
|
+
...rule.condition,
|
|
49
|
+
groups: [{ ...(group ?? defaultGroup()), conditions: [nextCondition] }],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Flex direction="column" gap="3" className="dte-rule-card">
|
|
56
|
+
<ConditionRow
|
|
57
|
+
canRemove={canRemove}
|
|
58
|
+
condition={condition}
|
|
59
|
+
fillableOptions={fillableOptions}
|
|
60
|
+
formFieldsByFormId={formFieldsByFormId}
|
|
61
|
+
formFieldsLoadingFormId={formFieldsLoadingFormId}
|
|
62
|
+
forms={forms}
|
|
63
|
+
mergeTagOptions={mergeTagOptions}
|
|
64
|
+
onChange={handleConditionChange}
|
|
65
|
+
onRemove={onDelete}
|
|
66
|
+
onRequestFormFields={onRequestFormFields}
|
|
67
|
+
/>
|
|
68
|
+
<Flex>
|
|
69
|
+
<Flex
|
|
70
|
+
alignItems="center"
|
|
71
|
+
style={{
|
|
72
|
+
padding: '8px 12px',
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<Chip label="THEN" size="medium" />
|
|
76
|
+
</Flex>
|
|
77
|
+
<Flex direction="column" gap="1">
|
|
78
|
+
<NumberField
|
|
79
|
+
label="Number of days"
|
|
80
|
+
value={rule.days}
|
|
81
|
+
onChange={handleDaysChange}
|
|
82
|
+
suffix="days"
|
|
83
|
+
/>
|
|
84
|
+
<Text variant="body" size="small" subdued>
|
|
85
|
+
days range up to 365
|
|
86
|
+
</Text>
|
|
87
|
+
</Flex>
|
|
88
|
+
</Flex>
|
|
89
|
+
</Flex>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Button, Flex, NumberField } from '@servicetitan/anvil2';
|
|
2
|
+
import AddIcon from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
3
|
+
import { FC, Fragment, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
ESignDateSignedOffsetSettings,
|
|
6
|
+
ESignDateSignedOffsetSettingsFinalRule,
|
|
7
|
+
ESignDateSignedOffsetSettingsRule,
|
|
8
|
+
FormFieldsByFormIdI,
|
|
9
|
+
FormInfo,
|
|
10
|
+
PdfField,
|
|
11
|
+
SchemaObject,
|
|
12
|
+
} from '../../interface/types';
|
|
13
|
+
import { ESignDateRulesSummary } from './e-sign-date-rules-summary';
|
|
14
|
+
import { ESignDateSignedOffsetModal } from './e-sign-date-signed-offset-modal';
|
|
15
|
+
|
|
16
|
+
interface ESignOptionsModalProps {
|
|
17
|
+
initialState: ESignDateSignedOffsetSettings;
|
|
18
|
+
onSave: (state: ESignDateSignedOffsetSettings) => void;
|
|
19
|
+
schema?: SchemaObject;
|
|
20
|
+
documentFields?: PdfField[];
|
|
21
|
+
forms?: FormInfo[];
|
|
22
|
+
formFieldsByFormId?: FormFieldsByFormIdI;
|
|
23
|
+
onRequestFormFields?: (formId: number) => void;
|
|
24
|
+
formFieldsLoadingFormId?: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ESignOptionsModal: FC<ESignOptionsModalProps> = ({
|
|
28
|
+
documentFields,
|
|
29
|
+
formFieldsByFormId,
|
|
30
|
+
formFieldsLoadingFormId,
|
|
31
|
+
forms,
|
|
32
|
+
initialState,
|
|
33
|
+
onRequestFormFields,
|
|
34
|
+
onSave,
|
|
35
|
+
schema,
|
|
36
|
+
}) => {
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
/*
|
|
39
|
+
* `rules` is a tuple: zero or more conditioned rules followed by exactly one condition-less
|
|
40
|
+
* final rule (enforced by `ESignDateSignedOffsetSettings['rules']`). Slicing/indexing a
|
|
41
|
+
* variadic tuple widens to the member union, so the split needs one explicit, deliberate cast.
|
|
42
|
+
*/
|
|
43
|
+
const ruleWithOutCondition = initialState.rules[
|
|
44
|
+
initialState.rules.length - 1
|
|
45
|
+
] as ESignDateSignedOffsetSettingsFinalRule;
|
|
46
|
+
const rulesWithConditions = initialState.rules.slice(
|
|
47
|
+
0,
|
|
48
|
+
-1,
|
|
49
|
+
) as ESignDateSignedOffsetSettingsRule[];
|
|
50
|
+
|
|
51
|
+
const handleDaysChange = (days: number | null) => {
|
|
52
|
+
onSave({
|
|
53
|
+
rules: [...rulesWithConditions, { days: Math.max(Math.min(365, days ?? 0), 0) }],
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleSaveRules = (
|
|
58
|
+
rules: ESignDateSignedOffsetSettingsRule[],
|
|
59
|
+
finalRule: ESignDateSignedOffsetSettingsFinalRule,
|
|
60
|
+
) => {
|
|
61
|
+
onSave({ rules: [...rules, finalRule] });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Fragment>
|
|
66
|
+
{rulesWithConditions.length === 0 ? (
|
|
67
|
+
<Flex gap={1} direction="column" justifyContent="space-between">
|
|
68
|
+
<NumberField
|
|
69
|
+
size="small"
|
|
70
|
+
label="Number of days"
|
|
71
|
+
value={ruleWithOutCondition.days}
|
|
72
|
+
onChange={handleDaysChange}
|
|
73
|
+
suffix="days"
|
|
74
|
+
width="100%"
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<Button
|
|
78
|
+
appearance="ghost"
|
|
79
|
+
size="small"
|
|
80
|
+
icon={AddIcon}
|
|
81
|
+
onClick={() => setOpen(true)}
|
|
82
|
+
style={{ color: '#0265DC' }}
|
|
83
|
+
>
|
|
84
|
+
Set Date Rule
|
|
85
|
+
</Button>
|
|
86
|
+
</Flex>
|
|
87
|
+
) : (
|
|
88
|
+
<ESignDateRulesSummary
|
|
89
|
+
documentFields={documentFields}
|
|
90
|
+
onEdit={() => setOpen(true)}
|
|
91
|
+
rulesWithConditions={rulesWithConditions}
|
|
92
|
+
schema={schema}
|
|
93
|
+
elseDays={ruleWithOutCondition.days}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
{open && (
|
|
97
|
+
<ESignDateSignedOffsetModal
|
|
98
|
+
onClose={() => setOpen(false)}
|
|
99
|
+
onSave={handleSaveRules}
|
|
100
|
+
documentFields={documentFields}
|
|
101
|
+
finalRule={ruleWithOutCondition}
|
|
102
|
+
formFieldsByFormId={formFieldsByFormId}
|
|
103
|
+
formFieldsLoadingFormId={formFieldsLoadingFormId}
|
|
104
|
+
forms={forms}
|
|
105
|
+
initialState={rulesWithConditions}
|
|
106
|
+
onRequestFormFields={onRequestFormFields}
|
|
107
|
+
schema={schema}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</Fragment>
|
|
111
|
+
);
|
|
112
|
+
};
|