@ldquocc/dynamic-form 0.0.4 → 0.0.6

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.
Files changed (20) hide show
  1. package/fesm2022/ldquocc-dynamic-form.mjs +1029 -2842
  2. package/fesm2022/ldquocc-dynamic-form.mjs.map +1 -1
  3. package/lib/shared/components/component.d.ts +13 -14
  4. package/lib/shared/components/dynamic-field/dynamic-field.component.d.ts +5 -5
  5. package/lib/shared/components/fields/upload/avatar-upload/avatar-upload.component.d.ts +1 -0
  6. package/lib/shared/components/fields/upload/multi-file-upload/multi-file-upload.component.d.ts +7 -6
  7. package/lib/shared/components/fields/upload/multi-image-upload/multi-image-upload.component.d.ts +1 -0
  8. package/lib/shared/components/fields/upload/single-file-upload/single-file-upload.component.d.ts +1 -0
  9. package/lib/shared/consts/common/index.d.ts +0 -1
  10. package/lib/shared/models/fields/upload/index.d.ts +4 -0
  11. package/lib/shared/services/dynamic-form.service.d.ts +3 -3
  12. package/lib/shared/utils/validators/custom-validators.d.ts +1 -2
  13. package/package.json +1 -1
  14. package/public-api.d.ts +3 -4
  15. package/lib/shared/components/fields/radio/custom/reminder-type-radio/reminder-type-radio.component.d.ts +0 -24
  16. package/lib/shared/consts/common/sample-test.const.d.ts +0 -68
  17. package/lib/shared/models/fields/radio/custom/reminder-type-radio/reminder-option.model.d.ts +0 -26
  18. package/lib/shared/models/fields/radio/custom/reminder-type-radio/reminder-type-radio-field.model.d.ts +0 -14
  19. package/lib/shared/models/fields/radio/custom/reminder-type-radio/reminder-type.model.d.ts +0 -1
  20. package/lib/shared/models/fields/radio/custom/reminder-type-radio/reminder-value.model.d.ts +0 -7
@@ -1,5 +1,5 @@
1
1
  import * as i1 from '@angular/forms';
2
- import { FormsModule, ReactiveFormsModule, FormControl, Validators, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
2
+ import { FormsModule, ReactiveFormsModule, Validators, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
3
3
  import { of, delay, Subject, takeUntil, finalize, Observable, debounceTime, distinctUntilChanged } from 'rxjs';
4
4
  import * as i1$2 from '@angular/common';
5
5
  import { CommonModule, CurrencyPipe } from '@angular/common';
@@ -22,24 +22,23 @@ import { NzButtonModule } from 'ng-zorro-antd/button';
22
22
  import * as i5 from 'ng-zorro-antd/core/transition-patch';
23
23
  import * as i6 from 'ng-zorro-antd/core/wave';
24
24
  import { NgxCurrencyDirective } from 'ngx-currency';
25
- import * as i3$1 from 'ng-zorro-antd/radio';
26
- import { NzRadioModule } from 'ng-zorro-antd/radio';
27
- import * as i4$1 from 'ng-zorro-antd/select';
25
+ import * as i3$1 from 'ng-zorro-antd/select';
28
26
  import { NzSelectModule } from 'ng-zorro-antd/select';
29
- import { ToastrService } from 'ngx-toastr';
30
27
  import * as i3$2 from 'ng-zorro-antd/spin';
31
28
  import { NzSpinModule } from 'ng-zorro-antd/spin';
32
29
  import * as i2$1 from 'ng-zorro-antd/tree-select';
33
30
  import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select';
34
31
  import * as i2$2 from 'ng-zorro-antd/upload';
35
32
  import { NzUploadModule } from 'ng-zorro-antd/upload';
33
+ import { NzNotificationService } from 'ng-zorro-antd/notification';
34
+ import { ToastrService } from 'ngx-toastr';
36
35
  import { TranslateService } from '@ngx-translate/core';
37
36
  import { NzMessageService } from 'ng-zorro-antd/message';
38
37
  import * as i7 from 'ng-zorro-antd/image';
39
38
  import { NzImageModule } from 'ng-zorro-antd/image';
40
39
  import * as i2$3 from 'ng-zorro-antd/grid';
41
40
  import { NzGridModule } from 'ng-zorro-antd/grid';
42
- import * as i4$2 from 'ng-zorro-antd/collapse';
41
+ import * as i4$1 from 'ng-zorro-antd/collapse';
43
42
  import { NzCollapseModule } from 'ng-zorro-antd/collapse';
44
43
 
45
44
  // Core Angular modules (cần thiết ở hầu hết các component).
@@ -171,6 +170,220 @@ const DEFAULT_ACCEPTED_IMAGE_MIME_TYPES = [
171
170
  IMAGE_TYPES.svg,
172
171
  ];
173
172
 
173
+ const COMBINE_DEPEND_TYPES = ['skip', 'null', 'valid'];
174
+
175
+ // Format file size, đầu vào là Byte, và loại muốn format sang.
176
+ const formatFileSize = (size, type) => {
177
+ let returnVal = 0;
178
+ switch (type) {
179
+ case 'KB':
180
+ returnVal = size / 1024;
181
+ break;
182
+ case 'MB':
183
+ returnVal = size / (1024 * 1024);
184
+ break;
185
+ case 'GB':
186
+ returnVal = size / (1024 * 1024 * 1024);
187
+ break;
188
+ case 'TB':
189
+ returnVal = size / (1024 * 1024 * 1024 * 1024);
190
+ break;
191
+ default:
192
+ returnVal = size;
193
+ break;
194
+ }
195
+ return returnVal;
196
+ };
197
+ // Lấy extension. dựa vào các cấu hình mimeTypes sẵn.
198
+ const getExtensions = (acptMimeTypes) => {
199
+ return acptMimeTypes.map(mime => `.${MIME_TYPES_TO_EXTENSION[mime] || mime}`);
200
+ };
201
+ // Tạo một file giả từ thông tin name, type và size.
202
+ const createFakeFile = (name, type, size) => {
203
+ const buffer = new Uint8Array(size); // tạo buffer theo size, không crash nếu file có dung lượng lớn.
204
+ const blob = new Blob([buffer], { type });
205
+ return new File([blob], name, { type, lastModified: Date.now() });
206
+ };
207
+
208
+ var FileSizeTypeEnum;
209
+ (function (FileSizeTypeEnum) {
210
+ FileSizeTypeEnum["MB"] = "MB";
211
+ FileSizeTypeEnum["KB"] = "KB";
212
+ FileSizeTypeEnum["GB"] = "GB";
213
+ FileSizeTypeEnum["TB"] = "TB";
214
+ })(FileSizeTypeEnum || (FileSizeTypeEnum = {}));
215
+
216
+ // Kiểm tra tính hợp lệ của JSON string.
217
+ const isValidJSON = (str) => {
218
+ try {
219
+ JSON.parse(str);
220
+ }
221
+ catch (e) {
222
+ return false;
223
+ }
224
+ return true;
225
+ };
226
+ // Tạo một chuỗi uuid.
227
+ const uuidGenerator = () => {
228
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
229
+ var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
230
+ return v.toString(16);
231
+ });
232
+ };
233
+ const lowerCaseFirstChar = (str) => {
234
+ return str.charAt(0).toLowerCase() + str.slice(1);
235
+ };
236
+ const upperCaseFirstChar = (str) => {
237
+ return str.charAt(0).toUpperCase() + str.slice(1);
238
+ };
239
+ const deepCopyArray = (array) => {
240
+ return array.map(item => {
241
+ if (Array.isArray(item)) {
242
+ return deepCopyArray(item); // Deep copy nếu phần tử là mảng lồng nhau
243
+ }
244
+ else if (item && typeof item === 'object') {
245
+ return deepCopyObject(item); // Deep copy nếu phần tử là đối tượng
246
+ }
247
+ return item; // Trả về giá trị gốc nếu không phải mảng hoặc đối tượng
248
+ });
249
+ };
250
+ const deepCopyObject = (obj) => {
251
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
252
+ if (Array.isArray(value)) {
253
+ return [key, deepCopyArray(value)]; // Deep copy nếu giá trị là mảng
254
+ }
255
+ else if (value && typeof value === 'object') {
256
+ return [key, deepCopyObject(value)]; // Deep copy nếu giá trị là đối tượng
257
+ }
258
+ return [key, value]; // Trả về giá trị gốc
259
+ }));
260
+ };
261
+
262
+ // Không cho phép nhập khoảng trắng ở đầu chuỗi, nếu không nhập gì vẫn tính đúng.
263
+ const noLeadingSpaceValidator = () => {
264
+ return (control) => {
265
+ const value = (control.value ?? "").toString();
266
+ if (!value)
267
+ return null; // Không required thì để trống là hợp lệ
268
+ return value[0] === " " ? { leadingSpace: true } : null;
269
+ };
270
+ };
271
+ // Không cho phép nhập toàn khoảng trắng.
272
+ const noWhitespaceValidator = () => {
273
+ return (control) => {
274
+ const isWhitespace = (control.value || "").toString().trim().length === 0;
275
+ return isWhitespace ? { whitespace: true } : null;
276
+ };
277
+ };
278
+ // Bắt đầu với một kí tự được chỉ định.
279
+ const startsWithValidator = (prefix) => {
280
+ return (control) => {
281
+ const value = control.value ?? "";
282
+ return value.startsWith(prefix)
283
+ ? null
284
+ : { startsWith: { requiredPrefix: prefix } };
285
+ };
286
+ };
287
+ // So sánh giá trị field A không được lớn hơn field B.
288
+ const minMaxFieldValidator = (minKey, maxKey) => {
289
+ // group: AbstractControl sẽ đại diện cho FormGroup tức là cha bao hàm các field.
290
+ return (group) => {
291
+ const min = group.get(minKey)?.value;
292
+ const max = group.get(maxKey)?.value;
293
+ if (min != null && max != null && min > max) {
294
+ return { minMaxInvalid: true };
295
+ }
296
+ return null;
297
+ };
298
+ };
299
+ // Match giữa hai field (ví dụ: password/confirmPassword,...).
300
+ const matchFieldsValidator = (fieldToMatch) => {
301
+ return (control) => {
302
+ const parent = control.parent;
303
+ if (!parent)
304
+ return null;
305
+ const target = parent.get(fieldToMatch);
306
+ if (!target?.value)
307
+ return null;
308
+ // Nếu đang validate chính nó (ví dụ: confirmPassword)
309
+ const isMismatch = control.value !== target.value;
310
+ // Nếu trước đó password (target) đang bị lỗi matchField nhưng giờ đã khớp
311
+ if (!isMismatch && target.hasError("matchField")) {
312
+ const errors = { ...target.errors };
313
+ delete errors["matchField"];
314
+ // Nếu không còn lỗi nào khác, set null
315
+ target.setErrors(Object.keys(errors).length ? errors : null);
316
+ }
317
+ return isMismatch ? { matchField: true } : null;
318
+ };
319
+ };
320
+ // Async: Check if username is taken (simulate API).
321
+ const asyncUsernameValidator = (takenUsernames) => {
322
+ return (control) => {
323
+ const value = control.value ?? "";
324
+ const isTaken = takenUsernames.includes(value);
325
+ return of(isTaken ? { usernameTaken: true } : null).pipe(delay(500));
326
+ };
327
+ };
328
+ // Không có ký tự đặc biệt.
329
+ const noSpecialCharValidator = () => {
330
+ return (control) => {
331
+ const valid = /^[a-zA-Z0-9]*$/.test(control.value);
332
+ return valid ? null : { noSpecialChar: true };
333
+ };
334
+ };
335
+ // Username không được nằm trong blacklist.
336
+ const usernameBlacklistValidator = (blacklist) => {
337
+ return (control) => {
338
+ return blacklist.includes(control.value) ? { blacklisted: true } : null;
339
+ };
340
+ };
341
+ // Có thuộc mimetypes từ mảng mimetypes được chỉ định hay không?
342
+ const acceptedMimeTypesValidator = (acceptedMimeTypes) => {
343
+ return (control) => {
344
+ const value = control?.value;
345
+ if (value) {
346
+ let fileType = "";
347
+ if (value instanceof File) {
348
+ fileType = value.type;
349
+ }
350
+ if (isValidJSON(value)) {
351
+ const { contentType } = JSON.parse(value)[0] || {};
352
+ if (contentType)
353
+ fileType = contentType;
354
+ }
355
+ if (fileType) {
356
+ if (!acceptedMimeTypes.includes(fileType)) {
357
+ return {
358
+ acceptedMimeTypes: {
359
+ actualValue: fileType,
360
+ requiredValue: `Nằm trong ${acceptedMimeTypes}`,
361
+ },
362
+ };
363
+ }
364
+ }
365
+ }
366
+ return null;
367
+ };
368
+ };
369
+ const maxFileSizeInMBValidator = (maxSizeInMB) => {
370
+ return (control) => {
371
+ const file = control?.value;
372
+ if (file) {
373
+ const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
374
+ if (fileSizeInMB > maxSizeInMB) {
375
+ return {
376
+ maxSizeInMB: {
377
+ actualValue: fileSizeInMB,
378
+ requiredValue: `Nhỏ hơn ${maxSizeInMB}`,
379
+ },
380
+ };
381
+ }
382
+ }
383
+ return null;
384
+ };
385
+ };
386
+
174
387
  // Enum cho các validator chung được angular reactive form hỗ trợ.
175
388
  var CommonValidaterEnum;
176
389
  (function (CommonValidaterEnum) {
@@ -190,46 +403,13 @@ var CustomValidaterEnum;
190
403
  CustomValidaterEnum["LeadingSpace"] = "leadingSpace";
191
404
  })(CustomValidaterEnum || (CustomValidaterEnum = {}));
192
405
 
193
- // Dạng control.
194
- var ControlTypeEnum;
195
- (function (ControlTypeEnum) {
196
- ControlTypeEnum["Input"] = "input";
197
- ControlTypeEnum["Textarea"] = "textarea";
198
- ControlTypeEnum["Select"] = "select";
199
- ControlTypeEnum["Checkbox"] = "checkbox";
200
- ControlTypeEnum["Radio"] = "radio";
201
- ControlTypeEnum["Date"] = "date";
202
- ControlTypeEnum["Upload"] = "upload";
203
- ControlTypeEnum["Switch"] = "switch";
204
- ControlTypeEnum["Autocomplete"] = "autocomplete";
205
- })(ControlTypeEnum || (ControlTypeEnum = {}));
206
-
207
- /**
208
- * Interface base chung cho Các dạng Date.
209
- */
210
- class BaseDateField extends BaseField {
211
- /**
212
- * Props validation chung cho các dạng Date.
213
- */
214
- constructor(init) {
215
- super(init);
216
- /**
217
- * Gán giá trị cho các props cần giá trị mặc định ở Date.
218
- */
219
- this.controlType = ControlTypeEnum.Date;
220
- this.allowPastDates = true;
221
- this.allowFutureDates = true;
222
- this.format = 'dd-MM-yyyy';
223
- // this.value = undefined;
224
- Object.assign(this, init);
225
- }
226
- // Tạo Form Control cho Input Field.
227
- toFormControl() {
228
- const commonValidators = this.getCommonValidators();
229
- const customValidators = this.getCustomValidators();
230
- return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
231
- }
232
- }
406
+ var FormModeEnum;
407
+ (function (FormModeEnum) {
408
+ FormModeEnum["Add"] = "add";
409
+ FormModeEnum["Edit"] = "edit";
410
+ FormModeEnum["View"] = "view";
411
+ FormModeEnum["Design"] = "design";
412
+ })(FormModeEnum || (FormModeEnum = {}));
233
413
 
234
414
  var SelectOutputValueTypeEnum;
235
415
  (function (SelectOutputValueTypeEnum) {
@@ -255,14 +435,6 @@ var UploadOutputValueTypeEnum;
255
435
  UploadOutputValueTypeEnum["Object"] = "object";
256
436
  })(UploadOutputValueTypeEnum || (UploadOutputValueTypeEnum = {}));
257
437
 
258
- var FileSizeTypeEnum;
259
- (function (FileSizeTypeEnum) {
260
- FileSizeTypeEnum["MB"] = "MB";
261
- FileSizeTypeEnum["KB"] = "KB";
262
- FileSizeTypeEnum["GB"] = "GB";
263
- FileSizeTypeEnum["TB"] = "TB";
264
- })(FileSizeTypeEnum || (FileSizeTypeEnum = {}));
265
-
266
438
  var CombineDependTypeEnum;
267
439
  (function (CombineDependTypeEnum) {
268
440
  CombineDependTypeEnum["Valid"] = "valid";
@@ -281,283 +453,77 @@ var ViewModeEnum;
281
453
  ViewModeEnum["text"] = "text";
282
454
  })(ViewModeEnum || (ViewModeEnum = {}));
283
455
 
284
- // Dạng field cụ thể.
285
- var FieldTypeEnum;
286
- (function (FieldTypeEnum) {
287
- FieldTypeEnum["InputText"] = "input-text";
288
- FieldTypeEnum["InputPassword"] = "input-password";
289
- FieldTypeEnum["Textarea"] = "textarea";
290
- FieldTypeEnum["DatePicker"] = "date-picker";
291
- FieldTypeEnum["DateRange"] = "date-range";
292
- FieldTypeEnum["SingleSelect"] = "single-select";
293
- FieldTypeEnum["TreeSelect"] = "tree-select";
294
- FieldTypeEnum["MultiSelect"] = "multi-select";
295
- FieldTypeEnum["SingleFileUpload"] = "single-file-upload";
296
- FieldTypeEnum["MultiFileUpload"] = "multi-file-upload";
297
- FieldTypeEnum["MultiImageUpload"] = "multi-image-upload";
298
- FieldTypeEnum["AvatarUpload"] = "avatar-upload";
299
- FieldTypeEnum["Checkbox"] = "checkbox";
300
- FieldTypeEnum["Radio"] = "radio";
301
- FieldTypeEnum["ReminderTypeRadio"] = "reminder-type-radio";
302
- FieldTypeEnum["Switch"] = "switch";
303
- FieldTypeEnum["Autocomplete"] = "autocomplete";
304
- })(FieldTypeEnum || (FieldTypeEnum = {}));
305
-
306
- var InputFieldTypeEnum;
307
- (function (InputFieldTypeEnum) {
308
- InputFieldTypeEnum["Text"] = "text";
309
- InputFieldTypeEnum["Email"] = "email";
310
- InputFieldTypeEnum["Password"] = "password";
311
- InputFieldTypeEnum["Number"] = "number";
312
- })(InputFieldTypeEnum || (InputFieldTypeEnum = {}));
313
-
314
- var DateFieldTypeEnum;
315
- (function (DateFieldTypeEnum) {
316
- DateFieldTypeEnum["Picker"] = "picker";
317
- DateFieldTypeEnum["Range"] = "range";
318
- })(DateFieldTypeEnum || (DateFieldTypeEnum = {}));
319
-
320
- var SelectFieldTypeEnum;
321
- (function (SelectFieldTypeEnum) {
322
- SelectFieldTypeEnum["Single"] = "single";
323
- SelectFieldTypeEnum["Multi"] = "multi";
324
- })(SelectFieldTypeEnum || (SelectFieldTypeEnum = {}));
325
-
326
- var UploadFieldTypeEnum;
327
- (function (UploadFieldTypeEnum) {
328
- UploadFieldTypeEnum["SingleFile"] = "single-file";
329
- UploadFieldTypeEnum["MultiFile"] = "multi-file";
330
- UploadFieldTypeEnum["Avatar"] = "avatar";
331
- })(UploadFieldTypeEnum || (UploadFieldTypeEnum = {}));
332
-
333
- /**
334
- * FIELD_TYPES là readonly ['input', 'select', 'checkbox',...]
335
- */
336
- const FIELD_TYPES = [
337
- 'input-text',
338
- 'input-password',
339
- 'textarea',
340
- 'date-picker',
341
- 'date-range',
342
- 'single-select',
343
- 'tree-select',
344
- 'multi-select',
345
- 'single-file-upload',
346
- 'multi-file-upload',
347
- 'multi-image-upload',
348
- 'avatar-upload',
349
- 'checkbox',
350
- 'radio',
351
- 'reminder-type-radio',
352
- 'switch',
353
- 'autocomplete',
354
- ];
355
-
356
- /**
357
- * FIELD_TYPES là readonly ['input', 'select', 'checkbox',...]
358
- */
359
- const INPUT_FIELD_TYPES = ['text', 'email', 'password', 'number'];
360
-
361
456
  /**
362
- * FIELD_TYPES readonly ['single', 'multi', 'tree',...]
457
+ * Abstract class chung cho Field.
363
458
  */
364
- const SELECT_FIELD_TYPES = ['single', 'multi', 'tree'];
365
- const DEFAULT_BIND_LABEL = 'name';
366
- const DEFAULT_BIND_VALUE = 'code';
367
-
368
- // Loại date field.
369
- const DATE_FIELD_TYPES = ['picker', 'range'];
370
- // Các pattern dùng để check định dạng date.
371
- const FORMAT_DATE_PATTERNS = {
372
- YYYY_MM_DD: /^(\d{4})([-\/])(0[1-9]|1[0-2])\2(0[1-9]|[12]\d|3[01])$/,
373
- DD_MM_YYYY: /^(0[1-9]|[12]\d|3[01])([-\/])(0[1-9]|1[0-2])\2(\d{4})$/,
374
- MM_DD_YYYY: /^(0[1-9]|1[0-2])([-\/])(0[1-9]|[12]\d|3[01])\2(\d{4})$/,
375
- MM_DD_YYYY_HMS: /^(0[1-9]|1[0-2])([-\/])(0[1-9]|[12]\d|3[01])\2(\d{4})\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/,
376
- };
377
-
378
- // Chuyển đổi đối tượng Date thành string có format DDMMYYYY.
379
- const formatDateToDDMMYYYY = (date, separator = '-') => {
380
- const dd = String(date.getDate()).padStart(2, '0');
381
- const mm = String(date.getMonth() + 1).padStart(2, '0');
382
- const yyyy = date.getFullYear();
383
- return `${dd}${separator}${mm}${separator}${yyyy}`;
384
- };
385
- // Chuyển đổi đối tượng Date thành string có format DDMMYYYY.
386
- const formatDateToYYYYMMDD = (date, separator = '-') => {
387
- const dd = String(date.getDate()).padStart(2, '0');
388
- const mm = String(date.getMonth() + 1).padStart(2, '0');
389
- const yyyy = date.getFullYear();
390
- return `${yyyy}${separator}${mm}${separator}${dd}`;
391
- };
392
- // Chuyển đổi string ở các dạng: YYYY-MM-DD, DD-MM-YYYY, MM-DD-YYYY đến Date, nếu sẵn là Date thì mặc định không đổi.
393
- const normalizeToDate = (input) => {
394
- if (!input)
395
- return null;
396
- if (input instanceof Date && !isNaN(input.getTime()))
397
- return input;
398
- if (typeof input !== 'string')
399
- return null;
400
- const inputTrimed = input.trim();
401
- let match;
402
- let year, month, day;
403
- let hour = 0, minute = 0, second = 0;
404
- if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.YYYY_MM_DD))) {
405
- year = +match[1];
406
- month = +match[3] - 1;
407
- day = +match[4];
408
- }
409
- else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.DD_MM_YYYY))) {
410
- day = +match[1];
411
- month = +match[3] - 1;
412
- year = +match[4];
413
- }
414
- else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY))) {
415
- month = +match[1] - 1;
416
- day = +match[3];
417
- year = +match[4];
418
- }
419
- else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY_HMS))) {
420
- month = +match[1] - 1;
421
- day = +match[3];
422
- year = +match[4];
423
- hour = +match[5];
424
- minute = +match[6];
425
- second = +match[7];
459
+ class BaseField {
460
+ constructor(init) {
461
+ // Thứ tự hiển thị của field
462
+ this.seqNum = 0;
463
+ if (!init?.key || !init?.label) {
464
+ throw new Error('key, label, and type are required');
465
+ }
466
+ /**
467
+ * Gán giá trị cho các props cần giá trị mặc định ở BaseField.
468
+ */
469
+ this.id = uuidGenerator();
470
+ this.required = false;
471
+ this.grid = { xs: 24 };
472
+ this.labelGrid = { xs: 24 };
473
+ this.controlGrid = { xs: 24 };
474
+ this.labelAlign = 'left';
475
+ this.isHidden = false;
476
+ this.isDisabled = false;
477
+ this.isParent = false;
478
+ this.allowSetValue = [FormModeEnum.Add, FormModeEnum.Edit, FormModeEnum.View];
479
+ this.viewMode = ViewModeEnum.form;
480
+ this.enableFieldChange = false;
481
+ Object.assign(this, init);
426
482
  }
427
- else {
428
- console.warn('Sai định dạng chuỗi date');
429
- return null;
483
+ // Tạo các validate chung cho các props ở abstract. this khi gọi trong class con sẽ đại diện cho đối tượng tạo từ class đó.
484
+ getCommonValidators() {
485
+ const validators = [];
486
+ if (this.required)
487
+ validators.push(Validators.required);
488
+ return validators;
430
489
  }
431
- const date = new Date(year, month, day, hour, minute, second);
432
- if (date.getFullYear() === year &&
433
- date.getMonth() === month &&
434
- date.getDate() === day &&
435
- date.getHours() === hour &&
436
- date.getMinutes() === minute &&
437
- date.getSeconds() === second) {
438
- return date;
490
+ // Tạo các validate custom chung cho các props ở abstract. this khi gọi trong class con sẽ đại diện cho đối tượng tạo từ class đó.
491
+ getCustomValidators() {
492
+ const validators = [];
493
+ if (this.matchField)
494
+ validators.push(matchFieldsValidator(this.matchField));
495
+ return validators;
439
496
  }
440
- console.warn('Ngày hoặc giờ không hợp lệ');
441
- return null;
442
- };
443
- const normalizeToDate1 = (input) => {
444
- if (input) {
445
- if (input instanceof Date && !isNaN(input.getTime()))
446
- return input;
447
- if (typeof input !== 'string')
448
- return null;
449
- const inputTrimed = String(input).trim();
450
- let match;
451
- let year, month, day;
452
- if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.YYYY_MM_DD))) {
453
- year = +match[1];
454
- month = +match[3] - 1;
455
- day = +match[4];
456
- }
457
- else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.DD_MM_YYYY))) {
458
- day = +match[1];
459
- month = +match[3] - 1;
460
- year = +match[4];
461
- }
462
- else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY))) {
463
- month = +match[1] - 1;
464
- day = +match[3];
465
- year = +match[4];
466
- }
467
- else {
468
- console.warn('Sai định dạng chuỗi date');
469
- return null;
470
- }
471
- const date = new Date(year, month, day); // Validate: kiểm tra lại giá trị date (28/29/30/31 hợp lệ)
472
- if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
473
- return date;
497
+ getErrorMessage(errorKey, errorValue, allFields) {
498
+ if (errorKey === CommonValidaterEnum.Required) {
499
+ return `${this.label || 'Trường này'} là bắt buộc`;
474
500
  }
475
- else {
476
- console.warn('Ngày đưa vào không hợp lệ');
477
- return null;
501
+ if (errorKey === CustomValidaterEnum.MatchField) {
502
+ const matchKey = this.matchField;
503
+ const targetLabel = allFields.find(f => f.key === matchKey)?.label || matchKey;
504
+ return `${this.label} không khớp với ${targetLabel}`;
478
505
  }
506
+ return '';
479
507
  }
480
- return null;
481
- };
482
- // Tạo danh sách năm chạy ngược từ hiện tại đến năm chỉ định.
483
- const generateYearsDesc = (from) => {
484
- const currentYear = new Date().getFullYear();
485
- const years = [];
486
- for (let year = currentYear; year >= from; year--) {
487
- years.push(year);
488
- }
489
- return years;
490
- };
491
- // Tạo danh sách năm chạy từ năm chỉ định đến năm hiện tại.
492
- const generateYearsAsc = (from) => {
493
- const currentYear = new Date().getFullYear();
494
- const years = [];
495
- for (let year = from; year <= currentYear; year++) {
496
- years.push(year);
497
- }
498
- return years;
499
- };
500
-
501
- const DEFAULT_DATE_PICKER_VALUE = '';
502
- /**
503
- * Interface của Date type là Picker.
504
- */
505
- class DatePickerField extends BaseDateField {
506
- constructor(init) {
507
- super(init);
508
- /**
509
- * Gán giá trị cho các props cần giá trị mặc định ở Date picker.
510
- */
511
- this.type = FieldTypeEnum.DatePicker;
512
- this.value = DEFAULT_DATE_PICKER_VALUE;
513
- this.initialValue = DEFAULT_DATE_PICKER_VALUE;
514
- this.outputAs = DatePickerOutputValueTypeEnum.String;
515
- Object.assign(this, init);
516
- }
517
- getDefaultValue() {
518
- return DEFAULT_DATE_PICKER_VALUE;
519
- }
520
- toFormValue(modelValue) {
521
- const value = modelValue;
522
- if (!value)
523
- return null;
524
- return normalizeToDate(value);
508
+ }
509
+
510
+ // Dữ liệu sau khi đã tiến hành xử gom nhóm từ fields và groups.
511
+ class GroupedFields {
512
+ constructor(init) {
513
+ this.noGroup = [];
514
+ this.grouped = [];
515
+ Object.assign(this, init);
525
516
  }
526
517
  }
527
-
528
- const DEFAULT_DATE_RANGE_VALUE = [];
529
- /**
530
- * Interface của Date type là Range.
531
- */
532
- class DateRangeField extends BaseDateField {
518
+ // Đại diện cho 1 group.
519
+ class FieldGroup {
533
520
  constructor(init) {
534
- super(init);
535
- /**
536
- * Gán giá trị cho các props cần giá trị mặc định ở Date range.
537
- */
538
- this.type = FieldTypeEnum.DateRange;
539
- this.value = DEFAULT_DATE_RANGE_VALUE;
540
- this.initialValue = DEFAULT_DATE_RANGE_VALUE;
541
- this.outputAs = DateRangeOutputValueTypeEnum.StringArray;
521
+ this.isActive = false;
522
+ this.showArrow = true;
523
+ this.disabled = false;
524
+ this.required = false;
542
525
  Object.assign(this, init);
543
526
  }
544
- getDefaultValue() {
545
- return DEFAULT_DATE_RANGE_VALUE;
546
- }
547
- toFormValue(modelValue) {
548
- let value = modelValue;
549
- if (typeof value === 'string') {
550
- value = value.split(',').map(dateStr => dateStr.trim());
551
- }
552
- let dateRange = [];
553
- if (value?.length === 2) {
554
- const startDate = normalizeToDate(value[0]);
555
- const endDate = normalizeToDate(value[1]);
556
- if (startDate && endDate)
557
- dateRange = [startDate, endDate];
558
- }
559
- return dateRange;
560
- }
561
527
  }
562
528
 
563
529
  // Các patterns regex thường dùng trong ứng dụng
@@ -680,6 +646,51 @@ const resolvePattern = (pattern) => {
680
646
  }
681
647
  };
682
648
 
649
+ const getDefaultPatternErrorMessage = (label, pattern) => {
650
+ let finalErrorMessage = '';
651
+ const resolvePatternResult = resolvePattern(pattern);
652
+ // if (resolvePatternResult?.type === 'unknown') {
653
+ // return finalErrorMessage; // Không phải pattern hợp lệ
654
+ // }
655
+ if (resolvePatternResult.type === 'regexObject') {
656
+ finalErrorMessage = `${label || 'Trường này'} không hợp lệ!`;
657
+ }
658
+ if (resolvePatternResult.type === 'regexKey') {
659
+ const patternMess = PATTERN_ERROR_MESSAGES?.[pattern] ?? '';
660
+ finalErrorMessage = `${label || 'Trường này'} không hợp lệ! ${patternMess.replace(/%s/g, () => label)}`;
661
+ }
662
+ return finalErrorMessage;
663
+ };
664
+ const getDefaultMinLengthErrorMessage = (label, errorValue) => {
665
+ return `${label || 'Trường này'} cần tối thiểu ${errorValue.requiredLength} ký tự`;
666
+ };
667
+ const getDefaultMaxLengthErrorMessage = (label, errorValue) => {
668
+ return `${label || 'Trường này'} cần đât thiểu ${errorValue.requiredLength} ký tự`;
669
+ };
670
+ const getDefaultAcptMimeTypesErrorMessage = (typeString) => {
671
+ return `Tập tin tải lên không được hỗ trợ. Vui lòng tải các tập tin có định dạng ${typeString}`;
672
+ };
673
+ const getDefaultMaxSizeInMBErrorMessage = (maxSizeInMB) => {
674
+ return `Kích thước tập tin tải lên không được phép vượt quá ${maxSizeInMB} MB`;
675
+ };
676
+ const getLeadingSpaceErrorMessage = (label) => {
677
+ return `${label || 'Trường này'} không được bắt đầu bằng khoảng trắng`;
678
+ };
679
+
680
+ // Dạng control.
681
+ var ControlTypeEnum;
682
+ (function (ControlTypeEnum) {
683
+ ControlTypeEnum["Input"] = "input";
684
+ ControlTypeEnum["Textarea"] = "textarea";
685
+ ControlTypeEnum["Select"] = "select";
686
+ ControlTypeEnum["Checkbox"] = "checkbox";
687
+ ControlTypeEnum["Radio"] = "radio";
688
+ ControlTypeEnum["Date"] = "date";
689
+ ControlTypeEnum["Upload"] = "upload";
690
+ ControlTypeEnum["Switch"] = "switch";
691
+ ControlTypeEnum["Autocomplete"] = "autocomplete";
692
+ })(ControlTypeEnum || (ControlTypeEnum = {}));
693
+
683
694
  const DEFAULT_INPUT_VALUE = '';
684
695
  /**
685
696
  * Interface base chung cho Các dạng Input Field.
@@ -745,6 +756,55 @@ class InputBaseField extends BaseField {
745
756
  }
746
757
  }
747
758
 
759
+ // Dạng field cụ thể.
760
+ var FieldTypeEnum;
761
+ (function (FieldTypeEnum) {
762
+ FieldTypeEnum["InputText"] = "input-text";
763
+ FieldTypeEnum["InputPassword"] = "input-password";
764
+ FieldTypeEnum["Textarea"] = "textarea";
765
+ FieldTypeEnum["DatePicker"] = "date-picker";
766
+ FieldTypeEnum["DateRange"] = "date-range";
767
+ FieldTypeEnum["SingleSelect"] = "single-select";
768
+ FieldTypeEnum["TreeSelect"] = "tree-select";
769
+ FieldTypeEnum["MultiSelect"] = "multi-select";
770
+ FieldTypeEnum["SingleFileUpload"] = "single-file-upload";
771
+ FieldTypeEnum["MultiFileUpload"] = "multi-file-upload";
772
+ FieldTypeEnum["MultiImageUpload"] = "multi-image-upload";
773
+ FieldTypeEnum["AvatarUpload"] = "avatar-upload";
774
+ FieldTypeEnum["Checkbox"] = "checkbox";
775
+ FieldTypeEnum["Radio"] = "radio";
776
+ FieldTypeEnum["ReminderTypeRadio"] = "reminder-type-radio";
777
+ FieldTypeEnum["Switch"] = "switch";
778
+ FieldTypeEnum["Autocomplete"] = "autocomplete";
779
+ })(FieldTypeEnum || (FieldTypeEnum = {}));
780
+
781
+ var InputFieldTypeEnum;
782
+ (function (InputFieldTypeEnum) {
783
+ InputFieldTypeEnum["Text"] = "text";
784
+ InputFieldTypeEnum["Email"] = "email";
785
+ InputFieldTypeEnum["Password"] = "password";
786
+ InputFieldTypeEnum["Number"] = "number";
787
+ })(InputFieldTypeEnum || (InputFieldTypeEnum = {}));
788
+
789
+ var DateFieldTypeEnum;
790
+ (function (DateFieldTypeEnum) {
791
+ DateFieldTypeEnum["Picker"] = "picker";
792
+ DateFieldTypeEnum["Range"] = "range";
793
+ })(DateFieldTypeEnum || (DateFieldTypeEnum = {}));
794
+
795
+ var SelectFieldTypeEnum;
796
+ (function (SelectFieldTypeEnum) {
797
+ SelectFieldTypeEnum["Single"] = "single";
798
+ SelectFieldTypeEnum["Multi"] = "multi";
799
+ })(SelectFieldTypeEnum || (SelectFieldTypeEnum = {}));
800
+
801
+ var UploadFieldTypeEnum;
802
+ (function (UploadFieldTypeEnum) {
803
+ UploadFieldTypeEnum["SingleFile"] = "single-file";
804
+ UploadFieldTypeEnum["MultiFile"] = "multi-file";
805
+ UploadFieldTypeEnum["Avatar"] = "avatar";
806
+ })(UploadFieldTypeEnum || (UploadFieldTypeEnum = {}));
807
+
748
808
  /**
749
809
  * Interface của Input Field type là Text.
750
810
  */
@@ -795,122 +855,105 @@ class InputPasswordField extends InputBaseField {
795
855
  }
796
856
  }
