@servicetitan/dte-pdf-editor 1.21.0 → 1.23.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/components/display-conditions/condition-group.d.ts +11 -0
- package/dist/components/display-conditions/condition-group.d.ts.map +1 -0
- package/dist/components/display-conditions/condition-group.js +52 -0
- package/dist/components/display-conditions/condition-group.js.map +1 -0
- package/dist/components/display-conditions/condition-groups-section.d.ts +10 -0
- package/dist/components/display-conditions/condition-groups-section.d.ts.map +1 -0
- package/dist/components/display-conditions/condition-groups-section.js +14 -0
- package/dist/components/display-conditions/condition-groups-section.js.map +1 -0
- package/dist/components/display-conditions/condition-row.d.ts +10 -0
- package/dist/components/display-conditions/condition-row.d.ts.map +1 -0
- package/dist/components/display-conditions/condition-row.js +60 -0
- package/dist/components/display-conditions/condition-row.js.map +1 -0
- package/dist/components/display-conditions/display-condition-modal.d.ts +12 -0
- package/dist/components/display-conditions/display-condition-modal.d.ts.map +1 -0
- package/dist/components/display-conditions/display-condition-modal.js +89 -0
- package/dist/components/display-conditions/display-condition-modal.js.map +1 -0
- package/dist/components/display-conditions/display-condition.d.ts +9 -0
- package/dist/components/display-conditions/display-condition.d.ts.map +1 -0
- package/dist/components/display-conditions/display-condition.js +10 -0
- package/dist/components/display-conditions/display-condition.js.map +1 -0
- package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -1
- package/dist/components/field-config-panel/advanced-settings.js +10 -8
- package/dist/components/field-config-panel/advanced-settings.js.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +5 -3
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-generator.js +3 -20
- package/dist/components/field-config-panel/formula-generator.js.map +1 -1
- package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-modal.js +9 -3
- package/dist/components/field-config-panel/formula-modal.js.map +1 -1
- package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -1
- package/dist/components/field-config-panel/formula-workspace.js +4 -3
- package/dist/components/field-config-panel/formula-workspace.js.map +1 -1
- package/dist/components/field-config-panel/result-type-selector.d.ts.map +1 -1
- package/dist/components/field-config-panel/result-type-selector.js +2 -2
- package/dist/components/field-config-panel/result-type-selector.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.d.ts +2 -1
- package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.js +6 -2
- package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +1 -1
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/calculated.constants.d.ts +2 -0
- package/dist/constants/calculated.constants.d.ts.map +1 -0
- package/dist/constants/calculated.constants.js +2 -0
- package/dist/constants/calculated.constants.js.map +1 -0
- package/dist/constants/conditions.constants.d.ts +6 -0
- package/dist/constants/conditions.constants.d.ts.map +1 -0
- package/dist/constants/conditions.constants.js +17 -0
- package/dist/constants/conditions.constants.js.map +1 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/interface/types.d.ts +74 -0
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js +25 -0
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/conditions/evaluate.utils.d.ts +6 -0
- package/dist/utils/conditions/evaluate.utils.d.ts.map +1 -0
- package/dist/utils/conditions/evaluate.utils.js +95 -0
- package/dist/utils/conditions/evaluate.utils.js.map +1 -0
- package/dist/utils/conditions/index.d.ts +3 -0
- package/dist/utils/conditions/index.d.ts.map +1 -0
- package/dist/utils/conditions/index.js +3 -0
- package/dist/utils/conditions/index.js.map +1 -0
- package/dist/utils/conditions/schema-data-points.utils.d.ts +12 -0
- package/dist/utils/conditions/schema-data-points.utils.d.ts.map +1 -0
- package/dist/utils/conditions/schema-data-points.utils.js +67 -0
- package/dist/utils/conditions/schema-data-points.utils.js.map +1 -0
- package/dist/utils/data-model/extract-fields.utils.d.ts +1 -0
- package/dist/utils/data-model/extract-fields.utils.d.ts.map +1 -1
- package/dist/utils/data-model/extract-fields.utils.js.map +1 -1
- package/dist/utils/formula/render-formula.utils.d.ts +0 -6
- package/dist/utils/formula/render-formula.utils.d.ts.map +1 -1
- package/dist/utils/formula/render-formula.utils.js +0 -17
- package/dist/utils/formula/render-formula.utils.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/shared/index.d.ts +2 -0
- package/dist/utils/shared/index.d.ts.map +1 -0
- package/dist/utils/shared/index.js +2 -0
- package/dist/utils/shared/index.js.map +1 -0
- package/dist/utils/shared/number.utils.d.ts +2 -0
- package/dist/utils/shared/number.utils.d.ts.map +1 -0
- package/dist/utils/shared/number.utils.js +12 -0
- package/dist/utils/shared/number.utils.js.map +1 -0
- package/package.json +1 -1
- package/src/components/display-conditions/condition-group.tsx +141 -0
- package/src/components/display-conditions/condition-groups-section.tsx +63 -0
- package/src/components/display-conditions/condition-row.tsx +182 -0
- package/src/components/display-conditions/display-condition-modal.tsx +180 -0
- package/src/components/display-conditions/display-condition.tsx +41 -0
- package/src/components/field-config-panel/advanced-settings.tsx +42 -46
- package/src/components/field-config-panel/field-config-panel.tsx +13 -3
- package/src/components/field-config-panel/formula-generator.tsx +9 -44
- package/src/components/field-config-panel/formula-modal.tsx +72 -82
- package/src/components/field-config-panel/formula-workspace.tsx +6 -5
- package/src/components/field-config-panel/result-type-selector.tsx +8 -11
- package/src/components/pdf-view/pdf-view-field-container.tsx +11 -2
- package/src/components/pdf-view/pdf-view.tsx +1 -0
- package/src/constants/calculated.constants.ts +1 -0
- package/src/constants/conditions.constants.ts +26 -0
- package/src/constants/index.ts +3 -0
- package/src/interface/types.ts +59 -0
- package/src/styles/formula-modal.css +1 -155
- package/src/utils/conditions/evaluate.utils.ts +134 -0
- package/src/utils/conditions/index.ts +2 -0
- package/src/utils/conditions/schema-data-points.utils.ts +93 -0
- package/src/utils/data-model/extract-fields.utils.ts +1 -0
- package/src/utils/formula/render-formula.utils.ts +0 -19
- package/src/utils/index.ts +2 -0
- package/src/utils/shared/index.ts +1 -0
- package/src/utils/shared/number.utils.ts +13 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DataPointOption, type PdfField, type SchemaObject } from '../../interface/types';
|
|
2
|
+
export declare function getSchemaDataPointOptions(schema: SchemaObject | undefined): DataPointOption[];
|
|
3
|
+
/**
|
|
4
|
+
* Data point options from document fields: fillable only (no calculated fields).
|
|
5
|
+
* Uses field.path as fullKey so condition evaluation can read from the same data object.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getDocumentFieldsDataPointOptions(fields: PdfField[] | undefined): DataPointOption[];
|
|
8
|
+
/**
|
|
9
|
+
* Combined data point options: schema (data model) + fillable fields.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getDataPointOptions(schema: SchemaObject | undefined, documentFields: PdfField[] | undefined): DataPointOption[];
|
|
12
|
+
//# sourceMappingURL=schema-data-points.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-data-points.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/conditions/schema-data-points.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,eAAe,EAEf,KAAK,QAAQ,EAEb,KAAK,YAAY,EACpB,MAAM,uBAAuB,CAAC;AA6C/B,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,eAAe,EAAE,CAG7F;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC7C,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,GAC/B,eAAe,EAAE,CAiBnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,cAAc,EAAE,QAAQ,EAAE,GAAG,SAAS,GACvC,eAAe,EAAE,CAKnB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FieldTypeEnum, } from '../../interface/types';
|
|
2
|
+
function isUseInConditionals(node) {
|
|
3
|
+
var _a, _b;
|
|
4
|
+
return (((_a = node === null || node === void 0 ? void 0 : node.options) === null || _a === void 0 ? void 0 : _a.useInConditionals) === true || ((_b = node === null || node === void 0 ? void 0 : node.options) === null || _b === void 0 ? void 0 : _b.useInCalculatedFields) === true);
|
|
5
|
+
}
|
|
6
|
+
function isValueNode(node) {
|
|
7
|
+
return (node === null || node === void 0 ? void 0 : node.type) === 'string' || (node === null || node === void 0 ? void 0 : node.type) === 'number';
|
|
8
|
+
}
|
|
9
|
+
function walkSchema(schema, prefix, parentTitles) {
|
|
10
|
+
var _a;
|
|
11
|
+
if (!(schema === null || schema === void 0 ? void 0 : schema.properties)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const result = [];
|
|
15
|
+
for (const [key, node] of Object.entries(schema.properties)) {
|
|
16
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
17
|
+
const title = (_a = node.title) !== null && _a !== void 0 ? _a : key;
|
|
18
|
+
const fullTitle = [...parentTitles, title].join(' - ');
|
|
19
|
+
if ((node === null || node === void 0 ? void 0 : node.type) === 'object') {
|
|
20
|
+
result.push(...walkSchema(node, fullKey, [...parentTitles, title]));
|
|
21
|
+
}
|
|
22
|
+
else if (isValueNode(node) && isUseInConditionals(node)) {
|
|
23
|
+
result.push({
|
|
24
|
+
fieldType: node.type === 'number' ? 'number' : 'string',
|
|
25
|
+
fullKey,
|
|
26
|
+
title: fullTitle || fullKey,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
export function getSchemaDataPointOptions(schema) {
|
|
33
|
+
const options = walkSchema(schema, '', []);
|
|
34
|
+
return options.sort((a, b) => a.title.localeCompare(b.title));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Data point options from document fields: fillable only (no calculated fields).
|
|
38
|
+
* Uses field.path as fullKey so condition evaluation can read from the same data object.
|
|
39
|
+
*/
|
|
40
|
+
export function getDocumentFieldsDataPointOptions(fields) {
|
|
41
|
+
if (!(fields === null || fields === void 0 ? void 0 : fields.length)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const result = [];
|
|
45
|
+
for (const f of fields) {
|
|
46
|
+
if (!f.path || f.type !== FieldTypeEnum.fillable) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const fieldType = f.subType === 'number' ? 'number' : 'string';
|
|
50
|
+
result.push({
|
|
51
|
+
fieldType,
|
|
52
|
+
fullKey: f.path,
|
|
53
|
+
title: f.label || f.path,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return result.sort((a, b) => a.title.localeCompare(b.title));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Combined data point options: schema (data model) + fillable fields.
|
|
60
|
+
*/
|
|
61
|
+
export function getDataPointOptions(schema, documentFields) {
|
|
62
|
+
const schemaOptions = getSchemaDataPointOptions(schema);
|
|
63
|
+
const documentOptions = getDocumentFieldsDataPointOptions(documentFields);
|
|
64
|
+
const combined = [...schemaOptions, ...documentOptions];
|
|
65
|
+
return combined.sort((a, b) => a.title.localeCompare(b.title));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=schema-data-points.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-data-points.utils.js","sourceRoot":"","sources":["../../../src/utils/conditions/schema-data-points.utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { DataModelFieldGroup, SchemaObject } from '../../interface/types';
|
|
|
2
2
|
export interface ExtractGroupedFieldsOptions {
|
|
3
3
|
/** When true, only include fields with SchemaFieldBaseOptions.useInCalculatedFields === true */
|
|
4
4
|
onlyUseInCalculatedFields?: boolean;
|
|
5
|
+
onlyUseInConditionals?: boolean;
|
|
5
6
|
}
|
|
6
7
|
export declare const extractGroupedFieldsFromDataModel: (dataModel: SchemaObject, options?: ExtractGroupedFieldsOptions) => DataModelFieldGroup[];
|
|
7
8
|
//# sourceMappingURL=extract-fields.utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-fields.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/data-model/extract-fields.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EAInB,YAAY,EAGf,MAAM,uBAAuB,CAAC;AAc/B,MAAM,WAAW,2BAA2B;IACxC,gGAAgG;IAChG,yBAAyB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"extract-fields.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/data-model/extract-fields.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EAInB,YAAY,EAGf,MAAM,uBAAuB,CAAC;AAc/B,MAAM,WAAW,2BAA2B;IACxC,gGAAgG;IAChG,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACnC;AAQD,eAAO,MAAM,iCAAiC,GAC1C,WAAW,YAAY,EACvB,UAAU,2BAA2B,KACtC,mBAAmB,EAyDrB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-fields.utils.js","sourceRoot":"","sources":["../../../src/utils/data-model/extract-fields.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,aAAa,GAMhB,MAAM,uBAAuB,CAAC;AAE/B,SAAS,oBAAoB,CAAC,IAAgB;IAC1C,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;AAClC,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAgB;IACxC,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;AAChC,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAgB;;IAC3C,OAAO,CAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,qBAAqB,MAAK,IAAI,CAAC;AACxD,CAAC;
|
|
1
|
+
{"version":3,"file":"extract-fields.utils.js","sourceRoot":"","sources":["../../../src/utils/data-model/extract-fields.utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
import { FormulaToken } from '../../interface/types';
|
|
2
|
-
/**
|
|
3
|
-
* Renders formula tokens to HTML for the read-only preview (e.g. formula box).
|
|
4
|
-
* Uses the same token classes as the modal (dte-formula-token, dte-formula-token-field, etc.).
|
|
5
|
-
*/
|
|
6
|
-
export declare function renderFormulaPreviewHtml(tokens: FormulaToken[]): string;
|
|
7
1
|
export declare function renderFormulaHtml(expression: string, labelMap: Map<string, string>): string;
|
|
8
2
|
//# sourceMappingURL=render-formula.utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-formula.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/render-formula.utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-formula.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/render-formula.utils.ts"],"names":[],"mappings":"AAGA,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAiB3F"}
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
import { escapeHtml } from './dom.utils';
|
|
2
2
|
import { tokenizeExpression } from './expression.utils';
|
|
3
|
-
/**
|
|
4
|
-
* Renders formula tokens to HTML for the read-only preview (e.g. formula box).
|
|
5
|
-
* Uses the same token classes as the modal (dte-formula-token, dte-formula-token-field, etc.).
|
|
6
|
-
*/
|
|
7
|
-
export function renderFormulaPreviewHtml(tokens) {
|
|
8
|
-
if (!(tokens === null || tokens === void 0 ? void 0 : tokens.length)) {
|
|
9
|
-
return '';
|
|
10
|
-
}
|
|
11
|
-
return tokens
|
|
12
|
-
.map(token => {
|
|
13
|
-
const type = token.type;
|
|
14
|
-
const text = type === 'field' ? token.label : token.value;
|
|
15
|
-
const escaped = escapeHtml(text).replace(/ /g, ' ');
|
|
16
|
-
return `<span class="dte-formula-token dte-formula-token-${type}">${escaped}</span>`;
|
|
17
|
-
})
|
|
18
|
-
.join('');
|
|
19
|
-
}
|
|
20
3
|
export function renderFormulaHtml(expression, labelMap) {
|
|
21
4
|
if (!expression.trim()) {
|
|
22
5
|
return '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-formula.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/render-formula.utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-formula.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/render-formula.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,QAA6B;IAC/E,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,OAAO,KAAK;SACP,GAAG,CAAC,IAAI,CAAC,EAAE;;QACR,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACxB,MAAM,YAAY,GAAG,MAAA,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,mCAAI,IAAI,CAAC,KAAK,CAAC;YAC5D,MAAM,YAAY,GAAG,UAAU,CAAC;YAChC,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO,sEAAsE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,uBAAuB,YAAY,2DAA2D,UAAU,CAAC,YAAY,CAAC,qIAAqI,CAAC;QACnW,CAAC;QACD,OAAO,SAAS,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC;IAC5E,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
package/dist/utils/index.js
CHANGED
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/shared/number.utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAY7D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isValidNumber(value) {
|
|
2
|
+
if (typeof value === 'number') {
|
|
3
|
+
return Number.isFinite(value);
|
|
4
|
+
}
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (trimmed === '') {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const parsed = Number(trimmed);
|
|
10
|
+
return Number.isFinite(parsed);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=number.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.utils.js","sourceRoot":"","sources":["../../../src/utils/shared/number.utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,KAAsB;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Button, Flex, SegmentedControl, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import PlusIcon from '@servicetitan/anvil2/assets/icons/material/round/add.svg';
|
|
3
|
+
import TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
|
|
4
|
+
import { useCallback } from 'react';
|
|
5
|
+
import { defaultCondition } from '../../constants';
|
|
6
|
+
import type {
|
|
7
|
+
DataPointOption,
|
|
8
|
+
DisplayConditionGroup,
|
|
9
|
+
DisplayConditionSingle,
|
|
10
|
+
} from '../../interface/types';
|
|
11
|
+
import { ConditionRow } from './condition-row';
|
|
12
|
+
|
|
13
|
+
export interface ConditionGroupProps {
|
|
14
|
+
canDelete: boolean;
|
|
15
|
+
dataPointOptions: DataPointOption[];
|
|
16
|
+
group: DisplayConditionGroup;
|
|
17
|
+
onDelete: () => void;
|
|
18
|
+
onUpdate: (g: DisplayConditionGroup) => void;
|
|
19
|
+
ruleIndex: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ConditionGroup({
|
|
23
|
+
canDelete,
|
|
24
|
+
dataPointOptions,
|
|
25
|
+
group,
|
|
26
|
+
onDelete,
|
|
27
|
+
onUpdate,
|
|
28
|
+
ruleIndex,
|
|
29
|
+
}: Readonly<ConditionGroupProps>) {
|
|
30
|
+
const addCondition = useCallback(() => {
|
|
31
|
+
const newCondition: DisplayConditionSingle = {
|
|
32
|
+
...defaultCondition(),
|
|
33
|
+
logicalOperator: 'and',
|
|
34
|
+
};
|
|
35
|
+
onUpdate({
|
|
36
|
+
...group,
|
|
37
|
+
conditions: [...group.conditions, newCondition],
|
|
38
|
+
});
|
|
39
|
+
}, [group, onUpdate]);
|
|
40
|
+
|
|
41
|
+
const updateCondition = useCallback(
|
|
42
|
+
(index: number, c: DisplayConditionSingle) => {
|
|
43
|
+
const next = [...group.conditions];
|
|
44
|
+
next[index] = c;
|
|
45
|
+
onUpdate({ ...group, conditions: next });
|
|
46
|
+
},
|
|
47
|
+
[group, onUpdate],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const removeCondition = useCallback(
|
|
51
|
+
(index: number) => {
|
|
52
|
+
let next = group.conditions.filter((_, i) => i !== index);
|
|
53
|
+
if (index === 0 && next.length > 0) {
|
|
54
|
+
const { logicalOperator: omit, ...rest } = next[0];
|
|
55
|
+
next = [rest as DisplayConditionSingle, ...next.slice(1)];
|
|
56
|
+
}
|
|
57
|
+
onUpdate({
|
|
58
|
+
...group,
|
|
59
|
+
conditions: next.length ? next : [defaultCondition()],
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
[group, onUpdate],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const handleLogicalOperatorChange = useCallback(
|
|
66
|
+
(conditionIndex: number, value: string) => {
|
|
67
|
+
const next = [...group.conditions];
|
|
68
|
+
next[conditionIndex] = {
|
|
69
|
+
...next[conditionIndex],
|
|
70
|
+
logicalOperator: value as 'and' | 'or',
|
|
71
|
+
};
|
|
72
|
+
onUpdate({ ...group, conditions: next });
|
|
73
|
+
},
|
|
74
|
+
[group, onUpdate],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Flex direction="column" gap="2" flex={1}>
|
|
79
|
+
<Flex direction="row" alignItems="center" justifyContent="space-between" flex={1}>
|
|
80
|
+
<Text size="medium" variant="body" style={{ fontWeight: 'bold' }}>
|
|
81
|
+
Rule {ruleIndex + 1}
|
|
82
|
+
</Text>
|
|
83
|
+
{canDelete && (
|
|
84
|
+
<Button
|
|
85
|
+
appearance="ghost"
|
|
86
|
+
aria-label="Delete rule"
|
|
87
|
+
icon={{ before: TrashIcon }}
|
|
88
|
+
size="large"
|
|
89
|
+
onClick={onDelete}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</Flex>
|
|
93
|
+
<Flex
|
|
94
|
+
direction="column"
|
|
95
|
+
gap="3"
|
|
96
|
+
flex={1}
|
|
97
|
+
style={{
|
|
98
|
+
border: '1px solid #e0e0e0',
|
|
99
|
+
borderRadius: 8,
|
|
100
|
+
padding: 12,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{group.conditions.map((c, i) => (
|
|
104
|
+
<Flex key={c.id} direction="column" gap="3" style={{ padding: '8px 0' }}>
|
|
105
|
+
{i > 0 && (
|
|
106
|
+
<Flex justifyContent="center" alignItems="center" gap="2">
|
|
107
|
+
<SegmentedControl
|
|
108
|
+
selected={c.logicalOperator ?? 'and'}
|
|
109
|
+
onChange={(v: string) => handleLogicalOperatorChange(i, v)}
|
|
110
|
+
>
|
|
111
|
+
<SegmentedControl.Segment value="and">
|
|
112
|
+
And
|
|
113
|
+
</SegmentedControl.Segment>
|
|
114
|
+
<SegmentedControl.Segment value="or">
|
|
115
|
+
Or
|
|
116
|
+
</SegmentedControl.Segment>
|
|
117
|
+
</SegmentedControl>
|
|
118
|
+
</Flex>
|
|
119
|
+
)}
|
|
120
|
+
<ConditionRow
|
|
121
|
+
canRemove
|
|
122
|
+
condition={c}
|
|
123
|
+
dataPointOptions={dataPointOptions}
|
|
124
|
+
onRemove={() => removeCondition(i)}
|
|
125
|
+
onChange={next => updateCondition(i, next)}
|
|
126
|
+
/>
|
|
127
|
+
</Flex>
|
|
128
|
+
))}
|
|
129
|
+
<Flex justifyContent="center" flex={1} style={{ paddingTop: 8 }}>
|
|
130
|
+
<Button
|
|
131
|
+
appearance="secondary"
|
|
132
|
+
icon={{ before: PlusIcon }}
|
|
133
|
+
onClick={addCondition}
|
|
134
|
+
>
|
|
135
|
+
Add Condition
|
|
136
|
+
</Button>
|
|
137
|
+
</Flex>
|
|
138
|
+
</Flex>
|
|
139
|
+
</Flex>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Button, Flex, SegmentedControl } from '@servicetitan/anvil2';
|
|
2
|
+
import PlusIcon from '@servicetitan/anvil2/assets/icons/material/round/add.svg';
|
|
3
|
+
import type { DataPointOption, DisplayConditionGroup } from '../../interface/types';
|
|
4
|
+
import { ConditionGroup } from './condition-group';
|
|
5
|
+
|
|
6
|
+
export interface ConditionGroupsSectionProps {
|
|
7
|
+
dataPointOptions: DataPointOption[];
|
|
8
|
+
groups: DisplayConditionGroup[];
|
|
9
|
+
onAddGroup: () => void;
|
|
10
|
+
onRemoveGroup: (index: number) => void;
|
|
11
|
+
onUpdateGroup: (index: number, group: DisplayConditionGroup) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ConditionGroupsSection({
|
|
15
|
+
dataPointOptions,
|
|
16
|
+
groups,
|
|
17
|
+
onAddGroup,
|
|
18
|
+
onRemoveGroup,
|
|
19
|
+
onUpdateGroup,
|
|
20
|
+
}: Readonly<ConditionGroupsSectionProps>) {
|
|
21
|
+
return (
|
|
22
|
+
<Flex direction="column" gap="4">
|
|
23
|
+
{groups.map((group, index) => (
|
|
24
|
+
<Flex key={group.id} direction="column" gap="3">
|
|
25
|
+
{index > 0 && (
|
|
26
|
+
<Flex
|
|
27
|
+
justifyContent="center"
|
|
28
|
+
alignItems="center"
|
|
29
|
+
gap="2"
|
|
30
|
+
style={{ padding: '12px' }}
|
|
31
|
+
>
|
|
32
|
+
<SegmentedControl
|
|
33
|
+
selected={group.logicalOperator ?? 'and'}
|
|
34
|
+
onChange={(value: string) =>
|
|
35
|
+
onUpdateGroup(index, {
|
|
36
|
+
...group,
|
|
37
|
+
logicalOperator: value as 'and' | 'or',
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
<SegmentedControl.Segment value="and">And</SegmentedControl.Segment>
|
|
42
|
+
<SegmentedControl.Segment value="or">Or</SegmentedControl.Segment>
|
|
43
|
+
</SegmentedControl>
|
|
44
|
+
</Flex>
|
|
45
|
+
)}
|
|
46
|
+
<ConditionGroup
|
|
47
|
+
canDelete={groups.length > 1}
|
|
48
|
+
dataPointOptions={dataPointOptions}
|
|
49
|
+
group={group}
|
|
50
|
+
onDelete={() => onRemoveGroup(index)}
|
|
51
|
+
onUpdate={g => onUpdateGroup(index, g)}
|
|
52
|
+
ruleIndex={index}
|
|
53
|
+
/>
|
|
54
|
+
</Flex>
|
|
55
|
+
))}
|
|
56
|
+
<Flex justifyContent="center" flex={1} style={{ paddingTop: 8 }}>
|
|
57
|
+
<Button appearance="secondary" icon={{ before: PlusIcon }} onClick={onAddGroup}>
|
|
58
|
+
Add Rule
|
|
59
|
+
</Button>
|
|
60
|
+
</Flex>
|
|
61
|
+
</Flex>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Button, Chip, Combobox, Flex, TextField } from '@servicetitan/anvil2';
|
|
2
|
+
import TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
ConditionOperator,
|
|
6
|
+
DataPointOption,
|
|
7
|
+
DisplayConditionSingle,
|
|
8
|
+
NUMBER_OPERATORS,
|
|
9
|
+
STRING_OPERATORS,
|
|
10
|
+
VALUE_LESS_OPERATORS,
|
|
11
|
+
} from '../../interface/types';
|
|
12
|
+
|
|
13
|
+
export interface ConditionRowProps {
|
|
14
|
+
canRemove: boolean;
|
|
15
|
+
condition: DisplayConditionSingle;
|
|
16
|
+
dataPointOptions: DataPointOption[];
|
|
17
|
+
onChange: (c: DisplayConditionSingle) => void;
|
|
18
|
+
onRemove: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface OperatorOption {
|
|
22
|
+
label: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sanitizeNumericInput(raw: string): string {
|
|
27
|
+
let value = raw.replaceAll(/[^0-9.-]/g, '');
|
|
28
|
+
const isNegative = value.startsWith('-');
|
|
29
|
+
value = value.replaceAll('-', '');
|
|
30
|
+
if (isNegative) {
|
|
31
|
+
value = `-${value}`;
|
|
32
|
+
}
|
|
33
|
+
const firstDot = value.indexOf('.');
|
|
34
|
+
if (firstDot >= 0) {
|
|
35
|
+
value = `${value.slice(0, firstDot + 1)}${value.slice(firstDot + 1).replaceAll('.', '')}`;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ConditionRow({
|
|
41
|
+
canRemove,
|
|
42
|
+
condition,
|
|
43
|
+
dataPointOptions,
|
|
44
|
+
onChange,
|
|
45
|
+
onRemove,
|
|
46
|
+
}: Readonly<ConditionRowProps>) {
|
|
47
|
+
const selectedDataPoint = useMemo(
|
|
48
|
+
() => dataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null,
|
|
49
|
+
[dataPointOptions, condition.dataPointKey],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const fieldType = selectedDataPoint?.fieldType ?? 'string';
|
|
53
|
+
|
|
54
|
+
const operatorItems: OperatorOption[] = useMemo(
|
|
55
|
+
() =>
|
|
56
|
+
fieldType === 'number'
|
|
57
|
+
? ([...NUMBER_OPERATORS] as OperatorOption[])
|
|
58
|
+
: ([...STRING_OPERATORS] as OperatorOption[]),
|
|
59
|
+
[fieldType],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const selectedOperator = useMemo(
|
|
63
|
+
() => operatorItems.find(op => op.value === condition.operator) ?? null,
|
|
64
|
+
[operatorItems, condition.operator],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator as ConditionOperator);
|
|
68
|
+
|
|
69
|
+
const handleValueChange = (raw: string) => {
|
|
70
|
+
const value = fieldType === 'number' ? sanitizeNumericInput(raw) : raw;
|
|
71
|
+
onChange({ ...condition, value });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleDataPointChange = (item: DataPointOption | null) => {
|
|
75
|
+
const nextFieldType = item?.fieldType ?? 'string';
|
|
76
|
+
const nextIsNumber = nextFieldType === 'number';
|
|
77
|
+
const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;
|
|
78
|
+
const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);
|
|
79
|
+
onChange({
|
|
80
|
+
...condition,
|
|
81
|
+
dataPointKey: item?.fullKey ?? '',
|
|
82
|
+
operator: operatorReset
|
|
83
|
+
? nextIsNumber
|
|
84
|
+
? 'num_eq'
|
|
85
|
+
: 'is_equal_to'
|
|
86
|
+
: condition.operator,
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Flex direction="row" alignItems="flex-end" gap="2" flex={1}>
|
|
92
|
+
<Flex
|
|
93
|
+
alignItems="flex-end"
|
|
94
|
+
style={{
|
|
95
|
+
padding: '8px 12px',
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<Chip label="IF" size="medium" />
|
|
99
|
+
</Flex>
|
|
100
|
+
<Flex flex={1}>
|
|
101
|
+
<Combobox
|
|
102
|
+
flex={1}
|
|
103
|
+
itemToKey={(item: DataPointOption | null) => item?.fullKey ?? ''}
|
|
104
|
+
itemToString={(item: DataPointOption | null) => item?.title ?? ''}
|
|
105
|
+
items={dataPointOptions}
|
|
106
|
+
selectedItem={selectedDataPoint}
|
|
107
|
+
onChange={handleDataPointChange}
|
|
108
|
+
>
|
|
109
|
+
<Combobox.SelectTrigger label="Data point" placeholder="Select data point..." />
|
|
110
|
+
<Combobox.Content>
|
|
111
|
+
{({ items }: { items: DataPointOption[] }) => (
|
|
112
|
+
<Combobox.List>
|
|
113
|
+
{items.map((item, i) => (
|
|
114
|
+
<Combobox.Item index={i} item={item} key={item.fullKey}>
|
|
115
|
+
{item.title}
|
|
116
|
+
</Combobox.Item>
|
|
117
|
+
))}
|
|
118
|
+
</Combobox.List>
|
|
119
|
+
)}
|
|
120
|
+
</Combobox.Content>
|
|
121
|
+
</Combobox>
|
|
122
|
+
</Flex>
|
|
123
|
+
<Flex flex={1}>
|
|
124
|
+
<Combobox
|
|
125
|
+
flex={1}
|
|
126
|
+
itemToKey={(item: OperatorOption | null) => item?.value ?? ''}
|
|
127
|
+
itemToString={(item: OperatorOption | null) => item?.label ?? ''}
|
|
128
|
+
items={operatorItems}
|
|
129
|
+
selectedItem={selectedOperator}
|
|
130
|
+
onChange={(item: OperatorOption | null) =>
|
|
131
|
+
onChange({
|
|
132
|
+
...condition,
|
|
133
|
+
operator: (item?.value ??
|
|
134
|
+
'is_equal_to') as DisplayConditionSingle['operator'],
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
>
|
|
138
|
+
<Combobox.SelectTrigger label="Condition" placeholder="Select..." />
|
|
139
|
+
<Combobox.Content>
|
|
140
|
+
{({ items }: { items: OperatorOption[] }) => (
|
|
141
|
+
<Combobox.List>
|
|
142
|
+
{items.map((item, i) => (
|
|
143
|
+
<Combobox.Item index={i} item={item} key={item.value}>
|
|
144
|
+
{item.label}
|
|
145
|
+
</Combobox.Item>
|
|
146
|
+
))}
|
|
147
|
+
</Combobox.List>
|
|
148
|
+
)}
|
|
149
|
+
</Combobox.Content>
|
|
150
|
+
</Combobox>
|
|
151
|
+
</Flex>
|
|
152
|
+
{!isValueLess && (
|
|
153
|
+
<Flex flex={1}>
|
|
154
|
+
<TextField
|
|
155
|
+
label="Value"
|
|
156
|
+
flex={1}
|
|
157
|
+
value={condition.value}
|
|
158
|
+
onChange={e => handleValueChange(e.target.value)}
|
|
159
|
+
placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}
|
|
160
|
+
aria-label="Value"
|
|
161
|
+
{...(fieldType === 'number' && { inputMode: 'decimal' })}
|
|
162
|
+
/>
|
|
163
|
+
</Flex>
|
|
164
|
+
)}
|
|
165
|
+
{canRemove && (
|
|
166
|
+
<Flex
|
|
167
|
+
alignItems="flex-end"
|
|
168
|
+
style={{
|
|
169
|
+
padding: '6px 12px',
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<Button
|
|
173
|
+
appearance="secondary"
|
|
174
|
+
aria-label="Remove condition"
|
|
175
|
+
icon={{ before: TrashIcon }}
|
|
176
|
+
onClick={onRemove}
|
|
177
|
+
/>
|
|
178
|
+
</Flex>
|
|
179
|
+
)}
|
|
180
|
+
</Flex>
|
|
181
|
+
);
|
|
182
|
+
}
|