@mintplayer/ng-spark 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mintplayer-ng-spark-icon.mjs +35 -0
- package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-models.mjs +48 -0
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs +427 -0
- package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-po-create.mjs +121 -0
- package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs +368 -0
- package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs +139 -0
- package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +393 -0
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-query-list.mjs +392 -0
- package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-renderers.mjs +25 -0
- package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +87 -0
- package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-routes.mjs +31 -0
- package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark-services.mjs +348 -0
- package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -0
- package/fesm2022/mintplayer-ng-spark.mjs +17 -2262
- package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
- package/package.json +49 -1
- package/types/mintplayer-ng-spark-icon.d.ts +13 -0
- package/types/mintplayer-ng-spark-models.d.ts +275 -0
- package/types/mintplayer-ng-spark-pipes.d.ts +143 -0
- package/types/mintplayer-ng-spark-po-create.d.ts +29 -0
- package/types/mintplayer-ng-spark-po-detail.d.ts +88 -0
- package/types/mintplayer-ng-spark-po-edit.d.ts +31 -0
- package/types/mintplayer-ng-spark-po-form.d.ts +84 -0
- package/types/mintplayer-ng-spark-query-list.d.ts +60 -0
- package/types/mintplayer-ng-spark-renderers.d.ts +68 -0
- package/types/mintplayer-ng-spark-retry-action-modal.d.ts +14 -0
- package/types/mintplayer-ng-spark-routes.d.ts +12 -0
- package/types/mintplayer-ng-spark-services.d.ts +100 -0
- package/types/mintplayer-ng-spark.d.ts +3 -855
|
@@ -1,2270 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { signal, InjectionToken, Injectable, inject, NgZone, Pipe, input, computed, ChangeDetectionStrategy, Component, model, output, effect, DestroyRef } from '@angular/core';
|
|
3
|
-
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
4
|
-
import { firstValueFrom, Observable } from 'rxjs';
|
|
5
|
-
import * as i1 from '@angular/common';
|
|
6
|
-
import { CommonModule, NgTemplateOutlet, NgComponentOutlet } 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 { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
|
|
11
|
-
import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
|
|
12
|
-
import { BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective } from '@mintplayer/ng-bootstrap/grid';
|
|
13
|
-
import { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';
|
|
14
|
-
import { BsButtonTypeDirective } from '@mintplayer/ng-bootstrap/button-type';
|
|
15
|
-
import { BsSelectComponent, BsSelectOption } from '@mintplayer/ng-bootstrap/select';
|
|
16
|
-
import { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';
|
|
17
|
-
import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
|
|
18
|
-
import { BsToggleButtonComponent } from '@mintplayer/ng-bootstrap/toggle-button';
|
|
19
|
-
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
20
|
-
import { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';
|
|
21
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
22
|
-
import { BsTableComponent } from '@mintplayer/ng-bootstrap/table';
|
|
23
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
24
|
-
import * as i2$1 from '@angular/router';
|
|
25
|
-
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|
26
|
-
import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
|
|
27
|
-
import { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';
|
|
28
|
-
import { BsButtonGroupComponent } from '@mintplayer/ng-bootstrap/button-group';
|
|
29
|
-
import { VirtualDatatableDataSource, BsVirtualDatatableComponent, BsVirtualRowTemplateDirective } from '@mintplayer/ng-bootstrap/virtual-datatable';
|
|
30
|
-
|
|
31
|
-
/** Global reactive language state — shared across library boundaries via globalThis */
|
|
32
|
-
const currentLanguage = (globalThis.__sparkCurrentLanguage ??= signal('en'));
|
|
33
|
-
function resolveTranslation(ts, lang) {
|
|
34
|
-
if (!ts)
|
|
35
|
-
return '';
|
|
36
|
-
const language = lang ?? currentLanguage();
|
|
37
|
-
return ts[language] ?? ts['en'] ?? Object.values(ts)[0] ?? '';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Flags enum controlling on which pages an attribute should be displayed.
|
|
42
|
-
* Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject
|
|
43
|
-
*/
|
|
44
|
-
var ShowedOn;
|
|
45
|
-
(function (ShowedOn) {
|
|
46
|
-
ShowedOn[ShowedOn["Query"] = 1] = "Query";
|
|
47
|
-
ShowedOn[ShowedOn["PersistentObject"] = 2] = "PersistentObject";
|
|
48
|
-
})(ShowedOn || (ShowedOn = {}));
|
|
49
|
-
/**
|
|
50
|
-
* Helper function to check if a ShowedOn value includes a specific flag.
|
|
51
|
-
*/
|
|
52
|
-
function hasShowedOnFlag(value, flag) {
|
|
53
|
-
if (value === undefined)
|
|
54
|
-
return true; // Default: show on all pages
|
|
55
|
-
// Handle string values from JSON (e.g., "Query, PersistentObject")
|
|
56
|
-
if (typeof value === 'string') {
|
|
57
|
-
const parts = value.split(',').map(s => s.trim());
|
|
58
|
-
const flagName = ShowedOn[flag];
|
|
59
|
-
return parts.includes(flagName);
|
|
60
|
-
}
|
|
61
|
-
// Handle numeric flag values
|
|
62
|
-
return (value & flag) === flag;
|
|
63
|
-
}
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
64
2
|
|
|
65
3
|
const SPARK_CONFIG = new InjectionToken('SPARK_CONFIG');
|
|
66
4
|
const defaultSparkConfig = {
|
|
67
5
|
baseUrl: '/spark'
|
|
68
6
|
};
|
|
69
7
|
|
|
70
|
-
var ELookupDisplayType;
|
|
71
|
-
(function (ELookupDisplayType) {
|
|
72
|
-
ELookupDisplayType[ELookupDisplayType["Dropdown"] = 0] = "Dropdown";
|
|
73
|
-
ELookupDisplayType[ELookupDisplayType["Modal"] = 1] = "Modal";
|
|
74
|
-
})(ELookupDisplayType || (ELookupDisplayType = {}));
|
|
75
|
-
|
|
76
|
-
class RetryActionService {
|
|
77
|
-
resolveRetry = null;
|
|
78
|
-
payload = signal(null, ...(ngDevMode ? [{ debugName: "payload" }] : []));
|
|
79
|
-
show(payload) {
|
|
80
|
-
this.payload.set(payload);
|
|
81
|
-
return new Promise(resolve => { this.resolveRetry = resolve; });
|
|
82
|
-
}
|
|
83
|
-
respond(result) {
|
|
84
|
-
this.payload.set(null);
|
|
85
|
-
this.resolveRetry?.(result);
|
|
86
|
-
this.resolveRetry = null;
|
|
87
|
-
}
|
|
88
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RetryActionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
89
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RetryActionService, providedIn: 'root' });
|
|
90
|
-
}
|
|
91
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RetryActionService, decorators: [{
|
|
92
|
-
type: Injectable,
|
|
93
|
-
args: [{ providedIn: 'root' }]
|
|
94
|
-
}] });
|
|
95
|
-
|
|
96
|
-
class SparkService {
|
|
97
|
-
config = inject(SPARK_CONFIG, { optional: true });
|
|
98
|
-
baseUrl = this.config?.baseUrl ?? '/spark';
|
|
99
|
-
http = inject(HttpClient);
|
|
100
|
-
retryActionService = inject(RetryActionService);
|
|
101
|
-
// Entity Types
|
|
102
|
-
async getEntityTypes() {
|
|
103
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/types`));
|
|
104
|
-
}
|
|
105
|
-
async getEntityType(id) {
|
|
106
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/types/${encodeURIComponent(id)}`));
|
|
107
|
-
}
|
|
108
|
-
async getEntityTypeByClrType(clrType) {
|
|
109
|
-
const types = await this.getEntityTypes();
|
|
110
|
-
return types.find(t => t.clrType === clrType);
|
|
111
|
-
}
|
|
112
|
-
// Permissions
|
|
113
|
-
async getPermissions(entityTypeId) {
|
|
114
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/permissions/${encodeURIComponent(entityTypeId)}`));
|
|
115
|
-
}
|
|
116
|
-
// Queries
|
|
117
|
-
async getQueries() {
|
|
118
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/queries`));
|
|
119
|
-
}
|
|
120
|
-
async getQuery(id) {
|
|
121
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/queries/${encodeURIComponent(id)}`));
|
|
122
|
-
}
|
|
123
|
-
async getQueryByName(name) {
|
|
124
|
-
const queries = await this.getQueries();
|
|
125
|
-
return queries.find(q => q.name === name);
|
|
126
|
-
}
|
|
127
|
-
async executeQuery(queryId, options) {
|
|
128
|
-
let params = new HttpParams();
|
|
129
|
-
if (options?.sortColumns?.length) {
|
|
130
|
-
params = params.set('sortColumns', options.sortColumns.map(c => `${c.property}:${c.direction === 'descending' ? 'desc' : 'asc'}`).join(','));
|
|
131
|
-
}
|
|
132
|
-
if (options?.parentId)
|
|
133
|
-
params = params.set('parentId', options.parentId);
|
|
134
|
-
if (options?.parentType)
|
|
135
|
-
params = params.set('parentType', options.parentType);
|
|
136
|
-
if (options?.skip != null)
|
|
137
|
-
params = params.set('skip', options.skip);
|
|
138
|
-
if (options?.take != null)
|
|
139
|
-
params = params.set('take', options.take);
|
|
140
|
-
if (options?.search)
|
|
141
|
-
params = params.set('search', options.search);
|
|
142
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/queries/${encodeURIComponent(queryId)}/execute`, { params }));
|
|
143
|
-
}
|
|
144
|
-
async executeQueryByName(queryName, options) {
|
|
145
|
-
const query = await this.getQueryByName(queryName);
|
|
146
|
-
return query ? this.executeQuery(query.id, { parentId: options?.parentId, parentType: options?.parentType }) : { data: [], totalRecords: 0, skip: 0, take: 50 };
|
|
147
|
-
}
|
|
148
|
-
// Program Units
|
|
149
|
-
async getProgramUnits() {
|
|
150
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/program-units`));
|
|
151
|
-
}
|
|
152
|
-
// Persistent Objects
|
|
153
|
-
async list(type) {
|
|
154
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/po/${encodeURIComponent(type)}`));
|
|
155
|
-
}
|
|
156
|
-
async get(type, id) {
|
|
157
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`));
|
|
158
|
-
}
|
|
159
|
-
async create(type, data) {
|
|
160
|
-
return this.postWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}`, { persistentObject: data });
|
|
161
|
-
}
|
|
162
|
-
async update(type, id, data) {
|
|
163
|
-
return this.putWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, { persistentObject: data });
|
|
164
|
-
}
|
|
165
|
-
async delete(type, id) {
|
|
166
|
-
return this.deleteWithRetry(`${this.baseUrl}/po/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, {});
|
|
167
|
-
}
|
|
168
|
-
// Custom Actions
|
|
169
|
-
async getCustomActions(objectTypeId) {
|
|
170
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/actions/${encodeURIComponent(objectTypeId)}`));
|
|
171
|
-
}
|
|
172
|
-
async executeCustomAction(objectTypeId, actionName, parent, selectedItems) {
|
|
173
|
-
const body = { parent, selectedItems };
|
|
174
|
-
return this.postWithRetry(`${this.baseUrl}/actions/${encodeURIComponent(objectTypeId)}/${encodeURIComponent(actionName)}`, body);
|
|
175
|
-
}
|
|
176
|
-
// LookupReferences
|
|
177
|
-
async getLookupReferences() {
|
|
178
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/lookupref`));
|
|
179
|
-
}
|
|
180
|
-
async getLookupReference(name) {
|
|
181
|
-
return firstValueFrom(this.http.get(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`));
|
|
182
|
-
}
|
|
183
|
-
async addLookupReferenceValue(name, value) {
|
|
184
|
-
return firstValueFrom(this.http.post(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}`, value));
|
|
185
|
-
}
|
|
186
|
-
async updateLookupReferenceValue(name, key, value) {
|
|
187
|
-
return firstValueFrom(this.http.put(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`, value));
|
|
188
|
-
}
|
|
189
|
-
async deleteLookupReferenceValue(name, key) {
|
|
190
|
-
return firstValueFrom(this.http.delete(`${this.baseUrl}/lookupref/${encodeURIComponent(name)}/${encodeURIComponent(key)}`));
|
|
191
|
-
}
|
|
192
|
-
// Retry Action helpers
|
|
193
|
-
async postWithRetry(url, body) {
|
|
194
|
-
try {
|
|
195
|
-
return await firstValueFrom(this.http.post(url, body));
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
return this.handleRetryError(error, () => this.postWithRetry(url, body), body);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async putWithRetry(url, body) {
|
|
202
|
-
try {
|
|
203
|
-
return await firstValueFrom(this.http.put(url, body));
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
return this.handleRetryError(error, () => this.putWithRetry(url, body), body);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
async deleteWithRetry(url, body) {
|
|
210
|
-
try {
|
|
211
|
-
const hasRetry = body.retryResults && body.retryResults.length > 0;
|
|
212
|
-
return await firstValueFrom(hasRetry
|
|
213
|
-
? this.http.delete(url, { body })
|
|
214
|
-
: this.http.delete(url));
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
return this.handleRetryError(error, () => this.deleteWithRetry(url, body), body);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async handleRetryError(error, retryFn, body) {
|
|
221
|
-
if (error.status !== 449 || error.error?.type !== 'retry-action') {
|
|
222
|
-
throw error;
|
|
223
|
-
}
|
|
224
|
-
const payload = error.error;
|
|
225
|
-
const result = await this.retryActionService.show(payload);
|
|
226
|
-
// Modal dismissed (Escape/X) - Cancel was not an explicit developer option
|
|
227
|
-
if (result.option === 'Cancel' && !payload.options.includes('Cancel')) {
|
|
228
|
-
throw error;
|
|
229
|
-
}
|
|
230
|
-
body.retryResults = [...(body.retryResults || []), result];
|
|
231
|
-
return retryFn();
|
|
232
|
-
}
|
|
233
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
234
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkService, providedIn: 'root' });
|
|
235
|
-
}
|
|
236
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkService, decorators: [{
|
|
237
|
-
type: Injectable,
|
|
238
|
-
args: [{ providedIn: 'root' }]
|
|
239
|
-
}] });
|
|
240
|
-
|
|
241
|
-
class SparkStreamingService {
|
|
242
|
-
config = inject(SPARK_CONFIG, { optional: true });
|
|
243
|
-
baseUrl = this.config?.baseUrl ?? '/spark';
|
|
244
|
-
ngZone = inject(NgZone);
|
|
245
|
-
connectToStreamingQuery(queryId) {
|
|
246
|
-
return new Observable(subscriber => {
|
|
247
|
-
let ws = null;
|
|
248
|
-
let retryCount = 0;
|
|
249
|
-
let retryTimeout = null;
|
|
250
|
-
let closed = false;
|
|
251
|
-
const maxRetries = 10;
|
|
252
|
-
const maxDelay = 30000;
|
|
253
|
-
const connect = () => {
|
|
254
|
-
if (closed)
|
|
255
|
-
return;
|
|
256
|
-
const url = this.buildWebSocketUrl(queryId);
|
|
257
|
-
ws = new WebSocket(url);
|
|
258
|
-
ws.onopen = () => {
|
|
259
|
-
retryCount = 0; // Reset on successful connect
|
|
260
|
-
};
|
|
261
|
-
ws.onmessage = (event) => {
|
|
262
|
-
try {
|
|
263
|
-
const message = JSON.parse(event.data);
|
|
264
|
-
this.ngZone.run(() => subscriber.next(message));
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
// Ignore malformed messages
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
ws.onerror = () => {
|
|
271
|
-
// Error will trigger onclose
|
|
272
|
-
};
|
|
273
|
-
ws.onclose = (event) => {
|
|
274
|
-
if (closed)
|
|
275
|
-
return;
|
|
276
|
-
// Don't reconnect on normal closure
|
|
277
|
-
if (event.code === 1000) {
|
|
278
|
-
this.ngZone.run(() => subscriber.complete());
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
// Reconnect with exponential backoff
|
|
282
|
-
if (retryCount < maxRetries) {
|
|
283
|
-
const delay = Math.min(1000 * Math.pow(2, retryCount), maxDelay);
|
|
284
|
-
retryCount++;
|
|
285
|
-
retryTimeout = setTimeout(() => connect(), delay);
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
this.ngZone.run(() => subscriber.error(new Error('WebSocket connection failed after maximum retries')));
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
|
-
connect();
|
|
293
|
-
// Teardown: close WebSocket on unsubscribe
|
|
294
|
-
return () => {
|
|
295
|
-
closed = true;
|
|
296
|
-
if (retryTimeout) {
|
|
297
|
-
clearTimeout(retryTimeout);
|
|
298
|
-
}
|
|
299
|
-
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
300
|
-
ws.close(1000, 'Client unsubscribed');
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
buildWebSocketUrl(queryId) {
|
|
306
|
-
const encodedId = encodeURIComponent(queryId);
|
|
307
|
-
const path = `${this.baseUrl}/queries/${encodedId}/stream`;
|
|
308
|
-
// If baseUrl is absolute (starts with http/https), replace protocol
|
|
309
|
-
if (this.baseUrl.startsWith('http://')) {
|
|
310
|
-
return path.replace(/^http:\/\//, 'ws://');
|
|
311
|
-
}
|
|
312
|
-
if (this.baseUrl.startsWith('https://')) {
|
|
313
|
-
return path.replace(/^https:\/\//, 'wss://');
|
|
314
|
-
}
|
|
315
|
-
// Relative URL — construct from window.location
|
|
316
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
317
|
-
return `${protocol}//${window.location.host}${path}`;
|
|
318
|
-
}
|
|
319
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkStreamingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
320
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkStreamingService, providedIn: 'root' });
|
|
321
|
-
}
|
|
322
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkStreamingService, decorators: [{
|
|
323
|
-
type: Injectable,
|
|
324
|
-
args: [{ providedIn: 'root' }]
|
|
325
|
-
}] });
|
|
326
|
-
|
|
327
|
-
class SparkLanguageService {
|
|
328
|
-
http = inject(HttpClient);
|
|
329
|
-
config = inject(SPARK_CONFIG, { optional: true });
|
|
330
|
-
baseUrl = this.config?.baseUrl ?? '/spark';
|
|
331
|
-
currentLang = signal('en', ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
|
|
332
|
-
translationsMap = signal({}, ...(ngDevMode ? [{ debugName: "translationsMap" }] : []));
|
|
333
|
-
language = this.currentLang.asReadonly();
|
|
334
|
-
languages = signal({}, ...(ngDevMode ? [{ debugName: "languages" }] : []));
|
|
335
|
-
constructor() {
|
|
336
|
-
this.loadCulture();
|
|
337
|
-
this.loadTranslations();
|
|
338
|
-
}
|
|
339
|
-
async loadCulture() {
|
|
340
|
-
const config = await firstValueFrom(this.http.get(`${this.baseUrl}/culture`));
|
|
341
|
-
this.languages.set(config.languages);
|
|
342
|
-
const saved = localStorage.getItem('spark-lang');
|
|
343
|
-
const lang = saved ?? config.defaultLanguage;
|
|
344
|
-
this.currentLang.set(lang);
|
|
345
|
-
currentLanguage.set(lang);
|
|
346
|
-
}
|
|
347
|
-
async loadTranslations() {
|
|
348
|
-
const t = await firstValueFrom(this.http.get(`${this.baseUrl}/translations`));
|
|
349
|
-
this.translationsMap.set(t);
|
|
350
|
-
}
|
|
351
|
-
setLanguage(lang) {
|
|
352
|
-
this.currentLang.set(lang);
|
|
353
|
-
currentLanguage.set(lang);
|
|
354
|
-
localStorage.setItem('spark-lang', lang);
|
|
355
|
-
}
|
|
356
|
-
resolve(ts) {
|
|
357
|
-
if (!ts)
|
|
358
|
-
return '';
|
|
359
|
-
const lang = this.currentLang();
|
|
360
|
-
return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? '';
|
|
361
|
-
}
|
|
362
|
-
t(key) {
|
|
363
|
-
const ts = this.translationsMap()[key];
|
|
364
|
-
return this.resolve(ts) || key;
|
|
365
|
-
}
|
|
366
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
367
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkLanguageService, providedIn: 'root' });
|
|
368
|
-
}
|
|
369
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkLanguageService, decorators: [{
|
|
370
|
-
type: Injectable,
|
|
371
|
-
args: [{ providedIn: 'root' }]
|
|
372
|
-
}], ctorParameters: () => [] });
|
|
373
|
-
|
|
374
|
-
class TranslateKeyPipe {
|
|
375
|
-
lang = inject(SparkLanguageService);
|
|
376
|
-
transform(key) {
|
|
377
|
-
return this.lang.t(key);
|
|
378
|
-
}
|
|
379
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: TranslateKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
380
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: TranslateKeyPipe, isStandalone: true, name: "t", pure: false });
|
|
381
|
-
}
|
|
382
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: TranslateKeyPipe, decorators: [{
|
|
383
|
-
type: Pipe,
|
|
384
|
-
args: [{ name: 't', pure: false, standalone: true }]
|
|
385
|
-
}] });
|
|
386
|
-
|
|
387
|
-
class ResolveTranslationPipe {
|
|
388
|
-
transform(value, fallback) {
|
|
389
|
-
return resolveTranslation(value) || fallback || '';
|
|
390
|
-
}
|
|
391
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ResolveTranslationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
392
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ResolveTranslationPipe, isStandalone: true, name: "resolveTranslation", pure: false });
|
|
393
|
-
}
|
|
394
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ResolveTranslationPipe, decorators: [{
|
|
395
|
-
type: Pipe,
|
|
396
|
-
args: [{ name: 'resolveTranslation', standalone: true, pure: false }]
|
|
397
|
-
}] });
|
|
398
|
-
|
|
399
|
-
class InputTypePipe {
|
|
400
|
-
transform(dataType) {
|
|
401
|
-
switch (dataType) {
|
|
402
|
-
case 'number':
|
|
403
|
-
case 'decimal':
|
|
404
|
-
return 'number';
|
|
405
|
-
case 'boolean':
|
|
406
|
-
return 'checkbox';
|
|
407
|
-
case 'datetime':
|
|
408
|
-
return 'datetime-local';
|
|
409
|
-
case 'date':
|
|
410
|
-
return 'date';
|
|
411
|
-
case 'color':
|
|
412
|
-
return 'color';
|
|
413
|
-
default:
|
|
414
|
-
return 'text';
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InputTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
418
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: InputTypePipe, isStandalone: true, name: "inputType" });
|
|
419
|
-
}
|
|
420
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InputTypePipe, decorators: [{
|
|
421
|
-
type: Pipe,
|
|
422
|
-
args: [{ name: 'inputType', standalone: true, pure: true }]
|
|
423
|
-
}] });
|
|
424
|
-
|
|
425
|
-
class LookupDisplayValuePipe {
|
|
426
|
-
transform(attr, formData, lookupRefOptions) {
|
|
427
|
-
const currentValue = formData[attr.name];
|
|
428
|
-
if (currentValue == null || currentValue === '')
|
|
429
|
-
return '';
|
|
430
|
-
const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
|
|
431
|
-
const options = lookupRef?.values.filter(v => v.isActive) || [];
|
|
432
|
-
const selected = options.find(o => o.key === String(currentValue));
|
|
433
|
-
if (!selected)
|
|
434
|
-
return String(currentValue);
|
|
435
|
-
return resolveTranslation(selected.values) || selected.key;
|
|
436
|
-
}
|
|
437
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
438
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayValuePipe, isStandalone: true, name: "lookupDisplayValue" });
|
|
439
|
-
}
|
|
440
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayValuePipe, decorators: [{
|
|
441
|
-
type: Pipe,
|
|
442
|
-
args: [{ name: 'lookupDisplayValue', standalone: true, pure: true }]
|
|
443
|
-
}] });
|
|
444
|
-
|
|
445
|
-
class LookupDisplayTypePipe {
|
|
446
|
-
transform(attr, lookupRefOptions) {
|
|
447
|
-
const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
|
|
448
|
-
return lookupRef?.displayType ?? ELookupDisplayType.Dropdown;
|
|
449
|
-
}
|
|
450
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
451
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayTypePipe, isStandalone: true, name: "lookupDisplayType" });
|
|
452
|
-
}
|
|
453
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupDisplayTypePipe, decorators: [{
|
|
454
|
-
type: Pipe,
|
|
455
|
-
args: [{ name: 'lookupDisplayType', standalone: true, pure: true }]
|
|
456
|
-
}] });
|
|
457
|
-
|
|
458
|
-
class LookupOptionsPipe {
|
|
459
|
-
transform(attr, lookupRefOptions) {
|
|
460
|
-
const lookupRef = attr.lookupReferenceType ? lookupRefOptions[attr.lookupReferenceType] : null;
|
|
461
|
-
return lookupRef?.values.filter(v => v.isActive) || [];
|
|
462
|
-
}
|
|
463
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupOptionsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
464
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: LookupOptionsPipe, isStandalone: true, name: "lookupOptions" });
|
|
465
|
-
}
|
|
466
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: LookupOptionsPipe, decorators: [{
|
|
467
|
-
type: Pipe,
|
|
468
|
-
args: [{ name: 'lookupOptions', standalone: true, pure: true }]
|
|
469
|
-
}] });
|
|
470
|
-
|
|
471
|
-
class ReferenceDisplayValuePipe {
|
|
472
|
-
lang = inject(SparkLanguageService);
|
|
473
|
-
transform(attr, formData, referenceOptions) {
|
|
474
|
-
const selectedId = formData[attr.name];
|
|
475
|
-
if (!selectedId)
|
|
476
|
-
return this.lang.t('notSelected');
|
|
477
|
-
const options = referenceOptions[attr.name] || [];
|
|
478
|
-
const selected = options.find(o => o.id === selectedId);
|
|
479
|
-
return selected?.breadcrumb || selected?.name || selectedId;
|
|
480
|
-
}
|
|
481
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
482
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ReferenceDisplayValuePipe, isStandalone: true, name: "referenceDisplayValue" });
|
|
483
|
-
}
|
|
484
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceDisplayValuePipe, decorators: [{
|
|
485
|
-
type: Pipe,
|
|
486
|
-
args: [{ name: 'referenceDisplayValue', standalone: true, pure: true }]
|
|
487
|
-
}] });
|
|
488
|
-
|
|
489
|
-
class AsDetailDisplayValuePipe {
|
|
490
|
-
lang = inject(SparkLanguageService);
|
|
491
|
-
transform(attr, formData, asDetailTypes) {
|
|
492
|
-
const value = formData[attr.name];
|
|
493
|
-
if (!value)
|
|
494
|
-
return this.lang.t('notSet');
|
|
495
|
-
const asDetailType = asDetailTypes[attr.name] || null;
|
|
496
|
-
// 1. Try displayFormat (template with {PropertyName} placeholders)
|
|
497
|
-
if (asDetailType?.displayFormat) {
|
|
498
|
-
const result = this.resolveDisplayFormat(asDetailType.displayFormat, value);
|
|
499
|
-
if (result && result.trim())
|
|
500
|
-
return result;
|
|
501
|
-
}
|
|
502
|
-
// 2. Try displayAttribute (single property name)
|
|
503
|
-
if (asDetailType?.displayAttribute && value[asDetailType.displayAttribute]) {
|
|
504
|
-
return value[asDetailType.displayAttribute];
|
|
505
|
-
}
|
|
506
|
-
// 3. Fallback to common property names
|
|
507
|
-
const displayProps = ['Name', 'Title', 'Street', 'name', 'title'];
|
|
508
|
-
for (const prop of displayProps) {
|
|
509
|
-
if (value[prop])
|
|
510
|
-
return value[prop];
|
|
511
|
-
}
|
|
512
|
-
return this.lang.t('clickToEdit');
|
|
513
|
-
}
|
|
514
|
-
resolveDisplayFormat(format, data) {
|
|
515
|
-
return format.replace(/\{(\w+)\}/g, (match, propertyName) => {
|
|
516
|
-
const value = data[propertyName];
|
|
517
|
-
return value != null ? String(value) : '';
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailDisplayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
521
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: AsDetailDisplayValuePipe, isStandalone: true, name: "asDetailDisplayValue" });
|
|
522
|
-
}
|
|
523
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailDisplayValuePipe, decorators: [{
|
|
524
|
-
type: Pipe,
|
|
525
|
-
args: [{ name: 'asDetailDisplayValue', standalone: true, pure: true }]
|
|
526
|
-
}] });
|
|
527
|
-
|
|
528
|
-
class AsDetailTypePipe {
|
|
529
|
-
transform(attr, asDetailTypes) {
|
|
530
|
-
return asDetailTypes[attr.name] || null;
|
|
531
|
-
}
|
|
532
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
533
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: AsDetailTypePipe, isStandalone: true, name: "asDetailType" });
|
|
534
|
-
}
|
|
535
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailTypePipe, decorators: [{
|
|
536
|
-
type: Pipe,
|
|
537
|
-
args: [{ name: 'asDetailType', standalone: true, pure: true }]
|
|
538
|
-
}] });
|
|
539
|
-
|
|
540
|
-
class AsDetailColumnsPipe {
|
|
541
|
-
transform(attr, asDetailTypes) {
|
|
542
|
-
const type = asDetailTypes[attr.name];
|
|
543
|
-
if (!type)
|
|
544
|
-
return [];
|
|
545
|
-
return type.attributes
|
|
546
|
-
.filter(a => a.isVisible)
|
|
547
|
-
.sort((a, b) => a.order - b.order);
|
|
548
|
-
}
|
|
549
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailColumnsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
550
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: AsDetailColumnsPipe, isStandalone: true, name: "asDetailColumns" });
|
|
551
|
-
}
|
|
552
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailColumnsPipe, decorators: [{
|
|
553
|
-
type: Pipe,
|
|
554
|
-
args: [{ name: 'asDetailColumns', standalone: true, pure: true }]
|
|
555
|
-
}] });
|
|
556
|
-
|
|
557
|
-
class AsDetailCellValuePipe {
|
|
558
|
-
transform(row, parentAttr, col, asDetailRefOptions) {
|
|
559
|
-
const value = row[col.name];
|
|
560
|
-
if (value == null)
|
|
561
|
-
return '';
|
|
562
|
-
if (col.dataType === 'Reference' && col.query) {
|
|
563
|
-
const parentOptions = asDetailRefOptions[parentAttr.name];
|
|
564
|
-
if (parentOptions) {
|
|
565
|
-
const options = parentOptions[col.name];
|
|
566
|
-
if (options) {
|
|
567
|
-
const match = options.find(o => o.id === value);
|
|
568
|
-
if (match)
|
|
569
|
-
return match.breadcrumb || match.name || String(value);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return String(value);
|
|
574
|
-
}
|
|
575
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailCellValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
576
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: AsDetailCellValuePipe, isStandalone: true, name: "asDetailCellValue" });
|
|
577
|
-
}
|
|
578
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AsDetailCellValuePipe, decorators: [{
|
|
579
|
-
type: Pipe,
|
|
580
|
-
args: [{ name: 'asDetailCellValue', standalone: true, pure: true }]
|
|
581
|
-
}] });
|
|
582
|
-
|
|
583
|
-
class CanCreateDetailRowPipe {
|
|
584
|
-
transform(attr, permissions) {
|
|
585
|
-
const perms = permissions[attr.name];
|
|
586
|
-
return perms ? perms.canCreate : true;
|
|
587
|
-
}
|
|
588
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CanCreateDetailRowPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
589
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: CanCreateDetailRowPipe, isStandalone: true, name: "canCreateDetailRow" });
|
|
590
|
-
}
|
|
591
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CanCreateDetailRowPipe, decorators: [{
|
|
592
|
-
type: Pipe,
|
|
593
|
-
args: [{ name: 'canCreateDetailRow', standalone: true, pure: true }]
|
|
594
|
-
}] });
|
|
595
|
-
|
|
596
|
-
class CanDeleteDetailRowPipe {
|
|
597
|
-
transform(attr, permissions) {
|
|
598
|
-
const perms = permissions[attr.name];
|
|
599
|
-
return perms ? perms.canDelete : true;
|
|
600
|
-
}
|
|
601
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CanDeleteDetailRowPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
602
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: CanDeleteDetailRowPipe, isStandalone: true, name: "canDeleteDetailRow" });
|
|
603
|
-
}
|
|
604
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CanDeleteDetailRowPipe, decorators: [{
|
|
605
|
-
type: Pipe,
|
|
606
|
-
args: [{ name: 'canDeleteDetailRow', standalone: true, pure: true }]
|
|
607
|
-
}] });
|
|
608
|
-
|
|
609
|
-
class InlineRefOptionsPipe {
|
|
610
|
-
transform(parentAttr, col, asDetailRefOptions) {
|
|
611
|
-
return asDetailRefOptions[parentAttr.name]?.[col.name] || [];
|
|
612
|
-
}
|
|
613
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InlineRefOptionsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
614
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: InlineRefOptionsPipe, isStandalone: true, name: "inlineRefOptions" });
|
|
615
|
-
}
|
|
616
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InlineRefOptionsPipe, decorators: [{
|
|
617
|
-
type: Pipe,
|
|
618
|
-
args: [{ name: 'inlineRefOptions', standalone: true, pure: true }]
|
|
619
|
-
}] });
|
|
620
|
-
|
|
621
|
-
class ReferenceAttrValuePipe {
|
|
622
|
-
transform(item, attrName) {
|
|
623
|
-
const attr = item.attributes.find(a => a.name === attrName);
|
|
624
|
-
if (!attr)
|
|
625
|
-
return '';
|
|
626
|
-
if (attr.breadcrumb)
|
|
627
|
-
return attr.breadcrumb;
|
|
628
|
-
return attr.value ?? '';
|
|
629
|
-
}
|
|
630
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceAttrValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
631
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ReferenceAttrValuePipe, isStandalone: true, name: "referenceAttrValue" });
|
|
632
|
-
}
|
|
633
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceAttrValuePipe, decorators: [{
|
|
634
|
-
type: Pipe,
|
|
635
|
-
args: [{ name: 'referenceAttrValue', standalone: true, pure: true }]
|
|
636
|
-
}] });
|
|
637
|
-
|
|
638
|
-
class ErrorForAttributePipe {
|
|
639
|
-
transform(attrName, validationErrors) {
|
|
640
|
-
const error = validationErrors.find(e => e.attributeName === attrName);
|
|
641
|
-
return error ? resolveTranslation(error.errorMessage) : null;
|
|
642
|
-
}
|
|
643
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ErrorForAttributePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
644
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ErrorForAttributePipe, isStandalone: true, name: "errorForAttribute" });
|
|
645
|
-
}
|
|
646
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ErrorForAttributePipe, decorators: [{
|
|
647
|
-
type: Pipe,
|
|
648
|
-
args: [{ name: 'errorForAttribute', standalone: true, pure: true }]
|
|
649
|
-
}] });
|
|
650
|
-
|
|
651
|
-
/** Built-in SVG icons used by ng-spark library templates. */
|
|
652
|
-
const SPARK_BUILT_IN_ICONS = {
|
|
653
|
-
'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>',
|
|
654
|
-
'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>',
|
|
655
|
-
'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>',
|
|
656
|
-
'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>',
|
|
657
|
-
'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>',
|
|
658
|
-
'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>',
|
|
659
|
-
'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>',
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
class SparkIconRegistry {
|
|
663
|
-
sanitizer = inject(DomSanitizer);
|
|
664
|
-
icons = new Map();
|
|
665
|
-
constructor() {
|
|
666
|
-
for (const [name, svg] of Object.entries(SPARK_BUILT_IN_ICONS)) {
|
|
667
|
-
this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
register(name, svg) {
|
|
671
|
-
this.icons.set(name, this.sanitizer.bypassSecurityTrustHtml(svg));
|
|
672
|
-
}
|
|
673
|
-
get(name) {
|
|
674
|
-
return this.icons.get(name);
|
|
675
|
-
}
|
|
676
|
-
has(name) {
|
|
677
|
-
return this.icons.has(name);
|
|
678
|
-
}
|
|
679
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkIconRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
680
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkIconRegistry, providedIn: 'root' });
|
|
681
|
-
}
|
|
682
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkIconRegistry, decorators: [{
|
|
683
|
-
type: Injectable,
|
|
684
|
-
args: [{ providedIn: 'root' }]
|
|
685
|
-
}], ctorParameters: () => [] });
|
|
686
|
-
|
|
687
|
-
class SparkIconComponent {
|
|
688
|
-
registry = inject(SparkIconRegistry);
|
|
689
|
-
name = input.required(...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
690
|
-
iconHtml = computed(() => this.registry.get(this.name()), ...(ngDevMode ? [{ debugName: "iconHtml" }] : []));
|
|
691
|
-
cssFallbackClass = computed(() => `bi-${this.name()}`, ...(ngDevMode ? [{ debugName: "cssFallbackClass" }] : []));
|
|
692
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
693
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkIconComponent, isStandalone: true, selector: "spark-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
694
|
-
@if (iconHtml(); as html) {
|
|
695
|
-
<span [innerHTML]="html"></span>
|
|
696
|
-
} @else {
|
|
697
|
-
<i class="bi" [class]="cssFallbackClass()"></i>
|
|
698
|
-
}
|
|
699
|
-
`, 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 });
|
|
700
|
-
}
|
|
701
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkIconComponent, decorators: [{
|
|
702
|
-
type: Component,
|
|
703
|
-
args: [{ selector: 'spark-icon', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
704
|
-
@if (iconHtml(); as html) {
|
|
705
|
-
<span [innerHTML]="html"></span>
|
|
706
|
-
} @else {
|
|
707
|
-
<i class="bi" [class]="cssFallbackClass()"></i>
|
|
708
|
-
}
|
|
709
|
-
`, 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"] }]
|
|
710
|
-
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }] } });
|
|
711
|
-
|
|
712
|
-
const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken('SparkAttributeRenderers', { factory: () => [] });
|
|
713
|
-
/**
|
|
714
|
-
* Register custom attribute renderers globally.
|
|
715
|
-
*
|
|
716
|
-
* @example
|
|
717
|
-
* provideSparkAttributeRenderers([
|
|
718
|
-
* { name: 'video-player', detailComponent: VideoDetailComponent, columnComponent: VideoColumnComponent },
|
|
719
|
-
* { name: 'color-swatch', detailComponent: ColorDetailComponent, columnComponent: ColorColumnComponent },
|
|
720
|
-
* ])
|
|
721
|
-
*/
|
|
722
|
-
function provideSparkAttributeRenderers(renderers) {
|
|
723
|
-
return {
|
|
724
|
-
provide: SPARK_ATTRIBUTE_RENDERERS,
|
|
725
|
-
useValue: renderers,
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
class SparkPoFormComponent {
|
|
730
|
-
sparkService = inject(SparkService);
|
|
731
|
-
translations = inject(SparkLanguageService);
|
|
732
|
-
rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
733
|
-
entityType = input(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
734
|
-
formData = model({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
|
|
735
|
-
validationErrors = input([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
|
|
736
|
-
showButtons = input(false, ...(ngDevMode ? [{ debugName: "showButtons" }] : []));
|
|
737
|
-
isSaving = input(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
|
|
738
|
-
parentId = input(undefined, ...(ngDevMode ? [{ debugName: "parentId" }] : []));
|
|
739
|
-
parentType = input(undefined, ...(ngDevMode ? [{ debugName: "parentType" }] : []));
|
|
740
|
-
save = output();
|
|
741
|
-
cancel = output();
|
|
742
|
-
colors = Color;
|
|
743
|
-
referenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "referenceOptions" }] : []));
|
|
744
|
-
asDetailTypes = signal({}, ...(ngDevMode ? [{ debugName: "asDetailTypes" }] : []));
|
|
745
|
-
lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
|
|
746
|
-
// Modal state for AsDetail object editing
|
|
747
|
-
editingAsDetailAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingAsDetailAttr" }] : []));
|
|
748
|
-
asDetailFormData = signal({}, ...(ngDevMode ? [{ debugName: "asDetailFormData" }] : []));
|
|
749
|
-
showAsDetailModal = signal(false, ...(ngDevMode ? [{ debugName: "showAsDetailModal" }] : []));
|
|
750
|
-
editingArrayIndex = signal(null, ...(ngDevMode ? [{ debugName: "editingArrayIndex" }] : []));
|
|
751
|
-
// Permissions for array AsDetail entity types
|
|
752
|
-
asDetailPermissions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailPermissions" }] : []));
|
|
753
|
-
// Reference options for columns within array AsDetail types (keyed by parent attr name, then column name)
|
|
754
|
-
asDetailReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : []));
|
|
755
|
-
// Modal state for Reference selection
|
|
756
|
-
editingReferenceAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingReferenceAttr" }] : []));
|
|
757
|
-
showReferenceModal = signal(false, ...(ngDevMode ? [{ debugName: "showReferenceModal" }] : []));
|
|
758
|
-
referenceModalItems = signal([], ...(ngDevMode ? [{ debugName: "referenceModalItems" }] : []));
|
|
759
|
-
referenceModalEntityType = signal(null, ...(ngDevMode ? [{ debugName: "referenceModalEntityType" }] : []));
|
|
760
|
-
referenceModalPagination = signal(undefined, ...(ngDevMode ? [{ debugName: "referenceModalPagination" }] : []));
|
|
761
|
-
referenceModalSettings = signal(new DatatableSettings({
|
|
762
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
763
|
-
page: { values: [1], selected: 1 },
|
|
764
|
-
sortColumns: []
|
|
765
|
-
}), ...(ngDevMode ? [{ debugName: "referenceModalSettings" }] : []));
|
|
766
|
-
referenceSearchTerm = '';
|
|
767
|
-
// Modal state for LookupReference selection (Modal display type)
|
|
768
|
-
editingLookupAttr = signal(null, ...(ngDevMode ? [{ debugName: "editingLookupAttr" }] : []));
|
|
769
|
-
showLookupModal = signal(false, ...(ngDevMode ? [{ debugName: "showLookupModal" }] : []));
|
|
770
|
-
lookupModalItems = signal([], ...(ngDevMode ? [{ debugName: "lookupModalItems" }] : []));
|
|
771
|
-
lookupSearchTerm = signal('', ...(ngDevMode ? [{ debugName: "lookupSearchTerm" }] : []));
|
|
772
|
-
ELookupDisplayType = ELookupDisplayType;
|
|
773
|
-
editableAttributes = computed(() => {
|
|
774
|
-
return this.entityType()?.attributes
|
|
775
|
-
.filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
|
|
776
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
777
|
-
}, ...(ngDevMode ? [{ debugName: "editableAttributes" }] : []));
|
|
778
|
-
static DEFAULT_TAB = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };
|
|
779
|
-
ungroupedAttributes = computed(() => {
|
|
780
|
-
const attrs = this.editableAttributes();
|
|
781
|
-
const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));
|
|
782
|
-
return attrs.filter(a => !a.group || !groupIds.has(a.group));
|
|
783
|
-
}, ...(ngDevMode ? [{ debugName: "ungroupedAttributes" }] : []));
|
|
784
|
-
resolvedTabs = computed(() => {
|
|
785
|
-
const et = this.entityType();
|
|
786
|
-
const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];
|
|
787
|
-
const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;
|
|
788
|
-
const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);
|
|
789
|
-
if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {
|
|
790
|
-
return [SparkPoFormComponent.DEFAULT_TAB, ...definedTabs];
|
|
791
|
-
}
|
|
792
|
-
return definedTabs;
|
|
793
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedTabs" }] : []));
|
|
794
|
-
groupsForTab(tab) {
|
|
795
|
-
const groups = this.entityType()?.groups || [];
|
|
796
|
-
if (tab.id === '__default__') {
|
|
797
|
-
return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);
|
|
798
|
-
}
|
|
799
|
-
return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);
|
|
800
|
-
}
|
|
801
|
-
attrsForGroup(group) {
|
|
802
|
-
return this.editableAttributes().filter(a => a.group === group.id);
|
|
803
|
-
}
|
|
804
|
-
referenceVisibleAttributes = computed(() => {
|
|
805
|
-
return this.referenceModalEntityType()?.attributes
|
|
806
|
-
.filter(a => a.isVisible)
|
|
807
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
808
|
-
}, ...(ngDevMode ? [{ debugName: "referenceVisibleAttributes" }] : []));
|
|
809
|
-
filteredLookupItems = computed(() => {
|
|
810
|
-
if (!this.lookupSearchTerm().trim()) {
|
|
811
|
-
return this.lookupModalItems();
|
|
812
|
-
}
|
|
813
|
-
const term = this.lookupSearchTerm().toLowerCase().trim();
|
|
814
|
-
return this.lookupModalItems().filter(item => {
|
|
815
|
-
const translation = resolveTranslation(item.values);
|
|
816
|
-
return translation.toLowerCase().includes(term) || item.key.toLowerCase().includes(term);
|
|
817
|
-
});
|
|
818
|
-
}, ...(ngDevMode ? [{ debugName: "filteredLookupItems" }] : []));
|
|
819
|
-
constructor() {
|
|
820
|
-
effect(() => {
|
|
821
|
-
const et = this.entityType();
|
|
822
|
-
const _pid = this.parentId();
|
|
823
|
-
const _ptype = this.parentType();
|
|
824
|
-
if (et) {
|
|
825
|
-
this.loadReferenceOptions();
|
|
826
|
-
this.loadAsDetailTypes();
|
|
827
|
-
this.loadLookupReferenceOptions();
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
toRecord(entries) {
|
|
832
|
-
const result = {};
|
|
833
|
-
for (const [key, value] of entries) {
|
|
834
|
-
result[key] = value;
|
|
835
|
-
}
|
|
836
|
-
return result;
|
|
837
|
-
}
|
|
838
|
-
async loadReferenceOptions() {
|
|
839
|
-
const refAttrs = this.editableAttributes().filter(a => a.dataType === 'Reference' && a.query);
|
|
840
|
-
if (refAttrs.length === 0)
|
|
841
|
-
return;
|
|
842
|
-
const entries = await Promise.all(refAttrs.filter(a => a.query).map(async (attr) => {
|
|
843
|
-
const result = await this.sparkService.executeQueryByName(attr.query, {
|
|
844
|
-
parentId: this.parentId(),
|
|
845
|
-
parentType: this.parentType(),
|
|
846
|
-
});
|
|
847
|
-
return [attr.name, result.data];
|
|
848
|
-
}));
|
|
849
|
-
this.referenceOptions.set(this.toRecord(entries));
|
|
850
|
-
}
|
|
851
|
-
async loadAsDetailTypes() {
|
|
852
|
-
const asDetailAttrs = this.editableAttributes().filter(a => a.dataType === 'AsDetail' && a.asDetailType);
|
|
853
|
-
if (asDetailAttrs.length === 0)
|
|
854
|
-
return;
|
|
855
|
-
const types = await this.sparkService.getEntityTypes();
|
|
856
|
-
const newAsDetailTypes = {};
|
|
857
|
-
for (const attr of asDetailAttrs) {
|
|
858
|
-
const asDetailType = types.find(t => t.clrType === attr.asDetailType);
|
|
859
|
-
if (asDetailType) {
|
|
860
|
-
newAsDetailTypes[attr.name] = asDetailType;
|
|
861
|
-
if (attr.isArray) {
|
|
862
|
-
const perms = await this.sparkService.getPermissions(asDetailType.id);
|
|
863
|
-
this.asDetailPermissions.update(prev => ({ ...prev, [attr.name]: perms }));
|
|
864
|
-
const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);
|
|
865
|
-
if (refCols.length > 0) {
|
|
866
|
-
const refEntries = await Promise.all(refCols.filter(c => c.query).map(async (col) => {
|
|
867
|
-
const result = await this.sparkService.executeQueryByName(col.query, {
|
|
868
|
-
parentId: this.parentId(),
|
|
869
|
-
parentType: this.parentType(),
|
|
870
|
-
});
|
|
871
|
-
return [col.name, result.data];
|
|
872
|
-
}));
|
|
873
|
-
this.asDetailReferenceOptions.update(prev => ({ ...prev, [attr.name]: this.toRecord(refEntries) }));
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
this.asDetailTypes.set(newAsDetailTypes);
|
|
879
|
-
}
|
|
880
|
-
async loadLookupReferenceOptions() {
|
|
881
|
-
const lookupAttrs = this.editableAttributes().filter(a => a.lookupReferenceType);
|
|
882
|
-
if (lookupAttrs.length === 0)
|
|
883
|
-
return;
|
|
884
|
-
const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
|
|
885
|
-
const entries = await Promise.all(lookupNames.map(async (name) => {
|
|
886
|
-
const ref = await this.sparkService.getLookupReference(name);
|
|
887
|
-
return [name, ref];
|
|
888
|
-
}));
|
|
889
|
-
this.lookupReferenceOptions.set(this.toRecord(entries));
|
|
890
|
-
}
|
|
891
|
-
getReferenceOptions(attr) {
|
|
892
|
-
return this.referenceOptions()[attr.name] || [];
|
|
893
|
-
}
|
|
894
|
-
getLookupOptions(attr) {
|
|
895
|
-
const lookupRef = attr.lookupReferenceType ? this.lookupReferenceOptions()[attr.lookupReferenceType] : null;
|
|
896
|
-
return lookupRef?.values.filter(v => v.isActive) || [];
|
|
897
|
-
}
|
|
898
|
-
// LookupReference modal methods
|
|
899
|
-
openLookupSelector(attr) {
|
|
900
|
-
this.editingLookupAttr.set(attr);
|
|
901
|
-
this.lookupSearchTerm.set('');
|
|
902
|
-
this.lookupModalItems.set(this.getLookupOptions(attr));
|
|
903
|
-
this.showLookupModal.set(true);
|
|
904
|
-
}
|
|
905
|
-
selectLookupItem(item) {
|
|
906
|
-
const attr = this.editingLookupAttr();
|
|
907
|
-
if (attr) {
|
|
908
|
-
const data = { ...this.formData() };
|
|
909
|
-
data[attr.name] = item.key;
|
|
910
|
-
this.formData.set(data);
|
|
911
|
-
}
|
|
912
|
-
this.closeLookupModal();
|
|
913
|
-
}
|
|
914
|
-
closeLookupModal() {
|
|
915
|
-
this.showLookupModal.set(false);
|
|
916
|
-
this.editingLookupAttr.set(null);
|
|
917
|
-
this.lookupModalItems.set([]);
|
|
918
|
-
this.lookupSearchTerm.set('');
|
|
919
|
-
}
|
|
920
|
-
getEditRendererComponent(attr) {
|
|
921
|
-
if (!attr.renderer)
|
|
922
|
-
return null;
|
|
923
|
-
const reg = this.rendererRegistry.find(r => r.name === attr.renderer);
|
|
924
|
-
return reg?.editComponent ?? null;
|
|
925
|
-
}
|
|
926
|
-
getEditRendererInputs(attr) {
|
|
927
|
-
return {
|
|
928
|
-
value: this.formData()[attr.name],
|
|
929
|
-
attribute: attr,
|
|
930
|
-
options: attr.rendererOptions,
|
|
931
|
-
valueChange: (newValue) => {
|
|
932
|
-
const data = { ...this.formData() };
|
|
933
|
-
data[attr.name] = newValue;
|
|
934
|
-
this.formData.set(data);
|
|
935
|
-
},
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
hasError(attrName) {
|
|
939
|
-
return this.validationErrors().some(e => e.attributeName === attrName);
|
|
940
|
-
}
|
|
941
|
-
onFieldChange() {
|
|
942
|
-
this.formData.set({ ...this.formData() });
|
|
943
|
-
}
|
|
944
|
-
onSave() {
|
|
945
|
-
this.save.emit();
|
|
946
|
-
}
|
|
947
|
-
onCancel() {
|
|
948
|
-
this.cancel.emit();
|
|
949
|
-
}
|
|
950
|
-
// AsDetail object modal methods
|
|
951
|
-
openAsDetailEditor(attr) {
|
|
952
|
-
this.editingAsDetailAttr.set(attr);
|
|
953
|
-
this.editingArrayIndex.set(null);
|
|
954
|
-
this.asDetailFormData.set({ ...(this.formData()[attr.name] || {}) });
|
|
955
|
-
this.showAsDetailModal.set(true);
|
|
956
|
-
}
|
|
957
|
-
saveAsDetailObject() {
|
|
958
|
-
const attr = this.editingAsDetailAttr();
|
|
959
|
-
if (attr) {
|
|
960
|
-
const data = { ...this.formData() };
|
|
961
|
-
if (attr.isArray) {
|
|
962
|
-
const arr = [...(data[attr.name] || [])];
|
|
963
|
-
const idx = this.editingArrayIndex();
|
|
964
|
-
if (idx !== null) {
|
|
965
|
-
arr[idx] = { ...this.asDetailFormData() };
|
|
966
|
-
}
|
|
967
|
-
else {
|
|
968
|
-
arr.push({ ...this.asDetailFormData() });
|
|
969
|
-
}
|
|
970
|
-
data[attr.name] = arr;
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
data[attr.name] = { ...this.asDetailFormData() };
|
|
974
|
-
}
|
|
975
|
-
this.formData.set(data);
|
|
976
|
-
}
|
|
977
|
-
this.closeAsDetailModal();
|
|
978
|
-
}
|
|
979
|
-
closeAsDetailModal() {
|
|
980
|
-
this.showAsDetailModal.set(false);
|
|
981
|
-
this.editingAsDetailAttr.set(null);
|
|
982
|
-
this.editingArrayIndex.set(null);
|
|
983
|
-
this.asDetailFormData.set({});
|
|
984
|
-
}
|
|
985
|
-
// Inline AsDetail methods
|
|
986
|
-
addInlineRow(attr) {
|
|
987
|
-
const data = { ...this.formData() };
|
|
988
|
-
const arr = [...(data[attr.name] || [])];
|
|
989
|
-
arr.push({});
|
|
990
|
-
data[attr.name] = arr;
|
|
991
|
-
this.formData.set(data);
|
|
992
|
-
}
|
|
993
|
-
// Array AsDetail methods
|
|
994
|
-
addArrayItem(attr) {
|
|
995
|
-
this.editingAsDetailAttr.set(attr);
|
|
996
|
-
this.editingArrayIndex.set(null);
|
|
997
|
-
this.asDetailFormData.set({});
|
|
998
|
-
this.showAsDetailModal.set(true);
|
|
999
|
-
}
|
|
1000
|
-
editArrayItem(attr, index) {
|
|
1001
|
-
this.editingAsDetailAttr.set(attr);
|
|
1002
|
-
this.editingArrayIndex.set(index);
|
|
1003
|
-
const arr = this.formData()[attr.name] || [];
|
|
1004
|
-
this.asDetailFormData.set({ ...(arr[index] || {}) });
|
|
1005
|
-
this.showAsDetailModal.set(true);
|
|
1006
|
-
}
|
|
1007
|
-
removeArrayItem(attr, index) {
|
|
1008
|
-
const data = { ...this.formData() };
|
|
1009
|
-
const arr = [...(data[attr.name] || [])];
|
|
1010
|
-
arr.splice(index, 1);
|
|
1011
|
-
data[attr.name] = arr;
|
|
1012
|
-
this.formData.set(data);
|
|
1013
|
-
}
|
|
1014
|
-
// Reference modal methods
|
|
1015
|
-
async openReferenceSelector(attr) {
|
|
1016
|
-
this.editingReferenceAttr.set(attr);
|
|
1017
|
-
this.referenceSearchTerm = '';
|
|
1018
|
-
this.referenceModalItems.set(this.getReferenceOptions(attr));
|
|
1019
|
-
const types = await this.sparkService.getEntityTypes();
|
|
1020
|
-
this.referenceModalEntityType.set(types.find(t => t.clrType === attr.referenceType) || null);
|
|
1021
|
-
this.referenceModalSettings.set(new DatatableSettings({
|
|
1022
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
1023
|
-
page: { values: [1], selected: 1 },
|
|
1024
|
-
sortColumns: []
|
|
1025
|
-
}));
|
|
1026
|
-
this.applyReferenceFilter();
|
|
1027
|
-
this.showReferenceModal.set(true);
|
|
1028
|
-
}
|
|
1029
|
-
onReferenceSearchChange() {
|
|
1030
|
-
this.referenceModalSettings().page.selected = 1;
|
|
1031
|
-
this.applyReferenceFilter();
|
|
1032
|
-
}
|
|
1033
|
-
applyReferenceFilter() {
|
|
1034
|
-
let filteredItems = this.referenceModalItems();
|
|
1035
|
-
if (this.referenceSearchTerm.trim()) {
|
|
1036
|
-
const term = this.referenceSearchTerm.toLowerCase().trim();
|
|
1037
|
-
filteredItems = this.referenceModalItems().filter(item => {
|
|
1038
|
-
if (item.name?.toLowerCase().includes(term))
|
|
1039
|
-
return true;
|
|
1040
|
-
if (item.breadcrumb?.toLowerCase().includes(term))
|
|
1041
|
-
return true;
|
|
1042
|
-
return item.attributes.some(attr => {
|
|
1043
|
-
const value = attr.breadcrumb || attr.value;
|
|
1044
|
-
if (value == null)
|
|
1045
|
-
return false;
|
|
1046
|
-
return String(value).toLowerCase().includes(term);
|
|
1047
|
-
});
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
const totalPages = Math.ceil(filteredItems.length / this.referenceModalSettings().perPage.selected) || 1;
|
|
1051
|
-
this.referenceModalPagination.set({
|
|
1052
|
-
data: filteredItems,
|
|
1053
|
-
totalRecords: filteredItems.length,
|
|
1054
|
-
totalPages: totalPages,
|
|
1055
|
-
perPage: this.referenceModalSettings().perPage.selected,
|
|
1056
|
-
page: this.referenceModalSettings().page.selected
|
|
1057
|
-
});
|
|
1058
|
-
this.referenceModalSettings().page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
1059
|
-
if (this.referenceModalSettings().page.selected > totalPages) {
|
|
1060
|
-
this.referenceModalSettings().page.selected = 1;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
clearReferenceSearch() {
|
|
1064
|
-
this.referenceSearchTerm = '';
|
|
1065
|
-
this.onReferenceSearchChange();
|
|
1066
|
-
}
|
|
1067
|
-
selectReferenceItem(item) {
|
|
1068
|
-
const attr = this.editingReferenceAttr();
|
|
1069
|
-
if (attr) {
|
|
1070
|
-
const data = { ...this.formData() };
|
|
1071
|
-
data[attr.name] = item.id;
|
|
1072
|
-
this.formData.set(data);
|
|
1073
|
-
}
|
|
1074
|
-
this.closeReferenceModal();
|
|
1075
|
-
}
|
|
1076
|
-
closeReferenceModal() {
|
|
1077
|
-
this.showReferenceModal.set(false);
|
|
1078
|
-
this.editingReferenceAttr.set(null);
|
|
1079
|
-
this.referenceModalItems.set([]);
|
|
1080
|
-
this.referenceModalEntityType.set(null);
|
|
1081
|
-
this.referenceSearchTerm = '';
|
|
1082
|
-
}
|
|
1083
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1084
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.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 }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\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 (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 class=\"align-middle\">\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 class=\"align-middle\">\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 if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\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 </ng-template>\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 <bs-spinner class=\"me-1\" />\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 [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\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 <bs-spinner />\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 class=\"align-middle\">\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", "parentId", "parentType"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { 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: ["data"], outputs: ["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: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "restrictDragging", "selectFirstTab", "tabsPosition", "allowDragDrop"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { 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 });
|
|
1085
|
-
}
|
|
1086
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoFormComponent, decorators: [{
|
|
1087
|
-
type: Component,
|
|
1088
|
-
args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsTableComponent, BsToggleButtonComponent, BsSpinnerComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, 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 <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\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 (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 class=\"align-middle\">\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 class=\"align-middle\">\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 if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\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 </ng-template>\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 <bs-spinner class=\"me-1\" />\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 [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\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 <bs-spinner />\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 class=\"align-middle\">\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" }]
|
|
1089
|
-
}], ctorParameters: () => [], propDecorators: { 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 }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
|
|
1090
|
-
|
|
1091
|
-
class SparkPoCreateComponent {
|
|
1092
|
-
route = inject(ActivatedRoute);
|
|
1093
|
-
router = inject(Router);
|
|
1094
|
-
sparkService = inject(SparkService);
|
|
1095
|
-
saved = output();
|
|
1096
|
-
cancelled = output();
|
|
1097
|
-
colors = Color;
|
|
1098
|
-
entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
1099
|
-
type = signal('', ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
1100
|
-
formData = signal({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
|
|
1101
|
-
validationErrors = signal([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
|
|
1102
|
-
isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
|
|
1103
|
-
generalErrors = computed(() => this.validationErrors().filter(e => !e.attributeName), ...(ngDevMode ? [{ debugName: "generalErrors" }] : []));
|
|
1104
|
-
constructor() {
|
|
1105
|
-
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
|
|
1106
|
-
}
|
|
1107
|
-
async onParamsChange(params) {
|
|
1108
|
-
this.type.set(params.get('type') || '');
|
|
1109
|
-
const types = await this.sparkService.getEntityTypes();
|
|
1110
|
-
const entityType = types.find(t => t.id === this.type() || t.alias === this.type()) || null;
|
|
1111
|
-
this.entityType.set(entityType);
|
|
1112
|
-
this.initFormData();
|
|
1113
|
-
}
|
|
1114
|
-
initFormData() {
|
|
1115
|
-
const data = {};
|
|
1116
|
-
this.getEditableAttributes().forEach(attr => {
|
|
1117
|
-
if (attr.dataType === 'Reference') {
|
|
1118
|
-
data[attr.name] = null;
|
|
1119
|
-
}
|
|
1120
|
-
else if (attr.dataType === 'AsDetail') {
|
|
1121
|
-
data[attr.name] = attr.isArray ? [] : {};
|
|
1122
|
-
}
|
|
1123
|
-
else if (attr.dataType === 'boolean') {
|
|
1124
|
-
data[attr.name] = false;
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
data[attr.name] = '';
|
|
1128
|
-
}
|
|
1129
|
-
});
|
|
1130
|
-
this.formData.set(data);
|
|
1131
|
-
}
|
|
1132
|
-
getEditableAttributes() {
|
|
1133
|
-
return this.entityType()?.attributes
|
|
1134
|
-
.filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
|
|
1135
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
1136
|
-
}
|
|
1137
|
-
async onSave() {
|
|
1138
|
-
if (!this.entityType())
|
|
1139
|
-
return;
|
|
1140
|
-
this.validationErrors.set([]);
|
|
1141
|
-
this.isSaving.set(true);
|
|
1142
|
-
const attributes = this.getEditableAttributes().map(attr => ({
|
|
1143
|
-
id: attr.id,
|
|
1144
|
-
name: attr.name,
|
|
1145
|
-
value: this.formData()[attr.name],
|
|
1146
|
-
dataType: attr.dataType,
|
|
1147
|
-
isRequired: attr.isRequired,
|
|
1148
|
-
isVisible: attr.isVisible,
|
|
1149
|
-
isReadOnly: attr.isReadOnly,
|
|
1150
|
-
isValueChanged: true,
|
|
1151
|
-
order: attr.order,
|
|
1152
|
-
rules: attr.rules
|
|
1153
|
-
}));
|
|
1154
|
-
const po = {
|
|
1155
|
-
name: this.formData()['Name'] || 'New Item',
|
|
1156
|
-
objectTypeId: this.entityType().id,
|
|
1157
|
-
attributes
|
|
1158
|
-
};
|
|
1159
|
-
try {
|
|
1160
|
-
const result = await this.sparkService.create(this.type(), po);
|
|
1161
|
-
this.isSaving.set(false);
|
|
1162
|
-
this.saved.emit(result);
|
|
1163
|
-
this.router.navigate(['/po', this.type(), result.id]);
|
|
1164
|
-
}
|
|
1165
|
-
catch (e) {
|
|
1166
|
-
this.isSaving.set(false);
|
|
1167
|
-
const error = e;
|
|
1168
|
-
if (error.status === 400 && error.error?.errors) {
|
|
1169
|
-
this.validationErrors.set(error.error.errors);
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
this.validationErrors.set([{
|
|
1173
|
-
attributeName: '',
|
|
1174
|
-
errorMessage: { en: error.message || 'An unexpected error occurred' },
|
|
1175
|
-
ruleType: 'error'
|
|
1176
|
-
}]);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
onCancel() {
|
|
1181
|
-
this.cancelled.emit();
|
|
1182
|
-
window.history.back();
|
|
1183
|
-
}
|
|
1184
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1185
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkPoCreateComponent, isStandalone: true, selector: "spark-po-create", outputs: { saved: "saved", cancelled: "cancelled" }, 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 <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\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: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1186
|
-
}
|
|
1187
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoCreateComponent, decorators: [{
|
|
1188
|
-
type: Component,
|
|
1189
|
-
args: [{ selector: 'spark-po-create', imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, 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 <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
|
|
1190
|
-
}], ctorParameters: () => [], propDecorators: { saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
1191
|
-
|
|
1192
|
-
var sparkPoCreate_component = /*#__PURE__*/Object.freeze({
|
|
1193
|
-
__proto__: null,
|
|
1194
|
-
SparkPoCreateComponent: SparkPoCreateComponent
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
class SparkPoEditComponent {
|
|
1198
|
-
route = inject(ActivatedRoute);
|
|
1199
|
-
router = inject(Router);
|
|
1200
|
-
sparkService = inject(SparkService);
|
|
1201
|
-
saved = output();
|
|
1202
|
-
cancelled = output();
|
|
1203
|
-
colors = Color;
|
|
1204
|
-
entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
1205
|
-
item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1206
|
-
type = '';
|
|
1207
|
-
id = '';
|
|
1208
|
-
formData = signal({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
|
|
1209
|
-
validationErrors = signal([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
|
|
1210
|
-
isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
|
|
1211
|
-
generalErrors = computed(() => this.validationErrors().filter(e => !e.attributeName), ...(ngDevMode ? [{ debugName: "generalErrors" }] : []));
|
|
1212
|
-
constructor() {
|
|
1213
|
-
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
|
|
1214
|
-
}
|
|
1215
|
-
async onParamsChange(params) {
|
|
1216
|
-
this.type = params.get('type') || '';
|
|
1217
|
-
this.id = params.get('id') || '';
|
|
1218
|
-
try {
|
|
1219
|
-
const [types, item] = await Promise.all([
|
|
1220
|
-
this.sparkService.getEntityTypes(),
|
|
1221
|
-
this.sparkService.get(this.type, this.id)
|
|
1222
|
-
]);
|
|
1223
|
-
const entityType = types.find(t => t.id === this.type || t.alias === this.type) || null;
|
|
1224
|
-
this.entityType.set(entityType);
|
|
1225
|
-
this.item.set(item);
|
|
1226
|
-
this.initFormData();
|
|
1227
|
-
}
|
|
1228
|
-
catch (e) {
|
|
1229
|
-
const error = e;
|
|
1230
|
-
this.validationErrors.set([{
|
|
1231
|
-
attributeName: '',
|
|
1232
|
-
errorMessage: { en: error.error?.error || error.message || 'An unexpected error occurred' },
|
|
1233
|
-
ruleType: 'error'
|
|
1234
|
-
}]);
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
initFormData() {
|
|
1238
|
-
const data = {};
|
|
1239
|
-
const currentItem = this.item();
|
|
1240
|
-
this.getEditableAttributes().forEach(attr => {
|
|
1241
|
-
const itemAttr = currentItem?.attributes.find(a => a.name === attr.name);
|
|
1242
|
-
if (attr.dataType === 'Reference') {
|
|
1243
|
-
data[attr.name] = itemAttr?.value ?? null;
|
|
1244
|
-
}
|
|
1245
|
-
else if (attr.dataType === 'AsDetail') {
|
|
1246
|
-
data[attr.name] = itemAttr?.value ?? (attr.isArray ? [] : {});
|
|
1247
|
-
}
|
|
1248
|
-
else if (attr.dataType === 'boolean') {
|
|
1249
|
-
data[attr.name] = itemAttr?.value ?? false;
|
|
1250
|
-
}
|
|
1251
|
-
else {
|
|
1252
|
-
data[attr.name] = itemAttr?.value ?? '';
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
this.formData.set(data);
|
|
1256
|
-
}
|
|
1257
|
-
getEditableAttributes() {
|
|
1258
|
-
return this.entityType()?.attributes
|
|
1259
|
-
.filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
|
|
1260
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
1261
|
-
}
|
|
1262
|
-
async onSave() {
|
|
1263
|
-
const currentItem = this.item();
|
|
1264
|
-
if (!this.entityType() || !currentItem)
|
|
1265
|
-
return;
|
|
1266
|
-
this.validationErrors.set([]);
|
|
1267
|
-
this.isSaving.set(true);
|
|
1268
|
-
const attributes = currentItem.attributes.map(attr => {
|
|
1269
|
-
const editableAttr = this.getEditableAttributes().find(a => a.name === attr.name);
|
|
1270
|
-
const newValue = editableAttr ? this.formData()[attr.name] : attr.value;
|
|
1271
|
-
return {
|
|
1272
|
-
...attr,
|
|
1273
|
-
value: newValue,
|
|
1274
|
-
isValueChanged: editableAttr ? newValue !== attr.value : false
|
|
1275
|
-
};
|
|
1276
|
-
});
|
|
1277
|
-
const po = {
|
|
1278
|
-
id: currentItem.id,
|
|
1279
|
-
name: this.formData()['Name'] || currentItem.name,
|
|
1280
|
-
objectTypeId: this.entityType().id,
|
|
1281
|
-
attributes
|
|
1282
|
-
};
|
|
1283
|
-
try {
|
|
1284
|
-
const result = await this.sparkService.update(this.type, this.id, po);
|
|
1285
|
-
this.isSaving.set(false);
|
|
1286
|
-
this.saved.emit(result);
|
|
1287
|
-
this.router.navigate(['/po', this.type, this.id]);
|
|
1288
|
-
}
|
|
1289
|
-
catch (e) {
|
|
1290
|
-
this.isSaving.set(false);
|
|
1291
|
-
const error = e;
|
|
1292
|
-
if (error.status === 400 && error.error?.errors) {
|
|
1293
|
-
this.validationErrors.set(error.error.errors);
|
|
1294
|
-
}
|
|
1295
|
-
else {
|
|
1296
|
-
this.validationErrors.set([{
|
|
1297
|
-
attributeName: '',
|
|
1298
|
-
errorMessage: { en: error.message || 'An unexpected error occurred' },
|
|
1299
|
-
ruleType: 'error'
|
|
1300
|
-
}]);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
onCancel() {
|
|
1305
|
-
this.cancelled.emit();
|
|
1306
|
-
this.router.navigate(['/po', this.type, this.id]);
|
|
1307
|
-
}
|
|
1308
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1309
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkPoEditComponent, isStandalone: true, selector: "spark-po-edit", outputs: { saved: "saved", cancelled: "cancelled" }, 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 <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\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: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1310
|
-
}
|
|
1311
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoEditComponent, decorators: [{
|
|
1312
|
-
type: Component,
|
|
1313
|
-
args: [{ selector: 'spark-po-edit', imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, 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 <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [parentId]=\"id\"\n [parentType]=\"type\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
|
|
1314
|
-
}], ctorParameters: () => [], propDecorators: { saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
1315
|
-
|
|
1316
|
-
var sparkPoEdit_component = /*#__PURE__*/Object.freeze({
|
|
1317
|
-
__proto__: null,
|
|
1318
|
-
SparkPoEditComponent: SparkPoEditComponent
|
|
1319
|
-
});
|
|
1320
|
-
|
|
1321
|
-
class AttributeValuePipe {
|
|
1322
|
-
transform(attrName, item, entityType, lookupRefOptions, allEntityTypes) {
|
|
1323
|
-
if (!item)
|
|
1324
|
-
return '';
|
|
1325
|
-
const attr = item.attributes.find(a => a.name === attrName);
|
|
1326
|
-
if (!attr)
|
|
1327
|
-
return '';
|
|
1328
|
-
if (attr.breadcrumb)
|
|
1329
|
-
return attr.breadcrumb;
|
|
1330
|
-
const attrDef = entityType?.attributes.find(a => a.name === attrName);
|
|
1331
|
-
if (attrDef?.dataType === 'AsDetail' && attr.value) {
|
|
1332
|
-
if (Array.isArray(attr.value)) {
|
|
1333
|
-
return `${attr.value.length} item${attr.value.length !== 1 ? 's' : ''}`;
|
|
1334
|
-
}
|
|
1335
|
-
if (typeof attr.value === 'object') {
|
|
1336
|
-
return this.formatAsDetailValue(attrDef, attr.value, allEntityTypes);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
if (attrDef?.lookupReferenceType && attr.value != null && attr.value !== '') {
|
|
1340
|
-
const lookupRef = lookupRefOptions[attrDef.lookupReferenceType];
|
|
1341
|
-
if (lookupRef) {
|
|
1342
|
-
const option = lookupRef.values.find(v => v.key === String(attr.value));
|
|
1343
|
-
if (option) {
|
|
1344
|
-
return resolveTranslation(option.values) || option.key;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
if (attrDef?.dataType === 'boolean') {
|
|
1349
|
-
return attr.value ?? null;
|
|
1350
|
-
}
|
|
1351
|
-
return attr.value ?? '';
|
|
1352
|
-
}
|
|
1353
|
-
formatAsDetailValue(attrDef, value, allEntityTypes) {
|
|
1354
|
-
const asDetailType = allEntityTypes.find(t => t.clrType === attrDef.asDetailType);
|
|
1355
|
-
if (asDetailType?.displayFormat) {
|
|
1356
|
-
const result = this.resolveDisplayFormat(asDetailType.displayFormat, value);
|
|
1357
|
-
if (result && result.trim())
|
|
1358
|
-
return result;
|
|
1359
|
-
}
|
|
1360
|
-
if (asDetailType?.displayAttribute && value[asDetailType.displayAttribute]) {
|
|
1361
|
-
return value[asDetailType.displayAttribute];
|
|
1362
|
-
}
|
|
1363
|
-
const displayProps = ['Name', 'Title', 'Street', 'name', 'title'];
|
|
1364
|
-
for (const prop of displayProps) {
|
|
1365
|
-
if (value[prop])
|
|
1366
|
-
return value[prop];
|
|
1367
|
-
}
|
|
1368
|
-
return '(object)';
|
|
1369
|
-
}
|
|
1370
|
-
resolveDisplayFormat(format, data) {
|
|
1371
|
-
return format.replace(/\{(\w+)\}/g, (match, propertyName) => {
|
|
1372
|
-
const value = data[propertyName];
|
|
1373
|
-
return value != null ? String(value) : '';
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AttributeValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1377
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: AttributeValuePipe, isStandalone: true, name: "attributeValue" });
|
|
1378
|
-
}
|
|
1379
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AttributeValuePipe, decorators: [{
|
|
1380
|
-
type: Pipe,
|
|
1381
|
-
args: [{ name: 'attributeValue', standalone: true, pure: true }]
|
|
1382
|
-
}] });
|
|
1383
|
-
|
|
1384
|
-
class RawAttributeValuePipe {
|
|
1385
|
-
transform(attrName, item) {
|
|
1386
|
-
return item?.attributes.find(a => a.name === attrName)?.value;
|
|
1387
|
-
}
|
|
1388
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RawAttributeValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1389
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: RawAttributeValuePipe, isStandalone: true, name: "rawAttributeValue" });
|
|
1390
|
-
}
|
|
1391
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RawAttributeValuePipe, decorators: [{
|
|
1392
|
-
type: Pipe,
|
|
1393
|
-
args: [{ name: 'rawAttributeValue', standalone: true, pure: true }]
|
|
1394
|
-
}] });
|
|
1395
|
-
|
|
1396
|
-
class ArrayValuePipe {
|
|
1397
|
-
transform(attrName, item) {
|
|
1398
|
-
const attr = item?.attributes.find(a => a.name === attrName);
|
|
1399
|
-
if (!attr || !Array.isArray(attr.value))
|
|
1400
|
-
return [];
|
|
1401
|
-
return attr.value;
|
|
1402
|
-
}
|
|
1403
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ArrayValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1404
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ArrayValuePipe, isStandalone: true, name: "arrayValue" });
|
|
1405
|
-
}
|
|
1406
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ArrayValuePipe, decorators: [{
|
|
1407
|
-
type: Pipe,
|
|
1408
|
-
args: [{ name: 'arrayValue', standalone: true, pure: true }]
|
|
1409
|
-
}] });
|
|
1410
|
-
|
|
1411
|
-
class ReferenceLinkRoutePipe {
|
|
1412
|
-
transform(referenceClrType, referenceId, allEntityTypes) {
|
|
1413
|
-
if (!referenceId || !referenceClrType)
|
|
1414
|
-
return null;
|
|
1415
|
-
const targetType = allEntityTypes.find(t => t.clrType === referenceClrType);
|
|
1416
|
-
if (!targetType)
|
|
1417
|
-
return null;
|
|
1418
|
-
return ['/po', targetType.alias || targetType.id, referenceId];
|
|
1419
|
-
}
|
|
1420
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceLinkRoutePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1421
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: ReferenceLinkRoutePipe, isStandalone: true, name: "referenceLinkRoute" });
|
|
1422
|
-
}
|
|
1423
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ReferenceLinkRoutePipe, decorators: [{
|
|
1424
|
-
type: Pipe,
|
|
1425
|
-
args: [{ name: 'referenceLinkRoute', standalone: true, pure: true }]
|
|
1426
|
-
}] });
|
|
1427
|
-
|
|
1428
|
-
class SparkSubQueryComponent {
|
|
1429
|
-
sparkService = inject(SparkService);
|
|
1430
|
-
rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
1431
|
-
queryId = input.required(...(ngDevMode ? [{ debugName: "queryId" }] : []));
|
|
1432
|
-
parentId = input.required(...(ngDevMode ? [{ debugName: "parentId" }] : []));
|
|
1433
|
-
parentType = input.required(...(ngDevMode ? [{ debugName: "parentType" }] : []));
|
|
1434
|
-
query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
1435
|
-
entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
1436
|
-
allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
|
|
1437
|
-
paginationData = signal(undefined, ...(ngDevMode ? [{ debugName: "paginationData" }] : []));
|
|
1438
|
-
lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
|
|
1439
|
-
loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
1440
|
-
canRead = signal(false, ...(ngDevMode ? [{ debugName: "canRead" }] : []));
|
|
1441
|
-
settings = signal(new DatatableSettings({
|
|
1442
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
1443
|
-
page: { values: [1], selected: 1 },
|
|
1444
|
-
sortColumns: []
|
|
1445
|
-
}), ...(ngDevMode ? [{ debugName: "settings" }] : []));
|
|
1446
|
-
virtualDataSource = signal(null, ...(ngDevMode ? [{ debugName: "virtualDataSource" }] : []));
|
|
1447
|
-
virtualSettings = signal(new DatatableSettings({
|
|
1448
|
-
sortColumns: []
|
|
1449
|
-
}), ...(ngDevMode ? [{ debugName: "virtualSettings" }] : []));
|
|
1450
|
-
visibleAttributes = computed(() => {
|
|
1451
|
-
return this.entityType()?.attributes
|
|
1452
|
-
.filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
|
|
1453
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
1454
|
-
}, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
|
|
1455
|
-
constructor() {
|
|
1456
|
-
effect(() => {
|
|
1457
|
-
const qId = this.queryId();
|
|
1458
|
-
const pId = this.parentId();
|
|
1459
|
-
const pType = this.parentType();
|
|
1460
|
-
if (qId && pId && pType) {
|
|
1461
|
-
this.loadData(qId, pId, pType);
|
|
1462
|
-
}
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
async loadData(queryId, parentId, parentType) {
|
|
1466
|
-
this.loading.set(true);
|
|
1467
|
-
try {
|
|
1468
|
-
const [resolvedQuery, entityTypes] = await Promise.all([
|
|
1469
|
-
this.sparkService.getQuery(queryId),
|
|
1470
|
-
this.sparkService.getEntityTypes()
|
|
1471
|
-
]);
|
|
1472
|
-
this.query.set(resolvedQuery);
|
|
1473
|
-
this.allEntityTypes.set(entityTypes);
|
|
1474
|
-
const initialSortColumns = (resolvedQuery.sortColumns || []).map(sc => ({
|
|
1475
|
-
property: sc.property,
|
|
1476
|
-
direction: sc.direction === 'desc' ? 'descending' : 'ascending'
|
|
1477
|
-
}));
|
|
1478
|
-
// Resolve entity type from query's entityType field
|
|
1479
|
-
if (resolvedQuery.entityType) {
|
|
1480
|
-
const et = entityTypes.find(t => t.name === resolvedQuery.entityType || t.alias === resolvedQuery.entityType?.toLowerCase());
|
|
1481
|
-
this.entityType.set(et || null);
|
|
1482
|
-
if (et) {
|
|
1483
|
-
const permissions = await this.sparkService.getPermissions(et.id);
|
|
1484
|
-
this.canRead.set(permissions.canRead);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
if (resolvedQuery.renderMode === 'VirtualScrolling') {
|
|
1488
|
-
this.virtualSettings.set(new DatatableSettings({ sortColumns: initialSortColumns }));
|
|
1489
|
-
this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => this.sparkService.executeQuery(resolvedQuery.id, {
|
|
1490
|
-
sortColumns: this.virtualSettings().sortColumns,
|
|
1491
|
-
skip, take,
|
|
1492
|
-
parentId, parentType,
|
|
1493
|
-
}).then(r => ({ data: r.data, totalRecords: r.totalRecords })), 50));
|
|
1494
|
-
}
|
|
1495
|
-
else {
|
|
1496
|
-
this.settings.set(new DatatableSettings({
|
|
1497
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
1498
|
-
page: { values: [1], selected: 1 },
|
|
1499
|
-
sortColumns: initialSortColumns
|
|
1500
|
-
}));
|
|
1501
|
-
await this.loadPage(resolvedQuery.id, parentId, parentType);
|
|
1502
|
-
}
|
|
1503
|
-
this.loadLookupReferenceOptions();
|
|
1504
|
-
}
|
|
1505
|
-
catch {
|
|
1506
|
-
this.paginationData.set(undefined);
|
|
1507
|
-
}
|
|
1508
|
-
finally {
|
|
1509
|
-
this.loading.set(false);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
async loadPage(queryId, parentId, parentType) {
|
|
1513
|
-
const s = this.settings();
|
|
1514
|
-
const result = await this.sparkService.executeQuery(queryId, {
|
|
1515
|
-
sortColumns: s.sortColumns,
|
|
1516
|
-
skip: (s.page.selected - 1) * s.perPage.selected,
|
|
1517
|
-
take: s.perPage.selected,
|
|
1518
|
-
parentId, parentType,
|
|
1519
|
-
});
|
|
1520
|
-
const totalPages = Math.ceil(result.totalRecords / s.perPage.selected) || 1;
|
|
1521
|
-
this.paginationData.set({
|
|
1522
|
-
data: result.data,
|
|
1523
|
-
totalRecords: result.totalRecords,
|
|
1524
|
-
totalPages,
|
|
1525
|
-
perPage: s.perPage.selected,
|
|
1526
|
-
page: s.page.selected,
|
|
1527
|
-
});
|
|
1528
|
-
s.page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
1529
|
-
}
|
|
1530
|
-
onSettingsChange() {
|
|
1531
|
-
const q = this.query();
|
|
1532
|
-
if (!q)
|
|
1533
|
-
return;
|
|
1534
|
-
if (q.renderMode === 'VirtualScrolling') {
|
|
1535
|
-
this.virtualDataSource()?.reset();
|
|
1536
|
-
const pId = this.parentId();
|
|
1537
|
-
const pType = this.parentType();
|
|
1538
|
-
this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => this.sparkService.executeQuery(q.id, {
|
|
1539
|
-
sortColumns: this.virtualSettings().sortColumns,
|
|
1540
|
-
skip, take,
|
|
1541
|
-
parentId: pId, parentType: pType,
|
|
1542
|
-
}).then(r => ({ data: r.data, totalRecords: r.totalRecords })), 50));
|
|
1543
|
-
}
|
|
1544
|
-
else {
|
|
1545
|
-
this.loadPage(q.id, this.parentId(), this.parentType());
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
async loadLookupReferenceOptions() {
|
|
1549
|
-
const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);
|
|
1550
|
-
if (lookupAttrs.length === 0)
|
|
1551
|
-
return;
|
|
1552
|
-
const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
|
|
1553
|
-
const entries = await Promise.all(lookupNames.map(async (name) => {
|
|
1554
|
-
const result = await this.sparkService.getLookupReference(name);
|
|
1555
|
-
return [name, result];
|
|
1556
|
-
}));
|
|
1557
|
-
this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}));
|
|
1558
|
-
}
|
|
1559
|
-
getColumnRendererComponent(attr) {
|
|
1560
|
-
if (!attr.renderer)
|
|
1561
|
-
return null;
|
|
1562
|
-
return this.rendererRegistry.find(r => r.name === attr.renderer)?.columnComponent ?? null;
|
|
1563
|
-
}
|
|
1564
|
-
getColumnRendererInputs(item, attr) {
|
|
1565
|
-
const itemAttr = item.attributes.find(a => a.name === attr.name);
|
|
1566
|
-
return {
|
|
1567
|
-
value: itemAttr?.value,
|
|
1568
|
-
attribute: attr,
|
|
1569
|
-
options: attr.rendererOptions,
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkSubQueryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1573
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkSubQueryComponent, isStandalone: true, selector: "spark-sub-query", inputs: { queryId: { classPropertyName: "queryId", publicName: "queryId", isSignal: true, isRequired: true, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: true, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (query(); as q) {\n <bs-card style=\"display: block; margin: 1rem 0;\">\n <bs-card-header>{{ (q.description | resolveTranslation) || q.name }}</bs-card-header>\n @if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n } @else if (q.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <div class=\"p-3 d-flex flex-column\" style=\"height: 400px;\">\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (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 <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n </div>\n }\n } @else {\n <div class=\"p-3\">\n @if (!paginationData() || paginationData()!.totalRecords === 0) {\n <div class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </div>\n } @else {\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()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n </div>\n }\n </bs-card>\n}\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(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 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</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["data"], outputs: ["dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsVirtualDatatableComponent, selector: "bs-virtual-datatable", inputs: ["dataSource", "isResponsive", "itemSize"] }, { kind: "directive", type: BsVirtualRowTemplateDirective, selector: "[bsVirtualRowTemplate]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1574
|
-
}
|
|
1575
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkSubQueryComponent, decorators: [{
|
|
1576
|
-
type: Component,
|
|
1577
|
-
args: [{ selector: 'spark-sub-query', imports: [CommonModule, NgComponentOutlet, RouterModule, BsCardComponent, BsCardHeaderComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsVirtualDatatableComponent, BsVirtualRowTemplateDirective, BsTableComponent, BsSpinnerComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (query(); as q) {\n <bs-card style=\"display: block; margin: 1rem 0;\">\n <bs-card-header>{{ (q.description | resolveTranslation) || q.name }}</bs-card-header>\n @if (loading()) {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n } @else if (q.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <div class=\"p-3 d-flex flex-column\" style=\"height: 400px;\">\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (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 <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n </div>\n }\n } @else {\n <div class=\"p-3\">\n @if (!paginationData() || paginationData()!.totalRecords === 0) {\n <div class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </div>\n } @else {\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()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n </div>\n }\n </bs-card>\n}\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(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 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</ng-template>\n" }]
|
|
1578
|
-
}], ctorParameters: () => [], propDecorators: { queryId: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryId", required: true }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: true }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: true }] }] } });
|
|
1579
|
-
|
|
1580
|
-
class SparkPoDetailComponent {
|
|
1581
|
-
route = inject(ActivatedRoute);
|
|
1582
|
-
router = inject(Router);
|
|
1583
|
-
sparkService = inject(SparkService);
|
|
1584
|
-
lang = inject(SparkLanguageService);
|
|
1585
|
-
rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
1586
|
-
showCustomActions = input(true, ...(ngDevMode ? [{ debugName: "showCustomActions" }] : []));
|
|
1587
|
-
extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
|
|
1588
|
-
extraContentTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraContentTemplate" }] : []));
|
|
1589
|
-
edited = output();
|
|
1590
|
-
deleted = output();
|
|
1591
|
-
customActionExecuted = output();
|
|
1592
|
-
colors = Color;
|
|
1593
|
-
errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
1594
|
-
entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
1595
|
-
allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
|
|
1596
|
-
item = signal(null, ...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1597
|
-
lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
|
|
1598
|
-
asDetailTypes = signal({}, ...(ngDevMode ? [{ debugName: "asDetailTypes" }] : []));
|
|
1599
|
-
asDetailReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : []));
|
|
1600
|
-
type = '';
|
|
1601
|
-
id = '';
|
|
1602
|
-
canEdit = signal(false, ...(ngDevMode ? [{ debugName: "canEdit" }] : []));
|
|
1603
|
-
canDelete = signal(false, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
|
|
1604
|
-
customActions = signal([], ...(ngDevMode ? [{ debugName: "customActions" }] : []));
|
|
1605
|
-
constructor() {
|
|
1606
|
-
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
|
|
1607
|
-
}
|
|
1608
|
-
async onParamsChange(params) {
|
|
1609
|
-
this.type = params.get('type') || '';
|
|
1610
|
-
this.id = params.get('id') || '';
|
|
1611
|
-
try {
|
|
1612
|
-
const [entityTypes, item] = await Promise.all([
|
|
1613
|
-
this.sparkService.getEntityTypes(),
|
|
1614
|
-
this.sparkService.get(this.type, this.id)
|
|
1615
|
-
]);
|
|
1616
|
-
this.allEntityTypes.set(entityTypes);
|
|
1617
|
-
this.entityType.set(entityTypes.find(t => t.id === this.type || t.alias === this.type) || null);
|
|
1618
|
-
this.item.set(item);
|
|
1619
|
-
this.loadLookupReferenceOptions();
|
|
1620
|
-
this.loadAsDetailTypes();
|
|
1621
|
-
const et = this.entityType();
|
|
1622
|
-
if (et) {
|
|
1623
|
-
const [permissions, actions] = await Promise.all([
|
|
1624
|
-
this.sparkService.getPermissions(et.id),
|
|
1625
|
-
this.sparkService.getCustomActions(et.id)
|
|
1626
|
-
]);
|
|
1627
|
-
this.canEdit.set(permissions.canEdit);
|
|
1628
|
-
this.canDelete.set(permissions.canDelete);
|
|
1629
|
-
this.customActions.set(actions.filter(a => a.showedOn === 'detail' || a.showedOn === 'both'));
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
catch (e) {
|
|
1633
|
-
const error = e;
|
|
1634
|
-
this.errorMessage.set(error.error?.error || error.message || 'An unexpected error occurred');
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
visibleAttributes = computed(() => {
|
|
1638
|
-
return this.entityType()?.attributes
|
|
1639
|
-
.filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
|
|
1640
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
1641
|
-
}, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
|
|
1642
|
-
static DEFAULT_TAB = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };
|
|
1643
|
-
ungroupedAttributes = computed(() => {
|
|
1644
|
-
const attrs = this.visibleAttributes();
|
|
1645
|
-
const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));
|
|
1646
|
-
return attrs.filter(a => !a.group || !groupIds.has(a.group));
|
|
1647
|
-
}, ...(ngDevMode ? [{ debugName: "ungroupedAttributes" }] : []));
|
|
1648
|
-
resolvedTabs = computed(() => {
|
|
1649
|
-
const et = this.entityType();
|
|
1650
|
-
const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];
|
|
1651
|
-
const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;
|
|
1652
|
-
const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);
|
|
1653
|
-
if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {
|
|
1654
|
-
return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];
|
|
1655
|
-
}
|
|
1656
|
-
return definedTabs;
|
|
1657
|
-
}, ...(ngDevMode ? [{ debugName: "resolvedTabs" }] : []));
|
|
1658
|
-
groupsForTab(tab) {
|
|
1659
|
-
const groups = this.entityType()?.groups || [];
|
|
1660
|
-
if (tab.id === '__default__') {
|
|
1661
|
-
return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);
|
|
1662
|
-
}
|
|
1663
|
-
return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);
|
|
1664
|
-
}
|
|
1665
|
-
attrsForGroup(group) {
|
|
1666
|
-
return this.visibleAttributes().filter(a => a.group === group.id);
|
|
1667
|
-
}
|
|
1668
|
-
getDetailRendererComponent(attr) {
|
|
1669
|
-
if (!attr.renderer)
|
|
1670
|
-
return null;
|
|
1671
|
-
return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;
|
|
1672
|
-
}
|
|
1673
|
-
getDetailRendererInputs(attr, item) {
|
|
1674
|
-
const itemAttr = item.attributes.find(a => a.name === attr.name);
|
|
1675
|
-
const formData = {};
|
|
1676
|
-
for (const a of item.attributes) {
|
|
1677
|
-
formData[a.name] = a.value;
|
|
1678
|
-
}
|
|
1679
|
-
return {
|
|
1680
|
-
value: itemAttr?.value,
|
|
1681
|
-
attribute: attr,
|
|
1682
|
-
options: attr.rendererOptions,
|
|
1683
|
-
formData,
|
|
1684
|
-
};
|
|
1685
|
-
}
|
|
1686
|
-
async loadLookupReferenceOptions() {
|
|
1687
|
-
const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);
|
|
1688
|
-
if (lookupAttrs.length === 0)
|
|
1689
|
-
return;
|
|
1690
|
-
const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
|
|
1691
|
-
const entries = await Promise.all(lookupNames.map(async (name) => {
|
|
1692
|
-
const result = await this.sparkService.getLookupReference(name);
|
|
1693
|
-
return [name, result];
|
|
1694
|
-
}));
|
|
1695
|
-
this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}));
|
|
1696
|
-
}
|
|
1697
|
-
async loadAsDetailTypes() {
|
|
1698
|
-
const asDetailAttrs = this.visibleAttributes().filter(a => a.dataType === 'AsDetail' && a.isArray && a.asDetailType);
|
|
1699
|
-
if (asDetailAttrs.length === 0)
|
|
1700
|
-
return;
|
|
1701
|
-
const types = this.allEntityTypes();
|
|
1702
|
-
const newAsDetailTypes = {};
|
|
1703
|
-
for (const attr of asDetailAttrs) {
|
|
1704
|
-
const asDetailType = types.find(t => t.clrType === attr.asDetailType);
|
|
1705
|
-
if (asDetailType) {
|
|
1706
|
-
newAsDetailTypes[attr.name] = asDetailType;
|
|
1707
|
-
const refCols = asDetailType.attributes.filter(a => a.dataType === 'Reference' && a.query);
|
|
1708
|
-
if (refCols.length > 0) {
|
|
1709
|
-
const refEntries = await Promise.all(refCols.map(async (col) => {
|
|
1710
|
-
const result = await this.sparkService.executeQueryByName(col.query, {
|
|
1711
|
-
parentId: this.id,
|
|
1712
|
-
parentType: this.type,
|
|
1713
|
-
});
|
|
1714
|
-
return [col.name, result.data];
|
|
1715
|
-
}));
|
|
1716
|
-
this.asDetailReferenceOptions.update(prev => ({
|
|
1717
|
-
...prev,
|
|
1718
|
-
[attr.name]: refEntries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
|
|
1719
|
-
}));
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
this.asDetailTypes.set(newAsDetailTypes);
|
|
1724
|
-
}
|
|
1725
|
-
async onCustomAction(action) {
|
|
1726
|
-
if (action.confirmationMessageKey) {
|
|
1727
|
-
const message = this.lang.t(action.confirmationMessageKey) || 'Are you sure?';
|
|
1728
|
-
if (!confirm(message))
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1731
|
-
try {
|
|
1732
|
-
await this.sparkService.executeCustomAction(this.type, action.name, this.item() || undefined);
|
|
1733
|
-
this.customActionExecuted.emit({ action, item: this.item() });
|
|
1734
|
-
if (action.refreshOnCompleted) {
|
|
1735
|
-
const item = await this.sparkService.get(this.type, this.id);
|
|
1736
|
-
this.item.set(item);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
catch (e) {
|
|
1740
|
-
const err = e;
|
|
1741
|
-
this.errorMessage.set(err.error?.error || err.message || 'Action failed');
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
onEdit() {
|
|
1745
|
-
this.edited.emit();
|
|
1746
|
-
this.router.navigate(['/po', this.type, this.id, 'edit']);
|
|
1747
|
-
}
|
|
1748
|
-
async onDelete() {
|
|
1749
|
-
if (confirm(this.lang.t('confirmDelete'))) {
|
|
1750
|
-
await this.sparkService.delete(this.type, this.id);
|
|
1751
|
-
this.deleted.emit();
|
|
1752
|
-
this.router.navigate(['/']);
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
onBack() {
|
|
1756
|
-
window.history.back();
|
|
1757
|
-
}
|
|
1758
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1759
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.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" }, 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-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(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 class=\"align-middle\">\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 </ng-template>\n </bs-grid>\n @if (et.queries?.length) {\n @for (queryAlias of et.queries; track queryAlias) {\n <spark-sub-query [queryId]=\"queryAlias\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\" />\n }\n }\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 <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "restrictDragging", "selectFirstTab", "tabsPosition", "allowDragDrop"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "component", type: SparkSubQueryComponent, selector: "spark-sub-query", inputs: ["queryId", "parentId", "parentType"] }, { 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 });
|
|
1760
|
-
}
|
|
1761
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkPoDetailComponent, decorators: [{
|
|
1762
|
-
type: Component,
|
|
1763
|
-
args: [{ selector: 'spark-po-detail', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsButtonGroupComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, SparkSubQueryComponent, 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-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(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 class=\"align-middle\">\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 </ng-template>\n </bs-grid>\n @if (et.queries?.length) {\n @for (queryAlias of et.queries; track queryAlias) {\n <spark-sub-query [queryId]=\"queryAlias\" [parentId]=\"currentItem.id!\" [parentType]=\"et.name\" />\n }\n }\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 <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
|
|
1764
|
-
}], ctorParameters: () => [], propDecorators: { 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"] }] } });
|
|
1765
|
-
|
|
1766
|
-
var sparkPoDetail_component = /*#__PURE__*/Object.freeze({
|
|
1767
|
-
__proto__: null,
|
|
1768
|
-
SparkPoDetailComponent: SparkPoDetailComponent
|
|
1769
|
-
});
|
|
1770
|
-
|
|
1771
|
-
class SparkQueryListComponent {
|
|
1772
|
-
route = inject(ActivatedRoute);
|
|
1773
|
-
router = inject(Router);
|
|
1774
|
-
sparkService = inject(SparkService);
|
|
1775
|
-
streamingService = inject(SparkStreamingService);
|
|
1776
|
-
rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
|
|
1777
|
-
destroyRef = inject(DestroyRef);
|
|
1778
|
-
extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
|
|
1779
|
-
rowClicked = output();
|
|
1780
|
-
createClicked = output();
|
|
1781
|
-
colors = Color;
|
|
1782
|
-
errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
|
|
1783
|
-
query = signal(null, ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
1784
|
-
entityType = signal(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
|
|
1785
|
-
allEntityTypes = signal([], ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : []));
|
|
1786
|
-
lookupReferenceOptions = signal({}, ...(ngDevMode ? [{ debugName: "lookupReferenceOptions" }] : []));
|
|
1787
|
-
paginationData = signal(undefined, ...(ngDevMode ? [{ debugName: "paginationData" }] : []));
|
|
1788
|
-
searchTerm = '';
|
|
1789
|
-
canRead = signal(false, ...(ngDevMode ? [{ debugName: "canRead" }] : []));
|
|
1790
|
-
canCreate = signal(false, ...(ngDevMode ? [{ debugName: "canCreate" }] : []));
|
|
1791
|
-
isStreaming = signal(false, ...(ngDevMode ? [{ debugName: "isStreaming" }] : []));
|
|
1792
|
-
streamingSub = null;
|
|
1793
|
-
allItems = signal([], ...(ngDevMode ? [{ debugName: "allItems" }] : []));
|
|
1794
|
-
filteredItems = [];
|
|
1795
|
-
settings = signal(new DatatableSettings({
|
|
1796
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
1797
|
-
page: { values: [1], selected: 1 },
|
|
1798
|
-
sortColumns: []
|
|
1799
|
-
}), ...(ngDevMode ? [{ debugName: "settings" }] : []));
|
|
1800
|
-
virtualDataSource = signal(null, ...(ngDevMode ? [{ debugName: "virtualDataSource" }] : []));
|
|
1801
|
-
virtualSettings = signal(new DatatableSettings({
|
|
1802
|
-
sortColumns: []
|
|
1803
|
-
}), ...(ngDevMode ? [{ debugName: "virtualSettings" }] : []));
|
|
1804
|
-
constructor() {
|
|
1805
|
-
this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
|
|
1806
|
-
this.destroyRef.onDestroy(() => this.disconnectStreaming());
|
|
1807
|
-
}
|
|
1808
|
-
async onParamsChange(params) {
|
|
1809
|
-
const queryId = params.get('queryId');
|
|
1810
|
-
const typeParam = params.get('type');
|
|
1811
|
-
let resolvedQuery = null;
|
|
1812
|
-
let resolvedEntityType = null;
|
|
1813
|
-
let resolvedEntityTypes = [];
|
|
1814
|
-
if (queryId) {
|
|
1815
|
-
resolvedQuery = await this.sparkService.getQuery(queryId);
|
|
1816
|
-
if (resolvedQuery) {
|
|
1817
|
-
const result = await this.resolveEntityTypeForQuery(resolvedQuery);
|
|
1818
|
-
resolvedEntityType = result.entityType;
|
|
1819
|
-
resolvedEntityTypes = result.entityTypes;
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
else if (typeParam) {
|
|
1823
|
-
resolvedEntityTypes = await this.sparkService.getEntityTypes();
|
|
1824
|
-
resolvedEntityType = resolvedEntityTypes.find(t => t.id === typeParam || t.alias === typeParam) || null;
|
|
1825
|
-
if (resolvedEntityType) {
|
|
1826
|
-
const queries = await this.sparkService.getQueries();
|
|
1827
|
-
const singularName = resolvedEntityType.name;
|
|
1828
|
-
resolvedQuery = queries.find(q => {
|
|
1829
|
-
// Match by explicit entityType
|
|
1830
|
-
if (q.entityType === singularName)
|
|
1831
|
-
return true;
|
|
1832
|
-
// Match by source name
|
|
1833
|
-
const sourceName = this.extractSourceName(q.source);
|
|
1834
|
-
const sourceSingular = this.singularize(sourceName);
|
|
1835
|
-
return sourceName === singularName ||
|
|
1836
|
-
sourceSingular === singularName ||
|
|
1837
|
-
sourceName === singularName + 's';
|
|
1838
|
-
}) || null;
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
if (resolvedQuery)
|
|
1842
|
-
this.query.set(resolvedQuery);
|
|
1843
|
-
if (resolvedEntityType) {
|
|
1844
|
-
this.entityType.set(resolvedEntityType);
|
|
1845
|
-
this.allEntityTypes.set(resolvedEntityTypes);
|
|
1846
|
-
const initialSortColumns = (resolvedQuery?.sortColumns || []).map(sc => ({
|
|
1847
|
-
property: sc.property,
|
|
1848
|
-
direction: sc.direction === 'desc' ? 'descending' : 'ascending'
|
|
1849
|
-
}));
|
|
1850
|
-
if (resolvedQuery?.renderMode === 'VirtualScrolling') {
|
|
1851
|
-
this.virtualSettings.set(new DatatableSettings({ sortColumns: initialSortColumns }));
|
|
1852
|
-
if (resolvedQuery?.isStreamingQuery) {
|
|
1853
|
-
// Streaming + VirtualScrolling: create a stable client-side data source, then connect WebSocket
|
|
1854
|
-
this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => Promise.resolve({
|
|
1855
|
-
data: this.filteredItems.slice(skip, skip + take),
|
|
1856
|
-
totalRecords: this.filteredItems.length
|
|
1857
|
-
}), 50));
|
|
1858
|
-
this.connectStreaming(resolvedQuery.id);
|
|
1859
|
-
}
|
|
1860
|
-
else {
|
|
1861
|
-
this.initVirtualDataSource();
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
else {
|
|
1865
|
-
this.settings.set(new DatatableSettings({
|
|
1866
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
1867
|
-
page: { values: [1], selected: 1 },
|
|
1868
|
-
sortColumns: initialSortColumns
|
|
1869
|
-
}));
|
|
1870
|
-
await this.loadItems();
|
|
1871
|
-
}
|
|
1872
|
-
this.loadLookupReferenceOptions();
|
|
1873
|
-
const permissions = await this.sparkService.getPermissions(resolvedEntityType.id);
|
|
1874
|
-
this.canRead.set(permissions.canRead);
|
|
1875
|
-
this.canCreate.set(permissions.canCreate);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
async resolveEntityTypeForQuery(query) {
|
|
1879
|
-
const entityTypes = await this.sparkService.getEntityTypes();
|
|
1880
|
-
// If entityType is explicitly set on the query, use it directly
|
|
1881
|
-
if (query.entityType) {
|
|
1882
|
-
const type = entityTypes.find(t => t.name === query.entityType || t.alias === query.entityType?.toLowerCase());
|
|
1883
|
-
return { entityType: type || null, entityTypes };
|
|
1884
|
-
}
|
|
1885
|
-
// For Database.X sources, extract the property name and try to match
|
|
1886
|
-
const sourceName = this.extractSourceName(query.source);
|
|
1887
|
-
const singularName = this.singularize(sourceName);
|
|
1888
|
-
const type = entityTypes.find(t => t.name === sourceName ||
|
|
1889
|
-
t.name === singularName ||
|
|
1890
|
-
t.clrType.endsWith(singularName));
|
|
1891
|
-
return { entityType: type || null, entityTypes };
|
|
1892
|
-
}
|
|
1893
|
-
extractSourceName(source) {
|
|
1894
|
-
const dotIndex = source.indexOf('.');
|
|
1895
|
-
return dotIndex >= 0 ? source.substring(dotIndex + 1) : source;
|
|
1896
|
-
}
|
|
1897
|
-
singularize(plural) {
|
|
1898
|
-
const irregulars = {
|
|
1899
|
-
'People': 'Person',
|
|
1900
|
-
'Children': 'Child',
|
|
1901
|
-
'Men': 'Men',
|
|
1902
|
-
'Women': 'Woman'
|
|
1903
|
-
};
|
|
1904
|
-
if (irregulars[plural])
|
|
1905
|
-
return irregulars[plural];
|
|
1906
|
-
if (plural.endsWith('ies')) {
|
|
1907
|
-
return plural.slice(0, -3) + 'y';
|
|
1908
|
-
}
|
|
1909
|
-
if (plural.endsWith('es')) {
|
|
1910
|
-
return plural.slice(0, -2);
|
|
1911
|
-
}
|
|
1912
|
-
if (plural.endsWith('s')) {
|
|
1913
|
-
return plural.slice(0, -1);
|
|
1914
|
-
}
|
|
1915
|
-
return plural;
|
|
1916
|
-
}
|
|
1917
|
-
initVirtualDataSource() {
|
|
1918
|
-
const currentQuery = this.query();
|
|
1919
|
-
if (!currentQuery)
|
|
1920
|
-
return;
|
|
1921
|
-
this.virtualDataSource.set(new VirtualDatatableDataSource((skip, take) => this.sparkService.executeQuery(currentQuery.id, {
|
|
1922
|
-
sortColumns: this.virtualSettings().sortColumns,
|
|
1923
|
-
skip, take,
|
|
1924
|
-
search: this.searchTerm || undefined,
|
|
1925
|
-
}).then(r => ({ data: r.data, totalRecords: r.totalRecords })), 50));
|
|
1926
|
-
}
|
|
1927
|
-
async loadItems() {
|
|
1928
|
-
const currentQuery = this.query();
|
|
1929
|
-
if (!currentQuery)
|
|
1930
|
-
return;
|
|
1931
|
-
// Streaming queries use WebSocket instead of HTTP
|
|
1932
|
-
if (currentQuery.isStreamingQuery) {
|
|
1933
|
-
this.connectStreaming(currentQuery.id);
|
|
1934
|
-
return;
|
|
1935
|
-
}
|
|
1936
|
-
// Non-streaming: disconnect any previous streaming connection
|
|
1937
|
-
this.disconnectStreaming();
|
|
1938
|
-
try {
|
|
1939
|
-
const s = this.settings();
|
|
1940
|
-
const result = await this.sparkService.executeQuery(currentQuery.id, {
|
|
1941
|
-
sortColumns: s.sortColumns,
|
|
1942
|
-
skip: (s.page.selected - 1) * s.perPage.selected,
|
|
1943
|
-
take: s.perPage.selected,
|
|
1944
|
-
search: this.searchTerm || undefined,
|
|
1945
|
-
});
|
|
1946
|
-
this.errorMessage.set(null);
|
|
1947
|
-
const totalPages = Math.ceil(result.totalRecords / s.perPage.selected) || 1;
|
|
1948
|
-
this.paginationData.set({
|
|
1949
|
-
data: result.data,
|
|
1950
|
-
totalRecords: result.totalRecords,
|
|
1951
|
-
totalPages: totalPages,
|
|
1952
|
-
perPage: s.perPage.selected,
|
|
1953
|
-
page: s.page.selected
|
|
1954
|
-
});
|
|
1955
|
-
s.page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
1956
|
-
}
|
|
1957
|
-
catch (e) {
|
|
1958
|
-
this.errorMessage.set(e.error?.error || e.message || 'An unexpected error occurred');
|
|
1959
|
-
this.paginationData.set(undefined);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
onSettingsChange() {
|
|
1963
|
-
if (this.isStreaming()) {
|
|
1964
|
-
// Streaming: sort/filter is client-side only
|
|
1965
|
-
this.applyFilter();
|
|
1966
|
-
return;
|
|
1967
|
-
}
|
|
1968
|
-
if (this.query()?.renderMode === 'VirtualScrolling') {
|
|
1969
|
-
this.virtualDataSource()?.reset();
|
|
1970
|
-
this.initVirtualDataSource();
|
|
1971
|
-
}
|
|
1972
|
-
else {
|
|
1973
|
-
this.loadItems();
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
onSearchChange() {
|
|
1977
|
-
if (this.isStreaming()) {
|
|
1978
|
-
this.applyFilter();
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
if (this.query()?.renderMode === 'VirtualScrolling') {
|
|
1982
|
-
this.virtualDataSource()?.reset();
|
|
1983
|
-
this.initVirtualDataSource();
|
|
1984
|
-
}
|
|
1985
|
-
else {
|
|
1986
|
-
this.settings().page.selected = 1;
|
|
1987
|
-
this.loadItems();
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
clearSearch() {
|
|
1991
|
-
this.searchTerm = '';
|
|
1992
|
-
this.onSearchChange();
|
|
1993
|
-
}
|
|
1994
|
-
isVirtualScrolling = computed(() => this.query()?.renderMode === 'VirtualScrolling', ...(ngDevMode ? [{ debugName: "isVirtualScrolling" }] : []));
|
|
1995
|
-
visibleAttributes = computed(() => {
|
|
1996
|
-
return this.entityType()?.attributes
|
|
1997
|
-
.filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
|
|
1998
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
1999
|
-
}, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
|
|
2000
|
-
getColumnRendererComponent(attr) {
|
|
2001
|
-
if (!attr.renderer)
|
|
2002
|
-
return null;
|
|
2003
|
-
return this.rendererRegistry.find(r => r.name === attr.renderer)?.columnComponent ?? null;
|
|
2004
|
-
}
|
|
2005
|
-
getColumnRendererInputs(item, attr) {
|
|
2006
|
-
const itemAttr = item.attributes.find(a => a.name === attr.name);
|
|
2007
|
-
return {
|
|
2008
|
-
value: itemAttr?.value,
|
|
2009
|
-
attribute: attr,
|
|
2010
|
-
options: attr.rendererOptions,
|
|
2011
|
-
};
|
|
2012
|
-
}
|
|
2013
|
-
async loadLookupReferenceOptions() {
|
|
2014
|
-
const lookupAttrs = this.visibleAttributes().filter(a => a.lookupReferenceType);
|
|
2015
|
-
if (lookupAttrs.length === 0)
|
|
2016
|
-
return;
|
|
2017
|
-
const lookupNames = [...new Set(lookupAttrs.map(a => a.lookupReferenceType))];
|
|
2018
|
-
const entries = await Promise.all(lookupNames.map(async (name) => {
|
|
2019
|
-
const result = await this.sparkService.getLookupReference(name);
|
|
2020
|
-
return [name, result];
|
|
2021
|
-
}));
|
|
2022
|
-
this.lookupReferenceOptions.set(entries.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}));
|
|
2023
|
-
}
|
|
2024
|
-
onCreate() {
|
|
2025
|
-
this.createClicked.emit();
|
|
2026
|
-
const et = this.entityType();
|
|
2027
|
-
if (et) {
|
|
2028
|
-
this.router.navigate(['/po', et.alias || et.id, 'new']);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
connectStreaming(queryId) {
|
|
2032
|
-
this.disconnectStreaming();
|
|
2033
|
-
this.isStreaming.set(true);
|
|
2034
|
-
this.streamingSub = this.streamingService.connectToStreamingQuery(queryId).subscribe({
|
|
2035
|
-
next: (message) => this.handleStreamingMessage(message),
|
|
2036
|
-
error: (err) => {
|
|
2037
|
-
this.errorMessage.set(err?.message || 'Streaming connection failed');
|
|
2038
|
-
this.isStreaming.set(false);
|
|
2039
|
-
},
|
|
2040
|
-
complete: () => {
|
|
2041
|
-
this.isStreaming.set(false);
|
|
2042
|
-
}
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
disconnectStreaming() {
|
|
2046
|
-
if (this.streamingSub) {
|
|
2047
|
-
this.streamingSub.unsubscribe();
|
|
2048
|
-
this.streamingSub = null;
|
|
2049
|
-
}
|
|
2050
|
-
this.isStreaming.set(false);
|
|
2051
|
-
}
|
|
2052
|
-
handleStreamingMessage(message) {
|
|
2053
|
-
switch (message.type) {
|
|
2054
|
-
case 'snapshot':
|
|
2055
|
-
this.errorMessage.set(null);
|
|
2056
|
-
this.allItems.set(message.data);
|
|
2057
|
-
this.applyFilter();
|
|
2058
|
-
break;
|
|
2059
|
-
case 'patch':
|
|
2060
|
-
if (message.updated.length > 0) {
|
|
2061
|
-
const currentItems = this.allItems();
|
|
2062
|
-
const updatedItems = currentItems.map(item => {
|
|
2063
|
-
const patch = message.updated.find(u => u.id === item.id);
|
|
2064
|
-
if (!patch)
|
|
2065
|
-
return item;
|
|
2066
|
-
// Clone the item and update only changed attribute values
|
|
2067
|
-
const updatedAttributes = item.attributes.map(attr => {
|
|
2068
|
-
if (attr.name in patch.attributes) {
|
|
2069
|
-
return { ...attr, value: patch.attributes[attr.name] };
|
|
2070
|
-
}
|
|
2071
|
-
return attr;
|
|
2072
|
-
});
|
|
2073
|
-
return { ...item, attributes: updatedAttributes };
|
|
2074
|
-
});
|
|
2075
|
-
this.allItems.set(updatedItems);
|
|
2076
|
-
this.applyFilter();
|
|
2077
|
-
}
|
|
2078
|
-
break;
|
|
2079
|
-
case 'error':
|
|
2080
|
-
this.errorMessage.set(message.message);
|
|
2081
|
-
break;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
applyFilter() {
|
|
2085
|
-
let items = this.allItems();
|
|
2086
|
-
// Apply search filter
|
|
2087
|
-
if (this.searchTerm) {
|
|
2088
|
-
const term = this.searchTerm.toLowerCase();
|
|
2089
|
-
items = items.filter(item => item.attributes.some(a => String(a.value ?? '').toLowerCase().includes(term)));
|
|
2090
|
-
}
|
|
2091
|
-
// Apply sorting
|
|
2092
|
-
const isVirtual = this.query()?.renderMode === 'VirtualScrolling';
|
|
2093
|
-
const sortCols = isVirtual ? this.virtualSettings().sortColumns : this.settings().sortColumns;
|
|
2094
|
-
if (sortCols.length > 0) {
|
|
2095
|
-
items = [...items].sort((a, b) => {
|
|
2096
|
-
for (const col of sortCols) {
|
|
2097
|
-
const aVal = a.attributes.find(attr => attr.name === col.property)?.value ?? '';
|
|
2098
|
-
const bVal = b.attributes.find(attr => attr.name === col.property)?.value ?? '';
|
|
2099
|
-
const cmp = String(aVal).localeCompare(String(bVal));
|
|
2100
|
-
if (cmp !== 0)
|
|
2101
|
-
return col.direction === 'descending' ? -cmp : cmp;
|
|
2102
|
-
}
|
|
2103
|
-
return 0;
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
if (isVirtual) {
|
|
2107
|
-
// Update the mutable filtered items array.
|
|
2108
|
-
// The stable data source's fetchFn closure reads from this.filteredItems,
|
|
2109
|
-
// so clearing its cache and emitting empty triggers the CDK viewport to re-fetch.
|
|
2110
|
-
this.filteredItems = items;
|
|
2111
|
-
this.virtualDataSource()?.reset();
|
|
2112
|
-
}
|
|
2113
|
-
else {
|
|
2114
|
-
this.paginationData.set({
|
|
2115
|
-
data: items,
|
|
2116
|
-
totalRecords: items.length,
|
|
2117
|
-
totalPages: 1,
|
|
2118
|
-
perPage: items.length,
|
|
2119
|
-
page: 1
|
|
2120
|
-
});
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkQueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2124
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.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" }, host: { properties: { "class.virtual-scrolling": "isVirtualScrolling()" } }, ngImport: i0, template: "<div class=\"d-flex flex-column h-100\">\n <!-- Title + Actions row -->\n <div class=\"d-flex justify-content-between align-items-center mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <h2>\n {{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </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 <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\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 </div>\n\n @if (query()?.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (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 <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n }\n } @else {\n <bs-datatable class=\"flex-grow-1\" [(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()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(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</ng-template>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}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.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { 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: "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: BsDatatableComponent, selector: "bs-datatable", inputs: ["data"], outputs: ["dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsVirtualDatatableComponent, selector: "bs-virtual-datatable", inputs: ["dataSource", "isResponsive", "itemSize"] }, { kind: "directive", type: BsVirtualRowTemplateDirective, selector: "[bsVirtualRowTemplate]" }, { 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: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { 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 });
|
|
2125
|
-
}
|
|
2126
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkQueryListComponent, decorators: [{
|
|
2127
|
-
type: Component,
|
|
2128
|
-
args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, RouterModule, BsAlertComponent, BsContainerComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsVirtualDatatableComponent, BsVirtualRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, BsSpinnerComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
2129
|
-
'[class.virtual-scrolling]': 'isVirtualScrolling()'
|
|
2130
|
-
}, template: "<div class=\"d-flex flex-column h-100\">\n <!-- Title + Actions row -->\n <div class=\"d-flex justify-content-between align-items-center mb-4 flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\n <h2>\n {{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}\n @if (isStreaming()) {\n <span class=\"badge bg-success ms-2\" style=\"font-size: 0.5em; vertical-align: middle;\">LIVE</span>\n }\n </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 <div class=\"flex-shrink-0\" [class.px-4]=\"isVirtualScrolling()\">\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 </div>\n\n @if (query()?.renderMode === 'VirtualScrolling') {\n @if (virtualDataSource(); as vds) {\n <bs-virtual-datatable class=\"flex-grow-1 overflow-hidden\"\n [(settings)]=\"virtualSettings\"\n [dataSource]=\"vds\"\n [isResponsive]=\"true\"\n (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 <ng-template bsVirtualRowTemplate let-item>\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (item) {\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n } @else {\n \n }\n </td>\n }\n </ng-template>\n </bs-virtual-datatable>\n }\n } @else {\n <bs-datatable class=\"flex-grow-1\" [(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()\">\n @for (attr of visibleAttributes(); track attr.id; let first = $first) {\n <td>\n @if (first && canRead()) {\n <a [routerLink]=\"['/po', entityType()!.alias || entityType()!.id, item.id]\" (click)=\"rowClicked.emit(item)\">\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"cellContent; context: { $implicit: item, attr: attr }\"></ng-container>\n }\n </td>\n }\n </tr>\n </bs-datatable>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n\n<ng-template #cellContent let-item let-attr=\"attr\">\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(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</ng-template>\n", styles: [":host{display:flex;flex-direction:column;flex:1;min-height:0}:host.virtual-scrolling{margin:0 -1.5rem -1.5rem}tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
|
|
2131
|
-
}], ctorParameters: () => [], propDecorators: { extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }] } });
|
|
2132
|
-
|
|
2133
|
-
var sparkQueryList_component = /*#__PURE__*/Object.freeze({
|
|
2134
|
-
__proto__: null,
|
|
2135
|
-
SparkQueryListComponent: SparkQueryListComponent
|
|
2136
|
-
});
|
|
2137
|
-
|
|
2138
|
-
class SparkRetryActionModalComponent {
|
|
2139
|
-
retryActionService = inject(RetryActionService);
|
|
2140
|
-
colors = Color;
|
|
2141
|
-
isOpen = computed(() => this.retryActionService.payload() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
2142
|
-
onOption(option) {
|
|
2143
|
-
const payload = this.retryActionService.payload();
|
|
2144
|
-
if (!payload)
|
|
2145
|
-
return;
|
|
2146
|
-
this.retryActionService.respond({
|
|
2147
|
-
step: payload.step,
|
|
2148
|
-
option,
|
|
2149
|
-
persistentObject: payload.persistentObject
|
|
2150
|
-
});
|
|
2151
|
-
}
|
|
2152
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkRetryActionModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2153
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SparkRetryActionModalComponent, isStandalone: true, selector: "spark-retry-action-modal", ngImport: i0, template: `
|
|
2154
|
-
<bs-modal [isOpen]="isOpen()" (isOpenChange)="!$event && onOption('Cancel')">
|
|
2155
|
-
<div *bsModal>
|
|
2156
|
-
<div bsModalHeader>
|
|
2157
|
-
<h5 class="modal-title">{{ retryActionService.payload()?.title }}</h5>
|
|
2158
|
-
</div>
|
|
2159
|
-
@if (retryActionService.payload()?.message; as message) {
|
|
2160
|
-
<div bsModalBody>
|
|
2161
|
-
<p>{{ message }}</p>
|
|
2162
|
-
</div>
|
|
2163
|
-
}
|
|
2164
|
-
<div bsModalFooter>
|
|
2165
|
-
@for (option of retryActionService.payload()?.options; track option) {
|
|
2166
|
-
<button
|
|
2167
|
-
type="button"
|
|
2168
|
-
[color]="option === 'Cancel' ? colors.secondary : colors.primary"
|
|
2169
|
-
(click)="onOption(option)">
|
|
2170
|
-
{{ option }}
|
|
2171
|
-
</button>
|
|
2172
|
-
}
|
|
2173
|
-
</div>
|
|
2174
|
-
</div>
|
|
2175
|
-
</bs-modal>
|
|
2176
|
-
`, 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 });
|
|
2177
|
-
}
|
|
2178
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SparkRetryActionModalComponent, decorators: [{
|
|
2179
|
-
type: Component,
|
|
2180
|
-
args: [{
|
|
2181
|
-
selector: 'spark-retry-action-modal',
|
|
2182
|
-
imports: [CommonModule, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsButtonTypeDirective],
|
|
2183
|
-
template: `
|
|
2184
|
-
<bs-modal [isOpen]="isOpen()" (isOpenChange)="!$event && onOption('Cancel')">
|
|
2185
|
-
<div *bsModal>
|
|
2186
|
-
<div bsModalHeader>
|
|
2187
|
-
<h5 class="modal-title">{{ retryActionService.payload()?.title }}</h5>
|
|
2188
|
-
</div>
|
|
2189
|
-
@if (retryActionService.payload()?.message; as message) {
|
|
2190
|
-
<div bsModalBody>
|
|
2191
|
-
<p>{{ message }}</p>
|
|
2192
|
-
</div>
|
|
2193
|
-
}
|
|
2194
|
-
<div bsModalFooter>
|
|
2195
|
-
@for (option of retryActionService.payload()?.options; track option) {
|
|
2196
|
-
<button
|
|
2197
|
-
type="button"
|
|
2198
|
-
[color]="option === 'Cancel' ? colors.secondary : colors.primary"
|
|
2199
|
-
(click)="onOption(option)">
|
|
2200
|
-
{{ option }}
|
|
2201
|
-
</button>
|
|
2202
|
-
}
|
|
2203
|
-
</div>
|
|
2204
|
-
</div>
|
|
2205
|
-
</bs-modal>
|
|
2206
|
-
`,
|
|
2207
|
-
changeDetection: ChangeDetectionStrategy.OnPush
|
|
2208
|
-
}]
|
|
2209
|
-
}] });
|
|
2210
|
-
|
|
2211
|
-
class RouterLinkPipe {
|
|
2212
|
-
transform(unit) {
|
|
2213
|
-
if (unit.type === 'query') {
|
|
2214
|
-
return ['/query', unit.alias || unit.queryId];
|
|
2215
|
-
}
|
|
2216
|
-
else if (unit.type === 'persistentObject') {
|
|
2217
|
-
return ['/po', unit.alias || unit.persistentObjectId];
|
|
2218
|
-
}
|
|
2219
|
-
return ['/'];
|
|
2220
|
-
}
|
|
2221
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RouterLinkPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
2222
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: RouterLinkPipe, isStandalone: true, name: "routerLink" });
|
|
2223
|
-
}
|
|
2224
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RouterLinkPipe, decorators: [{
|
|
2225
|
-
type: Pipe,
|
|
2226
|
-
args: [{ name: 'routerLink', standalone: true, pure: true }]
|
|
2227
|
-
}] });
|
|
2228
|
-
|
|
2229
|
-
class IconNamePipe {
|
|
2230
|
-
transform(value, fallback) {
|
|
2231
|
-
const iconClass = value || fallback;
|
|
2232
|
-
// Strip 'bi-' prefix if present
|
|
2233
|
-
return iconClass.startsWith('bi-') ? iconClass.substring(3) : iconClass;
|
|
2234
|
-
}
|
|
2235
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: IconNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
2236
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: IconNamePipe, isStandalone: true, name: "iconName" });
|
|
2237
|
-
}
|
|
2238
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: IconNamePipe, decorators: [{
|
|
2239
|
-
type: Pipe,
|
|
2240
|
-
args: [{ name: 'iconName', standalone: true, pure: true }]
|
|
2241
|
-
}] });
|
|
2242
|
-
|
|
2243
|
-
function sparkRoutes(config) {
|
|
2244
|
-
return [
|
|
2245
|
-
{
|
|
2246
|
-
path: 'query/:queryId',
|
|
2247
|
-
loadComponent: config?.queryList ?? (() => Promise.resolve().then(function () { return sparkQueryList_component; }).then(m => m.SparkQueryListComponent))
|
|
2248
|
-
},
|
|
2249
|
-
{
|
|
2250
|
-
path: 'po/:type/new',
|
|
2251
|
-
loadComponent: config?.poCreate ?? (() => Promise.resolve().then(function () { return sparkPoCreate_component; }).then(m => m.SparkPoCreateComponent))
|
|
2252
|
-
},
|
|
2253
|
-
{
|
|
2254
|
-
path: 'po/:type/:id/edit',
|
|
2255
|
-
loadComponent: config?.poEdit ?? (() => Promise.resolve().then(function () { return sparkPoEdit_component; }).then(m => m.SparkPoEditComponent))
|
|
2256
|
-
},
|
|
2257
|
-
{
|
|
2258
|
-
path: 'po/:type/:id',
|
|
2259
|
-
loadComponent: config?.poDetail ?? (() => Promise.resolve().then(function () { return sparkPoDetail_component; }).then(m => m.SparkPoDetailComponent))
|
|
2260
|
-
},
|
|
2261
|
-
{
|
|
2262
|
-
path: 'po/:type',
|
|
2263
|
-
loadComponent: config?.queryList ?? (() => Promise.resolve().then(function () { return sparkQueryList_component; }).then(m => m.SparkQueryListComponent))
|
|
2264
|
-
}
|
|
2265
|
-
];
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
8
|
function provideSpark(config) {
|
|
2269
9
|
return [
|
|
2270
10
|
{
|
|
@@ -2274,9 +14,24 @@ function provideSpark(config) {
|
|
|
2274
14
|
];
|
|
2275
15
|
}
|
|
2276
16
|
|
|
17
|
+
// Root entry point — bootstrap API only.
|
|
18
|
+
// For other members import from sub-paths:
|
|
19
|
+
// @mintplayer/ng-spark/models (PersistentObject, EntityType, SparkQuery, etc. + currentLanguage/resolveTranslation/ShowedOn/ELookupDisplayType)
|
|
20
|
+
// @mintplayer/ng-spark/services (SparkService, SparkStreamingService, SparkLanguageService, RetryActionService, SparkIconRegistry)
|
|
21
|
+
// @mintplayer/ng-spark/pipes (all 22 pipes)
|
|
22
|
+
// @mintplayer/ng-spark/renderers (SPARK_ATTRIBUTE_RENDERERS, provideSparkAttributeRenderers, renderer interfaces)
|
|
23
|
+
// @mintplayer/ng-spark/routes (sparkRoutes, SparkRouteConfig)
|
|
24
|
+
// @mintplayer/ng-spark/po-form (SparkPoFormComponent)
|
|
25
|
+
// @mintplayer/ng-spark/po-create (SparkPoCreateComponent)
|
|
26
|
+
// @mintplayer/ng-spark/po-edit (SparkPoEditComponent)
|
|
27
|
+
// @mintplayer/ng-spark/po-detail (SparkPoDetailComponent + SparkSubQueryComponent)
|
|
28
|
+
// @mintplayer/ng-spark/query-list (SparkQueryListComponent)
|
|
29
|
+
// @mintplayer/ng-spark/retry-action-modal (SparkRetryActionModalComponent)
|
|
30
|
+
// @mintplayer/ng-spark/icon (SparkIconComponent)
|
|
31
|
+
|
|
2277
32
|
/**
|
|
2278
33
|
* Generated bundle index. Do not edit.
|
|
2279
34
|
*/
|
|
2280
35
|
|
|
2281
|
-
export {
|
|
36
|
+
export { SPARK_CONFIG, defaultSparkConfig, provideSpark };
|
|
2282
37
|
//# sourceMappingURL=mintplayer-ng-spark.mjs.map
|