@maro-tech/card 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/maro-tech-card.mjs +403 -1147
- package/fesm2022/maro-tech-card.mjs.map +1 -1
- package/package.json +1 -1
- package/types/maro-tech-card.d.ts +84 -157
|
@@ -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
|
|
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 {
|
|
7
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
8
8
|
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
|
9
|
-
import { shareReplay, forkJoin, switchMap, timer, of
|
|
10
|
-
import {
|
|
9
|
+
import { map, shareReplay, forkJoin, 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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
63
|
-
&& (!context.recordId || context
|
|
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 },
|
|
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 }] }],
|
|
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
|
-
|
|
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
|
-
|
|
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?.('[
|
|
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
|
|
196
|
-
|
|
197
|
-
if (!actions.length)
|
|
243
|
+
const raw = actionElement?.getAttribute('data-card-action');
|
|
244
|
+
if (!raw)
|
|
198
245
|
return null;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
|
213
|
-
}], propDecorators: {
|
|
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
|
-
|
|
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.
|
|
300
|
-
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
424
|
-
|
|
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(
|
|
435
|
-
|
|
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,266 @@ 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: {
|
|
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: {
|
|
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 TableComponent {
|
|
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
|
+
pageSizeOptionsSignal = signal([10, 20, 30], ...(ngDevMode ? [{ debugName: "pageSizeOptionsSignal" }] : /* istanbul ignore next */ []));
|
|
520
|
+
headerActionsSignal = signal([], ...(ngDevMode ? [{ debugName: "headerActionsSignal" }] : /* istanbul ignore next */ []));
|
|
521
|
+
loadingSignal = signal(false, ...(ngDevMode ? [{ debugName: "loadingSignal" }] : /* istanbul ignore next */ []));
|
|
522
|
+
errorSignal = signal('', ...(ngDevMode ? [{ debugName: "errorSignal" }] : /* 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
|
+
if (view.pagination?.sizes?.length)
|
|
620
|
+
this.pageSizeOptionsSignal.set(view.pagination.sizes);
|
|
621
|
+
this.page = view.pagination?.page ?? this.page;
|
|
622
|
+
this.pageSize = view.pagination?.size ?? this.pageSize;
|
|
623
|
+
this.paginationSignal.set({
|
|
624
|
+
page: this.page,
|
|
625
|
+
pageSize: this.pageSize,
|
|
626
|
+
total: view.pagination?.totalElements ?? view.pagination?.total ?? this.rowsSignal().length,
|
|
627
|
+
});
|
|
628
|
+
this.loadingSignal.set(false);
|
|
629
|
+
},
|
|
630
|
+
error: (error) => {
|
|
631
|
+
this.rowsSignal.set([]);
|
|
632
|
+
this.columnsSignal.set([]);
|
|
633
|
+
this.headerActionsSignal.set([]);
|
|
634
|
+
this.errorSignal.set(error?.error?.message || 'Не вдалося завантажити таблицю');
|
|
635
|
+
this.loadingSignal.set(false);
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
setColumns() {
|
|
640
|
+
const editable = this.editableColumns();
|
|
641
|
+
this.columnsSignal.set(this.sourceColumns.filter((column) => !column['hidden']).map((column) => {
|
|
642
|
+
const key = String(column['id'] || '');
|
|
643
|
+
const config = editable.find((item) => item.key === key);
|
|
644
|
+
return {
|
|
645
|
+
key,
|
|
646
|
+
labelKey: String(column['label'] || key),
|
|
647
|
+
type: column['type'],
|
|
648
|
+
width: column['width'],
|
|
649
|
+
sortable: true,
|
|
650
|
+
link: column['link'],
|
|
651
|
+
editable: !!config,
|
|
652
|
+
editableCondition: String(this.config?.props?.['editableCondition'] || config?.['condition'] || ''),
|
|
653
|
+
editor: config?.editor,
|
|
654
|
+
bindKey: config?.bindKey || key,
|
|
655
|
+
options: config?.options || [],
|
|
656
|
+
data: config?.data,
|
|
657
|
+
dataParams: config?.dataParams,
|
|
658
|
+
placeholder: config?.placeholder,
|
|
659
|
+
};
|
|
660
|
+
}));
|
|
661
|
+
}
|
|
662
|
+
rowsFrom(view, columns) {
|
|
663
|
+
const configured = view.actions?.row?.custom || [];
|
|
664
|
+
return (view.rows || []).map((raw) => {
|
|
665
|
+
const row = Array.isArray(raw)
|
|
666
|
+
? columns.reduce((result, column, index) => {
|
|
667
|
+
result[String(column['id'])] = raw[index] ?? '';
|
|
668
|
+
return result;
|
|
669
|
+
}, { id: String(raw[0] || '') })
|
|
670
|
+
: { ...(raw || {}) };
|
|
671
|
+
row['id'] = String(row['id'] || '');
|
|
672
|
+
if (!Array.isArray(row['actions'])) {
|
|
673
|
+
const actions = [...configured];
|
|
674
|
+
const actionIds = new Set(actions.map((action) => String(action.id || '').trim()).filter(Boolean));
|
|
675
|
+
if (view.viewActions?.edit !== false && view.actions?.row?.edit !== false && this.editEnabled() && !actionIds.has('edit')) {
|
|
676
|
+
actions.push({ id: 'edit', label: 'Редагувати', type: 'form', formId: this.tableName() });
|
|
677
|
+
}
|
|
678
|
+
if (view.viewActions?.delete !== false && view.actions?.row?.delete !== false && !actionIds.has('delete')) {
|
|
679
|
+
actions.push({ id: 'delete', label: 'Видалити', type: 'submit', api: `/api/crud/${this.tableName()}/${row['id']}`, method: 'DELETE' });
|
|
680
|
+
}
|
|
681
|
+
if (actions.length)
|
|
682
|
+
row['actions'] = actions;
|
|
683
|
+
}
|
|
684
|
+
return row;
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
tableName() {
|
|
688
|
+
return String(this.config?.props?.['table'] || this.config?.props?.['entity'] || '').trim();
|
|
689
|
+
}
|
|
690
|
+
editableColumns() {
|
|
691
|
+
const value = this.config?.props?.['editableColumns'];
|
|
692
|
+
return Array.isArray(value)
|
|
693
|
+
? value.filter((item) => !!item && typeof item === 'object' && !!String(item.key || '').trim())
|
|
694
|
+
: [];
|
|
695
|
+
}
|
|
696
|
+
tableEndpoint(table) {
|
|
697
|
+
const params = new URLSearchParams({
|
|
698
|
+
page: String(this.page),
|
|
699
|
+
size: String(this.pageSize),
|
|
700
|
+
sortField: this.sortField,
|
|
701
|
+
sortDirection: this.sortDirection,
|
|
702
|
+
filters: JSON.stringify(this.config?.props?.['initialFilters'] || this.config?.props?.['filter'] || {}),
|
|
703
|
+
});
|
|
704
|
+
return `/api/crud/${encodeURIComponent(table)}?${params.toString()}`;
|
|
705
|
+
}
|
|
706
|
+
editEnabled() {
|
|
707
|
+
return this.config?.props?.['edit'] !== false && this.config?.props?.['edit'] !== 'false';
|
|
708
|
+
}
|
|
709
|
+
numberProp(key, fallback) {
|
|
710
|
+
const value = Number(this.config?.props?.[key]);
|
|
711
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;
|
|
712
|
+
}
|
|
713
|
+
resolveObject(value, row) {
|
|
714
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, this.resolveValue(item, row)]));
|
|
715
|
+
}
|
|
716
|
+
resolve(value, row) {
|
|
717
|
+
return String(this.resolveValue(value, row));
|
|
718
|
+
}
|
|
719
|
+
resolveValue(value, row) {
|
|
720
|
+
if (typeof value === 'string') {
|
|
721
|
+
return value.replace(/\{\{([a-zA-Z0-9_.-]+)\}\}/g, (_match, path) => {
|
|
722
|
+
const result = String(path).split('.').reduce((current, key) => current && typeof current === 'object' ? current[key] : undefined, row);
|
|
723
|
+
return result == null ? '' : String(result);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (Array.isArray(value))
|
|
727
|
+
return value.map((item) => this.resolveValue(item, row));
|
|
728
|
+
if (value && typeof value === 'object')
|
|
729
|
+
return this.resolveObject(value, row);
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
733
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: TableComponent, 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 [pageSizeOptions]=\"pageSizeOptionsSignal()\"\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: ["pageSizeOptions", "columns", "isLoading", "data", "disablePaging", "rowClickable", "imageResolver"], outputs: ["pageChange", "rowClick", "actionClick", "cellChange"] }] });
|
|
734
|
+
}
|
|
735
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: TableComponent, decorators: [{
|
|
736
|
+
type: Component,
|
|
737
|
+
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 [pageSizeOptions]=\"pageSizeOptionsSignal()\"\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" }]
|
|
738
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
739
|
+
type: Input,
|
|
740
|
+
args: [{ required: true }]
|
|
741
|
+
}], data: [{
|
|
742
|
+
type: Input
|
|
743
|
+
}] } });
|
|
744
|
+
function normalizeTableResponse(response) {
|
|
745
|
+
const raw = (response || {});
|
|
746
|
+
const source = (raw['config'] || raw);
|
|
747
|
+
return {
|
|
748
|
+
...source,
|
|
749
|
+
rows: raw['rows'] ?? raw['items'] ?? source.rows ?? [],
|
|
750
|
+
pagination: {
|
|
751
|
+
page: raw['page'] ?? source.pagination?.page,
|
|
752
|
+
size: raw['size'] ?? source.pagination?.size,
|
|
753
|
+
total: raw['total'] ?? source.pagination?.total,
|
|
754
|
+
totalElements: raw['total'] ?? source.pagination?.totalElements,
|
|
755
|
+
sizes: source.pagination?.sizes,
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
}
|
|
453
759
|
|
|
454
760
|
const UI_CARD_TAB_REGISTRY = new InjectionToken('UI_CARD_TAB_REGISTRY');
|
|
455
761
|
|
|
456
762
|
class UiCardPanelsLayoutComponent {
|
|
457
763
|
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
764
|
tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
461
765
|
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
462
766
|
embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
|
|
@@ -518,20 +822,18 @@ class UiCardPanelsLayoutComponent {
|
|
|
518
822
|
return found.resolveInputs(tab, this.data());
|
|
519
823
|
}
|
|
520
824
|
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: {
|
|
825
|
+
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(() => TableComponent), selector: "app-card-table", inputs: ["config", "data"] }] });
|
|
522
826
|
}
|
|
523
827
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardPanelsLayoutComponent, decorators: [{
|
|
524
828
|
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
|
|
526
|
-
}], propDecorators: {
|
|
829
|
+
args: [{ selector: 'app-ui-card-panels-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, DetailsGridComponent, ImagesComponent, TableComponent], 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" }]
|
|
830
|
+
}], 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
831
|
|
|
528
832
|
class UiCardTabsLayoutComponent {
|
|
529
833
|
registry = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];
|
|
530
834
|
destroyRef = inject(DestroyRef);
|
|
531
835
|
route = inject(ActivatedRoute);
|
|
532
836
|
router = inject(Router);
|
|
533
|
-
entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
|
|
534
|
-
recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
|
|
535
837
|
tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
536
838
|
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
537
839
|
activeTabId = signal('', ...(ngDevMode ? [{ debugName: "activeTabId" }] : /* istanbul ignore next */ []));
|
|
@@ -590,27 +892,25 @@ class UiCardTabsLayoutComponent {
|
|
|
590
892
|
return found.resolveInputs(tab, this.data());
|
|
591
893
|
}
|
|
592
894
|
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: {
|
|
895
|
+
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(() => TableComponent), selector: "app-card-table", inputs: ["config", "data"] }] });
|
|
594
896
|
}
|
|
595
897
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardTabsLayoutComponent, decorators: [{
|
|
596
898
|
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 [
|
|
598
|
-
}], propDecorators: {
|
|
899
|
+
args: [{ selector: 'app-ui-card-tabs-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, ImagesComponent, DetailsGridComponent, TableComponent], 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" }]
|
|
900
|
+
}], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
|
|
599
901
|
|
|
600
902
|
class CardComponent {
|
|
601
|
-
entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
|
|
602
|
-
recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
|
|
603
903
|
tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
604
904
|
data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
605
905
|
layout = input('panels', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
606
906
|
embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
|
|
607
907
|
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: {
|
|
908
|
+
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
909
|
}
|
|
610
910
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardComponent, decorators: [{
|
|
611
911
|
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 [
|
|
613
|
-
}], propDecorators: {
|
|
912
|
+
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" }]
|
|
913
|
+
}], 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
914
|
|
|
615
915
|
const UI_ENTITY_CARD_HOST = new InjectionToken('UI_ENTITY_CARD_HOST');
|
|
616
916
|
|
|
@@ -861,7 +1161,7 @@ class DocumentsComponent {
|
|
|
861
1161
|
plusIcon = Plus;
|
|
862
1162
|
sparkleIcon = Sparkles;
|
|
863
1163
|
deleteIcon = Trash;
|
|
864
|
-
isAdmin = toSignal(inject(CardCurrentUserService).getCurrentUser().pipe(map((user) => String(user.role || '').trim().toUpperCase() === 'ADMIN')), { initialValue: false });
|
|
1164
|
+
isAdmin = toSignal(inject(CardCurrentUserService).getCurrentUser().pipe(map$1((user) => String(user.role || '').trim().toUpperCase() === 'ADMIN')), { initialValue: false });
|
|
865
1165
|
documentsService = inject(UiEntityDocumentsService);
|
|
866
1166
|
dictionaryService = inject(CardDictionaryService);
|
|
867
1167
|
toast = inject(UiToastService);
|
|
@@ -1103,8 +1403,8 @@ class DocumentsComponent {
|
|
|
1103
1403
|
const pendingDocuments = this.documents().filter((document) => this.isRecognitionPending(document));
|
|
1104
1404
|
if (!pendingDocuments.length)
|
|
1105
1405
|
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) => {
|
|
1406
|
+
return forkJoin(pendingDocuments.map((document) => this.documentsService.recognitionStatus(document.id).pipe(catchError(() => of(null))))).pipe(map$1((statuses) => statuses.filter((status) => !!status)));
|
|
1407
|
+
}), map$1((statuses) => {
|
|
1108
1408
|
statuses.forEach((status) => this.upsertDocument(status));
|
|
1109
1409
|
return statuses;
|
|
1110
1410
|
}), takeWhile(() => this.documents().some((document) => this.isRecognitionPending(document)), true), takeUntilDestroyed(this.destroyRef), takeUntil(timer(RECOGNITION_POLLING_TIMEOUT_MS)), finalize(() => {
|
|
@@ -1283,1053 +1583,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
1283
1583
|
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
1584
|
}], 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
1585
|
|
|
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) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[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
1586
|
/**
|
|
2331
1587
|
* Generated bundle index. Do not edit.
|
|
2332
1588
|
*/
|
|
2333
1589
|
|
|
2334
|
-
export {
|
|
1590
|
+
export { CARD_ACTION_COMPLETED, CARD_ACTION_FINISHED, CARD_ACTION_REQUESTED, CARD_ACTION_STARTED, CardComponent, CardCurrentUserService, CardDictionaryService, CardImageService, CardSectionHeaderComponent, DocumentsComponent, ENTITY_UPDATED, HistoryTabComponent, ImagesViewerComponent, TableComponent, UI_CARD_API_BASE_URL, UI_CARD_TAB_REGISTRY, UI_ENTITY_CARD_HOST, UiEntityDocumentsService, cardApiParams, cardApiResourceUrl, normalizeCardConfig, resolveCardConfig };
|
|
2335
1591
|
//# sourceMappingURL=maro-tech-card.mjs.map
|