@servicetitan/dte-pdf-editor 1.38.0 → 1.40.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 +7 -4
- package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -1
- package/dist/components/field-config-panel/advanced-settings.js +1 -6
- package/dist/components/field-config-panel/advanced-settings.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 +20 -41
- package/dist/components/field-config-panel/formula-modal.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.js +2 -16
- package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
- package/dist/constants/calculated.constants.d.ts +1 -0
- package/dist/constants/calculated.constants.d.ts.map +1 -1
- package/dist/constants/calculated.constants.js +1 -0
- package/dist/constants/calculated.constants.js.map +1 -1
- package/dist/constants/field.constants.d.ts +7 -1
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +20 -0
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/interface/types.d.ts +11 -5
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/formula/evaluate-formula.utils.d.ts +5 -4
- package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -1
- package/dist/utils/formula/evaluate-formula.utils.js +20 -9
- package/dist/utils/formula/evaluate-formula.utils.js.map +1 -1
- package/dist/utils/formula/expression.utils.d.ts +1 -1
- package/dist/utils/formula/expression.utils.d.ts.map +1 -1
- package/dist/utils/formula/expression.utils.js +3 -1
- package/dist/utils/formula/expression.utils.js.map +1 -1
- package/dist/utils/formula/format-calculated-result.utils.d.ts +1 -1
- package/dist/utils/formula/format-calculated-result.utils.d.ts.map +1 -1
- package/dist/utils/formula/format-calculated-result.utils.js +24 -9
- package/dist/utils/formula/format-calculated-result.utils.js.map +1 -1
- package/package.json +1 -1
- package/src/components/field-config-panel/advanced-settings.tsx +4 -10
- package/src/components/field-config-panel/formula-modal.tsx +24 -42
- package/src/components/pdf-view/pdf-view-calculated.tsx +2 -15
- package/src/constants/calculated.constants.ts +2 -0
- package/src/constants/field.constants.ts +28 -0
- package/src/interface/types.ts +13 -6
- package/src/utils/formula/evaluate-formula.utils.ts +21 -10
- package/src/utils/formula/expression.utils.ts +7 -1
- package/src/utils/formula/format-calculated-result.utils.ts +30 -14
|
@@ -14,7 +14,7 @@ export declare function tokenizeExpression(expression: string): ExpressionPart[]
|
|
|
14
14
|
* Parse an expression string into FormulaToken[] using valid paths.
|
|
15
15
|
* Invalid or unknown identifiers are skipped; only known paths become field tokens.
|
|
16
16
|
*/
|
|
17
|
-
export declare function parseExpression(expression: string, validPaths: Set<string>): FormulaToken[];
|
|
17
|
+
export declare function parseExpression(expression: string, validPaths: Set<string>, knownDateFields?: Set<string>): FormulaToken[];
|
|
18
18
|
export interface NormalizeMergeTagsResult {
|
|
19
19
|
normalized: string;
|
|
20
20
|
removed: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAExE;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAoBjF;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,EAAE,CAgBvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,
|
|
1
|
+
{"version":3,"file":"expression.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAExE;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAoBjF;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,EAAE,CAgBvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC9B,YAAY,EAAE,CAsChB;AAED,MAAM,WAAW,wBAAwB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACxB,wBAAwB,CAqC1B"}
|
|
@@ -50,7 +50,7 @@ export function tokenizeExpression(expression) {
|
|
|
50
50
|
* Parse an expression string into FormulaToken[] using valid paths.
|
|
51
51
|
* Invalid or unknown identifiers are skipped; only known paths become field tokens.
|
|
52
52
|
*/
|
|
53
|
-
export function parseExpression(expression, validPaths) {
|
|
53
|
+
export function parseExpression(expression, validPaths, knownDateFields) {
|
|
54
54
|
const parts = tokenizeExpression(expression).filter(p => p.type !== 'text' || p.value.trim() !== '');
|
|
55
55
|
const tokens = [];
|
|
56
56
|
for (const part of parts) {
|
|
@@ -59,9 +59,11 @@ export function parseExpression(expression, validPaths) {
|
|
|
59
59
|
}
|
|
60
60
|
if (part.type === 'field') {
|
|
61
61
|
if (validPaths.has(part.value)) {
|
|
62
|
+
const fieldType = (knownDateFields === null || knownDateFields === void 0 ? void 0 : knownDateFields.has(part.value)) ? 'date' : 'number';
|
|
62
63
|
tokens.push({
|
|
63
64
|
type: 'field',
|
|
64
65
|
path: part.value,
|
|
66
|
+
fieldType,
|
|
65
67
|
});
|
|
66
68
|
}
|
|
67
69
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAsC;;IACrE,IAAI,CAAC,CAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM,0CAAE,MAAM,CAAA,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACd,CAAC;IACD,OAAO,OAAO,CAAC,MAAM;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE;QACL,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,OAAO,CAAC,CAAC,IAAI,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,OAAO,GAAG,CAAC;QACf,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,OAAO,GAAG,CAAC;QACf,CAAC;QACD,OAAO,CAAC,CAAC,KAAK,CAAC;IACnB,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AAChB,CAAC;AAQD,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG,yDAAyD,CAAC;AAEnF,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACjD,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QACxF,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,
|
|
1
|
+
{"version":3,"file":"expression.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@ import { CalculatedFieldFormat } from '../../interface/types';
|
|
|
3
3
|
* Format a calculated formula result for display using advanced settings.
|
|
4
4
|
* Applies rounding, decimal places, thousands/decimal separators, result type (number/currency/percent/date), and prefix/postfix.
|
|
5
5
|
*/
|
|
6
|
-
export declare
|
|
6
|
+
export declare const formatCalculatedResult: (value: number, format: CalculatedFieldFormat | undefined) => string;
|
|
7
7
|
//# sourceMappingURL=format-calculated-result.utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-calculated-result.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/format-calculated-result.utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"format-calculated-result.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/formula/format-calculated-result.utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAA6B,MAAM,uBAAuB,CAAC;AA2EzF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAC/B,OAAO,MAAM,EACb,QAAQ,qBAAqB,GAAG,SAAS,KAC1C,MAiCF,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_DATE_FORMAT } from '../../constants';
|
|
2
|
-
|
|
1
|
+
import { DEFAULT_DATE_FORMAT, MAX_DATE_DISPLAY_RANGE_YEARS } from '../../constants';
|
|
2
|
+
const roundValue = (value, decimals, mode) => {
|
|
3
3
|
const factor = 10 ** decimals;
|
|
4
4
|
const scaled = value * factor;
|
|
5
5
|
switch (mode) {
|
|
@@ -10,8 +10,8 @@ function roundValue(value, decimals, mode) {
|
|
|
10
10
|
default:
|
|
11
11
|
return Math.round(scaled) / factor;
|
|
12
12
|
}
|
|
13
|
-
}
|
|
14
|
-
|
|
13
|
+
};
|
|
14
|
+
const formatIntegerPart = (s, thousandsSeparator, decimalSeparator) => {
|
|
15
15
|
if (!thousandsSeparator) {
|
|
16
16
|
return s;
|
|
17
17
|
}
|
|
@@ -24,7 +24,7 @@ function formatIntegerPart(s, thousandsSeparator, decimalSeparator) {
|
|
|
24
24
|
i = start;
|
|
25
25
|
}
|
|
26
26
|
return parts.join(sep);
|
|
27
|
-
}
|
|
27
|
+
};
|
|
28
28
|
const DATE_FORMAT_TOKENS = {
|
|
29
29
|
YYYY: d => String(d.getFullYear()),
|
|
30
30
|
YY: d => String(d.getFullYear()).slice(-2),
|
|
@@ -36,7 +36,7 @@ const DATE_FORMAT_TOKENS = {
|
|
|
36
36
|
D: d => String(d.getDate()),
|
|
37
37
|
};
|
|
38
38
|
const DATE_TOKEN_PATTERN = new RegExp(Object.keys(DATE_FORMAT_TOKENS).join('|'), 'g');
|
|
39
|
-
|
|
39
|
+
const formatDateValue = (epochMs, dateFormat) => {
|
|
40
40
|
const date = new Date(epochMs);
|
|
41
41
|
if (isNaN(date.getTime())) {
|
|
42
42
|
return '';
|
|
@@ -45,17 +45,32 @@ function formatDateValue(epochMs, dateFormat) {
|
|
|
45
45
|
const fn = DATE_FORMAT_TOKENS[match];
|
|
46
46
|
return fn ? fn(date) : match;
|
|
47
47
|
});
|
|
48
|
-
}
|
|
48
|
+
};
|
|
49
|
+
const isDateWithinDisplayRange = (epochMs, referenceMs = Date.now()) => {
|
|
50
|
+
const date = new Date(epochMs);
|
|
51
|
+
if (isNaN(date.getTime())) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const ref = new Date(referenceMs);
|
|
55
|
+
const min = new Date(ref);
|
|
56
|
+
min.setFullYear(min.getFullYear() - MAX_DATE_DISPLAY_RANGE_YEARS);
|
|
57
|
+
const max = new Date(ref);
|
|
58
|
+
max.setFullYear(max.getFullYear() + MAX_DATE_DISPLAY_RANGE_YEARS);
|
|
59
|
+
return date >= min && date <= max;
|
|
60
|
+
};
|
|
49
61
|
/**
|
|
50
62
|
* Format a calculated formula result for display using advanced settings.
|
|
51
63
|
* Applies rounding, decimal places, thousands/decimal separators, result type (number/currency/percent/date), and prefix/postfix.
|
|
52
64
|
*/
|
|
53
|
-
export
|
|
65
|
+
export const formatCalculatedResult = (value, format) => {
|
|
54
66
|
var _a;
|
|
55
67
|
if (format == null) {
|
|
56
68
|
return String(value);
|
|
57
69
|
}
|
|
58
70
|
if (format.resultType === 'date') {
|
|
71
|
+
if (!isDateWithinDisplayRange(value)) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
59
74
|
return formatDateValue(value, (_a = format.dateFormat) !== null && _a !== void 0 ? _a : DEFAULT_DATE_FORMAT);
|
|
60
75
|
}
|
|
61
76
|
const { decimalSeparator, decimalSeparatorEnabled, decimals, postfixText, prefixText, roundingMode, thousandsSeparator, } = format;
|
|
@@ -67,5 +82,5 @@ export function formatCalculatedResult(value, format) {
|
|
|
67
82
|
const decSuffix = decimals > 0 ? decimalSep + (decPart !== null && decPart !== void 0 ? decPart : '') : '';
|
|
68
83
|
const numberStr = intFormatted + decSuffix;
|
|
69
84
|
return `${prefixText}${numberStr}${postfixText}`;
|
|
70
|
-
}
|
|
85
|
+
};
|
|
71
86
|
//# sourceMappingURL=format-calculated-result.utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-calculated-result.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/format-calculated-result.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"format-calculated-result.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/format-calculated-result.utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { Flex, SegmentedControl, Switch, Text } from '@servicetitan/anvil2';
|
|
2
2
|
import { Dispatch, FC, SetStateAction } from 'react';
|
|
3
|
-
import { DEFAULT_DATE_FORMAT } from '../../constants';
|
|
4
|
-
import { CalculatedFieldFormat } from '../../interface/types';
|
|
3
|
+
import { DEFAULT_DATE_FORMAT, ROUNDING_OPTIONS } from '../../constants';
|
|
4
|
+
import { CalculatedFieldFormat, CalculatedFormatForNumber } from '../../interface/types';
|
|
5
5
|
|
|
6
6
|
interface AdvancedSettingsProps {
|
|
7
7
|
format: CalculatedFieldFormat;
|
|
8
8
|
setFormat: Dispatch<SetStateAction<CalculatedFieldFormat>>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const ROUNDING_OPTIONS: { label: string; value: CalculatedFieldFormat['roundingMode'] }[] = [
|
|
12
|
-
{ label: 'Round', value: 'round' },
|
|
13
|
-
{ label: 'Floor', value: 'floor' },
|
|
14
|
-
{ label: 'Ceil', value: 'ceil' },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
11
|
const DateFormatSettings: FC<AdvancedSettingsProps> = ({ format, setFormat }) => (
|
|
18
12
|
<div className="dte-formula-advanced">
|
|
19
13
|
<Text variant="body" size="small">
|
|
@@ -46,7 +40,7 @@ export const AdvancedSettings: FC<AdvancedSettingsProps> = ({ format, setFormat
|
|
|
46
40
|
</Text>
|
|
47
41
|
<SegmentedControl
|
|
48
42
|
selected={format.roundingMode}
|
|
49
|
-
onChange={(roundingMode:
|
|
43
|
+
onChange={(roundingMode: CalculatedFormatForNumber['roundingMode']) =>
|
|
50
44
|
setFormat(prev => ({ ...prev, roundingMode }))
|
|
51
45
|
}
|
|
52
46
|
fill
|
|
@@ -88,7 +82,7 @@ export const AdvancedSettings: FC<AdvancedSettingsProps> = ({ format, setFormat
|
|
|
88
82
|
size="small"
|
|
89
83
|
fill
|
|
90
84
|
selected={format.decimalSeparator}
|
|
91
|
-
onChange={(decimalSeparator:
|
|
85
|
+
onChange={(decimalSeparator: CalculatedFormatForNumber['decimalSeparator']) =>
|
|
92
86
|
setFormat(prev => ({ ...prev, decimalSeparator }))
|
|
93
87
|
}
|
|
94
88
|
>
|
|
@@ -2,7 +2,11 @@ import { Button, Dialog, Divider, Flex } from '@servicetitan/anvil2';
|
|
|
2
2
|
import IconRedo from '@servicetitan/anvil2/assets/icons/material/round/redo.svg';
|
|
3
3
|
import IconUndo from '@servicetitan/anvil2/assets/icons/material/round/undo.svg';
|
|
4
4
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_DATE_CALCULATED_FORMAT,
|
|
7
|
+
DEFAULT_NUMBER_FORMAT,
|
|
8
|
+
MODAL_CONTENT_MAX_HEIGHT,
|
|
9
|
+
} from '../../constants';
|
|
6
10
|
import { useFormulaEditor } from '../../hooks';
|
|
7
11
|
import {
|
|
8
12
|
CalculatedFieldFormat,
|
|
@@ -19,17 +23,6 @@ import {
|
|
|
19
23
|
import { FieldSidebar } from './field-sidebar';
|
|
20
24
|
import { FormulaWorkspace } from './formula-workspace';
|
|
21
25
|
|
|
22
|
-
const DEFAULT_CALCULATED_FIELD_FORMAT: CalculatedFieldFormat = {
|
|
23
|
-
resultType: 'number',
|
|
24
|
-
thousandsSeparator: false,
|
|
25
|
-
decimals: 2,
|
|
26
|
-
roundingMode: 'round',
|
|
27
|
-
decimalSeparatorEnabled: false,
|
|
28
|
-
decimalSeparator: '.',
|
|
29
|
-
prefixText: '',
|
|
30
|
-
postfixText: '',
|
|
31
|
-
};
|
|
32
|
-
|
|
33
26
|
function getAllFields(groups: DataModelFieldGroup[]): FieldTypeOption[] {
|
|
34
27
|
const list: FieldTypeOption[] = [];
|
|
35
28
|
for (const g of groups) {
|
|
@@ -111,7 +104,7 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
111
104
|
|
|
112
105
|
const [highlightElementPath, setHighlightElementPath] = useState('');
|
|
113
106
|
const [format, setFormat] = useState<CalculatedFieldFormat>(
|
|
114
|
-
() => initialFormat ??
|
|
107
|
+
() => initialFormat ?? DEFAULT_NUMBER_FORMAT,
|
|
115
108
|
);
|
|
116
109
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
117
110
|
|
|
@@ -124,23 +117,15 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
124
117
|
|
|
125
118
|
useEffect(() => {
|
|
126
119
|
if (formulaEditor.expressionHasDateFields && format.resultType !== 'date') {
|
|
127
|
-
setFormat(
|
|
128
|
-
...prev,
|
|
129
|
-
resultType: 'date',
|
|
130
|
-
dateFormat: prev.dateFormat ?? DEFAULT_DATE_FORMAT,
|
|
131
|
-
}));
|
|
132
|
-
setAdvancedOpen(true);
|
|
120
|
+
setFormat(DEFAULT_DATE_CALCULATED_FORMAT);
|
|
133
121
|
} else if (!formulaEditor.expressionHasDateFields && format.resultType === 'date') {
|
|
134
|
-
setFormat(
|
|
135
|
-
...prev,
|
|
136
|
-
resultType: 'number',
|
|
137
|
-
}));
|
|
122
|
+
setFormat(DEFAULT_NUMBER_FORMAT);
|
|
138
123
|
}
|
|
139
124
|
}, [formulaEditor.expressionHasDateFields, format.resultType]);
|
|
140
125
|
|
|
141
126
|
const parsedTokens = useMemo(
|
|
142
|
-
() => parseExpression(formulaEditor.draftExpression, validPaths),
|
|
143
|
-
[formulaEditor.draftExpression, validPaths],
|
|
127
|
+
() => parseExpression(formulaEditor.draftExpression, validPaths, knownDateFields),
|
|
128
|
+
[formulaEditor.draftExpression, validPaths, knownDateFields],
|
|
144
129
|
);
|
|
145
130
|
|
|
146
131
|
const formulaValidation = useMemo(
|
|
@@ -164,16 +149,9 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
164
149
|
}, [onClose]);
|
|
165
150
|
|
|
166
151
|
const handleSave = useCallback(() => {
|
|
167
|
-
|
|
168
|
-
.filter(t => t.type === 'field' && knownDateFields.has(t.path))
|
|
169
|
-
.map(t => (t as { type: 'field'; path: string }).path);
|
|
170
|
-
const savedFormat: CalculatedFieldFormat = {
|
|
171
|
-
...format,
|
|
172
|
-
dateFieldPaths: usedDatePaths.length > 0 ? [...new Set(usedDatePaths)] : undefined,
|
|
173
|
-
};
|
|
174
|
-
onSave({ tokens: parsedTokens }, savedFormat);
|
|
152
|
+
onSave({ tokens: parsedTokens }, format);
|
|
175
153
|
onClose();
|
|
176
|
-
}, [format,
|
|
154
|
+
}, [format, onClose, onSave, parsedTokens]);
|
|
177
155
|
|
|
178
156
|
const validationError = unknownFieldErrors[0] ?? formulaValidation.errors[0] ?? '';
|
|
179
157
|
const isInvalid = !formulaValidation.valid || unknownFieldErrors.length > 0;
|
|
@@ -208,14 +186,18 @@ export const FormulaModal: FC<FormulaModalProps> = ({
|
|
|
208
186
|
isInvalid={isInvalid && formulaEditor.isDirty}
|
|
209
187
|
validationError={validationError}
|
|
210
188
|
format={format}
|
|
211
|
-
onResultTypeChange={nextType =>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
189
|
+
onResultTypeChange={nextType => {
|
|
190
|
+
if (nextType === 'date') {
|
|
191
|
+
setFormat(DEFAULT_DATE_CALCULATED_FORMAT);
|
|
192
|
+
} else {
|
|
193
|
+
setFormat({
|
|
194
|
+
...DEFAULT_NUMBER_FORMAT,
|
|
195
|
+
resultType: nextType,
|
|
196
|
+
prefixText: nextType === 'currency' ? '$' : '',
|
|
197
|
+
postfixText: nextType === 'percent' ? '%' : '',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}}
|
|
219
201
|
advancedOpen={advancedOpen}
|
|
220
202
|
onToggleAdvanced={() => setAdvancedOpen(prev => !prev)}
|
|
221
203
|
setFormat={setFormat}
|
|
@@ -9,14 +9,9 @@ interface PdfViewCalculatedProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const PdfViewCalculated: FC<PdfViewCalculatedProps> = ({ data, field, holidays }) => {
|
|
12
|
-
const dateFieldPaths = useMemo(() => {
|
|
13
|
-
const paths = field.formulaFormat?.dateFieldPaths;
|
|
14
|
-
return paths?.length ? new Set(paths) : undefined;
|
|
15
|
-
}, [field.formulaFormat?.dateFieldPaths]);
|
|
16
|
-
|
|
17
12
|
const displayValue = useMemo(() => {
|
|
18
13
|
if (field.formula?.tokens?.length) {
|
|
19
|
-
const value = evaluateFormula(field.formula, data,
|
|
14
|
+
const value = evaluateFormula(field.formula, data, holidays);
|
|
20
15
|
if (value === null) {
|
|
21
16
|
return '';
|
|
22
17
|
}
|
|
@@ -26,15 +21,7 @@ export const PdfViewCalculated: FC<PdfViewCalculatedProps> = ({ data, field, hol
|
|
|
26
21
|
return resolvePdfDataValues(data, field.path) ?? '';
|
|
27
22
|
}
|
|
28
23
|
return field.label ?? '';
|
|
29
|
-
}, [
|
|
30
|
-
data,
|
|
31
|
-
dateFieldPaths,
|
|
32
|
-
field.formula,
|
|
33
|
-
field.formulaFormat,
|
|
34
|
-
field.label,
|
|
35
|
-
field.path,
|
|
36
|
-
holidays,
|
|
37
|
-
]);
|
|
24
|
+
}, [data, field.formula, field.formulaFormat, field.label, field.path, holidays]);
|
|
38
25
|
|
|
39
26
|
return <div className="dte-pdf-field-value">{displayValue}</div>;
|
|
40
27
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CalculatedFormatForDate,
|
|
3
|
+
CalculatedFormatForNumber,
|
|
2
4
|
ESignFieldType,
|
|
3
5
|
FieldTypeEnum,
|
|
4
6
|
FieldTypeOption,
|
|
5
7
|
PdfFieldSubType,
|
|
6
8
|
} from '../interface/types';
|
|
9
|
+
import { DEFAULT_DATE_FORMAT } from './calculated.constants';
|
|
7
10
|
import { BASE_PAGE_WIDTH } from './pdf-editor.constants';
|
|
8
11
|
|
|
9
12
|
export const FIELD_CONSTANTS = {
|
|
@@ -82,3 +85,28 @@ export const DISABLE_FIELD_RESIZE = {
|
|
|
82
85
|
FieldTypeEnum,
|
|
83
86
|
Partial<Record<PdfFieldSubType, { disableHorizontal?: boolean; disableVertical?: boolean }>>
|
|
84
87
|
>;
|
|
88
|
+
|
|
89
|
+
export const ROUNDING_OPTIONS = [
|
|
90
|
+
{ label: 'Round', value: 'round' },
|
|
91
|
+
{ label: 'Floor', value: 'floor' },
|
|
92
|
+
{ label: 'Ceil', value: 'ceil' },
|
|
93
|
+
] as {
|
|
94
|
+
label: string;
|
|
95
|
+
value: CalculatedFormatForNumber['roundingMode'];
|
|
96
|
+
}[];
|
|
97
|
+
|
|
98
|
+
export const DEFAULT_NUMBER_FORMAT: CalculatedFormatForNumber = {
|
|
99
|
+
resultType: 'number',
|
|
100
|
+
thousandsSeparator: false,
|
|
101
|
+
decimals: 2,
|
|
102
|
+
roundingMode: 'round',
|
|
103
|
+
decimalSeparatorEnabled: false,
|
|
104
|
+
decimalSeparator: '.',
|
|
105
|
+
prefixText: '',
|
|
106
|
+
postfixText: '',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const DEFAULT_DATE_CALCULATED_FORMAT: CalculatedFormatForDate = {
|
|
110
|
+
resultType: 'date',
|
|
111
|
+
dateFormat: DEFAULT_DATE_FORMAT,
|
|
112
|
+
};
|
package/src/interface/types.ts
CHANGED
|
@@ -105,16 +105,16 @@ export type FormulaToken =
|
|
|
105
105
|
| { type: 'operator'; value: FormulaOperator }
|
|
106
106
|
| { type: 'lparen' }
|
|
107
107
|
| { type: 'rparen' }
|
|
108
|
-
| { type: 'field'; path: string };
|
|
108
|
+
| { type: 'field'; path: string; fieldType: SchemaFieldType };
|
|
109
109
|
|
|
110
110
|
/** Structured formula representation (AST-like token list) for validation and safe editing */
|
|
111
111
|
export interface StructuredFormula {
|
|
112
112
|
tokens: FormulaToken[];
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/** Format options for a calculated field result (
|
|
116
|
-
export interface
|
|
117
|
-
resultType: 'number' | 'currency' | 'percent'
|
|
115
|
+
/** Format options for a calculated field with a numeric result (number, currency, percent) */
|
|
116
|
+
export interface CalculatedFormatForNumber {
|
|
117
|
+
resultType: 'number' | 'currency' | 'percent';
|
|
118
118
|
thousandsSeparator: boolean;
|
|
119
119
|
decimals: number;
|
|
120
120
|
roundingMode: 'round' | 'floor' | 'ceil';
|
|
@@ -123,10 +123,17 @@ export interface CalculatedFieldFormat {
|
|
|
123
123
|
prefixText: string;
|
|
124
124
|
postfixText: string;
|
|
125
125
|
dateFormat?: string;
|
|
126
|
-
/** Paths of date-typed fields used in the formula, persisted for date-aware evaluation */
|
|
127
|
-
dateFieldPaths?: string[];
|
|
128
126
|
}
|
|
129
127
|
|
|
128
|
+
/** Format options for a calculated field with a date result */
|
|
129
|
+
export interface CalculatedFormatForDate {
|
|
130
|
+
resultType: 'date';
|
|
131
|
+
dateFormat: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Format options for a calculated field result (display and rounding) */
|
|
135
|
+
export type CalculatedFieldFormat = CalculatedFormatForDate | CalculatedFormatForNumber;
|
|
136
|
+
|
|
130
137
|
export interface SchemaFieldBaseOptions {
|
|
131
138
|
placeholder?: any;
|
|
132
139
|
description?: any;
|
|
@@ -28,10 +28,20 @@ export function valueToNumber(raw: unknown): number {
|
|
|
28
28
|
return Number.isNaN(n) ? 0 : n;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function collectDateFieldPaths(formula: StructuredFormula): Set<string> {
|
|
32
|
+
const set = new Set<string>();
|
|
33
|
+
for (const t of formula.tokens) {
|
|
34
|
+
if (t.type === 'field' && t.fieldType === 'date') {
|
|
35
|
+
set.add(t.path);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return set;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
function hasAllFieldValues(
|
|
32
42
|
formula: StructuredFormula,
|
|
33
43
|
data: DataModelValues | undefined,
|
|
34
|
-
dateFieldPaths
|
|
44
|
+
dateFieldPaths: Set<string>,
|
|
35
45
|
): boolean {
|
|
36
46
|
return formula.tokens.every((t: FormulaToken) => {
|
|
37
47
|
if (t.type !== 'field') {
|
|
@@ -41,7 +51,7 @@ function hasAllFieldValues(
|
|
|
41
51
|
if (raw === '') {
|
|
42
52
|
return false;
|
|
43
53
|
}
|
|
44
|
-
if (dateFieldPaths
|
|
54
|
+
if (dateFieldPaths.has(t.path)) {
|
|
45
55
|
return !isNaN(new Date(raw).getTime());
|
|
46
56
|
}
|
|
47
57
|
return !isNaN(Number(raw.trim()));
|
|
@@ -282,12 +292,11 @@ function businessDayDiff(fromMs: number, toMs: number, holidaySet: Set<string>):
|
|
|
282
292
|
function resolveDateAwareTokens(
|
|
283
293
|
formula: StructuredFormula,
|
|
284
294
|
data: DataModelValues | undefined,
|
|
285
|
-
dateFieldPaths: Set<string>,
|
|
286
295
|
): DateAwareToken[] {
|
|
287
296
|
return formula.tokens.map((t: FormulaToken): DateAwareToken => {
|
|
288
297
|
if (t.type === 'field') {
|
|
289
298
|
const raw = resolvePdfDataValues(data, t.path);
|
|
290
|
-
if (
|
|
299
|
+
if (t.fieldType === 'date') {
|
|
291
300
|
const ms = parseDateValue(raw);
|
|
292
301
|
return { kind: 'date', value: isNaN(ms) ? 0 : ms };
|
|
293
302
|
}
|
|
@@ -395,26 +404,28 @@ function evalDateAwareTokens(tokens: DateAwareToken[], holidaySet: Set<string>):
|
|
|
395
404
|
* to null / undefined / empty / non-numeric (non-date) — making the entire
|
|
396
405
|
* calculation blank instead of silently substituting 0.
|
|
397
406
|
*
|
|
398
|
-
*
|
|
399
|
-
* arithmetic follows business-day semantics — weekends and
|
|
400
|
-
* when adding/subtracting days, and date diffs count only
|
|
407
|
+
* Date fields are identified by `fieldType: 'date'` on field tokens. When date
|
|
408
|
+
* fields are present, arithmetic follows business-day semantics — weekends and
|
|
409
|
+
* holidays are skipped when adding/subtracting days, and date diffs count only
|
|
410
|
+
* business days.
|
|
401
411
|
*/
|
|
402
412
|
export function evaluateFormula(
|
|
403
413
|
formula: StructuredFormula | undefined | null,
|
|
404
414
|
data: DataModelValues | undefined,
|
|
405
|
-
dateFieldPaths?: Set<string>,
|
|
406
415
|
holidays?: string[],
|
|
407
416
|
): number | null {
|
|
408
417
|
if (!formula?.tokens?.length) {
|
|
409
418
|
return null;
|
|
410
419
|
}
|
|
411
420
|
|
|
421
|
+
const dateFieldPaths = collectDateFieldPaths(formula);
|
|
422
|
+
|
|
412
423
|
if (!hasAllFieldValues(formula, data, dateFieldPaths)) {
|
|
413
424
|
return null;
|
|
414
425
|
}
|
|
415
426
|
|
|
416
|
-
if (dateFieldPaths
|
|
417
|
-
const resolved = resolveDateAwareTokens(formula, data
|
|
427
|
+
if (dateFieldPaths.size) {
|
|
428
|
+
const resolved = resolveDateAwareTokens(formula, data);
|
|
418
429
|
const holidaySet = buildHolidaySet(holidays);
|
|
419
430
|
const result = evalDateAwareTokens(resolved, holidaySet);
|
|
420
431
|
return result.value;
|
|
@@ -57,7 +57,11 @@ export function tokenizeExpression(expression: string): ExpressionPart[] {
|
|
|
57
57
|
* Parse an expression string into FormulaToken[] using valid paths.
|
|
58
58
|
* Invalid or unknown identifiers are skipped; only known paths become field tokens.
|
|
59
59
|
*/
|
|
60
|
-
export function parseExpression(
|
|
60
|
+
export function parseExpression(
|
|
61
|
+
expression: string,
|
|
62
|
+
validPaths: Set<string>,
|
|
63
|
+
knownDateFields?: Set<string>,
|
|
64
|
+
): FormulaToken[] {
|
|
61
65
|
const parts = tokenizeExpression(expression).filter(
|
|
62
66
|
p => p.type !== 'text' || p.value.trim() !== '',
|
|
63
67
|
);
|
|
@@ -69,9 +73,11 @@ export function parseExpression(expression: string, validPaths: Set<string>): Fo
|
|
|
69
73
|
}
|
|
70
74
|
if (part.type === 'field') {
|
|
71
75
|
if (validPaths.has(part.value)) {
|
|
76
|
+
const fieldType = knownDateFields?.has(part.value) ? 'date' : 'number';
|
|
72
77
|
tokens.push({
|
|
73
78
|
type: 'field',
|
|
74
79
|
path: part.value,
|
|
80
|
+
fieldType,
|
|
75
81
|
});
|
|
76
82
|
}
|
|
77
83
|
continue;
|