@servicetitan/dte-unlayer 0.154.0 → 0.155.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/dist/display-conditions/ConditionRow.d.ts.map +1 -1
- package/dist/display-conditions/ConditionRow.js +111 -95
- package/dist/display-conditions/ConditionRow.js.map +1 -1
- package/dist/display-conditions/DisplayConditionModal.d.ts +3 -3
- package/dist/display-conditions/DisplayConditionModal.d.ts.map +1 -1
- package/dist/display-conditions/DisplayConditionModal.js +8 -95
- package/dist/display-conditions/DisplayConditionModal.js.map +1 -1
- package/dist/display-conditions/useConditionForms.d.ts +22 -0
- package/dist/display-conditions/useConditionForms.d.ts.map +1 -0
- package/dist/display-conditions/useConditionForms.js +110 -0
- package/dist/display-conditions/useConditionForms.js.map +1 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts +11 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.d.ts.map +1 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.js +192 -0
- package/dist/e-sign-offset/ESignDateSignedOffsetModal.js.map +1 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts +20 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.d.ts.map +1 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.js +87 -0
- package/dist/e-sign-offset/ESignOffsetRuleRow.js.map +1 -0
- package/dist/e-sign-offset/eSignOffsetController.d.ts +10 -0
- package/dist/e-sign-offset/eSignOffsetController.d.ts.map +1 -0
- package/dist/e-sign-offset/eSignOffsetController.js +27 -0
- package/dist/e-sign-offset/eSignOffsetController.js.map +1 -0
- package/dist/editor-core-source.d.ts +2 -2
- package/dist/editor-core-source.d.ts.map +1 -1
- package/dist/editor-core-source.js +2 -2
- package/dist/editor-core-source.js.map +1 -1
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +8 -0
- package/dist/editor.js.map +1 -1
- package/dist/shared/const.d.ts +30 -0
- package/dist/shared/const.d.ts.map +1 -1
- package/dist/shared/const.js +5 -0
- package/dist/shared/const.js.map +1 -1
- package/dist/shared/e-sign-offset.d.ts +21 -0
- package/dist/shared/e-sign-offset.d.ts.map +1 -0
- package/dist/shared/e-sign-offset.js +130 -0
- package/dist/shared/e-sign-offset.js.map +1 -0
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +30 -3
- package/dist/store.js.map +1 -1
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +21 -1
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/e-sign-offset.test.ts +281 -0
- package/src/display-conditions/ConditionRow.tsx +86 -81
- package/src/display-conditions/DisplayConditionModal.tsx +12 -116
- package/src/display-conditions/useConditionForms.ts +146 -0
- package/src/e-sign-offset/ESignDateSignedOffsetModal.tsx +197 -0
- package/src/e-sign-offset/ESignOffsetRuleRow.tsx +101 -0
- package/src/e-sign-offset/eSignOffsetController.ts +39 -0
- package/src/editor-core-source.ts +2 -2
- package/src/editor.tsx +9 -0
- package/src/shared/const.ts +37 -0
- package/src/shared/e-sign-offset.ts +173 -0
- package/src/store.ts +45 -4
- package/src/tools.ts +36 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
buildFormFieldKey,
|
|
4
|
+
FormFieldInfo,
|
|
5
|
+
FormInfo,
|
|
6
|
+
getConditionalFieldTypeFromFormItemType,
|
|
7
|
+
parseFormFieldKey,
|
|
8
|
+
} from '../shared/forms';
|
|
9
|
+
import { ConditionGroup, FormFieldOption } from './types';
|
|
10
|
+
|
|
11
|
+
type SendFormFields = (formId: number, fields: FormFieldInfo[]) => void;
|
|
12
|
+
|
|
13
|
+
export type ConditionFormSelectHandler = (formId: number, sendFormFields: SendFormFields) => void;
|
|
14
|
+
|
|
15
|
+
export type ConditionalsOpenHandler = (
|
|
16
|
+
usedFormIds: number[],
|
|
17
|
+
sendFormList: (forms: FormInfo[]) => void,
|
|
18
|
+
sendFormFields: SendFormFields,
|
|
19
|
+
) => void;
|
|
20
|
+
|
|
21
|
+
export interface UseConditionFormsOptions {
|
|
22
|
+
onConditionFormSelect?: ConditionFormSelectHandler;
|
|
23
|
+
onConditionalsOpen?: ConditionalsOpenHandler;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Loads the host's forms and form fields for a condition-editing modal, so form fields can be
|
|
28
|
+
* picked as condition data points alongside schema fields.
|
|
29
|
+
*/
|
|
30
|
+
export const useConditionForms = ({
|
|
31
|
+
onConditionFormSelect,
|
|
32
|
+
onConditionalsOpen,
|
|
33
|
+
}: UseConditionFormsOptions) => {
|
|
34
|
+
const [forms, setForms] = useState<FormInfo[]>([]);
|
|
35
|
+
const [formFieldsByFormId, setFormFieldsByFormId] = useState<Record<number, FormFieldInfo[]>>(
|
|
36
|
+
{},
|
|
37
|
+
);
|
|
38
|
+
const [loadingFormIds, setLoadingFormIds] = useState<number[]>([]);
|
|
39
|
+
const [isFormsListLoading, setIsFormsListLoading] = useState(false);
|
|
40
|
+
const formFieldsByFormIdRef = useRef<Record<number, FormFieldInfo[]>>({});
|
|
41
|
+
const loadingFormIdsRef = useRef<number[]>([]);
|
|
42
|
+
|
|
43
|
+
const formFieldOptions = useMemo<FormFieldOption[]>(() => {
|
|
44
|
+
const nextOptions: FormFieldOption[] = [];
|
|
45
|
+
for (const form of forms) {
|
|
46
|
+
const fields = formFieldsByFormId[form.id];
|
|
47
|
+
if (!fields) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const field of fields) {
|
|
51
|
+
const fieldType = getConditionalFieldTypeFromFormItemType(field.itemType);
|
|
52
|
+
if (!fieldType) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
nextOptions.push({
|
|
56
|
+
fieldType,
|
|
57
|
+
formId: form.id,
|
|
58
|
+
fullKey: buildFormFieldKey(form.id, field.id),
|
|
59
|
+
title: field.header,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return nextOptions.sort((left, right) => left.title.localeCompare(right.title));
|
|
64
|
+
}, [forms, formFieldsByFormId]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
formFieldsByFormIdRef.current = formFieldsByFormId;
|
|
68
|
+
}, [formFieldsByFormId]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
loadingFormIdsRef.current = loadingFormIds;
|
|
72
|
+
}, [loadingFormIds]);
|
|
73
|
+
|
|
74
|
+
const requestFormFields = useCallback(
|
|
75
|
+
(formId: number) => {
|
|
76
|
+
if (!onConditionFormSelect) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
formFieldsByFormIdRef.current[formId] ||
|
|
81
|
+
loadingFormIdsRef.current.includes(formId)
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setLoadingFormIds(prev => (prev.includes(formId) ? prev : [...prev, formId]));
|
|
87
|
+
onConditionFormSelect(formId, (selectedFormId, fields) => {
|
|
88
|
+
setFormFieldsByFormId(prev => ({ ...prev, [selectedFormId]: fields }));
|
|
89
|
+
setLoadingFormIds(prev => prev.filter(id => id !== selectedFormId));
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
[onConditionFormSelect],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/** Resets form data and requests the forms list plus fields of forms the groups already use. */
|
|
96
|
+
const loadForms = useCallback(
|
|
97
|
+
(groups: ConditionGroup[]) => {
|
|
98
|
+
const usedFormIds = Array.from(
|
|
99
|
+
new Set(
|
|
100
|
+
groups
|
|
101
|
+
.flatMap(group => group.conditions)
|
|
102
|
+
.map(condition => parseFormFieldKey(condition.dataPointKey)?.formId)
|
|
103
|
+
.filter((formId): formId is number => formId != null),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* Reset synchronously so `requestFormFields` below does not skip forms loaded by a
|
|
109
|
+
* previous open whose state is being cleared.
|
|
110
|
+
*/
|
|
111
|
+
formFieldsByFormIdRef.current = {};
|
|
112
|
+
loadingFormIdsRef.current = [];
|
|
113
|
+
setForms([]);
|
|
114
|
+
setFormFieldsByFormId({});
|
|
115
|
+
setIsFormsListLoading(!!onConditionalsOpen);
|
|
116
|
+
setLoadingFormIds([]);
|
|
117
|
+
|
|
118
|
+
if (!onConditionalsOpen) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onConditionalsOpen(
|
|
123
|
+
usedFormIds,
|
|
124
|
+
nextForms => {
|
|
125
|
+
setForms(nextForms);
|
|
126
|
+
setIsFormsListLoading(false);
|
|
127
|
+
},
|
|
128
|
+
(formId, fields) => {
|
|
129
|
+
setFormFieldsByFormId(prev => ({ ...prev, [formId]: fields }));
|
|
130
|
+
setLoadingFormIds(prev => prev.filter(id => id !== formId));
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
usedFormIds.forEach(formId => requestFormFields(formId));
|
|
135
|
+
},
|
|
136
|
+
[onConditionalsOpen, requestFormFields],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
formFieldOptions,
|
|
141
|
+
forms,
|
|
142
|
+
isFieldDataLoading: isFormsListLoading || loadingFormIds.length > 0,
|
|
143
|
+
loadForms,
|
|
144
|
+
requestFormFields,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Button, Card, Dialog, Flex, Text, ThemeProvider } from '@servicetitan/anvil2';
|
|
2
|
+
import PlusCircleIcon from '@servicetitan/anvil2/assets/icons/material/round/add_circle.svg';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { MODAL_CONTENT_MAX_HEIGHT } from '../display-conditions/constants';
|
|
6
|
+
import { getSchemaDataPointOptions } from '../display-conditions/schemaDataPoints';
|
|
7
|
+
import {
|
|
8
|
+
ConditionalsOpenHandler,
|
|
9
|
+
ConditionFormSelectHandler,
|
|
10
|
+
useConditionForms,
|
|
11
|
+
} from '../display-conditions/useConditionForms';
|
|
12
|
+
import type { ESignDateSignedOffsetRule } from '../shared/const';
|
|
13
|
+
import {
|
|
14
|
+
addESignOffsetRule,
|
|
15
|
+
buildESignOffsetSettings,
|
|
16
|
+
canAddESignOffsetRule,
|
|
17
|
+
createESignOffsetRule,
|
|
18
|
+
getESignOffsetRuleCondition,
|
|
19
|
+
isCompleteESignOffsetCondition,
|
|
20
|
+
normalizeESignOffsetSettings,
|
|
21
|
+
removeESignOffsetRule,
|
|
22
|
+
splitESignOffsetSettings,
|
|
23
|
+
updateESignOffsetRule,
|
|
24
|
+
} from '../shared/e-sign-offset';
|
|
25
|
+
import type { SchemaObject } from '../shared/schema';
|
|
26
|
+
import { ESignOffsetRequest, onESignOffsetRequest } from './eSignOffsetController';
|
|
27
|
+
import { ESignOffsetDaysField, ESignOffsetRuleRow } from './ESignOffsetRuleRow';
|
|
28
|
+
|
|
29
|
+
export interface ESignDateSignedOffsetModalProps {
|
|
30
|
+
/** Same contract as `DisplayConditionModalProps.portalContainer`. */
|
|
31
|
+
portalContainer?: Element | DocumentFragment | null;
|
|
32
|
+
onConditionFormSelect?: ConditionFormSelectHandler;
|
|
33
|
+
onConditionalsOpen?: ConditionalsOpenHandler;
|
|
34
|
+
schema?: SchemaObject;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ESignDateSignedOffsetModal({
|
|
38
|
+
onConditionFormSelect,
|
|
39
|
+
onConditionalsOpen,
|
|
40
|
+
portalContainer,
|
|
41
|
+
schema,
|
|
42
|
+
}: Readonly<ESignDateSignedOffsetModalProps>) {
|
|
43
|
+
const [request, setRequest] = useState<ESignOffsetRequest | null>(null);
|
|
44
|
+
const [rules, setRules] = useState<ESignDateSignedOffsetRule[]>([]);
|
|
45
|
+
const [finalDays, setFinalDays] = useState(0);
|
|
46
|
+
const { formFieldOptions, forms, isFieldDataLoading, loadForms, requestFormFields } =
|
|
47
|
+
useConditionForms({ onConditionFormSelect, onConditionalsOpen });
|
|
48
|
+
|
|
49
|
+
const dataPointOptions = useMemo(() => getSchemaDataPointOptions(schema), [schema]);
|
|
50
|
+
const allFieldOptions = useMemo(
|
|
51
|
+
() => [...dataPointOptions, ...formFieldOptions],
|
|
52
|
+
[dataPointOptions, formFieldOptions],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
return onESignOffsetRequest(nextRequest => {
|
|
57
|
+
const { conditionedRules, finalDays: nextFinalDays } = splitESignOffsetSettings(
|
|
58
|
+
normalizeESignOffsetSettings(nextRequest.value),
|
|
59
|
+
);
|
|
60
|
+
const initialRules = conditionedRules.length
|
|
61
|
+
? conditionedRules
|
|
62
|
+
: [createESignOffsetRule()];
|
|
63
|
+
|
|
64
|
+
loadForms(initialRules.flatMap(rule => rule.condition.groups));
|
|
65
|
+
setRules(initialRules);
|
|
66
|
+
setFinalDays(nextFinalDays);
|
|
67
|
+
setRequest(nextRequest);
|
|
68
|
+
});
|
|
69
|
+
}, [loadForms]);
|
|
70
|
+
|
|
71
|
+
const canSave = useMemo(() => {
|
|
72
|
+
if (isFieldDataLoading) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return rules.every(rule => {
|
|
77
|
+
const condition = getESignOffsetRuleCondition(rule);
|
|
78
|
+
const fieldType =
|
|
79
|
+
allFieldOptions.find(option => option.fullKey === condition?.dataPointKey)
|
|
80
|
+
?.fieldType ?? 'string';
|
|
81
|
+
|
|
82
|
+
return isCompleteESignOffsetCondition(condition, fieldType);
|
|
83
|
+
});
|
|
84
|
+
}, [allFieldOptions, isFieldDataLoading, rules]);
|
|
85
|
+
|
|
86
|
+
const handleClose = useCallback(() => {
|
|
87
|
+
setRequest(null);
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const handleSave = useCallback(() => {
|
|
91
|
+
request?.done(buildESignOffsetSettings(rules, finalDays));
|
|
92
|
+
setRequest(null);
|
|
93
|
+
}, [finalDays, request, rules]);
|
|
94
|
+
|
|
95
|
+
const handleClearRules = useCallback(() => {
|
|
96
|
+
setRules([]);
|
|
97
|
+
setFinalDays(0);
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const handleAddRule = useCallback(() => {
|
|
101
|
+
setRules(prev => addESignOffsetRule(prev));
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
const handleUpdateRule = useCallback((index: number, rule: ESignDateSignedOffsetRule) => {
|
|
105
|
+
setRules(prev => updateESignOffsetRule(prev, index, rule));
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
const handleRemoveRule = useCallback((index: number) => {
|
|
109
|
+
setRules(prev => removeESignOffsetRule(prev, index));
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
if (!request) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const dialog = (
|
|
117
|
+
<Dialog open onClose={handleClose} size="xlarge">
|
|
118
|
+
<Dialog.Header>Date Signed Offset Rules</Dialog.Header>
|
|
119
|
+
<Dialog.Content>
|
|
120
|
+
<Flex
|
|
121
|
+
direction="column"
|
|
122
|
+
gap="4"
|
|
123
|
+
style={{
|
|
124
|
+
maxHeight: MODAL_CONTENT_MAX_HEIGHT,
|
|
125
|
+
overflowY: 'auto',
|
|
126
|
+
paddingRight: 8,
|
|
127
|
+
width: '100%',
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
{rules.length === 0 && (
|
|
131
|
+
<Flex justifyContent="center" alignItems="center">
|
|
132
|
+
<Text variant="body" size="xlarge" subdued>
|
|
133
|
+
Rule not configured yet.
|
|
134
|
+
</Text>
|
|
135
|
+
</Flex>
|
|
136
|
+
)}
|
|
137
|
+
{rules.map((rule, index) => (
|
|
138
|
+
<ESignOffsetRuleRow
|
|
139
|
+
key={rule.condition.groups[0]?.id ?? index}
|
|
140
|
+
dataPointOptions={dataPointOptions}
|
|
141
|
+
formFieldOptions={formFieldOptions}
|
|
142
|
+
forms={forms}
|
|
143
|
+
onChange={nextRule => handleUpdateRule(index, nextRule)}
|
|
144
|
+
onFormSelect={requestFormFields}
|
|
145
|
+
onRemove={() => handleRemoveRule(index)}
|
|
146
|
+
rule={rule}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
<Flex>
|
|
150
|
+
<Button
|
|
151
|
+
size="small"
|
|
152
|
+
appearance="ghost"
|
|
153
|
+
icon={{ before: PlusCircleIcon }}
|
|
154
|
+
onClick={handleAddRule}
|
|
155
|
+
disabled={!canAddESignOffsetRule(rules)}
|
|
156
|
+
>
|
|
157
|
+
{rules.length === 0 ? 'Add Condition' : 'Add Else Condition'}
|
|
158
|
+
</Button>
|
|
159
|
+
</Flex>
|
|
160
|
+
<Card background="strong" padding="small">
|
|
161
|
+
<ESignOffsetDaysField
|
|
162
|
+
chipLabel={rules.length === 0 ? 'THEN' : 'ELSE'}
|
|
163
|
+
days={finalDays}
|
|
164
|
+
onChange={setFinalDays}
|
|
165
|
+
/>
|
|
166
|
+
</Card>
|
|
167
|
+
</Flex>
|
|
168
|
+
</Dialog.Content>
|
|
169
|
+
<Dialog.Footer sticky>
|
|
170
|
+
<Flex
|
|
171
|
+
justifyContent="flex-end"
|
|
172
|
+
alignItems="center"
|
|
173
|
+
gap="3"
|
|
174
|
+
style={{ width: '100%' }}
|
|
175
|
+
>
|
|
176
|
+
{rules.length > 0 && (
|
|
177
|
+
<Button appearance="ghost" onClick={handleClearRules}>
|
|
178
|
+
Clear Rules
|
|
179
|
+
</Button>
|
|
180
|
+
)}
|
|
181
|
+
<Button appearance="secondary" onClick={handleClose}>
|
|
182
|
+
Cancel
|
|
183
|
+
</Button>
|
|
184
|
+
<Button appearance="primary" disabled={!canSave} onClick={handleSave}>
|
|
185
|
+
Save Changes
|
|
186
|
+
</Button>
|
|
187
|
+
</Flex>
|
|
188
|
+
</Dialog.Footer>
|
|
189
|
+
</Dialog>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (portalContainer) {
|
|
193
|
+
return createPortal(<ThemeProvider>{dialog}</ThemeProvider>, portalContainer);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return dialog;
|
|
197
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Card, Chip, Flex, NumberField } from '@servicetitan/anvil2';
|
|
2
|
+
import { ConditionRow } from '../display-conditions/ConditionRow';
|
|
3
|
+
import { defaultCondition, defaultGroup } from '../display-conditions/constants';
|
|
4
|
+
import type {
|
|
5
|
+
DataPointOption,
|
|
6
|
+
FormFieldOption,
|
|
7
|
+
SingleCondition,
|
|
8
|
+
} from '../display-conditions/types';
|
|
9
|
+
import { type ESignDateSignedOffsetRule, MAX_E_SIGN_OFFSET_DAYS } from '../shared/const';
|
|
10
|
+
import { clampESignOffsetDays } from '../shared/e-sign-offset';
|
|
11
|
+
import { FormInfo } from '../shared/forms';
|
|
12
|
+
|
|
13
|
+
export interface ESignOffsetDaysFieldProps {
|
|
14
|
+
chipLabel: string;
|
|
15
|
+
days: number;
|
|
16
|
+
onChange: (days: number) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ESignOffsetDaysField({
|
|
20
|
+
chipLabel,
|
|
21
|
+
days,
|
|
22
|
+
onChange,
|
|
23
|
+
}: Readonly<ESignOffsetDaysFieldProps>) {
|
|
24
|
+
return (
|
|
25
|
+
<Flex>
|
|
26
|
+
<Flex alignItems="center" style={{ padding: '8px 12px' }}>
|
|
27
|
+
<Chip label={chipLabel} size="medium" />
|
|
28
|
+
</Flex>
|
|
29
|
+
<NumberField
|
|
30
|
+
label="Number of days"
|
|
31
|
+
size="small"
|
|
32
|
+
value={days}
|
|
33
|
+
minValue={0}
|
|
34
|
+
maxValue={MAX_E_SIGN_OFFSET_DAYS}
|
|
35
|
+
maximumFractionDigits={0}
|
|
36
|
+
suffix="days"
|
|
37
|
+
hint={`days range up to ${MAX_E_SIGN_OFFSET_DAYS}`}
|
|
38
|
+
onChange={value => onChange(clampESignOffsetDays(value))}
|
|
39
|
+
/>
|
|
40
|
+
</Flex>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ESignOffsetRuleRowProps {
|
|
45
|
+
dataPointOptions: DataPointOption[];
|
|
46
|
+
formFieldOptions: FormFieldOption[];
|
|
47
|
+
forms: FormInfo[];
|
|
48
|
+
onChange: (rule: ESignDateSignedOffsetRule) => void;
|
|
49
|
+
onFormSelect: (formId: number) => void;
|
|
50
|
+
onRemove: () => void;
|
|
51
|
+
rule: ESignDateSignedOffsetRule;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ESignOffsetRuleRow({
|
|
55
|
+
dataPointOptions,
|
|
56
|
+
formFieldOptions,
|
|
57
|
+
forms,
|
|
58
|
+
onChange,
|
|
59
|
+
onFormSelect,
|
|
60
|
+
onRemove,
|
|
61
|
+
rule,
|
|
62
|
+
}: Readonly<ESignOffsetRuleRowProps>) {
|
|
63
|
+
const group = rule.condition.groups[0] ?? defaultGroup();
|
|
64
|
+
const condition = group.conditions[0] ?? defaultCondition();
|
|
65
|
+
|
|
66
|
+
const handleConditionChange = (nextCondition: SingleCondition) => {
|
|
67
|
+
onChange({
|
|
68
|
+
...rule,
|
|
69
|
+
condition: {
|
|
70
|
+
behavior: 'show',
|
|
71
|
+
groups: [{ conditions: [nextCondition], id: group.id }],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleDaysChange = (days: number) => {
|
|
77
|
+
onChange({ ...rule, days });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Card background="strong" padding="small">
|
|
82
|
+
<Flex direction="column" gap="3">
|
|
83
|
+
<ConditionRow
|
|
84
|
+
canRemove
|
|
85
|
+
condition={condition}
|
|
86
|
+
dataPointOptions={dataPointOptions}
|
|
87
|
+
formFieldOptions={formFieldOptions}
|
|
88
|
+
forms={forms}
|
|
89
|
+
onChange={handleConditionChange}
|
|
90
|
+
onFormSelect={onFormSelect}
|
|
91
|
+
onRemove={onRemove}
|
|
92
|
+
/>
|
|
93
|
+
<ESignOffsetDaysField
|
|
94
|
+
chipLabel="THEN"
|
|
95
|
+
days={rule.days}
|
|
96
|
+
onChange={handleDaysChange}
|
|
97
|
+
/>
|
|
98
|
+
</Flex>
|
|
99
|
+
</Card>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ESignDateSignedOffsetSettings } from '../shared/const';
|
|
2
|
+
|
|
3
|
+
export interface ESignOffsetRequest {
|
|
4
|
+
value: ESignDateSignedOffsetSettings;
|
|
5
|
+
done: (value: ESignDateSignedOffsetSettings) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Listener = (request: ESignOffsetRequest) => void;
|
|
9
|
+
|
|
10
|
+
const E_SIGN_OFFSET_EVENT = 'dte:e-sign-offset';
|
|
11
|
+
const eSignOffsetEvents = new EventTarget();
|
|
12
|
+
let activeListeners = 0;
|
|
13
|
+
|
|
14
|
+
export const onESignOffsetRequest = (listener: Listener) => {
|
|
15
|
+
const handler = (event: Event) => {
|
|
16
|
+
if (event instanceof CustomEvent && event.detail) {
|
|
17
|
+
listener(event.detail);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
activeListeners += 1;
|
|
22
|
+
eSignOffsetEvents.addEventListener(E_SIGN_OFFSET_EVENT, handler);
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
activeListeners = Math.max(0, activeListeners - 1);
|
|
26
|
+
eSignOffsetEvents.removeEventListener(E_SIGN_OFFSET_EVENT, handler);
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const emitESignOffsetRequest = (request: ESignOffsetRequest) => {
|
|
31
|
+
if (activeListeners === 0) {
|
|
32
|
+
request.done(request.value);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
eSignOffsetEvents.dispatchEvent(
|
|
37
|
+
new CustomEvent<ESignOffsetRequest>(E_SIGN_OFFSET_EVENT, { detail: request }),
|
|
38
|
+
);
|
|
39
|
+
};
|