797
857
 
798
- /**
799
- * Interface base chung cho Các dạng Input Field.
800
- */
801
- class SelectBaseField extends BaseField {
802
- /**
803
- * Props validation chung cho các dạng Select.
804
- */
805
- // ................. Chưa .................
806
- constructor(init) {
807
- super(init);
808
- /**
809
- * Gán giá trị cho các props cần giá trị mặc định ở Select.
810
- */
811
- this.controlType = ControlTypeEnum.Select;
812
- this.bindLabel = DEFAULT_BIND_LABEL;
813
- this.bindValue = DEFAULT_BIND_VALUE;
814
- this.matchSelectWidth = true;
815
- this.outputAs = SelectOutputValueTypeEnum.Object;
816
- this.dependsOn = [];
817
- this.dependType = DependTypeEnum.All;
818
- this.suffixes = [];
819
- this.optionsToPrepend = [];
820
- this.optionsToAppend = [];
821
- this.options = [];
822
- this.exceptOptions = [];
823
- Object.assign(this, init);
824
- }
825
- // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi Reactive form của angular cho Select.
826
- getCommonSelectValidators() {
827
- const validators = this.getCommonValidators();
828
- return validators;
858
+ const buildForm = (fields, mode, model) => {
859
+ const groupObj = {};
860
+ // console.log("model", model);
861
+ for (const field of fields) {
862
+ // Loại bỏ nếu quyết định ẩn field đi.
863
+ if (field.isHidden)
864
+ continue;
865
+ // Nếu dạng InputPasswordField nhưng ở mode edit và view thì không được dùng.
866
+ if (field instanceof InputPasswordField &&
867
+ (mode === FormModeEnum.Edit || mode === FormModeEnum.View))
868
+ continue;
869
+ const fieldKey = field?.key;
870
+ // field.value = field.allowSetValue ? (model?.[fieldKey] || field.value) : null;
871
+ if (field.allowSetValue) {
872
+ field.value = field.getDefaultValue();
873
+ field.modelValue = null;
874
+ if (model?.[fieldKey]) {
875
+ field.value = field.toFormValue(model[fieldKey]);
876
+ field.modelValue = model[fieldKey];
877
+ }
878
+ field.initialValue = field.value;
879
+ }
880
+ // console.log("e", field.value );
881
+ // field.value = field.allowSetValue ? field.toFormValue(model?.[fieldKey] | field.getDefaultValue()) : field.getDefaultValue();
882
+ const control = field.toFormControl();
883
+ control.reset(field.value);
884
+ if (field.isDisabled) {
885
+ //console.log(field.key, 'dfdfdfdf');
886
+ control?.disable();
887
+ }
888
+ groupObj[fieldKey] = control;
829
889
  }
830
- // Mảng ValidatorFn các custom validation có thể dùng cho Select.
831
- getCustomSelectValidators() {
832
- const validators = this.getCustomValidators();
833
- return validators;
890
+ return new FormGroup(groupObj);
891
+ };
892
+ // Tạo placeholder mặc định cho các dạng Input, Textarea.
893
+ const createPlaceholder = (field) => {
894
+ const placeholder = field?.placeholder;
895
+ if (placeholder)
896
+ return placeholder;
897
+ const label = field?.label;
898
+ if (label)
899
+ return `Nhập ${label?.toLowerCase()}`;
900
+ return `Nhập ${field.key?.toLowerCase()}`;
901
+ };
902
+ // Tạo placeholder mặc định cho các dạng Select.
903
+ const createSelectPlaceholder = (field) => {
904
+ const placeholder = field?.placeholder;
905
+ if (placeholder)
906
+ return placeholder;
907
+ const label = field?.label;
908
+ if (label) {
909
+ const isSingleSelect = field?.type === FieldTypeEnum.SingleSelect;
910
+ return `Chọn ${isSingleSelect ? '' : 'các'} ${label?.toLowerCase()}`;
834
911
  }
835
- // Tạo Form Control cho Input Field.
836
- toFormControl() {
837
- const commonValidators = this.getCommonSelectValidators();
838
- const customValidators = this.getCustomSelectValidators();
839
- return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
840
- }
841
- getErrorMessage(errorKey, errorValue, allFields) {
842
- return super.getErrorMessage(errorKey, errorValue, allFields);
843
- }
844
- }
845
-
846
- const DEFAULT_MULTI_SELECT_VALUE = [];
847
- /**
848
- * Interface của Select Field type là dạng Multi.
849
- */
850
- class MultiSelectField extends SelectBaseField {
851
- constructor(init) {
852
- super(init);
853
- /**
854
- * Gán giá trị cho các props cần giá trị mặc định ở Multi Select.
855
- */
856
- this.type = FieldTypeEnum.MultiSelect;
857
- this.value = DEFAULT_MULTI_SELECT_VALUE;
858
- this.initialValue = DEFAULT_MULTI_SELECT_VALUE;
859
- this.maxTagCount = 53;
860
- this.maxMultipleCount = Infinity;
861
- Object.assign(this, init);
862
- }
863
- getDefaultValue() {
864
- return DEFAULT_MULTI_SELECT_VALUE;
865
- }
866
- toFormValue(modelValue) {
867
- const value = modelValue;
868
- const optionsValue = value.map((val) => {
869
- if (typeof val === 'object') {
870
- let transformVal = val?.[this.bindValue] ?? '';
871
- return transformVal;
872
- }
873
- return val;
874
- });
875
- return optionsValue;
876
- }
877
- }
912
+ return `Nhập ${field.key?.toLowerCase()}`;
913
+ };
878
914
 
879
- const DEFAULT_SINGLE_SELECT_VALUE = null;
880
- /**
881
- * Interface của Select Field type là dạng Single.
882
- */
883
- class SingleSelectField extends SelectBaseField {
884
- constructor(init) {
885
- super(init);
886
- /**
887
- * Gán giá trị cho các props cần giá trị mặc định ở Single Select.
888
- */
889
- this.type = FieldTypeEnum.SingleSelect;
890
- this.value = DEFAULT_SINGLE_SELECT_VALUE;
891
- this.initialValue = DEFAULT_SINGLE_SELECT_VALUE;
892
- Object.assign(this, init);
893
- }
894
- getDefaultValue() {
895
- return DEFAULT_SINGLE_SELECT_VALUE;
896
- }
897
- toFormValue(modelValue) {
898
- if (!modelValue)
899
- return null;
900
- const value = modelValue;
901
- if (typeof value === 'object') {
902
- const valueFromObj = value?.[this.bindValue];
903
- const typeOfValueFromObj = typeof valueFromObj;
904
- if (typeOfValueFromObj !== 'object')
905
- return String(valueFromObj);
906
- else {
907
- console.warn('Từ bindValue nhưng đối tượng vẫn dạng object');
908
- return '';
909
- }
910
- }
911
- return String(value);
912
- }
913
- }
915
+ const isInputTextField = (field) => {
916
+ return field.controlType === ControlTypeEnum.Input && field.type === FieldTypeEnum.InputText;
917
+ };
918
+ const isInputPasswordField = (field) => {
919
+ return field.controlType === ControlTypeEnum.Input && field.type === FieldTypeEnum.InputPassword;
920
+ };
921
+ const isSingleSelectField = (field) => {
922
+ return field.controlType === ControlTypeEnum.Select && field.type === FieldTypeEnum.SingleSelect;
923
+ };
924
+ const isMultiSelectField = (field) => {
925
+ return field.controlType === ControlTypeEnum.Select && field.type === FieldTypeEnum.MultiSelect;
926
+ };
927
+ const isDatePickerField = (field) => {
928
+ return field.controlType === ControlTypeEnum.Date && field.type === FieldTypeEnum.DatePicker;
929
+ };
930
+ const isDateRangeField = (field) => {
931
+ return field.controlType === ControlTypeEnum.Date && field.type === FieldTypeEnum.DateRange;
932
+ };
933
+ const isSingleFileUploadField = (field) => {
934
+ return (field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.SingleFileUpload);
935
+ };
936
+ const isMultiFileUploadField = (field) => {
937
+ return (field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.MultiFileUpload);
938
+ };
939
+ const isAvatarUploadField = (field) => {
940
+ return field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.AvatarUpload;
941
+ };
942
+ // Tạo FieldGroup render UI từ thông tin fields và groups truyền vào cho dynamic-form.
943
+ const buildGroupedFields = (fields, groups = []) => {
944
+ const noGroup = fields.filter(field => !field?.groupCode || field?.groupCode.toLowerCase() === 'none');
945
+ const sortedGroups = [...groups].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
946
+ const grouped = sortedGroups
947
+ .map(group => ({
948
+ group,
949
+ fields: fields.filter(field => field.groupCode?.toLowerCase() === group.code.toLowerCase()),
950
+ }))
951
+ .filter(groupedFields => groupedFields.fields.length > 0);
952
+ return new GroupedFields({
953
+ noGroup,
954
+ grouped,
955
+ });
956
+ };
914
957
 
915
958
  const DEFAULT_TEXTAREA_VALUE = '';
916
959
  /**
@@ -977,2190 +1020,506 @@ class TextareaField extends BaseField {
977
1020
  }
978
1021
 
979
1022
  /**
980
- * Interface base chung cho Các dạng Date.
1023
+ * FIELD_TYPES readonly ['single', 'multi', 'tree',...]
981
1024
  */
982
- class BaseUploadField extends BaseField {
1025
+ const SELECT_FIELD_TYPES = ['single', 'multi', 'tree'];
1026
+ const DEFAULT_BIND_LABEL = 'name';
1027
+ const DEFAULT_BIND_VALUE = 'code';
1028
+
1029
+ /**
1030
+ * Interface base chung cho Các dạng Input Field.
1031
+ */
1032
+ class SelectBaseField extends BaseField {
983
1033
  /**
984
- * Props validation chung cho các dạng Date.
1034
+ * Props validation chung cho các dạng Select.
985
1035
  */
1036
+ // ................. Chưa có .................
986
1037
  constructor(init) {
987
1038
  super(init);
988
1039
  /**
989
- * Gán giá trị cho các props cần giá trị mặc định ở Date.
1040
+ * Gán giá trị cho các props cần giá trị mặc định ở Select.
990
1041
  */
991
- this.controlType = ControlTypeEnum.Upload;
992
- this.outputAs = UploadOutputValueTypeEnum.Object;
993
- this.apiFileConfig = {
994
- uploadUrl: '',
995
- deleteUrl: '',
996
- };
997
- this.acceptedMimeTypes = DEFAULT_ACCEPTED_MIME_TYPES;
998
- this.maxSizeInMB = 25;
999
- this.showAcceptedExtensionGuide = true;
1000
- this.uploadTemp = true;
1042
+ this.controlType = ControlTypeEnum.Select;
1043
+ this.bindLabel = DEFAULT_BIND_LABEL;
1044
+ this.bindValue = DEFAULT_BIND_VALUE;
1045
+ this.matchSelectWidth = true;
1046
+ this.outputAs = SelectOutputValueTypeEnum.Object;
1047
+ this.dependsOn = [];
1048
+ this.dependType = DependTypeEnum.All;
1049
+ this.suffixes = [];
1050
+ this.optionsToPrepend = [];
1051
+ this.optionsToAppend = [];
1052
+ this.options = [];
1053
+ this.exceptOptions = [];
1001
1054
  Object.assign(this, init);
1002
1055
  }
1003
- // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi angular cho Input Password.
1004
- getCustomBaseUploadValidators() {
1056
+ // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi Reactive form của angular cho Select.
1057
+ getCommonSelectValidators() {
1058
+ const validators = this.getCommonValidators();
1059
+ return validators;
1060
+ }
1061
+ // Mảng ValidatorFn các custom validation có thể dùng cho Select.
1062
+ getCustomSelectValidators() {
1005
1063
  const validators = this.getCustomValidators();
1006
- // Kiểm tra xem file tải lên có type nằm trong mảng acceptedMimeTypes đã chỉ định hay không.
1007
- if (this.acceptedMimeTypes?.length > 0)
1008
- validators.push(acceptedMimeTypesValidator(this.acceptedMimeTypes));
1009
- // Kiểm tra kích thước tối đa mà file tải lên được cho phép.
1010
- validators.push(maxFileSizeInMBValidator(this.maxSizeInMB));
1011
1064
  return validators;
1012
1065
  }
1013
1066
  // Tạo Form Control cho Input Field.
1014
1067
  toFormControl() {
1015
- const commonValidators = this.getCommonValidators();
1016
- const customValidators = this.getCustomBaseUploadValidators();
1068
+ const commonValidators = this.getCommonSelectValidators();
1069
+ const customValidators = this.getCustomSelectValidators();
1017
1070
  return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
1018
1071
  }
1019
1072
  getErrorMessage(errorKey, errorValue, allFields) {
1020
- // Lấy error message mặc định của acceptedMimeTypes.
1021
- if (errorKey === CustomValidaterEnum.AcceptedMimiTypes) {
1022
- const typeString = getExtensions(this.acceptedMimeTypes).join(', ');
1023
- return getDefaultAcptMimeTypesErrorMessage(typeString);
1024
- }
1025
- // Lấy error message mặc định của maxSizeInMB.
1026
- if (errorKey === CustomValidaterEnum.MaxSizeInMB) {
1027
- return getDefaultMaxSizeInMBErrorMessage(this.maxSizeInMB);
1028
- }
1029
1073
  return super.getErrorMessage(errorKey, errorValue, allFields);
1030
1074
  }
1031
1075
  }
1032
1076
 
1033
- const DEFAULT_AVATAR_UPLOAD_VALUE = '';
1077
+ const DEFAULT_SINGLE_SELECT_VALUE = null;
1034
1078
  /**
1035
- * Interface của Avatar upload.
1079
+ * Interface của Select Field type là dạng Single.
1036
1080
  */
1037
- class AvatarUploadField extends BaseUploadField {
1081
+ class SingleSelectField extends SelectBaseField {
1038
1082
  constructor(init) {
1039
1083
  super(init);
1040
1084
  /**
1041
- * Gán giá trị cho các props cần giá trị mặc định ở Avatar upload.
1085
+ * Gán giá trị cho các props cần giá trị mặc định ở Single Select.
1042
1086
  */
1043
- this.type = FieldTypeEnum.AvatarUpload;
1044
- this.value = DEFAULT_AVATAR_UPLOAD_VALUE;
1045
- this.acceptedMimeTypes = DEFAULT_ACCEPTED_IMAGE_MIME_TYPES;
1046
- this.initialValue = DEFAULT_AVATAR_UPLOAD_VALUE;
1087
+ this.type = FieldTypeEnum.SingleSelect;
1088
+ this.value = DEFAULT_SINGLE_SELECT_VALUE;
1089
+ this.initialValue = DEFAULT_SINGLE_SELECT_VALUE;
1047
1090
  Object.assign(this, init);
1048
1091
  }
1049
1092
  getDefaultValue() {
1050
- return DEFAULT_AVATAR_UPLOAD_VALUE;
1093
+ return DEFAULT_SINGLE_SELECT_VALUE;
1051
1094
  }
1052
1095
  toFormValue(modelValue) {
1053
- return String(modelValue);
1096
+ if (!modelValue)
1097
+ return null;
1098
+ const value = modelValue;
1099
+ if (typeof value === 'object') {
1100
+ const valueFromObj = value?.[this.bindValue];
1101
+ const typeOfValueFromObj = typeof valueFromObj;
1102
+ if (typeOfValueFromObj !== 'object')
1103
+ return String(valueFromObj);
1104
+ else {
1105
+ console.warn('Từ bindValue nhưng đối tượng vẫn là dạng object');
1106
+ return '';
1107
+ }
1108
+ }
1109
+ return String(value);
1054
1110
  }
1055
1111
  }
1056
1112
 
1057
- const DEFAULT_MULTI_FILE_UPLOAD_VALUE = '';
1113
+ const DEFAULT_MULTI_SELECT_VALUE = [];
1058
1114
  /**
1059
- * Interface của Multi file upload.
1115
+ * Interface của Select Field type là dạng Multi.
1060
1116
  */
1061
- class MultiFileUploadField extends BaseUploadField {
1117
+ class MultiSelectField extends SelectBaseField {
1062
1118
  constructor(init) {
1063
1119
  super(init);
1064
1120
  /**
1065
- * Gán giá trị cho các props cần giá trị mặc định ở Multi file upload.
1121
+ * Gán giá trị cho các props cần giá trị mặc định ở Multi Select.
1066
1122
  */
1067
- this.type = FieldTypeEnum.MultiFileUpload;
1068
- this.value = DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1069
- this.initialValue = DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1070
- this.maxFileCount = 6;
1123
+ this.type = FieldTypeEnum.MultiSelect;
1124
+ this.value = DEFAULT_MULTI_SELECT_VALUE;
1125
+ this.initialValue = DEFAULT_MULTI_SELECT_VALUE;
1126
+ this.maxTagCount = 53;
1127
+ this.maxMultipleCount = Infinity;
1071
1128
  Object.assign(this, init);
1072
1129
  }
1073
1130
  getDefaultValue() {
1074
- return DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1131
+ return DEFAULT_MULTI_SELECT_VALUE;
1075
1132
  }
1076
1133
  toFormValue(modelValue) {
1077
- return String(modelValue);
1134
+ const value = modelValue;
1135
+ const optionsValue = value.map((val) => {
1136
+ if (typeof val === 'object') {
1137
+ let transformVal = val?.[this.bindValue] ?? '';
1138
+ return transformVal;
1139
+ }
1140
+ return val;
1141
+ });
1142
+ return optionsValue;
1078
1143
  }
1079
1144
  }
1080
1145
 
1081
- const DEFAULT_MULTI_IMAGE_UPLOAD_VALUE = '';
1082
1146
  /**
1083
- * Interface của Multi image upload.
1147
+ * Interface base chung cho Các dạng Date.
1084
1148
  */
1085
- class MultiImageUploadField extends BaseUploadField {
1149
+ class BaseDateField extends BaseField {
1150
+ /**
1151
+ * Props validation chung cho các dạng Date.
1152
+ */
1086
1153
  constructor(init) {
1087
1154
  super(init);
1088
1155
  /**
1089
- * Gán giá trị cho các props cần giá trị mặc định ở Multi image upload.
1156
+ * Gán giá trị cho các props cần giá trị mặc định ở Date.
1090
1157
  */
1091
- this.type = FieldTypeEnum.MultiImageUpload;
1092
- this.value = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1093
- this.initialValue = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1094
- this.maxFileCount = 6;
1095
- this.imageCardWidth = '108px';
1096
- this.imageCardHeight = '108px';
1158
+ this.controlType = ControlTypeEnum.Date;
1159
+ this.allowPastDates = true;
1160
+ this.allowFutureDates = true;
1161
+ this.format = 'dd-MM-yyyy';
1162
+ // this.value = undefined;
1097
1163
  Object.assign(this, init);
1098
1164
  }
1099
- getDefaultValue() {
1100
- return DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1101
- }
1102
- toFormValue(modelValue) {
1103
- return String(modelValue);
1165
+ // Tạo Form Control cho Input Field.
1166
+ toFormControl() {
1167
+ const commonValidators = this.getCommonValidators();
1168
+ const customValidators = this.getCustomValidators();
1169
+ return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
1104
1170
  }
1105
1171
  }
1106
1172
 
1107
- const DEFAULT_SINGLE_FILE_UPLOAD_VALUE = '';
1108
1173
  /**
1109
- * Interface của Single file upload. Đầu vào có thể chuỗi JSON hoặc đối tượng File.
1174
+ * FIELD_TYPESreadonly ['input', 'select', 'checkbox',...]
1110
1175
  */
1111
- class SingleFileUploadField extends BaseUploadField {
1112
- constructor(init) {
1113
- super(init);
1114
- /**
1115
- * Gán giá trị cho các props cần giá trị mặc định ở Single file upload.
1116
- */
1117
- this.type = FieldTypeEnum.SingleFileUpload;
1118
- this.value = DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
1119
- this.initialValue = DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
1120
- this.suffixes = [];
1121
- Object.assign(this, init);
1122
- }
1123
- getDefaultValue() {
1124
- return DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
1125
- }
1126
- toFormValue(modelValue) {
1127
- return String(modelValue);
1128
- }
1129
- }
1130
-
1131
- // Sample extra.
1132
- // InputText
1133
- const inputTextExtra = {
1134
- form: {
1135
- formTypes: ['add'],
1136
- configs: {
1137
- add: {
1138
- key: 'name',
1139
- label: 'Tên nhiệm vụ đề xuất',
1140
- type: 'input-text',
1141
- controlType: 'input',
1142
- placeholder: 'Nhập tên nhiệm vụ đề xuất nào',
1143
- required: false,
1144
- grid: { xs: 12 },
1145
- labelGrid: { xs: 8 },
1146
- controlGrid: { xs: 16 },
1147
- isHidden: false,
1148
- isParent: false,
1149
- allowSetValue: ['add'],
1150
- },
1151
- },
1152
- },
1153
- };
1154
- // Input Password
1155
- // SingleSelect Province
1156
- // SingleSelect District
1157
- // DatePicker
1158
- // DateRange
1159
- // SingleFileUpload
1160
- // MultiFileUpload
1161
- // Sample test ObjFields lấy từ API.
1162
- const sampleObjFields = [
1163
- // Input Text
1164
- {
1165
- code: 'Name',
1166
- name: 'Tên nhiệm vụ',
1167
- nameEn: 'Name',
1168
- dataType: 'string',
1169
- allowShare: 1,
1170
- objClassCode: 'SciTaskProposal',
1171
- componentUI: 'InputText',
1172
- codeOnDb: 'tenNhiemVu',
1173
- seqNum: 3,
1174
- required: 3,
1175
- allowStatistic: 0,
1176
- preventEdit: 0,
1177
- validateRegex: 'ALPHA_DASH_VN',
1178
- extra: JSON.stringify(inputTextExtra),
1179
- id: '7902fd41-f7c7-47e3-86f4-29b59a97dae3',
1180
- },
1181
- // Input Password
1182
- // SingleSelect Province
1183
- // SingleSelect District
1184
- // DatePicker
1185
- // DateRange
1186
- // SingleFileUpload
1187
- // MultiFileUpload
1188
- // Field rác test k có cấu hình 1
1189
- // Field rác test k có cấu hình 2
1190
- // {
1191
- // code: 'Code',
1192
- // name: 'Mã đề xuất',
1193
- // nameEn: 'Code',
1194
- // dataType: 'string',
1195
- // allowShare: 1,
1196
- // objClassCode: 'SciTaskProposal',
1197
- // componentUI: 'InputText',
1198
- // codeOnDb: 'maDeXuat',
1199
- // seqNum: 1,
1200
- // required: 2,
1201
- // allowStatistic: 0,
1202
- // preventEdit: 0,
1203
- // viewType: 'Basic',
1204
- // id: '39d81def-6a34-4594-ab59-978233bea797',
1205
- // },
1206
- // {
1207
- // code: 'Id',
1208
- // name: 'Id',
1209
- // nameEn: 'Id',
1210
- // dataType: 'Guid',
1211
- // allowShare: 1,
1212
- // objClassCode: 'SciTaskProposal',
1213
- // componentUI: 'TextView',
1214
- // codeOnDb: 'id',
1215
- // seqNum: 1,
1216
- // required: 2,
1217
- // allowStatistic: 0,
1218
- // preventEdit: 1,
1219
- // validateRegex: 'UUID',
1220
- // viewType: 'Detail',
1221
- // id: '0e460f53-76b0-4718-be0a-7e87c4a252a1',
1222
- // },
1223
- // {
1224
- // code: 'ProposalName',
1225
- // name: 'Tên đề xuất',
1226
- // nameEn: 'ProposalName',
1227
- // dataType: 'string',
1228
- // allowShare: 1,
1229
- // objClassCode: 'SciTaskProposal',
1230
- // componentUI: 'InputText',
1231
- // codeOnDb: 'tenDeXuat',
1232
- // seqNum: 2,
1233
- // required: 1,
1234
- // allowStatistic: 0,
1235
- // preventEdit: 0,
1236
- // viewType: 'Detail',
1237
- // id: '425739aa-0ab5-4524-8eec-1ccfaaf53ccc',
1238
- // },
1239
- // {
1240
- // code: 'Name',
1241
- // name: 'Tên nhiệm vụ',
1242
- // nameEn: 'Name',
1243
- // dataType: 'string',
1244
- // allowShare: 1,
1245
- // objClassCode: 'SciTaskProposal',
1246
- // componentUI: 'InputText',
1247
- // codeOnDb: 'tenNhiemVu',
1248
- // seqNum: 3,
1249
- // required: 3,
1250
- // allowStatistic: 0,
1251
- // preventEdit: 0,
1252
- // validateRegex: 'ALPHA_DASH_VN',
1253
- // extra:
1254
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":1,"viewType":"taskInfo"},"editting":{"seqNumber":1,"viewType":"taskInfo"},"viewing":{"seqNumber":1,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}}}}',
1255
- // viewType: 'Basic',
1256
- // id: '7902fd41-f7c7-47e3-86f4-29b59a97dae3',
1257
- // },
1258
- // {
1259
- // code: 'Level',
1260
- // name: 'Cấp nhiệm vụ đề xuất',
1261
- // nameEn: 'Level',
1262
- // dataType: 'ObjType',
1263
- // allowShare: 1,
1264
- // objClassCode: 'SciTaskProposal',
1265
- // apiURL: '{MainApiURL}/CommonCats/ObjType/byTypeGroupCode/{ObjTypeGroupCode}',
1266
- // componentUI: 'Combobox(SingleChoice)',
1267
- // codeOnDb: 'cap',
1268
- // seqNum: 4,
1269
- // required: 3,
1270
- // allowStatistic: 0,
1271
- // preventEdit: 0,
1272
- // extra:
1273
- // '{"formField":{"displayMode":["add","edit"],"adding":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":2,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}}}}',
1274
- // viewType: 'Detail',
1275
- // objTypeGroupCode: 'RECOMMEND_TASK',
1276
- // id: '03230f20-2c88-4c05-a990-8290d435ce31',
1277
- // },
1278
- // {
1279
- // code: 'Program',
1280
- // name: 'Chương trình mà nhiệm vụ trực thuộc',
1281
- // nameEn: 'Program',
1282
- // dataType: 'string',
1283
- // allowShare: 1,
1284
- // objClassCode: 'SciTaskProposal',
1285
- // componentUI: 'InputText',
1286
- // codeOnDb: 'chuongTrinh',
1287
- // seqNum: 5,
1288
- // required: 1,
1289
- // allowStatistic: 0,
1290
- // preventEdit: 0,
1291
- // validateRegex: 'ALPHA_DASH_VN',
1292
- // extra:
1293
- // '{"formField":{"displayMode":["add","edit"],"adding":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":2,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}}}}',
1294
- // viewType: 'Detail',
1295
- // id: 'c03a9d28-ca43-44ca-a59c-fc54494f3c73',
1296
- // },
1297
- // {
1298
- // code: 'ResearchField',
1299
- // name: 'Lĩnh vực',
1300
- // nameEn: 'ResearchField',
1301
- // dataType: 'ObjType',
1302
- // allowShare: 1,
1303
- // objClassCode: 'SciTaskProposal',
1304
- // apiURL: '{MainApiURL}/CommonCats/ObjType/byTypeGroupCode/{ObjTypeGroupCode}',
1305
- // componentUI: 'Combobox(SingleChoice)',
1306
- // codeOnDb: 'linhVucNghienCuu',
1307
- // seqNum: 6,
1308
- // required: 3,
1309
- // allowStatistic: 0,
1310
- // preventEdit: 0,
1311
- // extra:
1312
- // '{"formField":{"displayMode":["add","edit","view","filter"],"adding":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"seqNumber":2,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":2,"section":"left","isShowBgInput":true,"inputBgColor":"#2E7D32","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}},"filtering":{"codeField":"researchFieldCode","seqNumber":3,"required":false,"dataTypeCombobox":"single"}}}',
1313
- // viewType: 'Detail',
1314
- // objTypeGroupCode: 'RESEARCH_FIELD',
1315
- // id: 'b5248535-fb73-486e-a980-24e8ada1993b',
1316
- // },
1317
- // {
1318
- // code: 'Type',
1319
- // name: 'Loại hình nhiệm vụ',
1320
- // nameEn: 'Type',
1321
- // dataType: 'ObjType',
1322
- // allowShare: 1,
1323
- // objClassCode: 'SciTaskProposal',
1324
- // apiURL: '{MainApiURL}/CommonCats/ObjType/byTypeGroupCode/{ObjTypeGroupCode}',
1325
- // componentUI: 'Combobox(SingleChoice)',
1326
- // codeOnDb: 'loaiHinhNhiemVu',
1327
- // seqNum: 7,
1328
- // required: 3,
1329
- // allowStatistic: 0,
1330
- // preventEdit: 0,
1331
- // extra:
1332
- // '{"formField":{"displayMode":["add","edit","view","filter"],"adding":{"seqNumber":3,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"seqNumber":3,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":3,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}},"filtering":{"codeField":"typeCode","seqNumber":1,"required":false,"dataTypeCombobox": "single"}}}',
1333
- // viewType: 'Detail',
1334
- // objTypeGroupCode: 'TYPE_TASK',
1335
- // id: 'f6793a1e-f451-4050-b751-aa3a5f86d862',
1336
- // },
1337
- // {
1338
- // code: 'PlanningProposalYear',
1339
- // name: 'Năm kế hoạch đề xuất',
1340
- // nameEn: 'PlanningProposalYear',
1341
- // dataType: 'int',
1342
- // allowShare: 1,
1343
- // objClassCode: 'SciTaskProposal',
1344
- // componentUI: 'InputText',
1345
- // codeOnDb: 'namKeHoachDeXuat',
1346
- // seqNum: 8,
1347
- // required: 1,
1348
- // allowStatistic: 0,
1349
- // preventEdit: 0,
1350
- // viewType: 'Detail',
1351
- // id: '90f48f9f-5d3d-43d0-bbac-1dd072a4ffd1',
1352
- // },
1353
- // {
1354
- // code: 'ResearchObjective',
1355
- // name: 'Mục tiêu của nhiệm vụ ',
1356
- // nameEn: 'ResearchObjective',
1357
- // dataType: 'string',
1358
- // allowShare: 1,
1359
- // objClassCode: 'SciTaskProposal',
1360
- // componentUI: 'TextArea',
1361
- // codeOnDb: 'mucTieu',
1362
- // seqNum: 9,
1363
- // required: 3,
1364
- // allowStatistic: 0,
1365
- // preventEdit: 0,
1366
- // extra:
1367
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":3,"viewType":"taskInfo"},"editting":{"seqNumber":3,"viewType":"taskInfo"},"viewing":{"seqNumber":1,"section":"bottom"}}}',
1368
- // viewType: 'Detail',
1369
- // id: 'ecc54ec4-ee0e-4b97-9c5b-2e06d25d6bf0',
1370
- // },
1371
- // {
1372
- // code: 'ExpectedContent',
1373
- // name: 'Dự kiến các nội dung cần thực hiện',
1374
- // nameEn: 'ExpectedContent',
1375
- // dataType: 'string',
1376
- // allowShare: 1,
1377
- // objClassCode: 'SciTaskProposal',
1378
- // componentUI: 'InputText',
1379
- // codeOnDb: 'duKienNoiDung',
1380
- // seqNum: 10,
1381
- // required: 1,
1382
- // allowStatistic: 0,
1383
- // preventEdit: 0,
1384
- // viewType: 'Detail',
1385
- // id: '180c8300-11da-41f4-8685-97d2e8885200',
1386
- // },
1387
- // {
1388
- // code: 'ExpectedResultsAndIndicators',
1389
- // name: 'Dự kiến kết quả chính và các chỉ tiêu cần đạt',
1390
- // nameEn: 'ExpectedResultsAndIndicators',
1391
- // dataType: 'string',
1392
- // allowShare: 1,
1393
- // objClassCode: 'SciTaskProposal',
1394
- // componentUI: 'TextArea',
1395
- // codeOnDb: 'duKienKetQuaVaChiTieu',
1396
- // seqNum: 11,
1397
- // required: 3,
1398
- // allowStatistic: 0,
1399
- // preventEdit: 0,
1400
- // extra:
1401
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":5,"viewType":"taskInfo"},"editting":{"seqNumber":5,"viewType":"taskInfo"},"viewing":{"seqNumber":2,"section":"bottom"}}}',
1402
- // viewType: 'Detail',
1403
- // id: '617af2b0-d4d8-4e41-b0cb-388c4fbd917a',
1404
- // },
1405
- // {
1406
- // code: 'ExpectedApplicationPlan',
1407
- // name: 'Dự kiến phương án ứng dụng hoặc sử dụng kết quả tạo ra',
1408
- // nameEn: 'ExpectedApplicationPlan',
1409
- // dataType: 'string',
1410
- // allowShare: 1,
1411
- // objClassCode: 'SciTaskProposal',
1412
- // componentUI: 'TextArea',
1413
- // codeOnDb: 'duKienPhuongAnUngDung',
1414
- // seqNum: 12,
1415
- // required: 3,
1416
- // allowStatistic: 0,
1417
- // preventEdit: 0,
1418
- // extra:
1419
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":6,"viewType":"taskInfo"},"editting":{"seqNumber":6,"viewType":"taskInfo"},"viewing":{"seqNumber":3,"section":"bottom"}}}',
1420
- // viewType: 'Detail',
1421
- // id: 'c89bb6c9-3dcf-4380-94f5-630baadb9b31',
1422
- // },
1423
- // {
1424
- // code: 'EstimatedBudget',
1425
- // name: 'Dự kiến kinh phí (đồng)',
1426
- // nameEn: 'EstimatedBudget',
1427
- // dataType: 'long',
1428
- // allowShare: 1,
1429
- // objClassCode: 'SciTaskProposal',
1430
- // componentUI: 'InputText',
1431
- // codeOnDb: 'duKienKinhPhi',
1432
- // seqNum: 13,
1433
- // required: 3,
1434
- // allowStatistic: 0,
1435
- // preventEdit: 0,
1436
- // validateRegex: 'INTEGER',
1437
- // extra:
1438
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":7,"span":{"sm":24,"md":"12"},"viewType":"taskInfo","isCurrency":true},"editting":{"seqNumber":7,"span":{"sm":24,"md":"12"},"viewType":"taskInfo","isCurrency":true},"viewing":{"seqNumber":4,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}},"isCurrency":true}}}',
1439
- // viewType: 'Detail',
1440
- // id: 'a8072db6-0bd1-4352-8e30-326446989eef',
1441
- // },
1442
- // {
1443
- // code: 'EstimatedDuration',
1444
- // name: 'Dự kiến thời gian thực hiện (tháng)',
1445
- // nameEn: 'EstimatedDuration',
1446
- // dataType: 'int',
1447
- // allowShare: 1,
1448
- // objClassCode: 'SciTaskProposal',
1449
- // componentUI: 'InputText',
1450
- // codeOnDb: 'duKienThoiGianThucHien',
1451
- // seqNum: 14,
1452
- // required: 3,
1453
- // allowStatistic: 0,
1454
- // preventEdit: 0,
1455
- // validateRegex: 'INTEGER',
1456
- // extra:
1457
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":8,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"seqNumber":8,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":5,"section":"left","executionTime":"month","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}}}}',
1458
- // viewType: 'Detail',
1459
- // id: 'f69bdf14-c555-4cf5-b793-1eba85a25701',
1460
- // },
1461
- // {
1462
- // code: 'ProposalAttachment',
1463
- // name: 'Văn bản đề xuất đính kèm (pdf)',
1464
- // nameEn: 'ProposalAttachment',
1465
- // dataType: 'string',
1466
- // allowShare: 1,
1467
- // objClassCode: 'SciTaskProposal',
1468
- // componentUI: 'File',
1469
- // codeOnDb: 'vanBanDeXuat',
1470
- // seqNum: 15,
1471
- // required: 3,
1472
- // allowStatistic: 0,
1473
- // preventEdit: 0,
1474
- // extra:
1475
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"title":"Văn bản đề xuất đính kèm (hỗ trợ tập tin dạng .pdf)","seqNumber":9,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"editting":{"title":"Văn bản đề xuất đính kèm (hỗ trợ tập tin dạng .pdf)","seqNumber":9,"span":{"sm":24,"md":"12"},"viewType":"taskInfo"},"viewing":{"seqNumber":7,"section":"left","viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}}}}',
1476
- // viewType: 'Detail',
1477
- // id: '75596544-c7b1-40dd-ad24-deee21e8b8d1',
1478
- // },
1479
- // {
1480
- // code: 'ProposalOrganizationName',
1481
- // name: 'Tên đơn vị/cá nhân đề xuất',
1482
- // nameEn: 'ProposalOrganizationName',
1483
- // dataType: 'string',
1484
- // allowShare: 1,
1485
- // objClassCode: 'SciTaskProposal',
1486
- // componentUI: 'InputText',
1487
- // codeOnDb: 'tenToChucDeXuat',
1488
- // seqNum: 16,
1489
- // required: 3,
1490
- // allowStatistic: 0,
1491
- // preventEdit: 0,
1492
- // validateRegex: 'ALPHA_DASH_VN',
1493
- // extra:
1494
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":2,"viewType":"taskOrgzIndivInfo"},"editting":{"seqNumber":2,"viewType":"taskOrgzIndivInfo"},"viewing":{"seqNumber":1,"section":"right"}}}',
1495
- // viewType: 'Detail',
1496
- // id: '9ba7ea0a-e1d3-4006-9a10-bba7b54f17e4',
1497
- // },
1498
- // {
1499
- // code: 'ProposerName',
1500
- // name: 'Tên cá nhân đề xuất',
1501
- // nameEn: 'ProposerName',
1502
- // dataType: 'string',
1503
- // allowShare: 1,
1504
- // objClassCode: 'SciTaskProposal',
1505
- // componentUI: 'InputText',
1506
- // codeOnDb: 'tenCaNhanDeXuat',
1507
- // seqNum: 17,
1508
- // required: 3,
1509
- // allowStatistic: 0,
1510
- // preventEdit: 0,
1511
- // extra: '',
1512
- // viewType: 'Detail',
1513
- // id: 'cb2e752c-ed93-46f1-8cd8-75aeb399952b',
1514
- // },
1515
- // {
1516
- // code: 'PhoneNumber',
1517
- // name: 'Số điện thoại',
1518
- // nameEn: 'PhoneNumber',
1519
- // dataType: 'string',
1520
- // allowShare: 1,
1521
- // objClassCode: 'SciTaskProposal',
1522
- // componentUI: 'InputText',
1523
- // codeOnDb: 'soDienThoai',
1524
- // seqNum: 18,
1525
- // required: 1,
1526
- // allowStatistic: 0,
1527
- // preventEdit: 0,
1528
- // validateRegex: 'PHONE_NUMBER',
1529
- // extra:
1530
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":4,"viewType":"taskOrgzIndivInfo","span":{"sm":24,"md":12}},"editting":{"seqNumber":4,"viewType":"taskOrgzIndivInfo","span":{"sm":24,"md":12}},"viewing":{"seqNumber":3,"section":"right"}}}',
1531
- // viewType: 'Detail',
1532
- // id: '06137a76-2793-42db-a0d1-2039e087b2ce',
1533
- // },
1534
- // {
1535
- // code: 'Email',
1536
- // name: 'Thư điện tử',
1537
- // nameEn: 'Email',
1538
- // dataType: 'string',
1539
- // allowShare: 1,
1540
- // objClassCode: 'SciTaskProposal',
1541
- // componentUI: 'InputText',
1542
- // codeOnDb: 'thuDienTu',
1543
- // seqNum: 19,
1544
- // required: 3,
1545
- // allowStatistic: 0,
1546
- // preventEdit: 0,
1547
- // validateRegex: 'EMAIL',
1548
- // extra:
1549
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":3,"viewType":"taskOrgzIndivInfo","span":{"sm":24,"md":12}},"editting":{"seqNumber":3,"viewType":"taskOrgzIndivInfo","span":{"sm":24,"md":12}},"viewing":{"seqNumber":2,"section":"right"}}}',
1550
- // viewType: 'Detail',
1551
- // id: 'acef6046-1f41-48f2-b53a-c0ecebb42939',
1552
- // },
1553
- // {
1554
- // code: 'Address',
1555
- // name: 'Địa chỉ liên hệ',
1556
- // nameEn: 'Address',
1557
- // dataType: 'string',
1558
- // allowShare: 1,
1559
- // objClassCode: 'SciTaskProposal',
1560
- // componentUI: 'TextArea',
1561
- // codeOnDb: 'diaChi',
1562
- // seqNum: 20,
1563
- // required: 1,
1564
- // allowStatistic: 0,
1565
- // preventEdit: 0,
1566
- // extra:
1567
- // '{"formField":{"displayMode":["add","edit","view"],"adding":{"seqNumber":4,"viewType":"taskOrgzIndivInfo"},"editting":{"seqNumber":4,"viewType":"taskOrgzIndivInfo"},"viewing":{"type":"InputText","seqNumber":3,"section":"right"}}}',
1568
- // viewType: 'Detail',
1569
- // id: 'faef0e51-f288-424e-9e72-50ed155313a5',
1570
- // },
1571
- // {
1572
- // code: 'ProposalSource',
1573
- // name: 'Nguồn đề xuất',
1574
- // nameEn: 'ProposalSource',
1575
- // dataType: 'ObjType',
1576
- // allowShare: 1,
1577
- // objClassCode: 'SciTaskProposal',
1578
- // apiURL: '{MainApiURL}/CommonCats/ObjType/byTypeGroupCode/{ObjTypeGroupCode}',
1579
- // componentUI: 'Radiobox',
1580
- // codeOnDb: 'nguonDeXuat',
1581
- // seqNum: 21,
1582
- // required: 3,
1583
- // allowStatistic: 0,
1584
- // preventEdit: 0,
1585
- // extra:
1586
- // '{"formField":{"displayMode":["add","edit","filter"],"adding":{"seqNumber":1,"viewType":"taskOrgzIndivInfo"},"editting":{"seqNumber":1,"viewType":"taskOrgzIndivInfo"},"filtering":{"codeField":"proposalSourceCode","seqNumber":2,"type":"Dropdown","required":false,"dataTypeCombobox": "single"}}}',
1587
- // viewType: 'Detail',
1588
- // objTypeGroupCode: 'PROPOSAL_SOURCE',
1589
- // id: '6dea381c-368f-4423-83fb-957df68b176d',
1590
- // },
1591
- // {
1592
- // code: 'ProposalStatus',
1593
- // name: 'Trạng thái',
1594
- // nameEn: 'ProposalStatus',
1595
- // dataType: 'ObjType',
1596
- // allowShare: 1,
1597
- // objClassCode: 'SciTaskProposal',
1598
- // apiURL: '{MainApiURL}/CommonCats/ObjType/byTypeGroupCode/{ObjTypeGroupCode}',
1599
- // componentUI: 'Combobox(SingleChoice)',
1600
- // codeOnDb: 'trangThaiDeXuat',
1601
- // seqNum: 22,
1602
- // required: 2,
1603
- // allowStatistic: 0,
1604
- // preventEdit: 0,
1605
- // extra:
1606
- // '{"formField":{"displayMode":["view","filter"],"viewing":{"seqNumber":6,"section":"left","isShowBgInput":true,"viewConfig":{"labelSpan":{"sm":24,"md":10},"formControlSpan":{"sm":24,"md":14}}},"filtering":{"codeField":"proposalStatusCode","seqNumber":4,"dataTypeCombobox":"single"}}}',
1607
- // viewType: 'Detail',
1608
- // objTypeGroupCode: 'PROPOSAL_STATUS',
1609
- // id: 'b77dcf37-880b-48f9-acfb-09b983df4a0d',
1610
- // },
1611
- // {
1612
- // code: 'ApplicationStartDate',
1613
- // name: 'Ngày bắt đầu mở ứng tuyển cho đề xuất ',
1614
- // nameEn: 'ApplicationStartDate',
1615
- // dataType: 'Date',
1616
- // allowShare: 1,
1617
- // objClassCode: 'SciTaskProposal',
1618
- // componentUI: 'Calendar(Date)',
1619
- // codeOnDb: 'ngayBatDauUngTuyen',
1620
- // seqNum: 23,
1621
- // required: 3,
1622
- // allowStatistic: 0,
1623
- // preventEdit: 0,
1624
- // extra:
1625
- // '{"formField":{"displayMode":["edit","view"],"editting":{"title":"Thời gian bắt đầu","allowFutureDates":true,"seqNumber":1,"span":{"sm":24,"md":12},"section":"approval"},"viewing":{"title":"Thời gian bắt đầu","seqNumber":1,"section":"application"}}}',
1626
- // viewType: 'Detail',
1627
- // id: '790d2aee-4fb2-456f-8fd0-8cd5e0296a57',
1628
- // },
1629
- // {
1630
- // code: 'ApplicationEndDate',
1631
- // name: 'Ngày kết thúc ứng tuyển cho đề xuất ',
1632
- // nameEn: 'ApplicationEndDate',
1633
- // dataType: 'Date',
1634
- // allowShare: 1,
1635
- // objClassCode: 'SciTaskProposal',
1636
- // componentUI: 'Calendar(Date)',
1637
- // codeOnDb: 'ngayKetThucUngTuyen',
1638
- // seqNum: 24,
1639
- // required: 3,
1640
- // allowStatistic: 0,
1641
- // preventEdit: 0,
1642
- // extra:
1643
- // '{"formField":{"displayMode":["edit","view"],"editting":{"title":"Thời gian kết thúc","allowFutureDates":true,"seqNumber":1,"span":{"sm":24,"md":12},"section":"approval"},"viewing":{"title":"Thời gian kết thúc","seqNumber":2,"section":"application"}}}',
1644
- // viewType: 'Detail',
1645
- // id: '95822a49-2883-4ddb-a6ff-a3c8fc84f3bf',
1646
- // },
1647
- // {
1648
- // code: 'ApprovalAttachment',
1649
- // name: 'Văn bản giao nhiệm vụ - ứng tuyển được duyệt làm ',
1650
- // nameEn: 'ApprovalAttachment',
1651
- // dataType: 'string',
1652
- // allowShare: 1,
1653
- // objClassCode: 'SciTaskProposal',
1654
- // componentUI: 'InputText',
1655
- // codeOnDb: 'vanBanGiaoNhiemVu',
1656
- // seqNum: 25,
1657
- // required: 3,
1658
- // allowStatistic: 0,
1659
- // preventEdit: 0,
1660
- // viewType: 'Detail',
1661
- // id: 'fa00b65d-ebfb-49f9-8da2-09cd44138b79',
1662
- // },
1663
- // {
1664
- // code: 'ApprovalDuration',
1665
- // name: 'thời gian thức hiện duyệt',
1666
- // nameEn: 'ApprovalDuration',
1667
- // dataType: 'int',
1668
- // allowShare: 1,
1669
- // objClassCode: 'SciTaskProposal',
1670
- // codeOnDb: 'thoiGianThucHienDuyet',
1671
- // seqNum: 26,
1672
- // required: 0,
1673
- // allowStatistic: 0,
1674
- // preventEdit: 0,
1675
- // viewType: 'Detail',
1676
- // id: '54477413-ec20-43d8-8f9e-6e73205b86ad',
1677
- // },
1678
- // {
1679
- // code: 'ApprovalTotalBudget',
1680
- // name: 'Tổng kinh phí cần thiết được duyệt với văn bản giao nhiệm vụ ',
1681
- // nameEn: 'ApprovalTotalBudget',
1682
- // dataType: 'long',
1683
- // allowShare: 1,
1684
- // objClassCode: 'SciTaskProposal',
1685
- // codeOnDb: 'tongKinhPhiCanThietDuyet',
1686
- // seqNum: 27,
1687
- // required: 0,
1688
- // allowStatistic: 0,
1689
- // preventEdit: 0,
1690
- // viewType: 'Detail',
1691
- // id: 'f9a6c1a6-780e-4e9c-87ac-41a7dfbf37df',
1692
- // },
1693
- // {
1694
- // code: 'ApprovalGovermentBudget',
1695
- // name: 'Kinh phí từ Ngân sách nhà nước được duyệt với văn bản giao nhiệm vụ ',
1696
- // nameEn: 'ApprovalGovermentBudget',
1697
- // dataType: 'long',
1698
- // allowShare: 1,
1699
- // objClassCode: 'SciTaskProposal',
1700
- // codeOnDb: 'kinhPhiTuNSNNDuyet',
1701
- // seqNum: 28,
1702
- // required: 0,
1703
- // allowStatistic: 0,
1704
- // preventEdit: 0,
1705
- // viewType: 'Detail',
1706
- // id: '37690b42-c4f7-40bf-ae4a-c42f10735409',
1707
- // },
1708
- // {
1709
- // code: 'ApprovalNonGovermentBudget',
1710
- // name: 'Kinh phí từ các nguồn ngoài ngân sách nhà nước được duyệt với văn bản giao nhiệm vụ ',
1711
- // nameEn: 'ApprovalNonGovermentBudget',
1712
- // dataType: 'long',
1713
- // allowShare: 1,
1714
- // objClassCode: 'SciTaskProposal',
1715
- // codeOnDb: 'kinhPhiTuNgoaiNSNNDuyet',
1716
- // seqNum: 29,
1717
- // required: 0,
1718
- // allowStatistic: 0,
1719
- // preventEdit: 0,
1720
- // viewType: 'Detail',
1721
- // id: 'f9cf4aa3-6593-4b2d-88ca-1513069ce57f',
1722
- // },
1723
- // {
1724
- // code: 'ApprovalContractStateBudget',
1725
- // name: 'Kinh phí Ngân sách nhà nước khoán được duyệt với văn bản giao nhiệm vụ ',
1726
- // nameEn: 'ApprovalContractStateBudget',
1727
- // dataType: 'long',
1728
- // allowShare: 1,
1729
- // objClassCode: 'SciTaskProposal',
1730
- // codeOnDb: 'kinhPhiNSNNKhoanDuyet',
1731
- // seqNum: 30,
1732
- // required: 0,
1733
- // allowStatistic: 0,
1734
- // preventEdit: 0,
1735
- // viewType: 'Detail',
1736
- // id: '9c0aeb4b-b6db-460e-8c01-7bc728535e7a',
1737
- // },
1738
- // {
1739
- // code: 'ApprovalNonContractStateBudget',
1740
- // name: 'Kinh phí Ngân sách nhà nước không khoán được duyệt với văn bản giao nhiệm vụ ',
1741
- // nameEn: 'ApprovalNonContractStateBudget',
1742
- // dataType: 'long',
1743
- // allowShare: 1,
1744
- // objClassCode: 'SciTaskProposal',
1745
- // codeOnDb: 'kinhPhiNSNNKhongKhoanDuyet',
1746
- // seqNum: 31,
1747
- // required: 0,
1748
- // allowStatistic: 0,
1749
- // preventEdit: 0,
1750
- // viewType: 'Detail',
1751
- // id: 'c5cf6569-306a-4ede-ace6-5b9212003a0c',
1752
- // },
1753
- // {
1754
- // code: 'ApprovalImplementationMethod',
1755
- // name: 'Phương thức thực hiện',
1756
- // nameEn: 'ApprovalImplementationMethod',
1757
- // dataType: 'ObjType',
1758
- // allowShare: 1,
1759
- // objClassCode: 'SciTaskProposal',
1760
- // codeOnDb: 'phuongThucThucHienDuyet',
1761
- // seqNum: 32,
1762
- // required: 1,
1763
- // allowStatistic: 0,
1764
- // preventEdit: 0,
1765
- // viewType: 'Detail',
1766
- // id: 'cf73d6e1-581d-4228-b016-6557466ed61a',
1767
- // },
1768
- // {
1769
- // code: 'UpdatedById',
1770
- // name: 'Mã người cập nhật trang thái hiện tại của nhiệm vụ',
1771
- // nameEn: 'UpdatedById',
1772
- // dataType: 'Guid',
1773
- // allowShare: 1,
1774
- // objClassCode: 'SciTaskProposal',
1775
- // apiURL: '1',
1776
- // codeOnDb: 'maNguoiCapNhat',
1777
- // seqNum: 33,
1778
- // required: 2,
1779
- // allowStatistic: 0,
1780
- // preventEdit: 0,
1781
- // viewType: 'Detail',
1782
- // id: '8c8de675-f6fa-4dd7-b706-39ea7408a12d',
1783
- // },
1784
- // {
1785
- // code: 'UpdatedBy',
1786
- // name: 'Người cập nhật trang thái hiện tại của nhiệm vụ',
1787
- // nameEn: 'UpdatedBy',
1788
- // dataType: 'string',
1789
- // allowShare: 1,
1790
- // objClassCode: 'SciTaskProposal',
1791
- // apiURL: '1',
1792
- // codeOnDb: 'nguoiCapNhat',
1793
- // seqNum: 34,
1794
- // required: 2,
1795
- // allowStatistic: 0,
1796
- // preventEdit: 0,
1797
- // viewType: 'Detail',
1798
- // id: 'f09197be-09c3-43e2-b2a0-b535be2643f0',
1799
- // },
1800
- // {
1801
- // code: 'CreatedAt',
1802
- // name: 'Thời gian tạo',
1803
- // nameEn: 'CreatedAt',
1804
- // dataType: 'DateTime',
1805
- // allowShare: 1,
1806
- // objClassCode: 'SciTaskProposal',
1807
- // componentUI: 'Calendar(Date)',
1808
- // codeOnDb: 'createdAt',
1809
- // seqNum: 35,
1810
- // required: 2,
1811
- // allowStatistic: 0,
1812
- // preventEdit: 0,
1813
- // extra:
1814
- // '{"formField":{"displayMode":["filter"],"filtering":{"seqNumber":5,"type":"DateRange"}}}',
1815
- // viewType: 'Detail',
1816
- // id: '8b425192-574b-4df3-8b9a-c28bcd47bdc7',
1817
- // },
1818
- // {
1819
- // code: 'UpdatedAt',
1820
- // name: 'Thời gian cập nhật',
1821
- // nameEn: 'UpdatedAt',
1822
- // dataType: 'DateTime',
1823
- // allowShare: 1,
1824
- // objClassCode: 'SciTaskProposal',
1825
- // componentUI: 'TextView',
1826
- // codeOnDb: 'updatedAt',
1827
- // seqNum: 36,
1828
- // required: 0,
1829
- // allowStatistic: 0,
1830
- // preventEdit: 0,
1831
- // viewType: 'Detail',
1832
- // id: '004a60a1-0b66-4d0b-acb0-eb76d57f49f1',
1833
- // },
1834
- // {
1835
- // code: 'ApprovalNumber',
1836
- // name: 'Số quyết định giao nhiệm vụ',
1837
- // nameEn: 'ApprovalNumber',
1838
- // dataType: 'string',
1839
- // allowShare: 1,
1840
- // objClassCode: 'SciTaskProposal',
1841
- // componentUI: 'InputText',
1842
- // codeOnDb: 'soQuyetDinhGiaoNhiemVu',
1843
- // seqNum: 37,
1844
- // required: 1,
1845
- // allowStatistic: 0,
1846
- // preventEdit: 0,
1847
- // viewType: 'Detail',
1848
- // id: '4000def5-2820-4d20-a624-9fc91834487e',
1849
- // },
1850
- ];
1851
- // Sample test mảng fields json đầu vào cho tham số @Input() fields.
1852
- const sampleFields = [
1853
- // new MultiImageUploadField({
1854
- // key: 'images',
1855
- // label: 'Hình ảnh kèm theo',
1856
- // required: true,
1857
- // maxSizeInMB: 62,
1858
- // imageCardWidth: '208px'
1859
- // }),
1860
- // new SingleSelectField({
1861
- // key: 'role',
1862
- // label: 'Vai trò',
1863
- // bindLabel: 'name',
1864
- // bindValue: 'code',
1865
- // required: true,
1866
- // // outputAs: 'single',
1867
- // suffixes: [
1868
- // new Suffix({
1869
- // key: 'test',
1870
- // visibleOn: ['add', 'edit', 'view']
1871
- // }),
1872
- // new Suffix({
1873
- // key: 'edit',
1874
- // }),
1875
- // ],
1876
- // transformLabel: (option: Record<string, unknown>) => {
1877
- // const { name, code } = option;
1878
- // if (name && code) return `${name} - ${code}`;
1879
- // return '';
1880
- // },
1881
- // apiConfig: {
1882
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/system/Role',
1883
- // },
1884
- // options: [
1885
- // { name: 'Quản trị viên', code: 'admin' },
1886
- // { name: 'Người vận hành', code: 'operator' },
1887
- // { name: 'Người dân', code: 'anonymous' },
1888
- // ],
1889
- // errorMessages: {
1890
- // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
1891
- // },
1892
- // }),
1893
- // new SingleSelectField({
1894
- // key: 'province',
1895
- // label: 'Tỉnh/Thành phố',
1896
- // required: true,
1897
- // outputAs: 'single',
1898
- // transformLabel: (option: Record<string, unknown>) => {
1899
- // const { name, code } = option;
1900
- // if (name && code) return `${name} - ${code}`;
1901
- // return '';
1902
- // },
1903
- // apiConfig: {
1904
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/main/CommonCats/Province',
1905
- // },
1906
- // isParent: true,
1907
- // }),
1908
- // new SingleSelectField({
1909
- // key: 'district',
1910
- // label: 'Quận/Huyện',
1911
- // required: false,
1912
- // transformLabel: (option: Record<string, unknown>) => {
1913
- // const { name, code } = option;
1914
- // if (name && code) return `${name} - ${code}`;
1915
- // return '';
1916
- // },
1917
- // apiConfig: {
1918
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/main/CommonCats/District',
1919
- // },
1920
- // isParent: true,
1921
- // dependsOn: [
1922
- // {
1923
- // fieldKey: 'province',
1924
- // paramKey: 'provinceCode',
1925
- // },
1926
- // ],
1927
- // }),
1928
- // new SingleSelectField({
1929
- // key: 'ward',
1930
- // label: 'Phường/Xã',
1931
- // required: false,
1932
- // transformLabel: (option: Record<string, unknown>) => {
1933
- // const { name, code } = option;
1934
- // if (name && code) return `${name} - ${code}`;
1935
- // return '';
1936
- // },
1937
- // apiConfig: {
1938
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/main/CommonCats/Ward',
1939
- // },
1940
- // dependsOn: [
1941
- // {
1942
- // fieldKey: 'district',
1943
- // paramKey: 'districtCode',
1944
- // },
1945
- // ],
1946
- // }),
1947
- // new MultiSelectField({
1948
- // key: 'multiRole',
1949
- // label: 'Nhiều vai trò',
1950
- // bindLabel: 'name',
1951
- // bindValue: 'code',
1952
- // outputAs: 'single',
1953
- // required: true,
1954
- // isDisabled: false,
1955
- // transformLabel: (option: Record<string, unknown>) => {
1956
- // const { name, code } = option;
1957
- // if (name && code) return `${name} - ${code}`;
1958
- // return '';
1959
- // },
1960
- // apiConfig: {
1961
- // baseUrl: `${environment.GDCI_SYSTEM_URL}CommonCats/Position`,
1962
- // },
1963
- // errorMessages: {
1964
- // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
1965
- // },
1966
- // suffixes: [
1967
- // new Suffix({
1968
- // key: 'test',
1969
- // visibleOn: ['add', 'edit'],
1970
- // }),
1971
- // new Suffix({
1972
- // key: 'edit',
1973
- // }),
1974
- // ],
1975
- // }),
1976
- // new InputTextField({
1977
- // key: 'username',
1978
- // label: 'Tên tài khoản',
1979
- // value: '',
1980
- // required: true,
1981
- // minLength: 1,
1982
- // isDisabled: false,
1983
- // suffixes: [
1984
- // new Suffix({
1985
- // key: 'view',
1986
- // visibleOn: ['view']
1987
- // }),
1988
- // new Suffix({
1989
- // key: 'edit',
1990
- // }),
1991
- // ],
1992
- // errorMessages: {
1993
- // // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
1994
- // // [CommonValidaterEnum.MinLength]: 'Tối thiểu nè hẹ hẹ',
1995
- // },
1996
- // pattern: 'alphaDashVN',
1997
- // // grid: { xs: 12 },
1998
- // // labelGrid: { xs: 8 },
1999
- // // controlGrid: { xs: 16 }
2000
- // }),
2001
- // new InputPasswordField({
2002
- // key: 'password',
2003
- // label: 'Mật khẩu',
2004
- // value: '',
2005
- // required: false,
2006
- // matchField: 'confirmPassword',
2007
- // errorMessages: {
2008
- // // [CustomValidaterEnum.MatchField]: 'Mật khẩu không khớp',
2009
- // },
2010
- // // grid: { xs: 12 },
2011
- // // labelGrid: { xs: 8 },
2012
- // // controlGrid: { xs: 16 }
2013
- // }),
2014
- // new InputPasswordField({
2015
- // key: 'confirmPassword',
2016
- // label: 'Xác nhận mật khẩu',
2017
- // value: '',
2018
- // required: false,
2019
- // matchField: 'password',
2020
- // errorMessages: {
2021
- // // [CustomValidaterEnum.MatchField]: 'Mật khẩu không khớp',
2022
- // },
2023
- // // labelGrid: { xs: 16 },
2024
- // // controlGrid: { xs: 8 }
2025
- // }),
2026
- // new TextareaField({
2027
- // key: 'note',
2028
- // label: 'Ghi chú',
2029
- // value: '',
2030
- // required: false,
2031
- // minLength: 3,
2032
- // maxLength: 128,
2033
- // errorMessages: {
2034
- // // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
2035
- // // [CommonValidaterEnum.MinLength]: 'Tối thiểu nè hẹ hẹ',
2036
- // },
2037
- // // grid: { xs: 12 },
2038
- // // labelGrid: { xs: 8 },
2039
- // // controlGrid: { xs: 16 }
2040
- // }),
2041
- // new DatePickerField({
2042
- // key: 'date',
2043
- // label: 'Ngày',
2044
- // required: true,
2045
- // allowPastDates: false,
2046
- // allowFutureDates: true,
2047
- // isDisabled: true
2048
- // }),
2049
- // new DateRangeField({
2050
- // key: 'expectedTime',
2051
- // label: 'Thời gian dự kiến',
2052
- // required: true,
2053
- // outputAs: 'date',
2054
- // allowPastDates: false,
2055
- // allowFutureDates: true,
2056
- // isDisabled: true
2057
- // }),
2058
- // new SingleFileUploadField({
2059
- // key: 'attachment',
2060
- // label: 'Văn bản đính kèm',
2061
- // // required: true,
2062
- // acceptedMimeTypes: [APPLICATION_TYPES.pdf, IMAGE_TYPES.tif, IMAGE_TYPES.tiff],
2063
- // maxSizeInMB: 100000,
2064
- // required: true,
2065
- // apiFileConfig: {
2066
- // uploadUrl: DEFAULT_UPLOAD_TIFF_URL,
2067
- // deleteUrl: DEFAULT_DELETE_URL,
2068
- // perMimeTypeConfig: {
2069
- // 'image/tiff': {
2070
- // uploadUrl: DEFAULT_UPLOAD_TIFF_URL,
2071
- // buildFormData: (file, extraParams) => {
2072
- // const fd = new FormData();
2073
- // fd.append('File', file);
2074
- // fd.append('SaveFile', String(true));
2075
- // fd.append('BucketName', 'documents');
2076
- // return fd;
2077
- // },
2078
- // },
2079
- // },
2080
- // },
2081
- // }),
2082
- new MultiFileUploadField({
2083
- key: 'pictures',
2084
- label: 'Những hình ảnh kèm theo',
2085
- // required: true,
2086
- // acceptedMimeTypes: [APPLICATION_TYPES.pdf],
2087
- maxSizeInMB: 2,
2088
- required: true,
2089
- }),
2090
- // new AvatarUploadField({
2091
- // key: 'avatar',
2092
- // label: 'Ảnh đại diện',
2093
- // acceptedMimeTypes: [IMAGE_TYPES.jpeg, IMAGE_TYPES.jpg, IMAGE_TYPES.png],
2094
- // required: false,
2095
- // labelGrid: { xs: 4 },
2096
- // controlGrid: { xs: 20 },
2097
- // }),
2098
- ];
2099
- const sampleFields2 = [
2100
- new SingleSelectField({
2101
- key: 'role',
2102
- label: 'Vai trò',
2103
- bindLabel: 'name',
2104
- bindValue: 'code',
2105
- required: false,
2106
- outputAs: 'single',
2107
- transformLabel: (option) => {
2108
- const { name, code } = option;
2109
- if (name && code)
2110
- return `${name} - ${code}`;
2111
- return '';
2112
- },
2113
- apiConfig: {
2114
- baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/system/Role',
2115
- },
2116
- options: [
2117
- { name: 'Quản trị viên', code: 'admin' },
2118
- { name: 'Người vận hành', code: 'operator' },
2119
- { name: 'Người dân', code: 'anonymous' },
2120
- ],
2121
- errorMessages: {
2122
- [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
2123
- },
2124
- }),
2125
- new SingleSelectField({
2126
- key: 'province',
2127
- label: 'Tỉnh/Thành phố',
2128
- required: true,
2129
- transformLabel: (option) => {
2130
- const { name, code } = option;
2131
- if (name && code)
2132
- return `${name} - ${code}`;
2133
- return '';
2134
- },
2135
- apiConfig: {
2136
- baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/main/CommonCats/Province',
2137
- },
2138
- isParent: true,
2139
- }),
2140
- new SingleSelectField({
2141
- key: 'district',
2142
- label: 'Quận/Huyện',
2143
- required: false,
2144
- transformLabel: (option) => {
2145
- const { name, code } = option;
2146
- if (name && code)
2147
- return `${name} - ${code}`;
2148
- return '';
2149
- },
2150
- apiConfig: {
2151
- baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/main/CommonCats/District',
2152
- },
2153
- isParent: true,
2154
- dependsOn: [
2155
- {
2156
- fieldKey: 'province',
2157
- paramKey: 'provinceCode',
2158
- },
2159
- ],
2160
- }),
2161
- new MultiSelectField({
2162
- key: 'multiRole',
2163
- label: 'Nhiều vai trò',
2164
- bindLabel: 'name',
2165
- bindValue: 'code',
2166
- outputAs: 'single',
2167
- required: true,
2168
- transformLabel: (option) => {
2169
- const { name, code } = option;
2170
- if (name && code)
2171
- return `${name} - ${code}`;
2172
- return '';
2173
- },
2174
- apiConfig: {
2175
- baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/system/Role',
2176
- },
2177
- errorMessages: {
2178
- [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
2179
- },
2180
- }),
2181
- new InputTextField({
2182
- key: 'username',
2183
- label: 'Tên tài khoản',
2184
- value: '',
2185
- required: true,
2186
- minLength: 1,
2187
- errorMessages: {
2188
- // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
2189
- // [CommonValidaterEnum.MinLength]: 'Tối thiểu nè hẹ hẹ',
2190
- },
2191
- pattern: 'alphaDashVN',
2192
- // grid: { xs: 12 },
2193
- // labelGrid: { xs: 8 },
2194
- // controlGrid: { xs: 16 }
2195
- }),
2196
- new TextareaField({
2197
- key: 'note',
2198
- label: 'Ghi chú',
2199
- value: '',
2200
- required: false,
2201
- minLength: 3,
2202
- maxLength: 128,
2203
- errorMessages: {
2204
- // [CommonValidaterEnum.Required]: 'Bắt buộc nè hẹ hẹ',
2205
- // [CommonValidaterEnum.MinLength]: 'Tối thiểu nè hẹ hẹ',
2206
- },
2207
- // grid: { xs: 12 },
2208
- // labelGrid: { xs: 8 },
2209
- // controlGrid: { xs: 16 }
2210
- }),
2211
- new DatePickerField({
2212
- key: 'date',
2213
- label: 'Ngày',
2214
- required: true,
2215
- allowPastDates: false,
2216
- allowFutureDates: true,
2217
- }),
2218
- new DateRangeField({
2219
- key: 'expectedTime',
2220
- label: 'Thời gian dự kiến',
2221
- required: true,
2222
- outputAs: 'string',
2223
- allowPastDates: false,
2224
- allowFutureDates: true,
2225
- }),
2226
- new SingleFileUploadField({
2227
- key: 'attachment',
2228
- label: 'Văn bản đính kèm',
2229
- // required: true,
2230
- acceptedMimeTypes: [APPLICATION_TYPES.pdf],
2231
- maxSizeInMB: 2,
2232
- required: true,
2233
- }),
2234
- new MultiFileUploadField({
2235
- key: 'documents',
2236
- label: 'Những hồ sơ đính kèm',
2237
- // required: true,
2238
- acceptedMimeTypes: [APPLICATION_TYPES.pdf],
2239
- maxSizeInMB: 2,
2240
- required: false,
2241
- }),
2242
- ];
2243
- // Sample test model data 1.
2244
- const sampleModel = {
2245
- province: { code: 35, name: 'Hà Name' },
2246
- username: 'ldquocccccccccc',
2247
- role: {
2248
- name: 'Quản trị viên',
2249
- code: 'ADMIN',
2250
- },
2251
- // role: 'ADMIN',
2252
- multiRole: [
2253
- {
2254
- name: 'Quản trị',
2255
- code: 'ADMIN',
2256
- },
2257
- {
2258
- name: 'Vận hành',
2259
- code: 'OPERATOR',
2260
- },
2261
- ],
2262
- note: 'Hãy nhớ những điều này:\n-Tôi là tôi.\n-Ai là tôi?\n-Sẽ ra sao nếu tôi tiến lên.\n-Hẹ hẹ.',
2263
- // note: 'hè hè\nhihihi\nfefefef',
2264
- // date: '08-01-2025',
2265
- // date: '30-08-2025',
2266
- // date: '2025-08-28',
2267
- date: new Date('2025-08-28'),
2268
- expectedTime: ['2025-08-28', new Date('2025-08-30')],
2269
- // attachment: [{
2270
- // id: 'd3f549e0-a036-4b16-aa67-30444d23d795',
2271
- // name: 'dummy.pdf',
2272
- // path: '/dummy.pdf',
2273
- // type: APPLICATION_TYPES.pdf,
2274
- // size: 1800
2275
- // }],
2276
- // attachment:
2277
- // '[{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238804458\",\"name\":\"Ho_Cubic_30L_Tong_Hop.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_30L_Tong_Hop.pdf\",\"isPublic\":true}]',
2278
- attachment: '[{"id":"8fe5f9b1-18aa-415d-a33e-b4a843b7cab1","name":"image.png","contentType":"image/png","size":77239,"bucket":"public","isDir":false,"createdAt":"2025-09-04","updatedAt":"2025-09-04","path":"/image.png","isPublic":true},{"id":"b8963fb1-e842-42ac-9fc4-1733a3b65dc5","name":"image (1).png","contentType":"image/png","size":79610,"bucket":"public","isDir":false,"createdAt":"2025-09-04","updatedAt":"2025-09-04","path":"/image (1).png","isPublic":true}]',
2279
- pictures: '[{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238803338\",\"name\":\"Ho_Cubic_30L_Tong_Hop.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_30L_Tong_Hop.pdf\",\"isPublic\":true},{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238804458\",\"name\":\"Ho_Cubic_35L_Tong_Hop.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_35L_Tong_Hop.pdf\",\"isPublic\":true}]',
2280
- // pictures: '[{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800}]'
2281
- };
2282
- // Sample test model data 2.
2283
- const sampleModel2 = {
2284
- images: '[{"id":"56062785-a619-41f7-8b64-ed648e57f934","name":"vrg_file_5141201818_104814_39-11_anh_bai_chinh_ml41ozadgy_ug51tjdtjb.jpg","contentType":"image/jpeg","size":176638,"bucket":"public","isDir":false,"createdAt":"2025-09-08","updatedAt":"2025-09-08","path":"/vrg_file_5141201818_104814_39-11_anh_bai_chinh_ml41ozadgy_ug51tjdtjb.jpg","isPublic":true},{"id":"79c62863-8241-4785-ac52-162a1d5347a6","name":"vrg_file_5409423468_coral-reef-fish-reef-fish-aquarium-wallpaper-preview.jpg","contentType":"image/jpeg","size":128651,"bucket":"public","isDir":false,"createdAt":"2025-09-08","updatedAt":"2025-09-08","path":"/vrg_file_5409423468_coral-reef-fish-reef-fish-aquarium-wallpaper-preview.jpg","isPublic":true},{"id":"973d2861-fe87-481f-9dc7-aeb7991516de","name":"vrg_file_8710772406_nemo_fi.jpg","contentType":"image/jpeg","size":19134,"bucket":"public","isDir":false,"createdAt":"2025-09-08","updatedAt":"2025-09-08","path":"/vrg_file_8710772406_nemo_fi.jpg","isPublic":true}]',
2285
- avatar: '[{"id":"55283e86-6d16-4f12-9d9b-fce1b928eae5","name":"104814_39-11_anh_bai_chinh_ml41ozadgy_ug51tjdtjb.jpg","contentType":"image/jpeg","size":176638,"bucket":"public","isDir":false,"createdAt":"2025-08-19","updatedAt":"2025-08-19","path":"/104814_39-11_anh_bai_chinh_ml41ozadgy_ug51tjdtjb.jpg","isPublic":true}]',
2286
- province: { code: 35, name: 'Hà Name' },
2287
- district: { code: 349, name: 'Duy tiên' },
2288
- username: 'dddddddddddddddddddddddd',
2289
- role: {
2290
- name: 'Vận hành',
2291
- code: 'OPERATOR',
2292
- },
2293
- // role: 'ADMIN',
2294
- multiRole: [
2295
- {
2296
- name: 'Quản trị',
2297
- code: 'ADMIN',
2298
- },
2299
- {
2300
- name: 'Vận hành',
2301
- code: 'OPERATOR',
2302
- },
2303
- {
2304
- name: 'Người dân',
2305
- code: 'ANONYMOUS',
2306
- },
2307
- ],
2308
- note: 'CCCCC',
2309
- // note: 'hè hè\nhihihi\nfefefef',
2310
- // date: '08-01-2025',
2311
- // date: '30-08-2025',
2312
- // date: '2025-08-28',
2313
- date: new Date('2025-08-30'),
2314
- expectedTime: ['2025-08-01', new Date('2025-08-02')],
2315
- // attachment: [{
2316
- // id: 'd3f549e0-a036-4b16-aa67-30444d23d795',
2317
- // name: 'dummy.pdf',
2318
- // path: '/dummy.pdf',
2319
- // type: APPLICATION_TYPES.pdf,
2320
- // size: 1800
2321
- // }],
2322
- attachment: '[{"id":"8fe5f9b1-18aa-415d-a33e-b4a843b7cab1","name":"image.png","contentType":"image/png","size":77239,"bucket":"public","isDir":false,"createdAt":"2025-09-04","updatedAt":"2025-09-04","path":"/image.png","isPublic":true},{"id":"b8963fb1-e842-42ac-9fc4-1733a3b65dc5","name":"image (1).png","contentType":"image/png","size":79610,"bucket":"public","isDir":false,"createdAt":"2025-09-04","updatedAt":"2025-09-04","path":"/image (1).png","isPublic":true}]',
2323
- // attachment:
2324
- // '[{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238804458\",\"name\":\"dumpleeeee.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_30L_Tong_Hop.pdf\",\"isPublic\":true}]',
2325
- pictures: '[{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238803338\",\"name\":\"Ho_Cubic_45L_Tong_Hop.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_30L_Tong_Hop.pdf\",\"isPublic\":true},{\"id\":\"1952e9ea-61c3-4b8d-abe2-4df238804458\",\"name\":\"Ho_Cubic_60L_Tong_Hop.pdf\",\"contentType\":\"application/pdf\",\"size\":28407,\"bucket\":\"caosuvn\",\"isDir\":false,\"createdAt\":\"2025-08-08\",\"updatedAt\":\"2025-08-08\",\"path\":\"/Ho_Cubic_35L_Tong_Hop.pdf\",\"isPublic\":true}]',
2326
- // pictures: '[{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800},{"id":"d3f549e0-a036-4b16-aa67-30444d23d795","name":"dummy.pdf","path":"/dummy.pdf","contentType":"application/pdf","size":1800}]'
2327
- };
2328
- //#region Multi Form
2329
- const sampleMultiFormFieldsA = [
2330
- // ======== Input Text ========
2331
- new InputTextField({
2332
- key: 'usernameA',
2333
- label: 'Tên tài khoản A',
2334
- value: '',
2335
- required: true,
2336
- minLength: 2,
2337
- isDisabled: false,
2338
- errorMessages: {
2339
- [CommonValidaterEnum.Required]: 'Bắt buộc nha hẹ hẹ',
2340
- [CommonValidaterEnum.MinLength]: 'Tối thiểu là 2 nha hẹ hẹ',
2341
- },
2342
- pattern: 'alphaDashVN',
2343
- // grid: { xs: 12 },
2344
- // labelGrid: { xs: 8 },
2345
- // controlGrid: { xs: 16 },
2346
- // suffixes: [
2347
- // new Suffix({
2348
- // key: 'view',
2349
- // visibleOn: ['view'],
2350
- // }),
2351
- // new Suffix({
2352
- // key: 'edit',
2353
- // }),
2354
- // ],
2355
- }),
2356
- // ======== Input Text ========
2357
- // ======== Input Password ========
2358
- // new InputPasswordField({
2359
- // key: 'passwordA',
2360
- // label: 'Mật khẩu',
2361
- // // value: '',
2362
- // required: false,
2363
- // matchField: 'confirmPasswordA',
2364
- // errorMessages: {
2365
- // [CustomValidaterEnum.MatchField]: 'Mật khẩu không khớp',
2366
- // },
2367
- // // grid: { xs: 12 },
2368
- // // labelGrid: { xs: 8 },
2369
- // // controlGrid: { xs: 16 }
2370
- // }),
2371
- // new InputPasswordField({
2372
- // key: 'confirmPasswordA',
2373
- // label: 'Xác nhận mật khẩu',
2374
- // // value: '',
2375
- // required: false,
2376
- // matchField: 'passwordA',
2377
- // errorMessages: {
2378
- // [CustomValidaterEnum.MatchField]: 'Xác nhận mật khẩu không khớp',
2379
- // },
2380
- // // labelGrid: { xs: 16 },
2381
- // // controlGrid: { xs: 8 }
2382
- // }),
2383
- // ======== Input Password ========
2384
- // ======== Textarea ========
2385
- // new TextareaField({
2386
- // key: 'noteA',
2387
- // label: 'Ghi chú A',
2388
- // value: '',
2389
- // required: false,
2390
- // minLength: 3,
2391
- // maxLength: 128,
2392
- // // grid: { xs: 12 },
2393
- // // labelGrid: { xs: 8 },
2394
- // // controlGrid: { xs: 16 }
2395
- // }),
2396
- // ======== Textarea ========
2397
- // ======== DATE PICKER ========
2398
- // new DatePickerField({
2399
- // key: 'dateA',
2400
- // label: 'Ngày A',
2401
- // required: true,
2402
- // allowPastDates: false,
2403
- // allowFutureDates: true,
2404
- // isDisabled: false,
2405
- // }),
2406
- // ======== DATE PICKER ========
2407
- // ======== DATE RANGE ========
2408
- // new DateRangeField({
2409
- // key: 'expectedTimeA',
2410
- // label: 'Thời gian dự kiến A',
2411
- // required: true,
2412
- // outputAs: 'date-array',
2413
- // allowPastDates: false,
2414
- // allowFutureDates: true,
2415
- // isDisabled: false,
2416
- // }),
2417
- // ======== DATE RANGE ========
2418
- // ======== SINGLE SELECT ========
2419
- // new SingleSelectField({
2420
- // key: 'roleA',
2421
- // label: 'Vai trò A',
2422
- // bindLabel: 'name',
2423
- // bindValue: 'code',
2424
- // required: true,
2425
- // // outputAs: 'single',
2426
- // transformLabel: (option: Record<string, unknown>) => {
2427
- // const { name, code } = option;
2428
- // if (name && code) return `${name} - ${code}`;
2429
- // return '';
2430
- // },
2431
- // apiConfig: {
2432
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/system/Role',
2433
- // },
2434
- // options: [
2435
- // { name: 'Quản trị viên', code: 'admin' },
2436
- // { name: 'Người vận hành', code: 'operator' },
2437
- // { name: 'Người dân', code: 'anonymous' },
2438
- // ],
2439
- // }),
2440
- // ======== SINGLE SELECT ========
2441
- // ======== MULTI SELECT ========
2442
- // new MultiSelectField({
2443
- // key: 'multiRoleA',
2444
- // label: 'Nhiều vai trò A',
2445
- // bindLabel: 'name',
2446
- // bindValue: 'code',
2447
- // outputAs: 'single',
2448
- // required: true,
2449
- // isDisabled: false,
2450
- // transformLabel: (option: Record<string, unknown>) => {
2451
- // const { name, code } = option;
2452
- // if (name && code) return `${name} - ${code}`;
2453
- // return '';
2454
- // },
2455
- // apiConfig: {
2456
- // baseUrl: `${environment.GDCI_SYSTEM_URL}CommonCats/Position`,
2457
- // },
2458
- // }),
2459
- // ======== MULTI SELECT ========
2460
- // ======== SINGLE FILE UPLOAD ========
2461
- new SingleFileUploadField({
2462
- key: 'attachmentA',
2463
- label: 'Văn bản đính kèm A',
2464
- // required: true,
2465
- acceptedMimeTypes: [APPLICATION_TYPES.pdf, IMAGE_TYPES.tif, IMAGE_TYPES.tiff],
2466
- maxSizeInMB: 100000,
2467
- required: true,
2468
- }),
2469
- // ======== SINGLE FILE UPLOAD ========
2470
- // ======== MULTI FILE UPLOAD ========
2471
- new MultiFileUploadField({
2472
- key: 'picturesA',
2473
- label: 'Những hình ảnh kèm theo A',
2474
- acceptedMimeTypes: [APPLICATION_TYPES.pdf],
2475
- maxSizeInMB: 2,
2476
- required: false,
2477
- }),
2478
- // ======== MULTI FILE UPLOAD ========
2479
- // ======== MULTI IMAGE UPLOAD ========
2480
- new MultiImageUploadField({
2481
- key: 'imagesA',
2482
- label: 'Hình ảnh kèm theo A',
2483
- required: true,
2484
- maxSizeInMB: 62,
2485
- imageCardWidth: '208px',
2486
- }),
2487
- // ======== MULTI IMAGE UPLOAD ========
2488
- // ======== AVATAR UPLOAD ========
2489
- new AvatarUploadField({
2490
- key: 'avatarA',
2491
- label: 'Ảnh đại diện A',
2492
- acceptedMimeTypes: [IMAGE_TYPES.jpeg, IMAGE_TYPES.jpg, IMAGE_TYPES.png],
2493
- required: false,
2494
- labelGrid: { xs: 4 },
2495
- controlGrid: { xs: 20 },
2496
- }),
2497
- // ======== AVATAR UPLOAD ========
2498
- ];
2499
- const sampleMultiFormFieldsB = [
2500
- // ======== Input Text ========
2501
- new InputTextField({
2502
- key: 'usernameB',
2503
- label: 'Tên tài khoản B',
2504
- value: '',
2505
- required: true,
2506
- minLength: 2,
2507
- isDisabled: false,
2508
- errorMessages: {
2509
- [CommonValidaterEnum.Required]: 'Bắt buộc nha hẹ hẹ',
2510
- [CommonValidaterEnum.MinLength]: 'Tối thiểu là 2 nha hẹ hẹ',
2511
- },
2512
- pattern: 'alphaDashVN',
2513
- // grid: { xs: 12 },
2514
- // labelGrid: { xs: 8 },
2515
- // controlGrid: { xs: 16 },
2516
- // suffixes: [
2517
- // new Suffix({
2518
- // key: 'view',
2519
- // visibleOn: ['view'],
2520
- // }),
2521
- // new Suffix({
2522
- // key: 'edit',
2523
- // }),
2524
- // ],
2525
- }),
2526
- // ======== Input Text ========
2527
- // ======== Input Password ========
2528
- // new InputPasswordField({
2529
- // key: 'passwordB',
2530
- // label: 'Mật khẩu',
2531
- // // value: '',
2532
- // required: false,
2533
- // matchField: 'confirmPasswordB',
2534
- // errorMessages: {
2535
- // [CustomValidaterEnum.MatchField]: 'Mật khẩu không khớp',
2536
- // },
2537
- // // grid: { xs: 12 },
2538
- // // labelGrid: { xs: 8 },
2539
- // // controlGrid: { xs: 16 }
2540
- // }),
2541
- // new InputPasswordField({
2542
- // key: 'confirmPasswordB',
2543
- // label: 'Xác nhận mật khẩu',
2544
- // // value: '',
2545
- // required: false,
2546
- // matchField: 'passwordB',
2547
- // errorMessages: {
2548
- // [CustomValidaterEnum.MatchField]: 'Xác nhận mật khẩu không khớp',
2549
- // },
2550
- // // labelGrid: { xs: 16 },
2551
- // // controlGrid: { xs: 8 }
2552
- // }),
2553
- // ======== Input Password ========
2554
- // ======== Textarea ========
2555
- // new TextareaField({
2556
- // key: 'noteB',
2557
- // label: 'Ghi chú B',
2558
- // value: '',
2559
- // required: false,
2560
- // minLength: 3,
2561
- // maxLength: 128,
2562
- // // grid: { xs: 12 },
2563
- // // labelGrid: { xs: 8 },
2564
- // // controlGrid: { xs: 16 }
2565
- // }),
2566
- // ======== Textarea ========
2567
- // ======== DATE PICKER ========
2568
- // new DatePickerField({
2569
- // key: 'dateB',
2570
- // label: 'Ngày B',
2571
- // required: true,
2572
- // allowPastDates: false,
2573
- // allowFutureDates: true,
2574
- // isDisabled: false,
2575
- // }),
2576
- // ======== DATE PICKER ========
2577
- // ======== DATE RANGE ========
2578
- // new DateRangeField({
2579
- // key: 'expectedTimeB',
2580
- // label: 'Thời gian dự kiến B',
2581
- // required: true,
2582
- // outputAs: 'date-array',
2583
- // allowPastDates: false,
2584
- // allowFutureDates: true,
2585
- // isDisabled: false,
2586
- // }),
2587
- // ======== DATE RANGE ========
2588
- // ======== SINGLE SELECT ========
2589
- // new SingleSelectField({
2590
- // key: 'roleB',
2591
- // label: 'Vai trò B',
2592
- // bindLabel: 'name',
2593
- // bindValue: 'code',
2594
- // required: true,
2595
- // // outputAs: 'single',
2596
- // transformLabel: (option: Record<string, unknown>) => {
2597
- // const { name, code } = option;
2598
- // if (name && code) return `${name} - ${code}`;
2599
- // return '';
2600
- // },
2601
- // apiConfig: {
2602
- // baseUrl: 'https://quyhoachxaydung.hanam.gov.vn/gdci/system/Role',
2603
- // },
2604
- // options: [
2605
- // { name: 'Quản trị viên', code: 'admin' },
2606
- // { name: 'Người vận hành', code: 'operator' },
2607
- // { name: 'Người dân', code: 'anonymous' },
2608
- // ],
2609
- // }),
2610
- // ======== SINGLE SELECT ========
2611
- // ======== MULTI SELECT ========
2612
- // new MultiSelectField({
2613
- // key: 'multiRoleB',
2614
- // label: 'Nhiều vai trò B',
2615
- // bindLabel: 'name',
2616
- // bindValue: 'code',
2617
- // outputAs: 'single',
2618
- // required: true,
2619
- // isDisabled: false,
2620
- // transformLabel: (option: Record<string, unknown>) => {
2621
- // const { name, code } = option;
2622
- // if (name && code) return `${name} - ${code}`;
2623
- // return '';
2624
- // },
2625
- // apiConfig: {
2626
- // baseUrl: `${environment.GDCI_SYSTEM_URL}CommonCats/Position`,
2627
- // },
2628
- // }),
2629
- // ======== MULTI SELECT ========
2630
- // ======== SINGLE FILE UPLOAD ========
2631
- new SingleFileUploadField({
2632
- key: 'attachmentB',
2633
- label: 'Văn bản đính kèm B',
2634
- // required: true,
2635
- acceptedMimeTypes: [APPLICATION_TYPES.pdf, IMAGE_TYPES.tif, IMAGE_TYPES.tiff],
2636
- maxSizeInMB: 100000,
2637
- required: true,
2638
- }),
2639
- // ======== SINGLE FILE UPLOAD ========
2640
- // ======== MULTI FILE UPLOAD ========
2641
- new MultiFileUploadField({
2642
- key: 'picturesB',
2643
- label: 'Những hình ảnh kèm theo B',
2644
- acceptedMimeTypes: [APPLICATION_TYPES.pdf],
2645
- maxSizeInMB: 2,
2646
- required: false,
2647
- }),
2648
- // ======== MULTI FILE UPLOAD ========
2649
- // ======== MULTI IMAGE UPLOAD ========
2650
- new MultiImageUploadField({
2651
- key: 'imagesB',
2652
- label: 'Hình ảnh kèm theo B',
2653
- required: true,
2654
- maxSizeInMB: 62,
2655
- imageCardWidth: '208px',
2656
- }),
2657
- // ======== MULTI IMAGE UPLOAD ========
2658
- // ======== AVATAR UPLOAD ========
2659
- new AvatarUploadField({
2660
- key: 'avatarB',
2661
- label: 'Ảnh đại diện B',
2662
- acceptedMimeTypes: [IMAGE_TYPES.jpeg, IMAGE_TYPES.jpg, IMAGE_TYPES.png],
2663
- required: false,
2664
- labelGrid: { xs: 4 },
2665
- controlGrid: { xs: 20 },
2666
- }),
2667
- // ======== AVATAR UPLOAD ========
2668
- ];
2669
- const sampleMultiFormModelA = {
2670
- usernameA: 'Lương Văn Lào',
2671
- };
2672
- const sampleMultiFormModelB = {
2673
- usernameB: 'Đào Mai Tôn',
2674
- };
2675
- //#endregion Multi Form
2676
- //#region MultiSelect depend on field has isParent
2677
- const sampleMultiSelectDependOnParent = [
2678
- new MultiFileUploadField({
2679
- key: 'picturesB',
2680
- label: 'Những hình ảnh kèm theo B',
2681
- acceptedMimeTypes: [APPLICATION_TYPES.pdf, IMAGE_TYPES.png, IMAGE_TYPES.jpg],
2682
- maxSizeInMB: 1,
2683
- required: true,
2684
- }),
2685
- new SingleSelectField({
2686
- key: 'province',
2687
- label: 'Tỉnh/Thành phố',
2688
- required: true,
2689
- outputAs: 'single',
2690
- transformLabel: (option) => {
2691
- const { name, code } = option;
2692
- if (name && code)
2693
- return `${name} - ${code}`;
2694
- return '';
2695
- },
2696
- apiConfig: {
2697
- baseUrl: `http://mock-api.com/CommonCats/Province`,
2698
- },
2699
- isParent: true,
2700
- }),
2701
- new MultiSelectField({
2702
- key: 'ward',
2703
- label: 'Phường/Xã',
2704
- required: false,
2705
- transformLabel: (option) => {
2706
- const { name, code } = option;
2707
- if (name && code)
2708
- return `${name} - ${code}`;
2709
- return '';
2710
- },
2711
- apiConfig: {
2712
- baseUrl: `http://mock-api.com/CommonCats/Ward`,
2713
- },
2714
- dependsOn: [
2715
- {
2716
- fieldKey: 'province',
2717
- paramKey: 'provinceCode',
2718
- },
2719
- ],
2720
- // dependType: DependTypeEnum.All
2721
- }),
1176
+ const FIELD_TYPES = [
1177
+ 'input-text',
1178
+ 'input-password',
1179
+ 'textarea',
1180
+ 'date-picker',
1181
+ 'date-range',
1182
+ 'single-select',
1183
+ 'tree-select',
1184
+ 'multi-select',
1185
+ 'single-file-upload',
1186
+ 'multi-file-upload',
1187
+ 'multi-image-upload',
1188
+ 'avatar-upload',
1189
+ 'checkbox',
1190
+ 'radio',
1191
+ 'reminder-type-radio',
1192
+ 'switch',
1193
+ 'autocomplete',
2722
1194
  ];
2723
- //#endregion MultiSelect depend on field has isParent
2724
1195
 
2725
- const COMBINE_DEPEND_TYPES = ['skip', 'null', 'valid'];
1196
+ /**
1197
+ * FIELD_TYPES là readonly ['input', 'select', 'checkbox',...]
1198
+ */
1199
+ const INPUT_FIELD_TYPES = ['text', 'email', 'password', 'number'];
2726
1200
 
2727
- // Format file size, đầu vào là Byte, và loại muốn format sang.
2728
- const formatFileSize = (size, type) => {
2729
- let returnVal = 0;
2730
- switch (type) {
2731
- case 'KB':
2732
- returnVal = size / 1024;
2733
- break;
2734
- case 'MB':
2735
- returnVal = size / (1024 * 1024);
2736
- break;
2737
- case 'GB':
2738
- returnVal = size / (1024 * 1024 * 1024);
2739
- break;
2740
- case 'TB':
2741
- returnVal = size / (1024 * 1024 * 1024 * 1024);
2742
- break;
2743
- default:
2744
- returnVal = size;
2745
- break;
2746
- }
2747
- return returnVal;
1201
+ // Loại date field.
1202
+ const DATE_FIELD_TYPES = ['picker', 'range'];
1203
+ // Các pattern dùng để check định dạng date.
1204
+ const FORMAT_DATE_PATTERNS = {
1205
+ YYYY_MM_DD: /^(\d{4})([-\/])(0[1-9]|1[0-2])\2(0[1-9]|[12]\d|3[01])$/,
1206
+ DD_MM_YYYY: /^(0[1-9]|[12]\d|3[01])([-\/])(0[1-9]|1[0-2])\2(\d{4})$/,
1207
+ MM_DD_YYYY: /^(0[1-9]|1[0-2])([-\/])(0[1-9]|[12]\d|3[01])\2(\d{4})$/,
1208
+ MM_DD_YYYY_HMS: /^(0[1-9]|1[0-2])([-\/])(0[1-9]|[12]\d|3[01])\2(\d{4})\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/,
2748
1209
  };
2749
- // Lấy extension. dựa vào các cấu hình mimeTypes sẵn.
2750
- const getExtensions = (acptMimeTypes) => {
2751
- return acptMimeTypes.map(mime => `.${MIME_TYPES_TO_EXTENSION[mime] || mime}`);
1210
+
1211
+ // Chuyển đổi đối tượng Date thành string có format DDMMYYYY.
1212
+ const formatDateToDDMMYYYY = (date, separator = '-') => {
1213
+ const dd = String(date.getDate()).padStart(2, '0');
1214
+ const mm = String(date.getMonth() + 1).padStart(2, '0');
1215
+ const yyyy = date.getFullYear();
1216
+ return `${dd}${separator}${mm}${separator}${yyyy}`;
2752
1217
  };
2753
- // Tạo một file giả từ thông tin name, type và size.
2754
- const createFakeFile = (name, type, size) => {
2755
- const buffer = new Uint8Array(size); // tạo buffer theo size, không crash nếu file có dung lượng lớn.
2756
- const blob = new Blob([buffer], { type });
2757
- return new File([blob], name, { type, lastModified: Date.now() });
1218
+ // Chuyển đổi đối tượng Date thành string format DDMMYYYY.
1219
+ const formatDateToYYYYMMDD = (date, separator = '-') => {
1220
+ const dd = String(date.getDate()).padStart(2, '0');
1221
+ const mm = String(date.getMonth() + 1).padStart(2, '0');
1222
+ const yyyy = date.getFullYear();
1223
+ return `${yyyy}${separator}${mm}${separator}${dd}`;
2758
1224
  };
2759
-
2760
- // Kiểm tra tính hợp lệ của JSON string.
2761
- const isValidJSON = (str) => {
2762
- try {
2763
- JSON.parse(str);
1225
+ // Chuyển đổi string ở các dạng: YYYY-MM-DD, DD-MM-YYYY, MM-DD-YYYY đến Date, nếu sẵn là Date thì mặc định không đổi.
1226
+ const normalizeToDate = (input) => {
1227
+ if (!input)
1228
+ return null;
1229
+ if (input instanceof Date && !isNaN(input.getTime()))
1230
+ return input;
1231
+ if (typeof input !== 'string')
1232
+ return null;
1233
+ const inputTrimed = input.trim();
1234
+ let match;
1235
+ let year, month, day;
1236
+ let hour = 0, minute = 0, second = 0;
1237
+ if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.YYYY_MM_DD))) {
1238
+ year = +match[1];
1239
+ month = +match[3] - 1;
1240
+ day = +match[4];
2764
1241
  }
2765
- catch (e) {
2766
- return false;
1242
+ else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.DD_MM_YYYY))) {
1243
+ day = +match[1];
1244
+ month = +match[3] - 1;
1245
+ year = +match[4];
2767
1246
  }
2768
- return true;
2769
- };
2770
- // Tạo một chuỗi uuid.
2771
- const uuidGenerator = () => {
2772
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
2773
- var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
2774
- return v.toString(16);
2775
- });
2776
- };
2777
- const lowerCaseFirstChar = (str) => {
2778
- return str.charAt(0).toLowerCase() + str.slice(1);
2779
- };
2780
- const upperCaseFirstChar = (str) => {
2781
- return str.charAt(0).toUpperCase() + str.slice(1);
1247
+ else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY))) {
1248
+ month = +match[1] - 1;
1249
+ day = +match[3];
1250
+ year = +match[4];
1251
+ }
1252
+ else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY_HMS))) {
1253
+ month = +match[1] - 1;
1254
+ day = +match[3];
1255
+ year = +match[4];
1256
+ hour = +match[5];
1257
+ minute = +match[6];
1258
+ second = +match[7];
1259
+ }
1260
+ else {
1261
+ console.warn('Sai định dạng chuỗi date');
1262
+ return null;
1263
+ }
1264
+ const date = new Date(year, month, day, hour, minute, second);
1265
+ if (date.getFullYear() === year &&
1266
+ date.getMonth() === month &&
1267
+ date.getDate() === day &&
1268
+ date.getHours() === hour &&
1269
+ date.getMinutes() === minute &&
1270
+ date.getSeconds() === second) {
1271
+ return date;
1272
+ }
1273
+ console.warn('Ngày hoặc giờ không hợp lệ');
1274
+ return null;
2782
1275
  };
2783
- const deepCopyArray = (array) => {
2784
- return array.map(item => {
2785
- if (Array.isArray(item)) {
2786
- return deepCopyArray(item); // Deep copy nếu phần tử là mảng lồng nhau
1276
+ const normalizeToDate1 = (input) => {
1277
+ if (input) {
1278
+ if (input instanceof Date && !isNaN(input.getTime()))
1279
+ return input;
1280
+ if (typeof input !== 'string')
1281
+ return null;
1282
+ const inputTrimed = String(input).trim();
1283
+ let match;
1284
+ let year, month, day;
1285
+ if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.YYYY_MM_DD))) {
1286
+ year = +match[1];
1287
+ month = +match[3] - 1;
1288
+ day = +match[4];
2787
1289
  }
2788
- else if (item && typeof item === 'object') {
2789
- return deepCopyObject(item); // Deep copy nếu phần tử là đối tượng
1290
+ else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.DD_MM_YYYY))) {
1291
+ day = +match[1];
1292
+ month = +match[3] - 1;
1293
+ year = +match[4];
2790
1294
  }
2791
- return item; // Trả về giá trị gốc nếu không phải mảng hoặc đối tượng
2792
- });
2793
- };
2794
- const deepCopyObject = (obj) => {
2795
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
2796
- if (Array.isArray(value)) {
2797
- return [key, deepCopyArray(value)]; // Deep copy nếu giá trị là mảng
1295
+ else if ((match = inputTrimed.match(FORMAT_DATE_PATTERNS.MM_DD_YYYY))) {
1296
+ month = +match[1] - 1;
1297
+ day = +match[3];
1298
+ year = +match[4];
2798
1299
  }
2799
- else if (value && typeof value === 'object') {
2800
- return [key, deepCopyObject(value)]; // Deep copy nếu giá trị là đối tượng
1300
+ else {
1301
+ console.warn('Sai định dạng chuỗi date');
1302
+ return null;
2801
1303
  }
2802
- return [key, value]; // Trả về giá trị gốc
2803
- }));
2804
- };
2805
-
2806
- // Không cho phép nhập khoảng trắng ở đầu chuỗi, nếu không nhập gì vẫn tính đúng.
2807
- const noLeadingSpaceValidator = () => {
2808
- return (control) => {
2809
- const value = (control.value ?? '').toString();
2810
- if (!value)
2811
- return null; // Không required thì để trống là hợp lệ
2812
- return value[0] === ' ' ? { leadingSpace: true } : null;
2813
- };
2814
- };
2815
- // Không cho phép nhập toàn khoảng trắng.
2816
- const noWhitespaceValidator = () => {
2817
- return (control) => {
2818
- const isWhitespace = (control.value || '').toString().trim().length === 0;
2819
- return isWhitespace ? { whitespace: true } : null;
2820
- };
2821
- };
2822
- // Bắt đầu với một kí tự được chỉ định.
2823
- const startsWithValidator = (prefix) => {
2824
- return (control) => {
2825
- const value = control.value ?? '';
2826
- return value.startsWith(prefix) ? null : { startsWith: { requiredPrefix: prefix } };
2827
- };
2828
- };
2829
- // So sánh giá trị field A không được lớn hơn field B.
2830
- const minMaxFieldValidator = (minKey, maxKey) => {
2831
- // group: AbstractControl sẽ đại diện cho FormGroup tức là cha bao hàm các field.
2832
- return (group) => {
2833
- const min = group.get(minKey)?.value;
2834
- const max = group.get(maxKey)?.value;
2835
- if (min != null && max != null && min > max) {
2836
- return { minMaxInvalid: true };
1304
+ const date = new Date(year, month, day); // Validate: kiểm tra lại giá trị date (28/29/30/31 hợp lệ)
1305
+ if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
1306
+ return date;
2837
1307
  }
2838
- return null;
2839
- };
2840
- };
2841
- // Match giữa hai field (ví dụ: password/confirmPassword,...).
2842
- const matchFieldsValidator = (fieldToMatch) => {
2843
- return (control) => {
2844
- const parent = control.parent;
2845
- if (!parent)
2846
- return null;
2847
- const target = parent.get(fieldToMatch);
2848
- if (!target?.value)
1308
+ else {
1309
+ console.warn('Ngày đưa vào không hợp lệ');
2849
1310
  return null;
2850
- // Nếu đang validate chính nó (ví dụ: confirmPassword)
2851
- const isMismatch = control.value !== target.value;
2852
- // Nếu trước đó password (target) đang bị lỗi matchField nhưng giờ đã khớp
2853
- if (!isMismatch && target.hasError('matchField')) {
2854
- const errors = { ...target.errors };
2855
- delete errors['matchField'];
2856
- // Nếu không còn lỗi nào khác, set null
2857
- target.setErrors(Object.keys(errors).length ? errors : null);
2858
1311
  }
2859
- return isMismatch ? { matchField: true } : null;
2860
- };
2861
- };
2862
- // Async: Check if username is taken (simulate API).
2863
- const asyncUsernameValidator = (takenUsernames) => {
2864
- return (control) => {
2865
- const value = control.value ?? '';
2866
- const isTaken = takenUsernames.includes(value);
2867
- return of(isTaken ? { usernameTaken: true } : null).pipe(delay(500));
2868
- };
2869
- };
2870
- // Không có ký tự đặc biệt.
2871
- const noSpecialCharValidator = () => {
2872
- return (control) => {
2873
- const valid = /^[a-zA-Z0-9]*$/.test(control.value);
2874
- return valid ? null : { noSpecialChar: true };
2875
- };
2876
- };
2877
- // Username không được nằm trong blacklist.
2878
- const usernameBlacklistValidator = (blacklist) => {
2879
- return (control) => {
2880
- return blacklist.includes(control.value) ? { blacklisted: true } : null;
2881
- };
2882
- };
2883
- // Có thuộc mimetypes từ mảng mimetypes được chỉ định hay không?
2884
- const acceptedMimeTypesValidator = (acceptedMimeTypes) => {
2885
- return (control) => {
2886
- const value = control?.value;
2887
- if (value) {
2888
- let fileType = '';
2889
- if (value instanceof File) {
2890
- fileType = value.type;
2891
- }
2892
- if (isValidJSON(value)) {
2893
- const { contentType } = JSON.parse(value)[0] || {};
2894
- if (contentType)
2895
- fileType = contentType;
2896
- }
2897
- if (fileType) {
2898
- if (!acceptedMimeTypes.includes(fileType)) {
2899
- return {
2900
- acceptedMimeTypes: {
2901
- actualValue: fileType,
2902
- requiredValue: `Nằm trong ${acceptedMimeTypes}`,
2903
- },
2904
- };
2905
- }
2906
- }
2907
- }
2908
- return null;
2909
- };
1312
+ }
1313
+ return null;
2910
1314
  };
2911
- const maxFileSizeInMBValidator = (maxSizeInMB) => {
2912
- return (control) => {
2913
- const file = control?.value;
2914
- if (file) {
2915
- const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
2916
- if (fileSizeInMB > maxSizeInMB) {
2917
- return {
2918
- maxSizeInMB: {
2919
- actualValue: fileSizeInMB,
2920
- requiredValue: `Nhỏ hơn ${maxSizeInMB}`,
2921
- },
2922
- };
2923
- }
2924
- }
2925
- return null;
2926
- };
1315
+ // Tạo danh sách năm chạy ngược từ hiện tại đến năm chỉ định.
1316
+ const generateYearsDesc = (from) => {
1317
+ const currentYear = new Date().getFullYear();
1318
+ const years = [];
1319
+ for (let year = currentYear; year >= from; year--) {
1320
+ years.push(year);
1321
+ }
1322
+ return years;
2927
1323
  };
2928
- const reminderCustomRequiredValidator = () => {
2929
- return (control) => {
2930
- const reminderValue = control?.value;
2931
- if (!reminderValue) {
2932
- return {
2933
- reminderCustomRequired: {
2934
- requiredValue: `Giá trị radio và cả từ select hoặc date`,
2935
- },
2936
- };
2937
- }
2938
- if (reminderValue) {
2939
- const { reminderDate } = reminderValue;
2940
- if (!reminderDate) {
2941
- return {
2942
- reminderCustomRequired: {
2943
- actualValue: reminderDate,
2944
- requiredValue: `Giá trị từ select hoặc date`,
2945
- },
2946
- };
2947
- }
2948
- }
2949
- return null;
2950
- };
1324
+ // Tạo danh sách năm chạy từ năm chỉ định đến năm hiện tại.
1325
+ const generateYearsAsc = (from) => {
1326
+ const currentYear = new Date().getFullYear();
1327
+ const years = [];
1328
+ for (let year = from; year <= currentYear; year++) {
1329
+ years.push(year);
1330
+ }
1331
+ return years;
2951
1332
  };
2952
1333
 
2953
- const getDefaultPatternErrorMessage = (label, pattern) => {
2954
- let finalErrorMessage = '';
2955
- const resolvePatternResult = resolvePattern(pattern);
2956
- // if (resolvePatternResult?.type === 'unknown') {
2957
- // return finalErrorMessage; // Không phải pattern hợp lệ
2958
- // }
2959
- if (resolvePatternResult.type === 'regexObject') {
2960
- finalErrorMessage = `${label || 'Trường này'} không hợp lệ!`;
1334
+ const DEFAULT_DATE_PICKER_VALUE = '';
1335
+ /**
1336
+ * Interface của Date type là Picker.
1337
+ */
1338
+ class DatePickerField extends BaseDateField {
1339
+ constructor(init) {
1340
+ super(init);
1341
+ /**
1342
+ * Gán giá trị cho các props cần giá trị mặc định ở Date picker.
1343
+ */
1344
+ this.type = FieldTypeEnum.DatePicker;
1345
+ this.value = DEFAULT_DATE_PICKER_VALUE;
1346
+ this.initialValue = DEFAULT_DATE_PICKER_VALUE;
1347
+ this.outputAs = DatePickerOutputValueTypeEnum.String;
1348
+ Object.assign(this, init);
1349
+ }
1350
+ getDefaultValue() {
1351
+ return DEFAULT_DATE_PICKER_VALUE;
1352
+ }
1353
+ toFormValue(modelValue) {
1354
+ const value = modelValue;
1355
+ if (!value)
1356
+ return null;
1357
+ return normalizeToDate(value);
1358
+ }
1359
+ }
1360
+
1361
+ const DEFAULT_DATE_RANGE_VALUE = [];
1362
+ /**
1363
+ * Interface của Date type là Range.
1364
+ */
1365
+ class DateRangeField extends BaseDateField {
1366
+ constructor(init) {
1367
+ super(init);
1368
+ /**
1369
+ * Gán giá trị cho các props cần giá trị mặc định ở Date range.
1370
+ */
1371
+ this.type = FieldTypeEnum.DateRange;
1372
+ this.value = DEFAULT_DATE_RANGE_VALUE;
1373
+ this.initialValue = DEFAULT_DATE_RANGE_VALUE;
1374
+ this.outputAs = DateRangeOutputValueTypeEnum.StringArray;
1375
+ Object.assign(this, init);
1376
+ }
1377
+ getDefaultValue() {
1378
+ return DEFAULT_DATE_RANGE_VALUE;
2961
1379
  }
2962
- if (resolvePatternResult.type === 'regexKey') {
2963
- const patternMess = PATTERN_ERROR_MESSAGES?.[pattern] ?? '';
2964
- finalErrorMessage = `${label || 'Trường này'} không hợp lệ! ${patternMess.replace(/%s/g, () => label)}`;
1380
+ toFormValue(modelValue) {
1381
+ let value = modelValue;
1382
+ if (typeof value === 'string') {
1383
+ value = value.split(',').map(dateStr => dateStr.trim());
1384
+ }
1385
+ let dateRange = [];
1386
+ if (value?.length === 2) {
1387
+ const startDate = normalizeToDate(value[0]);
1388
+ const endDate = normalizeToDate(value[1]);
1389
+ if (startDate && endDate)
1390
+ dateRange = [startDate, endDate];
1391
+ }
1392
+ return dateRange;
2965
1393
  }
2966
- return finalErrorMessage;
2967
- };
2968
- const getDefaultMinLengthErrorMessage = (label, errorValue) => {
2969
- return `${label || 'Trường này'} cần tối thiểu ${errorValue.requiredLength} ký tự`;
2970
- };
2971
- const getDefaultMaxLengthErrorMessage = (label, errorValue) => {
2972
- return `${label || 'Trường này'} cần đât thiểu ${errorValue.requiredLength} ký tự`;
2973
- };
2974
- const getDefaultAcptMimeTypesErrorMessage = (typeString) => {
2975
- return `Tập tin tải lên không được hỗ trợ. Vui lòng tải các tập tin có định dạng ${typeString}`;
2976
- };
2977
- const getDefaultMaxSizeInMBErrorMessage = (maxSizeInMB) => {
2978
- return `Kích thước tập tin tải lên không được phép vượt quá ${maxSizeInMB} MB`;
2979
- };
2980
- const getLeadingSpaceErrorMessage = (label) => {
2981
- return `${label || 'Trường này'} không được bắt đầu bằng khoảng trắng`;
2982
- };
2983
-
2984
- var FormModeEnum;
2985
- (function (FormModeEnum) {
2986
- FormModeEnum["Add"] = "add";
2987
- FormModeEnum["Edit"] = "edit";
2988
- FormModeEnum["View"] = "view";
2989
- FormModeEnum["Design"] = "design";
2990
- })(FormModeEnum || (FormModeEnum = {}));
1394
+ }
2991
1395
 
2992
1396
  /**
2993
- * Abstract class chung cho Field.
1397
+ * Interface base chung cho Các dạng Date.
2994
1398
  */
2995
- class BaseField {
1399
+ class BaseUploadField extends BaseField {
1400
+ /**
1401
+ * Props validation chung cho các dạng Date.
1402
+ */
2996
1403
  constructor(init) {
2997
- // Thứ tự hiển thị của field
2998
- this.seqNum = 0;
2999
- if (!init?.key || !init?.label) {
3000
- throw new Error('key, label, and type are required');
3001
- }
1404
+ super(init);
3002
1405
  /**
3003
- * Gán giá trị cho các props cần giá trị mặc định ở BaseField.
1406
+ * Gán giá trị cho các props cần giá trị mặc định ở Date.
3004
1407
  */
3005
- this.id = uuidGenerator();
3006
- this.required = false;
3007
- this.grid = { xs: 24 };
3008
- this.labelGrid = { xs: 24 };
3009
- this.controlGrid = { xs: 24 };
3010
- this.labelAlign = 'left';
3011
- this.isHidden = false;
3012
- this.isDisabled = false;
3013
- this.isParent = false;
3014
- this.allowSetValue = [FormModeEnum.Add, FormModeEnum.Edit, FormModeEnum.View];
3015
- this.viewMode = ViewModeEnum.form;
3016
- this.enableFieldChange = false;
1408
+ this.controlType = ControlTypeEnum.Upload;
1409
+ this.outputAs = UploadOutputValueTypeEnum.Object;
1410
+ this.apiFileConfig = {
1411
+ uploadUrl: '',
1412
+ deleteUrl: '',
1413
+ };
1414
+ this.acceptedMimeTypes = DEFAULT_ACCEPTED_MIME_TYPES;
1415
+ this.maxSizeInMB = 25;
1416
+ this.showAcceptedExtensionGuide = true;
1417
+ this.uploadTemp = true;
3017
1418
  Object.assign(this, init);
3018
1419
  }
3019
- // Tạo các validate chung cho các props abstract. this khi gọi trong class con sẽ đại diện cho đối tượng tạo từ class đó.
3020
- getCommonValidators() {
3021
- const validators = [];
3022
- if (this.required)
3023
- validators.push(Validators.required);
1420
+ // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi angular cho Input Password.
1421
+ getCustomBaseUploadValidators() {
1422
+ const validators = this.getCustomValidators();
1423
+ // Kiểm tra xem file tải lên có type nằm trong mảng acceptedMimeTypes đã chỉ định hay không.
1424
+ if (this.acceptedMimeTypes?.length > 0)
1425
+ validators.push(acceptedMimeTypesValidator(this.acceptedMimeTypes));
1426
+ // Kiểm tra kích thước tối đa mà file tải lên được cho phép.
1427
+ validators.push(maxFileSizeInMBValidator(this.maxSizeInMB));
3024
1428
  return validators;
3025
1429
  }
3026
- // Tạo các validate custom chung cho các props ở abstract. this khi gọi trong class con sẽ đại diện cho đối tượng tạo từ class đó.
3027
- getCustomValidators() {
3028
- const validators = [];
3029
- if (this.matchField)
3030
- validators.push(matchFieldsValidator(this.matchField));
3031
- return validators;
1430
+ // Tạo Form Control cho Input Field.
1431
+ toFormControl() {
1432
+ const commonValidators = this.getCommonValidators();
1433
+ const customValidators = this.getCustomBaseUploadValidators();
1434
+ return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
3032
1435
  }
3033
1436
  getErrorMessage(errorKey, errorValue, allFields) {
3034
- if (errorKey === CommonValidaterEnum.Required) {
3035
- return `${this.label || 'Trường này'} là bắt buộc`;
1437
+ // Lấy error message mặc định của acceptedMimeTypes.
1438
+ if (errorKey === CustomValidaterEnum.AcceptedMimiTypes) {
1439
+ const typeString = getExtensions(this.acceptedMimeTypes).join(', ');
1440
+ return getDefaultAcptMimeTypesErrorMessage(typeString);
3036
1441
  }
3037
- if (errorKey === CustomValidaterEnum.MatchField) {
3038
- const matchKey = this.matchField;
3039
- const targetLabel = allFields.find(f => f.key === matchKey)?.label || matchKey;
3040
- return `${this.label} không khớp với ${targetLabel}`;
1442
+ // Lấy error message mặc định của maxSizeInMB.
1443
+ if (errorKey === CustomValidaterEnum.MaxSizeInMB) {
1444
+ return getDefaultMaxSizeInMBErrorMessage(this.maxSizeInMB);
3041
1445
  }
3042
- return '';
1446
+ return super.getErrorMessage(errorKey, errorValue, allFields);
3043
1447
  }
3044
1448
  }
3045
1449
 
3046
- // Dữ liệu sau khi đã tiến hành xử lý gom nhóm từ fields và groups.
3047
- class GroupedFields {
3048
- constructor(init) {
3049
- this.noGroup = [];
3050
- this.grouped = [];
3051
- Object.assign(this, init);
3052
- }
3053
- }
3054
- // Đại diện cho 1 group.
3055
- class FieldGroup {
1450
+ const DEFAULT_SINGLE_FILE_UPLOAD_VALUE = '';
1451
+ /**
1452
+ * Interface của Single file upload. Đầu vào có thể là chuỗi JSON hoặc đối tượng File.
1453
+ */
1454
+ class SingleFileUploadField extends BaseUploadField {
3056
1455
  constructor(init) {
3057
- this.isActive = false;
3058
- this.showArrow = true;
3059
- this.disabled = false;
3060
- this.required = false;
1456
+ super(init);
1457
+ /**
1458
+ * Gán giá trị cho các props cần giá trị mặc định ở Single file upload.
1459
+ */
1460
+ this.type = FieldTypeEnum.SingleFileUpload;
1461
+ this.value = DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
1462
+ this.initialValue = DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
1463
+ this.suffixes = [];
3061
1464
  Object.assign(this, init);
3062
1465
  }
3063
- }
3064
-
3065
- const buildForm = (fields, mode, model) => {
3066
- const groupObj = {};
3067
- // console.log("model", model);
3068
- for (const field of fields) {
3069
- // Loại bỏ nếu quyết định ẩn field đi.
3070
- if (field.isHidden)
3071
- continue;
3072
- // Nếu là dạng InputPasswordField nhưng ở mode edit và view thì không được dùng.
3073
- if (field instanceof InputPasswordField &&
3074
- (mode === FormModeEnum.Edit || mode === FormModeEnum.View))
3075
- continue;
3076
- const fieldKey = field?.key;
3077
- // field.value = field.allowSetValue ? (model?.[fieldKey] || field.value) : null;
3078
- if (field.allowSetValue) {
3079
- field.value = field.getDefaultValue();
3080
- field.modelValue = null;
3081
- if (model?.[fieldKey]) {
3082
- field.value = field.toFormValue(model[fieldKey]);
3083
- field.modelValue = model[fieldKey];
3084
- }
3085
- field.initialValue = field.value;
3086
- }
3087
- // console.log("e", field.value );
3088
- // field.value = field.allowSetValue ? field.toFormValue(model?.[fieldKey] | field.getDefaultValue()) : field.getDefaultValue();
3089
- const control = field.toFormControl();
3090
- control.reset(field.value);
3091
- if (field.isDisabled) {
3092
- //console.log(field.key, 'dfdfdfdf');
3093
- control?.disable();
3094
- }
3095
- groupObj[fieldKey] = control;
1466
+ getDefaultValue() {
1467
+ return DEFAULT_SINGLE_FILE_UPLOAD_VALUE;
3096
1468
  }
3097
- return new FormGroup(groupObj);
3098
- };
3099
- // Tạo placeholder mặc định cho các dạng Input, Textarea.
3100
- const createPlaceholder = (field) => {
3101
- const placeholder = field?.placeholder;
3102
- if (placeholder)
3103
- return placeholder;
3104
- const label = field?.label;
3105
- if (label)
3106
- return `Nhập ${label?.toLowerCase()}`;
3107
- return `Nhập ${field.key?.toLowerCase()}`;
3108
- };
3109
- // Tạo placeholder mặc định cho các dạng Select.
3110
- const createSelectPlaceholder = (field) => {
3111
- const placeholder = field?.placeholder;
3112
- if (placeholder)
3113
- return placeholder;
3114
- const label = field?.label;
3115
- if (label) {
3116
- const isSingleSelect = field?.type === FieldTypeEnum.SingleSelect;
3117
- return `Chọn ${isSingleSelect ? '' : 'các'} ${label?.toLowerCase()}`;
1469
+ toFormValue(modelValue) {
1470
+ return String(modelValue);
3118
1471
  }
3119
- return `Nhập ${field.key?.toLowerCase()}`;
3120
- };
1472
+ }
3121
1473
 
3122
- const isInputTextField = (field) => {
3123
- return field.controlType === ControlTypeEnum.Input && field.type === FieldTypeEnum.InputText;
3124
- };
3125
- const isInputPasswordField = (field) => {
3126
- return field.controlType === ControlTypeEnum.Input && field.type === FieldTypeEnum.InputPassword;
3127
- };
3128
- const isSingleSelectField = (field) => {
3129
- return field.controlType === ControlTypeEnum.Select && field.type === FieldTypeEnum.SingleSelect;
3130
- };
3131
- const isMultiSelectField = (field) => {
3132
- return field.controlType === ControlTypeEnum.Select && field.type === FieldTypeEnum.MultiSelect;
3133
- };
3134
- const isDatePickerField = (field) => {
3135
- return field.controlType === ControlTypeEnum.Date && field.type === FieldTypeEnum.DatePicker;
3136
- };
3137
- const isDateRangeField = (field) => {
3138
- return field.controlType === ControlTypeEnum.Date && field.type === FieldTypeEnum.DateRange;
3139
- };
3140
- const isSingleFileUploadField = (field) => {
3141
- return (field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.SingleFileUpload);
3142
- };
3143
- const isMultiFileUploadField = (field) => {
3144
- return (field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.MultiFileUpload);
3145
- };
3146
- const isAvatarUploadField = (field) => {
3147
- return field.controlType === ControlTypeEnum.Upload && field.type === FieldTypeEnum.AvatarUpload;
3148
- };
3149
- // Tạo FieldGroup render UI từ thông tin fields và groups truyền vào cho dynamic-form.
3150
- const buildGroupedFields = (fields, groups = []) => {
3151
- const noGroup = fields.filter(field => !field?.groupCode || field?.groupCode.toLowerCase() === 'none');
3152
- const sortedGroups = [...groups].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
3153
- const grouped = sortedGroups
3154
- .map(group => ({
3155
- group,
3156
- fields: fields.filter(field => field.groupCode?.toLowerCase() === group.code.toLowerCase()),
3157
- }))
3158
- .filter(groupedFields => groupedFields.fields.length > 0);
3159
- return new GroupedFields({
3160
- noGroup,
3161
- grouped,
3162
- });
3163
- };
1474
+ const DEFAULT_MULTI_FILE_UPLOAD_VALUE = '';
1475
+ /**
1476
+ * Interface của Multi file upload.
1477
+ */
1478
+ class MultiFileUploadField extends BaseUploadField {
1479
+ constructor(init) {
1480
+ super(init);
1481
+ /**
1482
+ * Gán giá trị cho các props cần giá trị mặc định ở Multi file upload.
1483
+ */
1484
+ this.type = FieldTypeEnum.MultiFileUpload;
1485
+ this.value = DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1486
+ this.initialValue = DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1487
+ this.maxFileCount = 6;
1488
+ Object.assign(this, init);
1489
+ }
1490
+ getDefaultValue() {
1491
+ return DEFAULT_MULTI_FILE_UPLOAD_VALUE;
1492
+ }
1493
+ toFormValue(modelValue) {
1494
+ return String(modelValue);
1495
+ }
1496
+ }
1497
+
1498
+ const DEFAULT_MULTI_IMAGE_UPLOAD_VALUE = '';
1499
+ /**
1500
+ * Interface của Multi image upload.
1501
+ */
1502
+ class MultiImageUploadField extends BaseUploadField {
1503
+ constructor(init) {
1504
+ super(init);
1505
+ /**
1506
+ * Gán giá trị cho các props cần giá trị mặc định ở Multi image upload.
1507
+ */
1508
+ this.type = FieldTypeEnum.MultiImageUpload;
1509
+ this.value = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1510
+ this.initialValue = DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1511
+ this.maxFileCount = 6;
1512
+ this.imageCardWidth = '108px';
1513
+ this.imageCardHeight = '108px';
1514
+ Object.assign(this, init);
1515
+ }
1516
+ getDefaultValue() {
1517
+ return DEFAULT_MULTI_IMAGE_UPLOAD_VALUE;
1518
+ }
1519
+ toFormValue(modelValue) {
1520
+ return String(modelValue);
1521
+ }
1522
+ }
3164
1523
 
3165
1524
  const DYNAMIC_FORM_CONFIG = new InjectionToken('DYNAMIC_FORM_CONFIG');
3166
1525
 
@@ -3264,6 +1623,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
3264
1623
  args: [{ providedIn: 'root' }]
3265
1624
  }] });
3266
1625
 
1626
+ const DEFAULT_AVATAR_UPLOAD_VALUE = '';
1627
+ /**
1628
+ * Interface của Avatar upload.
1629
+ */
1630
+ class AvatarUploadField extends BaseUploadField {
1631
+ constructor(init) {
1632
+ super(init);
1633
+ /**
1634
+ * Gán giá trị cho các props cần giá trị mặc định ở Avatar upload.
1635
+ */
1636
+ this.type = FieldTypeEnum.AvatarUpload;
1637
+ this.value = DEFAULT_AVATAR_UPLOAD_VALUE;
1638
+ this.acceptedMimeTypes = DEFAULT_ACCEPTED_IMAGE_MIME_TYPES;
1639
+ this.initialValue = DEFAULT_AVATAR_UPLOAD_VALUE;
1640
+ Object.assign(this, init);
1641
+ }
1642
+ getDefaultValue() {
1643
+ return DEFAULT_AVATAR_UPLOAD_VALUE;
1644
+ }
1645
+ toFormValue(modelValue) {
1646
+ return String(modelValue);
1647
+ }
1648
+ }
1649
+
3267
1650
  const DEFAULT_TREE_SELECT_VALUE = null;
3268
1651
  /**
3269
1652
  * Interface của Select Field type là dạng Single.
@@ -3332,7 +1715,7 @@ class DynamicFormService {
3332
1715
  * Thao tác với control và fields cũng như output đầu ra của data form.
3333
1716
  */
3334
1717
  setFieldValue(fieldKey, rawValue) {
3335
- const field = this.fields.find(f => f.key === fieldKey);
1718
+ const field = this.fields.find((f) => f.key === fieldKey);
3336
1719
  if (!field)
3337
1720
  return;
3338
1721
  const formValue = field.toFormValue(rawValue);
@@ -3398,9 +1781,10 @@ class DynamicFormService {
3398
1781
  return fields.sort((a, b) => a.seqNum - b.seqNum);
3399
1782
  }
3400
1783
  createFieldFromJson(fieldConfig, objField) {
3401
- fieldConfig.key = fieldConfig?.key || lowerCaseFirstChar(objField.code) || '';
3402
- fieldConfig.label = fieldConfig?.label || objField?.name || '';
3403
- fieldConfig.groupCode = fieldConfig.groupCode ?? objField.viewType ?? '';
1784
+ fieldConfig.key =
1785
+ fieldConfig?.key || lowerCaseFirstChar(objField.code) || "";
1786
+ fieldConfig.label = fieldConfig?.label || objField?.name || "";
1787
+ fieldConfig.groupCode = fieldConfig.groupCode ?? objField.viewType ?? "";
3404
1788
  fieldConfig.seqNum = fieldConfig.seqNum ?? objField.seqNum;
3405
1789
  fieldConfig.required = fieldConfig.required ?? objField.required === 3;
3406
1790
  const fieldType = fieldConfig.type;
@@ -3416,14 +1800,18 @@ class DynamicFormService {
3416
1800
  if (fieldType === FieldTypeEnum.SingleSelect) {
3417
1801
  const singleSelect = fieldConfig;
3418
1802
  if (!singleSelect.apiConfig) {
3419
- singleSelect.apiConfig = { baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || '', objField.objTypeGroupCode) };
1803
+ singleSelect.apiConfig = {
1804
+ baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || "", objField.objTypeGroupCode),
1805
+ };
3420
1806
  }
3421
1807
  return new SingleSelectField({ ...singleSelect });
3422
1808
  }
3423
1809
  if (fieldType === FieldTypeEnum.MultiSelect) {
3424
1810
  const multiSelect = fieldConfig;
3425
1811
  if (!multiSelect.apiConfig) {
3426
- multiSelect.apiConfig = { baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || '', objField.objTypeGroupCode) };
1812
+ multiSelect.apiConfig = {
1813
+ baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || "", objField.objTypeGroupCode),
1814
+ };
3427
1815
  }
3428
1816
  return new MultiSelectField({ ...multiSelect });
3429
1817
  }
@@ -3431,7 +1819,7 @@ class DynamicFormService {
3431
1819
  const treeSelect = fieldConfig;
3432
1820
  if (!treeSelect.apiConfig) {
3433
1821
  treeSelect.apiConfig = {
3434
- baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || '', objField.objTypeGroupCode),
1822
+ baseUrl: this.formConfigService.handleApiUrlCreation(objField.apiURL || "", objField.objTypeGroupCode),
3435
1823
  };
3436
1824
  }
3437
1825
  return new TreeSelectField({ ...treeSelect });
@@ -3443,26 +1831,32 @@ class DynamicFormService {
3443
1831
  return new DateRangeField({ ...fieldConfig });
3444
1832
  }
3445
1833
  if (fieldType === FieldTypeEnum.SingleFileUpload) {
3446
- return new SingleFileUploadField({ ...fieldConfig });
1834
+ return new SingleFileUploadField({
1835
+ ...fieldConfig,
1836
+ });
3447
1837
  }
3448
1838
  if (fieldType === FieldTypeEnum.MultiFileUpload) {
3449
- return new MultiFileUploadField({ ...fieldConfig });
1839
+ return new MultiFileUploadField({
1840
+ ...fieldConfig,
1841
+ });
3450
1842
  }
3451
1843
  if (fieldType === FieldTypeEnum.AvatarUpload) {
3452
1844
  return new AvatarUploadField({ ...fieldConfig });
3453
1845
  }
3454
1846
  if (fieldType === FieldTypeEnum.MultiImageUpload) {
3455
- return new MultiImageUploadField({ ...fieldConfig });
1847
+ return new MultiImageUploadField({
1848
+ ...fieldConfig,
1849
+ });
3456
1850
  }
3457
1851
  return null;
3458
1852
  }
3459
1853
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3460
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormService, providedIn: 'root' }); }
1854
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormService, providedIn: "root" }); }
3461
1855
  }
