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