@mintplayer/ng-spark 0.0.1

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.
@@ -0,0 +1,1889 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, signal, Injectable, inject, Pipe, input, computed, ChangeDetectionStrategy, Component, Directive, model, output, effect, ContentChildren } from '@angular/core';
3
+ import { HttpClient, HttpParams } from '@angular/common/http';
4
+ import { firstValueFrom } from 'rxjs';
5
+ import * as i1 from '@angular/common';
6
+ import { CommonModule, NgTemplateOutlet } from '@angular/common';
7
+ import * as i2 from '@angular/forms';
8
+ import { FormsModule } from '@angular/forms';
9
+ import { Color } from '@mintplayer/ng-bootstrap';
10
+ import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
11
+ import { BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective } from '@mintplayer/ng-bootstrap/grid';
12
+ import { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';
13
+ import { BsButtonTypeDirective } from '@mintplayer/ng-bootstrap/button-type';
14
+ import { BsSelectComponent, BsSelectOption } from '@mintplayer/ng-bootstrap/select';
15
+ import { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';
16
+ import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
17
+ import { BsToggleButtonComponent } from '@mintplayer/ng-bootstrap/toggle-button';
18
+ import { DomSanitizer } from '@angular/platform-browser';
19
+ import { BsTableComponent } from '@mintplayer/ng-bootstrap/table';
20
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
21
+ import * as i2$1 from '@angular/router';
22
+ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
23
+ import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
24
+ import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
25
+ import { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';
26
+ import { BsButtonGroupComponent } from '@mintplayer/ng-bootstrap/button-group';
27
+
28
+ function resolveTranslation(ts, lang) {
29
+ if (!ts)
30
+ return '';
31
+ const language = lang ?? localStorage.getItem('spark-lang') ?? navigator.language?.split('-')[0] ?? 'en';
32
+ return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';
33
+ }
34
+
35
+ /**
36
+ * Flags enum controlling on which pages an attribute should be displayed.
37
+ * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject
38
+ */
39
+ var ShowedOn;
40
+ (function (ShowedOn) {
41
+ ShowedOn[ShowedOn["Query"] = 1] = "Query";
42
+ ShowedOn[ShowedOn["PersistentObject"] = 2] = "PersistentObject";
43
+ })(ShowedOn || (ShowedOn = {}));
44
+ /**
45
+ * Helper function to check if a ShowedOn value includes a specific flag.
46
+ */
47
+ function hasShowedOnFlag(value, flag) {
48
+ if (value === undefined)
49
+ return true; // Default: show on all pages
50
+ // Handle string values from JSON (e.g., "Query, PersistentObject")
51
+ if (typeof value === 'string') {
52
+ const parts = value.split(',').map(s => s.trim());
53
+ const flagName = ShowedOn[flag];
54
+ return parts.includes(flagName);
55
+ }
56
+ // Handle numeric flag values
57
+ return (value & flag) === flag;
58
+ }
59
+
60
+ const SPARK_CONFIG = new InjectionToken('SPARK_CONFIG');
61
+ const defaultSparkConfig = {
62
+ baseUrl: '/spark'
63
+ };
64
+
65
+ var ELookupDisplayType;
66
+ (function (ELookupDisplayType) {
67
+ ELookupDisplayType[ELookupDisplayType["Dropdown"] = 0] = "Dropdown";
68
+ ELookupDisplayType[ELookupDisplayType["Modal"] = 1] = "Modal";
69
+ })(ELookupDisplayType || (ELookupDisplayType = {}));
70
+
71
+ class RetryActionService {
72
+ resolveRetry = null;
73
+ payload = signal(null, ...(ngDevMode ? [{ debugName: "payload" }] : []));
74
+ show(payload) {
75
+ this.payload.set(payload);
76
+ return new Promise(resolve => { this.resolveRetry = resolve; });
77
+ }
78
+ respond(result) {
79
+ this.payload.set(null);
80
+ this.resolveRetry?.(result);
81
+ this.resolveRetry = null;
82
+ }
83
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RetryActionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
84
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RetryActionService, providedIn: 'root' });
85
+ }
86
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RetryActionService, decorators: [{
87
+ type: Injectable,
88
+ args: [{ providedIn: 'root' }]
89
+ }] });
90
+
91
+ class SparkService {
92
+ config = inject(SPARK_CONFIG, { optional: true });
93
+ baseUrl = this.config?.baseUrl ?? '/spark';
94
+ http = inject(HttpClient);
95
+ retryActionService = inject(RetryActionService);
96
+ // Entity Types
97
+ async getEntityTypes() {
98
+ return firstValueFrom(this.http.get(`${this.baseUrl}/types`));
99
+ }
100
+ async getEntityType(id) {
101
+ return firstValueFrom(this.http.get(`${this.baseUrl}/types/${encodeURIComponent(id)}`));
102
+ }
103
+ async getEntityTypeByClrType(clrType) {
104
+ const types = await this.getEntityTypes();
105
+ return types.find(t => t.clrType === clrType);
106
+ }
107
+ // Permissions
108
+ async getPermissions(entityTypeId) {
109
+ return firstValueFrom(this.http.get(`${this.baseUrl}/permissions/${encodeURIComponent(entityTypeId)}`));
110
+ }
111
+ // Queries
112
+ async getQueries() {
113
+ return firstValueFrom(this.http.get(`${this.baseUrl}/queries`));
114
+ }
115
+ async getQuery(id) {
116
+ return firstValueFrom(this.http.get(`${this.baseUrl}/queries/${encodeURIComponent(id)}`));
117
+ }
118
+ async getQueryByName(name) {
119
+ const queries = await this.getQueries();
120
+ return queries.find(q => q.name === name);
121
+ }
122
+ async executeQuery(queryId, sortBy, sortDirection) {
123
+ let params = new HttpParams();
124
+ if (sortBy)
125
+ params = params.set('sortBy', sortBy);
126
+ if (sortDirection)
127
+ params = params.set('sortDirection', sortDirection);
128
+ return firstValueFrom(this.http.get(`${this.baseUrl}/queries/${encodeURIComponent(queryId)}/execute`, { params }));
129
+ }
130
+ async executeQueryByName(queryName) {
131
+ const query = await this.getQueryByName(queryName);
132
+ return query ? this.executeQuery(query.id) : [];
133
+ }
134
+ // Program Units
135
+ async getProgramUnits() {
136
+ return firstValueFrom(this.http.get(`${this.baseUrl}/program-units`));
137
+ }
138
+ // Persistent Objects
139
+ async list(type) {
140
+ return firstValueFrom(this.http.get(`${this.baseUrl}/po/${encodeURIComponent(type)}`));
141
+ }
142
+ async get(type, id) {
143
+ return firstValueFrom(this.http.get(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`));
144
+ }
145
+ async create(type, data) {
146
+ return this.postWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}`, { persistentObject: data });
147
+ }
148
+ async update(type, id, data) {
149
+ return this.putWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, { persistentObject: data });
150
+ }
151
+ async delete(type, id) {
152
+ return this.deleteWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, {});
153
+ }
154
+ // Custom Actions
155
+ async getCustomActions(objectTypeId) {
156
+ return firstValueFrom(this.http.get(`${this.baseUrl}/actions/${encodeURIComponent(objectTypeId)}`));
157
+ }
158
+ async executeCustomAction(objectTypeId, actionName, parent, selectedItems) {
159
+ const body = { parent, selectedItems };
160
+ return this.postWithRetry(`${this.baseUrl}/actions/${encodeURIComponent(objectTypeId)}/${encodeURIComponent(actionName)}`, body);
161
+ }
162
+ // LookupReferences
163
+ async getLookupReferences() {
164
+ return firstValueFrom(this.http.get(`${this.baseUrl}/lookupref`));
165
+ }
166
+ async getLookupReference(name) {
167
+ return firstValueFrom(this.http.get(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`));
168
+ }
169
+ async addLookupReferenceValue(name, value) {
170
+ return firstValueFrom(this.http.post(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`, value));
171
+ }
172
+ async updateLookupReferenceValue(name, key, value) {
173
+ return firstValueFrom(this.http.put(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`, value));
174
+ }
175
+ async deleteLookupReferenceValue(name, key) {
176
+ return firstValueFrom(this.http.delete(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`));
177
+ }
178
+ // Retry Action helpers
179
+ async postWithRetry(url, body) {
180
+ try {
181
+ return await firstValueFrom(this.http.post(url, body));
182
+ }
183
+ catch (error) {
184
+ return this.handleRetryError(error, () => this.postWithRetry(url, body), body);
185
+ }
186
+ }
187
+ async putWithRetry(url, body) {
188
+ try {
189
+ return await firstValueFrom(this.http.put(url, body));
190
+ }
191
+ catch (error) {
192
+ return this.handleRetryError(error, () => this.putWithRetry(url, body), body);
193
+ }
194
+ }
195
+ async deleteWithRetry(url, body) {
196
+ try {
197
+ const hasRetry = body.retryResults && body.retryResults.length > 0;
198
+ return await firstValueFrom(hasRetry
199
+ ? this.http.delete(url, { body })
200
+ : this.http.delete(url));
201
+ }
202
+ catch (error) {
203
+ return this.handleRetryError(error, () => this.deleteWithRetry(url, body), body);
204
+ }
205
+ }
206
+ async handleRetryError(error, retryFn, body) {
207
+ if (error.status !== 449 || error.error?.type !== 'retry-action') {
208
+ throw error;
209
+ }
210
+ const payload = error.error;
211
+ const result = await this.retryActionService.show(payload);
212
+ // Modal dismissed (Escape/X) - Cancel was not an explicit developer option
213
+ if (result.option === 'Cancel' && !payload.options.includes('Cancel')) {
214
+ throw error;
215
+ }
216
+ body.retryResults = [...(body.retryResults || []), result];
217
+ return retryFn();
218
+ }
219
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
220
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkService, providedIn: 'root' });
221
+ }
222
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkService, decorators: [{
223
+ type: Injectable,
224
+ args: [{ providedIn: 'root' }]
225
+ }] });
226
+
227
+ class SparkLanguageService {
228
+ http = inject(HttpClient);
229
+ config = inject(SPARK_CONFIG, { optional: true });
230
+ baseUrl = this.config?.baseUrl ?? '/spark';
231
+ currentLang = signal('en', ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
232
+ translationsMap = signal({}, ...(ngDevMode ? [{ debugName: "translationsMap" }] : []));
233
+ language = this.currentLang.asReadonly();
234
+ languages = signal({}, ...(ngDevMode ? [{ debugName: "languages" }] : []));
235
+ constructor() {
236
+ this.loadCulture();
237
+ this.loadTranslations();
238
+ }
239
+ async loadCulture() {
240
+ const config = await firstValueFrom(this.http.get(`${this.baseUrl}/culture`));
241
+ this.languages.set(config.languages);
242
+ const saved = localStorage.getItem('spark-lang');
243
+ this.currentLang.set(saved ?? config.defaultLanguage);
244
+ }
245
+ async loadTranslations() {
246
+ const t = await firstValueFrom(this.http.get(`${this.baseUrl}/translations`));
247
+ this.translationsMap.set(t);
248
+ }
249
+ setLanguage(lang) {
250
+ this.currentLang.set(lang);
251
+ localStorage.setItem('spark-lang', lang);
252
+ }
253
+ resolve(ts) {
254
+ if (!ts)
255
+ return '';
256
+ const lang = this.currentLang();
257
+ return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? '';
258
+ }
259
+ t(key) {
260
+ const ts = this.translationsMap()[key];
261
+ return this.resolve(ts) || key;
262
+ }
263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
264
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkLanguageService, providedIn: 'root' });
265
+ }
266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkLanguageService, decorators: [{
267
+ type: Injectable,
268
+ args: [{ providedIn: 'root' }]
269
+ }], ctorParameters: () => [] });
270
+
271
+ class TranslateKeyPipe {
272
+ lang = inject(SparkLanguageService);
273
+ transform(key) {
274
+ return this.lang.t(key);
275
+ }
276
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TranslateKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
277
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: TranslateKeyPipe, isStandalone: true, name: "t", pure: false });
278
+ }
279
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TranslateKeyPipe, decorators: [{
280
+ type: Pipe,
281
+ args: [{ name: 't', pure: false, standalone: true }]
282
+ }] });
283
+
284
+ class ResolveTranslationPipe {
285
+ transform(value, fallback) {
286
+ return resolveTranslation(value) || fallback || '';
287
+ }
288
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResolveTranslationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
289
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ResolveTranslationPipe, isStandalone: true, name: "resolveTranslation" });
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ResolveTranslationPipe, decorators: [{
292
+ type: Pipe,
293
+ args: [{ name: 'resolveTranslation', standalone: true, pure: true }]
294
+ }] });
295
+
296
+ class InputTypePipe {
297
+ transform(dataType) {
298
+ switch (dataType) {
299
+ case 'number':
300
+ case 'decimal':
301
+ return 'number';
302
+ case 'boolean':
303
+ return 'checkbox';
304
+ case 'datetime':
305
+ return 'datetime-local';
306
+ case 'date':
307
+ return 'date';
308
+ case 'color':
309
+ return 'color';
310
+ default:
311
+ return 'text';
312
+ }
313
+ }
314
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InputTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
315
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: InputTypePipe, isStandalone: true, name: "inputType" });
316
+ }
317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InputTypePipe, decorators: [{
318
+ type: Pipe,
319
+ args: [{ name: 'inputType', standalone: true, pure: true }]
320
+ }] });
321
+
322
+ class LookupDisplayValuePipe {
323
+ transform(attr, formData, lookupRefOptions) {
324
+ const currentValue = formData[attr.name];
325
+ if (currentValue == null || currentValue === '')
326
+ return '';
327
+ const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
328
+ const options = lookupRef?.values.filter(v => v.isActive) || [];
329
+ const selected = options.find(o => o.key === String(currentValue));
330
+ if (!selected)
331
+ return String(currentValue);
332
+ return resolveTranslation(selected.values) || selected.key;
333
+ }
334
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
335
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayValuePipe, isStandalone: true, name: "lookupDisplayValue" });
336
+ }
337
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayValuePipe, decorators: [{
338
+ type: Pipe,
339
+ args: [{ name: 'lookupDisplayValue', standalone: true, pure: true }]
340
+ }] });
341
+
342
+ class LookupDisplayTypePipe {
343
+ transform(attr, lookupRefOptions) {
344
+ const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
345
+ return lookupRef?.displayType ?? ELookupDisplayType.Dropdown;
346
+ }
347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
348
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayTypePipe, isStandalone: true, name: "lookupDisplayType" });
349
+ }
350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupDisplayTypePipe, decorators: [{
351
+ type: Pipe,
352
+ args: [{ name: 'lookupDisplayType', standalone: true, pure: true }]
353
+ }] });
354
+
355
+ class LookupOptionsPipe {
356
+ transform(attr, lookupRefOptions) {
357
+ const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
358
+ return lookupRef?.values.filter(v => v.isActive) || [];
359
+ }
360
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupOptionsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
361
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: LookupOptionsPipe, isStandalone: true, name: "lookupOptions" });
362
+ }
363
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LookupOptionsPipe, decorators: [{
364
+ type: Pipe,
365
+ args: [{ name: 'lookupOptions', standalone: true, pure: true }]
366
+ }] });
367
+
368
+ class ReferenceDisplayValuePipe {
369
+ lang = inject(SparkLanguageService);
370
+ transform(attr, formData, referenceOptions) {
371
+ const selectedId = formData[attr.name];
372
+ if (!selectedId)
373
+ return this.lang.t('notSelected');
374
+ const options = referenceOptions[attr.name] || [];
375
+ const selected = options.find(o => o.id === selectedId);
376
+ return selected?.breadcrumb || selected?.name || selectedId;
377
+ }
378
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
379
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ReferenceDisplayValuePipe, isStandalone: true, name: "referenceDisplayValue" });
380
+ }
381
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceDisplayValuePipe, decorators: [{
382
+ type: Pipe,
383
+ args: [{ name: 'referenceDisplayValue', standalone: true, pure: true }]
384
+ }] });
385
+
386
+ class AsDetailDisplayValuePipe {
387
+ lang = inject(SparkLanguageService);
388
+ transform(attr, formData, asDetailTypes) {
389
+ const value = formData[attr.name];
390
+ if (!value)
391
+ return this.lang.t('notSet');
392
+ const asDetailType = asDetailTypes[attr.name] || null;
393
+ // 1. Try displayFormat (template with {PropertyName} placeholders)
394
+ if (asDetailType?.displayFormat) {
395
+ const result = this.resolveDisplayFormat(asDetailType.displayFormat, value);
396
+ if (result && result.trim())
397
+ return result;
398
+ }
399
+ // 2. Try displayAttribute (single property name)
400
+ if (asDetailType?.displayAttribute && value[asDetailType.displayAttribute]) {
401
+ return value[asDetailType.displayAttribute];
402
+ }
403
+ // 3. Fallback to common property names
404
+ const displayProps = ['Name', 'Title', 'Street', 'name', 'title'];
405
+ for (const prop of displayProps) {
406
+ if (value[prop])
407
+ return value[prop];
408
+ }
409
+ return this.lang.t('clickToEdit');
410
+ }
411
+ resolveDisplayFormat(format, data) {
412
+ return format.replace(/\{(\w+)\}/g, (match, propertyName) => {
413
+ const value = data[propertyName];
414
+ return value != null ? String(value) : '';
415
+ });
416
+ }
417
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
418
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: AsDetailDisplayValuePipe, isStandalone: true, name: "asDetailDisplayValue" });
419
+ }
420
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailDisplayValuePipe, decorators: [{
421
+ type: Pipe,
422
+ args: [{ name: 'asDetailDisplayValue', standalone: true, pure: true }]
423
+ }] });
424
+
425
+ class AsDetailTypePipe {
426
+ transform(attr, asDetailTypes) {
427
+ return asDetailTypes[attr.name] || null;
428
+ }
429
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
430
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: AsDetailTypePipe, isStandalone: true, name: "asDetailType" });
431
+ }
432
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailTypePipe, decorators: [{
433
+ type: Pipe,
434
+ args: [{ name: 'asDetailType', standalone: true, pure: true }]
435
+ }] });
436
+
437
+ class AsDetailColumnsPipe {
438
+ transform(attr, asDetailTypes) {
439
+ const type = asDetailTypes[attr.name];
440
+ if (!type)
441
+ return [];
442
+ return type.attributes
443
+ .filter(a => a.isVisible)
444
+ .sort((a, b) => a.order - b.order);
445
+ }
446
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailColumnsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
447
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: AsDetailColumnsPipe, isStandalone: true, name: "asDetailColumns" });
448
+ }
449
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailColumnsPipe, decorators: [{
450
+ type: Pipe,
451
+ args: [{ name: 'asDetailColumns', standalone: true, pure: true }]
452
+ }] });
453
+
454
+ class AsDetailCellValuePipe {
455
+ transform(row, parentAttr, col, asDetailRefOptions) {
456
+ const value = row[col.name];
457
+ if (value == null)
458
+ return '';
459
+ if (col.dataType === 'Reference' && col.query) {
460
+ const parentOptions = asDetailRefOptions[parentAttr.name];
461
+ if (parentOptions) {
462
+ const options = parentOptions[col.name];
463
+ if (options) {
464
+ const match = options.find(o => o.id === value);
465
+ if (match)
466
+ return match.breadcrumb || match.name || String(value);
467
+ }
468
+ }
469
+ }
470
+ return String(value);
471
+ }
472
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailCellValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
473
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: AsDetailCellValuePipe, isStandalone: true, name: "asDetailCellValue" });
474
+ }
475
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AsDetailCellValuePipe, decorators: [{
476
+ type: Pipe,
477
+ args: [{ name: 'asDetailCellValue', standalone: true, pure: true }]
478
+ }] });
479
+
480
+ class CanCreateDetailRowPipe {
481
+ transform(attr, permissions) {
482
+ const perms = permissions[attr.name];
483
+ return perms ? perms.canCreate : true;
484
+ }
485
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CanCreateDetailRowPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
486
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: CanCreateDetailRowPipe, isStandalone: true, name: "canCreateDetailRow" });
487
+ }
488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CanCreateDetailRowPipe, decorators: [{
489
+ type: Pipe,
490
+ args: [{ name: 'canCreateDetailRow', standalone: true, pure: true }]
491
+ }] });
492
+
493
+ class CanDeleteDetailRowPipe {
494
+ transform(attr, permissions) {
495
+ const perms = permissions[attr.name];
496
+ return perms ? perms.canDelete : true;
497
+ }
498
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CanDeleteDetailRowPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
499
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: CanDeleteDetailRowPipe, isStandalone: true, name: "canDeleteDetailRow" });
500
+ }
501
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CanDeleteDetailRowPipe, decorators: [{
502
+ type: Pipe,
503
+ args: [{ name: 'canDeleteDetailRow', standalone: true, pure: true }]
504
+ }] });
505
+
506
+ class InlineRefOptionsPipe {
507
+ transform(parentAttr, col, asDetailRefOptions) {
508
+ return asDetailRefOptions[parentAttr.name]?.[col.name] || [];
509
+ }
510
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InlineRefOptionsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
511
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: InlineRefOptionsPipe, isStandalone: true, name: "inlineRefOptions" });
512
+ }
513
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InlineRefOptionsPipe, decorators: [{
514
+ type: Pipe,
515
+ args: [{ name: 'inlineRefOptions', standalone: true, pure: true }]
516
+ }] });
517
+
518
+ class ReferenceAttrValuePipe {
519
+ transform(item, attrName) {
520
+ const attr = item.attributes.find(a => a.name === attrName);
521
+ if (!attr)
522
+ return '';
523
+ if (attr.breadcrumb)
524
+ return attr.breadcrumb;
525
+ return attr.value ?? '';
526
+ }
527
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceAttrValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
528
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ReferenceAttrValuePipe, isStandalone: true, name: "referenceAttrValue" });
529
+ }
530
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceAttrValuePipe, decorators: [{
531
+ type: Pipe,
532
+ args: [{ name: 'referenceAttrValue', standalone: true, pure: true }]
533
+ }] });
534
+
535
+ class ErrorForAttributePipe {
536
+ transform(attrName, validationErrors) {
537
+ const error = validationErrors.find(e => e.attributeName === attrName);
538
+ return error ? resolveTranslation(error.errorMessage) : null;
539
+ }
540
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ErrorForAttributePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
541
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ErrorForAttributePipe, isStandalone: true, name: "errorForAttribute" });
542
+ }
543
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ErrorForAttributePipe, decorators: [{
544
+ type: Pipe,
545
+ args: [{ name: 'errorForAttribute', standalone: true, pure: true }]
546
+ }] });
547
+
548
+ /** Built-in SVG icons used by ng-spark library templates. */
549
+ const SPARK_BUILT_IN_ICONS = {
550
+ 'arrow-left': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/></svg>',
551
+ 'pencil': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16"><path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/></svg>',
552
+ 'plus': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus" viewBox="0 0 16 16"><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/></svg>',
553
+ 'plus-lg': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2"/></svg>',
554
+ 'search': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/></svg>',
555
+ 'trash': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/><path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/></svg>',
556
+ 'x-lg': '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16"><path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/></svg>',
557
+ };
558
+
559
+ class SparkIconRegistry {
560
+ sanitizer = inject(DomSanitizer);
561
+ icons = new Map();
562
+ constructor() {
563
+ for (const [name, svg] of Object.entries(SPARK_BUILT_IN_ICONS)) {
564
+ this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));
565
+ }
566
+ }
567
+ register(name, svg) {
568
+ this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));
569
+ }
570
+ get(name) {
571
+ return this.icons.get(name);
572
+ }
573
+ has(name) {
574
+ return this.icons.has(name);
575
+ }
576
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkIconRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
577
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkIconRegistry, providedIn: 'root' });
578
+ }
579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkIconRegistry, decorators: [{
580
+ type: Injectable,
581
+ args: [{ providedIn: 'root' }]
582
+ }], ctorParameters: () => [] });
583
+
584
+ class SparkIconComponent {
585
+ registry = inject(SparkIconRegistry);
586
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
587
+ iconHtml = computed(() => this.registry.get(this.name()), ...(ngDevMode ? [{ debugName: "iconHtml" }] : []));
588
+ cssFallbackClass = computed(() => `bi-${this.name()}`, ...(ngDevMode ? [{ debugName: "cssFallbackClass" }] : []));
589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
590
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkIconComponent, isStandalone: true, selector: "spark-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
591
+ @if (iconHtml(); as html) {
592
+ <span [innerHTML]="html"></span>
593
+ } @else {
594
+ <i class="bi" [class]="cssFallbackClass()"></i>
595
+ }
596
+ `, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center}span{display:inline-flex;align-items:center}span ::ng-deep svg{width:1em;height:1em;fill:currentColor}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
597
+ }
598
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkIconComponent, decorators: [{
599
+ type: Component,
600
+ args: [{ selector: 'spark-icon', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
601
+ @if (iconHtml(); as html) {
602
+ <span [innerHTML]="html"></span>
603
+ } @else {
604
+ <i class="bi" [class]="cssFallbackClass()"></i>
605
+ }
606
+ `, styles: [":host{display:inline-flex;align-items:center;justify-content:center}span{display:inline-flex;align-items:center}span ::ng-deep svg{width:1em;height:1em;fill:currentColor}\n"] }]
607
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }] } });
608
+
609
+ class SparkFieldTemplateDirective {
610
+ template;
611
+ name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkFieldTemplate' });
612
+ constructor(template) {
613
+ this.template = template;
614
+ }
615
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkFieldTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
616
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkFieldTemplateDirective, isStandalone: true, selector: "[sparkFieldTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkFieldTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
617
+ }
618
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkFieldTemplateDirective, decorators: [{
619
+ type: Directive,
620
+ args: [{
621
+ selector: '[sparkFieldTemplate]',
622
+ standalone: true
623
+ }]
624
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkFieldTemplate", required: true }] }] } });
625
+
626
+ class SparkPoFormComponent {
627
+ sparkService = inject(SparkService);
628
+ translations = inject(SparkLanguageService);
629
+ fieldTemplates;
630
+ entityType = input(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
631
+ formData = model({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
632
+ validationErrors = input([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
633
+ showButtons = input(false, ...(ngDevMode ? [{ debugName: "showButtons" }] : []));
634
+ isSaving = input(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
635
+ externalFieldTemplates = input([], ...(ngDevMode ? [{ debugName: "externalFieldTemplates" }] : []));
636
+ save = output();
637
+ cancel = output();
638
+ colors = Color;
639
+ referenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "referenceOptions" }] : []));
640
+ asDetailTypes = signal({}, ...(ngDevMode ? [{ debugName: "asDetailTypes" }] : []));
641
+ lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
642
+ // Modal state for AsDetail object editing
643
+ editingAsDetailAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingAsDetailAttr" }] : []));
644
+ asDetailFormData = signal({}, ...(ngDevMode ? [{ debugName: "asDetailFormData" }] : []));
645
+ showAsDetailModal = signal(false, ...(ngDevMode ? [{ debugName: "showAsDetailModal" }] : []));
646
+ editingArrayIndex = signal(null, ...(ngDevMode ? [{ debugName: "editingArrayIndex" }] : []));
647
+ // Permissions for array AsDetail entity types
648
+ asDetailPermissions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailPermissions" }] : []));
649
+ // Reference options for columns within array AsDetail types (keyed by parent attr name, then column name)
650
+ asDetailReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : []));
651
+ // Modal state for Reference selection
652
+ editingReferenceAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingReferenceAttr" }] : []));
653
+ showReferenceModal = signal(false, ...(ngDevMode ? [{ debugName: "showReferenceModal" }] : []));
654
+ referenceModalItems = signal([], ...(ngDevMode ? [{ debugName: "referenceModalItems" }] : []));
655
+ referenceModalEntityType = signal(null, ...(ngDevMode ? [{ debugName: "referenceModalEntityType" }] : []));
656
+ referenceModalPagination = signal(undefined, ...(ngDevMode ? [{ debugName: "referenceModalPagination" }] : []));
657
+ referenceModalSettings = new DatatableSettings({
658
+ perPage: { values: [10, 25, 50], selected: 10 },
659
+ page: { values: [1], selected: 1 },
660
+ sortProperty: '',
661
+ sortDirection: 'ascending'
662
+ });
663
+ referenceSearchTerm = '';
664
+ // Modal state for LookupReference selection (Modal display type)
665
+ editingLookupAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingLookupAttr" }] : []));
666
+ showLookupModal = signal(false, ...(ngDevMode ? [{ debugName: "showLookupModal" }] : []));
667
+ lookupModalItems = signal([], ...(ngDevMode ? [{ debugName: "lookupModalItems" }] : []));
668
+ lookupSearchTerm = signal('', ...(ngDevMode ? [{ debugName: "lookupSearchTerm" }] : []));
669
+ ELookupDisplayType = ELookupDisplayType;
670
+ editableAttributes = computed(() => {
671
+ return this.entityType()?.attributes
672
+ .filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
673
+ .sort((a, b) => a.order - b.order) || [];
674
+ }, ...(ngDevMode ? [{ debugName: "editableAttributes" }] : []));
675
+ referenceVisibleAttributes = computed(() => {
676
+ return this.referenceModalEntityType()?.attributes
677
+ .filter(a => a.isVisible)
678
+ .sort((a, b) => a.order - b.order) || [];
679
+ }, ...(ngDevMode ? [{ debugName: "referenceVisibleAttributes" }] : []));
680
+ filteredLookupItems = computed(() => {
681
+ if (!this.lookupSearchTerm().trim()) {
682
+ return this.lookupModalItems();
683
+ }
684
+ const term = this.lookupSearchTerm().toLowerCase().trim();
685
+ return this.lookupModalItems().filter(item => {
686
+ const translation = resolveTranslation(item.values);
687
+ return translation.toLowerCase().includes(term) || item.key.toLowerCase().includes(term);
688
+ });
689
+ }, ...(ngDevMode ? [{ debugName: "filteredLookupItems" }] : []));
690
+ getFieldTemplate(attr) {
691
+ const allTemplates = [
692
+ ...(this.fieldTemplates?.toArray() || []),
693
+ ...this.externalFieldTemplates()
694
+ ];
695
+ // Priority 1: match by field name
696
+ const byName = allTemplates.find(t => t.name() === attr.name);
697
+ if (byName)
698
+ return byName.template;
699
+ // Priority 2: match by data type
700
+ const byType = allTemplates.find(t => t.name() === attr.dataType);
701
+ if (byType)
702
+ return byType.template;
703
+ return null;
704
+ }
705
+ getFieldTemplateContext(attr) {
706
+ const errorEntry = this.validationErrors().find(e => e.attributeName === attr.name);
707
+ return {
708
+ $implicit: attr,
709
+ formData: this.formData(),
710
+ value: this.formData()[attr.name],
711
+ hasError: this.hasError(attr.name),
712
+ errorMessage: errorEntry?.errorMessage || null
713
+ };
714
+ }
715
+ constructor() {
716
+ effect(() => {
717
+ const et = this.entityType();
718
+ if (et) {
719
+ this.loadReferenceOptions();
720
+ this.loadAsDetailTypes();
721
+ this.loadLookupReferenceOptions();
722
+ }
723
+ });
724
+ }
725
+ toRecord(entries) {
726
+ const result = {};
727
+ for (const [key, value] of entries) {
728
+ result[key] = value;
729
+ }
730
+ return result;
731
+ }
732
+ async loadReferenceOptions() {
733
+ const refAttrs = this.editableAttributes().filter(a => a.dataType === 'Reference' && a.query);
734
+ if (refAttrs.length === 0)
735
+ return;
736
+ const entries = await Promise.all(refAttrs.filter(a => a.query).map(async (attr) => {
737
+ const items = await this.sparkService.executeQueryByName(attr.query);
738
+ return [attr.name, items];
739
+ }));
740
+ this.referenceOptions.set(this.toRecord(entries));
741
+ }
742
+ async loadAsDetailTypes() {
743
+ const asDetailAttrs = this.editableAttributes().filter(a => a.dataType === 'AsDetail' && a.asDetailType);
744
+ if (asDetailAttrs.length === 0)
745
+ return;
746
+ const types = await this.sparkService.getEntityTypes();
747
+ const newAsDetailTypes = {};
748
+ for (const attr of asDetailAttrs) {
749
+ const asDetailType = types.find(t => t.clrType === attr.asDetailType);
750
+ if (asDetailType) {
751
+ newAsDetailTypes[attr.name] = asDetailType;
752
+ if (attr.isArray) {
753
+ const perms = await this.sparkService.getPermissions(asDetailType.id);
754
+ this.asDetailPermissions.update(prev => ({ ...prev, [attr.name]: perms }));
755
+ const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);
756
+ if (refCols.length > 0) {
757
+ const refEntries = await Promise.all(refCols.filter(c => c.query).map(async (col) => {
758
+ const items = await this.sparkService.executeQueryByName(col.query);
759
+ return [col.name, items];
760
+ }));
761
+ this.asDetailReferenceOptions.update(prev => ({ ...prev, [attr.name]: this.toRecord(refEntries) }));
762
+ }
763
+ }
764
+ }
765
+ }
766
+ this.asDetailTypes.set(newAsDetailTypes);
767
+ }
768
+ async loadLookupReferenceOptions() {
769
+ const lookupAttrs = this.editableAttributes().filter(a => a.lookupReferenceType);
770
+ if (lookupAttrs.length === 0)
771
+ return;
772
+ const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
773
+ const entries = await Promise.all(lookupNames.map(async (name) => {
774
+ const ref = await this.sparkService.getLookupReference(name);
775
+ return [name, ref];
776
+ }));
777
+ this.lookupReferenceOptions.set(this.toRecord(entries));
778
+ }
779
+ getReferenceOptions(attr) {
780
+ return this.referenceOptions()[attr.name] || [];
781
+ }
782
+ getLookupOptions(attr) {
783
+ const lookupRef = attr.lookupReferenceType ? this.lookupReferenceOptions()[attr.lookupReferenceType] : null;
784
+ return lookupRef?.values.filter(v => v.isActive) || [];
785
+ }
786
+ // LookupReference modal methods
787
+ openLookupSelector(attr) {
788
+ this.editingLookupAttr.set(attr);
789
+ this.lookupSearchTerm.set('');
790
+ this.lookupModalItems.set(this.getLookupOptions(attr));
791
+ this.showLookupModal.set(true);
792
+ }
793
+ selectLookupItem(item) {
794
+ const attr = this.editingLookupAttr();
795
+ if (attr) {
796
+ const data = { ...this.formData() };
797
+ data[attr.name] = item.key;
798
+ this.formData.set(data);
799
+ }
800
+ this.closeLookupModal();
801
+ }
802
+ closeLookupModal() {
803
+ this.showLookupModal.set(false);
804
+ this.editingLookupAttr.set(null);
805
+ this.lookupModalItems.set([]);
806
+ this.lookupSearchTerm.set('');
807
+ }
808
+ hasError(attrName) {
809
+ return this.validationErrors().some(e => e.attributeName === attrName);
810
+ }
811
+ onFieldChange() {
812
+ this.formData.set({ ...this.formData() });
813
+ }
814
+ onSave() {
815
+ this.save.emit();
816
+ }
817
+ onCancel() {
818
+ this.cancel.emit();
819
+ }
820
+ // AsDetail object modal methods
821
+ openAsDetailEditor(attr) {
822
+ this.editingAsDetailAttr.set(attr);
823
+ this.editingArrayIndex.set(null);
824
+ this.asDetailFormData.set({ ...(this.formData()[attr.name] || {}) });
825
+ this.showAsDetailModal.set(true);
826
+ }
827
+ saveAsDetailObject() {
828
+ const attr = this.editingAsDetailAttr();
829
+ if (attr) {
830
+ const data = { ...this.formData() };
831
+ if (attr.isArray) {
832
+ const arr = [...(data[attr.name] || [])];
833
+ const idx = this.editingArrayIndex();
834
+ if (idx !== null) {
835
+ arr[idx] = { ...this.asDetailFormData() };
836
+ }
837
+ else {
838
+ arr.push({ ...this.asDetailFormData() });
839
+ }
840
+ data[attr.name] = arr;
841
+ }
842
+ else {
843
+ data[attr.name] = { ...this.asDetailFormData() };
844
+ }
845
+ this.formData.set(data);
846
+ }
847
+ this.closeAsDetailModal();
848
+ }
849
+ closeAsDetailModal() {
850
+ this.showAsDetailModal.set(false);
851
+ this.editingAsDetailAttr.set(null);
852
+ this.editingArrayIndex.set(null);
853
+ this.asDetailFormData.set({});
854
+ }
855
+ // Inline AsDetail methods
856
+ addInlineRow(attr) {
857
+ const data = { ...this.formData() };
858
+ const arr = [...(data[attr.name] || [])];
859
+ arr.push({});
860
+ data[attr.name] = arr;
861
+ this.formData.set(data);
862
+ }
863
+ // Array AsDetail methods
864
+ addArrayItem(attr) {
865
+ this.editingAsDetailAttr.set(attr);
866
+ this.editingArrayIndex.set(null);
867
+ this.asDetailFormData.set({});
868
+ this.showAsDetailModal.set(true);
869
+ }
870
+ editArrayItem(attr, index) {
871
+ this.editingAsDetailAttr.set(attr);
872
+ this.editingArrayIndex.set(index);
873
+ const arr = this.formData()[attr.name] || [];
874
+ this.asDetailFormData.set({ ...(arr[index] || {}) });
875
+ this.showAsDetailModal.set(true);
876
+ }
877
+ removeArrayItem(attr, index) {
878
+ const data = { ...this.formData() };
879
+ const arr = [...(data[attr.name] || [])];
880
+ arr.splice(index, 1);
881
+ data[attr.name] = arr;
882
+ this.formData.set(data);
883
+ }
884
+ // Reference modal methods
885
+ async openReferenceSelector(attr) {
886
+ this.editingReferenceAttr.set(attr);
887
+ this.referenceSearchTerm = '';
888
+ this.referenceModalItems.set(this.getReferenceOptions(attr));
889
+ const types = await this.sparkService.getEntityTypes();
890
+ this.referenceModalEntityType.set(types.find(t => t.clrType === attr.referenceType) || null);
891
+ this.referenceModalSettings = new DatatableSettings({
892
+ perPage: { values: [10, 25, 50], selected: 10 },
893
+ page: { values: [1], selected: 1 },
894
+ sortProperty: '',
895
+ sortDirection: 'ascending'
896
+ });
897
+ this.applyReferenceFilter();
898
+ this.showReferenceModal.set(true);
899
+ }
900
+ onReferenceSearchChange() {
901
+ this.referenceModalSettings.page.selected = 1;
902
+ this.applyReferenceFilter();
903
+ }
904
+ applyReferenceFilter() {
905
+ let filteredItems = this.referenceModalItems();
906
+ if (this.referenceSearchTerm.trim()) {
907
+ const term = this.referenceSearchTerm.toLowerCase().trim();
908
+ filteredItems = this.referenceModalItems().filter(item => {
909
+ if (item.name?.toLowerCase().includes(term))
910
+ return true;
911
+ if (item.breadcrumb?.toLowerCase().includes(term))
912
+ return true;
913
+ return item.attributes.some(attr => {
914
+ const value = attr.breadcrumb || attr.value;
915
+ if (value == null)
916
+ return false;
917
+ return String(value).toLowerCase().includes(term);
918
+ });
919
+ });
920
+ }
921
+ const totalPages = Math.ceil(filteredItems.length / this.referenceModalSettings.perPage.selected) || 1;
922
+ this.referenceModalPagination.set({
923
+ data: filteredItems,
924
+ totalRecords: filteredItems.length,
925
+ totalPages: totalPages,
926
+ perPage: this.referenceModalSettings.perPage.selected,
927
+ page: this.referenceModalSettings.page.selected
928
+ });
929
+ this.referenceModalSettings.page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
930
+ if (this.referenceModalSettings.page.selected > totalPages) {
931
+ this.referenceModalSettings.page.selected = 1;
932
+ }
933
+ }
934
+ clearReferenceSearch() {
935
+ this.referenceSearchTerm = '';
936
+ this.onReferenceSearchChange();
937
+ }
938
+ selectReferenceItem(item) {
939
+ const attr = this.editingReferenceAttr();
940
+ if (attr) {
941
+ const data = { ...this.formData() };
942
+ data[attr.name] = item.id;
943
+ this.formData.set(data);
944
+ }
945
+ this.closeReferenceModal();
946
+ }
947
+ closeReferenceModal() {
948
+ this.showReferenceModal.set(false);
949
+ this.editingReferenceAttr.set(null);
950
+ this.referenceModalItems.set([]);
951
+ this.referenceModalEntityType.set(null);
952
+ this.referenceSearchTerm = '';
953
+ }
954
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
955
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoFormComponent, isStandalone: true, selector: "spark-po-form", inputs: { entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, formData: { classPropertyName: "formData", publicName: "formData", isSignal: true, isRequired: false, transformFunction: null }, validationErrors: { classPropertyName: "validationErrors", publicName: "validationErrors", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, isSaving: { classPropertyName: "isSaving", publicName: "isSaving", isSignal: true, isRequired: false, transformFunction: null }, externalFieldTemplates: { classPropertyName: "externalFieldTemplates", publicName: "externalFieldTemplates", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n @for (attr of editableAttributes(); track attr.id) {\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (getFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getFieldTemplateContext(attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <bs-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | lookupDisplayValue:formData():lookupReferenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openLookupSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | referenceDisplayValue:formData():referenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openReferenceSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ 'selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + 1\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + 1\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n }\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\" aria-hidden=\"true\"></span>\n }\n {{ 'save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting Reference items -->\n<bs-modal [isOpen]=\"showReferenceModal()\" (isOpenChange)=\"!$event && closeReferenceModal()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'select' | t }} {{ editingReferenceAttr()?.label | resolveTranslation:editingReferenceAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n @if (referenceModalEntityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [(ngModel)]=\"referenceSearchTerm\"\n (ngModelChange)=\"onReferenceSearchChange()\">\n @if (referenceSearchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearReferenceSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (referenceSearchTerm && referenceModalPagination()) {\n <span class=\"text-muted\">\n {{ referenceModalPagination()!.totalRecords }} {{ referenceModalPagination()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting LookupReference items -->\n<bs-modal [isOpen]=\"showLookupModal()\" (isOpenChange)=\"!$event && closeLookupModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'select' | t }} {{ editingLookupAttr()?.label | resolveTranslation:editingLookupAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [ngModel]=\"lookupSearchTerm()\"\n (ngModelChange)=\"lookupSearchTerm.set($event)\">\n @if (lookupSearchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"lookupSearchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody>\n @for (item of filteredLookupItems(); track item.key) {\n <tr\n [class.table-primary]=\"formData()[editingLookupAttr()?.name ?? ''] === item.key\"\n (click)=\"selectLookupItem(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeLookupModal()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "directive", type: BsGridColDirective, selector: "[col]", inputs: ["col"] }, { kind: "directive", type: BsColFormLabelDirective, selector: "[bsColFormLabel]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "component", type: BsSelectComponent, selector: "bs-select", inputs: ["identifier", "size", "multiple", "numberVisible", "disabled"] }, { kind: "directive", type: BsSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: BsToggleButtonComponent, selector: "bs-toggle-button", inputs: ["type", "isToggled", "name", "value", "group"], outputs: ["isToggledChange"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: InputTypePipe, name: "inputType" }, { kind: "pipe", type: LookupDisplayValuePipe, name: "lookupDisplayValue" }, { kind: "pipe", type: LookupDisplayTypePipe, name: "lookupDisplayType" }, { kind: "pipe", type: LookupOptionsPipe, name: "lookupOptions" }, { kind: "pipe", type: ReferenceDisplayValuePipe, name: "referenceDisplayValue" }, { kind: "pipe", type: AsDetailDisplayValuePipe, name: "asDetailDisplayValue" }, { kind: "pipe", type: AsDetailTypePipe, name: "asDetailType" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: CanCreateDetailRowPipe, name: "canCreateDetailRow" }, { kind: "pipe", type: CanDeleteDetailRowPipe, name: "canDeleteDetailRow" }, { kind: "pipe", type: InlineRefOptionsPipe, name: "inlineRefOptions" }, { kind: "pipe", type: ReferenceAttrValuePipe, name: "referenceAttrValue" }, { kind: "pipe", type: ErrorForAttributePipe, name: "errorForAttribute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
956
+ }
957
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoFormComponent, decorators: [{
958
+ type: Component,
959
+ args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, FormsModule, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsTableComponent, BsToggleButtonComponent, SparkIconComponent, SparkPoFormComponent, TranslateKeyPipe, ResolveTranslationPipe, InputTypePipe, LookupDisplayValuePipe, LookupDisplayTypePipe, LookupOptionsPipe, ReferenceDisplayValuePipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ReferenceAttrValuePipe, ErrorForAttributePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n @for (attr of editableAttributes(); track attr.id) {\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (getFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getFieldTemplateContext(attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <bs-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | lookupDisplayValue:formData():lookupReferenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openLookupSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | referenceDisplayValue:formData():referenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openReferenceSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ 'selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + 1\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + 1\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n }\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\" aria-hidden=\"true\"></span>\n }\n {{ 'save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting Reference items -->\n<bs-modal [isOpen]=\"showReferenceModal()\" (isOpenChange)=\"!$event && closeReferenceModal()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'select' | t }} {{ editingReferenceAttr()?.label | resolveTranslation:editingReferenceAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n @if (referenceModalEntityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [(ngModel)]=\"referenceSearchTerm\"\n (ngModelChange)=\"onReferenceSearchChange()\">\n @if (referenceSearchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearReferenceSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (referenceSearchTerm && referenceModalPagination()) {\n <span class=\"text-muted\">\n {{ referenceModalPagination()!.totalRecords }} {{ referenceModalPagination()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting LookupReference items -->\n<bs-modal [isOpen]=\"showLookupModal()\" (isOpenChange)=\"!$event && closeLookupModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'select' | t }} {{ editingLookupAttr()?.label | resolveTranslation:editingLookupAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [ngModel]=\"lookupSearchTerm()\"\n (ngModelChange)=\"lookupSearchTerm.set($event)\">\n @if (lookupSearchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"lookupSearchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody>\n @for (item of filteredLookupItems(); track item.key) {\n <tr\n [class.table-primary]=\"formData()[editingLookupAttr()?.name ?? ''] === item.key\"\n (click)=\"selectLookupItem(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeLookupModal()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n" }]
960
+ }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
961
+ type: ContentChildren,
962
+ args: [SparkFieldTemplateDirective]
963
+ }], entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }], formData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formData", required: false }] }, { type: i0.Output, args: ["formDataChange"] }], validationErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationErrors", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], isSaving: [{ type: i0.Input, args: [{ isSignal: true, alias: "isSaving", required: false }] }], externalFieldTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalFieldTemplates", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
964
+
965
+ class SparkPoCreateComponent {
966
+ route = inject(ActivatedRoute);
967
+ router = inject(Router);
968
+ sparkService = inject(SparkService);
969
+ fieldTemplates;
970
+ saved = output();
971
+ cancelled = output();
972
+ colors = Color;
973
+ entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
974
+ type = signal('', ...(ngDevMode ? [{ debugName: "type" }] : []));
975
+ formData = signal({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
976
+ validationErrors = signal([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
977
+ isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
978
+ generalErrors = computed(() => this.validationErrors().filter(e => !e.attributeName), ...(ngDevMode ? [{ debugName: "generalErrors" }] : []));
979
+ constructor() {
980
+ this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
981
+ }
982
+ async onParamsChange(params) {
983
+ this.type.set(params.get('type') || '');
984
+ const types = await this.sparkService.getEntityTypes();
985
+ const entityType = types.find(t => t.id === this.type() || t.alias === this.type()) || null;
986
+ this.entityType.set(entityType);
987
+ this.initFormData();
988
+ }
989
+ initFormData() {
990
+ const data = {};
991
+ this.getEditableAttributes().forEach(attr => {
992
+ if (attr.dataType === 'Reference') {
993
+ data[attr.name] = null;
994
+ }
995
+ else if (attr.dataType === 'AsDetail') {
996
+ data[attr.name] = attr.isArray ? [] : {};
997
+ }
998
+ else if (attr.dataType === 'boolean') {
999
+ data[attr.name] = false;
1000
+ }
1001
+ else {
1002
+ data[attr.name] = '';
1003
+ }
1004
+ });
1005
+ this.formData.set(data);
1006
+ }
1007
+ getEditableAttributes() {
1008
+ return this.entityType()?.attributes
1009
+ .filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
1010
+ .sort((a, b) => a.order - b.order) || [];
1011
+ }
1012
+ async onSave() {
1013
+ if (!this.entityType())
1014
+ return;
1015
+ this.validationErrors.set([]);
1016
+ this.isSaving.set(true);
1017
+ const attributes = this.getEditableAttributes().map(attr => ({
1018
+ id: attr.id,
1019
+ name: attr.name,
1020
+ value: this.formData()[attr.name],
1021
+ dataType: attr.dataType,
1022
+ isRequired: attr.isRequired,
1023
+ isVisible: attr.isVisible,
1024
+ isReadOnly: attr.isReadOnly,
1025
+ isValueChanged: true,
1026
+ order: attr.order,
1027
+ rules: attr.rules
1028
+ }));
1029
+ const po = {
1030
+ name: this.formData()['Name'] || 'New Item',
1031
+ objectTypeId: this.entityType().id,
1032
+ attributes
1033
+ };
1034
+ try {
1035
+ const result = await this.sparkService.create(this.type(), po);
1036
+ this.isSaving.set(false);
1037
+ this.saved.emit(result);
1038
+ this.router.navigate(['/po', this.type(), result.id]);
1039
+ }
1040
+ catch (e) {
1041
+ this.isSaving.set(false);
1042
+ const error = e;
1043
+ if (error.status === 400 && error.error?.errors) {
1044
+ this.validationErrors.set(error.error.errors);
1045
+ }
1046
+ else {
1047
+ this.validationErrors.set([{
1048
+ attributeName: '',
1049
+ errorMessage: { en: error.message || 'An unexpected error occurred' },
1050
+ ruleType: 'error'
1051
+ }]);
1052
+ }
1053
+ }
1054
+ }
1055
+ onCancel() {
1056
+ this.cancelled.emit();
1057
+ window.history.back();
1058
+ }
1059
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1060
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoCreateComponent, isStandalone: true, selector: "spark-po-create", outputs: { saved: "saved", cancelled: "cancelled" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1061
+ }
1062
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoCreateComponent, decorators: [{
1063
+ type: Component,
1064
+ args: [{ selector: 'spark-po-create', imports: [CommonModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1065
+ }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
1066
+ type: ContentChildren,
1067
+ args: [SparkFieldTemplateDirective]
1068
+ }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1069
+
1070
+ var sparkPoCreate_component = /*#__PURE__*/Object.freeze({
1071
+ __proto__: null,
1072
+ SparkPoCreateComponent: SparkPoCreateComponent
1073
+ });
1074
+
1075
+ class SparkPoEditComponent {
1076
+ route = inject(ActivatedRoute);
1077
+ router = inject(Router);
1078
+ sparkService = inject(SparkService);
1079
+ fieldTemplates;
1080
+ saved = output();
1081
+ cancelled = output();
1082
+ colors = Color;
1083
+ entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
1084
+ item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
1085
+ type = '';
1086
+ id = '';
1087
+ formData = signal({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
1088
+ validationErrors = signal([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
1089
+ isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
1090
+ generalErrors = computed(() => this.validationErrors().filter(e => !e.attributeName), ...(ngDevMode ? [{ debugName: "generalErrors" }] : []));
1091
+ constructor() {
1092
+ this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
1093
+ }
1094
+ async onParamsChange(params) {
1095
+ this.type = params.get('type') || '';
1096
+ this.id = params.get('id') || '';
1097
+ try {
1098
+ const [types, item] = await Promise.all([
1099
+ this.sparkService.getEntityTypes(),
1100
+ this.sparkService.get(this.type, this.id)
1101
+ ]);
1102
+ const entityType = types.find(t => t.id === this.type || t.alias === this.type) || null;
1103
+ this.entityType.set(entityType);
1104
+ this.item.set(item);
1105
+ this.initFormData();
1106
+ }
1107
+ catch (e) {
1108
+ const error = e;
1109
+ this.validationErrors.set([{
1110
+ attributeName: '',
1111
+ errorMessage: { en: error.error?.error || error.message || 'An unexpected error occurred' },
1112
+ ruleType: 'error'
1113
+ }]);
1114
+ }
1115
+ }
1116
+ initFormData() {
1117
+ const data = {};
1118
+ const currentItem = this.item();
1119
+ this.getEditableAttributes().forEach(attr => {
1120
+ const itemAttr = currentItem?.attributes.find(a => a.name === attr.name);
1121
+ if (attr.dataType === 'Reference') {
1122
+ data[attr.name] = itemAttr?.value ?? null;
1123
+ }
1124
+ else if (attr.dataType === 'AsDetail') {
1125
+ data[attr.name] = itemAttr?.value ?? (attr.isArray ? [] : {});
1126
+ }
1127
+ else if (attr.dataType === 'boolean') {
1128
+ data[attr.name] = itemAttr?.value ?? false;
1129
+ }
1130
+ else {
1131
+ data[attr.name] = itemAttr?.value ?? '';
1132
+ }
1133
+ });
1134
+ this.formData.set(data);
1135
+ }
1136
+ getEditableAttributes() {
1137
+ return this.entityType()?.attributes
1138
+ .filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
1139
+ .sort((a, b) => a.order - b.order) || [];
1140
+ }
1141
+ async onSave() {
1142
+ const currentItem = this.item();
1143
+ if (!this.entityType() || !currentItem)
1144
+ return;
1145
+ this.validationErrors.set([]);
1146
+ this.isSaving.set(true);
1147
+ const attributes = currentItem.attributes.map(attr => {
1148
+ const editableAttr = this.getEditableAttributes().find(a => a.name === attr.name);
1149
+ const newValue = editableAttr ? this.formData()[attr.name] : attr.value;
1150
+ return {
1151
+ ...attr,
1152
+ value: newValue,
1153
+ isValueChanged: editableAttr ? newValue !== attr.value : false
1154
+ };
1155
+ });
1156
+ const po = {
1157
+ id: currentItem.id,
1158
+ name: this.formData()['Name'] || currentItem.name,
1159
+ objectTypeId: this.entityType().id,
1160
+ attributes
1161
+ };
1162
+ try {
1163
+ const result = await this.sparkService.update(this.type, this.id, po);
1164
+ this.isSaving.set(false);
1165
+ this.saved.emit(result);
1166
+ this.router.navigate(['/po', this.type, this.id]);
1167
+ }
1168
+ catch (e) {
1169
+ this.isSaving.set(false);
1170
+ const error = e;
1171
+ if (error.status === 400 && error.error?.errors) {
1172
+ this.validationErrors.set(error.error.errors);
1173
+ }
1174
+ else {
1175
+ this.validationErrors.set([{
1176
+ attributeName: '',
1177
+ errorMessage: { en: error.message || 'An unexpected error occurred' },
1178
+ ruleType: 'error'
1179
+ }]);
1180
+ }
1181
+ }
1182
+ }
1183
+ onCancel() {
1184
+ this.cancelled.emit();
1185
+ this.router.navigate(['/po', this.type, this.id]);
1186
+ }
1187
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1188
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoEditComponent, isStandalone: true, selector: "spark-po-edit", outputs: { saved: "saved", cancelled: "cancelled" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1189
+ }
1190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoEditComponent, decorators: [{
1191
+ type: Component,
1192
+ args: [{ selector: 'spark-po-edit', imports: [CommonModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1193
+ }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
1194
+ type: ContentChildren,
1195
+ args: [SparkFieldTemplateDirective]
1196
+ }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1197
+
1198
+ var sparkPoEdit_component = /*#__PURE__*/Object.freeze({
1199
+ __proto__: null,
1200
+ SparkPoEditComponent: SparkPoEditComponent
1201
+ });
1202
+
1203
+ class AttributeValuePipe {
1204
+ transform(attrName, item, entityType, lookupRefOptions, allEntityTypes) {
1205
+ if (!item)
1206
+ return '';
1207
+ const attr = item.attributes.find(a => a.name === attrName);
1208
+ if (!attr)
1209
+ return '';
1210
+ if (attr.breadcrumb)
1211
+ return attr.breadcrumb;
1212
+ const attrDef = entityType?.attributes.find(a => a.name === attrName);
1213
+ if (attrDef?.dataType === 'AsDetail' && attr.value) {
1214
+ if (Array.isArray(attr.value)) {
1215
+ return `${attr.value.length} item${attr.value.length !== 1 ? 's' : ''}`;
1216
+ }
1217
+ if (typeof attr.value === 'object') {
1218
+ return this.formatAsDetailValue(attrDef, attr.value, allEntityTypes);
1219
+ }
1220
+ }
1221
+ if (attrDef?.lookupReferenceType && attr.value != null && attr.value !== '') {
1222
+ const lookupRef = lookupRefOptions[attrDef.lookupReferenceType];
1223
+ if (lookupRef) {
1224
+ const option = lookupRef.values.find(v => v.key === String(attr.value));
1225
+ if (option) {
1226
+ return resolveTranslation(option.values) || option.key;
1227
+ }
1228
+ }
1229
+ }
1230
+ if (attrDef?.dataType === 'boolean') {
1231
+ return attr.value ?? null;
1232
+ }
1233
+ return attr.value ?? '';
1234
+ }
1235
+ formatAsDetailValue(attrDef, value, allEntityTypes) {
1236
+ const asDetailType = allEntityTypes.find(t => t.clrType === attrDef.asDetailType);
1237
+ if (asDetailType?.displayFormat) {
1238
+ const result = this.resolveDisplayFormat(asDetailType.displayFormat, value);
1239
+ if (result && result.trim())
1240
+ return result;
1241
+ }
1242
+ if (asDetailType?.displayAttribute && value[asDetailType.displayAttribute]) {
1243
+ return value[asDetailType.displayAttribute];
1244
+ }
1245
+ const displayProps = ['Name', 'Title', 'Street', 'name', 'title'];
1246
+ for (const prop of displayProps) {
1247
+ if (value[prop])
1248
+ return value[prop];
1249
+ }
1250
+ return '(object)';
1251
+ }
1252
+ resolveDisplayFormat(format, data) {
1253
+ return format.replace(/\{(\w+)\}/g, (match, propertyName) => {
1254
+ const value = data[propertyName];
1255
+ return value != null ? String(value) : '';
1256
+ });
1257
+ }
1258
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AttributeValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1259
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: AttributeValuePipe, isStandalone: true, name: "attributeValue" });
1260
+ }
1261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AttributeValuePipe, decorators: [{
1262
+ type: Pipe,
1263
+ args: [{ name: 'attributeValue', standalone: true, pure: true }]
1264
+ }] });
1265
+
1266
+ class RawAttributeValuePipe {
1267
+ transform(attrName, item) {
1268
+ return item?.attributes.find(a => a.name === attrName)?.value;
1269
+ }
1270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RawAttributeValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1271
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: RawAttributeValuePipe, isStandalone: true, name: "rawAttributeValue" });
1272
+ }
1273
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RawAttributeValuePipe, decorators: [{
1274
+ type: Pipe,
1275
+ args: [{ name: 'rawAttributeValue', standalone: true, pure: true }]
1276
+ }] });
1277
+
1278
+ class ArrayValuePipe {
1279
+ transform(attrName, item) {
1280
+ const attr = item?.attributes.find(a => a.name === attrName);
1281
+ if (!attr || !Array.isArray(attr.value))
1282
+ return [];
1283
+ return attr.value;
1284
+ }
1285
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1286
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ArrayValuePipe, isStandalone: true, name: "arrayValue" });
1287
+ }
1288
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayValuePipe, decorators: [{
1289
+ type: Pipe,
1290
+ args: [{ name: 'arrayValue', standalone: true, pure: true }]
1291
+ }] });
1292
+
1293
+ class ReferenceLinkRoutePipe {
1294
+ transform(referenceClrType, referenceId, allEntityTypes) {
1295
+ if (!referenceId || !referenceClrType)
1296
+ return null;
1297
+ const targetType = allEntityTypes.find(t => t.clrType === referenceClrType);
1298
+ if (!targetType)
1299
+ return null;
1300
+ return ['/po', targetType.alias || targetType.id, referenceId];
1301
+ }
1302
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceLinkRoutePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1303
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: ReferenceLinkRoutePipe, isStandalone: true, name: "referenceLinkRoute" });
1304
+ }
1305
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ReferenceLinkRoutePipe, decorators: [{
1306
+ type: Pipe,
1307
+ args: [{ name: 'referenceLinkRoute', standalone: true, pure: true }]
1308
+ }] });
1309
+
1310
+ class SparkDetailFieldTemplateDirective {
1311
+ template;
1312
+ name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkDetailFieldTemplate' });
1313
+ constructor(template) {
1314
+ this.template = template;
1315
+ }
1316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkDetailFieldTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1317
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkDetailFieldTemplateDirective, isStandalone: true, selector: "[sparkDetailFieldTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkDetailFieldTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
1318
+ }
1319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkDetailFieldTemplateDirective, decorators: [{
1320
+ type: Directive,
1321
+ args: [{
1322
+ selector: '[sparkDetailFieldTemplate]',
1323
+ standalone: true
1324
+ }]
1325
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkDetailFieldTemplate", required: true }] }] } });
1326
+
1327
+ class SparkPoDetailComponent {
1328
+ route = inject(ActivatedRoute);
1329
+ router = inject(Router);
1330
+ sparkService = inject(SparkService);
1331
+ lang = inject(SparkLanguageService);
1332
+ detailFieldTemplates;
1333
+ showCustomActions = input(true, ...(ngDevMode ? [{ debugName: "showCustomActions" }] : []));
1334
+ extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
1335
+ extraContentTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraContentTemplate" }] : []));
1336
+ edited = output();
1337
+ deleted = output();
1338
+ customActionExecuted = output();
1339
+ colors = Color;
1340
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
1341
+ entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
1342
+ allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
1343
+ item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
1344
+ lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
1345
+ asDetailTypes = signal({}, ...(ngDevMode ? [{ debugName: "asDetailTypes" }] : []));
1346
+ asDetailReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : []));
1347
+ type = '';
1348
+ id = '';
1349
+ canEdit = signal(false, ...(ngDevMode ? [{ debugName: "canEdit" }] : []));
1350
+ canDelete = signal(false, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
1351
+ customActions = signal([], ...(ngDevMode ? [{ debugName: "customActions" }] : []));
1352
+ constructor() {
1353
+ this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
1354
+ }
1355
+ async onParamsChange(params) {
1356
+ this.type = params.get('type') || '';
1357
+ this.id = params.get('id') || '';
1358
+ try {
1359
+ const [entityTypes, item] = await Promise.all([
1360
+ this.sparkService.getEntityTypes(),
1361
+ this.sparkService.get(this.type, this.id)
1362
+ ]);
1363
+ this.allEntityTypes.set(entityTypes);
1364
+ this.entityType.set(entityTypes.find(t => t.id === this.type || t.alias === this.type) || null);
1365
+ this.item.set(item);
1366
+ this.loadLookupReferenceOptions();
1367
+ this.loadAsDetailTypes();
1368
+ const et = this.entityType();
1369
+ if (et) {
1370
+ const [permissions, actions] = await Promise.all([
1371
+ this.sparkService.getPermissions(et.id),
1372
+ this.sparkService.getCustomActions(et.id)
1373
+ ]);
1374
+ this.canEdit.set(permissions.canEdit);
1375
+ this.canDelete.set(permissions.canDelete);
1376
+ this.customActions.set(actions.filter(a => a.showedOn === 'detail' || a.showedOn === 'both'));
1377
+ }
1378
+ }
1379
+ catch (e) {
1380
+ const error = e;
1381
+ this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');
1382
+ }
1383
+ }
1384
+ visibleAttributes = computed(() => {
1385
+ return this.entityType()?.attributes
1386
+ .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
1387
+ .sort((a, b) => a.order - b.order) || [];
1388
+ }, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
1389
+ getDetailFieldTemplate(attr) {
1390
+ if (!this.detailFieldTemplates)
1391
+ return null;
1392
+ const byName = this.detailFieldTemplates.find(t => t.name() === attr.name);
1393
+ if (byName)
1394
+ return byName.template;
1395
+ const byType = this.detailFieldTemplates.find(t => t.name() === attr.dataType);
1396
+ if (byType)
1397
+ return byType.template;
1398
+ return null;
1399
+ }
1400
+ getDetailFieldContext(attr, item) {
1401
+ const itemAttr = item.attributes.find(a => a.name === attr.name);
1402
+ return {
1403
+ $implicit: attr,
1404
+ item,
1405
+ value: itemAttr?.value
1406
+ };
1407
+ }
1408
+ async loadLookupReferenceOptions() {
1409
+ const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);
1410
+ if (lookupAttrs.length === 0)
1411
+ return;
1412
+ const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
1413
+ const entries = await Promise.all(lookupNames.map(async (name) => {
1414
+ const result = await this.sparkService.getLookupReference(name);
1415
+ return [name, result];
1416
+ }));
1417
+ this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}));
1418
+ }
1419
+ async loadAsDetailTypes() {
1420
+ const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);
1421
+ if (asDetailAttrs.length === 0)
1422
+ return;
1423
+ const types = this.allEntityTypes();
1424
+ const newAsDetailTypes = {};
1425
+ for (const attr of asDetailAttrs) {
1426
+ const asDetailType = types.find(t => t.clrType === attr.asDetailType);
1427
+ if (asDetailType) {
1428
+ newAsDetailTypes[attr.name] = asDetailType;
1429
+ const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);
1430
+ if (refCols.length > 0) {
1431
+ const refEntries = await Promise.all(refCols.map(async (col) => {
1432
+ const results = await this.sparkService.executeQueryByName(col.query);
1433
+ return [col.name, results];
1434
+ }));
1435
+ this.asDetailReferenceOptions.update(prev => ({
1436
+ ...prev,
1437
+ [attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
1438
+ }));
1439
+ }
1440
+ }
1441
+ }
1442
+ this.asDetailTypes.set(newAsDetailTypes);
1443
+ }
1444
+ async onCustomAction(action) {
1445
+ if (action.confirmationMessageKey) {
1446
+ const message = this.lang.t(action.confirmationMessageKey) || 'Are you sure?';
1447
+ if (!confirm(message))
1448
+ return;
1449
+ }
1450
+ try {
1451
+ await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);
1452
+ this.customActionExecuted.emit({ action, item: this.item() });
1453
+ if (action.refreshOnCompleted) {
1454
+ const item = await this.sparkService.get(this.type, this.id);
1455
+ this.item.set(item);
1456
+ }
1457
+ }
1458
+ catch (e) {
1459
+ const err = e;
1460
+ this.errorMessage.set(err.error?.error || err.message || 'Action failed');
1461
+ }
1462
+ }
1463
+ onEdit() {
1464
+ this.edited.emit();
1465
+ this.router.navigate(['/po', this.type, this.id, 'edit']);
1466
+ }
1467
+ async onDelete() {
1468
+ if (confirm(this.lang.t('confirmDelete'))) {
1469
+ await this.sparkService.delete(this.type, this.id);
1470
+ this.deleted.emit();
1471
+ this.router.navigate(['/']);
1472
+ }
1473
+ }
1474
+ onBack() {
1475
+ window.history.back();
1476
+ }
1477
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1478
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoDetailComponent, isStandalone: true, selector: "spark-po-detail", inputs: { showCustomActions: { classPropertyName: "showCustomActions", publicName: "showCustomActions", isSignal: true, isRequired: false, transformFunction: null }, extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, extraContentTemplate: { classPropertyName: "extraContentTemplate", publicName: "extraContentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edited: "edited", deleted: "deleted", customActionExecuted: "customActionExecuted" }, queries: [{ propertyName: "detailFieldTemplates", predicate: SparkDetailFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\n <bs-card>\n <bs-card-header>{{ 'details' | t }}</bs-card-header>\n <div class=\"p-3\">\n <bs-grid>\n <dl bsRow>\n @for (attr of visibleAttributes(); track attr.id) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getDetailFieldContext(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n }\n </dl>\n </bs-grid>\n </div>\n </bs-card>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsButtonGroupComponent, selector: "bs-button-group" }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }, { kind: "pipe", type: RawAttributeValuePipe, name: "rawAttributeValue" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: ArrayValuePipe, name: "arrayValue" }, { kind: "pipe", type: ReferenceLinkRoutePipe, name: "referenceLinkRoute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1479
+ }
1480
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoDetailComponent, decorators: [{
1481
+ type: Component,
1482
+ args: [{ selector: 'spark-po-detail', imports: [CommonModule, NgTemplateOutlet, RouterModule, BsAlertComponent, BsButtonGroupComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsTableComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\n <bs-card>\n <bs-card-header>{{ 'details' | t }}</bs-card-header>\n <div class=\"p-3\">\n <bs-grid>\n <dl bsRow>\n @for (attr of visibleAttributes(); track attr.id) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getDetailFieldContext(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n }\n </dl>\n </bs-grid>\n </div>\n </bs-card>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1483
+ }], ctorParameters: () => [], propDecorators: { detailFieldTemplates: [{
1484
+ type: ContentChildren,
1485
+ args: [SparkDetailFieldTemplateDirective]
1486
+ }], showCustomActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomActions", required: false }] }], extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], extraContentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraContentTemplate", required: false }] }], edited: [{ type: i0.Output, args: ["edited"] }], deleted: [{ type: i0.Output, args: ["deleted"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
1487
+
1488
+ var sparkPoDetail_component = /*#__PURE__*/Object.freeze({
1489
+ __proto__: null,
1490
+ SparkPoDetailComponent: SparkPoDetailComponent
1491
+ });
1492
+
1493
+ class SparkColumnTemplateDirective {
1494
+ template;
1495
+ name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkColumnTemplate' });
1496
+ constructor(template) {
1497
+ this.template = template;
1498
+ }
1499
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkColumnTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1500
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkColumnTemplateDirective, isStandalone: true, selector: "[sparkColumnTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkColumnTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
1501
+ }
1502
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkColumnTemplateDirective, decorators: [{
1503
+ type: Directive,
1504
+ args: [{
1505
+ selector: '[sparkColumnTemplate]',
1506
+ standalone: true
1507
+ }]
1508
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkColumnTemplate", required: true }] }] } });
1509
+
1510
+ class SparkQueryListComponent {
1511
+ route = inject(ActivatedRoute);
1512
+ router = inject(Router);
1513
+ sparkService = inject(SparkService);
1514
+ columnTemplates;
1515
+ extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
1516
+ rowClicked = output();
1517
+ createClicked = output();
1518
+ colors = Color;
1519
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
1520
+ query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
1521
+ entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
1522
+ allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
1523
+ allItems = signal([], ...(ngDevMode ? [{ debugName: "allItems" }] : []));
1524
+ lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
1525
+ paginationData = signal(undefined, ...(ngDevMode ? [{ debugName: "paginationData" }] : []));
1526
+ searchTerm = '';
1527
+ canCreate = signal(false, ...(ngDevMode ? [{ debugName: "canCreate" }] : []));
1528
+ settings = new DatatableSettings({
1529
+ perPage: { values: [10, 25, 50], selected: 10 },
1530
+ page: { values: [1], selected: 1 },
1531
+ sortProperty: '',
1532
+ sortDirection: 'ascending'
1533
+ });
1534
+ currentSortProperty = '';
1535
+ currentSortDirection = '';
1536
+ constructor() {
1537
+ this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
1538
+ }
1539
+ async onParamsChange(params) {
1540
+ const queryId = params.get('queryId');
1541
+ const typeParam = params.get('type');
1542
+ let resolvedQuery = null;
1543
+ let resolvedEntityType = null;
1544
+ let resolvedEntityTypes = [];
1545
+ if (queryId) {
1546
+ resolvedQuery = await this.sparkService.getQuery(queryId);
1547
+ if (resolvedQuery) {
1548
+ const result = await this.resolveEntityTypeForQuery(resolvedQuery);
1549
+ resolvedEntityType = result.entityType;
1550
+ resolvedEntityTypes = result.entityTypes;
1551
+ }
1552
+ }
1553
+ else if (typeParam) {
1554
+ resolvedEntityTypes = await this.sparkService.getEntityTypes();
1555
+ resolvedEntityType = resolvedEntityTypes.find(t => t.id === typeParam || t.alias === typeParam) || null;
1556
+ if (resolvedEntityType) {
1557
+ const queries = await this.sparkService.getQueries();
1558
+ const singularName = resolvedEntityType.name;
1559
+ resolvedQuery = queries.find(q => {
1560
+ const contextSingular = this.singularize(q.contextProperty);
1561
+ return q.contextProperty === singularName ||
1562
+ contextSingular === singularName ||
1563
+ q.contextProperty === singularName + 's';
1564
+ }) || null;
1565
+ }
1566
+ }
1567
+ if (resolvedQuery)
1568
+ this.query.set(resolvedQuery);
1569
+ if (resolvedEntityType) {
1570
+ this.entityType.set(resolvedEntityType);
1571
+ this.allEntityTypes.set(resolvedEntityTypes);
1572
+ this.settings = new DatatableSettings({
1573
+ perPage: { values: [10, 25, 50], selected: 10 },
1574
+ page: { values: [1], selected: 1 },
1575
+ sortProperty: resolvedQuery?.sortBy || '',
1576
+ sortDirection: resolvedQuery?.sortDirection === 'desc' ? 'descending' : 'ascending'
1577
+ });
1578
+ this.loadLookupReferenceOptions();
1579
+ const permissions = await this.sparkService.getPermissions(resolvedEntityType.id);
1580
+ this.canCreate.set(permissions.canCreate);
1581
+ await this.loadItems();
1582
+ }
1583
+ }
1584
+ async resolveEntityTypeForQuery(query) {
1585
+ const singularName = this.singularize(query.contextProperty);
1586
+ const entityTypes = await this.sparkService.getEntityTypes();
1587
+ const type = entityTypes.find(t => t.name === query.contextProperty ||
1588
+ t.name === singularName ||
1589
+ t.clrType.endsWith(singularName));
1590
+ return { entityType: type || null, entityTypes };
1591
+ }
1592
+ singularize(plural) {
1593
+ const irregulars = {
1594
+ 'People': 'Person',
1595
+ 'Children': 'Child',
1596
+ 'Men': 'Men',
1597
+ 'Women': 'Woman'
1598
+ };
1599
+ if (irregulars[plural])
1600
+ return irregulars[plural];
1601
+ if (plural.endsWith('ies')) {
1602
+ return plural.slice(0, -3) + 'y';
1603
+ }
1604
+ if (plural.endsWith('es')) {
1605
+ return plural.slice(0, -2);
1606
+ }
1607
+ if (plural.endsWith('s')) {
1608
+ return plural.slice(0, -1);
1609
+ }
1610
+ return plural;
1611
+ }
1612
+ async loadItems() {
1613
+ const currentQuery = this.query();
1614
+ if (!currentQuery)
1615
+ return;
1616
+ try {
1617
+ const sortDirection = this.settings.sortDirection === 'descending' ? 'desc' : 'asc';
1618
+ const items = await this.sparkService.executeQuery(currentQuery.id, this.settings.sortProperty || undefined, this.settings.sortProperty ? sortDirection : undefined);
1619
+ this.errorMessage.set(null);
1620
+ this.allItems.set(items);
1621
+ this.currentSortProperty = this.settings.sortProperty;
1622
+ this.currentSortDirection = this.settings.sortDirection;
1623
+ this.applyFilter();
1624
+ }
1625
+ catch (e) {
1626
+ this.errorMessage.set(e.error?.error || e.message || 'An unexpected error occurred');
1627
+ this.allItems.set([]);
1628
+ this.applyFilter();
1629
+ }
1630
+ }
1631
+ onSettingsChange() {
1632
+ const sortChanged = this.settings.sortProperty !== this.currentSortProperty ||
1633
+ this.settings.sortDirection !== this.currentSortDirection;
1634
+ if (sortChanged) {
1635
+ this.settings.page.selected = 1;
1636
+ this.loadItems();
1637
+ }
1638
+ else {
1639
+ this.applyFilter();
1640
+ }
1641
+ }
1642
+ onSearchChange() {
1643
+ this.settings.page.selected = 1;
1644
+ this.applyFilter();
1645
+ }
1646
+ applyFilter() {
1647
+ let filteredItems = this.allItems();
1648
+ if (this.searchTerm.trim()) {
1649
+ const term = this.searchTerm.toLowerCase().trim();
1650
+ filteredItems = filteredItems.filter(item => {
1651
+ if (item.name?.toLowerCase().includes(term))
1652
+ return true;
1653
+ if (item.breadcrumb?.toLowerCase().includes(term))
1654
+ return true;
1655
+ return item.attributes.some(attr => {
1656
+ const value = attr.breadcrumb || attr.value;
1657
+ if (value == null)
1658
+ return false;
1659
+ return String(value).toLowerCase().includes(term);
1660
+ });
1661
+ });
1662
+ }
1663
+ const totalPages = Math.ceil(filteredItems.length / this.settings.perPage.selected) || 1;
1664
+ this.paginationData.set({
1665
+ data: filteredItems,
1666
+ totalRecords: filteredItems.length,
1667
+ totalPages: totalPages,
1668
+ perPage: this.settings.perPage.selected,
1669
+ page: this.settings.page.selected
1670
+ });
1671
+ this.settings.page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
1672
+ if (this.settings.page.selected > totalPages) {
1673
+ this.settings.page.selected = 1;
1674
+ }
1675
+ }
1676
+ clearSearch() {
1677
+ this.searchTerm = '';
1678
+ this.onSearchChange();
1679
+ }
1680
+ visibleAttributes = computed(() => {
1681
+ return this.entityType()?.attributes
1682
+ .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
1683
+ .sort((a, b) => a.order - b.order) || [];
1684
+ }, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
1685
+ getColumnTemplate(attr) {
1686
+ if (!this.columnTemplates)
1687
+ return null;
1688
+ const byName = this.columnTemplates.find(t => t.name() === attr.name);
1689
+ if (byName)
1690
+ return byName.template;
1691
+ const byType = this.columnTemplates.find(t => t.name() === attr.dataType);
1692
+ if (byType)
1693
+ return byType.template;
1694
+ return null;
1695
+ }
1696
+ getColumnTemplateContext(item, attr) {
1697
+ const itemAttr = item.attributes.find(a => a.name === attr.name);
1698
+ return {
1699
+ $implicit: item,
1700
+ attr,
1701
+ value: itemAttr?.value
1702
+ };
1703
+ }
1704
+ async loadLookupReferenceOptions() {
1705
+ const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);
1706
+ if (lookupAttrs.length === 0)
1707
+ return;
1708
+ const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
1709
+ const entries = await Promise.all(lookupNames.map(async (name) => {
1710
+ const result = await this.sparkService.getLookupReference(name);
1711
+ return [name, result];
1712
+ }));
1713
+ this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}));
1714
+ }
1715
+ onRowClick(item) {
1716
+ this.rowClicked.emit(item);
1717
+ const et = this.entityType();
1718
+ if (et) {
1719
+ this.router.navigate(['/po', et.alias || et.id, item.id]);
1720
+ }
1721
+ }
1722
+ onCreate() {
1723
+ this.createClicked.emit();
1724
+ const et = this.entityType();
1725
+ if (et) {
1726
+ this.router.navigate(['/po', et.alias || et.id, 'new']);
1727
+ }
1728
+ }
1729
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkQueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1730
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkQueryListComponent, isStandalone: true, selector: "spark-query-list", inputs: { extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClicked: "rowClicked", createClicked: "createClicked" }, queries: [{ propertyName: "columnTemplates", predicate: SparkColumnTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getColumnTemplateContext(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1731
+ }
1732
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkQueryListComponent, decorators: [{
1733
+ type: Component,
1734
+ args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, FormsModule, RouterModule, BsAlertComponent, BsContainerComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ (attr.label | resolveTranslation) || attr.name }}\n </div>\n }\n\n <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getColumnTemplateContext(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
1735
+ }], ctorParameters: () => [], propDecorators: { columnTemplates: [{
1736
+ type: ContentChildren,
1737
+ args: [SparkColumnTemplateDirective]
1738
+ }], extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }] } });
1739
+
1740
+ var sparkQueryList_component = /*#__PURE__*/Object.freeze({
1741
+ __proto__: null,
1742
+ SparkQueryListComponent: SparkQueryListComponent
1743
+ });
1744
+
1745
+ class SparkRetryActionModalComponent {
1746
+ retryActionService = inject(RetryActionService);
1747
+ colors = Color;
1748
+ isOpen = computed(() => this.retryActionService.payload() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1749
+ onOption(option) {
1750
+ const payload = this.retryActionService.payload();
1751
+ if (!payload)
1752
+ return;
1753
+ this.retryActionService.respond({
1754
+ step: payload.step,
1755
+ option,
1756
+ persistentObject: payload.persistentObject
1757
+ });
1758
+ }
1759
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkRetryActionModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1760
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkRetryActionModalComponent, isStandalone: true, selector: "spark-retry-action-modal", ngImport: i0, template: `
1761
+ <bs-modal [isOpen]="isOpen()" (isOpenChange)="!$event && onOption('Cancel')">
1762
+ <div *bsModal>
1763
+ <div bsModalHeader>
1764
+ <h5 class="modal-title">{{ retryActionService.payload()?.title }}</h5>
1765
+ </div>
1766
+ @if (retryActionService.payload()?.message; as message) {
1767
+ <div bsModalBody>
1768
+ <p>{{ message }}</p>
1769
+ </div>
1770
+ }
1771
+ <div bsModalFooter>
1772
+ @for (option of retryActionService.payload()?.options; track option) {
1773
+ <button
1774
+ type="button"
1775
+ [color]="option === 'Cancel' ? colors.secondary : colors.primary"
1776
+ (click)="onOption(option)">
1777
+ {{ option }}
1778
+ </button>
1779
+ }
1780
+ </div>
1781
+ </div>
1782
+ </bs-modal>
1783
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1784
+ }
1785
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkRetryActionModalComponent, decorators: [{
1786
+ type: Component,
1787
+ args: [{
1788
+ selector: 'spark-retry-action-modal',
1789
+ imports: [CommonModule, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsButtonTypeDirective],
1790
+ template: `
1791
+ <bs-modal [isOpen]="isOpen()" (isOpenChange)="!$event && onOption('Cancel')">
1792
+ <div *bsModal>
1793
+ <div bsModalHeader>
1794
+ <h5 class="modal-title">{{ retryActionService.payload()?.title }}</h5>
1795
+ </div>
1796
+ @if (retryActionService.payload()?.message; as message) {
1797
+ <div bsModalBody>
1798
+ <p>{{ message }}</p>
1799
+ </div>
1800
+ }
1801
+ <div bsModalFooter>
1802
+ @for (option of retryActionService.payload()?.options; track option) {
1803
+ <button
1804
+ type="button"
1805
+ [color]="option === 'Cancel' ? colors.secondary : colors.primary"
1806
+ (click)="onOption(option)">
1807
+ {{ option }}
1808
+ </button>
1809
+ }
1810
+ </div>
1811
+ </div>
1812
+ </bs-modal>
1813
+ `,
1814
+ changeDetection: ChangeDetectionStrategy.OnPush
1815
+ }]
1816
+ }] });
1817
+
1818
+ class RouterLinkPipe {
1819
+ transform(unit) {
1820
+ if (unit.type === 'query') {
1821
+ return ['/query', unit.alias || unit.queryId];
1822
+ }
1823
+ else if (unit.type === 'persistentObject') {
1824
+ return ['/po', unit.alias || unit.persistentObjectId];
1825
+ }
1826
+ return ['/'];
1827
+ }
1828
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RouterLinkPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1829
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: RouterLinkPipe, isStandalone: true, name: "routerLink" });
1830
+ }
1831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RouterLinkPipe, decorators: [{
1832
+ type: Pipe,
1833
+ args: [{ name: 'routerLink', standalone: true, pure: true }]
1834
+ }] });
1835
+
1836
+ class IconNamePipe {
1837
+ transform(value, fallback) {
1838
+ const iconClass = value || fallback;
1839
+ // Strip 'bi-' prefix if present
1840
+ return iconClass.startsWith('bi-') ? iconClass.substring(3) : iconClass;
1841
+ }
1842
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1843
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: IconNamePipe, isStandalone: true, name: "iconName" });
1844
+ }
1845
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: IconNamePipe, decorators: [{
1846
+ type: Pipe,
1847
+ args: [{ name: 'iconName', standalone: true, pure: true }]
1848
+ }] });
1849
+
1850
+ function sparkRoutes(config) {
1851
+ return [
1852
+ {
1853
+ path: 'query/:queryId',
1854
+ loadComponent: config?.queryList ?? (() => Promise.resolve().then(function () { return sparkQueryList_component; }).then(m => m.SparkQueryListComponent))
1855
+ },
1856
+ {
1857
+ path: 'po/:type/new',
1858
+ loadComponent: config?.poCreate ?? (() => Promise.resolve().then(function () { return sparkPoCreate_component; }).then(m => m.SparkPoCreateComponent))
1859
+ },
1860
+ {
1861
+ path: 'po/:type/:id/edit',
1862
+ loadComponent: config?.poEdit ?? (() => Promise.resolve().then(function () { return sparkPoEdit_component; }).then(m => m.SparkPoEditComponent))
1863
+ },
1864
+ {
1865
+ path: 'po/:type/:id',
1866
+ loadComponent: config?.poDetail ?? (() => Promise.resolve().then(function () { return sparkPoDetail_component; }).then(m => m.SparkPoDetailComponent))
1867
+ },
1868
+ {
1869
+ path: 'po/:type',
1870
+ loadComponent: config?.queryList ?? (() => Promise.resolve().then(function () { return sparkQueryList_component; }).then(m => m.SparkQueryListComponent))
1871
+ }
1872
+ ];
1873
+ }
1874
+
1875
+ function provideSpark(config) {
1876
+ return [
1877
+ {
1878
+ provide: SPARK_CONFIG,
1879
+ useValue: { ...defaultSparkConfig, ...config }
1880
+ }
1881
+ ];
1882
+ }
1883
+
1884
+ /**
1885
+ * Generated bundle index. Do not edit.
1886
+ */
1887
+
1888
+ export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, ELookupDisplayType, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RetryActionService, RouterLinkPipe, SPARK_CONFIG, ShowedOn, SparkColumnTemplateDirective, SparkDetailFieldTemplateDirective, SparkFieldTemplateDirective, SparkIconComponent, SparkIconRegistry, SparkLanguageService, SparkPoCreateComponent, SparkPoDetailComponent, SparkPoEditComponent, SparkPoFormComponent, SparkQueryListComponent, SparkRetryActionModalComponent, SparkService, TranslateKeyPipe, defaultSparkConfig, hasShowedOnFlag, provideSpark, resolveTranslation, sparkRoutes };
1889
+ //# sourceMappingURL=mintplayer-ng-spark.mjs.map