@myrtex-org/form 1.1.95 → 1.1.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ import { AutoSaveStore, ToasterType, LabelModule, ModalServiceComponent, MODAL_D
17
17
  import { HttpHeaders, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http';
18
18
  import * as i2$1 from '@myrtex-org/templates';
19
19
  import { FormHeaderModule, MenuAdminModule } from '@myrtex-org/templates';
20
- import { cloneDeep } from 'lodash-es';
20
+ import { cloneDeep, isArray } from 'lodash-es';
21
21
  import * as i2$2 from '@angular/forms';
22
22
  import { FormsModule } from '@angular/forms';
23
23
  import { provideNgxMask } from 'ngx-mask';
@@ -1264,51 +1264,8 @@ class ComponentFactoryDirective {
1264
1264
  }
1265
1265
  }
1266
1266
  applyValues(values) {
1267
- if (!this.dynamicComponent) {
1268
- return;
1269
- }
1270
- // Сохраняем базовое поведение системы Myrtex
1271
- this.dynamicComponent.instance.values = values;
1272
- // ХАК ДЛЯ РЕЖИМА МОДАЛКИ (MANUAL):
1273
- // Принудительно инициализируем компоненты дат в обход пустого Стора
1274
- const currentMode = this.valueMode || this.dynamicComponent.instance.valueMode;
1275
- if (currentMode === 'manual' && values && this.data) {
1276
- const targetField = values.find((v) => v.sysName === this.data?.sysName);
1277
- const instance = this.dynamicComponent.instance;
1278
- if (targetField && targetField.value !== undefined && targetField.value !== null) {
1279
- const componentOptions = this.data?.options;
1280
- // Специфичный хак для InputDate с range: true
1281
- if (this.type === 'inputDate' && componentOptions?.range && Array.isArray(targetField.value)) {
1282
- // Проверяем, что лежит внутри массива. Если там строки, заворачиваем их в объекты, которые ждет компонент
1283
- const firstElement = targetField.value[0];
1284
- const secondElement = targetField.value[1];
1285
- const startValue = firstElement?.sysName ? firstElement.value : firstElement;
1286
- const endValue = secondElement?.sysName ? secondElement.value : secondElement;
1287
- // Формируем полноценные структуры ComponentValueModel
1288
- instance.modelStart = {
1289
- sysName: targetField.sysName,
1290
- type: targetField.type,
1291
- valueType: targetField.valueType,
1292
- value: startValue || null
1293
- };
1294
- instance.modelEnd = {
1295
- sysName: targetField.sysName,
1296
- type: targetField.type,
1297
- valueType: targetField.valueType,
1298
- value: endValue || null
1299
- };
1300
- if (typeof instance._customInit === 'function') {
1301
- instance._customInit();
1302
- }
1303
- }
1304
- else if (this.type === 'inputDate') {
1305
- // Для одиночной даты
1306
- instance.model = structuredClone(targetField);
1307
- if (typeof instance._customInit === 'function') {
1308
- instance._customInit();
1309
- }
1310
- }
1311
- }
1267
+ if (this.dynamicComponent) {
1268
+ this.dynamicComponent.instance.values = values;
1312
1269
  }
1313
1270
  }
1314
1271
  applyValueMode(valueMode) {
@@ -1832,9 +1789,8 @@ class InputDateComponent extends BaseFieldComponent {
1832
1789
  _initSubscriptionForValue() {
1833
1790
  this._subscriptions$.push(this._store.select(selectValueModel(this.settings))
1834
1791
  .subscribe((result) => {
1835
- if (!result || this._isUpdatingInternal) {
1792
+ if (!result || this._isUpdatingInternal)
1836
1793
  return;
1837
- }
1838
1794
  // Если компонент уже инициализирован, проверяем, нужно ли обновляться.
1839
1795
  // Мы НЕ обновляемся, если данные в Store идентичны тем, что мы уже держим в памяти.
1840
1796
  if (this._isInit) {
@@ -1892,6 +1848,7 @@ class InputDateComponent extends BaseFieldComponent {
1892
1848
  const start = new Date(startDate);
1893
1849
  const end = new Date(endDate);
1894
1850
  if (isNaN(start.getTime()) || isNaN(end.getTime())) {
1851
+ console.error('Invalid date format');
1895
1852
  return 0;
1896
1853
  }
1897
1854
  const differenceInMs = Math.abs(end.getTime() - start.getTime());
@@ -1901,9 +1858,11 @@ class InputDateComponent extends BaseFieldComponent {
1901
1858
  if (startDate) {
1902
1859
  const start = new Date(startDate);
1903
1860
  if (isNaN(start.getTime())) {
1861
+ console.error('Invalid start date format');
1904
1862
  return startDate;
1905
1863
  }
1906
1864
  if (!Number.isInteger(days) || days < 0) {
1865
+ console.error('Days must be a non-negative integer');
1907
1866
  return startDate;
1908
1867
  }
1909
1868
  let newDate = new Date(start.getTime() + days * this._millisecondsInDay);
@@ -2238,26 +2197,12 @@ class InputTableModalComponent extends ModalServiceComponent {
2238
2197
  this.result = { result: false, rowModel: this.rowModel };
2239
2198
  }
2240
2199
  componentValueChanged(valueModel) {
2241
- if (Array.isArray(valueModel) && valueModel.length > 0) {
2242
- // Проверяем, это элементы диапазона дат (одинаковый sysName) или это разные компоненты
2243
- const firstSysName = valueModel[0].sysName;
2244
- const isRangeDate = valueModel.every(m => m.sysName === firstSysName && m.type === ComponentType.InputDate);
2245
- if (isRangeDate) {
2246
- // Собираем чистый массив значений дат [string | null, string | null]
2247
- const rangeValues = valueModel.map(m => m.value);
2248
- // Создаем синтетическую модель, где value — это массив дат
2249
- const rangePayload = {
2250
- ...valueModel[0],
2251
- value: rangeValues
2252
- };
2253
- this._transformValues(rangePayload);
2254
- }
2255
- else {
2256
- // Если это массив от разных компонентов (стандартная логика dispenser)
2257
- valueModel.forEach(model => this._transformValues(model));
2258
- }
2200
+ if (isArray(valueModel)) {
2201
+ valueModel.forEach(model => {
2202
+ this._transformValues(model);
2203
+ });
2259
2204
  }
2260
- else if (valueModel && !Array.isArray(valueModel)) {
2205
+ else {
2261
2206
  this._transformValues(valueModel);
2262
2207
  }
2263
2208
  this._recalculateFormulaFields();
@@ -2284,7 +2229,7 @@ class InputTableModalComponent extends ModalServiceComponent {
2284
2229
  const cloneRowModel = structuredClone(this.rowModel);
2285
2230
  const findValue = cloneRowModel.data.find((c) => c.sysName === value.sysName);
2286
2231
  if (findValue) {
2287
- findValue.value = value.value; // Теперь сюда запишется массив ['2026-05-05', '2026-05-07']
2232
+ findValue.value = value.value;
2288
2233
  }
2289
2234
  this.rowModel = cloneRowModel;
2290
2235
  }
@@ -2452,37 +2397,10 @@ class InputTableComponent {
2452
2397
  this._subscriptions$.push(this._store.select(selectValueModel(this.settings))
2453
2398
  .subscribe((result) => {
2454
2399
  const cloneResult = structuredClone(result);
2455
- if (cloneResult && !Array.isArray(cloneResult) && cloneResult.sysName === this.settings.sysName) {
2456
- // --- ОБНОВЛЕННЫЙ БЛОК ПАРСИНГА И ЗАЩИТЫ ОТ NULL ---
2457
- if (cloneResult.data && Array.isArray(cloneResult.data)) {
2458
- cloneResult.data.forEach((row) => {
2459
- if (row.data && Array.isArray(row.data)) {
2460
- row.data.forEach((cell) => {
2461
- // 1. Если пришла строка со слэшем — парсим в массив
2462
- if (cell.type === 'inputDate' && typeof cell.value === 'string' && cell.value.includes('/')) {
2463
- cell.value = cell.value.split('/');
2464
- }
2465
- // 2. ЗАЩИТА: Если бэк прислал null, но у нас в текущей модели таблицы КУДА-ТО УЖЕ введена дата
2466
- if (cell.type === 'inputDate' && cell.value === null) {
2467
- // Ищем эту же ячейку в наших текущих строках на UI
2468
- const currentRow = this._rows?.find((r) => r.id === row.id);
2469
- const currentCell = currentRow?.data?.find((c) => c.sysName === cell.sysName);
2470
- if (currentCell && currentCell.value) {
2471
- // Не даем занулить! Возвращаем ей текущее валидное значение из UI
2472
- cell.value = structuredClone(currentCell.value);
2473
- }
2474
- }
2475
- });
2476
- }
2477
- });
2478
- }
2479
- // -------------------------------------------------
2480
- const isDataChanged = JSON.stringify(this.model?.data) !== JSON.stringify(cloneResult.data);
2481
- if (isDataChanged) {
2482
- this.model = structuredClone(cloneResult);
2483
- this._rows = this._extractRows(this.model.data);
2484
- this._initDataSource(this._rows);
2485
- }
2400
+ if (cloneResult && !Array.isArray(cloneResult) && this.model.data !== cloneResult.data) {
2401
+ this.model = structuredClone(cloneResult);
2402
+ this._rows = this._extractRows(this.model.data);
2403
+ this._initDataSource(this._rows);
2486
2404
  }
2487
2405
  }));
2488
2406
  }
@@ -2491,7 +2409,6 @@ class InputTableComponent {
2491
2409
  this._subscriptions$.forEach((subscription) => subscription.unsubscribe());
2492
2410
  }
2493
2411
  createRow() {
2494
- const initialRowModel = getRowModel(this.settings);
2495
2412
  this._modalService.open(InputTableModalComponent, {
2496
2413
  title: 'Создание строки',
2497
2414
  okText: 'Создать',
@@ -2499,12 +2416,9 @@ class InputTableComponent {
2499
2416
  rowModel: getRowModel(this.settings),
2500
2417
  isCheckRequired: this._isCheckRequired
2501
2418
  }).afterClosed().subscribe(resolve => {
2502
- if (resolve && resolve.result && resolve.rowModel) {
2503
- // Накатываем модель из модалки (там внутри ДАТЫ ЕЩЕ В ВИДЕ МАССИВА Array(2))
2419
+ if (resolve.result) {
2504
2420
  this._rows = [...structuredClone(this._rows), resolve.rowModel];
2505
- // Инициализируем Грид МАССИВОМ — DevExtreme сразу отрендерит "дд.мм.гггг - дд.мм.гггг" без мигания!
2506
2421
  this._initDataSource(this._rows);
2507
- // А вот теперь принудительно вызываем сборку модели, которая сама сожмет массивы в строки ДЛЯ СТОРА
2508
2422
  this._changeSubject$.next(this._buildTableValueModel());
2509
2423
  }
2510
2424
  });
@@ -2521,54 +2435,26 @@ class InputTableComponent {
2521
2435
  return !this._canEditObject;
2522
2436
  }
2523
2437
  editRow(event) {
2524
- const rowId = event.row?.data?.id || event.data?.id;
2525
- const localRow = this._rows.find((r) => r.id === rowId);
2526
- if (!localRow)
2527
- return;
2528
- const rowModel = structuredClone(localRow);
2529
- const gridData = event.row?.data || event.data;
2530
- if (rowModel.data && Array.isArray(rowModel.data)) {
2531
- rowModel.data = rowModel.data.map((cell) => {
2532
- if (gridData && gridData[cell.sysName] !== undefined) {
2533
- const currentValue = gridData[cell.sysName];
2534
- // Если это range-дата и в гриде лежит массив из 2-х элементов
2535
- if (cell.type === 'inputDate' && cell.options?.range && Array.isArray(currentValue)) {
2536
- // Формируем структуру [ modelStart, modelEnd ], которую ждет InputDateComponent
2537
- cell.value = [
2538
- { sysName: cell.sysName, type: cell.type, valueType: cell.valueType, value: currentValue[0] },
2539
- { sysName: cell.sysName, type: cell.type, valueType: cell.valueType, value: currentValue[1] }
2540
- ];
2541
- }
2542
- else {
2543
- cell.value = structuredClone(currentValue);
2438
+ const findRow = this._rows.find(item => item.id === event.row.data.id);
2439
+ if (findRow) {
2440
+ this._modalService.open(InputTableModalComponent, {
2441
+ title: 'Редактирование строки',
2442
+ okText: 'Сохранить',
2443
+ settings: this.settings,
2444
+ rowModel: findRow
2445
+ }).afterClosed().subscribe(resolve => {
2446
+ if (resolve.result) {
2447
+ const cloneRows = structuredClone(this._rows);
2448
+ const editableRow = cloneRows.find(row => row.id === resolve.rowModel.id);
2449
+ if (editableRow) {
2450
+ editableRow.data = resolve.rowModel.data;
2451
+ this._rows = cloneRows;
2452
+ this._initDataSource(this._rows);
2453
+ this._changeSubject$.next(this._buildTableValueModel());
2544
2454
  }
2545
2455
  }
2546
- return cell;
2547
2456
  });
2548
2457
  }
2549
- this._modalService.open(InputTableModalComponent, {
2550
- title: 'Редактирование строки',
2551
- okText: 'Сохранить',
2552
- settings: this.settings,
2553
- rowModel: rowModel,
2554
- isCheckRequired: this._isCheckRequired
2555
- }).afterClosed().subscribe(resolve => {
2556
- if (resolve && resolve.result && resolve.rowModel) {
2557
- // При сохранении вытаскиваем из структуры [modelStart, modelEnd] чистый массив дат обратно для грида
2558
- if (resolve.rowModel.data && Array.isArray(resolve.rowModel.data)) {
2559
- resolve.rowModel.data = resolve.rowModel.data.map((cell) => {
2560
- if (cell.type === 'inputDate' && cell.options?.range && Array.isArray(cell.value)) {
2561
- // Если там прилетел массив моделей (или массив строк от componentValueChanged)
2562
- cell.value = cell.value.map((item) => item?.sysName ? item.value : item);
2563
- }
2564
- return cell;
2565
- });
2566
- }
2567
- this._rows = this._rows.map((r) => r.id === rowId ? resolve.rowModel : r);
2568
- this._initDataSource(this._rows);
2569
- this._changeSubject$.next(this._buildTableValueModel());
2570
- }
2571
- });
2572
2458
  }
2573
2459
  getFormat(component) {
2574
2460
  switch (component.type) {
@@ -2616,9 +2502,7 @@ class InputTableComponent {
2616
2502
  this.dataSource = data.map((item) => {
2617
2503
  const newDataItem = {};
2618
2504
  for (let i in item.data) {
2619
- const cell = item.data[i];
2620
- // Напрямую берем значение ячейки (будь то строка или массив дат)
2621
- newDataItem[cell.sysName] = cell.value;
2505
+ newDataItem[item.data[i].sysName] = item.data[i].value;
2622
2506
  }
2623
2507
  newDataItem.id = item.id;
2624
2508
  return newDataItem;
@@ -2663,31 +2547,6 @@ class InputTableComponent {
2663
2547
  format: this.getFormat(component),
2664
2548
  minWidth: 160
2665
2549
  };
2666
- if (component.type === ComponentType.InputDate && component.options.range) {
2667
- column.calculateCellValue = (rowData) => {
2668
- const dateRange = rowData[component.sysName];
2669
- if (Array.isArray(dateRange) && dateRange.length === 2) {
2670
- const formatDate = (dateStr) => {
2671
- if (!dateStr)
2672
- return '';
2673
- const d = new Date(dateStr);
2674
- if (isNaN(d.getTime()))
2675
- return '';
2676
- const day = String(d.getDate()).padStart(2, '0');
2677
- const month = String(d.getMonth() + 1).padStart(2, '0');
2678
- const year = d.getFullYear();
2679
- return `${day}.${month}.${year}`;
2680
- };
2681
- const startFormatted = formatDate(dateRange[0]);
2682
- const endFormatted = formatDate(dateRange[1]);
2683
- if (startFormatted && endFormatted) {
2684
- return `${startFormatted} — ${endFormatted}`;
2685
- }
2686
- return startFormatted || endFormatted || '';
2687
- }
2688
- return dateRange || '';
2689
- };
2690
- }
2691
2550
  if (dataType === 'boolean') {
2692
2551
  column.calculateCellValue = (rowData) => {
2693
2552
  const value = rowData[component.sysName];
@@ -2756,22 +2615,8 @@ class InputTableComponent {
2756
2615
  });
2757
2616
  }
2758
2617
  _buildTableValueModel() {
2759
- // 1. Пробегаемся по ТЕКУЩИМ локальным строкам таблицы и принудительно жмем массивы дат в строки
2760
- if (this._rows && Array.isArray(this._rows)) {
2761
- this._rows.forEach(row => {
2762
- if (row.data && Array.isArray(row.data)) {
2763
- row.data.forEach((cell) => {
2764
- if (cell.type === 'inputDate' && Array.isArray(cell.value)) {
2765
- cell.value = cell.value.join('/');
2766
- }
2767
- });
2768
- }
2769
- });
2770
- }
2771
- // 2. Делаем стандартную группировку Myrtex
2772
2618
  const groupedData = this._buildGroupedItems(this._rows, this.settings.options.groups || [], 0);
2773
2619
  const totals = this._calculateTotals(this._rows);
2774
- // 3. Формируем финальную модель, где внутри data уже гарантированно лежат строки вместо массивов
2775
2620
  this.model = {
2776
2621
  sysName: this.settings.sysName,
2777
2622
  type: this.settings.type,