@mintplayer/ng-spark 22.22.0 → 22.24.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.
Files changed (68) hide show
  1. package/README.md +181 -0
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
  3. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +308 -329
  5. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
  7. package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-grid.mjs +2259 -1117
  9. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  10. package/fesm2022/mintplayer-ng-spark-history.mjs +611 -0
  11. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
  12. package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
  13. package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
  14. package/fesm2022/mintplayer-ng-spark-models.mjs +365 -753
  15. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
  17. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
  18. package/fesm2022/mintplayer-ng-spark-panels.mjs +52 -0
  19. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
  20. package/fesm2022/mintplayer-ng-spark-pipes.mjs +1122 -765
  21. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  22. package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
  23. package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +786 -410
  25. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +292 -284
  27. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-po-form.mjs +2187 -1225
  29. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  30. package/fesm2022/mintplayer-ng-spark-query-list.mjs +516 -303
  31. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  32. package/fesm2022/mintplayer-ng-spark-renderers.mjs +17 -55
  33. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  34. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
  35. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  36. package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
  37. package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
  39. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  40. package/fesm2022/mintplayer-ng-spark-shell.mjs +1013 -397
  41. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  42. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
  43. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
  44. package/fesm2022/mintplayer-ng-spark.mjs +13 -50
  45. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  46. package/package.json +17 -1
  47. package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
  48. package/types/mintplayer-ng-spark-client-operations.d.ts +133 -160
  49. package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
  50. package/types/mintplayer-ng-spark-grid.d.ts +885 -448
  51. package/types/mintplayer-ng-spark-history.d.ts +223 -0
  52. package/types/mintplayer-ng-spark-icon.d.ts +15 -13
  53. package/types/mintplayer-ng-spark-models.d.ts +624 -573
  54. package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
  55. package/types/mintplayer-ng-spark-panels.d.ts +108 -0
  56. package/types/mintplayer-ng-spark-pipes.d.ts +120 -149
  57. package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
  58. package/types/mintplayer-ng-spark-po-detail.d.ts +177 -117
  59. package/types/mintplayer-ng-spark-po-edit.d.ts +64 -65
  60. package/types/mintplayer-ng-spark-po-form.d.ts +507 -371
  61. package/types/mintplayer-ng-spark-query-list.d.ts +118 -82
  62. package/types/mintplayer-ng-spark-renderers.d.ts +79 -85
  63. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
  64. package/types/mintplayer-ng-spark-routes.d.ts +7 -11
  65. package/types/mintplayer-ng-spark-services.d.ts +377 -261
  66. package/types/mintplayer-ng-spark-shell.d.ts +209 -128
  67. package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
  68. package/types/mintplayer-ng-spark.d.ts +7 -12
