@myrtex-org/form 1.1.95 → 1.1.97

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,13 @@ 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
+ console.log(valueModel, 'valueModel in componentValueChanged');
2201
+ if (isArray(valueModel)) {
2202
+ valueModel.forEach(model => {
2203
+ this._transformValues(model);
2204
+ });
2259
2205
  }
2260
- else if (valueModel && !Array.isArray(valueModel)) {
2206
+ else {
2261
2207
  this._transformValues(valueModel);
2262
2208
  }
2263
2209
  this._recalculateFormulaFields();
@@ -2284,7 +2230,7 @@ class InputTableModalComponent extends ModalServiceComponent {
2284
2230
  const cloneRowModel = structuredClone(this.rowModel);
2285
2231
  const findValue = cloneRowModel.data.find((c) => c.sysName === value.sysName);
2286
2232
  if (findValue) {
2287
- findValue.value = value.value; // Теперь сюда запишется массив ['2026-05-05', '2026-05-07']
2233
+ findValue.value = value.value;
2288
2234
  }
2289
2235
  this.rowModel = cloneRowModel;
2290
2236
  }
@@ -2452,37 +2398,10 @@ class InputTableComponent {
2452
2398
  this._subscriptions$.push(this._store.select(selectValueModel(this.settings))
2453
2399
  .subscribe((result) => {
2454
2400
  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
- }
2401
+ if (cloneResult && !Array.isArray(cloneResult) && this.model.data !== cloneResult.data) {
2402
+ this.model = structuredClone(cloneResult);
2403
+ this._rows = this._extractRows(this.model.data);
2404
+ this._initDataSource(this._rows);
2486
2405
  }
2487
2406
  }));
2488
2407
  }
@@ -2491,7 +2410,6 @@ class InputTableComponent {
2491
2410
  this._subscriptions$.forEach((subscription) => subscription.unsubscribe());
2492
2411
  }
2493
2412
  createRow() {
2494
- const initialRowModel = getRowModel(this.settings);
2495
2413
  this._modalService.open(InputTableModalComponent, {
2496
2414
  title: 'Создание строки',
2497
2415
  okText: 'Создать',
@@ -2499,12 +2417,9 @@ class InputTableComponent {
2499
2417
  rowModel: getRowModel(this.settings),
2500
2418
  isCheckRequired: this._isCheckRequired
2501
2419
  }).afterClosed().subscribe(resolve => {
2502
- if (resolve && resolve.result && resolve.rowModel) {
2503
- // Накатываем модель из модалки (там внутри ДАТЫ ЕЩЕ В ВИДЕ МАССИВА Array(2))
2420
+ if (resolve.result) {
2504
2421
  this._rows = [...structuredClone(this._rows), resolve.rowModel];
2505
- // Инициализируем Грид МАССИВОМ — DevExtreme сразу отрендерит "дд.мм.гггг - дд.мм.гггг" без мигания!
2506
2422
  this._initDataSource(this._rows);
2507
- // А вот теперь принудительно вызываем сборку модели, которая сама сожмет массивы в строки ДЛЯ СТОРА
2508
2423
  this._changeSubject$.next(this._buildTableValueModel());
2509
2424
  }
2510
2425
  });
@@ -2521,54 +2436,26 @@ class InputTableComponent {
2521
2436
  return !this._canEditObject;
2522
2437
  }
2523
2438
  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);
2439
+ const findRow = this._rows.find(item => item.id === event.row.data.id);
2440
+ if (findRow) {
2441
+ this._modalService.open(InputTableModalComponent, {
2442
+ title: 'Редактирование строки',
2443
+ okText: 'Сохранить',
2444
+ settings: this.settings,
2445
+ rowModel: findRow
2446
+ }).afterClosed().subscribe(resolve => {
2447
+ if (resolve.result) {
2448
+ const cloneRows = structuredClone(this._rows);
2449
+ const editableRow = cloneRows.find(row => row.id === resolve.rowModel.id);
2450
+ if (editableRow) {
2451
+ editableRow.data = resolve.rowModel.data;
2452
+ this._rows = cloneRows;
2453
+ this._initDataSource(this._rows);
2454
+ this._changeSubject$.next(this._buildTableValueModel());
2544
2455
  }
2545
2456
  }
2546
- return cell;
2547
2457
  });
2548
2458
  }
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
2459
  }
2573
2460
  getFormat(component) {
2574
2461
  switch (component.type) {
@@ -2616,9 +2503,7 @@ class InputTableComponent {
2616
2503
  this.dataSource = data.map((item) => {
2617
2504
  const newDataItem = {};
2618
2505
  for (let i in item.data) {
2619
- const cell = item.data[i];
2620
- // Напрямую берем значение ячейки (будь то строка или массив дат)
2621
- newDataItem[cell.sysName] = cell.value;
2506
+ newDataItem[item.data[i].sysName] = item.data[i].value;
2622
2507
  }
2623
2508
  newDataItem.id = item.id;
2624
2509
  return newDataItem;
@@ -2663,31 +2548,6 @@ class InputTableComponent {
2663
2548
  format: this.getFormat(component),
2664
2549
  minWidth: 160
2665
2550
  };
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
2551
  if (dataType === 'boolean') {
2692
2552
  column.calculateCellValue = (rowData) => {
2693
2553
  const value = rowData[component.sysName];
@@ -2756,22 +2616,8 @@ class InputTableComponent {
2756
2616
  });
2757
2617
  }
2758
2618
  _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
2619
  const groupedData = this._buildGroupedItems(this._rows, this.settings.options.groups || [], 0);
2773
2620
  const totals = this._calculateTotals(this._rows);
2774
- // 3. Формируем финальную модель, где внутри data уже гарантированно лежат строки вместо массивов
2775
2621
  this.model = {
2776
2622
  sysName: this.settings.sysName,
2777
2623
  type: this.settings.type,