@sd-angular/core 19.0.0-beta.94 → 19.0.0-beta.96
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 +10 -10
- package/components/anchor/src/components/anchor/anchor.component.d.ts +4 -1
- package/components/anchor/src/components/anchor-item/anchor-item.component.d.ts +2 -1
- package/components/anchor/src/components/anchor-vertical/anchor-vertical-list.component.d.ts +3 -1
- package/components/badge/src/badge.component.d.ts +3 -4
- package/components/button/src/button.component.d.ts +2 -2
- package/components/document-builder/src/components/header-footer-builder/header-footer-builder.component.d.ts +2 -2
- package/components/document-builder/src/document-builder.model.d.ts +2 -2
- package/components/editor/src/models/editor.model.d.ts +2 -2
- package/components/editor/src/plugins/image-upload/utils/validate.utils.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/attribute-table/attribute-table.component.d.ts +4 -2
- package/components/form-generic/src/components/form-builder/components/attribute-template/attribute-template.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/configure-validation/configure-validation.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/group/attribute/group-attribute.component.d.ts +23 -0
- package/components/form-generic/src/components/form-builder/components/group/index.d.ts +1 -0
- package/components/form-generic/src/components/form-builder/components/index.d.ts +1 -0
- package/components/form-generic/src/components/form-builder/components/table/control/table-control.component.d.ts +3 -0
- package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +61 -18
- package/components/form-generic/src/components/form-render/form-render.component.d.ts +2 -2
- package/components/form-generic/src/models/form-generic-component.model.d.ts +22 -2
- package/components/index.d.ts +1 -2
- package/components/modal/src/modal.component.d.ts +5 -3
- package/components/preview/index.d.ts +3 -0
- package/components/preview/src/preview-image/preview-image.component.d.ts +84 -24
- package/components/preview/src/preview-image/preview-image.types.d.ts +53 -0
- package/components/preview/src/preview-pdf/preview-pdf.component.d.ts +186 -0
- package/components/preview/src/preview-pdf/preview-pdf.types.d.ts +84 -0
- package/components/quick-action/src/quick-action.component.d.ts +5 -6
- package/components/section/src/section.component.d.ts +1 -2
- package/components/table/src/components/column-filter/column-filter.component.d.ts +3 -3
- package/components/table/src/table.component.d.ts +2 -2
- package/components/upload-file/src/upload-file.component.d.ts +4 -1
- package/configurations/src/sd-core.configuration.d.ts +2 -1
- package/fesm2022/sd-angular-core-components-anchor.mjs +19 -6
- package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-button.mjs +1 -1
- package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-code-editor.mjs +3 -3
- package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-document-builder.mjs +3 -3
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-editor.mjs +3 -3
- package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-form-generic.mjs +627 -317
- package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-history.mjs +3 -3
- package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-import-excel.mjs +5 -5
- package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-modal.mjs +8 -3
- package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-preview.mjs +1963 -91
- package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-quick-action.mjs +8 -19
- package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-tab-router.mjs +31 -26
- package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +32 -29
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs +12 -8
- package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components.mjs +1 -2
- package/fesm2022/sd-angular-core-components.mjs.map +1 -1
- package/fesm2022/sd-angular-core-configurations.mjs.map +1 -1
- package/fesm2022/sd-angular-core-directives.mjs +2 -2
- package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs +2 -2
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-checkbox.mjs +6 -13
- package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +6 -5
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip.mjs +6 -12
- package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date-range.mjs +33 -26
- package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs +29 -19
- package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs +98 -57
- package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs +2 -2
- package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs +25 -10
- package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-radio.mjs +7 -15
- package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +4 -4
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-switch.mjs +6 -14
- package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-textarea.mjs +5 -5
- package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
- package/fesm2022/sd-angular-core-handlers.mjs +2 -2
- package/fesm2022/sd-angular-core-handlers.mjs.map +1 -1
- package/fesm2022/sd-angular-core-i18n.mjs +1610 -38
- package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
- package/fesm2022/sd-angular-core-interceptors.mjs +2 -2
- package/fesm2022/sd-angular-core-interceptors.mjs.map +1 -1
- package/fesm2022/sd-angular-core-models.mjs +6 -0
- package/fesm2022/sd-angular-core-models.mjs.map +1 -0
- package/fesm2022/sd-angular-core-modules-layout.mjs +87 -39
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs +2 -2
- package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-docx.mjs +2 -2
- package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-excel.mjs +27 -23
- package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-notify.mjs +21 -21
- package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-extensions.mjs +74 -830
- package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-models.mjs +36 -136
- package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
- package/fesm2022/sd-angular-core.mjs +0 -1
- package/fesm2022/sd-angular-core.mjs.map +1 -1
- package/forms/autocomplete/src/autocomplete.component.d.ts +1 -2
- package/forms/checkbox/src/checkbox.component.d.ts +3 -3
- package/forms/chip/src/chip.component.d.ts +4 -4
- package/forms/chip-calendar/src/chip-calendar.component.d.ts +4 -4
- package/forms/date/src/date.component.d.ts +3 -5
- package/forms/date-range/src/date-range.component.d.ts +3 -5
- package/forms/datetime/src/datetime.component.d.ts +2 -2
- package/forms/datetime/src/popup/sd-datetime-picker.component.d.ts +10 -11
- package/forms/input/src/input.component.d.ts +1 -2
- package/forms/input-number/src/input-number.component.d.ts +1 -2
- package/forms/radio/src/radio.component.d.ts +3 -3
- package/forms/select/src/select.component.d.ts +5 -5
- package/forms/switch/src/switch.component.d.ts +3 -3
- package/forms/textarea/src/textarea.component.d.ts +1 -2
- package/i18n/index.d.ts +5 -5
- package/i18n/src/en.d.ts +406 -2
- package/i18n/src/i18n.messages.d.ts +2 -0
- package/i18n/src/i18n.pipe.d.ts +9 -0
- package/i18n/src/i18n.service.d.ts +14 -0
- package/i18n/src/i18n.token.d.ts +1 -0
- package/i18n/src/i18n.types.d.ts +31 -0
- package/i18n/src/ja.d.ts +2 -0
- package/i18n/src/ko.d.ts +2 -0
- package/i18n/src/vi.d.ts +2 -312
- package/i18n/src/zh.d.ts +2 -0
- package/models/index.d.ts +1 -0
- package/models/src/language.model.d.ts +2 -0
- package/modules/layout/modules/home/components/home-page/home-page.component.d.ts +25 -0
- package/package.json +82 -89
- package/public-api.d.ts +0 -1
- package/sd-angular-core-19.0.0-beta.96.tgz +0 -0
- package/services/notify/index.d.ts +1 -1
- package/services/notify/src/components/toast/toast.component.d.ts +3 -3
- package/services/notify/src/components/toast-container.component.d.ts +3 -4
- package/services/notify/src/notify.model.d.ts +1 -1
- package/services/notify/src/notify.service.d.ts +5 -5
- package/utilities/extensions/src/array.extension.d.ts +1 -7
- package/utilities/extensions/src/color.extension.d.ts +5 -20
- package/utilities/extensions/src/date.extension.d.ts +1 -18
- package/utilities/extensions/src/number.extension.d.ts +1 -10
- package/utilities/extensions/src/string.extension.d.ts +30 -4
- package/utilities/extensions/src/utility.extension.d.ts +6 -0
- package/utilities/models/src/color.model.d.ts +4 -1
- package/utilities/models/src/empty.model.d.ts +3 -0
- package/utilities/models/src/filter.model.d.ts +12 -24
- package/utilities/models/src/maybe-async.model.d.ts +10 -4
- package/utilities/models/src/nested-key-of.model.d.ts +4 -5
- package/utilities/models/src/operator.model.d.ts +9 -4
- package/utilities/models/src/order.model.d.ts +4 -5
- package/utilities/models/src/paging.model.d.ts +8 -16
- package/utilities/models/src/pattern.model.d.ts +13 -0
- package/utilities/models/src/size.model.d.ts +4 -1
- package/components/anchor-v2/index.d.ts +0 -1
- package/components/anchor-v2/src/components/anchor-item-v2/anchor-item-v2.component.d.ts +0 -12
- package/components/anchor-v2/src/components/anchor-v2/anchor-v2.component.d.ts +0 -19
- package/components/anchor-v2/src/components/anchor-vertical-v2/anchor-vertical-list-v2.component.d.ts +0 -16
- package/components/anchor-v2/src/components/index.d.ts +0 -2
- package/components/anchor-v2/src/index.d.ts +0 -2
- package/components/anchor-v2/src/models/index.d.ts +0 -1
- package/components/anchor-v2/src/models/sd-anchor-v2.model.d.ts +0 -7
- package/components/workflow/index.d.ts +0 -2
- package/components/workflow/src/configurations/index.d.ts +0 -1
- package/components/workflow/src/configurations/workflow.configuration.d.ts +0 -10
- package/fesm2022/sd-angular-core-components-anchor-v2.mjs +0 -151
- package/fesm2022/sd-angular-core-components-anchor-v2.mjs.map +0 -1
- package/fesm2022/sd-angular-core-components-workflow.mjs +0 -17
- package/fesm2022/sd-angular-core-components-workflow.mjs.map +0 -1
- package/fesm2022/sd-angular-core-services-firebase.mjs +0 -38
- package/fesm2022/sd-angular-core-services-firebase.mjs.map +0 -1
- package/i18n/src/sd-i18n.messages.d.ts +0 -2
- package/i18n/src/sd-i18n.pipe.d.ts +0 -9
- package/i18n/src/sd-i18n.service.d.ts +0 -12
- package/i18n/src/sd-i18n.token.d.ts +0 -1
- package/i18n/src/sd-i18n.types.d.ts +0 -5
- package/sd-angular-core-19.0.0-beta.94.tgz +0 -0
- package/services/firebase/index.d.ts +0 -2
- package/services/firebase/src/firebase.model.d.ts +0 -8
- package/services/firebase/src/firebase.service.d.ts +0 -14
|
@@ -1,764 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
import { ValidationUtilities, StringUtilities as StringUtilities$1, ColorUtilities } from '@sdcorejs/utils/fns';
|
|
2
|
+
export { ArrayUtilities, BrowserUtilities, ColorUtilities, DateUtilities, NumberUtilities, Utilities, ValidationUtilities } from '@sdcorejs/utils/fns';
|
|
3
|
+
|
|
2
4
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
// isValidCode(value: any): boolean;
|
|
8
|
-
// changeAliasLowerCase(alias: any): string;
|
|
9
|
-
// aliasIncludes(alias: any, searchText: any): boolean;
|
|
10
|
-
// format(template: string, ...arr: any[]): string;
|
|
11
|
-
// templateToDisplay(template: string, entity: any): string; // Convert theo format ${key}
|
|
12
|
-
// encrypt(obj: any): string;
|
|
13
|
-
// decrypt(encripted: string): any;
|
|
14
|
-
// isNullOrEmpty(value: any): boolean;
|
|
15
|
-
// isNullOrWhiteSpace(value: any): boolean;
|
|
16
|
-
// }
|
|
17
|
-
// }
|
|
18
|
-
const REGEX_EMAIL = '^(([^<>()[\\].,;:\\s@"]+(\\.[^<>()[\\].,;:\\s@"]+)*)|(".+"))@(([^<>()[\\].,;:\\s@"]+\\.)+[^<>()[\\].,;:\\s@"]{2,})$';
|
|
19
|
-
const REGEX_PHONE = '^[+]*[(]{0,1}[+]?[0-9]{1,4}[)]{0,1}[-\\s./0-9]*$';
|
|
20
|
-
const REGEX_PHONE_VN = '^(?:\\+84|0|84)(3[2-9]|5[2689]|7[06-9]|8[1-689]|9[0-9])\\d{7}$';
|
|
21
|
-
const REGEX_IDVN = '^\\d{12}$';
|
|
22
|
-
const REGEX_PASSPORT = '^[A-Z]\\d{7}$';
|
|
23
|
-
const REGEX_IDVN_OR_PASSPORT = '^(\\d{12}|[A-Z]\\d{7})$';
|
|
24
|
-
const REGEX_TIME = '^(?:[01]\\d|2[0-3]):[0-5]\\d$';
|
|
25
|
-
const isValidEmail = (value) => {
|
|
26
|
-
if (!value) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
const re = new RegExp(REGEX_EMAIL);
|
|
30
|
-
return re.test(value);
|
|
31
|
-
};
|
|
32
|
-
const isValidPhone = (value) => {
|
|
33
|
-
if (!value) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const re = new RegExp(REGEX_PHONE);
|
|
37
|
-
return re.test(value);
|
|
38
|
-
};
|
|
39
|
-
const isValidCode = (value) => {
|
|
40
|
-
if (!value) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const re = /^[a-zA-Z0-9\@\_\-]{2,20}$/;
|
|
44
|
-
return re.test(value);
|
|
45
|
-
};
|
|
46
|
-
const isNullOrEmpty = (value) => {
|
|
47
|
-
return value === undefined || value === null || value === '';
|
|
48
|
-
};
|
|
49
|
-
const isNullOrWhiteSpace = (value) => {
|
|
50
|
-
return value === undefined || value === null || typeof value !== 'string' || value.match(/^\s*$/) !== null;
|
|
51
|
-
};
|
|
52
|
-
const changeAliasLowerCase$1 = (alias) => {
|
|
53
|
-
let str = alias?.toString() ?? '';
|
|
54
|
-
str = str.toString().toLowerCase();
|
|
55
|
-
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
|
|
56
|
-
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e');
|
|
57
|
-
str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
|
|
58
|
-
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
|
|
59
|
-
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
|
|
60
|
-
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
|
|
61
|
-
str = str.replace(/đ/g, 'd');
|
|
62
|
-
str = str.replace(/!|@|%|\^|\*|\(|\)|\+|\=|\<|\>|\?|\/|,|\.|\:|\;|\'|\"|\&|\#|\[|\]|~|\$|_|`|-|{|}|\||\\/g, ' ');
|
|
63
|
-
str = str.replace(/ + /g, ' ');
|
|
64
|
-
str = str.trim();
|
|
65
|
-
return str;
|
|
66
|
-
};
|
|
67
|
-
const aliasIncludes = (alias, searchText) => {
|
|
68
|
-
return changeAliasLowerCase$1(alias).includes(changeAliasLowerCase$1(searchText));
|
|
69
|
-
};
|
|
70
|
-
const format = (template, ...arr) => {
|
|
71
|
-
for (let i = 0; i < arr.length; i++) {
|
|
72
|
-
const regexp = new RegExp(`\\{${i}\\}`, 'gi');
|
|
73
|
-
template = template.replace(regexp, arr[i]);
|
|
74
|
-
}
|
|
75
|
-
return template;
|
|
76
|
-
};
|
|
77
|
-
const getValueByPath = (entity, key) => {
|
|
78
|
-
let value = entity;
|
|
79
|
-
for (const part of key.split('.')) {
|
|
80
|
-
value = value?.[part];
|
|
81
|
-
}
|
|
82
|
-
return value;
|
|
83
|
-
};
|
|
84
|
-
// Convert: `[${code}] ${name}` => `[Mã] Tên`;
|
|
85
|
-
const templateToDisplay = (template, entity) => {
|
|
86
|
-
if (!template) {
|
|
87
|
-
return template;
|
|
88
|
-
}
|
|
89
|
-
const regex = /\$\{([A-Za-z0-9._-]*)\}/g;
|
|
90
|
-
const matches = template.match(regex) || [];
|
|
91
|
-
for (const match of matches) {
|
|
92
|
-
const key = match.slice(2, match.length - 1);
|
|
93
|
-
if (key) {
|
|
94
|
-
template = template.replace(match, getValueByPath(entity, key) ?? '');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return template;
|
|
98
|
-
};
|
|
99
|
-
const EXACT_TEMPLATE_REGEX = /^\$\{([A-Za-z0-9._-]*)\}$/;
|
|
100
|
-
const NUMBER_LITERAL_REGEX = /^-?\d+(\.\d+)?$/;
|
|
101
|
-
// Convert placeholder/literal an toàn, không thực thi JavaScript động.
|
|
102
|
-
const parseExpression = (template, entity) => {
|
|
103
|
-
if (!template) {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
const trimmedTemplate = template.trim();
|
|
107
|
-
const exactTemplateMatch = trimmedTemplate.match(EXACT_TEMPLATE_REGEX);
|
|
108
|
-
if (exactTemplateMatch?.[1]) {
|
|
109
|
-
return getValueByPath(entity, exactTemplateMatch[1]);
|
|
110
|
-
}
|
|
111
|
-
if (trimmedTemplate === 'true') {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
if (trimmedTemplate === 'false') {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (trimmedTemplate === 'null') {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
if (trimmedTemplate === 'undefined') {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
if (NUMBER_LITERAL_REGEX.test(trimmedTemplate)) {
|
|
124
|
-
return Number(trimmedTemplate);
|
|
125
|
-
}
|
|
126
|
-
return templateToDisplay(template, entity);
|
|
127
|
-
};
|
|
128
|
-
const SALT = 'cb9f4b2a-d26c-4787-a66e-e7130ee00f95';
|
|
129
|
-
const encrypt = (obj) => {
|
|
130
|
-
obj = JSON.stringify(obj).split('');
|
|
131
|
-
for (let i = 0, l = obj.length; i < l; i++)
|
|
132
|
-
if (obj[i] == '{')
|
|
133
|
-
obj[i] = '}';
|
|
134
|
-
else if (obj[i] == '}')
|
|
135
|
-
obj[i] = '{';
|
|
136
|
-
return encodeURI(SALT + obj.join(''));
|
|
137
|
-
};
|
|
138
|
-
const decrypt = (encripted) => {
|
|
139
|
-
encripted = decodeURI(encripted);
|
|
140
|
-
if (SALT && encripted.indexOf(SALT) != 0)
|
|
141
|
-
throw new Error('object cannot be decrypted');
|
|
142
|
-
const strs = encripted.substring(SALT.length).split('');
|
|
143
|
-
for (let i = 0, l = strs.length; i < l; i++)
|
|
144
|
-
if (strs[i] == '{')
|
|
145
|
-
strs[i] = '}';
|
|
146
|
-
else if (strs[i] == '}')
|
|
147
|
-
strs[i] = '{';
|
|
148
|
-
return JSON.parse(strs.join(''));
|
|
149
|
-
};
|
|
150
|
-
// Conver "Đội Kỹ Thuật" thành "doi_ky_thuat"
|
|
151
|
-
const convertToSnakeCaseCode = (name) => {
|
|
152
|
-
if (typeof name !== 'string') {
|
|
153
|
-
throw new Error('Invalid name');
|
|
154
|
-
}
|
|
155
|
-
return name
|
|
156
|
-
.normalize('NFD') // Tách dấu
|
|
157
|
-
.replace(/[\u0300-\u036f]/g, '') // Xóa các dấu vừa tách
|
|
158
|
-
.replace(/[đĐ]/g, 'd') // Quan trọng: Xử lý chữ đ/Đ thành d
|
|
159
|
-
.trim()
|
|
160
|
-
.replace(/[^a-zA-Z0-9]+/g, '_') // Thay ký tự đặc biệt bằng _
|
|
161
|
-
.replace(/^_+|_+$/g, '') // Xóa _ ở đầu/cuối
|
|
162
|
-
.toLowerCase(); // Chuyển thành chữ thường: doi_ky_thuat
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Tạo code duy nhất, tự động thêm suffix _n nếu bị trùng
|
|
166
|
-
* @param name Tên cần convert
|
|
167
|
-
* @param existingCodes Danh sách các code đã tồn tại
|
|
168
|
-
*/
|
|
169
|
-
const generateUniqueCode = (name, existingCodes) => {
|
|
170
|
-
// 1. Tạo base code trước
|
|
171
|
-
const baseCode = convertToSnakeCaseCode(name);
|
|
172
|
-
// 2. Nếu code chưa tồn tại thì trả về luôn
|
|
173
|
-
if (!existingCodes.includes(baseCode)) {
|
|
174
|
-
return baseCode;
|
|
175
|
-
}
|
|
176
|
-
// 3. Nếu trùng, bắt đầu loop để tìm index phù hợp
|
|
177
|
-
let index = 1;
|
|
178
|
-
let newCode = `${baseCode}_${index}`;
|
|
179
|
-
while (existingCodes.includes(newCode)) {
|
|
180
|
-
index++;
|
|
181
|
-
newCode = `${baseCode}_${index}`;
|
|
182
|
-
}
|
|
183
|
-
return newCode;
|
|
184
|
-
};
|
|
185
|
-
const sha256 = async (input) => {
|
|
186
|
-
const buffer = new TextEncoder().encode(input);
|
|
187
|
-
const hash = await crypto.subtle.digest('SHA-256', buffer);
|
|
188
|
-
const bytes = new Uint8Array(hash);
|
|
189
|
-
let binary = '';
|
|
190
|
-
for (let i = 0; i < bytes.length; i++)
|
|
191
|
-
binary += String.fromCharCode(bytes[i]);
|
|
192
|
-
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
193
|
-
};
|
|
5
|
+
// Deprecated wrappers — moved to ValidationUtilities
|
|
6
|
+
const isValidEmail = (value) => ValidationUtilities.isEmail(value);
|
|
7
|
+
const isValidPhone = (value) => ValidationUtilities.isPhone(value);
|
|
8
|
+
const isValidCode = (value) => ValidationUtilities.isCode(value);
|
|
194
9
|
const StringUtilities = {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
10
|
+
...StringUtilities$1,
|
|
11
|
+
// Deprecated aliases for renamed regex constants (renamed in @sdcorejs/utils v1.0.2)
|
|
12
|
+
/** @deprecated Use REGEX_VN_PHONE instead */
|
|
13
|
+
REGEX_PHONE_VN: StringUtilities$1.REGEX_VN_PHONE,
|
|
14
|
+
/** @deprecated Use REGEX_VN_ID instead */
|
|
15
|
+
REGEX_IDVN: StringUtilities$1.REGEX_VN_ID,
|
|
16
|
+
/** @deprecated Use REGEX_VN_ID_OR_PASSPORT instead */
|
|
17
|
+
REGEX_IDVN_OR_PASSPORT: StringUtilities$1.REGEX_VN_ID_OR_PASSPORT,
|
|
18
|
+
// Deprecated validators — moved to ValidationUtilities
|
|
19
|
+
/** @deprecated Use {@link ValidationUtilities.isEmail} instead */
|
|
202
20
|
isValidEmail,
|
|
21
|
+
/** @deprecated Use {@link ValidationUtilities.isPhone} instead */
|
|
203
22
|
isValidPhone,
|
|
23
|
+
/** @deprecated Use {@link ValidationUtilities.isCode} instead */
|
|
204
24
|
isValidCode,
|
|
205
|
-
changeAliasLowerCase: changeAliasLowerCase$1,
|
|
206
|
-
aliasIncludes,
|
|
207
|
-
format,
|
|
208
|
-
templateToDisplay,
|
|
209
|
-
parseExpression,
|
|
210
|
-
encrypt,
|
|
211
|
-
decrypt,
|
|
212
|
-
isNullOrEmpty,
|
|
213
|
-
isNullOrWhiteSpace,
|
|
214
|
-
convertToSnakeCaseCode,
|
|
215
|
-
generateUniqueCode,
|
|
216
|
-
sha256,
|
|
217
25
|
};
|
|
218
26
|
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
if (!fields) {
|
|
251
|
-
return items.filter(item => item !== undefined && item !== null && StringUtilities.aliasIncludes(item, searchText));
|
|
252
|
-
}
|
|
253
|
-
return items.filter(item => (item !== undefined && item !== null && StringUtilities.aliasIncludes(item[fields], searchText)) ||
|
|
254
|
-
(children && search(item[children], searchText, fields, children)?.length));
|
|
255
|
-
};
|
|
256
|
-
// Array.prototype.search = function <T = any>(
|
|
257
|
-
// searchText: any,
|
|
258
|
-
// fields?: (string | undefined) | (string | undefined)[],
|
|
259
|
-
// children?: string
|
|
260
|
-
// ): T[] {
|
|
261
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
262
|
-
// const items: T[] = this;
|
|
263
|
-
// return search(items, searchText, fields, children);
|
|
264
|
-
// };
|
|
265
|
-
// Array.prototype.paging = function <T extends Record<string, any> = any>(pageSize: number, page = 0): T[] {
|
|
266
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
267
|
-
// const items: T[] = this;
|
|
268
|
-
// return items.filter((_, index) => {
|
|
269
|
-
// return index >= page * pageSize && index < (page + 1) * pageSize;
|
|
270
|
-
// });
|
|
271
|
-
// };
|
|
272
|
-
// Array.prototype.toObject = function <T = any>(key: string): Record<string, T> {
|
|
273
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
274
|
-
// const items: T[] = this;
|
|
275
|
-
// return items
|
|
276
|
-
// .filter(item => item !== undefined && item !== null)
|
|
277
|
-
// .reduce<Record<string, any>>((obj, item: Record<string, any>) => {
|
|
278
|
-
// const objKey = item?.[key]?.toString();
|
|
279
|
-
// if (typeof objKey === 'string') {
|
|
280
|
-
// obj[objKey] = item;
|
|
281
|
-
// }
|
|
282
|
-
// return obj;
|
|
283
|
-
// }, {});
|
|
284
|
-
// };
|
|
285
|
-
// Array.prototype.union = function <T = Record<string, any>>(key: string, ...args: T[][]) {
|
|
286
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
287
|
-
// let results: T[] = this;
|
|
288
|
-
// const filterUnion = <T = any>(val: T, index: number, self: T[]) =>
|
|
289
|
-
// val !== undefined && val !== null && self.findIndex(e => (e as any)[key] === (val as any)[key]) === index;
|
|
290
|
-
// if (!args?.length) {
|
|
291
|
-
// return results.filter(filterUnion);
|
|
292
|
-
// }
|
|
293
|
-
// for (const arg of args) {
|
|
294
|
-
// if (Array.isArray(arg)) {
|
|
295
|
-
// results = [...results, ...arg].filter(filterUnion);
|
|
296
|
-
// }
|
|
297
|
-
// }
|
|
298
|
-
// return results;
|
|
299
|
-
// };
|
|
300
|
-
// Array.prototype.distinct = function () {
|
|
301
|
-
// if (Array.isArray(this)) {
|
|
302
|
-
// return [...new Set(this)];
|
|
303
|
-
// }
|
|
304
|
-
// return this;
|
|
305
|
-
// };
|
|
306
|
-
const union = (key, ...args) => {
|
|
307
|
-
let results = [];
|
|
308
|
-
const filterUnion = (val, index, self) => val !== undefined && val !== null && self.findIndex(e => e[key] === val[key]) === index;
|
|
309
|
-
if (!args?.length) {
|
|
310
|
-
return [];
|
|
311
|
-
}
|
|
312
|
-
for (const arg of args) {
|
|
313
|
-
if (Array.isArray(arg)) {
|
|
314
|
-
results = [...results, ...arg].filter(filterUnion);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return results;
|
|
318
|
-
};
|
|
319
|
-
const toObject = (key, items) => {
|
|
320
|
-
if (!Array.isArray(items)) {
|
|
321
|
-
return {};
|
|
322
|
-
}
|
|
323
|
-
return items
|
|
324
|
-
.filter(item => item !== undefined && item !== null)
|
|
325
|
-
.reduce((obj, item) => {
|
|
326
|
-
const objKey = item?.[key]?.toString();
|
|
327
|
-
if (typeof objKey === 'string') {
|
|
328
|
-
obj[objKey] = item;
|
|
329
|
-
}
|
|
330
|
-
return obj;
|
|
331
|
-
}, {});
|
|
332
|
-
};
|
|
333
|
-
const distinct = (items) => {
|
|
334
|
-
if (Array.isArray(items)) {
|
|
335
|
-
return [...new Set(items)];
|
|
336
|
-
}
|
|
337
|
-
return [];
|
|
338
|
-
};
|
|
339
|
-
const paging = (items, pageSize, page = 0) => {
|
|
340
|
-
if (!Array.isArray(items)) {
|
|
341
|
-
return [];
|
|
342
|
-
}
|
|
343
|
-
return items.filter((_, index) => {
|
|
344
|
-
return index >= page * pageSize && index < (page + 1) * pageSize;
|
|
345
|
-
});
|
|
346
|
-
};
|
|
347
|
-
const ArrayUtilities = {
|
|
348
|
-
search,
|
|
349
|
-
union,
|
|
350
|
-
toObject,
|
|
351
|
-
distinct,
|
|
352
|
-
paging,
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
356
|
-
// export {};
|
|
357
|
-
// declare global {
|
|
358
|
-
// interface NumberConstructor {
|
|
359
|
-
// toVNCurrency(value: any): string | null;
|
|
360
|
-
// toISO(value: any): string | null;
|
|
361
|
-
// isNumber(value: any): boolean;
|
|
362
|
-
// isPositiveInteger(value: any): boolean;
|
|
363
|
-
// isPositiveNumber(value: any): boolean;
|
|
364
|
-
// round(value: any, digits?: number): number | null;
|
|
365
|
-
// }
|
|
366
|
-
// }
|
|
367
|
-
const toVNCurrency = (value) => {
|
|
368
|
-
value = (value ?? '').toString().replace(/,/g, '');
|
|
369
|
-
if (!value) {
|
|
370
|
-
return null;
|
|
371
|
-
}
|
|
372
|
-
const val = +value;
|
|
373
|
-
if (!Number.isNaN(val)) {
|
|
374
|
-
return val.toLocaleString('vi-VN', { maximumFractionDigits: 10 });
|
|
375
|
-
}
|
|
376
|
-
return null;
|
|
377
|
-
};
|
|
378
|
-
const toVN = (value) => {
|
|
379
|
-
value = (value ?? '').toString().replace(/,/g, '');
|
|
380
|
-
if (!value) {
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
const val = +value;
|
|
384
|
-
if (!Number.isNaN(val)) {
|
|
385
|
-
return val.toLocaleString('vi-VN', { maximumFractionDigits: 10 });
|
|
386
|
-
}
|
|
387
|
-
return null;
|
|
388
|
-
};
|
|
389
|
-
const toISO = (value) => {
|
|
390
|
-
value = (value ?? '').toString().replace(/,/g, '');
|
|
391
|
-
if (!value) {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
const val = +value;
|
|
395
|
-
if (!Number.isNaN(val)) {
|
|
396
|
-
return val.toLocaleString('en-US', { maximumFractionDigits: 10 });
|
|
397
|
-
}
|
|
398
|
-
return null;
|
|
399
|
-
};
|
|
400
|
-
const isPositiveInteger = (value) => {
|
|
401
|
-
if (!value) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
const regex = /^([0-9]*)$/;
|
|
405
|
-
return regex.test(value) && +value > 0;
|
|
406
|
-
};
|
|
407
|
-
const isPositiveNumber = (value) => {
|
|
408
|
-
if (!value) {
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
const regex = /^([0-9]*)(\.[0-9]+$){0,1}$/;
|
|
412
|
-
return regex.test(value) && +value > 0;
|
|
413
|
-
};
|
|
414
|
-
const isNumber = (value) => {
|
|
415
|
-
if (value === undefined || value === null || value === '') {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
return !Number.isNaN(+value);
|
|
419
|
-
};
|
|
420
|
-
const round = (value, digits) => {
|
|
421
|
-
digits = digits ?? 2;
|
|
422
|
-
if (!NumberUtilities.isNumber(value)) {
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
const val = Math.pow(10, digits);
|
|
426
|
-
return Math.round(value * val) / val;
|
|
427
|
-
};
|
|
428
|
-
const NumberUtilities = {
|
|
429
|
-
toVNCurrency,
|
|
430
|
-
toVN,
|
|
431
|
-
toISO,
|
|
432
|
-
isPositiveInteger,
|
|
433
|
-
isPositiveNumber,
|
|
434
|
-
isNumber,
|
|
435
|
-
round,
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
439
|
-
// export {};
|
|
440
|
-
// declare global {
|
|
441
|
-
// interface Date {
|
|
442
|
-
// begin(): Date;
|
|
443
|
-
// end(): Date;
|
|
444
|
-
// addMiliseconds(miliseconds: number): Date;
|
|
445
|
-
// addHours(hours: number): Date;
|
|
446
|
-
// addDays(days: number): Date;
|
|
447
|
-
// addMonths(months: number): Date;
|
|
448
|
-
// toFormat(format: string): string;
|
|
449
|
-
// }
|
|
450
|
-
// interface DateConstructor {
|
|
451
|
-
// equal(date1: any, date2: any): boolean;
|
|
452
|
-
// dayDiff(date1: any, date2: any): number | null;
|
|
453
|
-
// monthDiff(date1: any, date2: any): number | null;
|
|
454
|
-
// yearDiff(date1: any, date2: any): number | null;
|
|
455
|
-
// age(date1: any, date2: any): number | null;
|
|
456
|
-
// parseFrom(value: any, format: string): Date | null;
|
|
457
|
-
// isDate(value: any): boolean;
|
|
458
|
-
// toFormat(value: any, format: string): string;
|
|
459
|
-
// addMiliseconds(value: any, miliseconds: number): Date | null;
|
|
460
|
-
// addHours(value: any, hours: number): Date | null;
|
|
461
|
-
// addDays(value: any, days: number): Date | null;
|
|
462
|
-
// addMonths(value: any, months: number): Date | null;
|
|
463
|
-
// begin(value: any): Date | null;
|
|
464
|
-
// end(value: any): Date | null;
|
|
465
|
-
// timeDifference(previous: any, current?: any): string;
|
|
466
|
-
// }
|
|
467
|
-
// }
|
|
468
|
-
const isDate = (value) => {
|
|
469
|
-
if (value === undefined || value === null || value === '') {
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
if (typeof value === 'string') {
|
|
473
|
-
// Kiểm tra regex
|
|
474
|
-
if (value.length < 8) {
|
|
475
|
-
return false;
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
const date1 = value.substring(0, 10); // yyyy/MM/dd
|
|
479
|
-
const date2 = value.substring(0, 9); // yyyy/M/dd, yyyy/MM/d
|
|
480
|
-
const date3 = value.substring(0, 8); // yyyy/M/d
|
|
481
|
-
const regex1 = /^(0[1-9]|[1-9]|1[0-2])(-|\/)(0[1-9]|[1-9]|[12][0-9]|3[01])(-|\/)(\d{4})$/; // MM/dd/yyyy, MM-dd-yyyy
|
|
482
|
-
const regex2 = /^(\d{4})(-|\/)(0[1-9]|[1-9]|1[0-2])(-|\/)(0[1-9]|[1-9]|[12][0-9]|3[01])$/; // yyyy/MM/dd, yyyy-MM-dd
|
|
483
|
-
const result = regex1.test(date1) || regex2.test(date1) || regex1.test(date2) || regex2.test(date2) || regex1.test(date3) || regex2.test(date3);
|
|
484
|
-
if (result) {
|
|
485
|
-
const date = new Date(value);
|
|
486
|
-
return !isNaN(date.getTime());
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
494
|
-
const date = new Date(value);
|
|
495
|
-
return !isNaN(date.getTime());
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
// const toFormatBackup = (value: any, format: string): string => {
|
|
499
|
-
// if (!isDate(value)) {
|
|
500
|
-
// return '';
|
|
501
|
-
// }
|
|
502
|
-
// const date: Date = new Date(value);
|
|
503
|
-
// let result = format;
|
|
504
|
-
// result = result.replace('yyyy', date.getFullYear().toString());
|
|
505
|
-
// result = result.replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'));
|
|
506
|
-
// result = result.replace('dd', date.getDate().toString().padStart(2, '0'));
|
|
507
|
-
// result = result.replace('HH', date.getHours().toString().padStart(2, '0'));
|
|
508
|
-
// result = result.replace('mm', date.getMinutes().toString().padStart(2, '0'));
|
|
509
|
-
// result = result.replace('ss', date.getSeconds().toString().padStart(2, '0'));
|
|
510
|
-
// return result;
|
|
511
|
-
// };
|
|
512
|
-
const toFormat = (value, format) => {
|
|
513
|
-
if (!isDate(value)) {
|
|
514
|
-
return '';
|
|
515
|
-
}
|
|
516
|
-
const isDatePart = (type) => {
|
|
517
|
-
return type === 'year' || type === 'month' || type === 'day' || type === 'hour' || type === 'minute' || type === 'second';
|
|
518
|
-
};
|
|
519
|
-
const date = new Date(value);
|
|
520
|
-
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
521
|
-
const parts = new Intl.DateTimeFormat('en-GB', {
|
|
522
|
-
timeZone,
|
|
523
|
-
year: 'numeric',
|
|
524
|
-
month: '2-digit',
|
|
525
|
-
day: '2-digit',
|
|
526
|
-
hour: '2-digit',
|
|
527
|
-
minute: '2-digit',
|
|
528
|
-
second: '2-digit',
|
|
529
|
-
hour12: false,
|
|
530
|
-
}).formatToParts(date);
|
|
531
|
-
const map = {};
|
|
532
|
-
parts.forEach(part => {
|
|
533
|
-
if (isDatePart(part.type)) {
|
|
534
|
-
map[part.type] = part.value;
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
return format
|
|
538
|
-
.replace('yyyy', map.year ?? '')
|
|
539
|
-
.replace('MM', map.month ?? '')
|
|
540
|
-
.replace('dd', map.day ?? '')
|
|
541
|
-
.replace('HH', map.hour ?? '')
|
|
542
|
-
.replace('mm', map.minute ?? '')
|
|
543
|
-
.replace('ss', map.second ?? '');
|
|
544
|
-
};
|
|
545
|
-
const addDays = (value, days) => {
|
|
546
|
-
if (!isDate(value)) {
|
|
547
|
-
return null;
|
|
548
|
-
}
|
|
549
|
-
const date = new Date(value);
|
|
550
|
-
date.setDate(date.getDate() + days);
|
|
551
|
-
return date;
|
|
552
|
-
};
|
|
553
|
-
const addMiliseconds = (value, miliseconds) => {
|
|
554
|
-
if (!isDate(value)) {
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
const date = new Date(value);
|
|
558
|
-
date.setMilliseconds(date.getMilliseconds() + miliseconds);
|
|
559
|
-
return date;
|
|
560
|
-
};
|
|
561
|
-
const begin = (value) => {
|
|
562
|
-
if (!isDate(value)) {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
return new Date(toFormat(value, 'MM/dd/yyyy'));
|
|
566
|
-
};
|
|
567
|
-
const end = (value) => {
|
|
568
|
-
if (!isDate(value)) {
|
|
569
|
-
return null;
|
|
570
|
-
}
|
|
571
|
-
return addMiliseconds(begin(addDays(value, 1)), -1);
|
|
572
|
-
};
|
|
573
|
-
const equal = (date1, date2) => {
|
|
574
|
-
if (!isDate(date1) && !isDate(date2)) {
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
if (!isDate(date1) || !isDate(date2)) {
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
return new Date(date1).getTime() === new Date(date2).getTime();
|
|
581
|
-
};
|
|
582
|
-
const dayDiff = (date1, date2) => {
|
|
583
|
-
if (!isDate(date1) || !isDate(date2)) {
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
const diff = new Date(date2).getTime() - new Date(date1).getTime();
|
|
587
|
-
return Math.floor(diff / (24 * 3600 * 1000));
|
|
588
|
-
};
|
|
589
|
-
const monthDiff = (date1, date2) => {
|
|
590
|
-
if (!isDate(date1) || !isDate(date2)) {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
const d1Y = date1.getFullYear();
|
|
594
|
-
const d2Y = date2.getFullYear();
|
|
595
|
-
const d1M = date2.getMonth();
|
|
596
|
-
const d2M = date2.getMonth();
|
|
597
|
-
return d2M + 12 * d2Y - (d1M + 12 * d1Y);
|
|
598
|
-
};
|
|
599
|
-
const yearDiff = (date1, date2) => {
|
|
600
|
-
if (!isDate(date1) || !isDate(date2)) {
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
return new Date(date2).getFullYear() - new Date(date1).getFullYear();
|
|
604
|
-
};
|
|
605
|
-
const age = (date1, date2) => {
|
|
606
|
-
const diff = monthDiff(date1, date2);
|
|
607
|
-
if (!diff) {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
return NumberUtilities.round(diff / 12);
|
|
611
|
-
};
|
|
612
|
-
const parseFrom = (value, format) => {
|
|
613
|
-
if (!value) {
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
if (!format) {
|
|
617
|
-
return null;
|
|
618
|
-
}
|
|
619
|
-
value = value.toString();
|
|
620
|
-
const dmy = format.indexOf('dd') > -1 && format.indexOf('MM') > -1 && format.indexOf('yyyy') > -1;
|
|
621
|
-
const hms = format.indexOf('HH') > -1 || format.indexOf('mm') > -1 || format.indexOf('ss') > -1;
|
|
622
|
-
let strDate = '';
|
|
623
|
-
if (dmy) {
|
|
624
|
-
const dd = value.substr(format.indexOf('dd'), 'dd'.length);
|
|
625
|
-
const MM = value.substr(format.indexOf('MM'), 'MM'.length);
|
|
626
|
-
const yyyy = value.substr(format.indexOf('yyyy'), 'yyyy'.length);
|
|
627
|
-
if (+yyyy > 0 && +MM > 0 && +dd > 0) {
|
|
628
|
-
strDate += `${MM}/${dd}/${yyyy}`;
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
const today = new Date();
|
|
636
|
-
const yyyy = today.getFullYear().toString();
|
|
637
|
-
const MM = (today.getMonth() + 1).toString().padStart(2, '0');
|
|
638
|
-
const dd = today.getDate().toString().padStart(2, '0');
|
|
639
|
-
strDate += `${MM}/${dd}/${yyyy}`;
|
|
640
|
-
}
|
|
641
|
-
if (hms) {
|
|
642
|
-
const HH = format.indexOf('HH') > -1 ? value.substr(format.indexOf('HH'), 'HH'.length) : '00';
|
|
643
|
-
const mm = format.indexOf('mm') > -1 ? value.substr(format.indexOf('mm'), 'mm'.length) : '00';
|
|
644
|
-
const ss = format.indexOf('ss') > -1 ? value.substr(format.indexOf('ss'), 'ss'.length) : '00';
|
|
645
|
-
strDate += ` ${HH || '00'}:${mm || '00'}:${ss || '00'}`;
|
|
646
|
-
}
|
|
647
|
-
if (!isDate(strDate)) {
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
return new Date(strDate);
|
|
651
|
-
};
|
|
652
|
-
const addHours = (value, hours) => {
|
|
653
|
-
if (!isDate(value)) {
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
const date = new Date(value);
|
|
657
|
-
date.setHours(date.getHours() + hours);
|
|
658
|
-
return date;
|
|
659
|
-
};
|
|
660
|
-
const addMonths = (value, months) => {
|
|
661
|
-
if (!isDate(value)) {
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
const date = new Date(value);
|
|
665
|
-
date.setMonth(date.getMonth() + months);
|
|
666
|
-
return date;
|
|
667
|
-
};
|
|
668
|
-
const timeDifference = function (previous, current = new Date()) {
|
|
669
|
-
if (!isDate(previous)) {
|
|
670
|
-
return '';
|
|
671
|
-
}
|
|
672
|
-
if (!isDate(current)) {
|
|
673
|
-
return '';
|
|
674
|
-
}
|
|
675
|
-
const from = new Date(previous);
|
|
676
|
-
const to = new Date(current);
|
|
677
|
-
const msPerMinute = 60 * 1000;
|
|
678
|
-
const msPerHour = msPerMinute * 60;
|
|
679
|
-
const msPerDay = msPerHour * 24;
|
|
680
|
-
const msPerMonth = msPerDay * 30;
|
|
681
|
-
const msPerYear = msPerDay * 365;
|
|
682
|
-
const elapsed = to.getTime() - from.getTime();
|
|
683
|
-
if (elapsed < msPerMinute) {
|
|
684
|
-
return `${Math.round(elapsed / 1000)} seconds ago`;
|
|
685
|
-
}
|
|
686
|
-
if (elapsed < msPerHour) {
|
|
687
|
-
return `${Math.round(elapsed / msPerMinute)} minutes ago`;
|
|
688
|
-
}
|
|
689
|
-
if (elapsed < msPerDay) {
|
|
690
|
-
return `${Math.round(elapsed / msPerHour)} hours ago`;
|
|
691
|
-
}
|
|
692
|
-
if (elapsed < msPerMonth) {
|
|
693
|
-
return `${Math.round(elapsed / msPerDay)} days ago`;
|
|
694
|
-
}
|
|
695
|
-
if (elapsed < msPerYear) {
|
|
696
|
-
return `${Math.round(elapsed / msPerMonth)} months ago`;
|
|
27
|
+
// Hardcoded i18n cho upload utility — pure function nên không inject() được I18nService.
|
|
28
|
+
// Đọc lang trực tiếp từ localStorage (cùng key với I18nService: 'sd-core.language').
|
|
29
|
+
// Khi I18nService chuyển sang reload-on-change model, lang ở đây luôn đồng bộ với UI.
|
|
30
|
+
const SD_UPLOAD_MESSAGES = {
|
|
31
|
+
vi: {
|
|
32
|
+
'invalid-format': '[{name}] File tải lên không đúng định dạng. Vui lòng chọn lại',
|
|
33
|
+
'invalid-size': '[{name}] Kích thước file không hợp lệ. Vui lòng chọn một file khác',
|
|
34
|
+
},
|
|
35
|
+
en: {
|
|
36
|
+
'invalid-format': '[{name}] Invalid file format. Please select again',
|
|
37
|
+
'invalid-size': '[{name}] Invalid file size. Please choose a different file',
|
|
38
|
+
},
|
|
39
|
+
ja: {
|
|
40
|
+
'invalid-format': '[{name}] ファイル形式が正しくありません。もう一度選択してください',
|
|
41
|
+
'invalid-size': '[{name}] ファイルサイズが正しくありません。別のファイルを選択してください',
|
|
42
|
+
},
|
|
43
|
+
ko: {
|
|
44
|
+
'invalid-format': '[{name}] 파일 형식이 올바르지 않습니다. 다시 선택해 주세요',
|
|
45
|
+
'invalid-size': '[{name}] 파일 크기가 올바르지 않습니다. 다른 파일을 선택해 주세요',
|
|
46
|
+
},
|
|
47
|
+
zh: {
|
|
48
|
+
'invalid-format': '[{name}] 文件格式不正确,请重新选择',
|
|
49
|
+
'invalid-size': '[{name}] 文件大小不符合要求,请选择其他文件',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const getSdUploadLang = () => {
|
|
53
|
+
try {
|
|
54
|
+
const stored = localStorage.getItem('sd-core.language');
|
|
55
|
+
if (stored && stored in SD_UPLOAD_MESSAGES)
|
|
56
|
+
return stored;
|
|
697
57
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
// Date.prototype.begin = function () {
|
|
701
|
-
// return new Date(this.toFormat('MM/dd/yyyy'));
|
|
702
|
-
// };
|
|
703
|
-
// Date.prototype.end = function () {
|
|
704
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
705
|
-
// const date: Date = this;
|
|
706
|
-
// return date.addDays(1).begin().addMiliseconds(-1);
|
|
707
|
-
// };
|
|
708
|
-
// Date.prototype.addMiliseconds = function (miliseconds: number) {
|
|
709
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
710
|
-
// const date: Date = this;
|
|
711
|
-
// date.setMilliseconds(date.getMilliseconds() + miliseconds);
|
|
712
|
-
// return date;
|
|
713
|
-
// };
|
|
714
|
-
// Date.prototype.addHours = function (hours: number) {
|
|
715
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
716
|
-
// const date: Date = this;
|
|
717
|
-
// date.setHours(date.getHours() + hours);
|
|
718
|
-
// return date;
|
|
719
|
-
// };
|
|
720
|
-
// Date.prototype.addDays = function (days: number) {
|
|
721
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
722
|
-
// const date: Date = this;
|
|
723
|
-
// date.setDate(date.getDate() + days);
|
|
724
|
-
// return date;
|
|
725
|
-
// };
|
|
726
|
-
// Date.prototype.addMonths = function (months: number) {
|
|
727
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
728
|
-
// const date: Date = this;
|
|
729
|
-
// date.setMonth(date.getMonth() + months);
|
|
730
|
-
// return date;
|
|
731
|
-
// };
|
|
732
|
-
// Date.prototype.toFormat = function (format: string) {
|
|
733
|
-
// // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
734
|
-
// const date: Date = this;
|
|
735
|
-
// let result = format;
|
|
736
|
-
// result = result.replace('yyyy', date.getFullYear().toString());
|
|
737
|
-
// result = result.replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'));
|
|
738
|
-
// result = result.replace('dd', date.getDate().toString().padStart(2, '0'));
|
|
739
|
-
// result = result.replace('HH', date.getHours().toString().padStart(2, '0'));
|
|
740
|
-
// result = result.replace('mm', date.getMinutes().toString().padStart(2, '0'));
|
|
741
|
-
// result = result.replace('ss', date.getSeconds().toString().padStart(2, '0'));
|
|
742
|
-
// return result;
|
|
743
|
-
// };
|
|
744
|
-
const DateUtilities = {
|
|
745
|
-
equal,
|
|
746
|
-
dayDiff,
|
|
747
|
-
monthDiff,
|
|
748
|
-
yearDiff,
|
|
749
|
-
age,
|
|
750
|
-
parseFrom,
|
|
751
|
-
isDate,
|
|
752
|
-
toFormat,
|
|
753
|
-
addMiliseconds,
|
|
754
|
-
addHours,
|
|
755
|
-
addDays,
|
|
756
|
-
addMonths,
|
|
757
|
-
begin,
|
|
758
|
-
end,
|
|
759
|
-
timeDifference,
|
|
58
|
+
catch { /* ignore */ }
|
|
59
|
+
return 'vi';
|
|
760
60
|
};
|
|
761
|
-
|
|
762
61
|
const upload = (option) => {
|
|
763
62
|
const uploadId = 'U1e09c1c0-b647-437e-995e-d7a1a1b60550';
|
|
764
63
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -783,13 +82,13 @@ const upload = (option) => {
|
|
|
783
82
|
element.addEventListener('change', (evt) => {
|
|
784
83
|
try {
|
|
785
84
|
const target = evt.target;
|
|
786
|
-
//
|
|
787
|
-
//
|
|
788
|
-
//
|
|
789
|
-
const throwUploadError = (
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
throw
|
|
85
|
+
// Hardcode i18n cho upload utility — pure function nên không inject() được I18nService.
|
|
86
|
+
// Đọc lang trực tiếp từ localStorage (cùng key với I18nService: 'sd-core.language').
|
|
87
|
+
// Throw plain Error(translatedText) để consumer không cần xử lý đặc biệt.
|
|
88
|
+
const throwUploadError = (msgKey, name) => {
|
|
89
|
+
const lang = getSdUploadLang();
|
|
90
|
+
const template = SD_UPLOAD_MESSAGES[lang][msgKey] ?? SD_UPLOAD_MESSAGES.vi[msgKey];
|
|
91
|
+
throw new Error(template.replace('{name}', name));
|
|
793
92
|
};
|
|
794
93
|
if (option?.multiple) {
|
|
795
94
|
const files = [];
|
|
@@ -797,15 +96,15 @@ const upload = (option) => {
|
|
|
797
96
|
if (file) {
|
|
798
97
|
const lastDot = file.name.lastIndexOf('.');
|
|
799
98
|
if (lastDot === -1) {
|
|
800
|
-
throwUploadError('
|
|
99
|
+
throwUploadError('invalid-format', file.name);
|
|
801
100
|
}
|
|
802
101
|
const extension = file.name.substring(lastDot + 1);
|
|
803
102
|
if (option) {
|
|
804
103
|
if (option.extensions?.length && !option.extensions.some(e => e.toLowerCase() === extension.toLowerCase())) {
|
|
805
|
-
throwUploadError('
|
|
104
|
+
throwUploadError('invalid-format', file.name);
|
|
806
105
|
}
|
|
807
106
|
if (option.maxSizeInMb && option.maxSizeInMb > 0 && option.maxSizeInMb * 1024 * 1024 < file.size) {
|
|
808
|
-
throwUploadError('
|
|
107
|
+
throwUploadError('invalid-size', file.name);
|
|
809
108
|
}
|
|
810
109
|
if (option.validator && option.validator(file.name)) {
|
|
811
110
|
// Custom validator returns its own message — pass through as-is (caller defines format).
|
|
@@ -823,15 +122,15 @@ const upload = (option) => {
|
|
|
823
122
|
if (file) {
|
|
824
123
|
const lastDot = file.name.lastIndexOf('.');
|
|
825
124
|
if (lastDot === -1) {
|
|
826
|
-
throwUploadError('
|
|
125
|
+
throwUploadError('invalid-format', file.name);
|
|
827
126
|
}
|
|
828
127
|
const extension = file.name.substring(lastDot + 1);
|
|
829
128
|
if (option) {
|
|
830
129
|
if (option.extensions?.length && !option.extensions.some(e => e.toLowerCase() === extension.toLowerCase())) {
|
|
831
|
-
throwUploadError('
|
|
130
|
+
throwUploadError('invalid-format', file.name);
|
|
832
131
|
}
|
|
833
132
|
if (option.maxSizeInMb && option.maxSizeInMb > 0 && option.maxSizeInMb * 1024 * 1024 < file.size) {
|
|
834
|
-
throwUploadError('
|
|
133
|
+
throwUploadError('invalid-size', file.name);
|
|
835
134
|
}
|
|
836
135
|
if (option.validator && option.validator(file.name)) {
|
|
837
136
|
const message = option.validator(file.name);
|
|
@@ -1057,6 +356,11 @@ const getNestedValue = (obj, path) => {
|
|
|
1057
356
|
// Tách chuỗi theo dấu chấm và duyệt qua từng cấp của object
|
|
1058
357
|
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
|
1059
358
|
};
|
|
359
|
+
/**
|
|
360
|
+
* @deprecated Use {@link Utilities} and {@link BrowserUtilities} from `@sdcorejs/utils/fns` instead.
|
|
361
|
+
* - upload/download/clipboard/isMobile → BrowserUtilities
|
|
362
|
+
* - allWithPaging (→ fetchAllByPaging) / randomId / hash / parseQueryParams / generateUuid / getNestedValue → Utilities
|
|
363
|
+
*/
|
|
1060
364
|
const SdUtilities = {
|
|
1061
365
|
upload,
|
|
1062
366
|
download,
|
|
@@ -1074,74 +378,14 @@ const SdUtilities = {
|
|
|
1074
378
|
getNestedValue
|
|
1075
379
|
};
|
|
1076
380
|
|
|
1077
|
-
/**
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
/**
|
|
1082
|
-
* Chuyển đổi màu HSL sang định dạng hex
|
|
1083
|
-
* @param h - Hue (0-360)
|
|
1084
|
-
* @param s - Saturation (0-100)
|
|
1085
|
-
* @param l - Lightness (0-100)
|
|
1086
|
-
* @returns Mã màu hex
|
|
1087
|
-
*/
|
|
1088
|
-
function hslToHex(h, s, l) {
|
|
1089
|
-
// Chuyển đổi sang định dạng 0-1
|
|
1090
|
-
const hNormalized = h / 360;
|
|
1091
|
-
const sNormalized = s / 100;
|
|
1092
|
-
const lNormalized = l / 100;
|
|
1093
|
-
let r, g, b;
|
|
1094
|
-
if (sNormalized === 0) {
|
|
1095
|
-
// Màu xám (grayscale)
|
|
1096
|
-
r = g = b = lNormalized;
|
|
1097
|
-
}
|
|
1098
|
-
else {
|
|
1099
|
-
const hue2rgb = (p, q, t) => {
|
|
1100
|
-
if (t < 0)
|
|
1101
|
-
t += 1;
|
|
1102
|
-
if (t > 1)
|
|
1103
|
-
t -= 1;
|
|
1104
|
-
if (t < 1 / 6)
|
|
1105
|
-
return p + (q - p) * 6 * t;
|
|
1106
|
-
if (t < 1 / 2)
|
|
1107
|
-
return q;
|
|
1108
|
-
if (t < 2 / 3)
|
|
1109
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1110
|
-
return p;
|
|
1111
|
-
};
|
|
1112
|
-
const q = lNormalized < 0.5
|
|
1113
|
-
? lNormalized * (1 + sNormalized)
|
|
1114
|
-
: lNormalized + sNormalized - lNormalized * sNormalized;
|
|
1115
|
-
const p = 2 * lNormalized - q;
|
|
1116
|
-
r = hue2rgb(p, q, hNormalized + 1 / 3);
|
|
1117
|
-
g = hue2rgb(p, q, hNormalized);
|
|
1118
|
-
b = hue2rgb(p, q, hNormalized - 1 / 3);
|
|
1119
|
-
}
|
|
1120
|
-
// Chuyển đổi sang hex
|
|
1121
|
-
const toHex = (x) => {
|
|
1122
|
-
const hex = Math.round(x * 255).toString(16);
|
|
1123
|
-
return hex.length === 1 ? '0' + hex : hex;
|
|
1124
|
-
};
|
|
1125
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
1126
|
-
}
|
|
1127
|
-
/**
|
|
1128
|
-
* Chuyển đổi màu RGB sang hex
|
|
1129
|
-
* @param r - Red (0-255)
|
|
1130
|
-
* @param g - Green (0-255)
|
|
1131
|
-
* @param b - Blue (0-255)
|
|
1132
|
-
* @returns Mã màu hex
|
|
1133
|
-
*/
|
|
1134
|
-
function rgbToHex(r, g, b) {
|
|
1135
|
-
const toHex = (x) => {
|
|
1136
|
-
const hex = Math.round(Math.max(0, Math.min(255, x))).toString(16);
|
|
1137
|
-
return hex.length === 1 ? '0' + hex : hex;
|
|
1138
|
-
};
|
|
1139
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
1140
|
-
}
|
|
381
|
+
/** @deprecated Use {@link ColorUtilities.hslToHex} from `@sdcorejs/utils/fns` instead */
|
|
382
|
+
const hslToHex = ColorUtilities.hslToHex;
|
|
383
|
+
/** @deprecated Use {@link ColorUtilities.rgbToHex} from `@sdcorejs/utils/fns` instead */
|
|
384
|
+
const rgbToHex = ColorUtilities.rgbToHex;
|
|
1141
385
|
|
|
1142
386
|
/**
|
|
1143
387
|
* Generated bundle index. Do not edit.
|
|
1144
388
|
*/
|
|
1145
389
|
|
|
1146
|
-
export {
|
|
390
|
+
export { SdUtilities, StringUtilities, hslToHex, rgbToHex };
|
|
1147
391
|
//# sourceMappingURL=sd-angular-core-utilities-extensions.mjs.map
|