@maro-tech/card 0.1.0 → 0.1.2

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.
@@ -1,20 +1,80 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { input, inject, DestroyRef, signal, Component, computed, output, effect, HostListener, InjectionToken, forwardRef, Injectable, ViewChild, untracked, Input } from '@angular/core';
4
+ import { input, inject, DestroyRef, signal, Component, computed, output, effect, HostListener, Input, InjectionToken, forwardRef, Injectable, ViewChild, untracked } from '@angular/core';
5
5
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
6
6
  import { EventBus, UiButtonComponent, UiToastService, UiModalComponent, UiConfirmService } from '@maro-tech/core';
7
- import { ActivatedRoute, Router, RouterLink } from '@angular/router';
7
+ import { DomSanitizer } from '@angular/platform-browser';
8
8
  import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
9
- import { shareReplay, forkJoin, switchMap, timer, of, map as map$1 } from 'rxjs';
10
- import { map, exhaustMap, catchError, takeWhile, takeUntil, finalize } from 'rxjs/operators';
9
+ import { map, forkJoin, shareReplay, switchMap, timer, of } from 'rxjs';
10
+ import { UiTableComponent } from '@maro-tech/table';
11
+ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
12
+ import { map as map$1, exhaustMap, catchError, takeWhile, takeUntil, finalize } from 'rxjs/operators';
11
13
  import * as i1$2 from 'lucide-angular';
12
14
  import { LucideAngularModule, Eye, FileText, Plus, Sparkles, Trash } from 'lucide-angular';
13
15
  import * as i1$1 from '@angular/forms';
14
16
  import { FormsModule } from '@angular/forms';
15
- import { InputSelectComponent, FormModalComponent } from '@maro-tech/form';
16
- import { UiTableComponent } from '@maro-tech/table';
17
- import JsBarcode from 'jsbarcode';
17
+ import { InputSelectComponent } from '@maro-tech/form';
18
+
19
+ function normalizeCardConfig(config) {
20
+ return {
21
+ ...config,
22
+ tabs: normalizeSections(config.tabs),
23
+ actions: normalizeActions(config.actions),
24
+ };
25
+ }
26
+ function resolveCardConfig(config, data) {
27
+ return {
28
+ ...config,
29
+ data,
30
+ actions: resolveActions(config.actions, data),
31
+ tabs: resolveSections(config.tabs, data),
32
+ };
33
+ }
34
+ function normalizeSections(sections) {
35
+ return (sections || []).map((section) => ({
36
+ ...section,
37
+ component: section.component || (section.html ? 'html' : undefined),
38
+ actions: normalizeActions(section.actions),
39
+ tabs: section.tabs?.length ? normalizeSections(section.tabs) : section.tabs,
40
+ panels: section.panels?.length ? normalizeSections(section.panels) : section.panels,
41
+ }));
42
+ }
43
+ function normalizeActions(actions) {
44
+ return (actions || []).filter(Boolean);
45
+ }
46
+ function resolveSections(sections, data) {
47
+ return (sections || []).map((section) => ({
48
+ ...section,
49
+ props: resolveValue(section.props, data),
50
+ actions: resolveActions(section.actions, data),
51
+ tabs: section.tabs?.length ? resolveSections(section.tabs, data) : section.tabs,
52
+ panels: section.panels?.length ? resolveSections(section.panels, data) : section.panels,
53
+ }));
54
+ }
55
+ function resolveActions(actions, data) {
56
+ return resolveValue(actions, data);
57
+ }
58
+ function resolveValue(value, data) {
59
+ if (typeof value === 'string') {
60
+ const match = value.match(/^\{\{([a-zA-Z0-9_.-]+)\}\}$/);
61
+ return match ? getByPath(data, match[1]) : value;
62
+ }
63
+ if (Array.isArray(value))
64
+ return value.map((item) => resolveValue(item, data));
65
+ if (value && typeof value === 'object') {
66
+ return Object.fromEntries(Object.entries(value)
67
+ .map(([key, item]) => [key, resolveValue(item, data)]));
68
+ }
69
+ return value;
70
+ }
71
+ function getByPath(source, path) {
72
+ return path.split('.').reduce((current, segment) => {
73
+ if (current == null || typeof current !== 'object')
74
+ return undefined;
75
+ return current[segment];
76
+ }, source);
77
+ }
18
78
 
19
79
  const CARD_ACTION_REQUESTED = 'card.action.requested';
20
80
  const CARD_ACTION_STARTED = 'card.action.started';
