@servicetitan/dte-unlayer 0.123.0 → 0.124.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DisplayConditionModal.d.ts","sourceRoot":"","sources":["../../src/display-conditions/DisplayConditionModal.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEH,aAAa,EACb,QAAQ,EAGX,MAAM,iBAAiB,CAAC;AAmDzB,MAAM,WAAW,0BAA0B;IACvC,qBAAqB,CAAC,EAAE,CACpB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,KAChE,IAAI,CAAC;IACV,kBAAkB,CAAC,EAAE,CACjB,WAAW,EAAE,MAAM,EAAE,EACrB,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,EACzC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,KAChE,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,kBAAkB,EAAE,YAAY,CAAC;CACpD;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,0BAA0B,
|
|
1
|
+
{"version":3,"file":"DisplayConditionModal.d.ts","sourceRoot":"","sources":["../../src/display-conditions/DisplayConditionModal.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEH,aAAa,EACb,QAAQ,EAGX,MAAM,iBAAiB,CAAC;AAmDzB,MAAM,WAAW,0BAA0B;IACvC,qBAAqB,CAAC,EAAE,CACpB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,KAChE,IAAI,CAAC;IACV,kBAAkB,CAAC,EAAE,CACjB,WAAW,EAAE,MAAM,EAAE,EACrB,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,EACzC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,IAAI,KAChE,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,kBAAkB,EAAE,YAAY,CAAC;CACpD;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,0BAA0B,uCA0StE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Combobox, Dialog, Flex, Text } from '@servicetitan/anvil2';
|
|
3
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { buildFormFieldKey, getConditionalFieldTypeFromFormItemType, parseFormFieldKey } from '../shared/forms';
|
|
6
6
|
import { ConditionGroupsSection } from './ConditionGroupsSection';
|
|
@@ -55,6 +55,8 @@ export const DisplayConditionModal = (props)=>{
|
|
|
55
55
|
const [formFieldsByFormId, setFormFieldsByFormId] = useState({});
|
|
56
56
|
const [loadingFormIds, setLoadingFormIds] = useState([]);
|
|
57
57
|
const [isFormsListLoading, setIsFormsListLoading] = useState(false);
|
|
58
|
+
const formFieldsByFormIdRef = useRef({});
|
|
59
|
+
const loadingFormIdsRef = useRef([]);
|
|
58
60
|
const dataPointOptions = useMemo(()=>getSchemaDataPointOptions(schema), [
|
|
59
61
|
schema
|
|
60
62
|
]);
|
|
@@ -91,6 +93,37 @@ export const DisplayConditionModal = (props)=>{
|
|
|
91
93
|
formFieldOptions
|
|
92
94
|
]);
|
|
93
95
|
const isFieldDataLoading = isFormsListLoading || loadingFormIds.length > 0;
|
|
96
|
+
useEffect(()=>{
|
|
97
|
+
formFieldsByFormIdRef.current = formFieldsByFormId;
|
|
98
|
+
}, [
|
|
99
|
+
formFieldsByFormId
|
|
100
|
+
]);
|
|
101
|
+
useEffect(()=>{
|
|
102
|
+
loadingFormIdsRef.current = loadingFormIds;
|
|
103
|
+
}, [
|
|
104
|
+
loadingFormIds
|
|
105
|
+
]);
|
|
106
|
+
const requestFormFields = useCallback((formId)=>{
|
|
107
|
+
if (!onConditionFormSelect) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (formFieldsByFormIdRef.current[formId] || loadingFormIdsRef.current.includes(formId)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setLoadingFormIds((prev)=>prev.includes(formId) ? prev : [
|
|
114
|
+
...prev,
|
|
115
|
+
formId
|
|
116
|
+
]);
|
|
117
|
+
onConditionFormSelect(formId, (selectedFormId, fields)=>{
|
|
118
|
+
setFormFieldsByFormId((prev)=>({
|
|
119
|
+
...prev,
|
|
120
|
+
[selectedFormId]: fields
|
|
121
|
+
}));
|
|
122
|
+
setLoadingFormIds((prev)=>prev.filter((id)=>id !== selectedFormId));
|
|
123
|
+
});
|
|
124
|
+
}, [
|
|
125
|
+
onConditionFormSelect
|
|
126
|
+
]);
|
|
94
127
|
const portalTarget = useMemo(()=>{
|
|
95
128
|
if (typeof document === 'undefined') {
|
|
96
129
|
return null;
|
|
@@ -110,7 +143,7 @@ export const DisplayConditionModal = (props)=>{
|
|
|
110
143
|
setForms([]);
|
|
111
144
|
setFormFieldsByFormId({});
|
|
112
145
|
setIsFormsListLoading(!!onConditionalsOpen);
|
|
113
|
-
setLoadingFormIds(
|
|
146
|
+
setLoadingFormIds([]);
|
|
114
147
|
if (onConditionalsOpen) {
|
|
115
148
|
onConditionalsOpen(usedFormIds, (nextForms)=>{
|
|
116
149
|
setForms(nextForms);
|
|
@@ -122,6 +155,11 @@ export const DisplayConditionModal = (props)=>{
|
|
|
122
155
|
}));
|
|
123
156
|
setLoadingFormIds((prev)=>prev.filter((id)=>id !== formId));
|
|
124
157
|
});
|
|
158
|
+
/*
|
|
159
|
+
* Keep display-condition flow aligned with calculated-field flow:
|
|
160
|
+
* when modal opens, request fields for already-used forms so labels
|
|
161
|
+
* are available in edit mode.
|
|
162
|
+
*/ usedFormIds.forEach((formId)=>requestFormFields(formId));
|
|
125
163
|
} else {
|
|
126
164
|
setIsFormsListLoading(false);
|
|
127
165
|
setLoadingFormIds([]);
|
|
@@ -131,7 +169,8 @@ export const DisplayConditionModal = (props)=>{
|
|
|
131
169
|
setIsOpen(true);
|
|
132
170
|
});
|
|
133
171
|
}, [
|
|
134
|
-
onConditionalsOpen
|
|
172
|
+
onConditionalsOpen,
|
|
173
|
+
requestFormFields
|
|
135
174
|
]);
|
|
136
175
|
const handleClose = useCallback(()=>{
|
|
137
176
|
setRequest(null);
|
|
@@ -182,23 +221,9 @@ export const DisplayConditionModal = (props)=>{
|
|
|
182
221
|
}
|
|
183
222
|
}, []);
|
|
184
223
|
const handleFormSelect = useCallback((formId)=>{
|
|
185
|
-
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
setLoadingFormIds((prev)=>prev.includes(formId) ? prev : [
|
|
189
|
-
...prev,
|
|
190
|
-
formId
|
|
191
|
-
]);
|
|
192
|
-
onConditionFormSelect(formId, (selectedFormId, fields)=>{
|
|
193
|
-
setFormFieldsByFormId((prev)=>({
|
|
194
|
-
...prev,
|
|
195
|
-
[selectedFormId]: fields
|
|
196
|
-
}));
|
|
197
|
-
setLoadingFormIds((prev)=>prev.filter((id)=>id !== selectedFormId));
|
|
198
|
-
});
|
|
224
|
+
requestFormFields(formId);
|
|
199
225
|
}, [
|
|
200
|
-
|
|
201
|
-
onConditionFormSelect
|
|
226
|
+
requestFormFields
|
|
202
227
|
]);
|
|
203
228
|
const canSave = useMemo(()=>{
|
|
204
229
|
if (isFieldDataLoading) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/display-conditions/DisplayConditionModal.tsx"],"sourcesContent":["import { Button, Combobox, Dialog, Flex, Text } from '@servicetitan/anvil2';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n buildFormFieldKey,\n FormFieldInfo,\n FormInfo,\n getConditionalFieldTypeFromFormItemType,\n parseFormFieldKey,\n} from '../shared/forms';\nimport { ConditionGroupsSection, MatchType } from './ConditionGroupsSection';\nimport { defaultState, MODAL_CONTENT_MAX_HEIGHT } from './constants';\nimport { DisplayConditionRequest, onDisplayCondition } from './displayConditionController';\nimport { buildUnlayerDisplayCondition, parseUnlayerDisplayCondition } from './nunjucks';\nimport { getSchemaDataPointOptions } from './schemaDataPoints';\nimport {\n ConditionGroup as ConditionGroupType,\n DisplayBehavior,\n DisplayConditionState,\n FormFieldOption,\n LogicalOperator,\n} from './types';\n\nconst BEHAVIOR_OPTIONS = [\n { label: 'Show', value: 'show' },\n { label: 'Hide', value: 'hide' },\n] as const;\nconst NUMERIC_VALUE_RE = /^-?(?:\\d+\\.?\\d*|\\.\\d+)$/;\ntype BehaviorOption = (typeof BEHAVIOR_OPTIONS)[number];\n\nfunction deriveMatchType(conditionState: DisplayConditionState): MatchType {\n const firstGroup = conditionState.groups[0];\n if (!firstGroup || firstGroup.conditions.length <= 1) {\n return 'all';\n }\n const hasOr = firstGroup.conditions.some(c => c.logicalOperator === 'or');\n return hasOr ? 'any' : 'all';\n}\n\nfunction applyMatchTypeToState(\n conditionState: DisplayConditionState,\n matchType: MatchType,\n): DisplayConditionState {\n const logicalOp: LogicalOperator = matchType === 'all' ? 'and' : 'or';\n return {\n ...conditionState,\n groups: conditionState.groups.map((group, groupIndex) => {\n if (groupIndex !== 0) {\n return group;\n }\n return {\n ...group,\n conditions: group.conditions.map((c, i) =>\n i === 0 ? c : { ...c, logicalOperator: logicalOp },\n ),\n };\n }),\n };\n}\n\nexport interface DisplayConditionModalProps {\n onConditionFormSelect?: (\n formId: number,\n sendFormFields: (formId: number, fields: FormFieldInfo[]) => void,\n ) => void;\n onConditionalsOpen?: (\n usedFormIds: number[],\n sendFormList: (forms: FormInfo[]) => void,\n sendFormFields: (formId: number, fields: FormFieldInfo[]) => void,\n ) => void;\n schema?: import('../shared/schema').SchemaObject;\n}\n\nexport const DisplayConditionModal = (props: DisplayConditionModalProps) => {\n const { onConditionFormSelect, onConditionalsOpen, schema } = props;\n const [request, setRequest] = useState<DisplayConditionRequest | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [state, setState] = useState(defaultState);\n const [matchType, setMatchType] = useState<MatchType>('all');\n const [forms, setForms] = useState<FormInfo[]>([]);\n const [formFieldsByFormId, setFormFieldsByFormId] = useState<Record<number, FormFieldInfo[]>>(\n {},\n );\n const [loadingFormIds, setLoadingFormIds] = useState<number[]>([]);\n const [isFormsListLoading, setIsFormsListLoading] = useState(false);\n\n const dataPointOptions = useMemo(() => getSchemaDataPointOptions(schema), [schema]);\n const formFieldOptions = useMemo<FormFieldOption[]>(() => {\n const nextOptions: FormFieldOption[] = [];\n for (const form of forms) {\n const fields = formFieldsByFormId[form.id];\n if (!fields) {\n continue;\n }\n for (const field of fields) {\n const fieldType = getConditionalFieldTypeFromFormItemType(field.itemType);\n if (!fieldType) {\n continue;\n }\n nextOptions.push({\n fieldType,\n formId: form.id,\n fullKey: buildFormFieldKey(form.id, field.id),\n title: field.header,\n });\n }\n }\n return nextOptions.sort((left, right) => left.title.localeCompare(right.title));\n }, [forms, formFieldsByFormId]);\n const allFieldOptions = useMemo(\n () => [...dataPointOptions, ...formFieldOptions],\n [dataPointOptions, formFieldOptions],\n );\n const isFieldDataLoading = isFormsListLoading || loadingFormIds.length > 0;\n\n const portalTarget = useMemo(() => {\n if (typeof document === 'undefined') {\n return null;\n }\n return document.body;\n }, []);\n\n useEffect(() => {\n return onDisplayCondition(nextRequest => {\n setRequest(nextRequest);\n const existing = nextRequest.data;\n const parsed = parseUnlayerDisplayCondition(existing);\n const initialState = parsed ?? defaultState();\n const usedFormIds = Array.from(\n new Set(\n initialState.groups\n .flatMap(group => group.conditions)\n .map(condition => parseFormFieldKey(condition.dataPointKey)?.formId)\n .filter((formId): formId is number => formId != null),\n ),\n );\n\n setForms([]);\n setFormFieldsByFormId({});\n setIsFormsListLoading(!!onConditionalsOpen);\n setLoadingFormIds(usedFormIds);\n\n if (onConditionalsOpen) {\n onConditionalsOpen(\n usedFormIds,\n nextForms => {\n setForms(nextForms);\n setIsFormsListLoading(false);\n },\n (formId, fields) => {\n setFormFieldsByFormId(prev => ({ ...prev, [formId]: fields }));\n setLoadingFormIds(prev => prev.filter(id => id !== formId));\n },\n );\n } else {\n setIsFormsListLoading(false);\n setLoadingFormIds([]);\n }\n\n setState(initialState);\n setMatchType(deriveMatchType(initialState));\n setIsOpen(true);\n });\n }, [onConditionalsOpen]);\n\n const handleClose = useCallback(() => {\n setRequest(null);\n setIsOpen(false);\n }, []);\n\n const handleSave = useCallback(() => {\n if (!request) {\n return;\n }\n const finalState = applyMatchTypeToState(state, matchType);\n const condition = buildUnlayerDisplayCondition(finalState);\n if (condition) {\n request.done(condition);\n } else {\n request.done(null);\n }\n setRequest(null);\n setIsOpen(false);\n }, [matchType, request, state]);\n\n const updateGroup = useCallback((index: number, group: ConditionGroupType) => {\n setState(prev => {\n const next = [...prev.groups];\n next[index] = group;\n return { ...prev, groups: next };\n });\n }, []);\n\n const selectedBehavior = useMemo(\n () => BEHAVIOR_OPTIONS.find(opt => opt.value === state.behavior) ?? BEHAVIOR_OPTIONS[0],\n [state.behavior],\n );\n\n const handleBehaviorChange = useCallback((item: BehaviorOption | null) => {\n if (item) {\n setState(prev => ({ ...prev, behavior: item.value as DisplayBehavior }));\n }\n }, []);\n\n const handleFormSelect = useCallback(\n (formId: number) => {\n if (!onConditionFormSelect || formFieldsByFormId[formId]) {\n return;\n }\n setLoadingFormIds(prev => (prev.includes(formId) ? prev : [...prev, formId]));\n onConditionFormSelect(formId, (selectedFormId, fields) => {\n setFormFieldsByFormId(prev => ({ ...prev, [selectedFormId]: fields }));\n setLoadingFormIds(prev => prev.filter(id => id !== selectedFormId));\n });\n },\n [formFieldsByFormId, onConditionFormSelect],\n );\n\n const canSave = useMemo(() => {\n if (isFieldDataLoading) {\n return false;\n }\n for (const group of state.groups) {\n for (const condition of group.conditions) {\n if (!condition.dataPointKey) {\n return false;\n }\n if (condition.operator === 'is_empty' || condition.operator === 'is_not_empty') {\n continue;\n }\n const trimmedValue = condition.value.trim();\n if (!trimmedValue) {\n return false;\n }\n const fieldType =\n allFieldOptions.find(opt => opt.fullKey === condition.dataPointKey)\n ?.fieldType ?? 'string';\n if (fieldType === 'number' && !NUMERIC_VALUE_RE.test(trimmedValue)) {\n return false;\n }\n }\n }\n return state.groups.length > 0;\n }, [allFieldOptions, isFieldDataLoading, state.groups]);\n\n if (!portalTarget || !isOpen) {\n return null;\n }\n\n return createPortal(\n <Dialog open={isOpen} onClose={handleClose} size=\"xlarge\">\n <Dialog.Header>Conditions</Dialog.Header>\n <Dialog.Content>\n <Flex\n direction=\"column\"\n gap=\"4\"\n style={{\n maxHeight: MODAL_CONTENT_MAX_HEIGHT,\n overflowY: 'auto',\n paddingRight: 8,\n width: '100%',\n }}\n >\n <ConditionGroupsSection\n behaviorSection={\n <Flex\n direction=\"row\"\n alignItems=\"center\"\n gap=\"2\"\n style={{ flexWrap: 'wrap' }}\n >\n <Text size=\"small\" subdued variant=\"body\">\n Select to include or exclude selected template/file.\n </Text>\n <div style={{ width: 90 }}>\n <Combobox.Select\n {...({ disableClearSelection: true } as object)}\n itemToKey={(item: BehaviorOption | null) =>\n item?.value ?? ''\n }\n itemToString={(item: BehaviorOption | null) =>\n item?.label ?? ''\n }\n items={[...BEHAVIOR_OPTIONS]}\n selectedItem={selectedBehavior}\n onChange={handleBehaviorChange}\n >\n <Combobox.SelectTrigger\n label=\"\"\n placeholder=\"Select...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: BehaviorOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item\n index={i}\n item={item}\n key={item.value}\n >\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n </Flex>\n }\n dataPointOptions={dataPointOptions}\n formFieldOptions={formFieldOptions}\n forms={forms}\n groups={state.groups}\n matchType={matchType}\n onFormSelect={handleFormSelect}\n onMatchTypeChange={setMatchType}\n onUpdateGroup={updateGroup}\n />\n </Flex>\n </Dialog.Content>\n <Dialog.Footer sticky>\n <Flex\n justifyContent=\"flex-end\"\n alignItems=\"center\"\n gap=\"3\"\n style={{ width: '100%' }}\n >\n <Button appearance=\"secondary\" onClick={handleClose}>\n Cancel\n </Button>\n <Button appearance=\"primary\" disabled={!canSave} onClick={handleSave}>\n Save Changes\n </Button>\n </Flex>\n </Dialog.Footer>\n </Dialog>,\n portalTarget,\n );\n};\n"],"names":["Button","Combobox","Dialog","Flex","Text","useCallback","useEffect","useMemo","useState","createPortal","buildFormFieldKey","getConditionalFieldTypeFromFormItemType","parseFormFieldKey","ConditionGroupsSection","defaultState","MODAL_CONTENT_MAX_HEIGHT","onDisplayCondition","buildUnlayerDisplayCondition","parseUnlayerDisplayCondition","getSchemaDataPointOptions","BEHAVIOR_OPTIONS","label","value","NUMERIC_VALUE_RE","deriveMatchType","conditionState","firstGroup","groups","conditions","length","hasOr","some","c","logicalOperator","applyMatchTypeToState","matchType","logicalOp","map","group","groupIndex","i","DisplayConditionModal","props","onConditionFormSelect","onConditionalsOpen","schema","request","setRequest","isOpen","setIsOpen","state","setState","setMatchType","forms","setForms","formFieldsByFormId","setFormFieldsByFormId","loadingFormIds","setLoadingFormIds","isFormsListLoading","setIsFormsListLoading","dataPointOptions","formFieldOptions","nextOptions","form","fields","id","field","fieldType","itemType","push","formId","fullKey","title","header","sort","left","right","localeCompare","allFieldOptions","isFieldDataLoading","portalTarget","document","body","nextRequest","existing","data","parsed","initialState","usedFormIds","Array","from","Set","flatMap","condition","dataPointKey","filter","nextForms","prev","handleClose","handleSave","finalState","done","updateGroup","index","next","selectedBehavior","find","opt","behavior","handleBehaviorChange","item","handleFormSelect","includes","selectedFormId","canSave","operator","trimmedValue","trim","test","open","onClose","size","Header","Content","direction","gap","style","maxHeight","overflowY","paddingRight","width","behaviorSection","alignItems","flexWrap","subdued","variant","div","Select","disableClearSelection","itemToKey","itemToString","items","selectedItem","onChange","SelectTrigger","placeholder","List","Item","onFormSelect","onMatchTypeChange","onUpdateGroup","Footer","sticky","justifyContent","appearance","onClick","disabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/display-conditions/DisplayConditionModal.tsx"],"sourcesContent":["import { Button, Combobox, Dialog, Flex, Text } from '@servicetitan/anvil2';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n buildFormFieldKey,\n FormFieldInfo,\n FormInfo,\n getConditionalFieldTypeFromFormItemType,\n parseFormFieldKey,\n} from '../shared/forms';\nimport { ConditionGroupsSection, MatchType } from './ConditionGroupsSection';\nimport { defaultState, MODAL_CONTENT_MAX_HEIGHT } from './constants';\nimport { DisplayConditionRequest, onDisplayCondition } from './displayConditionController';\nimport { buildUnlayerDisplayCondition, parseUnlayerDisplayCondition } from './nunjucks';\nimport { getSchemaDataPointOptions } from './schemaDataPoints';\nimport {\n ConditionGroup as ConditionGroupType,\n DisplayBehavior,\n DisplayConditionState,\n FormFieldOption,\n LogicalOperator,\n} from './types';\n\nconst BEHAVIOR_OPTIONS = [\n { label: 'Show', value: 'show' },\n { label: 'Hide', value: 'hide' },\n] as const;\nconst NUMERIC_VALUE_RE = /^-?(?:\\d+\\.?\\d*|\\.\\d+)$/;\ntype BehaviorOption = (typeof BEHAVIOR_OPTIONS)[number];\n\nfunction deriveMatchType(conditionState: DisplayConditionState): MatchType {\n const firstGroup = conditionState.groups[0];\n if (!firstGroup || firstGroup.conditions.length <= 1) {\n return 'all';\n }\n const hasOr = firstGroup.conditions.some(c => c.logicalOperator === 'or');\n return hasOr ? 'any' : 'all';\n}\n\nfunction applyMatchTypeToState(\n conditionState: DisplayConditionState,\n matchType: MatchType,\n): DisplayConditionState {\n const logicalOp: LogicalOperator = matchType === 'all' ? 'and' : 'or';\n return {\n ...conditionState,\n groups: conditionState.groups.map((group, groupIndex) => {\n if (groupIndex !== 0) {\n return group;\n }\n return {\n ...group,\n conditions: group.conditions.map((c, i) =>\n i === 0 ? c : { ...c, logicalOperator: logicalOp },\n ),\n };\n }),\n };\n}\n\nexport interface DisplayConditionModalProps {\n onConditionFormSelect?: (\n formId: number,\n sendFormFields: (formId: number, fields: FormFieldInfo[]) => void,\n ) => void;\n onConditionalsOpen?: (\n usedFormIds: number[],\n sendFormList: (forms: FormInfo[]) => void,\n sendFormFields: (formId: number, fields: FormFieldInfo[]) => void,\n ) => void;\n schema?: import('../shared/schema').SchemaObject;\n}\n\nexport const DisplayConditionModal = (props: DisplayConditionModalProps) => {\n const { onConditionFormSelect, onConditionalsOpen, schema } = props;\n const [request, setRequest] = useState<DisplayConditionRequest | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [state, setState] = useState(defaultState);\n const [matchType, setMatchType] = useState<MatchType>('all');\n const [forms, setForms] = useState<FormInfo[]>([]);\n const [formFieldsByFormId, setFormFieldsByFormId] = useState<Record<number, FormFieldInfo[]>>(\n {},\n );\n const [loadingFormIds, setLoadingFormIds] = useState<number[]>([]);\n const [isFormsListLoading, setIsFormsListLoading] = useState(false);\n const formFieldsByFormIdRef = useRef<Record<number, FormFieldInfo[]>>({});\n const loadingFormIdsRef = useRef<number[]>([]);\n\n const dataPointOptions = useMemo(() => getSchemaDataPointOptions(schema), [schema]);\n const formFieldOptions = useMemo<FormFieldOption[]>(() => {\n const nextOptions: FormFieldOption[] = [];\n for (const form of forms) {\n const fields = formFieldsByFormId[form.id];\n if (!fields) {\n continue;\n }\n for (const field of fields) {\n const fieldType = getConditionalFieldTypeFromFormItemType(field.itemType);\n if (!fieldType) {\n continue;\n }\n nextOptions.push({\n fieldType,\n formId: form.id,\n fullKey: buildFormFieldKey(form.id, field.id),\n title: field.header,\n });\n }\n }\n return nextOptions.sort((left, right) => left.title.localeCompare(right.title));\n }, [forms, formFieldsByFormId]);\n const allFieldOptions = useMemo(\n () => [...dataPointOptions, ...formFieldOptions],\n [dataPointOptions, formFieldOptions],\n );\n const isFieldDataLoading = isFormsListLoading || loadingFormIds.length > 0;\n\n useEffect(() => {\n formFieldsByFormIdRef.current = formFieldsByFormId;\n }, [formFieldsByFormId]);\n\n useEffect(() => {\n loadingFormIdsRef.current = loadingFormIds;\n }, [loadingFormIds]);\n\n const requestFormFields = useCallback(\n (formId: number) => {\n if (!onConditionFormSelect) {\n return;\n }\n if (\n formFieldsByFormIdRef.current[formId] ||\n loadingFormIdsRef.current.includes(formId)\n ) {\n return;\n }\n\n setLoadingFormIds(prev => (prev.includes(formId) ? prev : [...prev, formId]));\n onConditionFormSelect(formId, (selectedFormId, fields) => {\n setFormFieldsByFormId(prev => ({ ...prev, [selectedFormId]: fields }));\n setLoadingFormIds(prev => prev.filter(id => id !== selectedFormId));\n });\n },\n [onConditionFormSelect],\n );\n\n const portalTarget = useMemo(() => {\n if (typeof document === 'undefined') {\n return null;\n }\n return document.body;\n }, []);\n\n useEffect(() => {\n return onDisplayCondition(nextRequest => {\n setRequest(nextRequest);\n const existing = nextRequest.data;\n const parsed = parseUnlayerDisplayCondition(existing);\n const initialState = parsed ?? defaultState();\n const usedFormIds = Array.from(\n new Set(\n initialState.groups\n .flatMap(group => group.conditions)\n .map(condition => parseFormFieldKey(condition.dataPointKey)?.formId)\n .filter((formId): formId is number => formId != null),\n ),\n );\n\n setForms([]);\n setFormFieldsByFormId({});\n setIsFormsListLoading(!!onConditionalsOpen);\n setLoadingFormIds([]);\n\n if (onConditionalsOpen) {\n onConditionalsOpen(\n usedFormIds,\n nextForms => {\n setForms(nextForms);\n setIsFormsListLoading(false);\n },\n (formId, fields) => {\n setFormFieldsByFormId(prev => ({ ...prev, [formId]: fields }));\n setLoadingFormIds(prev => prev.filter(id => id !== formId));\n },\n );\n\n /*\n * Keep display-condition flow aligned with calculated-field flow:\n * when modal opens, request fields for already-used forms so labels\n * are available in edit mode.\n */\n usedFormIds.forEach(formId => requestFormFields(formId));\n } else {\n setIsFormsListLoading(false);\n setLoadingFormIds([]);\n }\n\n setState(initialState);\n setMatchType(deriveMatchType(initialState));\n setIsOpen(true);\n });\n }, [onConditionalsOpen, requestFormFields]);\n\n const handleClose = useCallback(() => {\n setRequest(null);\n setIsOpen(false);\n }, []);\n\n const handleSave = useCallback(() => {\n if (!request) {\n return;\n }\n const finalState = applyMatchTypeToState(state, matchType);\n const condition = buildUnlayerDisplayCondition(finalState);\n if (condition) {\n request.done(condition);\n } else {\n request.done(null);\n }\n setRequest(null);\n setIsOpen(false);\n }, [matchType, request, state]);\n\n const updateGroup = useCallback((index: number, group: ConditionGroupType) => {\n setState(prev => {\n const next = [...prev.groups];\n next[index] = group;\n return { ...prev, groups: next };\n });\n }, []);\n\n const selectedBehavior = useMemo(\n () => BEHAVIOR_OPTIONS.find(opt => opt.value === state.behavior) ?? BEHAVIOR_OPTIONS[0],\n [state.behavior],\n );\n\n const handleBehaviorChange = useCallback((item: BehaviorOption | null) => {\n if (item) {\n setState(prev => ({ ...prev, behavior: item.value as DisplayBehavior }));\n }\n }, []);\n\n const handleFormSelect = useCallback(\n (formId: number) => {\n requestFormFields(formId);\n },\n [requestFormFields],\n );\n\n const canSave = useMemo(() => {\n if (isFieldDataLoading) {\n return false;\n }\n for (const group of state.groups) {\n for (const condition of group.conditions) {\n if (!condition.dataPointKey) {\n return false;\n }\n if (condition.operator === 'is_empty' || condition.operator === 'is_not_empty') {\n continue;\n }\n const trimmedValue = condition.value.trim();\n if (!trimmedValue) {\n return false;\n }\n const fieldType =\n allFieldOptions.find(opt => opt.fullKey === condition.dataPointKey)\n ?.fieldType ?? 'string';\n if (fieldType === 'number' && !NUMERIC_VALUE_RE.test(trimmedValue)) {\n return false;\n }\n }\n }\n return state.groups.length > 0;\n }, [allFieldOptions, isFieldDataLoading, state.groups]);\n\n if (!portalTarget || !isOpen) {\n return null;\n }\n\n return createPortal(\n <Dialog open={isOpen} onClose={handleClose} size=\"xlarge\">\n <Dialog.Header>Conditions</Dialog.Header>\n <Dialog.Content>\n <Flex\n direction=\"column\"\n gap=\"4\"\n style={{\n maxHeight: MODAL_CONTENT_MAX_HEIGHT,\n overflowY: 'auto',\n paddingRight: 8,\n width: '100%',\n }}\n >\n <ConditionGroupsSection\n behaviorSection={\n <Flex\n direction=\"row\"\n alignItems=\"center\"\n gap=\"2\"\n style={{ flexWrap: 'wrap' }}\n >\n <Text size=\"small\" subdued variant=\"body\">\n Select to include or exclude selected template/file.\n </Text>\n <div style={{ width: 90 }}>\n <Combobox.Select\n {...({ disableClearSelection: true } as object)}\n itemToKey={(item: BehaviorOption | null) =>\n item?.value ?? ''\n }\n itemToString={(item: BehaviorOption | null) =>\n item?.label ?? ''\n }\n items={[...BEHAVIOR_OPTIONS]}\n selectedItem={selectedBehavior}\n onChange={handleBehaviorChange}\n >\n <Combobox.SelectTrigger\n label=\"\"\n placeholder=\"Select...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: BehaviorOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item\n index={i}\n item={item}\n key={item.value}\n >\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n </Flex>\n }\n dataPointOptions={dataPointOptions}\n formFieldOptions={formFieldOptions}\n forms={forms}\n groups={state.groups}\n matchType={matchType}\n onFormSelect={handleFormSelect}\n onMatchTypeChange={setMatchType}\n onUpdateGroup={updateGroup}\n />\n </Flex>\n </Dialog.Content>\n <Dialog.Footer sticky>\n <Flex\n justifyContent=\"flex-end\"\n alignItems=\"center\"\n gap=\"3\"\n style={{ width: '100%' }}\n >\n <Button appearance=\"secondary\" onClick={handleClose}>\n Cancel\n </Button>\n <Button appearance=\"primary\" disabled={!canSave} onClick={handleSave}>\n Save Changes\n </Button>\n </Flex>\n </Dialog.Footer>\n </Dialog>,\n portalTarget,\n );\n};\n"],"names":["Button","Combobox","Dialog","Flex","Text","useCallback","useEffect","useMemo","useRef","useState","createPortal","buildFormFieldKey","getConditionalFieldTypeFromFormItemType","parseFormFieldKey","ConditionGroupsSection","defaultState","MODAL_CONTENT_MAX_HEIGHT","onDisplayCondition","buildUnlayerDisplayCondition","parseUnlayerDisplayCondition","getSchemaDataPointOptions","BEHAVIOR_OPTIONS","label","value","NUMERIC_VALUE_RE","deriveMatchType","conditionState","firstGroup","groups","conditions","length","hasOr","some","c","logicalOperator","applyMatchTypeToState","matchType","logicalOp","map","group","groupIndex","i","DisplayConditionModal","props","onConditionFormSelect","onConditionalsOpen","schema","request","setRequest","isOpen","setIsOpen","state","setState","setMatchType","forms","setForms","formFieldsByFormId","setFormFieldsByFormId","loadingFormIds","setLoadingFormIds","isFormsListLoading","setIsFormsListLoading","formFieldsByFormIdRef","loadingFormIdsRef","dataPointOptions","formFieldOptions","nextOptions","form","fields","id","field","fieldType","itemType","push","formId","fullKey","title","header","sort","left","right","localeCompare","allFieldOptions","isFieldDataLoading","current","requestFormFields","includes","prev","selectedFormId","filter","portalTarget","document","body","nextRequest","existing","data","parsed","initialState","usedFormIds","Array","from","Set","flatMap","condition","dataPointKey","nextForms","forEach","handleClose","handleSave","finalState","done","updateGroup","index","next","selectedBehavior","find","opt","behavior","handleBehaviorChange","item","handleFormSelect","canSave","operator","trimmedValue","trim","test","open","onClose","size","Header","Content","direction","gap","style","maxHeight","overflowY","paddingRight","width","behaviorSection","alignItems","flexWrap","subdued","variant","div","Select","disableClearSelection","itemToKey","itemToString","items","selectedItem","onChange","SelectTrigger","placeholder","List","Item","onFormSelect","onMatchTypeChange","onUpdateGroup","Footer","sticky","justifyContent","appearance","onClick","disabled"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Button, Combobox, Dialog, Flex, Text } from '@servicetitan/anvil2';
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import {
|
|
5
5
|
buildFormFieldKey,
|
|
@@ -83,6 +83,8 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
83
83
|
);
|
|
84
84
|
const [loadingFormIds, setLoadingFormIds] = useState<number[]>([]);
|
|
85
85
|
const [isFormsListLoading, setIsFormsListLoading] = useState(false);
|
|
86
|
+
const formFieldsByFormIdRef = useRef<Record<number, FormFieldInfo[]>>({});
|
|
87
|
+
const loadingFormIdsRef = useRef<number[]>([]);
|
|
86
88
|
|
|
87
89
|
const dataPointOptions = useMemo(() => getSchemaDataPointOptions(schema), [schema]);
|
|
88
90
|
const formFieldOptions = useMemo<FormFieldOption[]>(() => {
|
|
@@ -113,6 +115,35 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
113
115
|
);
|
|
114
116
|
const isFieldDataLoading = isFormsListLoading || loadingFormIds.length > 0;
|
|
115
117
|
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
formFieldsByFormIdRef.current = formFieldsByFormId;
|
|
120
|
+
}, [formFieldsByFormId]);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
loadingFormIdsRef.current = loadingFormIds;
|
|
124
|
+
}, [loadingFormIds]);
|
|
125
|
+
|
|
126
|
+
const requestFormFields = useCallback(
|
|
127
|
+
(formId: number) => {
|
|
128
|
+
if (!onConditionFormSelect) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (
|
|
132
|
+
formFieldsByFormIdRef.current[formId] ||
|
|
133
|
+
loadingFormIdsRef.current.includes(formId)
|
|
134
|
+
) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setLoadingFormIds(prev => (prev.includes(formId) ? prev : [...prev, formId]));
|
|
139
|
+
onConditionFormSelect(formId, (selectedFormId, fields) => {
|
|
140
|
+
setFormFieldsByFormId(prev => ({ ...prev, [selectedFormId]: fields }));
|
|
141
|
+
setLoadingFormIds(prev => prev.filter(id => id !== selectedFormId));
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
[onConditionFormSelect],
|
|
145
|
+
);
|
|
146
|
+
|
|
116
147
|
const portalTarget = useMemo(() => {
|
|
117
148
|
if (typeof document === 'undefined') {
|
|
118
149
|
return null;
|
|
@@ -138,7 +169,7 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
138
169
|
setForms([]);
|
|
139
170
|
setFormFieldsByFormId({});
|
|
140
171
|
setIsFormsListLoading(!!onConditionalsOpen);
|
|
141
|
-
setLoadingFormIds(
|
|
172
|
+
setLoadingFormIds([]);
|
|
142
173
|
|
|
143
174
|
if (onConditionalsOpen) {
|
|
144
175
|
onConditionalsOpen(
|
|
@@ -152,6 +183,13 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
152
183
|
setLoadingFormIds(prev => prev.filter(id => id !== formId));
|
|
153
184
|
},
|
|
154
185
|
);
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* Keep display-condition flow aligned with calculated-field flow:
|
|
189
|
+
* when modal opens, request fields for already-used forms so labels
|
|
190
|
+
* are available in edit mode.
|
|
191
|
+
*/
|
|
192
|
+
usedFormIds.forEach(formId => requestFormFields(formId));
|
|
155
193
|
} else {
|
|
156
194
|
setIsFormsListLoading(false);
|
|
157
195
|
setLoadingFormIds([]);
|
|
@@ -161,7 +199,7 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
161
199
|
setMatchType(deriveMatchType(initialState));
|
|
162
200
|
setIsOpen(true);
|
|
163
201
|
});
|
|
164
|
-
}, [onConditionalsOpen]);
|
|
202
|
+
}, [onConditionalsOpen, requestFormFields]);
|
|
165
203
|
|
|
166
204
|
const handleClose = useCallback(() => {
|
|
167
205
|
setRequest(null);
|
|
@@ -204,16 +242,9 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
|
|
|
204
242
|
|
|
205
243
|
const handleFormSelect = useCallback(
|
|
206
244
|
(formId: number) => {
|
|
207
|
-
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
setLoadingFormIds(prev => (prev.includes(formId) ? prev : [...prev, formId]));
|
|
211
|
-
onConditionFormSelect(formId, (selectedFormId, fields) => {
|
|
212
|
-
setFormFieldsByFormId(prev => ({ ...prev, [selectedFormId]: fields }));
|
|
213
|
-
setLoadingFormIds(prev => prev.filter(id => id !== selectedFormId));
|
|
214
|
-
});
|
|
245
|
+
requestFormFields(formId);
|
|
215
246
|
},
|
|
216
|
-
[
|
|
247
|
+
[requestFormFields],
|
|
217
248
|
);
|
|
218
249
|
|
|
219
250
|
const canSave = useMemo(() => {
|