3462
1856
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormService, decorators: [{
3463
1857
  type: Injectable,
3464
1858
  args: [{
3465
- providedIn: 'root',
1859
+ providedIn: "root",
3466
1860
  }]
3467
1861
  }] });
3468
1862
 
@@ -3762,7 +2156,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
3762
2156
  args: [{ required: false }]
3763
2157
  }] } });
3764
2158
 
3765
- const ZORRO_MODULES$f = [NzDatePickerModule, NzInputModule];
2159
+ const ZORRO_MODULES$e = [NzDatePickerModule, NzInputModule];
3766
2160
  class DatePickerComponent extends BaseValueAccessor {
3767
2161
  constructor() {
3768
2162
  super();
@@ -3856,7 +2250,7 @@ class DatePickerComponent extends BaseValueAccessor {
3856
2250
  }
3857
2251
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DatePickerComponent, decorators: [{
3858
2252
  type: Component,
3859
- args: [{ selector: 'app-date-picker', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$f], providers: [
2253
+ args: [{ selector: 'app-date-picker', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$e], providers: [
3860
2254
  {
3861
2255
  provide: NG_VALUE_ACCESSOR,
3862
2256
  useExisting: forwardRef(() => DatePickerComponent),
@@ -3865,7 +2259,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
3865
2259
  ], template: "@if (modeSignal() === formModeEnum.View) {\r\n <input nz-input [value]=\"displayValue\" [disabled]=\"true\" class=\"modeView\" />\r\n} @else {\r\n <nz-date-picker\r\n [id]=\"fieldSignal().key\"\r\n [class]=\"'w-100'\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDate($event)\"\r\n >\r\n </nz-date-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .ant-input[disabled]{background-color:#fff}.modeView{pointer-events:none;color:#575757!important;font-size:12px}\n"] }]
3866
2260
  }], ctorParameters: () => [] });
3867
2261
 
3868
- const ZORRO_MODULES$e = [NzDatePickerModule, NzInputModule];
2262
+ const ZORRO_MODULES$d = [NzDatePickerModule, NzInputModule];
3869
2263
  class DateRangeComponent extends BaseValueAccessor {
3870
2264
  constructor() {
3871
2265
  super();
@@ -3957,7 +2351,7 @@ class DateRangeComponent extends BaseValueAccessor {
3957
2351
  }
3958
2352
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DateRangeComponent, decorators: [{
3959
2353
  type: Component,
3960
- args: [{ selector: 'app-date-range', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$e], providers: [
2354
+ args: [{ selector: 'app-date-range', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$d], providers: [
3961
2355
  {
3962
2356
  provide: NG_VALUE_ACCESSOR,
3963
2357
  useExisting: forwardRef(() => DateRangeComponent),
@@ -3966,7 +2360,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
3966
2360
  ], template: "@if (modeSignal() === formModeEnum.View) {\r\n @if (value!.length > 0) {\r\n <nz-range-picker\r\n [class]=\"'w-100'\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabled]=\"true\"\r\n [(ngModel)]=\"value\"\r\n class=\"modeView\"\r\n [nzSuffixIcon]=\"suffixIcon\"\r\n ></nz-range-picker>\r\n <ng-template #suffixIcon>\r\n <!-- \u0110\u1EC3 tr\u1ED1ng, ho\u1EB7c custom icon kh\u00E1c -->\r\n </ng-template>\r\n } @else {\r\n <input nz-input [value]=\"displayValueNoInfo\" [disabled]=\"true\" class=\"modeView\" />\r\n }\r\n} @else {\r\n <nz-range-picker\r\n [id]=\"fieldSignal().key\"\r\n class=\"w-100\"\r\n [nzDisabled]=\"isDisabled\"\r\n [nzFormat]=\"fieldSignal().format\"\r\n [nzDisabledDate]=\"disabledDate()\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder ?? ['Ng\u00E0y b\u1EAFt \u0111\u1EA7u', 'Ng\u00E0y k\u1EBFt th\u00FAc']\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeDateRange($event)\"\r\n ></nz-range-picker>\r\n}\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}:host ::ng-deep .modeView.ant-picker.ant-picker-disabled{background-color:inherit!important;cursor:default;pointer-events:none}:host ::ng-deep .modeView .ant-picker-input>input[disabled]{color:#575757!important}.modeView{pointer-events:none;background-color:#fff;font-size:12px;color:#575757!important}\n"] }]
3967
2361
  }], ctorParameters: () => [] });
3968
2362
 
3969
- const ZORRO_MODULES$d = [NzInputModule, NzIconModule];
2363
+ const ZORRO_MODULES$c = [NzInputModule, NzIconModule];
3970
2364
  class InputPasswordComponent extends BaseValueAccessor {
3971
2365
  constructor() {
3972
2366
  super();
@@ -4012,7 +2406,7 @@ class InputPasswordComponent extends BaseValueAccessor {
4012
2406
  }
4013
2407
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputPasswordComponent, decorators: [{
4014
2408
  type: Component,
4015
- args: [{ selector: 'app-input-password', standalone: true, imports: [...ZORRO_MODULES$d], providers: [
2409
+ args: [{ selector: 'app-input-password', standalone: true, imports: [...ZORRO_MODULES$c], providers: [
4016
2410
  {
4017
2411
  provide: NG_VALUE_ACCESSOR,
4018
2412
  useExisting: forwardRef(() => InputPasswordComponent),
@@ -4039,7 +2433,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
4039
2433
  }]
4040
2434
  }], ctorParameters: () => [] });
4041
2435
 
4042
- const ZORRO_MODULES$c = [NzButtonModule, NzIconModule];
2436
+ const ZORRO_MODULES$b = [NzButtonModule, NzIconModule];
4043
2437
  class SuffixComponent {
4044
2438
  constructor() {
4045
2439
  this.formId = '';
@@ -4057,14 +2451,14 @@ class SuffixComponent {
4057
2451
  }
4058
2452
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SuffixComponent, decorators: [{
4059
2453
  type: Component,
4060
- args: [{ selector: 'app-suffix', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$c], template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.suffix-btn{margin-left:.5rem}\n"] }]
2454
+ args: [{ selector: 'app-suffix', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$b], template: "<button\r\n nz-button\r\n nzType=\"primary\"\r\n nzSize=\"small\"\r\n [title]=\"suffixConfig.description\"\r\n class=\"h-100 suffix-btn font-size-12\"\r\n [ngClass]=\"suffixConfig.class ?? ''\"\r\n (click)=\"handleSuffix()\"\r\n>\r\n @if (suffixConfig.icon) {\r\n <span class=\"icon\">\r\n <!-- N\u1EBFu l\u00E0 icon Zorro -->\r\n @if (suffixConfig.icon.type === 'zorro') {\r\n <span\r\n nz-icon\r\n [nzType]=\"suffixConfig.icon.value\"\r\n nzTheme=\"outline\"\r\n [ngClass]=\"suffixConfig.icon.class ?? ''\"\r\n >\r\n </span>\r\n }\r\n\r\n <!-- N\u1EBFu l\u00E0 Boxicons -->\r\n @if (suffixConfig.icon.type === 'mdi') {\r\n <span [class]=\"'mdi ' + suffixConfig.icon.value\" [ngClass]=\"suffixConfig.icon.class ?? ''\">\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ suffixConfig.label }}\r\n </span>\r\n }\r\n\r\n @if (!suffixConfig.icon && !suffixConfig.label) {\r\n <span class=\"label\">\r\n {{ defaultText }}\r\n </span>\r\n }\r\n</button>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.suffix-btn{margin-left:.5rem}\n"] }]
4061
2455
  }], propDecorators: { suffixConfig: [{
4062
2456
  type: Input
4063
2457
  }], formId: [{
4064
2458
  type: Input
4065
2459
  }] } });
4066
2460
 
4067
- const ZORRO_MODULES$b = [
2461
+ const ZORRO_MODULES$a = [
4068
2462
  NzInputModule,
4069
2463
  NgxCurrencyDirective,
4070
2464
  CurrencyPipe,
@@ -4136,7 +2530,7 @@ class InputTextComponent extends BaseValueAccessor {
4136
2530
  }
4137
2531
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: InputTextComponent, decorators: [{
4138
2532
  type: Component,
4139
- args: [{ selector: 'app-input-text', standalone: true, imports: [...ZORRO_MODULES$b, ...COMPONENTS$6], providers: [
2533
+ args: [{ selector: 'app-input-text', standalone: true, imports: [...ZORRO_MODULES$a, ...COMPONENTS$6], providers: [
4140
2534
  {
4141
2535
  provide: NG_VALUE_ACCESSOR,
4142
2536
  useExisting: forwardRef(() => InputTextComponent),
@@ -4145,244 +2539,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImpo
4145
2539
  ], template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n [(ngModel)]=\"inputDisplayValue\"\r\n class=\"modeView\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n />\r\n } @else {\r\n <input nz-input [value]=\"inputDisplayValue\" class=\"modeView\" />\r\n }\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (fieldSignal().isCurrency) {\r\n <span class=\"modeView\">\r\n {{ inputDisplayValue | currency: 'VND' : 'symbol' : '1.0-0' : 'vi-VN' }}\r\n </span>\r\n } @else {\r\n <span class=\"modeView\">{{ inputDisplayValue }}</span>\r\n }\r\n }\r\n } @else {\r\n @if (fieldSignal().isCurrency) {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [currencyMask]=\"{\r\n prefix: '',\r\n suffix: ' VND',\r\n thousands: ',',\r\n decimal: '.',\r\n precision: 2,\r\n align: 'left',\r\n }\"\r\n [(ngModel)]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (ngModelChange)=\"handleChangeFormValue($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n } @else {\r\n <input\r\n nz-input\r\n class=\"input-control\"\r\n [value]=\"value\"\r\n [placeholder]=\"fieldSignal().placeholder!\"\r\n [disabled]=\"isDisabled\"\r\n (input)=\"handleChangeInputText($event)\"\r\n (blur)=\"onTouched()\"\r\n />\r\n }\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.input-text-wrapper{display:flex}.modeView{pointer-events:none;color:#575757;font-size:12px}\n"] }]
4146
2540
  }], ctorParameters: () => [] });
4147
2541
 
4148
- /**
4149
- * Interface base chung cho Các dạng Radio.
4150
- */
4151
- class RadioBaseField extends BaseField {
4152
- /**
4153
- * Props validation chung cho các dạng Radio.
4154
- */
4155
- // ................. Chưa có .................
4156
- constructor(init) {
4157
- super(init);
4158
- /**
4159
- * Gán giá trị cho các props cần giá trị mặc định ở Radio.
4160
- */
4161
- this.controlType = ControlTypeEnum.Radio;
4162
- this.bindLabel = DEFAULT_BIND_LABEL;
4163
- this.bindValue = DEFAULT_BIND_VALUE;
4164
- this.dependsOn = [];
4165
- Object.assign(this, init);
4166
- }
4167
- // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi Reactive form của angular cho Radio.
4168
- getCommonSelectValidators() {
4169
- const validators = this.getCommonValidators();
4170
- return validators;
4171
- }
4172
- // Mảng ValidatorFn các custom validation có thể dùng cho Radio.
4173
- getCustomSelectValidators() {
4174
- const validators = this.getCustomValidators();
4175
- return validators;
4176
- }
4177
- // Tạo Form Control cho Input Field.
4178
- toFormControl() {
4179
- const commonValidators = this.getCommonSelectValidators();
4180
- const customValidators = this.getCustomSelectValidators();
4181
- return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
4182
- }
4183
- getErrorMessage(errorKey, errorValue, allFields) {
4184
- return super.getErrorMessage(errorKey, errorValue, allFields);
4185
- }
4186
- }
4187
-
4188
- const DEFAULT_REMINDER_TYPE_RADIO_VALUE = null;
4189
- // const DEFAULT_OPTIONS = [
4190
- // {
4191
- // code: 'REMINDERDATE_01',
4192
- // name: 'Chọn thời điểm',
4193
- // type: 'select',
4194
- // },
4195
- // {
4196
- // code: 'REMINDERDATE_02',
4197
- // name: 'Chọn ngày cụ thể',
4198
- // type: 'date',
4199
- // },
4200
- // ] as ReminderOption[];
4201
- /**
4202
- * Interface của Select Field type là dạng Reminder Type Radio.
4203
- */
4204
- class ReminderTypeRadioField extends RadioBaseField {
4205
- constructor(init) {
4206
- super(init);
4207
- /**
4208
- * Gán giá trị cho các props cần giá trị mặc định ở Reminder Type Radio.
4209
- */
4210
- this.type = FieldTypeEnum.ReminderTypeRadio;
4211
- this.value = DEFAULT_REMINDER_TYPE_RADIO_VALUE;
4212
- this.initialValue = DEFAULT_REMINDER_TYPE_RADIO_VALUE;
4213
- // this.options = DEFAULT_OPTIONS;
4214
- Object.assign(this, init);
4215
- }
4216
- getDefaultValue() {
4217
- return DEFAULT_REMINDER_TYPE_RADIO_VALUE;
4218
- }
4219
- toFormValue(modelValue) {
4220
- console.log('modelValue', modelValue);
4221
- return modelValue;
4222
- }
4223
- getCustomReminderTypeValidators() {
4224
- const validators = [];
4225
- if (this.required)
4226
- validators.push(reminderCustomRequiredValidator());
4227
- return validators;
4228
- }
4229
- // Tạo Form Control cho Input Field.
4230
- toFormControl() {
4231
- const customValidators = this.getCustomReminderTypeValidators();
4232
- return new FormControl(this.value ?? '', [...customValidators]);
4233
- }
4234
- }
4235
-
4236
- class ReminderValue {
4237
- constructor(init) {
4238
- this.reminderDate = '';
4239
- Object.assign(this, init);
4240
- }
4241
- }
4242
-
4243
- const ZORRO_MODULES$a = [NzRadioModule, NzSelectModule, NzDatePickerModule];
4244
- class ReminderTypeRadioComponent extends BaseValueAccessor {
4245
- get selectDetail() {
4246
- return this.selectedOption.type === 'select'
4247
- ? this.selectedOption.detail
4248
- : undefined;
4249
- }
4250
- get dateDetail() {
4251
- return this.selectedOption.type === 'date'
4252
- ? this.selectedOption.detail
4253
- : undefined;
4254
- }
4255
- constructor() {
4256
- super();
4257
- // Field config từ mảng fields.
4258
- this.fieldSignal = computed(() => {
4259
- const base = this.fieldInput();
4260
- if (!(base instanceof ReminderTypeRadioField) ||
4261
- (base.type !== FieldTypeEnum.ReminderTypeRadio && base.controlType !== ControlTypeEnum.Radio)) {
4262
- throw new Error('Expected ReminderTypeRadioField');
4263
- }
4264
- return base;
4265
- });
4266
- this.options = [];
4267
- this.selectedOptionCode = '';
4268
- this.deadline = null;
4269
- this.disabledAfterLimit = (current) => {
4270
- if (!this.deadline)
4271
- return false;
4272
- // clone và normalize limitDate
4273
- const dayBefore = new Date(this.deadline);
4274
- dayBefore.setHours(0, 0, 0, 0);
4275
- dayBefore.setDate(dayBefore.getDate());
4276
- // normalize current
4277
- const cur = new Date(current);
4278
- cur.setHours(0, 0, 0, 0);
4279
- return cur >= dayBefore;
4280
- };
4281
- this.toast = inject(ToastrService);
4282
- // 1. Effect chung Add + Edit
4283
- effect(() => {
4284
- if (this.isAddOrEdit()) {
4285
- const field = untracked(() => this.fieldSignal());
4286
- const { options } = field;
4287
- // Reminder option.
4288
- this.options = options || [];
4289
- // Mặc định chọn index 0 của radio reminder.
4290
- if (this.options.length > 0) {
4291
- this.selectedOption = this.options[0];
4292
- this.selectedOptionCode = this.options[0].code;
4293
- this.value = new ReminderValue({
4294
- reminderDateType: {
4295
- code: this.selectedOption.code,
4296
- },
4297
- reminderDate: '',
4298
- });
4299
- }
4300
- }
4301
- });
4302
- effect(() => {
4303
- const key = this.fieldSignal().dependsOn[0].fieldKey;
4304
- const dependValuesMap = this.fieldService.getDependValuesMap();
4305
- const dependSignal = dependValuesMap.get(key); // <- giữ nguyên signal
4306
- if (dependSignal) {
4307
- const value = dependSignal(); // lấy giá trị hiện tại
4308
- if (value)
4309
- this.deadline = new Date(value);
4310
- else
4311
- this.deadline = null;
4312
- }
4313
- });
4314
- }
4315
- // Chạy khi value form được gán bởi các hàm reset, patchValue, setValue.
4316
- writeValueToView(value) {
4317
- // if (this.isView()) {
4318
- // this.displayValue = this.getDisplayValue(
4319
- // this.fieldSignal().modelValue as string | Record<string, unknown>,
4320
- // );
4321
- // return;
4322
- // }
4323
- if (!this.isAddOrEdit())
4324
- return;
4325
- const { key } = untracked(() => this.fieldSignal());
4326
- // Gán data vào đầu ra của form.
4327
- const newOutputValue = value;
4328
- this.patchFormData(key, newOutputValue);
4329
- }
4330
- // Xử lý phụ thuộc khi change value
4331
- handleValueChange(value) {
4332
- const newOutputValue = value && new ReminderValue({ ...value });
4333
- if (this.selectedOption.type === 'date') {
4334
- const date = newOutputValue.reminderDate;
4335
- newOutputValue.reminderDate = value && formatDateToYYYYMMDD(date);
4336
- }
4337
- this.patchFormData(this.fieldSignal().key, newOutputValue);
4338
- }
4339
- reminderRadioChange(radioValue) {
4340
- const currOption = this.options.find(o => o.code === radioValue);
4341
- if (currOption) {
4342
- this.selectedOption = currOption;
4343
- this.selectedOptionCode = currOption.code;
4344
- if (currOption.type === 'date' && !this.deadline)
4345
- this.toast.warning(`Chọn ${this.fieldSignal().dependsOn[0].fieldLabel} trước khi chọn ngày cụ thể cho việc Nhắc báo cáo`);
4346
- }
4347
- this.value = new ReminderValue({
4348
- reminderDateType: {
4349
- code: radioValue,
4350
- },
4351
- reminderDate: '',
4352
- });
4353
- }
4354
- changeOptionSelect(optionValue) {
4355
- this.propagateValue(new ReminderValue({
4356
- ...this.value,
4357
- reminderDate: optionValue,
4358
- }));
4359
- }
4360
- changeDate(date) {
4361
- this.propagateValue(new ReminderValue({
4362
- ...this.value,
4363
- reminderDate: date,
4364
- }));
4365
- }
4366
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ReminderTypeRadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4367
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: ReminderTypeRadioComponent, isStandalone: true, selector: "app-reminder-type-radio", providers: [
4368
- {
4369
- provide: NG_VALUE_ACCESSOR,
4370
- useExisting: forwardRef(() => ReminderTypeRadioComponent),
4371
- multi: true,
4372
- },
4373
- ], usesInheritance: true, ngImport: i0, template: "@if (modeSignal() === formModeEnum.View) {\r\n <!-- <input nz-input [value]=\"displayValue\" class=\"modeView\" /> -->\r\n} @else {\r\n <!-- *ngIf=\"!!deadline\" -->\r\n <div class=\"reminder-wrapper\">\r\n <div class=\"reminder\">\r\n <div class=\"radio-group\">\r\n <nz-radio-group\r\n [ngModel]=\"selectedOptionCode\"\r\n (ngModelChange)=\"reminderRadioChange($event)\"\r\n >\r\n <label *ngFor=\"let opt of options\" nz-radio [nzValue]=\"opt.code\">\r\n {{ opt.name }}\r\n </label>\r\n </nz-radio-group>\r\n </div>\r\n\r\n <div class=\"detail-for-radio\" [ngSwitch]=\"selectedOption.type\">\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <nz-select\r\n [nzPlaceHolder]=\"'Ch\u1ECDn th\u1EDDi \u0111i\u1EC3m nh\u1EAFc b\u00E1o c\u00E1o'\"\r\n [nzAllowClear]=\"true\"\r\n [ngModel]=\"value!.reminderDate || ''\"\r\n (ngModelChange)=\"changeOptionSelect($event)\"\r\n >\r\n <nz-option\r\n *ngFor=\"let o of selectDetail?.options\"\r\n [nzValue]=\"o?.code || ''\"\r\n [nzLabel]=\"o?.name || ''\"\r\n ></nz-option>\r\n </nz-select>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n <div>\r\n <nz-date-picker\r\n [ngModel]=\"value!.reminderDate || null\"\r\n (ngModelChange)=\"changeDate($event)\"\r\n [nzAllowClear]=\"true\"\r\n [nzPlaceHolder]=\"'Ch\u1ECDn ng\u00E0y nh\u1EAFc b\u00E1o c\u00E1o c\u1EE5 th\u1EC3'\"\r\n [nzDisabledDate]=\"disabledAfterLimit\"\r\n [nzDisabled]=\"!deadline\"\r\n ></nz-date-picker>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <!-- <div\r\n class=\"\"\r\n [ngClass]=\"{\r\n 'background-disabled': !deadline,\r\n }\"\r\n ></div> -->\r\n </div>\r\n}\r\n", styles: [".reminder-wrapper{position:relative}.reminder-wrapper .background-disabled{position:absolute;inset:0;z-index:99;background-color:#f5f5f5;opacity:.5}.radio-group{margin-bottom:.5rem}.detail-for-radio nz-date-picker{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzRadioModule }, { kind: "component", type: i3$1.NzRadioComponent, selector: "[nz-radio],[nz-radio-button]", inputs: ["nzValue", "nzDisabled", "nzAutoFocus", "nz-radio-button"], exportAs: ["nzRadio"] }, { kind: "component", type: i3$1.NzRadioGroupComponent, selector: "nz-radio-group", inputs: ["nzDisabled", "nzButtonStyle", "nzSize", "nzName"], exportAs: ["nzRadioGroup"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i4$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i4$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i2.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzAllowClear", "nzAutoFocus", "nzDisabled", "nzBorderless", "nzInputReadOnly", "nzInline", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }] }); }
4374
- }
4375
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: ReminderTypeRadioComponent, decorators: [{
4376
- type: Component,
4377
- args: [{ selector: 'app-reminder-type-radio', standalone: true, imports: [...CORE_MODULES, ...FORM_MODULES, ...ZORRO_MODULES$a], providers: [
4378
- {
4379
- provide: NG_VALUE_ACCESSOR,
4380
- useExisting: forwardRef(() => ReminderTypeRadioComponent),
4381
- multi: true,
4382
- },
4383
- ], template: "@if (modeSignal() === formModeEnum.View) {\r\n <!-- <input nz-input [value]=\"displayValue\" class=\"modeView\" /> -->\r\n} @else {\r\n <!-- *ngIf=\"!!deadline\" -->\r\n <div class=\"reminder-wrapper\">\r\n <div class=\"reminder\">\r\n <div class=\"radio-group\">\r\n <nz-radio-group\r\n [ngModel]=\"selectedOptionCode\"\r\n (ngModelChange)=\"reminderRadioChange($event)\"\r\n >\r\n <label *ngFor=\"let opt of options\" nz-radio [nzValue]=\"opt.code\">\r\n {{ opt.name }}\r\n </label>\r\n </nz-radio-group>\r\n </div>\r\n\r\n <div class=\"detail-for-radio\" [ngSwitch]=\"selectedOption.type\">\r\n <ng-container *ngSwitchCase=\"'select'\">\r\n <nz-select\r\n [nzPlaceHolder]=\"'Ch\u1ECDn th\u1EDDi \u0111i\u1EC3m nh\u1EAFc b\u00E1o c\u00E1o'\"\r\n [nzAllowClear]=\"true\"\r\n [ngModel]=\"value!.reminderDate || ''\"\r\n (ngModelChange)=\"changeOptionSelect($event)\"\r\n >\r\n <nz-option\r\n *ngFor=\"let o of selectDetail?.options\"\r\n [nzValue]=\"o?.code || ''\"\r\n [nzLabel]=\"o?.name || ''\"\r\n ></nz-option>\r\n </nz-select>\r\n </ng-container>\r\n\r\n <ng-container *ngSwitchCase=\"'date'\">\r\n <div>\r\n <nz-date-picker\r\n [ngModel]=\"value!.reminderDate || null\"\r\n (ngModelChange)=\"changeDate($event)\"\r\n [nzAllowClear]=\"true\"\r\n [nzPlaceHolder]=\"'Ch\u1ECDn ng\u00E0y nh\u1EAFc b\u00E1o c\u00E1o c\u1EE5 th\u1EC3'\"\r\n [nzDisabledDate]=\"disabledAfterLimit\"\r\n [nzDisabled]=\"!deadline\"\r\n ></nz-date-picker>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <!-- <div\r\n class=\"\"\r\n [ngClass]=\"{\r\n 'background-disabled': !deadline,\r\n }\"\r\n ></div> -->\r\n </div>\r\n}\r\n", styles: [".reminder-wrapper{position:relative}.reminder-wrapper .background-disabled{position:absolute;inset:0;z-index:99;background-color:#f5f5f5;opacity:.5}.radio-group{margin-bottom:.5rem}.detail-for-radio nz-date-picker{width:100%}\n"] }]
4384
- }], ctorParameters: () => [] });
4385
-
4386
2542
  const ZORRO_MODULES$9 = [NzSelectModule, NzInputModule, NzSpinModule];
4387
2543
  const COMPONENTS$5 = [SuffixComponent];
4388
2544
  class MultiSelectComponent extends BaseValueAccessor {
@@ -4585,7 +2741,7 @@ class MultiSelectComponent extends BaseValueAccessor {
4585
2741
  useExisting: forwardRef(() => MultiSelectComponent),
4586
2742
  multi: true,
4587
2743
  },
4588
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [(ngModel)]=\"displayOptionsValue\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of displayOptionsValue; track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\"> </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (displayOptionsValue && displayOptionsValue.length) {\r\n <ul class=\"view-list\">\r\n @for (label of displayOptionsValue; track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"loading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important;font-size:12px}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i4$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i4$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2744
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n @if (fieldSignal().value!.length > 0) {\r\n <nz-select\r\n nzAllowClear\r\n [nzMode]=\"'multiple'\"\r\n [(ngModel)]=\"displayOptionsValue\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzDisabled]=\"true\"\r\n >\r\n @for (displayOption of displayOptionsValue; track $index) {\r\n <nz-option [nzValue]=\"displayOption\" [nzLabel]=\"displayOption\"> </nz-option>\r\n }\r\n </nz-select>\r\n } @else {\r\n <input nz-input [value]=\"displayNoValueText\" [disabled]=\"true\" />\r\n }\r\n }\r\n\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n @if (displayOptionsValue && displayOptionsValue.length) {\r\n <ul class=\"view-list\">\r\n @for (label of displayOptionsValue; track $index) {\r\n <li>{{ label }}</li>\r\n }\r\n </ul>\r\n } @else {\r\n <span class=\"modeView\">{{ displayNoValueText }}</span>\r\n }\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzMode]=\"'multiple'\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzMaxTagCount]=\"fieldSignal().maxTagCount!\"\r\n [nzMaxMultipleCount]=\"fieldSignal().maxMultipleCount!\"\r\n [nzSuffixIcon]=\"loading() ? loadingIcon : null\"\r\n [ngModel]=\"value\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".view-list{margin-bottom:0;padding-inline-start:24px!important}:host ::ng-deep .ant-input[disabled]{background-color:#fff;color:#575757!important;font-size:12px}:host ::ng-deep .ant-select-multiple.ant-select-disabled.ant-select:not(.ant-select-customize-input) .ant-select-selector{background:#fff}:host ::ng-deep .ant-select-disabled.ant-select-multiple .ant-select-selection-item{color:#575757;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
4589
2745
  }
4590
2746
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiSelectComponent, decorators: [{
4591
2747
  type: Component,
@@ -4823,7 +2979,7 @@ class SingleSelectComponent extends BaseValueAccessor {
4823
2979
  useExisting: forwardRef(() => SingleSelectComponent),
4824
2980
  multi: true,
4825
2981
  },
4826
- ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input [title]=\"displayValue\" nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span [title]=\"displayValue\" class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzSuffixIcon]=\"loadingSignal() ? loadingIcon : null\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i4$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i4$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
2982
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"d-flex\">\r\n @if (modeSignal() === formModeEnum.View) {\r\n @if (fieldSignal().viewMode === viewModeEnum.form) {\r\n <input [title]=\"displayValue\" nz-input [value]=\"displayValue\" class=\"modeView\" />\r\n }\r\n @if (fieldSignal().viewMode === viewModeEnum.text) {\r\n <span [title]=\"displayValue\" class=\"modeView break-work\">{{ displayValue }}</span>\r\n }\r\n } @else {\r\n <nz-select\r\n [id]=\"fieldSignal().key\"\r\n nzShowSearch\r\n nzAllowClear\r\n [nzDisabled]=\"isDisabled\"\r\n [nzPlaceHolder]=\"fieldSignal().placeholder!\"\r\n [nzSuffixIcon]=\"loadingSignal() ? loadingIcon : null\"\r\n [title]=\"fieldSignal().tooltip || ''\"\r\n [nzDropdownMatchSelectWidth]=\"fieldSignal().matchSelectWidth\"\r\n [ngModel]=\"value\"\r\n class=\"flex-1 select-box\"\r\n (ngModelChange)=\"handleChangeOptions($event)\"\r\n >\r\n @for (option of options; track `${option[this.fieldSignal().bindValue]}-${$index}`) {\r\n <nz-option [nzValue]=\"transformOptionValue(option)\" [nzLabel]=\"getOptionLabel(option)\">\r\n </nz-option>\r\n }\r\n </nz-select>\r\n\r\n <ng-template #loadingIcon>\r\n <nz-spin nzSimple [nzSize]=\"'small'\"></nz-spin>\r\n </ng-template>\r\n }\r\n\r\n <!-- Suffixes -->\r\n @if (fieldSignal().suffixes.length > 0) {\r\n @for (suffix of fieldSignal().suffixes; track $index) {\r\n @if (suffix.visibleOn?.includes(this.modeSignal())) {\r\n <app-suffix [suffixConfig]=\"suffix\" [formId]=\"formId\"> </app-suffix>\r\n }\r\n }\r\n }\r\n</div>\r\n", styles: [".w-100{width:100%}.h-100{height:100%}.flex{display:flex}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.items-center{align-items:center}.mb-0{margin-bottom:0!important}.break-work{word-break:break-word}.modeView{pointer-events:none;color:#575757;font-size:12px}.select-box{min-width:0}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3$1.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3$1.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzBorderless", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzSpinModule }, { kind: "component", type: i3$2.NzSpinComponent, selector: "nz-spin", inputs: ["nzIndicator", "nzSize", "nzTip", "nzDelay", "nzSimple", "nzSpinning"], exportAs: ["nzSpin"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i1$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "component", type: SuffixComponent, selector: "app-suffix", inputs: ["suffixConfig", "formId"] }] }); }
4827
2983
  }
4828
2984
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: SingleSelectComponent, decorators: [{
4829
2985
  type: Component,
@@ -5234,6 +3390,7 @@ class AvatarUploadComponent extends BaseValueAccessor {
5234
3390
  this.objStorageList = signal([]);
5235
3391
  this.fileService = inject(FileService);
5236
3392
  this.formConfigService = inject(FormConfigService);
3393
+ this.nzNotification = inject(NzNotificationService);
5237
3394
  this.beforeUpload = (file, fileList) => {
5238
3395
  // const originFile = file?.originFileObj as unknown as File; // có thể ở version mới nhất đã fix lỗi NzUploadFile nhưng trả về File của tham số file.
5239
3396
  // Xử lý gán vào CVA để tiến hành validation theo các props đã truyền vào.
@@ -5372,8 +3529,9 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5372
3529
  this.fieldSignal = computed(() => {
5373
3530
  const base = this.fieldInput();
5374
3531
  if (!(base instanceof MultiFileUploadField) ||
5375
- (base.type !== FieldTypeEnum.MultiFileUpload && base.controlType !== ControlTypeEnum.Upload)) {
5376
- throw new Error('Expected MultiFileUploadField');
3532
+ (base.type !== FieldTypeEnum.MultiFileUpload &&
3533
+ base.controlType !== ControlTypeEnum.Upload)) {
3534
+ throw new Error("Expected MultiFileUploadField");
5377
3535
  }
5378
3536
  return base;
5379
3537
  });
@@ -5382,7 +3540,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5382
3540
  // Mảng đại diện các images.
5383
3541
  this.allFiles = signal([]);
5384
3542
  // Text hướng dẫn các loại extension được upload.
5385
- this.fileExtensionGuide = signal('');
3543
+ this.fileExtensionGuide = signal("");
5386
3544
  // Đếm số file đã pass beforeUpload nhưng chưa upload xong (đang trong hàng chờ customRequest/uploadFile).
5387
3545
  // Dùng để tính toán count chính xác khi user chọn nhiều file cùng lúc.
5388
3546
  this.pendingCount = signal(0);
@@ -5394,6 +3552,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5394
3552
  this.toast = inject(ToastrService);
5395
3553
  this.translate = inject(TranslateService);
5396
3554
  this.nzMessage = inject(NzMessageService);
3555
+ this.nzNotification = inject(NzNotificationService);
5397
3556
  this.formConfigService = inject(FormConfigService);
5398
3557
  /**
5399
3558
  * Xử lý trước khi upload — trả về Observable<boolean> để hỗ trợ batch detection.
@@ -5405,7 +3564,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5405
3564
  * batch một lần, cho phép kiểm tra tổng số lượng chính xác trước khi upload.
5406
3565
  */
5407
3566
  this.beforeUpload = (file) => {
5408
- return new Observable(subscriber => {
3567
+ return new Observable((subscriber) => {
5409
3568
  // Đẩy file vào buffer với hàm resolve tương ứng.
5410
3569
  this._batchBuffer.push({
5411
3570
  file,
@@ -5427,8 +3586,8 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5427
3586
  const originFile = item.file;
5428
3587
  // Nếu validation không thỏa, dừng tại đây và xóa optimistic entry khỏi list.
5429
3588
  if (this.control?.invalid) {
5430
- this.pendingCount.update(c => Math.max(0, c - 1));
5431
- this.allFiles.update(curr => curr.filter(f => f.uid !== uid));
3589
+ this.pendingCount.update((c) => Math.max(0, c - 1));
3590
+ this.allFiles.update((curr) => curr.filter((f) => f.uid !== uid));
5432
3591
  return of(null).subscribe(); // fake subscription.
5433
3592
  }
5434
3593
  // Validation đã thỏa, tiến hành upload file.
@@ -5439,7 +3598,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5439
3598
  if (this.isAddOrEdit()) {
5440
3599
  const field = untracked(() => this.fieldSignal());
5441
3600
  const { acceptedMimeTypes } = field;
5442
- this.fileExtensionGuide.set(`Hỗ trợ các định dạng ${getExtensions(acceptedMimeTypes).join(', ')}`);
3601
+ this.fileExtensionGuide.set(`Hỗ trợ các định dạng ${getExtensions(acceptedMimeTypes).join(", ")}`);
5443
3602
  }
5444
3603
  });
5445
3604
  // Trigger reset từ bên ngoài form.
@@ -5471,11 +3630,11 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5471
3630
  this.objStorageList.set(objStorageList);
5472
3631
  // Khởi tạo danh sách MyUploadFile từ data json.
5473
3632
  // File từ server luôn ở trạng thái success.
5474
- const myUploadFiles = objStorageList.map(objStorage => ({
3633
+ const myUploadFiles = objStorageList.map((objStorage) => ({
5475
3634
  objStorage,
5476
3635
  isTemp: false,
5477
- previewUrl: this.formConfigService.getViewFileUrl(objStorage.name ?? '', false),
5478
- uploadStatus: 'success',
3636
+ previewUrl: this.formConfigService.getViewFileUrl(objStorage.name ?? "", false),
3637
+ uploadStatus: "success",
5479
3638
  }));
5480
3639
  this.allFiles.set(myUploadFiles);
5481
3640
  }
@@ -5498,7 +3657,12 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5498
3657
  const currentCount = this.allFiles().length + this.pendingCount();
5499
3658
  // ── CHECK 1: Tổng batch vượt maxFileCount → reject TOÀN BỘ batch ──
5500
3659
  if (currentCount + batch.length > maxFileCount) {
5501
- this.toast.warning(this.translate.instant(`Không thể tải lên: số lượng file chọn (${batch.length}) vượt quá giới hạn cho phép. Tối đa ${maxFileCount} tập tin.`));
3660
+ // this.toast.warning(
3661
+ // this.translate.instant(
3662
+ // `Không thể tải lên: số lượng file chọn (${batch.length}) vượt quá giới hạn cho phép. Tối đa ${maxFileCount} tập tin.`,
3663
+ // ),
3664
+ // );
3665
+ this.nzNotification.warning("Cảnh báo", `Không thể tải lên: số lượng file tải lên vượt quá giới hạn cho phép. Tối đa ${maxFileCount} tập tin.`, { nzDuration: 4500 });
5502
3666
  batch.forEach(({ resolve }) => resolve(false));
5503
3667
  return;
5504
3668
  }
@@ -5506,7 +3670,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5506
3670
  for (const { file, resolve } of batch) {
5507
3671
  const originFile = file;
5508
3672
  const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
5509
- const fileType = file.type ?? '';
3673
+ const fileType = file.type ?? "";
5510
3674
  const isSizeOk = fileSizeInMB <= maxSizeInMB;
5511
3675
  const isMimeOk = acceptedMimeTypes.includes(fileType);
5512
3676
  if (!isSizeOk || !isMimeOk) {
@@ -5516,7 +3680,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5516
3680
  reasons.push(`định dạng không hợp lệ`);
5517
3681
  if (!isSizeOk)
5518
3682
  reasons.push(`kích thước vượt ${maxSizeInMB}MB`);
5519
- this.nzMessage.error(`"${file.name}" không được tải lên: ${reasons.join(', ')}.`);
3683
+ this.nzMessage.error(`"${file.name}" không được tải lên: ${reasons.join(", ")}.`);
5520
3684
  // Propagate để CVA/validator hiển thị trạng thái lỗi trên control.
5521
3685
  this.propagateValue(originFile);
5522
3686
  resolve(false);
@@ -5525,25 +3689,25 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5525
3689
  // File hợp lệ: thêm optimistic entry vào allFiles với trạng thái loading ngay lập tức.
5526
3690
  // Dùng file.uid (do nz-upload tự tạo, duy nhất cho mỗi NzUploadFile) làm key để track và update sau này.
5527
3691
  const uid = file.uid;
5528
- this.allFiles.update(curr => [
3692
+ this.allFiles.update((curr) => [
5529
3693
  ...curr,
5530
3694
  {
5531
3695
  uid,
5532
3696
  objStorage: { id: uid, name: originFile.name }, // placeholder, sẽ được thay bằng data thật sau khi upload
5533
3697
  isTemp: true,
5534
- previewUrl: '',
5535
- uploadStatus: 'loading',
3698
+ previewUrl: "",
3699
+ uploadStatus: "loading",
5536
3700
  },
5537
3701
  ]);
5538
3702
  // Propagate để trigger validator, tăng pendingCount, cho phép upload.
5539
3703
  this.propagateValue(originFile);
5540
- this.pendingCount.update(c => c + 1);
3704
+ this.pendingCount.update((c) => c + 1);
5541
3705
  resolve(true);
5542
3706
  }
5543
3707
  }
5544
3708
  uploadFile(file, uid) {
5545
3709
  const { apiFileConfig, uploadTemp } = this.fieldSignal();
5546
- const newFileName = this.formConfigService.generateRandomFileName(file.name, 'file');
3710
+ const newFileName = this.formConfigService.generateRandomFileName(file.name, "file");
5547
3711
  const renamedFile = new File([file], newFileName, { type: file.type });
5548
3712
  const mimeCfg = apiFileConfig.perMimeTypeConfig?.[file.type]; // Lấy mimeTypes file upload.
5549
3713
  const uploadUrl = mimeCfg?.uploadUrl ?? apiFileConfig.uploadUrl; // Nếu có config upload riêng cho mimeTypes đang kiểm tra.
@@ -5552,30 +3716,30 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5552
3716
  : (() => {
5553
3717
  // Mặc định
5554
3718
  const fd = new FormData();
5555
- fd.append('File', renamedFile);
3719
+ fd.append("File", renamedFile);
5556
3720
  // fd.append('isTemp', 'true');
5557
3721
  return fd;
5558
3722
  })();
5559
3723
  if (uploadTemp)
5560
- formData.append('isTemp', 'true');
3724
+ formData.append("isTemp", "true");
5561
3725
  return this.fileService
5562
3726
  .uploadFile(renamedFile, formData, { uploadUrl })
5563
3727
  .pipe(this.takeUntilDestroy())
5564
3728
  .subscribe({
5565
- next: res => {
3729
+ next: (res) => {
5566
3730
  // Giảm pending dù thành công hay thất bại.
5567
- this.pendingCount.update(c => Math.max(0, c - 1));
3731
+ this.pendingCount.update((c) => Math.max(0, c - 1));
5568
3732
  const { code, data, message } = res;
5569
3733
  if (code === 0) {
5570
3734
  // Cập nhật objStorageList với data thật từ server.
5571
- this.objStorageList.update(curr => [...curr, data]);
3735
+ this.objStorageList.update((curr) => [...curr, data]);
5572
3736
  // Thay thế optimistic entry (uid) bằng data thật, chuyển sang trạng thái success.
5573
- this.allFiles.update(curr => curr.map(f => f.uid === uid
3737
+ this.allFiles.update((curr) => curr.map((f) => f.uid === uid
5574
3738
  ? {
5575
3739
  ...f,
5576
3740
  objStorage: data,
5577
- previewUrl: this.formConfigService.getViewFileUrl(data.name ?? '', true),
5578
- uploadStatus: 'success',
3741
+ previewUrl: this.formConfigService.getViewFileUrl(data.name ?? "", true),
3742
+ uploadStatus: "success",
5579
3743
  }
5580
3744
  : f));
5581
3745
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
@@ -5584,7 +3748,9 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5584
3748
  }
5585
3749
  else {
5586
3750
  // Cập nhật optimistic entry sang trạng thái error.
5587
- this.allFiles.update(curr => curr.map(f => f.uid === uid ? { ...f, uploadStatus: 'error' } : f));
3751
+ this.allFiles.update((curr) => curr.map((f) => f.uid === uid
3752
+ ? { ...f, uploadStatus: "error" }
3753
+ : f));
5588
3754
  console.error(message);
5589
3755
  // Đồng bộ control value → nếu tất cả file đều fail, objStorageList rỗng → required kích hoạt.
5590
3756
  this._syncControlValue();
@@ -5592,10 +3758,12 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5592
3758
  },
5593
3759
  error: () => {
5594
3760
  // Giảm pending khi upload thất bại.
5595
- this.pendingCount.update(c => Math.max(0, c - 1));
3761
+ this.pendingCount.update((c) => Math.max(0, c - 1));
5596
3762
  // Cập nhật optimistic entry sang trạng thái error.
5597
- this.allFiles.update(curr => curr.map(f => (f.uid === uid ? { ...f, uploadStatus: 'error' } : f)));
5598
- console.error('Lỗi trong quá trình tải tập đến máy chủ!!');
3763
+ this.allFiles.update((curr) => curr.map((f) => f.uid === uid
3764
+ ? { ...f, uploadStatus: "error" }
3765
+ : f));
3766
+ console.error("Lỗi trong quá trình tải tập đến máy chủ!!");
5599
3767
  // Đồng bộ control value → nếu tất cả file đều fail → required kích hoạt.
5600
3768
  this._syncControlValue();
5601
3769
  },
@@ -5605,20 +3773,20 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5605
3773
  const { objStorage, uploadStatus } = file;
5606
3774
  const { uploadTemp } = this.fieldSignal();
5607
3775
  // Loại bỏ và cập nhật danh sách ObjectStorage.
5608
- const newObjStorageList = this.objStorageList().filter(item => item.id !== objStorage?.id);
3776
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== objStorage?.id);
5609
3777
  this.objStorageList.set(newObjStorageList);
5610
3778
  // Loại bỏ và cập nhật danh sách MyUploadFile.
5611
- const newAllFiles = this.allFiles().filter(item => item.objStorage?.id !== objStorage?.id);
3779
+ const newAllFiles = this.allFiles().filter((item) => item.objStorage?.id !== objStorage?.id);
5612
3780
  this.allFiles.set(newAllFiles);
5613
3781
  // File ở trạng thái error: upload chưa thành công, không cần gọi API xoá trên server.
5614
- if (uploadStatus === 'error') {
3782
+ if (uploadStatus === "error") {
5615
3783
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
5616
3784
  // Đồng bộ control value → nếu xóa hết file error còn lại → required kích hoạt.
5617
3785
  this._syncControlValue();
5618
3786
  return;
5619
3787
  }
5620
3788
  if (uploadTemp) {
5621
- this.deleteObjStorage(objStorage.id || '');
3789
+ this.deleteObjStorage(objStorage.id || "");
5622
3790
  return;
5623
3791
  }
5624
3792
  // Cập nhật đầu ra cho form.
@@ -5628,13 +3796,15 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5628
3796
  }
5629
3797
  deleteObjStorage(id) {
5630
3798
  return this.fileService
5631
- .deleteObjStorage(id, { deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl })
3799
+ .deleteObjStorage(id, {
3800
+ deleteUrl: this.fieldSignal().apiFileConfig.deleteUrl,
3801
+ })
5632
3802
  .pipe(this.takeUntilDestroy())
5633
3803
  .subscribe({
5634
- next: res => {
3804
+ next: (res) => {
5635
3805
  const { code, message } = res;
5636
3806
  if (code === 0) {
5637
- const newObjStorageList = this.objStorageList().filter(item => item.id !== id);
3807
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== id);
5638
3808
  this.objStorageList.set(newObjStorageList);
5639
3809
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
5640
3810
  // Đồng bộ control value sau khi server xác nhận xóa.
@@ -5645,9 +3815,9 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5645
3815
  }
5646
3816
  },
5647
3817
  error: () => {
5648
- console.error('Lỗi trong quá trình xóa tập tin khỏi máy chủ!!');
3818
+ console.error("Lỗi trong quá trình xóa tập tin khỏi máy chủ!!");
5649
3819
  // Fake vì hiện api xóa đang có vấn đề.
5650
- const newObjStorageList = this.objStorageList().filter(item => item.id !== id);
3820
+ const newObjStorageList = this.objStorageList().filter((item) => item.id !== id);
5651
3821
  this.objStorageList.set(newObjStorageList);
5652
3822
  this.patchFormData(this.fieldKey(), JSON.stringify(this.objStorageList()));
5653
3823
  // Đồng bộ control value (fake delete thành công).
@@ -5662,7 +3832,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5662
3832
  * - Gọi fileClick để xem file nếu đã upload thành công.
5663
3833
  */
5664
3834
  onFileItemClick(file) {
5665
- if (file.uploadStatus === 'loading' || file.uploadStatus === 'error')
3835
+ if (file.uploadStatus === "loading" || file.uploadStatus === "error")
5666
3836
  return;
5667
3837
  this.fileService.onFileClick(file.objStorage);
5668
3838
  }
@@ -5699,7 +3869,7 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5699
3869
  _syncControlValue() {
5700
3870
  const list = this.objStorageList();
5701
3871
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5702
- this.propagateValue((list.length > 0 ? JSON.stringify(list) : ''));
3872
+ this.propagateValue((list.length > 0 ? JSON.stringify(list) : ""));
5703
3873
  }
5704
3874
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiFileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5705
3875
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: MultiFileUploadComponent, isStandalone: true, selector: "app-multi-file-upload", providers: [
@@ -5708,17 +3878,17 @@ class MultiFileUploadComponent extends BaseValueAccessor {
5708
3878
  useExisting: forwardRef(() => MultiFileUploadComponent),
5709
3879
  multi: true,
5710
3880
  },
5711
- ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\">( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span>\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case ('loading') {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case ('error') {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== 'loading') {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (!isAddOrEdit() && file.uploadStatus !== 'error' && file.uploadStatus !== 'loading') {\r\n <span nz-icon nzType=\"eye\" nzTheme=\"outline\" class=\"view-icon\" title=\"Xem file\"></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }] }); }
3881
+ ], usesInheritance: true, ngImport: i0, template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: i1$2.CommonModule }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NzUploadModule }, { kind: "component", type: i2$2.NzUploadComponent, selector: "nz-upload", inputs: ["nzType", "nzLimit", "nzSize", "nzFileType", "nzAccept", "nzAction", "nzDirectory", "nzOpenFileDialogOnClick", "nzBeforeUpload", "nzCustomRequest", "nzData", "nzFilter", "nzFileList", "nzDisabled", "nzHeaders", "nzListType", "nzMultiple", "nzName", "nzShowUploadList", "nzShowButton", "nzWithCredentials", "nzRemove", "nzPreview", "nzPreviewFile", "nzPreviewIsImage", "nzTransformFile", "nzDownload", "nzIconRender", "nzFileListRender"], outputs: ["nzChange", "nzFileListChange"], exportAs: ["nzUpload"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i3.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i4.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i5.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i6.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }] }); }
5712
3882
  }
5713
3883
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: MultiFileUploadComponent, decorators: [{
5714
3884
  type: Component,
5715
- args: [{ selector: 'app-multi-file-upload', standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$4], providers: [
3885
+ args: [{ selector: "app-multi-file-upload", standalone: true, imports: [...CORE_MODULES, ...ZORRO_MODULES$4], providers: [
5716
3886
  {
5717
3887
  provide: NG_VALUE_ACCESSOR,
5718
3888
  useExisting: forwardRef(() => MultiFileUploadComponent),
5719
3889
  multi: true,
5720
3890
  },
5721
- ], template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\">( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span>\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case ('loading') {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case ('error') {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span nz-icon nzType=\"paper-clip\" nzTheme=\"outline\" class=\"paper-clip-icon\"></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== 'loading') {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (!isAddOrEdit() && file.uploadStatus !== 'error' && file.uploadStatus !== 'loading') {\r\n <span nz-icon nzType=\"eye\" nzTheme=\"outline\" class=\"view-icon\" title=\"Xem file\"></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"] }]
3891
+ ], template: "@if (isAddOrEdit()) {\r\n <nz-upload\r\n [nzShowUploadList]=\"false\"\r\n [nzMultiple]=\"true\"\r\n [nzBeforeUpload]=\"beforeUpload\"\r\n [nzCustomRequest]=\"customRequest\"\r\n >\r\n <button nz-button class=\"btn-upload\" nzType=\"primary\" nzSize=\"small\">\r\n <nz-icon nzType=\"upload\" nzTheme=\"outline\" />\r\n <span class=\"font-size-12\">T\u1EA3i l\u00EAn</span>\r\n </button>\r\n\r\n <span class=\"font-size-12\"\r\n >( T\u1ED1i \u0111a {{ fieldSignal().maxFileCount }} t\u1EADp tin )</span\r\n >\r\n\r\n @if (fieldSignal().showAcceptedExtensionGuide) {\r\n <div class=\"file-extension-guide\">{{ fileExtensionGuide() }}</div>\r\n }\r\n </nz-upload>\r\n}\r\n\r\n<!-- Custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n@if (allFiles().length > 0) {\r\n <ng-container *ngTemplateOutlet=\"renderFileTpl\"></ng-container>\r\n}\r\n\r\n<!-- Template custom hi\u1EC3n th\u1ECB danh s\u00E1ch t\u1EADp tin -->\r\n<ng-template #renderFileTpl>\r\n <ul class=\"custom-upload-list\">\r\n @for (file of allFiles(); track file.objStorage?.id) {\r\n <li\r\n class=\"upload-item\"\r\n [class.is-loading]=\"file.uploadStatus === 'loading'\"\r\n [class.is-error]=\"file.uploadStatus === 'error'\"\r\n (click)=\"onFileItemClick(file)\"\r\n >\r\n <!-- Icon tr\u1EA1ng th\u00E1i b\u00EAn tr\u00E1i -->\r\n @switch (file.uploadStatus) {\r\n @case (\"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"loading\"\r\n nzTheme=\"outline\"\r\n class=\"status-icon loading-icon\"\r\n ></span>\r\n }\r\n @case (\"error\") {\r\n <span\r\n nz-icon\r\n nzType=\"close-circle\"\r\n nzTheme=\"fill\"\r\n class=\"status-icon error-icon\"\r\n ></span>\r\n }\r\n @default {\r\n <span\r\n nz-icon\r\n nzType=\"paper-clip\"\r\n nzTheme=\"outline\"\r\n class=\"paper-clip-icon\"\r\n ></span>\r\n }\r\n }\r\n\r\n <span class=\"file-name\" [title]=\"file.objStorage.name ?? ''\">{{\r\n file.objStorage.name\r\n }}</span>\r\n\r\n @if (isAddOrEdit() && file.uploadStatus !== \"loading\") {\r\n <span\r\n nz-icon\r\n nzType=\"delete\"\r\n nzTheme=\"outline\"\r\n title=\"X\u00F3a\"\r\n class=\"delete-icon\"\r\n (click)=\"handleDelete(file); $event.stopPropagation()\"\r\n ></span>\r\n }\r\n\r\n @if (\r\n !isAddOrEdit() &&\r\n file.uploadStatus !== \"error\" &&\r\n file.uploadStatus !== \"loading\"\r\n ) {\r\n <span\r\n nz-icon\r\n nzType=\"eye\"\r\n nzTheme=\"outline\"\r\n class=\"view-icon\"\r\n title=\"Xem file\"\r\n ></span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n</ng-template>\r\n", styles: [".upload{display:flex}nz-upload .btn-upload{margin-right:.75rem}nz-upload .file-extension-guide{font-size:11px;margin-top:8px;font-style:italic;color:#0f766e}.custom-upload-list{margin-top:.625rem;list-style-type:none;padding-inline-start:10px}.custom-upload-list .upload-item{margin-bottom:.25rem;display:flex;align-items:center;max-width:100%;gap:.5rem;cursor:pointer;border-radius:4px;transition:background-color .15s ease}.custom-upload-list .upload-item .paper-clip-icon{margin-right:.5rem;color:#595959;flex-shrink:0}.custom-upload-list .upload-item .status-icon{flex-shrink:0}.custom-upload-list .upload-item .loading-icon{color:#1677ff}.custom-upload-list .upload-item .error-icon{color:#ff4d4f}.custom-upload-list .upload-item .file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-upload-list .upload-item .file-name:hover{text-decoration:underline;color:#0a4c9f;cursor:pointer}.custom-upload-list .upload-item .delete-icon{color:red;cursor:pointer;flex-shrink:0}.custom-upload-list .upload-item .view-icon{color:#1677ff;flex-shrink:0;opacity:.6;transition:opacity .15s ease}.custom-upload-list .upload-item:hover .view-icon{opacity:1}.custom-upload-list .upload-item.is-loading{cursor:default;opacity:.75}.custom-upload-list .upload-item.is-loading .file-name:hover{text-decoration:none;color:inherit;cursor:default}.custom-upload-list .upload-item.is-error{cursor:default}.custom-upload-list .upload-item.is-error .file-name{color:#ff4d4f}.custom-upload-list .upload-item.is-error .file-name:hover{text-decoration:none;color:#ff4d4f;cursor:default}\n"] }]
5722
3892
  }], ctorParameters: () => [] });
5723
3893
 
5724
3894
  const ZORRO_MODULES$3 = [NzUploadModule, NzIconModule, NzButtonModule, NzImageModule];
@@ -5741,6 +3911,7 @@ class MultiImageUploadComponent extends BaseValueAccessor {
5741
3911
  this.fileService = inject(FileService);
5742
3912
  this.toast = inject(ToastrService);
5743
3913
  this.translate = inject(TranslateService);
3914
+ this.nzNotification = inject(NzNotificationService);
5744
3915
  this.formConfigService = inject(FormConfigService);
5745
3916
  // Xử lý trước khi upload.
5746
3917
  this.beforeUpload = (file) => {
@@ -5751,7 +3922,10 @@ class MultiImageUploadComponent extends BaseValueAccessor {
5751
3922
  const { maxFileCount, maxSizeInMB, acceptedMimeTypes } = this.fieldSignal();
5752
3923
  // Kiểm tra số lượng tối đa hình ảnh có thể tải lên.
5753
3924
  if (this.allFiles().length + 1 > maxFileCount) {
5754
- this.toast.warning(this.translate.instant(`Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`));
3925
+ // this.toast.warning(
3926
+ // this.translate.instant(`Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`),
3927
+ // );
3928
+ this.nzNotification.warning('Cảnh báo', `Chỉ được phép tải lên tối đa ${maxFileCount} hình ảnh`, { nzDuration: 4500 });
5755
3929
  return false;
5756
3930
  }
5757
3931
  const fileSizeInMB = formatFileSize(file.size, FileSizeTypeEnum.MB);
@@ -5937,6 +4111,7 @@ class SingleFileUploadComponent extends BaseValueAccessor {
5937
4111
  this.fileService = inject(FileService);
5938
4112
  this.toast = inject(ToastrService);
5939
4113
  this.translate = inject(TranslateService);
4114
+ this.nzNotification = inject(NzNotificationService);
5940
4115
  this.formConfigService = inject(FormConfigService);
5941
4116
  this.beforeUpload = (file, fileList) => {
5942
4117
  // const originFile = file?.originFileObj as unknown as File; // có thể ở version mới nhất đã fix lỗi NzUploadFile nhưng trả về File của tham số file.
@@ -6140,7 +4315,6 @@ const FIELD_COMPONENTS = [
6140
4315
  MultiFileUploadComponent,
6141
4316
  MultiImageUploadComponent,
6142
4317
  AvatarUploadComponent,
6143
- ReminderTypeRadioComponent,
6144
4318
  ];
6145
4319
 
6146
4320
  class ValidationService {
@@ -6192,14 +4366,14 @@ class DynamicFieldComponent {
6192
4366
  * Lấy thông báo lỗi đầu tiên của trường đang thao tác dựa trên control và field.
6193
4367
  */
6194
4368
  getErrorMessage() {
6195
- return this.validationService.getFirstError(this.control(), this.field(), this.fields()) ?? '';
4369
+ return (this.validationService.getFirstError(this.control(), this.field(), this.fields()) ?? "");
6196
4370
  }
6197
4371
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6198
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFieldComponent, isStandalone: true, selector: "app-dynamic-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: true, transformFunction: null }, formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n @case (fieldTypeEnum.ReminderTypeRadio) {\r\n <app-reminder-type-radio\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-reminder-type-radio>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "component", type: i3$3.NzFormItemComponent, selector: "nz-form-item", exportAs: ["nzFormItem"] }, { kind: "component", type: i3$3.NzFormLabelComponent, selector: "nz-form-label", inputs: ["nzFor", "nzRequired", "nzNoColon", "nzTooltipTitle", "nzTooltipIcon", "nzLabelAlign", "nzLabelWrap"], exportAs: ["nzFormLabel"] }, { kind: "component", type: i3$3.NzFormControlComponent, selector: "nz-form-control", inputs: ["nzSuccessTip", "nzWarningTip", "nzErrorTip", "nzValidatingTip", "nzExtra", "nzAutoTips", "nzDisableAutoTips", "nzHasFeedback", "nzValidateStatus"], exportAs: ["nzFormControl"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "component", type: InputTextComponent, selector: "app-input-text" }, { kind: "component", type: InputPasswordComponent, selector: "app-input-password" }, { kind: "component", type: SingleSelectComponent, selector: "app-single-select", inputs: ["isCustomInputText"] }, { kind: "component", type: MultiSelectComponent, selector: "app-multi-select" }, { kind: "component", type: TreeSelectComponent, selector: "app-tree-select", inputs: ["isCustomInputText"] }, { kind: "component", type: TextareaComponent, selector: "app-textarea" }, { kind: "component", type: DatePickerComponent, selector: "app-date-picker" }, { kind: "component", type: DateRangeComponent, selector: "app-date-range" }, { kind: "component", type: SingleFileUploadComponent, selector: "app-single-file-upload" }, { kind: "component", type: MultiFileUploadComponent, selector: "app-multi-file-upload" }, { kind: "component", type: MultiImageUploadComponent, selector: "app-multi-image-upload" }, { kind: "component", type: AvatarUploadComponent, selector: "app-avatar-upload", inputs: ["size"] }, { kind: "component", type: ReminderTypeRadioComponent, selector: "app-reminder-type-radio" }] }); }
4372
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFieldComponent, isStandalone: true, selector: "app-dynamic-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: true, transformFunction: null }, formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "component", type: i3$3.NzFormItemComponent, selector: "nz-form-item", exportAs: ["nzFormItem"] }, { kind: "component", type: i3$3.NzFormLabelComponent, selector: "nz-form-label", inputs: ["nzFor", "nzRequired", "nzNoColon", "nzTooltipTitle", "nzTooltipIcon", "nzLabelAlign", "nzLabelWrap"], exportAs: ["nzFormLabel"] }, { kind: "component", type: i3$3.NzFormControlComponent, selector: "nz-form-control", inputs: ["nzSuccessTip", "nzWarningTip", "nzErrorTip", "nzValidatingTip", "nzExtra", "nzAutoTips", "nzDisableAutoTips", "nzHasFeedback", "nzValidateStatus"], exportAs: ["nzFormControl"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "component", type: InputTextComponent, selector: "app-input-text" }, { kind: "component", type: InputPasswordComponent, selector: "app-input-password" }, { kind: "component", type: SingleSelectComponent, selector: "app-single-select", inputs: ["isCustomInputText"] }, { kind: "component", type: MultiSelectComponent, selector: "app-multi-select" }, { kind: "component", type: TreeSelectComponent, selector: "app-tree-select", inputs: ["isCustomInputText"] }, { kind: "component", type: TextareaComponent, selector: "app-textarea" }, { kind: "component", type: DatePickerComponent, selector: "app-date-picker" }, { kind: "component", type: DateRangeComponent, selector: "app-date-range" }, { kind: "component", type: SingleFileUploadComponent, selector: "app-single-file-upload" }, { kind: "component", type: MultiFileUploadComponent, selector: "app-multi-file-upload" }, { kind: "component", type: MultiImageUploadComponent, selector: "app-multi-image-upload" }, { kind: "component", type: AvatarUploadComponent, selector: "app-avatar-upload", inputs: ["size"] }] }); }
6199
4373
  }
6200
4374
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFieldComponent, decorators: [{
6201
4375
  type: Component,
6202
- args: [{ selector: 'app-dynamic-field', standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$1, ...COMPONENTS$1], template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n @case (fieldTypeEnum.ReminderTypeRadio) {\r\n <app-reminder-type-radio\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-reminder-type-radio>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"] }]
4376
+ args: [{ selector: "app-dynamic-field", standalone: true, imports: [...FORM_MODULES, ...ZORRO_MODULES$1, ...COMPONENTS$1], template: "<div [formGroup]=\"formGroup()\">\r\n <nz-form-item>\r\n <nz-form-label\r\n [nzXXl]=\"field().labelGrid.xxl ?? null\"\r\n [nzXl]=\"field().labelGrid.xl ?? null\"\r\n [nzLg]=\"field().labelGrid.lg ?? null\"\r\n [nzMd]=\"field().labelGrid.md ?? null\"\r\n [nzSm]=\"field().labelGrid.sm ?? null\"\r\n [nzXs]=\"field().labelGrid.xs ?? null\"\r\n [nzLabelAlign]=\"field().labelAlign\"\r\n >\r\n @if (field().required) {\r\n <span class=\"required\">*</span>\r\n }\r\n <span class=\"form-label\">\r\n {{ field().label }}\r\n @if (field().sublabel) {\r\n <span class=\"sub-label mb-sm\">{{ field().sublabel }}</span>\r\n }\r\n </span>\r\n </nz-form-label>\r\n\r\n <nz-form-control\r\n [nzXXl]=\"field().controlGrid.xxl ?? null\"\r\n [nzXl]=\"field().controlGrid.xl ?? null\"\r\n [nzLg]=\"field().controlGrid.lg ?? null\"\r\n [nzMd]=\"field().controlGrid.md ?? null\"\r\n [nzSm]=\"field().controlGrid.sm ?? null\"\r\n [nzXs]=\"field().controlGrid.xs ?? null\"\r\n [nzErrorTip]=\"getErrorMessage()\"\r\n >\r\n @switch (field().type) {\r\n <!-- Input text -->\r\n @case (fieldTypeEnum.InputText) {\r\n <app-input-text\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-text>\r\n }\r\n\r\n <!-- Input password -->\r\n @case (fieldTypeEnum.InputPassword) {\r\n @if (mode() === formModeEnum.Add) {\r\n <app-input-password\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-input-password>\r\n }\r\n }\r\n\r\n <!-- Textarea -->\r\n @case (fieldTypeEnum.Textarea) {\r\n <app-textarea\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-textarea>\r\n }\r\n\r\n <!-- Single select -->\r\n @case (fieldTypeEnum.SingleSelect) {\r\n <app-single-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-select>\r\n }\r\n\r\n <!-- Multi select -->\r\n @case (fieldTypeEnum.MultiSelect) {\r\n <app-multi-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-select>\r\n }\r\n\r\n @case (fieldTypeEnum.TreeSelect) {\r\n <app-tree-select\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-tree-select>\r\n }\r\n\r\n <!-- Date Picker -->\r\n @case (fieldTypeEnum.DatePicker) {\r\n <app-date-picker\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-picker>\r\n }\r\n\r\n <!-- Date Range -->\r\n @case (fieldTypeEnum.DateRange) {\r\n <app-date-range\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-date-range>\r\n }\r\n\r\n <!-- Single File Upload -->\r\n @case (fieldTypeEnum.SingleFileUpload) {\r\n <app-single-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-single-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiFileUpload) {\r\n <app-multi-file-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-file-upload>\r\n }\r\n\r\n <!-- Multi File Upload -->\r\n @case (fieldTypeEnum.MultiImageUpload) {\r\n <app-multi-image-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-multi-image-upload>\r\n }\r\n\r\n <!-- Avatar -->\r\n @case (fieldTypeEnum.AvatarUpload) {\r\n <app-avatar-upload\r\n [formControlName]=\"field().key\"\r\n [fieldInput]=\"field()\"\r\n [modeSignal]=\"mode()\"\r\n [control]=\"control()\"\r\n [formId]=\"formId()\"\r\n ></app-avatar-upload>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Checkbox -->\r\n @case (fieldTypeEnum.Checkbox) {\r\n <span>Checkbox</span>\r\n }\r\n\r\n <!-- Radio -->\r\n @case (fieldTypeEnum.Radio) {\r\n <span>Radio</span>\r\n }\r\n\r\n <!-- Switch -->\r\n @case (fieldTypeEnum.Switch) {\r\n <span>Switch</span>\r\n }\r\n\r\n <!-- Autocomplete -->\r\n @case (fieldTypeEnum.Autocomplete) {\r\n <span>Autocomplete</span>\r\n }\r\n }\r\n </nz-form-control>\r\n </nz-form-item>\r\n</div>\r\n", styles: [".required{color:red;font-weight:700;margin-right:4px}.form-label{white-space:normal!important;word-break:break-word;font-weight:500;font-size:12px}.sub-label{white-space:normal!important;word-break:break-word;font-size:11px;font-style:italic}:host ::ng-deep .ant-form-item{align-items:center}:host ::ng-deep .ant-form-item-label>label{display:inline-block!important}\n"] }]
6203
4377
  }] });
6204
4378
 
6205
4379
  const ZORRO_MODULES = [NzFormModule, NzGridModule, NzCollapseModule];
@@ -6385,7 +4559,7 @@ class DynamicFormComponent {
6385
4559
  return field.key || `field-${index}`;
6386
4560
  }
6387
4561
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormComponent, deps: [{ token: DynamicFormService }], target: i0.ɵɵFactoryTarget.Component }); }
6388
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFormComponent, isStandalone: true, selector: "app-dynamic-forms", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: false, isRequired: false, transformFunction: null }, valueChangesDebounceTime: { classPropertyName: "valueChangesDebounceTime", publicName: "valueChangesDebounceTime", isSignal: false, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, fieldGroups: { classPropertyName: "fieldGroups", publicName: "fieldGroups", isSignal: true, isRequired: false, transformFunction: null }, displayNonGroupedFields: { classPropertyName: "displayNonGroupedFields", publicName: "displayNonGroupedFields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formValueChanges: "formValueChanges", formSubmit: "formSubmit", formInvalid: "formInvalid" }, ngImport: i0, template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (groupField of groupedFields().grouped; track groupField.group.code) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "ngmodule", type: NzCollapseModule }, { kind: "component", type: i4$2.NzCollapsePanelComponent, selector: "nz-collapse-panel", inputs: ["nzActive", "nzDisabled", "nzShowArrow", "nzExtra", "nzHeader", "nzExpandedIcon"], outputs: ["nzActiveChange"], exportAs: ["nzCollapsePanel"] }, { kind: "component", type: i4$2.NzCollapseComponent, selector: "nz-collapse", inputs: ["nzAccordion", "nzBordered", "nzGhost", "nzExpandIconPosition"], exportAs: ["nzCollapse"] }, { kind: "component", type: DynamicFieldComponent, selector: "app-dynamic-field", inputs: ["field", "fields", "mode", "control", "formGroup", "formId"] }] }); }
4562
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.15", type: DynamicFormComponent, isStandalone: true, selector: "app-dynamic-forms", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: false, isRequired: false, transformFunction: null }, valueChangesDebounceTime: { classPropertyName: "valueChangesDebounceTime", publicName: "valueChangesDebounceTime", isSignal: false, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, fieldGroups: { classPropertyName: "fieldGroups", publicName: "fieldGroups", isSignal: true, isRequired: false, transformFunction: null }, displayNonGroupedFields: { classPropertyName: "displayNonGroupedFields", publicName: "displayNonGroupedFields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formValueChanges: "formValueChanges", formSubmit: "formSubmit", formInvalid: "formInvalid" }, ngImport: i0, template: "@if (formGroup) {\r\n <form [formGroup]=\"formGroup\">\r\n @if (oldFields().length > 0) {\r\n <!-- 1. N\u1EBFu kh\u00F4ng c\u00F3 groups th\u00EC render t\u1EEB fields -->\r\n @if (!fieldGroups() || fieldGroups().length === 0) {\r\n <div nz-row [nzGutter]=\"[12, 0]\">\r\n @for (field of oldFields(); track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n [nzFlex]=\"\r\n !field?.grid?.xs &&\r\n !field?.grid?.sm &&\r\n !field?.grid?.md &&\r\n !field?.grid?.lg &&\r\n !field?.grid?.xl &&\r\n !field?.grid?.xxl\r\n ? '1 0 0'\r\n : null\r\n \"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- 2. N\u1EBFu c\u00F3 groups th\u00EC render t\u1EEB groupedFields -->\r\n @if (fieldGroups() && fieldGroups().length > 0) {\r\n <!-- C\u00E1c field noGroup -->\r\n @if (groupedFields().noGroup.length > 0 && displayNonGroupedFields()) {\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupedFields().noGroup; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group c\u00E1c field -->\r\n @if (groupedFields().grouped.length > 0) {\r\n <nz-collapse [nzBordered]=\"false\">\r\n @for (groupField of groupedFields().grouped; track groupField.group.code) {\r\n <nz-collapse-panel\r\n [nzHeader]=\"groupField.group.template ?? groupField.group.label\"\r\n [nzActive]=\"groupField.group.isActive\"\r\n [nzShowArrow]=\"groupField.group.showArrow\"\r\n [nzDisabled]=\"groupField.group.disabled\"\r\n [style]=\"collapseCustomStyle\"\r\n class=\"mt-sm\"\r\n >\r\n <div nz-row [nzGutter]=\"24\">\r\n @for (field of groupField.fields; track field?.id) {\r\n @if (!isHiddenField(field)) {\r\n <div\r\n nz-col\r\n [nzXXl]=\"field?.grid?.xxl ?? null\"\r\n [nzXl]=\"field?.grid?.xl ?? null\"\r\n [nzLg]=\"field?.grid?.lg ?? null\"\r\n [nzMd]=\"field?.grid?.md ?? null\"\r\n [nzSm]=\"field?.grid?.sm ?? null\"\r\n [nzXs]=\"field?.grid?.xs ?? null\"\r\n class=\"field\"\r\n >\r\n <app-dynamic-field\r\n [field]=\"field\"\r\n [fields]=\"oldFields()\"\r\n [mode]=\"mode()\"\r\n [control]=\"formGroup.get(field.key)!\"\r\n [formGroup]=\"formGroup\"\r\n [formId]=\"formId\"\r\n />\r\n </div>\r\n }\r\n }\r\n </div>\r\n </nz-collapse-panel>\r\n }\r\n </nz-collapse>\r\n }\r\n }\r\n }\r\n </form>\r\n}\r\n", styles: [":host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,:host ::ng-deep .ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:#575757;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: i1.FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: i1.ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzFormModule }, { kind: "directive", type: i2$3.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i2$3.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "ngmodule", type: NzCollapseModule }, { kind: "component", type: i4$1.NzCollapsePanelComponent, selector: "nz-collapse-panel", inputs: ["nzActive", "nzDisabled", "nzShowArrow", "nzExtra", "nzHeader", "nzExpandedIcon"], outputs: ["nzActiveChange"], exportAs: ["nzCollapsePanel"] }, { kind: "component", type: i4$1.NzCollapseComponent, selector: "nz-collapse", inputs: ["nzAccordion", "nzBordered", "nzGhost", "nzExpandIconPosition"], exportAs: ["nzCollapse"] }, { kind: "component", type: DynamicFieldComponent, selector: "app-dynamic-field", inputs: ["field", "fields", "mode", "control", "formGroup", "formId"] }] }); }
6389
4563
  }
6390
4564
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicFormComponent, decorators: [{
6391
4565
  type: Component,
@@ -6412,30 +4586,43 @@ var TableCellTypeEnum;
6412
4586
  TableCellTypeEnum["EMPTY"] = "empty";
6413
4587
  })(TableCellTypeEnum || (TableCellTypeEnum = {}));
6414
4588
 
6415
- // Loại select.
6416
- class SelectOption {
4589
+ /**
4590
+ * Interface base chung cho Các dạng Radio.
4591
+ */
4592
+ class RadioBaseField extends BaseField {
4593
+ /**
4594
+ * Props validation chung cho các dạng Radio.
4595
+ */
4596
+ // ................. Chưa có .................
6417
4597
  constructor(init) {
6418
- this.options = [];
4598
+ super(init);
4599
+ /**
4600
+ * Gán giá trị cho các props cần giá trị mặc định ở Radio.
4601
+ */
4602
+ this.controlType = ControlTypeEnum.Radio;
4603
+ this.bindLabel = DEFAULT_BIND_LABEL;
4604
+ this.bindValue = DEFAULT_BIND_VALUE;
4605
+ this.dependsOn = [];
6419
4606
  Object.assign(this, init);
6420
4607
  }
6421
- }
6422
- // Loại date.
6423
- class DateOptions {
6424
- constructor(init) {
6425
- Object.assign(this, init);
4608
+ // Mảng ValidatorFn các validation chung được hỗ trợ sẵn bởi Reactive form của angular cho Radio.
4609
+ getCommonSelectValidators() {
4610
+ const validators = this.getCommonValidators();
4611
+ return validators;
6426
4612
  }
6427
- }
6428
- // Gom detail vào chung ReminderOption
6429
- class ReminderOption {
6430
- constructor(init) {
6431
- Object.assign(this, init);
6432
- // Nếu cần tự động khởi tạo detail theo type
6433
- if (this.type === 'select' && !(this.detail instanceof SelectOption)) {
6434
- this.detail = new SelectOption(this.detail);
6435
- }
6436
- if (this.type === 'date' && !(this.detail instanceof DateOptions)) {
6437
- this.detail = new DateOptions(this.detail);
6438
- }
4613
+ // Mảng ValidatorFn các custom validation có thể dùng cho Radio.
4614
+ getCustomSelectValidators() {
4615
+ const validators = this.getCustomValidators();
4616
+ return validators;
4617
+ }
4618
+ // Tạo Form Control cho Input Field.
4619
+ toFormControl() {
4620
+ const commonValidators = this.getCommonSelectValidators();
4621
+ const customValidators = this.getCustomSelectValidators();
4622
+ return new FormControl(this.value ?? '', [...commonValidators, ...customValidators]);
4623
+ }
4624
+ getErrorMessage(errorKey, errorValue, allFields) {
4625
+ return super.getErrorMessage(errorKey, errorValue, allFields);
6439
4626
  }
6440
4627
  }
6441
4628
 
@@ -6443,12 +4630,12 @@ class ReminderOption {
6443
4630
  * Public API Surface of dynamic-form
6444
4631
  */
6445
4632
  // ==========================================
6446
- // 0. BASE FIELD MODELS (CẦN ĐƯỢC EXPORT TRƯỚC ĐỂ TRÁNH LỖI CIRCULAR DEPENDENCY CLASS EXTENDS UNDEFINED)
4633
+ // 0. BASE FIELD MODELS (export truoc de tranh loi circular dependency)
6447
4634
  // ==========================================
6448
4635
 
6449
4636
  /**
6450
4637
  * Generated bundle index. Do not edit.
6451
4638
  */
6452
4639
 
6453
- export { BaseDateField, BaseField, BaseUploadField, CombineDependTypeEnum, CommonValidaterEnum, ControlTypeEnum, CustomValidaterEnum, DYNAMIC_FORM_CONFIG, DateFieldTypeEnum, DateOptions, DatePickerField, DatePickerOutputValueTypeEnum, DateRangeField, DateRangeOutputValueTypeEnum, DependTypeEnum, DynamicFormComponent, DynamicFormService, FieldGroup, FieldTypeEnum, FileSizeTypeEnum, FormConfigService, FormDataService, FormModeEnum, GroupedFields, InputBaseField, InputFieldTypeEnum, InputPasswordField, InputTextField, MultiSelectField, RadioBaseField, ReminderOption, ReminderTypeRadioField, ReminderValue, SelectBaseField, SelectFieldTypeEnum, SelectOption, SelectOutputValueTypeEnum, SingleSelectField, TableCellTypeEnum, TextareaField, TreeSelectField, UploadFieldTypeEnum, UploadOutputValueTypeEnum, ViewModeEnum };
4640
+ export { ALL_MIME_TYPES, ALL_MIME_TYPES_VALUE, APPLICATION_TYPES, AUDIO_TYPES, AvatarUploadField, BaseDateField, BaseField, BaseUploadField, CombineDependTypeEnum, CommonValidaterEnum, ControlTypeEnum, CustomValidaterEnum, DEFAULT_ACCEPTED_IMAGE_MIME_TYPES, DEFAULT_ACCEPTED_MIME_TYPES, DYNAMIC_FORM_CONFIG, DateFieldTypeEnum, DatePickerField, DatePickerOutputValueTypeEnum, DateRangeField, DateRangeOutputValueTypeEnum, DependTypeEnum, DynamicFormComponent, DynamicFormService, FieldGroup, FieldTypeEnum, FileService, FileSizeTypeEnum, FormConfigService, FormDataService, FormModeEnum, GroupedFields, IMAGE_TYPES, InputBaseField, InputFieldTypeEnum, InputPasswordField, InputTextField, MIME_TYPES_TO_EXTENSION, MultiFileUploadField, MultiImageUploadField, MultiSelectField, RadioBaseField, SelectBaseField, SelectFieldTypeEnum, SelectOutputValueTypeEnum, SingleFileUploadField, SingleSelectField, SuffixService, TEXT_TYPES, TableCellTypeEnum, TextareaField, TreeSelectField, UploadFieldTypeEnum, UploadOutputValueTypeEnum, VIDEO_TYPES, ViewModeEnum, videoTypesPath };
6454
4641
  //# sourceMappingURL=ldquocc-dynamic-form.mjs.map