@@ -26,8 +86,6 @@ class CardSectionHeaderComponent {
26
86
  variant = input('panels', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
27
87
  title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
28
88
  actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
29
- entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
30
- recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
31
89
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
32
90
  eventBus = inject(EventBus);
33
91
  destroyRef = inject(DestroyRef);
@@ -50,20 +108,16 @@ class CardSectionHeaderComponent {
50
108
  return !!pendingId && !!actionId && pendingId === actionId;
51
109
  }
52
110
  onActionClick(action) {
53
- const context = {
54
- entity: this.entity(),
55
- recordId: this.recordId() || undefined,
56
- data: this.data(),
57
- };
111
+ const context = action.context || { entity: '', data: this.data() };
58
112
  this.pendingActionIdSignal.set(String(action.id || '').trim());
59
113
  this.eventBus.emit(CARD_ACTION_REQUESTED, { action, context });
60
114
  }
61
115
  matchesContext(context) {
62
- return context.entity === this.entity()
63
- && (!context.recordId || context.recordId === this.recordId());
116
+ return this.actions().some((action) => action.context?.entity === context.entity
117
+ && (!context.recordId || action.context?.recordId === context.recordId));
64
118
  }
65
119
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardSectionHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
66
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardSectionHeaderComponent, isStandalone: true, selector: "app-card-section-header", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
120
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardSectionHeaderComponent, isStandalone: true, selector: "app-card-section-header", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
67
121
  @if (variant() === 'tabs') {
68
122
  <header class="mb-4 flex justify-end">
69
123
  <div class="flex flex-wrap gap-2 md:justify-end">
@@ -157,15 +211,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
157
211
  }
158
212
  `,
159
213
  }]
160
- }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
214
+ }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
161
215
 
162
216
  class CardHtmlTabComponent {
163
- entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
164
- recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
165
- tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
166
- data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
217
+ html = input('', ...(ngDevMode ? [{ debugName: "html" }] : /* istanbul ignore next */ []));
167
218
  eventBus = inject(EventBus);
168
- html = computed(() => this.tab()?.html || '', ...(ngDevMode ? [{ debugName: "html" }] : /* istanbul ignore next */ []));
219
+ sanitizer = inject(DomSanitizer);
220
+ safeHtml = computed(() => this.sanitizer.bypassSecurityTrustHtml(this.html()), ...(ngDevMode ? [{ debugName: "safeHtml" }] : /* istanbul ignore next */ []));
169
221
  onClick(event) {
170
222
  this.dispatchFromEvent(event);
171
223
  }
@@ -174,7 +226,7 @@ class CardHtmlTabComponent {
174
226
  }
175
227
  dispatchFromEvent(event) {
176
228
  const target = event.target;
177
- const actionElement = target?.closest?.('[class*="card-action-"]');
229
+ const actionElement = target?.closest?.('[data-card-action]');
178
230
  if (!actionElement)
179
231
  return;
180
232
  const action = this.resolveAction(actionElement);
@@ -184,33 +236,41 @@ class CardHtmlTabComponent {
184
236
  event.stopPropagation();
185
237
  this.eventBus.emit(CARD_ACTION_REQUESTED, {
186
238
  action,
187
- context: {
188
- entity: this.entity(),
189
- recordId: this.recordId() || undefined,
190
- data: this.data(),
191
- },
239
+ context: action.context || { entity: '', data: null },
192
240
  });
193
241
  }
194
242
  resolveAction(actionElement) {
195
- const tab = this.tab();
196
- const actions = Array.isArray(tab?.actions) ? tab.actions : [];
197
- if (!actions.length)
243
+ const raw = actionElement?.getAttribute('data-card-action');
244
+ if (!raw)
198
245
  return null;
199
- const actionId = Array.from(actionElement?.classList || [])
200
- .find((className) => className.startsWith('card-action-'))
201
- ?.slice('card-action-'.length)
202
- .trim();
203
- if (!actionId)
246
+ try {
247
+ const parsed = JSON.parse(raw);
248
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
249
+ return null;
250
+ const action = parsed;
251
+ const id = String(action['id'] || '').trim();
252
+ const type = String(action['type'] || '').trim();
253
+ if (!id || !type)
254
+ return null;
255
+ return {
256
+ ...action,
257
+ id,
258
+ type,
259
+ label: String(action['label'] || actionElement?.textContent || id).trim(),
260
+ context: action['context'] || { entity: '', data: null },
261
+ };
262
+ }
263
+ catch {
204
264
  return null;
205
- return actions.find((item) => String(item.id || '').trim() === actionId) || null;
265
+ }
206
266
  }
207
267
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardHtmlTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
208
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CardHtmlTabComponent, isStandalone: true, selector: "app-card-html-tab", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section\n [attr.data-card-tab-id]=\"tab()?.id || ''\"\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"html()\"></div>\n</section>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
268
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CardHtmlTabComponent, isStandalone: true, selector: "app-card-html-tab", inputs: { html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"safeHtml()\"></div>\n</section>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
209
269
  }
210
270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardHtmlTabComponent, decorators: [{
211
271
  type: Component,
212
- args: [{ selector: 'app-card-html-tab', standalone: true, imports: [CommonModule], template: "<section\n [attr.data-card-tab-id]=\"tab()?.id || ''\"\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"html()\"></div>\n</section>\n" }]
213
- }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
272
+ args: [{ selector: 'app-card-html-tab', standalone: true, imports: [CommonModule], template: "<section\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"safeHtml()\"></div>\n</section>\n" }]
273
+ }], propDecorators: { html: [{ type: i0.Input, args: [{ isSignal: true, alias: "html", required: false }] }] } });
214
274
 
215
275
  class ImagesViewerComponent {
216
276
  urls = input([], ...(ngDevMode ? [{ debugName: "urls" }] : /* istanbul ignore next */ []));
@@ -281,7 +341,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
281
341
  }] } });
282
342
 
283
343
  class DetailsGridComponent {
284
- tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
344
+ config = input(null, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
345
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
285
346
  hasExplicitColumns = computed(() => this.resolveFields().some((field) => this.isValidColumnSpan(field.col)), ...(ngDevMode ? [{ debugName: "hasExplicitColumns" }] : /* istanbul ignore next */ []));
286
347
  displayFields = computed(() => this.resolveFields().map((field) => ({
287
348
  ...field,
@@ -296,8 +357,29 @@ class DetailsGridComponent {
296
357
  this.selectedImageSrc.set(null);
297
358
  }
298
359
  resolveFields() {
299
- const value = this.tab()?.props?.['fields'];
300
- return Array.isArray(value) ? value : [];
360
+ const value = this.config()?.props?.['fields'];
361
+ if (!Array.isArray(value))
362
+ return [];
363
+ const data = this.data() || {};
364
+ return value.map((field) => ({
365
+ ...field,
366
+ type: field.type || 'text',
367
+ value: field.value ?? (field.id
368
+ ? this.toFieldValue(data[`${field.id}_data`] ?? data[field.id])
369
+ : undefined),
370
+ }));
371
+ }
372
+ toFieldValue(value) {
373
+ if (value == null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
374
+ return value;
375
+ }
376
+ if (Array.isArray(value)) {
377
+ return value.map((item) => this.toFieldValue(item) ?? '').join(', ');
378
+ }
379
+ if (typeof value === 'object' && 'label' in value) {
380
+ return String(value['label'] ?? '');
381
+ }
382
+ return String(value);
301
383
  }
302
384
  getFieldColumnClass(field) {
303
385
  if (!this.isValidColumnSpan(field.col))
@@ -349,11 +431,7 @@ class DetailsGridComponent {
349
431
  const raw = String(value).trim();
350
432
  if (!raw)
351
433
  return null;
352
- if (/^https?:\/\//i.test(raw) || raw.startsWith('/')) {
353
- return raw;
354
- }
355
- const base = String(field.imageBaseUrl || '/api/files').trim().replace(/\/+$/, '');
356
- return `${base}/${encodeURIComponent(raw)}`;
434
+ return `/api/files/${encodeURIComponent(raw)}`;
357
435
  }
358
436
  formatEnum(raw, enumMap) {
359
437
  if (enumMap?.[raw]) {
@@ -375,41 +453,29 @@ class DetailsGridComponent {
375
453
  : { day: '2-digit', month: '2-digit', year: 'numeric' }).format(date);
376
454
  }
377
455
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DetailsGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
378
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DetailsGridComponent, isStandalone: true, selector: "app-details-grid", inputs: { tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
456
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DetailsGridComponent, isStandalone: true, selector: "app-details-grid", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
379
457
  }
380
458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DetailsGridComponent, decorators: [{
381
459
  type: Component,
382
460
  args: [{ selector: 'app-details-grid', standalone: true, imports: [CommonModule, ImagesViewerComponent], template: "<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n" }]
383
- }], propDecorators: { tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }] } });
461
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
384
462
 
385
463
  class ImagesComponent {
386
- tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
464
+ config = input(null, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
387
465
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
388
466
  isViewerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isViewerOpen" }] : /* istanbul ignore next */ []));
389
467
  viewerStartIndex = signal(0, ...(ngDevMode ? [{ debugName: "viewerStartIndex" }] : /* istanbul ignore next */ []));
390
468
  resolvedImageIds = computed(() => {
391
- const tab = this.tab();
392
- const props = tab?.props || {};
393
- const explicit = this.normalizeIds(props['imageIds']);
394
- if (explicit.length)
395
- return explicit;
469
+ const props = this.config()?.props || {};
396
470
  const key = String(props['images'] || 'images').trim();
397
- const value = key ? this.data()?.[key] : null;
398
- return this.normalizeIds(value);
471
+ return this.normalizeIds(key ? this.data()?.[key] : null);
399
472
  }, ...(ngDevMode ? [{ debugName: "resolvedImageIds" }] : /* istanbul ignore next */ []));
400
473
  resolvedImageUrls = computed(() => {
401
- const tab = this.tab();
402
- const props = tab?.props || {};
403
- const explicit = Array.isArray(props['imageUrls'])
404
- ? props['imageUrls'].filter((item) => typeof item === 'string')
405
- : [];
406
- if (explicit.length)
407
- return explicit;
408
- return this.resolvedImageIds().map((id) => this.resolveImageUrl(id)).filter((item) => !!item);
474
+ return this.resolvedImageIds().map((id) => this.resolveImageUrl(id));
409
475
  }, ...(ngDevMode ? [{ debugName: "resolvedImageUrls" }] : /* istanbul ignore next */ []));
410
- resolvedImagePreviewUrls = computed(() => this.resolvedImageUrls().map((url) => this.resolvePreviewUrl(url)), ...(ngDevMode ? [{ debugName: "resolvedImagePreviewUrls" }] : /* istanbul ignore next */ []));
476
+ resolvedImagePreviewUrls = computed(() => this.resolvedImageIds().map((id) => this.resolvePreviewUrl(id)), ...(ngDevMode ? [{ debugName: "resolvedImagePreviewUrls" }] : /* istanbul ignore next */ []));
411
477
  resolvedImageMaxHeight = computed(() => {
412
- const value = Number(this.tab()?.props?.['maxHeight']);
478
+ const value = Number(this.config()?.props?.['maxHeight']);
413
479
  return Number.isFinite(value) && value > 0 ? value : 400;
414
480
  }, ...(ngDevMode ? [{ debugName: "resolvedImageMaxHeight" }] : /* istanbul ignore next */ []));
415
481
  openViewer(index) {
@@ -420,20 +486,11 @@ class ImagesComponent {
420
486
  closeViewer() {
421
487
  this.isViewerOpen.set(false);
422
488
  }
423
- resolveImageUrl(imageIdOrUrl) {
424
- const value = String(imageIdOrUrl || '').trim();
425
- if (!value) {
426
- return '';
427
- }
428
- if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('data:') || value.startsWith('/')) {
429
- return value;
430
- }
431
- const base = String(this.tab()?.props?.['imageBaseUrl'] || '/api/files').trim().replace(/\/+$/, '');
432
- return `${base}/${encodeURIComponent(value)}`;
489
+ resolveImageUrl(imageId) {
490
+ return `/api/files/${encodeURIComponent(imageId)}`;
433
491
  }
434
- resolvePreviewUrl(url) {
435
- const match = url.match(/^(.*\/api\/files\/[^/?#]+)(?:\?.*)?$/i);
436
- return match ? `${match[1]}/preview?scale=25` : url;
492
+ resolvePreviewUrl(imageId) {
493
+ return `/api/files/${encodeURIComponent(imageId)}/preview?scale=25`;
437
494
  }
438
495
  normalizeIds(value) {
439
496
  if (!Array.isArray(value)) {
@@ -444,19 +501,281 @@ class ImagesComponent {
444
501
  .filter(Boolean)));
445
502
  }
446
503
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
447
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesComponent, isStandalone: true, selector: "app-images", inputs: { tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">\u0424\u043E\u0442\u043E \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u0456</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 \u0444\u043E\u0442\u043E ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'\u0424\u043E\u0442\u043E ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
504
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesComponent, isStandalone: true, selector: "app-images", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">\u0424\u043E\u0442\u043E \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u0456</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 \u0444\u043E\u0442\u043E ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'\u0424\u043E\u0442\u043E ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
448
505
  }
449
506
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesComponent, decorators: [{
450
507
  type: Component,
451
508
  args: [{ selector: 'app-images', standalone: true, imports: [CommonModule, ImagesViewerComponent], template: "@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">\u0424\u043E\u0442\u043E \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u0456</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 \u0444\u043E\u0442\u043E ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'\u0424\u043E\u0442\u043E ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n" }]
452
- }], propDecorators: { tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
509
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
510
+
511
+ class StandardTableComponent {
512
+ config;
513
+ data = null;
514
+ http = inject(HttpClient);
515
+ eventBus = inject(EventBus);
516
+ columnsSignal = signal([], ...(ngDevMode ? [{ debugName: "columnsSignal" }] : /* istanbul ignore next */ []));
517
+ rowsSignal = signal([], ...(ngDevMode ? [{ debugName: "rowsSignal" }] : /* istanbul ignore next */ []));
518
+ paginationSignal = signal({ page: 0, pageSize: 10, total: 0 }, ...(ngDevMode ? [{ debugName: "paginationSignal" }] : /* istanbul ignore next */ []));
519
+ headerActionsSignal = signal([], ...(ngDevMode ? [{ debugName: "headerActionsSignal" }] : /* istanbul ignore next */ []));
520
+ loadingSignal = signal(false, ...(ngDevMode ? [{ debugName: "loadingSignal" }] : /* istanbul ignore next */ []));
521
+ errorSignal = signal('', ...(ngDevMode ? [{ debugName: "errorSignal" }] : /* istanbul ignore next */ []));
522
+ editableOptionsSignal = signal({}, ...(ngDevMode ? [{ debugName: "editableOptionsSignal" }] : /* istanbul ignore next */ []));
523
+ columns = this.columnsSignal.asReadonly();
524
+ rows = this.rowsSignal.asReadonly();
525
+ headerActions = this.headerActionsSignal.asReadonly();
526
+ isLoading = this.loadingSignal.asReadonly();
527
+ error = this.errorSignal.asReadonly();
528
+ tableData = computed(() => ({
529
+ items: this.rowsSignal(),
530
+ pagination: this.paginationSignal(),
531
+ }), ...(ngDevMode ? [{ debugName: "tableData" }] : /* istanbul ignore next */ []));
532
+ page = 0;
533
+ pageSize = 10;
534
+ sortField = '';
535
+ sortDirection = '';
536
+ sourceColumns = [];
537
+ constructor() {
538
+ this.eventBus.on(ENTITY_UPDATED).subscribe((event) => {
539
+ if (!event.entity || event.entity === this.tableName())
540
+ this.load();
541
+ });
542
+ }
543
+ ngOnChanges(changes) {
544
+ if (!changes['config'] && !changes['data'])
545
+ return;
546
+ this.page = 0;
547
+ this.pageSize = this.numberProp('pageSize', 10);
548
+ this.sortField = '';
549
+ this.sortDirection = '';
550
+ this.load();
551
+ }
552
+ get disablePaging() {
553
+ return this.config?.props?.['disablePaging'] === true || this.config?.props?.['disablePaging'] === 'true';
554
+ }
555
+ onPageChange(event) {
556
+ this.page = Math.max(0, Number(event['pageIndex'] ?? this.page) || 0);
557
+ this.pageSize = Math.max(1, Number(event['pageSize'] ?? this.pageSize) || this.pageSize);
558
+ this.sortField = String(event['active'] || '').trim();
559
+ const direction = String(event['direction'] || '').toLowerCase();
560
+ this.sortDirection = direction === 'asc' || direction === 'desc' ? direction : '';
561
+ this.load();
562
+ }
563
+ onAction(event) {
564
+ this.emitAction(event.action, event.row);
565
+ }
566
+ onCellChange(event) {
567
+ const config = this.editableColumns().find((item) => item.key === event.column.key);
568
+ if (!config?.api)
569
+ return;
570
+ this.emitAction({
571
+ id: `edit:${config.key}`,
572
+ label: 'Зберегти',
573
+ type: 'api',
574
+ api: config.api,
575
+ method: config.method || 'PATCH',
576
+ payload: {
577
+ ...event.row,
578
+ ...(config.payload || {}),
579
+ id: event.row['id'],
580
+ [config.bindKey || config.key]: event.value,
581
+ },
582
+ }, event.row);
583
+ }
584
+ onHeaderAction(action) {
585
+ this.emitAction(action, this.data || {});
586
+ }
587
+ emitAction(action, row) {
588
+ const resolved = {
589
+ ...action,
590
+ id: String(action.id || '').trim() || undefined,
591
+ label: String(action.label || action.id || 'Дія'),
592
+ type: String(action.type || 'api'),
593
+ api: action.api ? this.resolve(action.api, row) : action.api,
594
+ payload: action.payload ? this.resolveObject(action.payload, row) : action.payload,
595
+ context: {
596
+ entity: this.tableName(),
597
+ recordId: String(row['id'] || '').trim() || undefined,
598
+ data: row,
599
+ },
600
+ };
601
+ this.eventBus.emit(CARD_ACTION_REQUESTED, {
602
+ action: resolved,
603
+ context: resolved.context,
604
+ });
605
+ }
606
+ load() {
607
+ const table = this.tableName();
608
+ if (!table)
609
+ return;
610
+ this.loadingSignal.set(true);
611
+ this.errorSignal.set('');
612
+ this.http.get(this.tableEndpoint(table)).pipe(map((response) => normalizeTableResponse(response))).subscribe({
613
+ next: (view) => {
614
+ const sourceColumns = view.columns || [];
615
+ this.sourceColumns = sourceColumns;
616
+ this.setColumns();
617
+ this.headerActionsSignal.set(view.actions?.header?.custom || []);
618
+ this.rowsSignal.set(this.rowsFrom(view, sourceColumns));
619
+ this.loadEditableOptions();
620
+ this.page = view.pagination?.page ?? this.page;
621
+ this.pageSize = view.pagination?.size ?? this.pageSize;
622
+ this.paginationSignal.set({
623
+ page: this.page,
624
+ pageSize: this.pageSize,
625
+ total: view.pagination?.totalElements ?? view.pagination?.total ?? this.rowsSignal().length,
626
+ });
627
+ this.loadingSignal.set(false);
628
+ },
629
+ error: (error) => {
630
+ this.rowsSignal.set([]);
631
+ this.columnsSignal.set([]);
632
+ this.headerActionsSignal.set([]);
633
+ this.errorSignal.set(error?.error?.message || 'Не вдалося завантажити таблицю');
634
+ this.loadingSignal.set(false);
635
+ },
636
+ });
637
+ }
638
+ setColumns() {
639
+ const editable = this.editableColumns();
640
+ this.columnsSignal.set(this.sourceColumns.filter((column) => !column['hidden']).map((column) => {
641
+ const key = String(column['id'] || '');
642
+ const config = editable.find((item) => item.key === key);
643
+ return {
644
+ key,
645
+ labelKey: String(column['label'] || key),
646
+ type: column['type'],
647
+ width: column['width'],
648
+ sortable: true,
649
+ link: column['link'],
650
+ editable: !!config,
651
+ editableCondition: String(this.config?.props?.['editableCondition'] || config?.['condition'] || ''),
652
+ editor: config?.editor,
653
+ bindKey: config?.bindKey || key,
654
+ options: config?.options || this.editableOptionsSignal()[key] || [],
655
+ placeholder: config?.placeholder,
656
+ includeBlankOption: config?.includeBlankOption,
657
+ blankOptionLabel: config?.blankOptionLabel,
658
+ };
659
+ }));
660
+ }
661
+ loadEditableOptions() {
662
+ const sources = this.editableColumns().filter((column) => column.data && !column.options);
663
+ if (!sources.length)
664
+ return;
665
+ forkJoin(sources.map((column) => this.http.get(`/api/suggest/${encodeURIComponent(column.data)}`))).subscribe({
666
+ next: (responses) => {
667
+ const options = { ...this.editableOptionsSignal() };
668
+ sources.forEach((column, index) => {
669
+ options[column.key] = (responses[index] || []).map((item) => ({
670
+ value: item?.key == null ? null : String(item.key),
671
+ label: String(item?.label || item?.key || '').trim(),
672
+ }));
673
+ });
674
+ this.editableOptionsSignal.set(options);
675
+ this.setColumns();
676
+ },
677
+ });
678
+ }
679
+ rowsFrom(view, columns) {
680
+ const configured = view.actions?.row?.custom || [];
681
+ return (view.rows || []).map((raw) => {
682
+ const row = Array.isArray(raw)
683
+ ? columns.reduce((result, column, index) => {
684
+ result[String(column['id'])] = raw[index] ?? '';
685
+ return result;
686
+ }, { id: String(raw[0] || '') })
687
+ : { ...(raw || {}) };
688
+ row['id'] = String(row['id'] || '');
689
+ if (!Array.isArray(row['actions'])) {
690
+ const actions = [...configured];
691
+ if (view.viewActions?.edit !== false && view.actions?.row?.edit !== false && this.editEnabled()) {
692
+ actions.push({ id: 'edit', label: 'Редагувати', type: 'form', formId: this.tableName() });
693
+ }
694
+ if (view.viewActions?.delete !== false && view.actions?.row?.delete !== false) {
695
+ actions.push({ id: 'delete', label: 'Видалити', type: 'submit', api: `/api/crud/${this.tableName()}/${row['id']}`, method: 'DELETE' });
696
+ }
697
+ if (actions.length)
698
+ row['actions'] = actions;
699
+ }
700
+ return row;
701
+ });
702
+ }
703
+ tableName() {
704
+ return String(this.config?.props?.['table'] || this.config?.props?.['entity'] || '').trim();
705
+ }
706
+ editableColumns() {
707
+ const value = this.config?.props?.['editableColumns'];
708
+ return Array.isArray(value)
709
+ ? value.filter((item) => !!item && typeof item === 'object' && !!String(item.key || '').trim())
710
+ : [];
711
+ }
712
+ tableEndpoint(table) {
713
+ const params = new URLSearchParams({
714
+ page: String(this.page),
715
+ size: String(this.pageSize),
716
+ sortField: this.sortField,
717
+ sortDirection: this.sortDirection,
718
+ filters: JSON.stringify(this.config?.props?.['initialFilters'] || this.config?.props?.['filter'] || {}),
719
+ });
720
+ return `/api/crud/${encodeURIComponent(table)}?${params.toString()}`;
721
+ }
722
+ editEnabled() {
723
+ return this.config?.props?.['edit'] !== false && this.config?.props?.['edit'] !== 'false';
724
+ }
725
+ numberProp(key, fallback) {
726
+ const value = Number(this.config?.props?.[key]);
727
+ return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
728
+ }
729
+ resolveObject(value, row) {
730
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, this.resolveValue(item, row)]));
731
+ }
732
+ resolve(value, row) {
733
+ return String(this.resolveValue(value, row));
734
+ }
735
+ resolveValue(value, row) {
736
+ if (typeof value === 'string') {
737
+ return value.replace(/\{\{([a-zA-Z0-9_.-]+)\}\}/g, (_match, path) => {
738
+ const result = String(path).split('.').reduce((current, key) => current && typeof current === 'object' ? current[key] : undefined, row);
739
+ return result == null ? '' : String(result);
740
+ });
741
+ }
742
+ if (Array.isArray(value))
743
+ return value.map((item) => this.resolveValue(item, row));
744
+ if (value && typeof value === 'object')
745
+ return this.resolveObject(value, row);
746
+ return value;
747
+ }
748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StandardTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
749
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: StandardTableComponent, isStandalone: true, selector: "app-card-table", inputs: { config: "config", data: "data" }, usesOnChanges: true, ngImport: i0, template: "<section class=\"space-y-4\">\n @if (headerActions().length) {\n <div class=\"flex justify-end gap-2\">\n @for (action of headerActions(); track action.id) {\n <button type=\"button\" class=\"rounded-lg border border-slate-200 px-3 py-1.5 text-sm\" (click)=\"onHeaderAction(action)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <app-ui-table\n [data]=\"tableData()\"\n [columns]=\"columns()\"\n [isLoading]=\"isLoading()\"\n [disablePaging]=\"disablePaging\"\n (pageChange)=\"onPageChange($event)\"\n (actionClick)=\"onAction($event)\"\n (cellChange)=\"onCellChange($event)\"\n ></app-ui-table>\n</section>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiTableComponent, selector: "app-ui-table", inputs: ["columns", "isLoading", "data", "disablePaging", "rowClickable", "imageResolver"], outputs: ["pageChange", "rowClick", "actionClick", "cellChange"] }] });
750
+ }
751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StandardTableComponent, decorators: [{
752
+ type: Component,
753
+ args: [{ selector: 'app-card-table', standalone: true, imports: [CommonModule, UiTableComponent], template: "<section class=\"space-y-4\">\n @if (headerActions().length) {\n <div class=\"flex justify-end gap-2\">\n @for (action of headerActions(); track action.id) {\n <button type=\"button\" class=\"rounded-lg border border-slate-200 px-3 py-1.5 text-sm\" (click)=\"onHeaderAction(action)\">\n {{ action.label }}\n </button>\n }\n </div>\n }\n\n <app-ui-table\n [data]=\"tableData()\"\n [columns]=\"columns()\"\n [isLoading]=\"isLoading()\"\n [disablePaging]=\"disablePaging\"\n (pageChange)=\"onPageChange($event)\"\n (actionClick)=\"onAction($event)\"\n (cellChange)=\"onCellChange($event)\"\n ></app-ui-table>\n</section>\n" }]
754
+ }], ctorParameters: () => [], propDecorators: { config: [{
755
+ type: Input,
756
+ args: [{ required: true }]
757
+ }], data: [{
758
+ type: Input
759
+ }] } });
760
+ function normalizeTableResponse(response) {
761
+ const raw = (response || {});
762
+ const source = (raw['config'] || raw);
763
+ return {
764
+ ...source,
765
+ rows: raw['rows'] ?? raw['items'] ?? source.rows ?? [],
766
+ pagination: {
767
+ page: raw['page'] ?? source.pagination?.page,
768
+ size: raw['size'] ?? source.pagination?.size,
769
+ total: raw['total'] ?? source.pagination?.total,
770
+ totalElements: raw['total'] ?? source.pagination?.totalElements,
771
+ },
772
+ };
773
+ }
453
774
 
454
775
  const UI_CARD_TAB_REGISTRY = new InjectionToken('UI_CARD_TAB_REGISTRY');
455
776
 
456
777
  class UiCardPanelsLayoutComponent {
457
778
  registry = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];
458
- entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
459
- recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
460
779
  tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
461
780
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
462
781
  embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
@@ -518,20 +837,18 @@ class UiCardPanelsLayoutComponent {
518
837
  return found.resolveInputs(tab, this.data());
519
838
  }
520
839
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardPanelsLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
521
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardPanelsLayoutComponent, isStandalone: true, selector: "app-ui-card-panels-layout", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n @if (!embedded()) {\n <section class=\"space-y-4\">\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n </section>\n } @else {\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n }\n}\n\n<ng-template #panelGrid>\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'images') {\n <app-images [tab]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["entity", "recordId", "tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "entity", "recordId", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["entity", "recordId", "tab", "data"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["tab"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["tab", "data"] }] });
840
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardPanelsLayoutComponent, isStandalone: true, selector: "app-ui-card-panels-layout", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [config]=\"tab\" [data]=\"data()\"></app-details-grid>\n } @else if (tab.component === 'table') {\n <app-card-table [config]=\"tab\" [data]=\"data()\"></app-card-table>\n } @else if (tab.component === 'images') {\n <app-images [config]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [html]=\"tab.html || ''\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["html"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["config", "data"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["config", "data"] }, { kind: "component", type: i0.forwardRef(() => StandardTableComponent), selector: "app-card-table", inputs: ["config", "data"] }] });
522
841
  }
523
842
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardPanelsLayoutComponent, decorators: [{
524
843
  type: Component,
525
- args: [{ selector: 'app-ui-card-panels-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, DetailsGridComponent, ImagesComponent], template: "@if (tabs().length) {\n @if (!embedded()) {\n <section class=\"space-y-4\">\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n </section>\n } @else {\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n }\n}\n\n<ng-template #panelGrid>\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'images') {\n <app-images [tab]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n</ng-template>\n" }]
526
- }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
844
+ args: [{ selector: 'app-ui-card-panels-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, DetailsGridComponent, ImagesComponent, StandardTableComponent], template: "@if (tabs().length) {\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [config]=\"tab\" [data]=\"data()\"></app-details-grid>\n } @else if (tab.component === 'table') {\n <app-card-table [config]=\"tab\" [data]=\"data()\"></app-card-table>\n } @else if (tab.component === 'images') {\n <app-images [config]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [html]=\"tab.html || ''\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n}\n" }]
845
+ }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
527
846
 
528
847
  class UiCardTabsLayoutComponent {
529
848
  registry = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];
530
849
  destroyRef = inject(DestroyRef);
531
850
  route = inject(ActivatedRoute);
532
851
  router = inject(Router);
533
- entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
534
- recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
535
852
  tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
536
853
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
537
854
  activeTabId = signal('', ...(ngDevMode ? [{ debugName: "activeTabId" }] : /* istanbul ignore next */ []));
@@ -590,27 +907,25 @@ class UiCardTabsLayoutComponent {
590
907
  return found.resolveInputs(tab, this.data());
591
908
  }
592
909
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardTabsLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
593
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardTabsLayoutComponent, isStandalone: true, selector: "app-ui-card-tabs-layout", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [tab]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["entity", "recordId", "tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "entity", "recordId", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["entity", "recordId", "tab", "data"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["tab", "data"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["tab"] }] });
910
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardTabsLayoutComponent, isStandalone: true, selector: "app-ui-card-tabs-layout", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [config]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [config]=\"tab\" [data]=\"data()\"></app-details-grid>\n } @else if (tab.component === 'table') {\n <app-card-table [config]=\"tab\" [data]=\"data()\"></app-card-table>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [html]=\"tab.html || ''\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["html"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["config", "data"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["config", "data"] }, { kind: "component", type: i0.forwardRef(() => StandardTableComponent), selector: "app-card-table", inputs: ["config", "data"] }] });
594
911
  }
595
912
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardTabsLayoutComponent, decorators: [{
596
913
  type: Component,
597
- args: [{ selector: 'app-ui-card-tabs-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, ImagesComponent, DetailsGridComponent], template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [tab]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n" }]
598
- }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
914
+ args: [{ selector: 'app-ui-card-tabs-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, ImagesComponent, DetailsGridComponent, StandardTableComponent], template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [config]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [config]=\"tab\" [data]=\"data()\"></app-details-grid>\n } @else if (tab.component === 'table') {\n <app-card-table [config]=\"tab\" [data]=\"data()\"></app-card-table>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [html]=\"tab.html || ''\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n" }]
915
+ }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
599
916
 
600
917
  class CardComponent {
601
- entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
602
- recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
603
918
  tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
604
919
  data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
605
920
  layout = input('panels', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
606
921
  embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
607
922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardComponent, isStandalone: true, selector: "app-card", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "component", type: i0.forwardRef(() => UiCardTabsLayoutComponent), selector: "app-ui-card-tabs-layout", inputs: ["entity", "recordId", "tabs", "data"] }, { kind: "component", type: i0.forwardRef(() => UiCardPanelsLayoutComponent), selector: "app-ui-card-panels-layout", inputs: ["entity", "recordId", "tabs", "data", "embedded"] }] });
923
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardComponent, isStandalone: true, selector: "app-card", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "component", type: i0.forwardRef(() => UiCardTabsLayoutComponent), selector: "app-ui-card-tabs-layout", inputs: ["tabs", "data"] }, { kind: "component", type: i0.forwardRef(() => UiCardPanelsLayoutComponent), selector: "app-ui-card-panels-layout", inputs: ["tabs", "data", "embedded"] }] });
609
924
  }
610
925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardComponent, decorators: [{
611
926
  type: Component,
612
- args: [{ selector: 'app-card', standalone: true, imports: [CommonModule, forwardRef(() => UiCardTabsLayoutComponent), forwardRef(() => UiCardPanelsLayoutComponent)], template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n" }]
613
- }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
927
+ args: [{ selector: 'app-card', standalone: true, imports: [CommonModule, forwardRef(() => UiCardTabsLayoutComponent), forwardRef(() => UiCardPanelsLayoutComponent)], template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n" }]
928
+ }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
614
929
 
615
930
  const UI_ENTITY_CARD_HOST = new InjectionToken('UI_ENTITY_CARD_HOST');
616
931
 
@@ -861,7 +1176,7 @@ class DocumentsComponent {
861
1176
  plusIcon = Plus;
862
1177
  sparkleIcon = Sparkles;
863
1178
  deleteIcon = Trash;
864
- isAdmin = toSignal(inject(CardCurrentUserService).getCurrentUser().pipe(map((user) => String(user.role || '').trim().toUpperCase() === 'ADMIN')), { initialValue: false });
1179
+ isAdmin = toSignal(inject(CardCurrentUserService).getCurrentUser().pipe(map$1((user) => String(user.role || '').trim().toUpperCase() === 'ADMIN')), { initialValue: false });
865
1180
  documentsService = inject(UiEntityDocumentsService);
866
1181
  dictionaryService = inject(CardDictionaryService);
867
1182
  toast = inject(UiToastService);
@@ -1103,8 +1418,8 @@ class DocumentsComponent {
1103
1418
  const pendingDocuments = this.documents().filter((document) => this.isRecognitionPending(document));
1104
1419
  if (!pendingDocuments.length)
1105
1420
  return of([]);
1106
- return forkJoin(pendingDocuments.map((document) => this.documentsService.recognitionStatus(document.id).pipe(catchError(() => of(null))))).pipe(map((statuses) => statuses.filter((status) => !!status)));
1107
- }), map((statuses) => {
1421
+ return forkJoin(pendingDocuments.map((document) => this.documentsService.recognitionStatus(document.id).pipe(catchError(() => of(null))))).pipe(map$1((statuses) => statuses.filter((status) => !!status)));
1422
+ }), map$1((statuses) => {
1108
1423
  statuses.forEach((status) => this.upsertDocument(status));
1109
1424
  return statuses;
1110
1425
  }), takeWhile(() => this.documents().some((document) => this.isRecognitionPending(document)), true), takeUntilDestroyed(this.destroyRef), takeUntil(timer(RECOGNITION_POLLING_TIMEOUT_MS)), finalize(() => {
@@ -1283,1053 +1598,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
1283
1598
  args: [{ selector: 'app-history-tab', standalone: true, imports: [CommonModule, ImagesViewerComponent, RouterLink], template: "<section class=\"space-y-4\">\n @if (isLoading()) {\n <div class=\"rounded-2xl border border-slate-200 bg-white p-6 text-sm text-slate-500\">\n \u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F \u0456\u0441\u0442\u043E\u0440\u0456\u0457\u2026\n </div>\n } @else if (hasError()) {\n <div class=\"rounded-2xl border border-red-200 bg-red-50 p-6 text-sm text-red-700\">\n \u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0456\u0441\u0442\u043E\u0440\u0456\u044E \u0437\u043C\u0456\u043D.\n </div>\n } @else if (!operations().length) {\n <div class=\"rounded-2xl border border-slate-200 bg-white p-6 text-sm text-slate-500\">\n \u0406\u0441\u0442\u043E\u0440\u0456\u044F \u0437\u043C\u0456\u043D \u043F\u043E\u043A\u0438 \u043F\u043E\u0440\u043E\u0436\u043D\u044F.\n </div>\n } @else {\n <div class=\"space-y-3\">\n @for (operation of operations(); track operation.id) {\n <article class=\"rounded-2xl border border-slate-200 bg-white p-5 shadow-sm\">\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h3 class=\"text-sm font-semibold text-slate-900\">{{ operation.description }}</h3>\n @if (operation.actorName) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ operation.actorName }}</p>\n }\n </div>\n <time class=\"text-xs text-slate-400\" [dateTime]=\"operation.createdAt\">\n {{ operation.createdAt | date: 'dd.MM.yyyy HH:mm' }}\n </time>\n </div>\n\n @if (getPrimaryChanges(operation).length) {\n <div class=\"mt-4 space-y-2 border-t border-slate-100 pt-4\">\n @for (change of getPrimaryChanges(operation); track change.entityId + change.entityType) {\n @for (field of (change.changedFields | keyvalue); track field.key) {\n <div class=\"flex flex-wrap items-center gap-2 text-sm\">\n <span class=\"font-medium text-slate-700\">{{ field.value.label }}</span>\n @if (isImageField(field.value)) {\n @if (getImageUrls(field.value.from.value).length) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (url of getImageUrls(field.value.from.value); track url; let imageIndex = $index) {\n <button type=\"button\" class=\"h-14 w-14 overflow-hidden rounded-lg border border-slate-200 bg-slate-100\" (click)=\"openImageViewer(getImageUrls(field.value.from.value))\" [attr.aria-label]=\"'\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u0441\u0442\u0430\u0440\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\">\n <img [src]=\"url\" [alt]=\"'\u0421\u0442\u0430\u0440\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\" class=\"h-full w-full object-cover\" />\n </button>\n }\n </div>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isFileField(field.value)) {\n @if (field.value.from.url) {\n <a class=\"font-medium text-sky-700 underline\" [href]=\"field.value.from.url\" target=\"_blank\" rel=\"noopener noreferrer\">\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0444\u0430\u0439\u043B</a>\n } @else {\n <span class=\"text-slate-400\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isHtmlField(field.value)) {\n @if (field.value.from.display !== '\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E') {\n <div class=\"prose prose-sm max-w-none text-slate-500\" [innerHTML]=\"field.value.from.display\"></div>\n } @else {\n <span class=\"text-slate-400\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else {\n @if (field.value.from.url) {\n <a class=\"font-medium text-sky-700 underline\" [routerLink]=\"field.value.from.url\">{{ field.value.from.display }}</a>\n } @else {\n <span class=\"font-medium text-slate-900\">{{ field.value.from.display }}</span>\n }\n }\n <span class=\"text-slate-400\">\u2192</span>\n @if (isImageField(field.value)) {\n @if (getImageUrls(field.value.to.value).length) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (url of getImageUrls(field.value.to.value); track url; let imageIndex = $index) {\n <button type=\"button\" class=\"h-14 w-14 overflow-hidden rounded-lg border border-slate-200 bg-slate-100\" (click)=\"openImageViewer(getImageUrls(field.value.to.value))\" [attr.aria-label]=\"'\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u043D\u043E\u0432\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\">\n <img [src]=\"url\" [alt]=\"'\u041D\u043E\u0432\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\" class=\"h-full w-full object-cover\" />\n </button>\n }\n </div>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isFileField(field.value)) {\n @if (field.value.to.url) {\n <a class=\"font-medium text-sky-700 underline\" [href]=\"field.value.to.url\" target=\"_blank\" rel=\"noopener noreferrer\">\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0444\u0430\u0439\u043B</a>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isHtmlField(field.value)) {\n @if (field.value.to.display !== '\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E') {\n <div class=\"prose prose-sm max-w-none text-slate-900\" [innerHTML]=\"field.value.to.display\"></div>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else {\n @if (field.value.to.url) {\n <a class=\"font-medium text-sky-700 underline\" [routerLink]=\"field.value.to.url\">{{ field.value.to.display }}</a>\n } @else {\n <span class=\"font-medium text-slate-900\">{{ field.value.to.display }}</span>\n }\n }\n </div>\n }\n }\n </div>\n }\n\n @if (getRelatedChanges(operation).length) {\n <div class=\"mt-4 border-t border-slate-100 pt-4\">\n <button\n type=\"button\"\n class=\"flex w-full items-center justify-between gap-3 text-left text-sm text-slate-600\"\n [attr.aria-expanded]=\"isExpanded(operation.id)\"\n (click)=\"toggleOperation(operation.id)\"\n >\n <span>\n \u0414\u0435\u0442\u0430\u043B\u0456 \u0437\u043C\u0456\u043D:\n <span class=\"font-medium text-slate-900\">{{ getAffectedSummary(operation) }}</span>\n </span>\n <span class=\"text-lg leading-none text-slate-400\">{{ isExpanded(operation.id) ? '\u2212' : '+' }}</span>\n </button>\n\n @if (isExpanded(operation.id)) {\n <div class=\"mt-4 space-y-4\">\n @for (change of getRelatedChanges(operation); track change.entityId + change.entityType) {\n <div class=\"rounded-xl bg-slate-50 p-3\">\n <p class=\"mb-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-500\">\n {{ getEntityLabel(change.entityType) }} \u00B7 {{ getActionLabel(change.action) }}\n </p>\n @for (field of (change.changedFields | keyvalue); track field.key) {\n <div class=\"flex flex-wrap items-center gap-2 text-sm\">\n <span class=\"font-medium text-slate-700\">{{ field.value.label }}</span>\n @if (isImageField(field.value)) {\n @if (getImageUrls(field.value.from.value).length) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (url of getImageUrls(field.value.from.value); track url; let imageIndex = $index) {\n <button type=\"button\" class=\"h-14 w-14 overflow-hidden rounded-lg border border-slate-200 bg-white\" (click)=\"openImageViewer(getImageUrls(field.value.from.value))\" [attr.aria-label]=\"'\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u0441\u0442\u0430\u0440\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\">\n <img [src]=\"url\" [alt]=\"'\u0421\u0442\u0430\u0440\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\" class=\"h-full w-full object-cover\" />\n </button>\n }\n </div>\n } @else {\n <span class=\"text-slate-400\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isFileField(field.value)) {\n @if (field.value.from.url) {\n <a class=\"text-sky-700 underline\" [href]=\"field.value.from.url\" target=\"_blank\" rel=\"noopener noreferrer\">\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0444\u0430\u0439\u043B</a>\n } @else {\n <span class=\"text-slate-400\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isHtmlField(field.value)) {\n @if (field.value.from.display !== '\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E') {\n <div class=\"prose prose-sm max-w-none text-slate-500\" [innerHTML]=\"field.value.from.display\"></div>\n } @else {\n <span class=\"text-slate-400\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else {\n @if (field.value.from.url) {\n <a class=\"text-sky-700 underline\" [routerLink]=\"field.value.from.url\">{{ field.value.from.display }}</a>\n } @else {\n <span class=\"text-slate-400\">{{ field.value.from.display }}</span>\n }\n }\n <span class=\"text-slate-400\">\u2192</span>\n @if (isImageField(field.value)) {\n @if (getImageUrls(field.value.to.value).length) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @for (url of getImageUrls(field.value.to.value); track url; let imageIndex = $index) {\n <button type=\"button\" class=\"h-14 w-14 overflow-hidden rounded-lg border border-slate-200 bg-white\" (click)=\"openImageViewer(getImageUrls(field.value.to.value))\" [attr.aria-label]=\"'\u0412\u0456\u0434\u043A\u0440\u0438\u0442\u0438 \u043D\u043E\u0432\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\">\n <img [src]=\"url\" [alt]=\"'\u041D\u043E\u0432\u0435 \u0444\u043E\u0442\u043E ' + (imageIndex + 1)\" class=\"h-full w-full object-cover\" />\n </button>\n }\n </div>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isFileField(field.value)) {\n @if (field.value.to.url) {\n <a class=\"font-medium text-sky-700 underline\" [href]=\"field.value.to.url\" target=\"_blank\" rel=\"noopener noreferrer\">\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 \u0444\u0430\u0439\u043B</a>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else if (isHtmlField(field.value)) {\n @if (field.value.to.display !== '\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E') {\n <div class=\"prose prose-sm max-w-none text-slate-900\" [innerHTML]=\"field.value.to.display\"></div>\n } @else {\n <span class=\"font-medium text-slate-900\">\u041D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E</span>\n }\n } @else {\n @if (field.value.to.url) {\n <a class=\"font-medium text-sky-700 underline\" [routerLink]=\"field.value.to.url\">{{ field.value.to.display }}</a>\n } @else {\n <span class=\"font-medium text-slate-900\">{{ field.value.to.display }}</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </article>\n }\n </div>\n }\n</section>\n\n@if (isViewerOpen()) {\n <app-images-viewer [urls]=\"viewerUrls()\" (closed)=\"closeImageViewer()\" />\n}\n" }]
1284
1599
  }], ctorParameters: () => [], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: false }] }], primaryEntity: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryEntity", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], entityLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityLabels", required: false }] }] } });
1285
1600
 
1286
- const ACTION_API = 'ACTION_API';
1287
- const ACTION_FORM = 'ACTION_FORM';
1288
- const ACTION_PRINT = 'ACTION_PRINT';
1289
- const normalizeActionType = (value) => {
1290
- const raw = String(value || '').trim().toLowerCase();
1291
- if (raw === 'api' || raw === ACTION_API.toLowerCase())
1292
- return ACTION_API;
1293
- if (raw === 'form' || raw === ACTION_FORM.toLowerCase())
1294
- return ACTION_FORM;
1295
- if (raw === 'print' || raw === ACTION_PRINT.toLowerCase())
1296
- return ACTION_PRINT;
1297
- return raw ? raw.toUpperCase() : null;
1298
- };
1299
- class CardTableDataService {
1300
- http = inject(HttpClient);
1301
- baseUrl = inject(UI_CARD_API_BASE_URL);
1302
- getTableView(code, options) {
1303
- let params = new HttpParams();
1304
- for (const [key, value] of Object.entries(options)) {
1305
- if (value !== undefined && value !== null && value !== '')
1306
- params = params.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
1307
- }
1308
- return this.http.get(cardApiResourceUrl(this.baseUrl, `/api/crud/${encodeURIComponent(code)}`), { params }).pipe(map$1((response) => ({
1309
- ...(response.config || response),
1310
- rows: response.rows ?? response.items ?? [],
1311
- pagination: {
1312
- page: response.page,
1313
- size: response.size,
1314
- totalElements: response.total,
1315
- totalPages: response.totalPages,
1316
- },
1317
- })));
1318
- }
1319
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1320
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableDataService, providedIn: 'root' });
1321
- }
1322
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableDataService, decorators: [{
1323
- type: Injectable,
1324
- args: [{ providedIn: 'root' }]
1325
- }] });
1326
- class CardTableWarehouseService {
1327
- http = inject(HttpClient);
1328
- baseUrl = inject(UI_CARD_API_BASE_URL);
1329
- getWarehouses() {
1330
- return this.http.get(cardApiResourceUrl(this.baseUrl, '/api/warehouses')).pipe(map$1((items) => ({ items: items || [] })));
1331
- }
1332
- getWarehouseItems(id) {
1333
- return this.http.get(cardApiResourceUrl(this.baseUrl, `/api/warehouses/${encodeURIComponent(id)}/items`));
1334
- }
1335
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableWarehouseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1336
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableWarehouseService, providedIn: 'root' });
1337
- }
1338
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableWarehouseService, decorators: [{
1339
- type: Injectable,
1340
- args: [{ providedIn: 'root' }]
1341
- }] });
1342
- class CardTableBarcodeService {
1343
- printBarcode(options) {
1344
- const barcode = String(options.barcode || '').trim();
1345
- if (!barcode || typeof window === 'undefined')
1346
- return;
1347
- const printWindow = window.open('', '_blank', 'width=860,height=920');
1348
- if (!printWindow)
1349
- return;
1350
- const barcodeSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1351
- try {
1352
- JsBarcode(barcodeSvg, barcode, { format: 'CODE128', displayValue: false, margin: 0, width: 2.1, height: 88, lineColor: '#0f172a', background: '#ffffff' });
1353
- }
1354
- catch {
1355
- printWindow.close();
1356
- return;
1357
- }
1358
- printWindow.document.write(`<!doctype html><html><head><title>${this.escape(options.title)}</title><style>body{font-family:Arial;text-align:center;padding:10mm}.barcode{width:58mm;margin:auto}.barcode svg{width:100%;height:18mm}</style></head><body><h2>${this.escape(options.title)}</h2><div class="barcode">${barcodeSvg.outerHTML}</div></body></html>`);
1359
- printWindow.document.close();
1360
- printWindow.focus();
1361
- printWindow.print();
1362
- }
1363
- escape(value) { return value.replace(/[&<>'"]/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[char] || char)); }
1364
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableBarcodeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1365
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableBarcodeService, providedIn: 'root' });
1366
- }
1367
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableBarcodeService, decorators: [{
1368
- type: Injectable,
1369
- args: [{ providedIn: 'root' }]
1370
- }] });
1371
- class CardTablePrintService {
1372
- openPrintWindow() { return typeof window === 'undefined' ? null : window.open('', '_blank', 'width=860,height=920'); }
1373
- print(config) {
1374
- const printWindow = this.openPrintWindow();
1375
- return printWindow ? this.printInWindow(printWindow, config) : false;
1376
- }
1377
- printInWindow(printWindow, config) {
1378
- printWindow.document.write(`<!doctype html><html><head><title>${config.title || 'Друк'}</title></head><body>${config.html}</body></html>`);
1379
- printWindow.document.close();
1380
- printWindow.focus();
1381
- printWindow.print();
1382
- return true;
1383
- }
1384
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTablePrintService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1385
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTablePrintService, providedIn: 'root' });
1386
- }
1387
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTablePrintService, decorators: [{
1388
- type: Injectable,
1389
- args: [{ providedIn: 'root' }]
1390
- }] });
1391
-
1392
- const getConditionPathValue = (source, pathRaw) => {
1393
- const path = String(pathRaw || '').trim();
1394
- if (!path)
1395
- return undefined;
1396
- const parts = path.split('.');
1397
- let current = source;
1398
- for (const part of parts) {
1399
- if (current == null || typeof current !== 'object')
1400
- return undefined;
1401
- current = current[part];
1402
- }
1403
- return current;
1404
- };
1405
- const evaluateCondition = (conditionRaw, context) => {
1406
- const condition = String(conditionRaw || '').trim();
1407
- if (!condition)
1408
- return true;
1409
- const tokens = [];
1410
- let index = 0;
1411
- const pushOperator = (value) => {
1412
- tokens.push({ type: 'operator', value });
1413
- };
1414
- while (index < condition.length) {
1415
- const char = condition[index];
1416
- if (/\s/.test(char)) {
1417
- index += 1;
1418
- continue;
1419
- }
1420
- const nextThree = condition.slice(index, index + 3);
1421
- const nextTwo = condition.slice(index, index + 2);
1422
- if (nextThree === '===') {
1423
- pushOperator('===');
1424
- index += 3;
1425
- continue;
1426
- }
1427
- if (nextThree === '!==') {
1428
- pushOperator('!==');
1429
- index += 3;
1430
- continue;
1431
- }
1432
- if (nextTwo === '==') {
1433
- pushOperator('==');
1434
- index += 2;
1435
- continue;
1436
- }
1437
- if (nextTwo === '!=') {
1438
- pushOperator('!=');
1439
- index += 2;
1440
- continue;
1441
- }
1442
- if (nextTwo === '&&') {
1443
- pushOperator('&&');
1444
- index += 2;
1445
- continue;
1446
- }
1447
- if (nextTwo === '||') {
1448
- pushOperator('||');
1449
- index += 2;
1450
- continue;
1451
- }
1452
- if (char === '!' || char === '(' || char === ')') {
1453
- pushOperator(char);
1454
- index += 1;
1455
- continue;
1456
- }
1457
- if (char === '"' || char === "'") {
1458
- const quote = char;
1459
- index += 1;
1460
- let value = '';
1461
- let escaped = false;
1462
- let closed = false;
1463
- while (index < condition.length) {
1464
- const current = condition[index];
1465
- index += 1;
1466
- if (escaped) {
1467
- value += current;
1468
- escaped = false;
1469
- continue;
1470
- }
1471
- if (current === '\\') {
1472
- escaped = true;
1473
- continue;
1474
- }
1475
- if (current === quote) {
1476
- closed = true;
1477
- break;
1478
- }
1479
- value += current;
1480
- }
1481
- if (!closed)
1482
- return true;
1483
- tokens.push({ type: 'string', value });
1484
- continue;
1485
- }
1486
- if (/[0-9.-]/.test(char)) {
1487
- const match = condition.slice(index).match(/^-?\d+(?:\.\d+)?/);
1488
- if (match) {
1489
- tokens.push({ type: 'number', value: Number(match[0]) });
1490
- index += match[0].length;
1491
- continue;
1492
- }
1493
- }
1494
- if (/[A-Za-z_$]/.test(char)) {
1495
- const match = condition.slice(index).match(/^[A-Za-z_$][A-Za-z0-9_.$-]*/);
1496
- if (match) {
1497
- const raw = match[0];
1498
- const normalized = raw.toLowerCase();
1499
- if (normalized === 'true' || normalized === 'false') {
1500
- tokens.push({ type: 'boolean', value: normalized === 'true' });
1501
- }
1502
- else if (normalized === 'null') {
1503
- tokens.push({ type: 'null', value: null });
1504
- }
1505
- else {
1506
- tokens.push({ type: 'identifier', value: raw });
1507
- }
1508
- index += raw.length;
1509
- continue;
1510
- }
1511
- }
1512
- return true;
1513
- }
1514
- let tokenIndex = 0;
1515
- const peek = () => tokens[tokenIndex];
1516
- const consume = () => {
1517
- const token = tokens[tokenIndex];
1518
- tokenIndex += 1;
1519
- return token;
1520
- };
1521
- const matchOperator = (...values) => {
1522
- const token = peek();
1523
- if (token?.type !== 'operator')
1524
- return false;
1525
- if (!values.includes(token.value))
1526
- return false;
1527
- tokenIndex += 1;
1528
- return true;
1529
- };
1530
- const toBoolean = (value) => {
1531
- if (typeof value === 'string')
1532
- return value.trim().length > 0;
1533
- return Boolean(value);
1534
- };
1535
- const getValue = (identifierRaw) => {
1536
- const identifier = String(identifierRaw || '').trim();
1537
- if (!identifier)
1538
- return undefined;
1539
- if (identifier === 'data')
1540
- return context;
1541
- if (identifier.startsWith('data.'))
1542
- return getConditionPathValue(context, identifier.slice(5));
1543
- return getConditionPathValue(context, identifier);
1544
- };
1545
- const parseExpression = () => parseOr();
1546
- const parseOr = () => {
1547
- let left = parseAnd();
1548
- while (matchOperator('||')) {
1549
- const right = parseAnd();
1550
- left = toBoolean(left) || toBoolean(right);
1551
- }
1552
- return left;
1553
- };
1554
- const parseAnd = () => {
1555
- let left = parseEquality();
1556
- while (matchOperator('&&')) {
1557
- const right = parseEquality();
1558
- left = toBoolean(left) && toBoolean(right);
1559
- }
1560
- return left;
1561
- };
1562
- const parseEquality = () => {
1563
- let left = parseUnary();
1564
- while (true) {
1565
- if (matchOperator('==')) {
1566
- left = left == parseUnary();
1567
- continue;
1568
- }
1569
- if (matchOperator('!=')) {
1570
- left = left != parseUnary();
1571
- continue;
1572
- }
1573
- if (matchOperator('===')) {
1574
- left = left === parseUnary();
1575
- continue;
1576
- }
1577
- if (matchOperator('!==')) {
1578
- left = left !== parseUnary();
1579
- continue;
1580
- }
1581
- break;
1582
- }
1583
- return left;
1584
- };
1585
- const parseUnary = () => {
1586
- if (matchOperator('!')) {
1587
- return !toBoolean(parseUnary());
1588
- }
1589
- return parsePrimary();
1590
- };
1591
- const parsePrimary = () => {
1592
- const token = consume();
1593
- if (!token)
1594
- return undefined;
1595
- if (token.type === 'operator' && token.value === '(') {
1596
- const value = parseExpression();
1597
- matchOperator(')');
1598
- return value;
1599
- }
1600
- if (token.type === 'identifier')
1601
- return getValue(token.value);
1602
- return token.value;
1603
- };
1604
- return toBoolean(parseExpression());
1605
- };
1606
- class CardTableComponent {
1607
- tab;
1608
- data = null;
1609
- tableViewsService = inject(CardTableDataService);
1610
- warehouseService = inject(CardTableWarehouseService);
1611
- barcodePrintService = inject(CardTableBarcodeService);
1612
- eventBus = inject(EventBus);
1613
- host = inject(UI_ENTITY_CARD_HOST, { optional: true });
1614
- actionBus = inject(CardTablePrintService);
1615
- tableColumnsSignal = signal([], ...(ngDevMode ? [{ debugName: "tableColumnsSignal" }] : /* istanbul ignore next */ []));
1616
- tableRowsSignal = signal([], ...(ngDevMode ? [{ debugName: "tableRowsSignal" }] : /* istanbul ignore next */ []));
1617
- tablePaginationSignal = signal({ page: 0, pageSize: 10, total: 0 }, ...(ngDevMode ? [{ debugName: "tablePaginationSignal" }] : /* istanbul ignore next */ []));
1618
- tableHeaderActionsSignal = signal([], ...(ngDevMode ? [{ debugName: "tableHeaderActionsSignal" }] : /* istanbul ignore next */ []));
1619
- editableColumnOptionsSignal = signal({}, ...(ngDevMode ? [{ debugName: "editableColumnOptionsSignal" }] : /* istanbul ignore next */ []));
1620
- isTableLoadingSignal = signal(false, ...(ngDevMode ? [{ debugName: "isTableLoadingSignal" }] : /* istanbul ignore next */ []));
1621
- isTableActionRunningSignal = signal(false, ...(ngDevMode ? [{ debugName: "isTableActionRunningSignal" }] : /* istanbul ignore next */ []));
1622
- activeEditableCellSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeEditableCellSignal" }] : /* istanbul ignore next */ []));
1623
- activeFormTargetSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeFormTargetSignal" }] : /* istanbul ignore next */ []));
1624
- activeFormTarget = computed(() => this.activeFormTargetSignal(), ...(ngDevMode ? [{ debugName: "activeFormTarget" }] : /* istanbul ignore next */ []));
1625
- tableData = computed(() => ({
1626
- items: this.tableRowsSignal(),
1627
- pagination: this.tablePaginationSignal(),
1628
- }), ...(ngDevMode ? [{ debugName: "tableData" }] : /* istanbul ignore next */ []));
1629
- pageIndex = 0;
1630
- pageSize = 10;
1631
- sortField = '';
1632
- sortDirection = '';
1633
- tableViewColumns = [];
1634
- editableColumnsLoaded = false;
1635
- refreshAfterFormCompleted = false;
1636
- ngOnChanges() {
1637
- this.pageIndex = 0;
1638
- this.pageSize = this.resolvePageSize();
1639
- this.sortField = '';
1640
- this.sortDirection = '';
1641
- this.editableColumnsLoaded = false;
1642
- this.activeEditableCellSignal.set(null);
1643
- this.editableColumnOptionsSignal.set({});
1644
- this.loadTable();
1645
- }
1646
- get disablePaging() {
1647
- const value = this.tab?.props?.['disablePaging'];
1648
- return value === true || value === 'true';
1649
- }
1650
- get tableColumns() {
1651
- return this.tableColumnsSignal();
1652
- }
1653
- get tableHeaderActions() {
1654
- return this.tableHeaderActionsSignal();
1655
- }
1656
- get tableActions() {
1657
- const blockActions = Array.isArray(this.tab?.actions) ? this.tab.actions : [];
1658
- return [
1659
- ...blockActions.map((action, index) => {
1660
- const rawAction = action;
1661
- return {
1662
- ...action,
1663
- variant: rawAction.variant || (index === 0 ? 'primary' : 'ghost'),
1664
- };
1665
- }),
1666
- ...this.tableHeaderActions.map((action) => ({
1667
- ...action,
1668
- variant: 'ghost',
1669
- })),
1670
- ];
1671
- }
1672
- get isTableLoading() {
1673
- return this.isTableLoadingSignal();
1674
- }
1675
- get isTableActionRunning() {
1676
- return this.isTableActionRunningSignal();
1677
- }
1678
- get rowClickable() {
1679
- return this.isEditEnabled();
1680
- }
1681
- onTablePageChange(event) {
1682
- this.pageIndex = Math.max(0, Number(event['pageIndex'] ?? this.pageIndex) || 0);
1683
- this.pageSize = Math.max(1, Number(event['pageSize'] ?? this.pageSize) || this.pageSize);
1684
- const active = String(event['active'] ?? '').trim();
1685
- this.sortField = this.tableViewColumns.find((column) => column.id === active)?.id || '';
1686
- const direction = String(event['direction'] ?? '').toLowerCase();
1687
- this.sortDirection = direction === 'asc' || direction === 'desc' ? direction : '';
1688
- this.loadTable();
1689
- }
1690
- onTableAction(event) {
1691
- if (normalizeActionType(event.action.type) === ACTION_PRINT || event.action.component === 'warehouseBarcodePrint' || event.action.template || event.action.id === 'print-certificate') {
1692
- this.runPrintAction(event.action, event.row);
1693
- return;
1694
- }
1695
- if (normalizeActionType(event.action.type) === ACTION_FORM && String(event.action.formId || '').trim()) {
1696
- this.runFormAction(event.action, event.row);
1697
- return;
1698
- }
1699
- if (!event.action.api) {
1700
- return;
1701
- }
1702
- const payload = this.resolveTemplatePayload(event.action.payload || {}, event.row);
1703
- this.runApiAction({
1704
- api: this.resolveTemplateString(event.action.api, {
1705
- ...event.row,
1706
- ...payload,
1707
- }),
1708
- method: event.action.method || 'POST',
1709
- payload,
1710
- title: event.action.title,
1711
- text: event.action.text,
1712
- submit: event.action.submit,
1713
- danger: event.action.danger,
1714
- successMessage: event.action.id === 'issue'
1715
- ? 'Матеріали списано зі складу'
1716
- : event.action.id === 'delete'
1717
- ? 'Позицію видалено'
1718
- : 'Дію виконано',
1719
- errorMessage: 'Не вдалося виконати дію',
1720
- });
1721
- }
1722
- onHeaderAction(action) {
1723
- if (!action?.formId) {
1724
- if (action?.api) {
1725
- this.runApiAction({
1726
- api: action.api,
1727
- method: action.method || 'POST',
1728
- payload: this.resolveTemplatePayload(action.payload || {}, this.data || {}),
1729
- title: action.title,
1730
- text: action.text,
1731
- submit: action.submit,
1732
- danger: action.variant === 'danger',
1733
- successMessage: 'Дію виконано',
1734
- errorMessage: 'Не вдалося виконати дію',
1735
- });
1736
- }
1737
- return;
1738
- }
1739
- this.runHeaderFormAction(action);
1740
- }
1741
- onTableRowClick(row) {
1742
- if (!this.isEditEnabled()) {
1743
- return;
1744
- }
1745
- this.runRowEditAction(row);
1746
- }
1747
- onTableCellChange(event) {
1748
- const editableConfig = this.resolveEditableColumnsConfig().find((column) => column.key === event.column.key);
1749
- if (!editableConfig) {
1750
- return;
1751
- }
1752
- const rowId = String(event.row['id'] ?? '').trim();
1753
- if (!rowId) {
1754
- return;
1755
- }
1756
- const nextValue = String(event.value ?? '').trim();
1757
- const nextRow = {
1758
- ...event.row,
1759
- [event.column.bindKey || event.column.key]: nextValue,
1760
- };
1761
- if (editableConfig.editor === 'select') {
1762
- nextRow[event.column.key] =
1763
- this.resolveEditableColumnOptions(event.column.key).find((option) => String(option.value ?? '').trim() === nextValue)?.label ?? '';
1764
- }
1765
- this.tableRowsSignal.update((rows) => rows.map((row) => (String(row['id'] ?? '').trim() === rowId ? nextRow : row)));
1766
- if (editableConfig.api) {
1767
- this.saveEditableColumnChange(editableConfig, nextRow);
1768
- }
1769
- }
1770
- onEditableCellActivate(row, columnKey, event) {
1771
- event.stopPropagation();
1772
- const rowId = String(row['id'] ?? '').trim();
1773
- const key = String(columnKey || '').trim();
1774
- if (!rowId || !key) {
1775
- return;
1776
- }
1777
- this.activeEditableCellSignal.set({ rowId, columnKey: key });
1778
- }
1779
- isEditableCellActive(row, columnKey) {
1780
- const active = this.activeEditableCellSignal();
1781
- return !!active && active.rowId === String(row['id'] ?? '').trim() && active.columnKey === String(columnKey || '').trim();
1782
- }
1783
- resolveEditableDisplayValue(row, column) {
1784
- const rawValue = this.getCellValue(row, column.bindKey || column.key);
1785
- if (rawValue === null || rawValue === undefined || rawValue === '') {
1786
- return '—';
1787
- }
1788
- if (column.editor === 'select') {
1789
- const value = String(rawValue).trim();
1790
- const option = this.resolveEditableColumnOptions(column.key).find((item) => String(item.value ?? '').trim() === value);
1791
- return String(option?.label || rawValue).trim() || '—';
1792
- }
1793
- return String(rawValue);
1794
- }
1795
- getCellValue(row, column) {
1796
- return row?.[column];
1797
- }
1798
- runPrintAction(action, row) {
1799
- const template = String(action.template || (action.id === 'print-certificate' ? 'print-certificate' : '')).trim();
1800
- const recordId = String(row['id'] || '').trim();
1801
- if (template && recordId) {
1802
- const printWindow = this.actionBus.openPrintWindow();
1803
- const entity = encodeURIComponent(this.resolveTableSource());
1804
- const id = encodeURIComponent(recordId);
1805
- const actionId = encodeURIComponent(template);
1806
- this.host?.callApi({
1807
- api: `/api/crud/${entity}/${id}/action/${actionId}`,
1808
- method: 'GET',
1809
- }).subscribe({
1810
- next: (response) => {
1811
- const html = String(response?.data?.html || '').trim();
1812
- const title = String(response?.data?.title || action.label || 'Друк');
1813
- if (!html) {
1814
- printWindow?.close();
1815
- this.notify('Шаблон друку порожній', 'error');
1816
- return;
1817
- }
1818
- const config = { html, title };
1819
- if (printWindow)
1820
- this.actionBus.printInWindow(printWindow, config);
1821
- else
1822
- this.actionBus.print(config);
1823
- },
1824
- error: (error) => {
1825
- printWindow?.close();
1826
- this.notify(error?.error?.message || 'Не вдалося завантажити сертифікат', 'error');
1827
- },
1828
- });
1829
- return;
1830
- }
1831
- const payload = this.resolveTemplatePayload(action.payload || {}, row);
1832
- const barcode = String(payload['barcode'] ?? row['barcode'] ?? '').trim();
1833
- if (!barcode) {
1834
- this.notify('У рядку немає штрихкоду', 'warning');
1835
- return;
1836
- }
1837
- const title = String(payload['title'] ?? row['name'] ?? this.tab?.title ?? 'Штрихкод').trim() || 'Штрихкод';
1838
- this.barcodePrintService.printBarcode({ barcode, title });
1839
- }
1840
- closeFormAction() {
1841
- this.activeFormTargetSignal.set(null);
1842
- }
1843
- refreshTable() {
1844
- this.loadTable();
1845
- }
1846
- onFormCompleted(response) {
1847
- this.refreshAfterFormCompleted = true;
1848
- this.applyFormResultToRow(response);
1849
- this.editableColumnsLoaded = false;
1850
- this.editableColumnOptionsSignal.set({});
1851
- this.loadEditableColumnOptions(true);
1852
- }
1853
- onFormClosed() {
1854
- this.activeFormTargetSignal.set(null);
1855
- this.activeEditableCellSignal.set(null);
1856
- if (!this.refreshAfterFormCompleted)
1857
- return;
1858
- this.refreshAfterFormCompleted = false;
1859
- this.loadTable();
1860
- }
1861
- applyFormResultToRow(response) {
1862
- const target = this.activeFormTargetSignal();
1863
- if (!target || !response || typeof response !== 'object')
1864
- return;
1865
- const responseData = response.data;
1866
- if (!responseData || typeof responseData !== 'object')
1867
- return;
1868
- const data = responseData;
1869
- const warehouse = data['warehouse'];
1870
- const warehouseData = warehouse && typeof warehouse === 'object'
1871
- ? warehouse
1872
- : null;
1873
- const warehouseId = String(warehouseData?.['id'] ?? data['warehouseId'] ?? '').trim();
1874
- const warehouseItemId = String(data['id'] ?? '').trim();
1875
- if (!warehouseId || !warehouseItemId)
1876
- return;
1877
- this.tableRowsSignal.update((rows) => rows.map((row) => {
1878
- if (String(row['id'] ?? '').trim() !== target.recordId)
1879
- return row;
1880
- return {
1881
- ...row,
1882
- warehouse_id: warehouseId,
1883
- warehouse_item_id: warehouseItemId,
1884
- warehouse_position: String(warehouseData?.['name'] ?? data['name'] ?? '').trim(),
1885
- };
1886
- }));
1887
- }
1888
- loadTable() {
1889
- const table = this.resolveTableSource();
1890
- this.isTableLoadingSignal.set(true);
1891
- this.tableRowsSignal.set([]);
1892
- this.tablePaginationSignal.set({ page: this.pageIndex, pageSize: this.pageSize, total: 0 });
1893
- if (!table) {
1894
- this.tableColumnsSignal.set([]);
1895
- this.isTableLoadingSignal.set(false);
1896
- return;
1897
- }
1898
- this.tableViewsService.getTableView(table, {
1899
- page: this.pageIndex,
1900
- size: this.pageSize,
1901
- sortField: this.sortField,
1902
- sortDirection: this.sortDirection,
1903
- filters: this.resolveTableFilters(),
1904
- }).subscribe({
1905
- next: (view) => {
1906
- const rows = this.normalizeTableRows(view);
1907
- this.tableViewColumns = view.columns || [];
1908
- this.tableHeaderActionsSignal.set(view.actions?.header?.custom || []);
1909
- this.tableColumnsSignal.set(this.normalizeTableColumns(this.tableViewColumns));
1910
- this.tableRowsSignal.set(rows);
1911
- this.activeEditableCellSignal.set(null);
1912
- this.loadEditableColumnOptions();
1913
- this.pageIndex = view.pagination?.page ?? this.pageIndex;
1914
- this.pageSize = view.pagination?.size ?? this.pageSize;
1915
- this.tablePaginationSignal.set({
1916
- page: this.pageIndex,
1917
- pageSize: this.pageSize,
1918
- total: view.pagination?.totalElements ?? rows.length,
1919
- });
1920
- this.isTableLoadingSignal.set(false);
1921
- },
1922
- error: (error) => {
1923
- this.tableRowsSignal.set([]);
1924
- this.tableHeaderActionsSignal.set([]);
1925
- this.tablePaginationSignal.set({ page: this.pageIndex, pageSize: this.pageSize, total: 0 });
1926
- this.activeEditableCellSignal.set(null);
1927
- this.isTableLoadingSignal.set(false);
1928
- this.notify(error?.error?.message || 'Не вдалося завантажити таблицю', 'error');
1929
- },
1930
- });
1931
- }
1932
- runApiAction(config) {
1933
- const api = String(config.api || '').trim();
1934
- if (!api) {
1935
- return;
1936
- }
1937
- this.isTableActionRunningSignal.set(true);
1938
- this.host?.callApi({
1939
- api,
1940
- method: config.method || 'POST',
1941
- title: config.title,
1942
- text: config.text,
1943
- submit: typeof config.submit === 'string' ? config.submit : undefined,
1944
- danger: config.danger,
1945
- payload: config.payload,
1946
- }).subscribe({
1947
- next: () => {
1948
- this.notify(config.successMessage, 'success');
1949
- this.isTableActionRunningSignal.set(false);
1950
- this.loadTable();
1951
- this.eventBus.emit(ENTITY_UPDATED, {
1952
- source: 'card-table.row-action',
1953
- });
1954
- },
1955
- error: (error) => {
1956
- this.isTableActionRunningSignal.set(false);
1957
- this.notify(error?.error?.message || config.errorMessage, 'error');
1958
- },
1959
- });
1960
- }
1961
- runFormAction(action, row) {
1962
- const formId = String(action.formId || '').trim();
1963
- const recordId = String(row['id'] || '').trim();
1964
- if (!formId || !recordId) {
1965
- this.notify('Не вдалося відкрити форму редагування', 'warning');
1966
- return;
1967
- }
1968
- const actionPayload = this.resolveTemplatePayload(action.payload || {}, row);
1969
- this.activeFormTargetSignal.set({
1970
- formId,
1971
- recordId,
1972
- initialValue: { ...row, ...actionPayload },
1973
- });
1974
- }
1975
- runRowEditAction(row) {
1976
- const recordId = String(row['id'] || '').trim();
1977
- const formId = this.resolveTableSource();
1978
- if (!recordId || !formId) {
1979
- return;
1980
- }
1981
- this.activeFormTargetSignal.set({
1982
- formId,
1983
- recordId,
1984
- initialValue: { ...row },
1985
- });
1986
- }
1987
- runHeaderFormAction(action) {
1988
- const formId = String(action.formId || '').trim();
1989
- if (!formId) {
1990
- return;
1991
- }
1992
- this.activeFormTargetSignal.set({
1993
- formId,
1994
- recordId: '',
1995
- initialValue: this.resolveTemplatePayload(action.payload || {}, this.data || {}),
1996
- });
1997
- }
1998
- normalizeTableColumns(columns) {
1999
- const editableColumns = this.resolveEditableColumnsConfig();
2000
- return (columns || [])
2001
- .filter((column) => !column.hidden)
2002
- .map((column) => {
2003
- const editableConfig = editableColumns.find((item) => item.key === column.id);
2004
- if (!editableConfig) {
2005
- return {
2006
- key: column.id,
2007
- labelKey: column.label,
2008
- sortable: true,
2009
- type: column.type,
2010
- width: column.width,
2011
- link: column.link,
2012
- };
2013
- }
2014
- return {
2015
- key: column.id,
2016
- labelKey: column.label,
2017
- sortable: true,
2018
- type: column.type,
2019
- width: column.width,
2020
- link: column.link,
2021
- editable: true,
2022
- editableCondition: this.resolveEditableCondition(),
2023
- bindKey: editableConfig.bindKey || column.sourceColumn || column.dataKey || column.id,
2024
- editor: editableConfig.editor,
2025
- options: this.resolveEditableColumnOptions(column.id),
2026
- includeBlankOption: editableConfig.includeBlankOption,
2027
- blankOptionLabel: editableConfig.blankOptionLabel,
2028
- placeholder: editableConfig.placeholder,
2029
- inputType: editableConfig.inputType,
2030
- };
2031
- });
2032
- }
2033
- normalizeTableRows(view) {
2034
- const columns = view.columns || [];
2035
- const hiddenRowActions = new Set(Array.isArray(this.tab?.props?.['hiddenRowActions'])
2036
- ? this.tab.props['hiddenRowActions'].map((id) => String(id || '').trim()).filter(Boolean)
2037
- : []);
2038
- const rowActions = (view.actions?.row?.custom || []).filter((action) => !hiddenRowActions.has(action.id));
2039
- const configuredRowActions = view.actions?.row || {};
2040
- const defaultRowActions = {
2041
- edit: view.viewActions?.edit !== false && configuredRowActions.edit !== false,
2042
- editCondition: configuredRowActions.edit && typeof configuredRowActions.edit === 'object'
2043
- ? configuredRowActions.edit.condition
2044
- : undefined,
2045
- delete: view.viewActions?.delete !== false && configuredRowActions.delete !== false,
2046
- deleteCondition: configuredRowActions.delete && typeof configuredRowActions.delete === 'object'
2047
- ? configuredRowActions.delete.condition
2048
- : undefined,
2049
- };
2050
- return (view.rows || []).map((row) => this.toTableRowData(row, columns, rowActions, defaultRowActions));
2051
- }
2052
- toTableRowData(row, columns, rowActions = [], configuredActions = {}) {
2053
- const baseRow = Array.isArray(row)
2054
- ? columns.reduce((acc, column, index) => {
2055
- acc[column.id] = row[index] ?? '';
2056
- return acc;
2057
- }, { id: String(row[0] ?? '') })
2058
- : {
2059
- ...row,
2060
- id: String(row['id'] ?? ''),
2061
- };
2062
- if (this.resolveTableSource() === 'invoice-lines' && String(baseRow['line_id'] || '').trim()) {
2063
- baseRow['id'] = String(baseRow['line_id']).trim();
2064
- }
2065
- if (!Array.isArray(baseRow['actions']) && rowActions.length) {
2066
- baseRow['actions'] = rowActions.map((action) => ({ ...action }));
2067
- }
2068
- if (!Array.isArray(baseRow['actions'])) {
2069
- const actions = [];
2070
- const editAllowed = configuredActions.edit !== false
2071
- && (this.isEditEnabled() || evaluateCondition(configuredActions.editCondition, baseRow));
2072
- if (editAllowed) {
2073
- actions.push({
2074
- id: 'edit',
2075
- label: 'Редагувати',
2076
- type: ACTION_FORM,
2077
- formId: this.resolveTableSource(),
2078
- });
2079
- }
2080
- const deleteAllowed = configuredActions.delete !== false
2081
- && (this.isDeleteEnabled(baseRow) || evaluateCondition(configuredActions.deleteCondition, baseRow));
2082
- if (deleteAllowed) {
2083
- actions.push({
2084
- id: 'delete',
2085
- label: 'Видалити',
2086
- type: ACTION_API,
2087
- api: `/api/${this.resolveTableSource()}/{{id}}`,
2088
- method: 'DELETE',
2089
- danger: true,
2090
- payload: { id: '{{id}}' },
2091
- });
2092
- }
2093
- if (actions.length)
2094
- baseRow['actions'] = actions;
2095
- }
2096
- return baseRow;
2097
- }
2098
- saveEditableColumnChange(editableConfig, row) {
2099
- const api = String(editableConfig.api || '').trim();
2100
- if (!api) {
2101
- return;
2102
- }
2103
- const payload = this.resolveEditableColumnPayload(editableConfig, row);
2104
- if (!payload) {
2105
- return;
2106
- }
2107
- this.isTableActionRunningSignal.set(true);
2108
- this.host?.callApi({
2109
- api,
2110
- method: editableConfig.method || 'PATCH',
2111
- payload,
2112
- }).subscribe({
2113
- next: () => {
2114
- this.isTableActionRunningSignal.set(false);
2115
- this.notify(editableConfig.successMessage || 'Збережено', 'success');
2116
- this.loadTable();
2117
- this.eventBus.emit(ENTITY_UPDATED, {
2118
- source: 'card-table.editable-cell',
2119
- });
2120
- },
2121
- error: (error) => {
2122
- this.isTableActionRunningSignal.set(false);
2123
- this.notify(error?.error?.message || editableConfig.errorMessage || 'Не вдалося зберегти зміни', 'error');
2124
- },
2125
- });
2126
- }
2127
- loadEditableColumnOptions(force = false) {
2128
- if (this.editableColumnsLoaded && !force) {
2129
- return;
2130
- }
2131
- const editableColumns = this.resolveEditableColumnsConfig();
2132
- const columnsNeedingOptions = editableColumns.filter((column) => column.optionsSource === 'warehouses' || column.optionsSource === 'warehouse-items');
2133
- if (!columnsNeedingOptions.length) {
2134
- this.editableColumnsLoaded = true;
2135
- return;
2136
- }
2137
- this.editableColumnsLoaded = true;
2138
- this.warehouseService.getWarehouses().subscribe({
2139
- next: (response) => {
2140
- const warehouses = response.items ?? [];
2141
- const warehouseOptions = warehouses
2142
- .map((warehouse) => ({
2143
- value: warehouse.id ? String(warehouse.id) : null,
2144
- label: String(warehouse.name || warehouse.labelName || warehouse.id || 'Склад').trim() || 'Склад',
2145
- }))
2146
- .filter((option) => !!option.value);
2147
- const loadWarehouseItemOptions = (done) => {
2148
- if (!warehouses.length) {
2149
- done([]);
2150
- return;
2151
- }
2152
- const options = [];
2153
- let remaining = warehouses.length;
2154
- for (const warehouse of warehouses) {
2155
- const warehouseId = String(warehouse.id || '').trim();
2156
- if (!warehouseId) {
2157
- remaining -= 1;
2158
- continue;
2159
- }
2160
- this.warehouseService.getWarehouseItems(warehouseId).subscribe({
2161
- next: (items) => {
2162
- for (const item of items ?? []) {
2163
- options.push({
2164
- value: item.id ? String(item.id) : null,
2165
- label: [
2166
- String(warehouse.labelName || warehouse.name || warehouse.id || '').trim(),
2167
- String(item.name || item.id || '').trim(),
2168
- ]
2169
- .filter(Boolean)
2170
- .join(' / '),
2171
- });
2172
- }
2173
- remaining -= 1;
2174
- if (remaining <= 0) {
2175
- done(options.filter((option) => !!option.value));
2176
- }
2177
- },
2178
- error: () => {
2179
- remaining -= 1;
2180
- if (remaining <= 0) {
2181
- done(options.filter((option) => !!option.value));
2182
- }
2183
- },
2184
- });
2185
- }
2186
- };
2187
- const applyOptions = (warehouseItemOptions = []) => {
2188
- this.editableColumnOptionsSignal.set(columnsNeedingOptions.reduce((acc, column) => {
2189
- acc[column.key] = column.optionsSource === 'warehouses' ? warehouseOptions : warehouseItemOptions;
2190
- return acc;
2191
- }, {}));
2192
- this.tableColumnsSignal.set(this.normalizeTableColumns(this.tableViewColumns));
2193
- };
2194
- if (!columnsNeedingOptions.some((column) => column.optionsSource === 'warehouse-items')) {
2195
- applyOptions();
2196
- return;
2197
- }
2198
- loadWarehouseItemOptions(applyOptions);
2199
- },
2200
- error: () => {
2201
- this.editableColumnOptionsSignal.set({});
2202
- this.tableColumnsSignal.set(this.normalizeTableColumns(this.tableViewColumns));
2203
- },
2204
- });
2205
- }
2206
- resolveEditableColumnsConfig() {
2207
- const value = this.tab?.props?.['editableColumns'];
2208
- return Array.isArray(value)
2209
- ? value.filter((item) => !!item && typeof item === 'object' && typeof item.key === 'string')
2210
- : [];
2211
- }
2212
- resolveEditableColumnPayload(editableConfig, row) {
2213
- const baseRow = {
2214
- ...row,
2215
- id: String(row['id'] ?? '').trim(),
2216
- };
2217
- const payloadConfig = editableConfig.payload && typeof editableConfig.payload === 'object' ? editableConfig.payload : {};
2218
- const payload = this.resolveTemplatePayload(payloadConfig, {
2219
- ...this.data,
2220
- ...baseRow,
2221
- });
2222
- if (!Object.keys(payload).length) {
2223
- return baseRow;
2224
- }
2225
- return {
2226
- ...baseRow,
2227
- ...payload,
2228
- };
2229
- }
2230
- resolveEditableColumnOptions(columnKey) {
2231
- return this.editableColumnOptionsSignal()[columnKey] || [];
2232
- }
2233
- isEditEnabled() {
2234
- const value = this.tab?.props?.['edit'];
2235
- const enabled = value === true || String(value).trim().toLowerCase() === 'true';
2236
- if (!enabled) {
2237
- return false;
2238
- }
2239
- return evaluateCondition(String(this.tab?.props?.['editCondition'] || '').trim(), this.data || {});
2240
- }
2241
- isDeleteEnabled(row) {
2242
- const value = this.tab?.props?.['delete'];
2243
- const enabled = value === true || String(value).trim().toLowerCase() === 'true';
2244
- if (!enabled)
2245
- return false;
2246
- return evaluateCondition(String(this.tab?.props?.['deleteCondition'] || '').trim(), row);
2247
- }
2248
- resolveTableSource() {
2249
- return this.resolveTemplateString(String(this.tab?.props?.['table'] || '').trim(), this.data || {});
2250
- }
2251
- resolveTableFilters() {
2252
- return {
2253
- ...this.resolveFiltersObject(this.tab?.props?.['initialFilters']),
2254
- ...this.resolveFiltersObject(this.tab?.props?.['filter']),
2255
- };
2256
- }
2257
- resolveFiltersObject(filter) {
2258
- if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
2259
- return {};
2260
- }
2261
- return this.resolveTemplatePayload(filter, this.data || {});
2262
- }
2263
- resolveTemplatePayload(payload, source) {
2264
- return Object.entries(payload || {}).reduce((acc, [key, value]) => {
2265
- const next = this.resolveTemplateValue(value, source);
2266
- if (next !== null && next !== undefined && next !== '') {
2267
- acc[key] = next;
2268
- }
2269
- return acc;
2270
- }, {});
2271
- }
2272
- resolveTemplateString(value, source) {
2273
- return String(value || '').replace(/\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g, (_match, key) => {
2274
- const next = String(key).split('.').reduce((current, part) => {
2275
- if (current == null || typeof current !== 'object')
2276
- return undefined;
2277
- return current[part];
2278
- }, source);
2279
- return next == null || next === '' ? '' : encodeURIComponent(String(next));
2280
- });
2281
- }
2282
- resolveTemplateValue(value, source) {
2283
- if (typeof value !== 'string') {
2284
- return value;
2285
- }
2286
- const raw = value.trim();
2287
- const match = raw.match(/^\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}$/);
2288
- if (match) {
2289
- return String(match[1]).split('.').reduce((current, part) => {
2290
- if (current == null || typeof current !== 'object')
2291
- return undefined;
2292
- return current[part];
2293
- }, source) ?? '';
2294
- }
2295
- return value;
2296
- }
2297
- resolvePageSize() {
2298
- const value = Number(this.tab?.props?.['pageSize'] ?? 10);
2299
- return Number.isFinite(value) && value > 0 ? Math.floor(value) : 10;
2300
- }
2301
- resolveEditCondition() {
2302
- const condition = String(this.tab?.props?.['editCondition'] || '').trim();
2303
- return condition || undefined;
2304
- }
2305
- resolveEditableCondition() {
2306
- const condition = String(this.tab?.props?.['editableCondition'] || '').trim();
2307
- return condition || this.resolveEditCondition();
2308
- }
2309
- notify(message, variant) {
2310
- this.host?.notify(message, variant, 2800);
2311
- }
2312
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2313
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardTableComponent, isStandalone: true, selector: "app-card-table", inputs: { tab: "tab", data: "data" }, usesOnChanges: true, ngImport: i0, template: "<section class=\"space-y-4\">\n @if (tableHeaderActions.length) {\n <div class=\"flex justify-end gap-2\">\n @for (action of tableHeaderActions; track action.id) {\n <ui-button\n [variant]=\"action.variant || 'ghost'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n type=\"button\"\n (click)=\"onHeaderAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n\n <app-ui-table\n [data]=\"tableData()\"\n [columns]=\"tableColumns\"\n [isLoading]=\"isTableLoading\"\n [disablePaging]=\"disablePaging\"\n [rowClickable]=\"rowClickable\"\n (pageChange)=\"onTablePageChange($event)\"\n (actionClick)=\"onTableAction($event)\"\n (rowClick)=\"onTableRowClick($event)\"\n (cellChange)=\"onTableCellChange($event)\"\n ></app-ui-table>\n\n @if (activeFormTarget(); as target) {\n <form-modal\n [isOpen]=\"true\"\n [formId]=\"target.formId\"\n [recordId]=\"target.recordId\"\n [initialValue]=\"target.initialValue\"\n (closed)=\"onFormClosed()\"\n (completed)=\"onFormCompleted($event)\"\n ></form-modal>\n }\n</section>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiTableComponent, selector: "app-ui-table", inputs: ["columns", "isLoading", "data", "disablePaging", "rowClickable", "imageResolver"], outputs: ["pageChange", "rowClick", "actionClick", "cellChange"] }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "component", type: FormModalComponent, selector: "form-modal", inputs: ["formId", "recordId", "size", "isOpen", "cancelLabel", "initialValue"], outputs: ["closed", "completed"] }] });
2314
- }
2315
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardTableComponent, decorators: [{
2316
- type: Component,
2317
- args: [{ selector: 'app-card-table', standalone: true, imports: [
2318
- CommonModule,
2319
- UiTableComponent,
2320
- UiButtonComponent,
2321
- FormModalComponent,
2322
- ], template: "<section class=\"space-y-4\">\n @if (tableHeaderActions.length) {\n <div class=\"flex justify-end gap-2\">\n @for (action of tableHeaderActions; track action.id) {\n <ui-button\n [variant]=\"action.variant || 'ghost'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n type=\"button\"\n (click)=\"onHeaderAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n\n <app-ui-table\n [data]=\"tableData()\"\n [columns]=\"tableColumns\"\n [isLoading]=\"isTableLoading\"\n [disablePaging]=\"disablePaging\"\n [rowClickable]=\"rowClickable\"\n (pageChange)=\"onTablePageChange($event)\"\n (actionClick)=\"onTableAction($event)\"\n (rowClick)=\"onTableRowClick($event)\"\n (cellChange)=\"onTableCellChange($event)\"\n ></app-ui-table>\n\n @if (activeFormTarget(); as target) {\n <form-modal\n [isOpen]=\"true\"\n [formId]=\"target.formId\"\n [recordId]=\"target.recordId\"\n [initialValue]=\"target.initialValue\"\n (closed)=\"onFormClosed()\"\n (completed)=\"onFormCompleted($event)\"\n ></form-modal>\n }\n</section>\n" }]
2323
- }], propDecorators: { tab: [{
2324
- type: Input,
2325
- args: [{ required: true }]
2326
- }], data: [{
2327
- type: Input
2328
- }] } });
2329
-
2330
1601
  /**
2331
1602
  * Generated bundle index. Do not edit.
2332
1603
  */
2333
1604
 
2334
- export { ACTION_API, ACTION_FORM, ACTION_PRINT, CARD_ACTION_COMPLETED, CARD_ACTION_FINISHED, CARD_ACTION_REQUESTED, CARD_ACTION_STARTED, CardComponent, CardCurrentUserService, CardDictionaryService, CardImageService, CardSectionHeaderComponent, CardTableBarcodeService, CardTableComponent, CardTableDataService, CardTablePrintService, CardTableWarehouseService, DocumentsComponent, ENTITY_UPDATED, HistoryTabComponent, ImagesViewerComponent, UI_CARD_API_BASE_URL, UI_CARD_TAB_REGISTRY, UI_ENTITY_CARD_HOST, UiEntityDocumentsService, cardApiParams, cardApiResourceUrl, normalizeActionType };
1605
+ export { CARD_ACTION_COMPLETED, CARD_ACTION_FINISHED, CARD_ACTION_REQUESTED, CARD_ACTION_STARTED, CardComponent, CardCurrentUserService, CardDictionaryService, CardImageService, CardSectionHeaderComponent, DocumentsComponent, ENTITY_UPDATED, HistoryTabComponent, ImagesViewerComponent, StandardTableComponent, UI_CARD_API_BASE_URL, UI_CARD_TAB_REGISTRY, UI_ENTITY_CARD_HOST, UiEntityDocumentsService, cardApiParams, cardApiResourceUrl, normalizeCardConfig, resolveCardConfig };
2335
1606
  //# sourceMappingURL=maro-tech-card.mjs.map