@@ -1,285 +1,293 @@
1
- import * as i0 from '@angular/core';
2
- import { inject, output, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
- import { CommonModule } from '@angular/common';
5
- import { ActivatedRoute, Router } from '@angular/router';
6
- import { Color } from '@mintplayer/ng-bootstrap';
7
- import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
8
- import { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';
9
- import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
10
- import { SparkService, SparkLanguageService } from '@mintplayer/ng-spark/services';
11
- import { SparkPoFormComponent } from '@mintplayer/ng-spark/po-form';
12
- import { ResolveTranslationPipe, TranslateKeyPipe } from '@mintplayer/ng-spark/pipes';
13
- import { nestedPoToDict, isDateDataType, toDateInputValue, applyOverlay, hasShowedOnFlag, ShowedOn, dictToNestedPo, fromDateInputValue, wireDatesEqual } from '@mintplayer/ng-spark/models';
14
-
15
- class SparkPoEditComponent {
16
- route = inject(ActivatedRoute);
17
- router = inject(Router);
18
- sparkService = inject(SparkService);
19
- language = inject(SparkLanguageService);
20
- saved = output();
21
- cancelled = output();
22
- colors = Color;
23
- entityType = signal(null, /* @ts-ignore */
24
- ...(ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []));
25
- item = signal(null, /* @ts-ignore */
26
- ...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
27
- type = '';
28
- id = '';
29
- formData = signal({}, /* @ts-ignore */
30
- ...(ngDevMode ? [{ debugName: "formData" }] : /* istanbul ignore next */ []));
31
- /**
32
- * Bound two-way to the form, which is where refreshes land. Read by
33
- * {@link getEditableAttributes} so the save sees the object the user was actually shown.
34
- */
35
- refreshOverlay = signal({}, /* @ts-ignore */
36
- ...(ngDevMode ? [{ debugName: "refreshOverlay" }] : /* istanbul ignore next */ []));
37
- validationErrors = signal([], /* @ts-ignore */
38
- ...(ngDevMode ? [{ debugName: "validationErrors" }] : /* istanbul ignore next */ []));
39
- isSaving = signal(false, /* @ts-ignore */
40
- ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
41
- // Cached list of every entity type — needed by the AsDetail save path to resolve
42
- // nested type schemas when rebuilding the nested PO wire shape from the flat form dict.
43
- allEntityTypes = signal([], /* @ts-ignore */
44
- ...(ngDevMode ? [{ debugName: "allEntityTypes" }] : /* istanbul ignore next */ []));
45
- generalErrors = computed(() => this.validationErrors().filter(e => !e.attributeName), /* @ts-ignore */
46
- ...(ngDevMode ? [{ debugName: "generalErrors" }] : /* istanbul ignore next */ []));
47
- constructor() {
48
- this.route.paramMap.pipe(takeUntilDestroyed()).subscribe(params => this.onParamsChange(params));
49
- }
50
- async onParamsChange(params) {
51
- this.type = params.get('type') || '';
52
- this.id = params.get('id') || '';
53
- try {
54
- const [types, item] = await Promise.all([
55
- this.sparkService.getEntityTypes(),
56
- this.sparkService.get(this.type, this.id)
57
- ]);
58
- const entityType = types.find(t => t.id === this.type || t.alias === this.type) || null;
59
- this.entityType.set(entityType);
60
- this.allEntityTypes.set(types);
61
- this.item.set(item);
62
- this.initFormData();
63
- }
64
- catch (e) {
65
- const error = e;
66
- this.validationErrors.set([{
67
- attributeName: '',
68
- errorMessage: { en: error.error?.error || error.message || 'An unexpected error occurred' },
69
- ruleType: 'error'
70
- }]);
71
- }
72
- }
73
- initFormData() {
74
- const data = {};
75
- const currentItem = this.item();
76
- this.getEditableAttributes().forEach(attr => {
77
- const itemAttr = currentItem?.attributes.find(a => a.name === attr.name);
78
- if (attr.dataType === 'Reference') {
79
- data[attr.name] = itemAttr?.value ?? null;
80
- }
81
- else if (attr.dataType === 'AsDetail') {
82
- // Server emits nested PO(s) in attr.object / attr.objects. Flatten back into the
83
- // Record<string, any> shape the form has always used so the rest of the component
84
- // tree stays unchanged.
85
- if (attr.isArray) {
86
- data[attr.name] = (itemAttr?.objects ?? []).map(po => nestedPoToDict(po));
87
- }
88
- else {
89
- data[attr.name] = itemAttr?.object ? nestedPoToDict(itemAttr.object) : {};
90
- }
91
- }
92
- else if (attr.dataType === 'boolean' && !attr.lookupReferenceType) {
93
- // `?? false` only for a real two-state checkbox. A lookup-backed boolean has a third state
94
- // that is expressed as the absence of a value, and coercing it here destroyed that BEFORE
95
- // the control rendered: merely opening the form and saving turned "unset" into an explicit
96
- // false, permanently and with nothing shown to the user.
97
- data[attr.name] = itemAttr?.value ?? false;
98
- }
99
- else if (isDateDataType(attr.dataType)) {
100
- // The wire carries a full ISO-8601 instant with an offset; a native date/time control accepts
101
- // only a bare local wall clock. Assigning the wire value directly does not fail loudly -- the
102
- // control just renders blank, and saving the untouched form writes that blank back.
103
- data[attr.name] = toDateInputValue(attr.dataType, itemAttr?.value);
104
- }
105
- else {
106
- data[attr.name] = itemAttr?.value ?? '';
107
- }
108
- });
109
- this.formData.set(data);
110
- }
111
- /**
112
- * Resolves a nested AsDetail type by CLR name, for rebuilding the nested PO wire shape on save.
113
- *
114
- * ⚠️ <b>`detailTypes` first, catalogue second.</b> The catalogue from `/spark/types` is
115
- * <b>Query-gated</b>, and an AsDetail row type usually has no rights of its own — nobody grants
116
- * `Query/GateSettings`, because a gate is edited through the Repository that owns it. So the
117
- * catalogue does not contain it, the resolver returned undefined, and the save quietly took the
118
- * scalar branch: the attribute went out as a raw dict under `value` instead of a nested PO under
119
- * `object`, the server could not map it, and `EntityMapper`'s conversion `catch` swallowed the
120
- * failure. The save reported success and wrote nothing.
121
- * <para>
122
- * `detailTypes` is the parent's own copy of its row types, carried on the type definition for
123
- * exactly this reason (#385 fixed the rendering half; this is the save half). It is gated on the
124
- * parent's right, which is the right that governs editing the row anyway.
125
- * </para>
126
- */
127
- resolveEntityType() {
128
- const cache = this.allEntityTypes();
129
- const detailTypes = this.entityType()?.detailTypes ?? [];
130
- return (clrName) => detailTypes.find(t => t.clrType === clrName) ?? cache.find(t => t.clrType === clrName);
131
- }
132
- /**
133
- * The attributes this page will read values from when it builds the save.
134
- *
135
- * ⚠️ <b>Overlaid before filtering, not after loading.</b> A refresh hook can reveal an attribute
136
- * that was hidden when the object was loaded, and the ordinary reason it does so is that the
137
- * attribute has just become required. Filtering on the loaded state left such an attribute out of
138
- * both {@link initFormData} and the save payload, so the user filled in a field whose value was
139
- * then dropped on the floor and refused by the server as missing — with no way out of the form.
140
- * The overlay is the form's, bound two-way, so the two halves cannot drift again.
141
- */
142
- getEditableAttributes() {
143
- const overlay = this.refreshOverlay();
144
- return this.entityType()?.attributes
145
- .map(a => applyOverlay(a, overlay[a.name]))
146
- .filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
147
- .sort((a, b) => a.order - b.order) || [];
148
- }
149
- async onSave() {
150
- const currentItem = this.item();
151
- if (!this.entityType() || !currentItem)
152
- return;
153
- this.validationErrors.set([]);
154
- this.isSaving.set(true);
155
- const resolver = this.resolveEntityType();
156
- const attributes = currentItem.attributes.map(attr => {
157
- const editableAttr = this.getEditableAttributes().find(a => a.name === attr.name);
158
- // AsDetail: formData[name] is a flat dict (single) or array of dicts (array). Rebuild
159
- // the nested PO wire shape so the server's polymorphic converter hydrates it into a
160
- // PersistentObjectAttributeAsDetail.
161
- if (editableAttr?.dataType === 'AsDetail' && editableAttr.asDetailType) {
162
- const nestedType = resolver(editableAttr.asDetailType);
163
- if (nestedType) {
164
- const raw = this.formData()[attr.name];
165
- if (editableAttr.isArray) {
166
- const items = Array.isArray(raw) ? raw : [];
167
- return {
168
- ...attr,
169
- value: null,
170
- object: null,
171
- objects: items.map(item => dictToNestedPo((item ?? {}), nestedType, resolver)),
172
- asDetailType: editableAttr.asDetailType,
173
- isValueChanged: true,
174
- };
175
- }
176
- return {
177
- ...attr,
178
- value: null,
179
- object: raw ? dictToNestedPo(raw, nestedType, resolver) : null,
180
- objects: null,
181
- asDetailType: editableAttr.asDetailType,
182
- isValueChanged: true,
183
- };
184
- }
185
- }
186
- // `in`, not a truthiness or `?? attr.value` check: an attribute the refresh revealed has no
187
- // slot until its control writes one, and an attribute the user cleared has a slot holding ''.
188
- // Coalescing would resurrect the loaded value on exactly the edit that removed it.
189
- const formData = this.formData();
190
- const rawValue = editableAttr && attr.name in formData ? formData[attr.name] : attr.value;
191
- if (editableAttr && isDateDataType(editableAttr.dataType)) {
192
- // Back out of the control's bare wall clock into a complete ISO-8601 instant, carrying the
193
- // viewer's offset for the entered date.
194
- const converted = fromDateInputValue(editableAttr.dataType, rawValue);
195
- // Compare by instant, not by text: the round trip rewrites the offset to the viewer's even
196
- // when nothing was edited.
197
- const changed = !wireDatesEqual(converted, attr.value);
198
- return {
199
- ...attr,
200
- // When the value did not actually change, send back exactly what was loaded. Sending the
201
- // re-converted string would preserve the instant but rewrite the stored offset to the
202
- // viewer's, so merely opening a record and saving it would relabel a Seattle registration
203
- // as a Brussels one. The offset is not business data (so the instant is what we guarantee),
204
- // but there is no reason to discard it on a save that changed nothing.
205
- value: changed ? converted : attr.value,
206
- isValueChanged: changed,
207
- };
208
- }
209
- const newValue = rawValue;
210
- return {
211
- ...attr,
212
- value: newValue,
213
- isValueChanged: editableAttr ? newValue !== attr.value : false
214
- };
215
- });
216
- const po = {
217
- id: currentItem.id,
218
- // Send back the token this object was loaded with. Left out, the server skips the
219
- // concurrency check entirely -- it is opt-in by presence -- and a save over somebody else's
220
- // edit succeeds silently. Left as undefined when the server sent none, which drops the key
221
- // from the JSON and restores exactly the old behaviour rather than sending an empty string
222
- // that could never match.
223
- etag: currentItem.etag,
224
- name: this.formData()['Name'] || currentItem.name,
225
- objectTypeId: this.entityType().id,
226
- attributes
227
- };
228
- try {
229
- const result = await this.sparkService.update(this.type, this.id, po);
230
- this.isSaving.set(false);
231
- this.saved.emit(result);
232
- this.router.navigate(['/po', this.type, this.id]);
233
- }
234
- catch (e) {
235
- this.isSaving.set(false);
236
- const error = e;
237
- // ⚠️ The errors live under `result`, because every endpoint wraps its body in a
238
- // ClientOperationEnvelope: `{ result: { errors: [...] }, operations: [] }`. Reading
239
- // `error.error.errors` matched nothing, so this branch was dead and EVERY save-time
240
- // validation failure fell through to the raw Angular HTTP string below.
241
- const errors = error.error?.result?.errors ?? error.error?.errors;
242
- if (error.status === 400 && errors) {
243
- this.validationErrors.set(errors);
244
- }
245
- else if (error.status === 409) {
246
- // Somebody saved this record between the load and this save. The server's own body says
247
- // only "Concurrency conflict" -- deliberately, since the real message carries the change
248
- // vector -- which is accurate, untranslated, and tells the user nothing to do about it.
249
- // The form keeps its values, so the typing is not lost.
250
- this.validationErrors.set([{
251
- attributeName: '',
252
- errorMessage: { en: this.language.t('common.concurrencyConflict') },
253
- ruleType: 'error'
254
- }]);
255
- }
256
- else {
257
- this.validationErrors.set([{
258
- attributeName: '',
259
- // Prefer the server's own message, as the load path already does. A refusal arrives as
260
- // 404 by design (an anti-oracle measure), and `error.message` renders that as
261
- // "Http failure response for /spark/po/...: 404 Not Found" inside the validation summary.
262
- errorMessage: { en: error.error?.result?.error || error.error?.error || error.message || 'An unexpected error occurred' },
263
- ruleType: 'error'
264
- }]);
265
- }
266
- }
267
- }
268
- onCancel() {
269
- this.cancelled.emit();
270
- this.router.navigate(['/po', this.type, this.id]);
271
- }
272
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkPoEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
273
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", 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 | resolveTranslation }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\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", "announce", "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", "objectTypeId", "objectId", "triggerPathPrefix", "refreshOverlay"], outputs: ["formDataChange", "save", "cancel", "nestedTriggerRequested", "nestedTriggerBlurred", "refreshOverlayChange"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
274
- }
275
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkPoEditComponent, decorators: [{
276
- type: Component,
277
- 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 | resolveTranslation }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\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" }]
278
- }], ctorParameters: () => [], propDecorators: { saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
279
-
280
- /**
281
- * Generated bundle index. Do not edit.
282
- */
283
-
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, computed, inject, output, signal } from "@angular/core";
3
+ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
4
+ import { CommonModule } from "@angular/common";
5
+ import { ActivatedRoute, Router } from "@angular/router";
6
+ import { Color } from "@mintplayer/ng-bootstrap";
7
+ import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
8
+ import { BsContainerComponent } from "@mintplayer/ng-bootstrap/container";
9
+ import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
10
+ import { SparkLanguageService, SparkService } from "@mintplayer/ng-spark/services";
11
+ import { SparkPoFormComponent } from "@mintplayer/ng-spark/po-form";
12
+ import { ResolveTranslationPipe, TranslateKeyPipe } from "@mintplayer/ng-spark/pipes";
13
+ import { ShowedOn, applyOverlay, dictToNestedPo, fromDateInputValue, hasShowedOnFlag, isDateDataType, nestedPoToDict, toDateInputValue, wireDatesEqual } from "@mintplayer/ng-spark/models";
14
+ var SparkPoEditComponent = class SparkPoEditComponent {
15
+ route = inject(ActivatedRoute);
16
+ router = inject(Router);
17
+ sparkService = inject(SparkService);
18
+ language = inject(SparkLanguageService);
19
+ saved = output();
20
+ cancelled = output();
21
+ colors = Color;
22
+ entityType = signal(null, ...ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []);
23
+ item = signal(null, ...ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []);
24
+ type = "";
25
+ id = "";
26
+ formData = signal({}, ...ngDevMode ? [{ debugName: "formData" }] : /* istanbul ignore next */ []);
27
+ refreshOverlay = signal({}, ...ngDevMode ? [{ debugName: "refreshOverlay" }] : /* istanbul ignore next */ []);
28
+ validationErrors = signal([], ...ngDevMode ? [{ debugName: "validationErrors" }] : /* istanbul ignore next */ []);
29
+ isSaving = signal(false, ...ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []);
30
+ allEntityTypes = signal([], ...ngDevMode ? [{ debugName: "allEntityTypes" }] : /* istanbul ignore next */ []);
31
+ generalErrors = computed(() => this.validationErrors().filter((e) => !e.attributeName), ...ngDevMode ? [{ debugName: "generalErrors" }] : /* istanbul ignore next */ []);
32
+ constructor() {
33
+ this.route.paramMap.pipe(takeUntilDestroyed()).subscribe((params) => this.onParamsChange(params));
34
+ }
35
+ async onParamsChange(params) {
36
+ this.type = params.get("type") || "";
37
+ this.id = params.get("id") || "";
38
+ try {
39
+ const [types, item] = await Promise.all([this.sparkService.getEntityTypes(), this.sparkService.get(this.type, this.id)]);
40
+ const entityType = types.find((t) => t.id === this.type || t.alias === this.type) || null;
41
+ this.entityType.set(entityType);
42
+ this.allEntityTypes.set(types);
43
+ this.item.set(item);
44
+ this.initFormData();
45
+ } catch (e) {
46
+ const error = e;
47
+ this.validationErrors.set([{
48
+ attributeName: "",
49
+ errorMessage: { en: error.error?.error || error.message || "An unexpected error occurred" },
50
+ ruleType: "error"
51
+ }]);
52
+ }
53
+ }
54
+ initFormData() {
55
+ const data = {};
56
+ const currentItem = this.item();
57
+ this.getEditableAttributes().forEach((attr) => {
58
+ const itemAttr = currentItem?.attributes.find((a) => a.name === attr.name);
59
+ if (attr.dataType === "Reference") data[attr.name] = itemAttr?.value ?? null;
60
+ else if (attr.dataType === "AsDetail") {
61
+ if (attr.isArray) data[attr.name] = (itemAttr?.objects ?? []).map((po) => nestedPoToDict(po));
62
+ else data[attr.name] = itemAttr?.object ? nestedPoToDict(itemAttr.object) : {};
63
+ } else if (attr.dataType === "boolean" && !attr.lookupReferenceType) data[attr.name] = itemAttr?.value ?? false;
64
+ else if (isDateDataType(attr.dataType)) data[attr.name] = toDateInputValue(attr.dataType, itemAttr?.value);
65
+ else data[attr.name] = itemAttr?.value ?? "";
66
+ });
67
+ this.formData.set(data);
68
+ }
69
+ resolveEntityType() {
70
+ const cache = this.allEntityTypes();
71
+ const detailTypes = this.entityType()?.detailTypes ?? [];
72
+ return (clrName) => detailTypes.find((t) => t.clrType === clrName) ?? cache.find((t) => t.clrType === clrName);
73
+ }
74
+ getEditableAttributes() {
75
+ const overlay = this.refreshOverlay();
76
+ return this.entityType()?.attributes.map((a) => applyOverlay(a, overlay[a.name])).filter((a) => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject)).sort((a, b) => a.order - b.order) || [];
77
+ }
78
+ async onSave() {
79
+ const currentItem = this.item();
80
+ if (!this.entityType() || !currentItem) return;
81
+ this.validationErrors.set([]);
82
+ this.isSaving.set(true);
83
+ const resolver = this.resolveEntityType();
84
+ const attributes = currentItem.attributes.map((attr) => {
85
+ const editableAttr = this.getEditableAttributes().find((a) => a.name === attr.name);
86
+ if (editableAttr?.dataType === "AsDetail" && editableAttr.asDetailType) {
87
+ const nestedType = resolver(editableAttr.asDetailType);
88
+ if (nestedType) {
89
+ const raw = this.formData()[attr.name];
90
+ if (editableAttr.isArray) {
91
+ const items = Array.isArray(raw) ? raw : [];
92
+ return {
93
+ ...attr,
94
+ value: null,
95
+ object: null,
96
+ objects: items.map((item) => dictToNestedPo(item ?? {}, nestedType, resolver)),
97
+ asDetailType: editableAttr.asDetailType,
98
+ isValueChanged: true
99
+ };
100
+ }
101
+ return {
102
+ ...attr,
103
+ value: null,
104
+ object: raw ? dictToNestedPo(raw, nestedType, resolver) : null,
105
+ objects: null,
106
+ asDetailType: editableAttr.asDetailType,
107
+ isValueChanged: true
108
+ };
109
+ }
110
+ }
111
+ const formData = this.formData();
112
+ const rawValue = editableAttr && attr.name in formData ? formData[attr.name] : attr.value;
113
+ if (editableAttr && isDateDataType(editableAttr.dataType)) {
114
+ const converted = fromDateInputValue(editableAttr.dataType, rawValue);
115
+ const changed = !wireDatesEqual(converted, attr.value);
116
+ return {
117
+ ...attr,
118
+ value: changed ? converted : attr.value,
119
+ isValueChanged: changed
120
+ };
121
+ }
122
+ const newValue = rawValue;
123
+ return {
124
+ ...attr,
125
+ value: newValue,
126
+ isValueChanged: editableAttr ? newValue !== attr.value : false
127
+ };
128
+ });
129
+ const po = {
130
+ id: currentItem.id,
131
+ etag: currentItem.etag,
132
+ name: this.formData()["Name"] || currentItem.name,
133
+ objectTypeId: this.entityType().id,
134
+ attributes
135
+ };
136
+ try {
137
+ const result = await this.sparkService.update(this.type, this.id, po);
138
+ this.isSaving.set(false);
139
+ this.saved.emit(result);
140
+ this.router.navigate([
141
+ "/po",
142
+ this.type,
143
+ this.id
144
+ ]);
145
+ } catch (e) {
146
+ this.isSaving.set(false);
147
+ const error = e;
148
+ const errors = error.error?.result?.errors ?? error.error?.errors;
149
+ if (error.status === 400 && errors) this.validationErrors.set(errors);
150
+ else if (error.status === 409) this.validationErrors.set([{
151
+ attributeName: "",
152
+ errorMessage: { en: this.language.t("common.concurrencyConflict") },
153
+ ruleType: "error"
154
+ }]);
155
+ else this.validationErrors.set([{
156
+ attributeName: "",
157
+ errorMessage: { en: error.error?.result?.error || error.error?.error || error.message || "An unexpected error occurred" },
158
+ ruleType: "error"
159
+ }]);
160
+ }
161
+ }
162
+ onCancel() {
163
+ this.cancelled.emit();
164
+ this.router.navigate([
165
+ "/po",
166
+ this.type,
167
+ this.id
168
+ ]);
169
+ }
170
+ static ɵfac = i0.ɵɵngDeclareFactory({
171
+ minVersion: "12.0.0",
172
+ version: "22.2.0",
173
+ ngImport: i0,
174
+ type: SparkPoEditComponent,
175
+ deps: [],
176
+ target: i0.ɵɵFactoryTarget.Component
177
+ });
178
+ static ɵcmp = i0.ɵɵngDeclareComponent({
179
+ minVersion: "17.0.0",
180
+ version: "22.2.0",
181
+ type: SparkPoEditComponent,
182
+ isStandalone: true,
183
+ selector: "spark-po-edit",
184
+ outputs: {
185
+ saved: "saved",
186
+ cancelled: "cancelled"
187
+ },
188
+ ngImport: i0,
189
+ 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 | resolveTranslation }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\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",
190
+ dependencies: [
191
+ {
192
+ kind: "ngmodule",
193
+ type: CommonModule
194
+ },
195
+ {
196
+ kind: "component",
197
+ type: BsAlertComponent,
198
+ selector: "bs-alert",
199
+ inputs: [
200
+ "type",
201
+ "announce",
202
+ "isVisible"
203
+ ],
204
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
205
+ },
206
+ {
207
+ kind: "component",
208
+ type: BsContainerComponent,
209
+ selector: "bs-container"
210
+ },
211
+ {
212
+ kind: "component",
213
+ type: BsSpinnerComponent,
214
+ selector: "bs-spinner",
215
+ inputs: ["type", "color"]
216
+ },
217
+ {
218
+ kind: "component",
219
+ type: SparkPoFormComponent,
220
+ selector: "spark-po-form",
221
+ inputs: [
222
+ "entityType",
223
+ "formData",
224
+ "validationErrors",
225
+ "showButtons",
226
+ "isSaving",
227
+ "parentId",
228
+ "parentType",
229
+ "objectTypeId",
230
+ "objectId",
231
+ "triggerPathPrefix",
232
+ "refreshOverlay"
233
+ ],
234
+ outputs: [
235
+ "formDataChange",
236
+ "save",
237
+ "cancel",
238
+ "nestedTriggerRequested",
239
+ "nestedTriggerBlurred",
240
+ "refreshOverlayChange"
241
+ ]
242
+ },
243
+ {
244
+ kind: "pipe",
245
+ type: ResolveTranslationPipe,
246
+ name: "resolveTranslation"
247
+ },
248
+ {
249
+ kind: "pipe",
250
+ type: TranslateKeyPipe,
251
+ name: "t"
252
+ }
253
+ ],
254
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
255
+ });
256
+ };
257
+ i0.ɵɵngDeclareClassMetadata({
258
+ minVersion: "12.0.0",
259
+ version: "22.2.0",
260
+ ngImport: i0,
261
+ type: SparkPoEditComponent,
262
+ decorators: [{
263
+ type: Component,
264
+ args: [{
265
+ selector: "spark-po-edit",
266
+ imports: [
267
+ CommonModule,
268
+ BsAlertComponent,
269
+ BsContainerComponent,
270
+ BsSpinnerComponent,
271
+ SparkPoFormComponent,
272
+ ResolveTranslationPipe,
273
+ TranslateKeyPipe
274
+ ],
275
+ changeDetection: ChangeDetectionStrategy.OnPush,
276
+ 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 | resolveTranslation }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'common.edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [(refreshOverlay)]=\"refreshOverlay\"\n [objectTypeId]=\"type\"\n [objectId]=\"id\"\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"
277
+ }]
278
+ }],
279
+ ctorParameters: () => [],
280
+ propDecorators: {
281
+ saved: [{
282
+ type: i0.Output,
283
+ args: ["saved"]
284
+ }],
285
+ cancelled: [{
286
+ type: i0.Output,
287
+ args: ["cancelled"]
288
+ }]
289
+ }
290
+ });
284
291
  export { SparkPoEditComponent };
285
- //# sourceMappingURL=mintplayer-ng-spark-po-edit.mjs.map
292
+
293
+ //# sourceMappingURL=mintplayer-ng-spark-po-edit.mjs.map