@acorex-platform/module-workplacesafety-management-client 0.0.21

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 (27) hide show
  1. package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs +1646 -0
  2. package/fesm2022/acorex-platform-module-workplacesafety-management-client-contracts.mjs.map +1 -0
  3. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs +203 -0
  4. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram-widget-view.component-C-qrrqOV.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs +119 -0
  6. package/fesm2022/acorex-platform-module-workplacesafety-management-client-control-hierarchy-diagram.service-BBhH7KP1.mjs.map +1 -0
  7. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-ppe-rule-status.provider-o4bAwYcq.mjs +42 -0
  8. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-ppe-rule-status.provider-o4bAwYcq.mjs.map +1 -0
  9. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs +42 -0
  10. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-ppe-rule-status.provider-DtqzcWh0.mjs.map +1 -0
  11. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-status.provider-B6IkRYbW.mjs +42 -0
  12. package/fesm2022/acorex-platform-module-workplacesafety-management-client-hazard-risk-status.provider-B6IkRYbW.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs +1900 -0
  14. package/fesm2022/acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs.map +1 -0
  15. package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs +175 -0
  16. package/fesm2022/acorex-platform-module-workplacesafety-management-client-preview-control-hierarchy-diagram.command-C2X3VpEt.mjs.map +1 -0
  17. package/fesm2022/acorex-platform-module-workplacesafety-management-client-publish-safety-plan.command-Ci-zcrmO.mjs +106 -0
  18. package/fesm2022/acorex-platform-module-workplacesafety-management-client-publish-safety-plan.command-Ci-zcrmO.mjs.map +1 -0
  19. package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-status.provider-BpWx3gIf.mjs +51 -0
  20. package/fesm2022/acorex-platform-module-workplacesafety-management-client-safety-plan-status.provider-BpWx3gIf.mjs.map +1 -0
  21. package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs +461 -0
  22. package/fesm2022/acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs +461 -0
  24. package/fesm2022/acorex-platform-module-workplacesafety-management-client.mjs.map +1 -0
  25. package/package.json +28 -0
  26. package/types/acorex-platform-module-workplacesafety-management-client-contracts.d.ts +1111 -0
  27. package/types/acorex-platform-module-workplacesafety-management-client.d.ts +20 -0
@@ -0,0 +1,1900 @@
1
+ import { AXPWidgetsCatalog, AXPSystemStatusType } from '@acorex-platform/framework-shared/core';
2
+ import { AXPDataGenerator, AXPHookService } from '@acorex-platform/framework-client/core';
3
+ import { AXPEntityDataSelectorService, AXPEntityDefinitionRegistryService, AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
4
+ import { AXPCommandService, AXPRuntimeRemoteHookKeys } from '@acorex-platform/framework-client/runtime';
5
+ import { AXPLayoutBuilderService, resolveLayoutBuilderDialogResult } from '@acorex-platform/framework-client/layout/builder';
6
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
7
+ import { clearDependentItemsOnParentChange, enrichSectionTypeSnapshot, isStepTableSectionType, safetyPlanSectionTypeAlreadyExists, SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_MESSAGE_KEY, collectBuiltInRepeatableSectionTypeIds, RootConfig, WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_WIDGET_OPTIONS, normalizeStepTableColumns, resolveSafetyPlanSectionItemFilterAsync, resolveSafetyPlanSectionItemEntityKey, resolveSafetyPlanSectionItemColumns, mapSafetyPlanSectionSelectedItems, dedupeSafetyPlanSectionItemSelection, sortStepTableColumns, stepTableColumnKey, resolveStepTableColumnLookupEntity, isStepTableColumnMultiLookup, stepTableColumnTitle, normalizeStepTableRows, resolveEntityRefId, AXMWorkplaceSafetyManagementCommandKeys, dedupeSafetyPlanSectionStructure, buildSectionStructureFromTypeSections, toBuilderValueFromStructure, SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY } from '@acorex-platform/module-workplacesafety-management-client/contracts';
8
+ import * as i0 from '@angular/core';
9
+ import { input, output, viewChild, inject, signal, computed, effect, ChangeDetectionStrategy, Component, untracked, Injectable } from '@angular/core';
10
+ import { isEqual, cloneDeep } from 'lodash';
11
+ import { AXDialogService } from '@acorex/components/dialog';
12
+ import * as i1 from '@acorex/components/button';
13
+ import { AXButtonModule } from '@acorex/components/button';
14
+ import * as i2 from '@acorex/components/decorators';
15
+ import { AXDecoratorModule } from '@acorex/components/decorators';
16
+ import { AXToastService } from '@acorex/components/toast';
17
+ import * as i3$1 from '@acorex/core/translation';
18
+ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
19
+ import { AXPStandardSectionItemsBuilderComponent } from '@acorex-platform/framework-client/layout/components';
20
+ import * as i3 from '@angular/common';
21
+ import { CommonModule, AsyncPipe } from '@angular/common';
22
+ import { n as normalizeSafetyPlanSectionBuilderValue, s as summarizeStepTableRow, S as SAFETY_PLAN_SECTIONS_SAVE_COMMAND, a as safetyPlanSectionsToBuilderValue, h as hydrateStepTableSectionsWithWorkSteps } from './acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs';
23
+ export { A as AXMSaveSafetyPlanSectionsCommand } from './acorex-platform-module-workplacesafety-management-client-save-safety-plan-sections.command-BJsg3pHZ.mjs';
24
+
25
+ //#endregion
26
+ //#region ----- Component -----
27
+ class AXMSafetyPlanSectionBuilderComponent {
28
+ constructor() {
29
+ //#region ----- Inputs / Outputs -----
30
+ this.value = input(null, /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
32
+ this.safetyPlanId = input(undefined, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "safetyPlanId" }] : /* istanbul ignore next */ []));
34
+ this.options = input({}, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
36
+ this.valueChange = output();
37
+ //#endregion
38
+ //#region ----- Services & Dependencies -----
39
+ this.standardRef = viewChild(AXPStandardSectionItemsBuilderComponent, /* @ts-ignore */
40
+ ...(ngDevMode ? [{ debugName: "standardRef" }] : /* istanbul ignore next */ []));
41
+ this.layoutBuilder = inject(AXPLayoutBuilderService);
42
+ this.translationService = inject(AXTranslationService);
43
+ this.toastService = inject(AXToastService);
44
+ this.entityDataSelector = inject(AXPEntityDataSelectorService);
45
+ this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
46
+ this.entityService = inject(AXPEntityService);
47
+ //#endregion
48
+ //#region ----- State Signals -----
49
+ this.builderValue = signal({ sections: [] }, /* @ts-ignore */
50
+ ...(ngDevMode ? [{ debugName: "builderValue" }] : /* istanbul ignore next */ []));
51
+ /** Applied on the next builder commit so section header title survives item add/edit. */
52
+ this.pendingSectionTitles = new Map();
53
+ this.disabledSectionPrompt = false;
54
+ this.standardConfig = computed(() => ({
55
+ mapItemToView: (item) => this.mapItemToView(item),
56
+ promptAddSection: this.allowAddSection() ? (value) => this.promptAddSection(value) : async () => null,
57
+ promptEditSection: this.allowEditSection()
58
+ ? (section, value) => this.promptEditSection(section, value)
59
+ : this.disabledSectionPrompt,
60
+ promptAddItems: this.allowAddItems() ? (sectionId, value) => this.promptAddItems(sectionId, value) : undefined,
61
+ promptEditItem: (item, sectionId, value) => this.promptEditItem(item, sectionId, value),
62
+ showSectionTechnicalName: false,
63
+ allowReorderSections: this.allowReorderSections(),
64
+ minSectionCount: this.allowAddSection() ? 0 : Number.MAX_SAFE_INTEGER,
65
+ texts: {
66
+ addSection: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-section.title',
67
+ addItem: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-item.title',
68
+ },
69
+ }), /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "standardConfig" }] : /* istanbul ignore next */ []));
71
+ this.hideHeaderActions = computed(() => this.options()['hideHeaderActions'] === true, /* @ts-ignore */
72
+ ...(ngDevMode ? [{ debugName: "hideHeaderActions" }] : /* istanbul ignore next */ []));
73
+ this.allowAddSection = computed(() => this.options()['allowAddSection'] !== false, /* @ts-ignore */
74
+ ...(ngDevMode ? [{ debugName: "allowAddSection" }] : /* istanbul ignore next */ []));
75
+ this.allowEditSection = computed(() => this.options()['allowEditSection'] === true, /* @ts-ignore */
76
+ ...(ngDevMode ? [{ debugName: "allowEditSection" }] : /* istanbul ignore next */ []));
77
+ this.allowEditItem = computed(() => this.options()['allowEditItem'] === true, /* @ts-ignore */
78
+ ...(ngDevMode ? [{ debugName: "allowEditItem" }] : /* istanbul ignore next */ []));
79
+ this.allowAddItems = computed(() => this.options()['allowAddItems'] !== false, /* @ts-ignore */
80
+ ...(ngDevMode ? [{ debugName: "allowAddItems" }] : /* istanbul ignore next */ []));
81
+ this.allowReorderSections = computed(() => {
82
+ const option = this.options()['allowReorderSections'];
83
+ return typeof option === 'boolean' ? option : true;
84
+ }, /* @ts-ignore */
85
+ ...(ngDevMode ? [{ debugName: "allowReorderSections" }] : /* istanbul ignore next */ []));
86
+ this.readonly = computed(() => this.options()['readonly'] === true, /* @ts-ignore */
87
+ ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
88
+ //#endregion
89
+ //#region ----- Lifecycle -----
90
+ this.#syncValueToBuilder = effect(() => {
91
+ const normalized = normalizeSafetyPlanSectionBuilderValue(this.value());
92
+ this.builderValue.set(this.toBuilderValue(normalized));
93
+ }, /* @ts-ignore */
94
+ ...(ngDevMode ? [{ debugName: "#syncValueToBuilder" }] : /* istanbul ignore next */ []));
95
+ }
96
+ //#endregion
97
+ //#region ----- Lifecycle -----
98
+ #syncValueToBuilder;
99
+ //#endregion
100
+ //#region ----- Public API -----
101
+ async addSection() {
102
+ await this.standardRef()?.addSection();
103
+ }
104
+ syncAfterSave(value) {
105
+ const normalized = normalizeSafetyPlanSectionBuilderValue(value);
106
+ this.builderValue.set(this.toBuilderValue(normalized));
107
+ queueMicrotask(() => this.standardRef()?.markClean());
108
+ }
109
+ resetAfterDiscard() {
110
+ const normalized = normalizeSafetyPlanSectionBuilderValue(this.value());
111
+ this.builderValue.set(this.toBuilderValue(normalized));
112
+ queueMicrotask(() => this.standardRef()?.markClean());
113
+ }
114
+ //#endregion
115
+ //#region ----- Event Handlers -----
116
+ onBuilderValueChange(next) {
117
+ const previous = this.fromBuilderValue(this.builderValue());
118
+ let nextValue = clearDependentItemsOnParentChange(previous, this.fromBuilderValue(next));
119
+ if (this.pendingSectionTitles.size > 0) {
120
+ nextValue = {
121
+ sections: nextValue.sections.map((section) => {
122
+ const title = this.pendingSectionTitles.get(section.id);
123
+ return title ? { ...section, title } : section;
124
+ }),
125
+ };
126
+ this.pendingSectionTitles.clear();
127
+ }
128
+ this.builderValue.set(this.toBuilderValue(nextValue));
129
+ this.valueChange.emit(nextValue);
130
+ }
131
+ //#endregion
132
+ //#region ----- Section prompts -----
133
+ async promptAddSection(value) {
134
+ const dialogValue = await this.promptSectionForm('create', {
135
+ sectionTypeId: '',
136
+ sectionTypeDescription: '',
137
+ title: '',
138
+ description: '',
139
+ });
140
+ if (!dialogValue)
141
+ return null;
142
+ if (await this.blockIfSectionTypeExists(value, dialogValue.sectionTypeId, {
143
+ sectionType: dialogValue.sectionType,
144
+ })) {
145
+ return null;
146
+ }
147
+ const sectionId = AXPDataGenerator.uuid();
148
+ return {
149
+ id: sectionId,
150
+ name: sectionId,
151
+ sectionTypeId: dialogValue.sectionTypeId,
152
+ sectionType: dialogValue.sectionType,
153
+ title: dialogValue.title,
154
+ description: dialogValue.description ?? '',
155
+ safetyPlanId: this.safetyPlanId() ?? '',
156
+ sortOrder: (value.sections?.length ?? 0) + 1,
157
+ items: [],
158
+ };
159
+ }
160
+ async promptEditSection(section, value) {
161
+ const previousSectionTypeId = String(section['sectionTypeId'] ?? '');
162
+ const sectionTypeSnapshot = section['sectionType'];
163
+ const dialogValue = await this.promptSectionForm('edit', {
164
+ sectionTypeId: previousSectionTypeId,
165
+ sectionType: sectionTypeSnapshot,
166
+ sectionTypeDescription: enrichSectionTypeSnapshot(previousSectionTypeId, sectionTypeSnapshot)?.description ??
167
+ String(section['description'] ?? ''),
168
+ title: String(section['title'] ?? ''),
169
+ description: String(section['description'] ?? ''),
170
+ });
171
+ if (!dialogValue)
172
+ return null;
173
+ if (await this.blockIfSectionTypeExists(value, dialogValue.sectionTypeId, {
174
+ excludeSectionId: String(section.id ?? ''),
175
+ sectionType: dialogValue.sectionType,
176
+ })) {
177
+ return null;
178
+ }
179
+ const sectionTypeChanged = previousSectionTypeId !== dialogValue.sectionTypeId;
180
+ const isStepTable = isStepTableSectionType(dialogValue.sectionTypeId, dialogValue.sectionType);
181
+ const keepWorkStep = isStepTable && !sectionTypeChanged
182
+ ? {
183
+ workStepTypeId: section['workStepTypeId'],
184
+ workStepType: section['workStepType'],
185
+ columns: section['columns'],
186
+ }
187
+ : {
188
+ workStepTypeId: undefined,
189
+ workStepType: undefined,
190
+ columns: [],
191
+ };
192
+ return {
193
+ ...section,
194
+ sectionTypeId: dialogValue.sectionTypeId,
195
+ sectionType: dialogValue.sectionType,
196
+ title: dialogValue.title,
197
+ description: dialogValue.description ?? '',
198
+ ...keepWorkStep,
199
+ ...(sectionTypeChanged ? { items: [] } : {}),
200
+ };
201
+ }
202
+ async blockIfSectionTypeExists(value, sectionTypeId, options) {
203
+ const repeatableSectionTypeIds = await this.loadRepeatableSectionTypeIds();
204
+ const sections = value.sections.map((section) => ({
205
+ id: String(section.id ?? ''),
206
+ sectionTypeId: section['sectionTypeId'],
207
+ sectionType: section['sectionType'],
208
+ }));
209
+ if (!safetyPlanSectionTypeAlreadyExists(sections, sectionTypeId, {
210
+ ...options,
211
+ repeatableSectionTypeIds,
212
+ sectionType: options?.sectionType,
213
+ })) {
214
+ return false;
215
+ }
216
+ this.toastService.show({
217
+ color: 'danger',
218
+ title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
219
+ content: await this.translationService.translateAsync(SAFETY_PLAN_SECTION_DUPLICATE_SECTION_TYPE_MESSAGE_KEY),
220
+ });
221
+ return true;
222
+ }
223
+ async loadRepeatableSectionTypeIds() {
224
+ const builtIn = collectBuiltInRepeatableSectionTypeIds();
225
+ try {
226
+ const entityKey = `${RootConfig.module.name}.${RootConfig.entities.sectionType.name}`;
227
+ const result = await this.entityService.withEntity(entityKey).data().query({
228
+ skip: 0,
229
+ take: 1000,
230
+ });
231
+ const fromQuery = (result.items ?? [])
232
+ .filter((row) => row.isRepeatable === true)
233
+ .map((row) => {
234
+ const id = row.id;
235
+ return typeof id === 'string' ? id.trim() : '';
236
+ })
237
+ .filter(Boolean);
238
+ return [...new Set([...fromQuery, ...builtIn])];
239
+ }
240
+ catch {
241
+ return builtIn;
242
+ }
243
+ }
244
+ async promptSectionForm(mode, seed) {
245
+ const titleKey = mode === 'create'
246
+ ? '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-section.title'
247
+ : '@general:actions.edit.title';
248
+ const title = (await this.translationService.translateAsync(titleKey)) || (mode === 'create' ? 'Add Section' : 'Edit');
249
+ const result = await this.layoutBuilder
250
+ .create()
251
+ .dialog((dialog) => {
252
+ dialog
253
+ .setTitle(title)
254
+ .setSize('md')
255
+ .setCloseButton(true)
256
+ .setContext({ ...seed })
257
+ .content((layout) => {
258
+ layout.mode('edit');
259
+ layout.dynamicForm(this.buildSectionFormDefinition());
260
+ })
261
+ .setActions((actions) => {
262
+ actions.cancel('@general:actions.cancel.title').submit('@general:actions.save.title');
263
+ })
264
+ .onAction((dialogRef) => {
265
+ if (dialogRef.action() === 'cancel') {
266
+ return { success: false };
267
+ }
268
+ const context = dialogRef.context();
269
+ const sectionTypeId = String(context.sectionTypeId ?? '').trim();
270
+ if (!sectionTypeId) {
271
+ return { success: false };
272
+ }
273
+ const sectionType = enrichSectionTypeSnapshot(sectionTypeId, context.sectionType);
274
+ const titleValue = String(context.title ?? '').trim() ||
275
+ String(sectionType?.title ?? '').trim() ||
276
+ String(sectionType?.code ?? '').trim();
277
+ if (!titleValue) {
278
+ return { success: false };
279
+ }
280
+ const description = String(context.sectionTypeDescription ?? '').trim() ||
281
+ String(context.description ?? '').trim() ||
282
+ String(sectionType?.description ?? '').trim();
283
+ return {
284
+ success: true,
285
+ kind: 'object',
286
+ value: {
287
+ sectionTypeId,
288
+ sectionType,
289
+ title: titleValue,
290
+ description,
291
+ sectionTypeDescription: description,
292
+ },
293
+ };
294
+ });
295
+ })
296
+ .show();
297
+ return result?.success && result.value ? result.value : null;
298
+ }
299
+ buildSectionFormDefinition() {
300
+ const group = {
301
+ name: 'section',
302
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.title',
303
+ parameters: [
304
+ {
305
+ path: 'sectionTypeId',
306
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.section-type.title',
307
+ widget: {
308
+ path: 'sectionTypeId',
309
+ type: AXPWidgetsCatalog.lookup,
310
+ options: {
311
+ entity: `${RootConfig.module.name}.${RootConfig.entities.sectionType.name}`,
312
+ ...WORKPLACE_SAFETY_SECTION_TYPE_LOOKUP_WIDGET_OPTIONS,
313
+ },
314
+ },
315
+ },
316
+ {
317
+ path: 'sectionTypeDescription',
318
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.description.title',
319
+ widget: {
320
+ path: 'sectionTypeDescription',
321
+ type: AXPWidgetsCatalog.largeText,
322
+ options: {
323
+ readonly: true,
324
+ rows: 2,
325
+ },
326
+ },
327
+ },
328
+ ],
329
+ };
330
+ return { groups: [group] };
331
+ }
332
+ //#endregion
333
+ //#region ----- Item prompts -----
334
+ async promptAddItems(sectionId, value) {
335
+ const section = value.sections.find((s) => s.id === sectionId);
336
+ if (!section || !this.sectionContextFromBuilderSection(section)) {
337
+ return null;
338
+ }
339
+ let sectionContext = this.sectionContextFromBuilderSection(section);
340
+ if (isStepTableSectionType(sectionContext.sectionTypeId, sectionContext.sectionType)) {
341
+ if (!sectionContext.workStepTypeId || sectionContext.columns.length === 0) {
342
+ const assigned = await this.ensureStepTableWorkStep(sectionId);
343
+ if (!assigned) {
344
+ return null;
345
+ }
346
+ const updatedSection = this.builderValue().sections.find((entry) => entry.id === sectionId);
347
+ const nextContext = updatedSection ? this.sectionContextFromBuilderSection(updatedSection) : null;
348
+ if (!nextContext?.workStepTypeId || nextContext.columns.length === 0) {
349
+ return null;
350
+ }
351
+ sectionContext = nextContext;
352
+ }
353
+ const currentSection = this.builderValue().sections.find((entry) => entry.id === sectionId) ?? section;
354
+ const sectionTitle = String(currentSection['title'] ?? '').trim();
355
+ const sectionTypeTitle = String(sectionContext.sectionType?.title ?? '').trim();
356
+ const applyTitleToSection = (currentSection.items?.length ?? 0) === 0 ||
357
+ !sectionTitle ||
358
+ sectionTitle === sectionTypeTitle ||
359
+ sectionTitle === sectionContext.sectionTypeId;
360
+ const row = await this.promptStepTableRowForm('create', sectionContext, {
361
+ title: applyTitleToSection
362
+ ? sectionTitle && sectionTitle !== sectionTypeTitle
363
+ ? sectionTitle
364
+ : String(sectionContext.workStepType?.title ?? '').trim()
365
+ : '',
366
+ }, { requireTitle: true, applyTitleToSection });
367
+ if (!row) {
368
+ return null;
369
+ }
370
+ if (applyTitleToSection && row.title?.trim()) {
371
+ this.patchSectionTitle(sectionId, row.title.trim());
372
+ }
373
+ const itemCount = this.builderValue().sections.find((entry) => entry.id === sectionId)?.items.length ??
374
+ currentSection.items.length;
375
+ return [this.toBuilderItemFromStepTableRow(row, itemCount)];
376
+ }
377
+ const existingIds = this.collectEntityIdsFromBuilderItems(section.items);
378
+ const dialogValue = await this.openItemDataSelector('create', undefined, sectionId, value, section, true);
379
+ if (!dialogValue)
380
+ return null;
381
+ const selected = this.expandDialogSelectionToLinkedEntities(dialogValue).filter((entry) => !existingIds.has(entry.id));
382
+ if (!selected.length) {
383
+ return null;
384
+ }
385
+ const startOrder = section.items.length;
386
+ return selected.map((entry, index) => this.toBuilderItemFromEntity(sectionId, entry, startOrder + index));
387
+ }
388
+ async promptEditItem(item, sectionId, value) {
389
+ const section = value.sections.find((s) => s.id === sectionId);
390
+ if (!section || !this.sectionContextFromBuilderSection(section)) {
391
+ return null;
392
+ }
393
+ const sectionContext = this.sectionContextFromBuilderSection(section);
394
+ if (isStepTableSectionType(sectionContext.sectionTypeId, sectionContext.sectionType)) {
395
+ const existing = item['stepTableRow'];
396
+ if (!existing || sectionContext.columns.length === 0) {
397
+ return null;
398
+ }
399
+ const seed = {
400
+ ...this.cellsToDialogValue(existing.cells ?? [], sectionContext.columns),
401
+ title: String(existing.title ?? item['title'] ?? '').trim(),
402
+ };
403
+ const isPrimaryRow = (item.order ?? 0) === 0;
404
+ const row = await this.promptStepTableRowForm('edit', sectionContext, seed, {
405
+ requireTitle: true,
406
+ applyTitleToSection: isPrimaryRow,
407
+ });
408
+ if (!row) {
409
+ return null;
410
+ }
411
+ if (isPrimaryRow && row.title?.trim()) {
412
+ this.patchSectionTitle(sectionId, row.title.trim());
413
+ }
414
+ return this.toBuilderItemFromStepTableRow({
415
+ ...row,
416
+ code: existing.code,
417
+ }, item.order ?? 0, String(item.id));
418
+ }
419
+ if (!this.allowEditItem()) {
420
+ return null;
421
+ }
422
+ const currentEntityId = this.resolveBuilderItemEntityId(item);
423
+ const seed = currentEntityId
424
+ ? {
425
+ itemIds: [currentEntityId],
426
+ items: Array.isArray(item['items'])
427
+ ? item['items']
428
+ : [],
429
+ }
430
+ : undefined;
431
+ const dialogValue = await this.openItemDataSelector('edit', seed, sectionId, value, section, false);
432
+ if (!dialogValue)
433
+ return null;
434
+ const selected = this.expandDialogSelectionToLinkedEntities(dialogValue);
435
+ if (!selected.length) {
436
+ return null;
437
+ }
438
+ const otherIds = this.collectEntityIdsFromBuilderItems(section.items.filter((row) => String(row.id) !== String(item.id)));
439
+ const replacement = selected.find((entry) => !otherIds.has(entry.id)) ?? selected[0];
440
+ return this.toBuilderItemFromEntity(sectionId, replacement, item.order ?? 0);
441
+ }
442
+ async toastWorkStepRequired() {
443
+ this.toastService.show({
444
+ color: 'warning',
445
+ title: await this.translationService.translateAsync('@general:messages.generic.error.title'),
446
+ content: await this.translationService.translateAsync('@workplacesafety-management:safety-plan-sections.messages.work-step-required.message'),
447
+ });
448
+ }
449
+ /** Prompts for Work Step and patches the Step Table section before rows can be added. */
450
+ async ensureStepTableWorkStep(sectionId) {
451
+ const picked = await this.promptWorkStepPicker();
452
+ if (!picked) {
453
+ return false;
454
+ }
455
+ const current = this.fromBuilderValue(this.builderValue());
456
+ const next = {
457
+ sections: current.sections.map((section) => {
458
+ if (section.id !== sectionId) {
459
+ return section;
460
+ }
461
+ return {
462
+ ...section,
463
+ workStepTypeId: picked.id,
464
+ workStepType: { id: picked.id, title: picked.title, code: picked.code },
465
+ columns: picked.columns,
466
+ };
467
+ }),
468
+ };
469
+ this.builderValue.set(this.toBuilderValue(next));
470
+ this.valueChange.emit(next);
471
+ return true;
472
+ }
473
+ patchSectionTitle(sectionId, title) {
474
+ const trimmed = title.trim();
475
+ if (!trimmed) {
476
+ return;
477
+ }
478
+ this.pendingSectionTitles.set(sectionId, trimmed);
479
+ }
480
+ async promptWorkStepPicker() {
481
+ const title = (await this.translationService.translateAsync('@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.work-step.title')) || 'Work Step';
482
+ const result = await this.layoutBuilder
483
+ .create()
484
+ .dialog((dialog) => {
485
+ dialog
486
+ .setTitle(title)
487
+ .setSize('md')
488
+ .setCloseButton(true)
489
+ .setContext({ workStepTypeId: '' })
490
+ .content((layout) => {
491
+ layout.mode('edit');
492
+ layout.dynamicForm({
493
+ groups: [
494
+ {
495
+ name: 'work-step',
496
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.work-step.title',
497
+ parameters: [
498
+ {
499
+ path: 'workStepTypeId',
500
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.work-step.title',
501
+ widget: {
502
+ path: 'workStepTypeId',
503
+ type: AXPWidgetsCatalog.lookup,
504
+ options: {
505
+ entity: `${RootConfig.module.name}.${RootConfig.entities.workStepType.name}`,
506
+ look: 'select',
507
+ textField: 'title',
508
+ valueField: 'id',
509
+ columns: ['code', 'title'],
510
+ expose: [
511
+ { source: 'id', target: 'workStepType.id' },
512
+ { source: 'title', target: 'workStepType.title' },
513
+ { source: 'code', target: 'workStepType.code' },
514
+ ],
515
+ },
516
+ },
517
+ },
518
+ ],
519
+ },
520
+ ],
521
+ });
522
+ })
523
+ .setActions((actions) => {
524
+ actions.cancel('@general:actions.cancel.title').submit('@general:actions.save.title');
525
+ })
526
+ .onAction(async (dialogRef) => {
527
+ if (dialogRef.action() === 'cancel') {
528
+ return { success: false };
529
+ }
530
+ const context = dialogRef.context();
531
+ const workStepTypeId = String(context.workStepTypeId ?? '').trim();
532
+ if (!workStepTypeId) {
533
+ return { success: false };
534
+ }
535
+ return {
536
+ success: true,
537
+ kind: 'object',
538
+ value: {
539
+ workStepTypeId,
540
+ workStepType: context.workStepType,
541
+ },
542
+ };
543
+ });
544
+ })
545
+ .show();
546
+ if (!result?.success || !result.value?.workStepTypeId) {
547
+ return null;
548
+ }
549
+ const loaded = await this.loadWorkStepType(result.value.workStepTypeId);
550
+ if (!loaded) {
551
+ await this.toastWorkStepRequired();
552
+ return null;
553
+ }
554
+ return loaded;
555
+ }
556
+ sectionContextFromBuilderSection(section) {
557
+ const sectionTypeId = String(section['sectionTypeId'] ?? '').trim();
558
+ if (!sectionTypeId) {
559
+ return null;
560
+ }
561
+ return {
562
+ sectionTypeId,
563
+ sectionType: enrichSectionTypeSnapshot(sectionTypeId, section['sectionType']),
564
+ workStepTypeId: typeof section['workStepTypeId'] === 'string' ? section['workStepTypeId'].trim() : undefined,
565
+ workStepType: section['workStepType'],
566
+ columns: normalizeStepTableColumns(section['columns']),
567
+ };
568
+ }
569
+ //#endregion
570
+ //#region ----- Catalog item selector -----
571
+ resolveSectionIndex(sectionId, builderValue) {
572
+ const orderedSections = [...builderValue.sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
573
+ return orderedSections.findIndex((section) => section.id === sectionId);
574
+ }
575
+ async loadHazardRiskRecords(hazardRiskIds) {
576
+ if (!hazardRiskIds.length) {
577
+ return [];
578
+ }
579
+ const entityKey = `${RootConfig.module.name}.${RootConfig.entities.hazardRisk.name}`;
580
+ const accessor = this.entityService.withEntity(entityKey);
581
+ const results = await Promise.all(hazardRiskIds.map((id) => accessor.data().byKey(id).catch(() => null)));
582
+ return results
583
+ .filter((record) => record != null)
584
+ .map((record) => record);
585
+ }
586
+ async resolveItemFilterForSection(sectionTypeId, sectionId, builderValue) {
587
+ const orderedSections = [...builderValue.sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
588
+ const sectionIndex = this.resolveSectionIndex(sectionId, builderValue);
589
+ if (sectionIndex < 0) {
590
+ return null;
591
+ }
592
+ const sections = this.fromBuilderValue({ sections: orderedSections }).sections;
593
+ return resolveSafetyPlanSectionItemFilterAsync(sectionTypeId, sections, sectionIndex, (ids) => this.loadHazardRiskRecords(ids));
594
+ }
595
+ async openItemDataSelector(mode, seed, sectionId, builderValue, section, allowMultiple) {
596
+ const sectionContext = this.sectionContextFromBuilderSection(section);
597
+ if (!sectionContext) {
598
+ return null;
599
+ }
600
+ const entityKey = resolveSafetyPlanSectionItemEntityKey(sectionContext.sectionTypeId, sectionContext.sectionType);
601
+ if (!entityKey) {
602
+ return null;
603
+ }
604
+ const dotIndex = entityKey.indexOf('.');
605
+ if (dotIndex <= 0) {
606
+ return null;
607
+ }
608
+ const entity = await this.entityRegistry.resolve(entityKey.substring(0, dotIndex), entityKey.substring(dotIndex + 1));
609
+ if (!entity) {
610
+ return null;
611
+ }
612
+ const filter = await this.resolveItemFilterForSection(sectionContext.sectionTypeId, sectionId, builderValue);
613
+ const columns = resolveSafetyPlanSectionItemColumns(sectionContext.sectionTypeId);
614
+ const selectedItemIds = (seed?.itemIds ?? []).filter((id) => typeof id === 'string' && id.length > 0);
615
+ const titleKey = mode === 'create'
616
+ ? '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-item.title'
617
+ : '@general:actions.edit.title';
618
+ const title = (await this.translationService.translateAsync(titleKey)) || (mode === 'create' ? 'Add Item' : 'Edit');
619
+ const result = await this.entityDataSelector.open({
620
+ entity,
621
+ title,
622
+ allowMultiple,
623
+ selectedItemIds: selectedItemIds.length > 0 ? selectedItemIds : undefined,
624
+ columns,
625
+ filters: filter ?? undefined,
626
+ allowCreate: 'none',
627
+ showCategoryFilter: false,
628
+ });
629
+ if (!result) {
630
+ return null;
631
+ }
632
+ if (!result.items?.length) {
633
+ return mode === 'edit' ? { itemIds: [], items: [] } : null;
634
+ }
635
+ const items = mapSafetyPlanSectionSelectedItems(sectionContext.sectionTypeId, result.items);
636
+ return dedupeSafetyPlanSectionItemSelection({
637
+ id: `${sectionId}__item_1`,
638
+ itemIds: items.map((item) => item.id),
639
+ items,
640
+ });
641
+ }
642
+ //#endregion
643
+ //#region ----- Builder value mapping -----
644
+ mapItemToView(item) {
645
+ const stepTableRow = item['stepTableRow'];
646
+ if (stepTableRow) {
647
+ const code = String(stepTableRow.code ?? '').trim();
648
+ return {
649
+ icon: 'fa-light fa-table-rows',
650
+ title: String(stepTableRow.title ?? item['title'] ?? '').trim() || 'Row',
651
+ name: code || undefined,
652
+ badges: [],
653
+ };
654
+ }
655
+ const snapshot = Array.isArray(item['items'])
656
+ ? item['items'][0]
657
+ : undefined;
658
+ const title = String(item['title'] ?? snapshot?.title ?? '').trim();
659
+ const code = String(item['name'] ?? snapshot?.code ?? '').trim();
660
+ return {
661
+ icon: 'fa-light fa-list-check',
662
+ title,
663
+ name: code || undefined,
664
+ badges: [],
665
+ };
666
+ }
667
+ toBuilderValue(value) {
668
+ return {
669
+ sections: value.sections.map((section, sectionIndex) => ({
670
+ id: section.id,
671
+ order: sectionIndex,
672
+ name: section.id,
673
+ sectionTypeId: section.sectionTypeId,
674
+ sectionType: enrichSectionTypeSnapshot(section.sectionTypeId, section.sectionType),
675
+ title: section.title,
676
+ description: section.description ?? '',
677
+ code: section.code,
678
+ safetyPlanId: section.safetyPlanId ?? this.safetyPlanId() ?? '',
679
+ sourceSafetyPlanTypeSectionId: section.sourceSafetyPlanTypeSectionId,
680
+ sortOrder: section.sortOrder ?? sectionIndex + 1,
681
+ required: section.required === true,
682
+ workStepTypeId: section.workStepTypeId,
683
+ workStepType: section.workStepType,
684
+ columns: normalizeStepTableColumns(section.columns),
685
+ items: this.expandSectionItemsToBuilderRows(section.id, section.items, section),
686
+ })),
687
+ };
688
+ }
689
+ fromBuilderValue(value) {
690
+ return normalizeSafetyPlanSectionBuilderValue({
691
+ sections: value.sections.map((section, sectionIndex) => ({
692
+ id: section.id,
693
+ sectionTypeId: String(section['sectionTypeId'] ?? ''),
694
+ sectionType: enrichSectionTypeSnapshot(String(section['sectionTypeId'] ?? ''), section['sectionType']),
695
+ title: String(section['title'] ?? ''),
696
+ description: String(section['description'] ?? ''),
697
+ code: typeof section['code'] === 'string' ? section['code'] : undefined,
698
+ safetyPlanId: String(section['safetyPlanId'] ?? this.safetyPlanId() ?? ''),
699
+ sourceSafetyPlanTypeSectionId: typeof section['sourceSafetyPlanTypeSectionId'] === 'string'
700
+ ? section['sourceSafetyPlanTypeSectionId']
701
+ : undefined,
702
+ sortOrder: typeof section['sortOrder'] === 'number' ? section['sortOrder'] : sectionIndex + 1,
703
+ required: section['required'] === true,
704
+ workStepTypeId: typeof section['workStepTypeId'] === 'string' ? section['workStepTypeId'] : undefined,
705
+ workStepType: section['workStepType'],
706
+ columns: normalizeStepTableColumns(section['columns']),
707
+ items: this.collapseBuilderRowsToStorageItems(section.id, section.items, section),
708
+ })),
709
+ });
710
+ }
711
+ expandSectionItemsToBuilderRows(sectionId, items, section) {
712
+ if (isStepTableSectionType(section.sectionTypeId, section.sectionType) ||
713
+ items.some((item) => item.stepTableRow)) {
714
+ const columns = normalizeStepTableColumns(section.columns);
715
+ return items
716
+ .filter((item) => item.stepTableRow)
717
+ .map((item, order) => this.toBuilderItemFromStepTableRow({
718
+ ...item.stepTableRow,
719
+ title: item.stepTableRow.title ||
720
+ summarizeStepTableRow(item.stepTableRow.cells ?? [], columns),
721
+ }, order, item.id));
722
+ }
723
+ const linked = this.flattenLinkedEntities(items);
724
+ return linked.map((entry, order) => this.toBuilderItemFromEntity(sectionId, entry, order));
725
+ }
726
+ collapseBuilderRowsToStorageItems(sectionId, items, section) {
727
+ const ordered = [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
728
+ const sectionTypeId = String(section['sectionTypeId'] ?? '');
729
+ const sectionType = section['sectionType'];
730
+ if (isStepTableSectionType(sectionTypeId, sectionType) || ordered.some((item) => item['stepTableRow'])) {
731
+ const columns = normalizeStepTableColumns(section['columns']);
732
+ const stepItems = [];
733
+ ordered.forEach((item, index) => {
734
+ const stepTableRow = item['stepTableRow'];
735
+ if (!stepTableRow) {
736
+ return;
737
+ }
738
+ const stepId = typeof item.id === 'string' && item.id && !String(item.id).includes('__entity_')
739
+ ? String(item.id)
740
+ : AXPDataGenerator.uuid();
741
+ const title = stepTableRow.title?.trim() ||
742
+ summarizeStepTableRow(stepTableRow.cells ?? [], columns) ||
743
+ 'Row';
744
+ stepItems.push({
745
+ id: stepId,
746
+ itemIds: [stepId],
747
+ items: [{ id: stepId, title, code: stepTableRow.code }],
748
+ stepTableRow: {
749
+ ...stepTableRow,
750
+ title,
751
+ sortOrder: index + 1,
752
+ },
753
+ });
754
+ });
755
+ return stepItems;
756
+ }
757
+ const itemIds = [];
758
+ const snapshots = [];
759
+ for (const item of ordered) {
760
+ const entityId = this.resolveBuilderItemEntityId(item);
761
+ if (!entityId || itemIds.includes(entityId)) {
762
+ continue;
763
+ }
764
+ itemIds.push(entityId);
765
+ const snapshot = Array.isArray(item['items'])
766
+ ? item['items'][0]
767
+ : undefined;
768
+ snapshots.push({
769
+ id: entityId,
770
+ title: (typeof snapshot?.title === 'string' && snapshot.title) ||
771
+ (typeof item['title'] === 'string' ? item['title'] : undefined),
772
+ code: (typeof snapshot?.code === 'string' && snapshot.code) ||
773
+ (typeof item['name'] === 'string' ? item['name'] : undefined),
774
+ });
775
+ }
776
+ if (!itemIds.length) {
777
+ return [];
778
+ }
779
+ return [
780
+ dedupeSafetyPlanSectionItemSelection({
781
+ id: `${sectionId}__item_1`,
782
+ itemIds,
783
+ items: snapshots,
784
+ }),
785
+ ];
786
+ }
787
+ flattenLinkedEntities(items) {
788
+ const linked = [];
789
+ const seen = new Set();
790
+ for (const slot of items) {
791
+ const ids = Array.isArray(slot.itemIds) ? slot.itemIds : [];
792
+ const snapshots = Array.isArray(slot.items) ? slot.items : [];
793
+ for (let index = 0; index < ids.length; index += 1) {
794
+ const id = String(ids[index] ?? '').trim();
795
+ if (!id || seen.has(id)) {
796
+ continue;
797
+ }
798
+ seen.add(id);
799
+ const snapshot = snapshots.find((entry) => entry?.id === id) ?? snapshots[index];
800
+ linked.push({
801
+ id,
802
+ title: snapshot?.title,
803
+ code: snapshot?.code,
804
+ });
805
+ }
806
+ }
807
+ return linked;
808
+ }
809
+ expandDialogSelectionToLinkedEntities(dialogValue) {
810
+ return this.flattenLinkedEntities([
811
+ {
812
+ id: 'dialog',
813
+ itemIds: [...(dialogValue.itemIds ?? [])],
814
+ items: dialogValue.items ? [...dialogValue.items] : [],
815
+ },
816
+ ]);
817
+ }
818
+ collectEntityIdsFromBuilderItems(items) {
819
+ const ids = new Set();
820
+ for (const item of items) {
821
+ const entityId = this.resolveBuilderItemEntityId(item);
822
+ if (entityId) {
823
+ ids.add(entityId);
824
+ }
825
+ }
826
+ return ids;
827
+ }
828
+ resolveBuilderItemEntityId(item) {
829
+ if (typeof item['entityId'] === 'string' && item['entityId'].trim()) {
830
+ return item['entityId'].trim();
831
+ }
832
+ if (Array.isArray(item['itemIds']) && typeof item['itemIds'][0] === 'string' && item['itemIds'][0].trim()) {
833
+ return item['itemIds'][0].trim();
834
+ }
835
+ const snapshot = Array.isArray(item['items'])
836
+ ? item['items'][0]
837
+ : undefined;
838
+ if (typeof snapshot?.id === 'string' && snapshot.id.trim()) {
839
+ return snapshot.id.trim();
840
+ }
841
+ return null;
842
+ }
843
+ toBuilderItemFromEntity(sectionId, entry, order) {
844
+ const title = String(entry.title ?? '').trim();
845
+ const code = String(entry.code ?? '').trim();
846
+ return {
847
+ id: `${sectionId}__entity_${entry.id}`,
848
+ order,
849
+ entityId: entry.id,
850
+ itemIds: [entry.id],
851
+ items: [{ id: entry.id, title: title || undefined, code: code || undefined }],
852
+ title,
853
+ name: code,
854
+ };
855
+ }
856
+ toBuilderItemFromStepTableRow(row, order, id) {
857
+ const stepId = typeof id === 'string' && id.trim() && !id.includes('__item_') && !id.includes('__entity_')
858
+ ? id.trim()
859
+ : AXPDataGenerator.uuid();
860
+ const title = String(row.title ?? '').trim() || 'Row';
861
+ const code = String(row.code ?? '').trim();
862
+ return {
863
+ id: stepId,
864
+ order,
865
+ entityId: stepId,
866
+ itemIds: [stepId],
867
+ items: [{ id: stepId, title, code: code || undefined }],
868
+ title,
869
+ name: code,
870
+ stepTableRow: {
871
+ ...row,
872
+ title,
873
+ sortOrder: order + 1,
874
+ },
875
+ };
876
+ }
877
+ //#endregion
878
+ //#region ----- Step Table row dialog -----
879
+ async promptStepTableRowForm(mode, sectionContext, seed, options) {
880
+ const columns = sortStepTableColumns(sectionContext.columns);
881
+ const requireTitle = options?.requireTitle === true;
882
+ const titleKey = mode === 'create'
883
+ ? '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-row.title'
884
+ : '@general:actions.edit.title';
885
+ const dialogTitle = (await this.translationService.translateAsync(titleKey)) ||
886
+ (mode === 'create' ? 'Add Row' : 'Edit');
887
+ const result = await this.layoutBuilder
888
+ .create()
889
+ .dialog((dialog) => {
890
+ dialog
891
+ .setTitle(dialogTitle)
892
+ .setSize('lg')
893
+ .setCloseButton(true)
894
+ .setContext({ ...seed })
895
+ .content((layout) => {
896
+ layout.mode('edit');
897
+ layout.dynamicForm(this.buildStepTableRowFormDefinition(columns, { includeTitle: requireTitle }));
898
+ })
899
+ .setActions((actions) => {
900
+ actions.cancel('@general:actions.cancel.title').submit('@general:actions.save.title');
901
+ })
902
+ .onAction((dialogRef) => {
903
+ if (dialogRef.action() === 'cancel') {
904
+ return { success: false };
905
+ }
906
+ const context = dialogRef.context();
907
+ const customTitle = String(context['title'] ?? '').trim();
908
+ if (requireTitle && !customTitle) {
909
+ return { success: false };
910
+ }
911
+ const cells = this.dialogValueToCells(context, columns);
912
+ if (!this.validateRequiredStepTableCells(cells, columns)) {
913
+ return { success: false };
914
+ }
915
+ return {
916
+ success: true,
917
+ kind: 'object',
918
+ value: {
919
+ cells,
920
+ title: customTitle || summarizeStepTableRow(cells, columns),
921
+ },
922
+ };
923
+ });
924
+ })
925
+ .show();
926
+ return result?.success && result.value ? result.value : null;
927
+ }
928
+ buildStepTableRowFormDefinition(columns, options) {
929
+ const ordered = sortStepTableColumns(columns);
930
+ const parameters = [];
931
+ if (options?.includeTitle) {
932
+ parameters.push({
933
+ path: 'title',
934
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.title.title',
935
+ widget: {
936
+ path: 'title',
937
+ type: AXPWidgetsCatalog.text,
938
+ options: {
939
+ placeholder: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.fields.title.placeholder',
940
+ },
941
+ },
942
+ });
943
+ }
944
+ for (const column of ordered) {
945
+ const key = stepTableColumnKey(column);
946
+ const lookupEntity = resolveStepTableColumnLookupEntity(column);
947
+ const multi = isStepTableColumnMultiLookup(column);
948
+ parameters.push({
949
+ path: key,
950
+ title: stepTableColumnTitle(column),
951
+ widget: lookupEntity
952
+ ? {
953
+ path: key,
954
+ type: AXPWidgetsCatalog.lookup,
955
+ options: {
956
+ look: multi ? 'lookup' : 'select',
957
+ entity: lookupEntity,
958
+ textField: 'title',
959
+ valueField: 'id',
960
+ columns: ['code', 'title'],
961
+ ...(multi ? { multiple: true } : {}),
962
+ expose: multi
963
+ ? [
964
+ { source: 'id', target: `${key}Refs.{id}` },
965
+ { source: 'title', target: `${key}Refs.{title}` },
966
+ { source: 'code', target: `${key}Refs.{code}` },
967
+ ]
968
+ : [
969
+ { source: 'id', target: `${key}Ref.id` },
970
+ { source: 'title', target: `${key}Ref.title` },
971
+ { source: 'code', target: `${key}Ref.code` },
972
+ ],
973
+ },
974
+ }
975
+ : {
976
+ path: key,
977
+ type: AXPWidgetsCatalog.text,
978
+ },
979
+ });
980
+ }
981
+ return {
982
+ groups: [
983
+ {
984
+ name: 'step-table-row',
985
+ title: '@workplacesafety-management:safety-plan-work-steps.entities.safety-plan-work-step.groups.table-content',
986
+ parameters,
987
+ },
988
+ ],
989
+ };
990
+ }
991
+ validateRequiredStepTableCells(cells, columns) {
992
+ for (const column of columns) {
993
+ if (column.required !== true) {
994
+ continue;
995
+ }
996
+ const key = stepTableColumnKey(column);
997
+ const cell = cells.find((entry) => entry.columnKey === key);
998
+ if (!cell) {
999
+ return false;
1000
+ }
1001
+ const lookupEntity = resolveStepTableColumnLookupEntity(column);
1002
+ if (lookupEntity) {
1003
+ if (!(cell.refIds?.length)) {
1004
+ return false;
1005
+ }
1006
+ }
1007
+ else if (!cell.textValue?.trim()) {
1008
+ return false;
1009
+ }
1010
+ }
1011
+ return true;
1012
+ }
1013
+ cellsToDialogValue(cells, columns) {
1014
+ const ordered = sortStepTableColumns(columns);
1015
+ const normalized = normalizeStepTableRows([{ id: 'row', sortOrder: 1, cells }], ordered)[0];
1016
+ const dialogRow = {};
1017
+ for (const column of ordered) {
1018
+ const key = stepTableColumnKey(column);
1019
+ const cell = normalized?.cells.find((entry) => entry.columnKey === key);
1020
+ if (!cell) {
1021
+ continue;
1022
+ }
1023
+ const lookupEntity = resolveStepTableColumnLookupEntity(column);
1024
+ if (!lookupEntity) {
1025
+ dialogRow[key] = cell.textValue ?? '';
1026
+ continue;
1027
+ }
1028
+ if (isStepTableColumnMultiLookup(column)) {
1029
+ dialogRow[key] = cell.refIds ?? [];
1030
+ dialogRow[`${key}Refs`] = cell.refs ?? [];
1031
+ }
1032
+ else {
1033
+ dialogRow[key] = cell.refIds?.[0];
1034
+ dialogRow[`${key}Ref`] = cell.refs?.[0];
1035
+ }
1036
+ }
1037
+ return dialogRow;
1038
+ }
1039
+ dialogValueToCells(row, columns) {
1040
+ const ordered = sortStepTableColumns(columns);
1041
+ return ordered.map((column) => {
1042
+ const key = stepTableColumnKey(column);
1043
+ const lookupEntity = resolveStepTableColumnLookupEntity(column);
1044
+ if (!lookupEntity) {
1045
+ return {
1046
+ columnKey: key,
1047
+ textValue: typeof row[key] === 'string' ? String(row[key]) : '',
1048
+ };
1049
+ }
1050
+ if (isStepTableColumnMultiLookup(column)) {
1051
+ const refs = Array.isArray(row[`${key}Refs`])
1052
+ ? row[`${key}Refs`]
1053
+ : [];
1054
+ const ids = Array.isArray(row[key])
1055
+ ? row[key].filter((value) => typeof value === 'string')
1056
+ : refs
1057
+ .map((ref) => (typeof ref?.id === 'string' ? ref.id : ''))
1058
+ .filter(Boolean);
1059
+ return {
1060
+ columnKey: key,
1061
+ refIds: ids,
1062
+ refs: ids.map((idValue) => {
1063
+ const ref = refs.find((entry) => entry?.id === idValue);
1064
+ return { id: idValue, title: ref?.title, code: ref?.code };
1065
+ }),
1066
+ };
1067
+ }
1068
+ const ref = row[`${key}Ref`];
1069
+ const idValue = (typeof row[key] === 'string' && String(row[key])) ||
1070
+ (typeof ref?.id === 'string' ? ref.id : '');
1071
+ return {
1072
+ columnKey: key,
1073
+ refIds: idValue ? [idValue] : [],
1074
+ refs: idValue ? [{ id: idValue, title: ref?.title, code: ref?.code }] : [],
1075
+ };
1076
+ });
1077
+ }
1078
+ async loadWorkStepType(workStepTypeId) {
1079
+ try {
1080
+ const entityKey = `${RootConfig.module.name}.${RootConfig.entities.workStepType.name}`;
1081
+ const row = await this.entityService.withEntity(entityKey).data().byKey(workStepTypeId);
1082
+ if (!row) {
1083
+ return null;
1084
+ }
1085
+ const record = row;
1086
+ return {
1087
+ id: String(record.id ?? workStepTypeId),
1088
+ title: record.title,
1089
+ code: record.code,
1090
+ columns: normalizeStepTableColumns(record.columns),
1091
+ };
1092
+ }
1093
+ catch {
1094
+ return null;
1095
+ }
1096
+ }
1097
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1098
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMSafetyPlanSectionBuilderComponent, isStandalone: true, selector: "axm-safety-plan-section-builder", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, safetyPlanId: { classPropertyName: "safetyPlanId", publicName: "safetyPlanId", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "standardRef", first: true, predicate: AXPStandardSectionItemsBuilderComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"axm-safety-plan-section-builder\">\n @if (!readonly() && !hideHeaderActions() && allowAddSection()) {\n <div class=\"__header\">\n <div class=\"__header-left\"></div>\n <div class=\"__header-right\">\n <ax-button\n class=\"ax-sm\"\n [text]=\"\n '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-section.title'\n | translate\n | async\n \"\n [color]=\"'primary'\"\n (onClick)=\"addSection()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-layer-group\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </div>\n }\n\n <axp-standard-section-items-builder\n class=\"block min-h-[200px]\"\n [value]=\"builderValue()\"\n [config]=\"standardConfig()\"\n [readonly]=\"readonly()\"\n (valueChange)=\"onBuilderValueChange($event)\"\n />\n</div>\n", styles: [".axm-safety-plan-section-builder .__header{margin-bottom:calc(var(--spacing, .25rem) * 3);display:flex;align-items:center;justify-content:space-between}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i2.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStandardSectionItemsBuilderComponent, selector: "axp-standard-section-items-builder", inputs: ["value", "config", "readonly"], outputs: ["valueChange"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1099
+ }
1100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionBuilderComponent, decorators: [{
1101
+ type: Component,
1102
+ args: [{ selector: 'axm-safety-plan-section-builder', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, AXButtonModule, AXDecoratorModule, AXTranslationModule, AXPStandardSectionItemsBuilderComponent], template: "<div class=\"axm-safety-plan-section-builder\">\n @if (!readonly() && !hideHeaderActions() && allowAddSection()) {\n <div class=\"__header\">\n <div class=\"__header-left\"></div>\n <div class=\"__header-right\">\n <ax-button\n class=\"ax-sm\"\n [text]=\"\n '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-section.title'\n | translate\n | async\n \"\n [color]=\"'primary'\"\n (onClick)=\"addSection()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-layer-group\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </div>\n }\n\n <axp-standard-section-items-builder\n class=\"block min-h-[200px]\"\n [value]=\"builderValue()\"\n [config]=\"standardConfig()\"\n [readonly]=\"readonly()\"\n (valueChange)=\"onBuilderValueChange($event)\"\n />\n</div>\n", styles: [".axm-safety-plan-section-builder .__header{margin-bottom:calc(var(--spacing, .25rem) * 3);display:flex;align-items:center;justify-content:space-between}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1103
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], safetyPlanId: [{ type: i0.Input, args: [{ isSignal: true, alias: "safetyPlanId", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], standardRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPStandardSectionItemsBuilderComponent), { isSignal: true }] }] } });
1104
+
1105
+ //#region ---- Imports ----
1106
+ //#endregion
1107
+ //#region ---- Constants ----
1108
+ const SAFETY_PLAN_SECTIONS_ADD_SECTION_COMMAND = 'SafetyPlan:Sections:AddSection';
1109
+ const SAFETY_PLAN_SECTIONS_DISCARD_COMMAND = 'SafetyPlan:Sections:Discard';
1110
+ const SAFETY_PLAN_SECTIONS_CHANGE_PLAN_TYPE_COMMAND = 'SafetyPlan:Sections:ChangePlanType';
1111
+ const SAFETY_PLAN_SECTION_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.safetyPlanSection.name}`;
1112
+ const SAFETY_PLAN_WORK_STEP_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.safetyPlanWorkStep.name}`;
1113
+ const SAFETY_PLAN_TYPE_SECTION_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.safetyPlanTypeSection.name}`;
1114
+ const SAFETY_PLAN_TYPE_ENTITY = RootConfig.entities.safetyPlanType.source;
1115
+ const SECTION_TYPE_ENTITY = `${RootConfig.module.name}.${RootConfig.entities.sectionType.name}`;
1116
+ //#endregion
1117
+ //#region ---- Component ----
1118
+ class AXMSafetyPlanSectionsPageComponent extends AXPPageLayoutBaseComponent {
1119
+ constructor() {
1120
+ super(...arguments);
1121
+ this.rootContext = input({}, /* @ts-ignore */
1122
+ ...(ngDevMode ? [{ debugName: "rootContext" }] : /* istanbul ignore next */ []));
1123
+ this.pageConfig = input(/* @ts-ignore */
1124
+ ...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
1125
+ this.options = input({}, /* @ts-ignore */
1126
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1127
+ this.builderRef = viewChild('builderRef', /* @ts-ignore */
1128
+ ...(ngDevMode ? [{ debugName: "builderRef" }] : /* istanbul ignore next */ []));
1129
+ this.commandService = inject(AXPCommandService);
1130
+ this.entityService = inject(AXPEntityService);
1131
+ this.dialogService = inject(AXDialogService);
1132
+ this.layoutBuilder = inject(AXPLayoutBuilderService);
1133
+ this.hooks = inject(AXPHookService);
1134
+ this.hostPageLayout = inject(AXPPageLayoutBase, { optional: true, skipSelf: true });
1135
+ this.baselineValue = signal(null, /* @ts-ignore */
1136
+ ...(ngDevMode ? [{ debugName: "baselineValue" }] : /* istanbul ignore next */ []));
1137
+ this.committedValue = signal(null, /* @ts-ignore */
1138
+ ...(ngDevMode ? [{ debugName: "committedValue" }] : /* istanbul ignore next */ []));
1139
+ this.isLoadingStructure = signal(false, /* @ts-ignore */
1140
+ ...(ngDevMode ? [{ debugName: "isLoadingStructure" }] : /* istanbul ignore next */ []));
1141
+ /** Prevents re-entrant sync loops when applyStructure mutates shared form context. */
1142
+ this.lastSyncedKey = signal(null, /* @ts-ignore */
1143
+ ...(ngDevMode ? [{ debugName: "lastSyncedKey" }] : /* istanbul ignore next */ []));
1144
+ /** In-flight sync key — blocks concurrent loads for the same pair without blocking retries after cancel. */
1145
+ this.inflightSyncKey = signal(null, /* @ts-ignore */
1146
+ ...(ngDevMode ? [{ debugName: "inflightSyncKey" }] : /* istanbul ignore next */ []));
1147
+ this.appliedPlanTypeId = signal(null, /* @ts-ignore */
1148
+ ...(ngDevMode ? [{ debugName: "appliedPlanTypeId" }] : /* istanbul ignore next */ []));
1149
+ /** Keeps plan type in sync after Change Plan Type when parent form context is mutated in place. */
1150
+ this.planTypeOverride = signal(null, /* @ts-ignore */
1151
+ ...(ngDevMode ? [{ debugName: "planTypeOverride" }] : /* istanbul ignore next */ []));
1152
+ this.safetyPlanId = computed(() => {
1153
+ const raw = this.rootContext()?.['id'];
1154
+ return typeof raw === 'string' && raw.length > 0 ? raw : null;
1155
+ }, /* @ts-ignore */
1156
+ ...(ngDevMode ? [{ debugName: "safetyPlanId" }] : /* istanbul ignore next */ []));
1157
+ this.planTypeId = computed(() => {
1158
+ const override = this.planTypeOverride();
1159
+ if (override) {
1160
+ return override;
1161
+ }
1162
+ const ctx = this.rootContext() ?? {};
1163
+ return (resolveEntityRefId(ctx['safetyPlanTypeId']) ??
1164
+ resolveEntityRefId(ctx['safetyPlanType']?.id) ??
1165
+ resolveEntityRefId(ctx['safetyPlanType']));
1166
+ }, /* @ts-ignore */
1167
+ ...(ngDevMode ? [{ debugName: "planTypeId" }] : /* istanbul ignore next */ []));
1168
+ this.hasPlanType = computed(() => !!this.planTypeId(), /* @ts-ignore */
1169
+ ...(ngDevMode ? [{ debugName: "hasPlanType" }] : /* istanbul ignore next */ []));
1170
+ this.isDraftPlan = computed(() => {
1171
+ const status = this.rootContext()?.['status'];
1172
+ return !status || status === AXPSystemStatusType.Draft;
1173
+ }, /* @ts-ignore */
1174
+ ...(ngDevMode ? [{ debugName: "isDraftPlan" }] : /* istanbul ignore next */ []));
1175
+ this.builderOptions = computed(() => ({
1176
+ hideHeaderActions: true,
1177
+ readonly: !this.isDraftPlan() || this.isLoadingStructure(),
1178
+ allowAddSection: this.isDraftPlan() && !this.isLoadingStructure(),
1179
+ allowAddItems: this.isDraftPlan() && !this.isLoadingStructure(),
1180
+ allowReorderSections: this.isDraftPlan() && !this.isLoadingStructure(),
1181
+ ...(this.pageConfig() ?? {}),
1182
+ ...(this.options() ?? {}),
1183
+ allowEditSection: false,
1184
+ allowEditItem: false,
1185
+ }), /* @ts-ignore */
1186
+ ...(ngDevMode ? [{ debugName: "builderOptions" }] : /* istanbul ignore next */ []));
1187
+ this.isDirty = computed(() => {
1188
+ return !isEqual(this.baselineValue() ?? { sections: [] }, this.committedValue() ?? { sections: [] });
1189
+ }, /* @ts-ignore */
1190
+ ...(ngDevMode ? [{ debugName: "isDirty" }] : /* istanbul ignore next */ []));
1191
+ this.#syncFromContext = effect((onCleanup) => {
1192
+ const planId = this.safetyPlanId();
1193
+ const typeId = this.planTypeId();
1194
+ const syncKey = `${planId ?? ''}|${typeId ?? ''}`;
1195
+ // Already applied or already loading for this plan/type pair.
1196
+ if (untracked(() => this.lastSyncedKey()) === syncKey ||
1197
+ untracked(() => this.inflightSyncKey()) === syncKey) {
1198
+ return;
1199
+ }
1200
+ this.inflightSyncKey.set(syncKey);
1201
+ let cancelled = false;
1202
+ const clearInflight = () => {
1203
+ if (untracked(() => this.inflightSyncKey()) === syncKey) {
1204
+ this.inflightSyncKey.set(null);
1205
+ }
1206
+ };
1207
+ const markSynced = () => {
1208
+ this.lastSyncedKey.set(syncKey);
1209
+ clearInflight();
1210
+ };
1211
+ const run = async () => {
1212
+ if (cancelled) {
1213
+ return;
1214
+ }
1215
+ const repeatableSectionTypeIds = await this.loadRepeatableSectionTypeIds();
1216
+ if (cancelled) {
1217
+ return;
1218
+ }
1219
+ // Edit: load independent saved sections once — never append type defaults on top.
1220
+ if (planId) {
1221
+ this.isLoadingStructure.set(true);
1222
+ try {
1223
+ const loaded = await this.fetchSectionsFromBackend(planId, repeatableSectionTypeIds);
1224
+ if (cancelled)
1225
+ return;
1226
+ if (loaded.sections.length > 0) {
1227
+ // Replace UI from backend (deduped). Never append defaults on top of saved rows.
1228
+ this.applyStructure(loaded, { asBaseline: true });
1229
+ this.appliedPlanTypeId.set(typeId);
1230
+ markSynced();
1231
+ return;
1232
+ }
1233
+ // No saved rows yet — fall through to type defaults when type is known.
1234
+ }
1235
+ catch {
1236
+ if (cancelled)
1237
+ return;
1238
+ clearInflight();
1239
+ await this.showStructureLoadError();
1240
+ return;
1241
+ }
1242
+ finally {
1243
+ if (!cancelled) {
1244
+ this.isLoadingStructure.set(false);
1245
+ }
1246
+ }
1247
+ if (cancelled) {
1248
+ return;
1249
+ }
1250
+ }
1251
+ if (!typeId) {
1252
+ this.appliedPlanTypeId.set(null);
1253
+ this.applyStructure({ sections: [] }, { asBaseline: true });
1254
+ markSynced();
1255
+ return;
1256
+ }
1257
+ const previousTypeId = untracked(() => this.appliedPlanTypeId());
1258
+ const currentSections = untracked(() => this.committedValue()?.sections ?? []);
1259
+ if (previousTypeId && previousTypeId !== typeId && currentSections.length > 0) {
1260
+ const confirmed = await this.confirmReplaceStructure();
1261
+ if (cancelled)
1262
+ return;
1263
+ if (!confirmed) {
1264
+ // Keep previous structure; mark new type as handled so we do not loop.
1265
+ this.appliedPlanTypeId.set(typeId);
1266
+ markSynced();
1267
+ return;
1268
+ }
1269
+ }
1270
+ this.isLoadingStructure.set(true);
1271
+ try {
1272
+ const defaults = await this.loadDefaultsForPlanType(typeId, repeatableSectionTypeIds);
1273
+ if (cancelled)
1274
+ return;
1275
+ // Replace (never append) the current collection with type defaults.
1276
+ this.applyStructure(defaults, { asBaseline: true });
1277
+ this.appliedPlanTypeId.set(typeId);
1278
+ markSynced();
1279
+ }
1280
+ catch {
1281
+ if (cancelled)
1282
+ return;
1283
+ clearInflight();
1284
+ await this.showStructureLoadError();
1285
+ }
1286
+ finally {
1287
+ if (!cancelled) {
1288
+ this.isLoadingStructure.set(false);
1289
+ }
1290
+ }
1291
+ };
1292
+ void run();
1293
+ onCleanup(() => {
1294
+ cancelled = true;
1295
+ clearInflight();
1296
+ });
1297
+ }, /* @ts-ignore */
1298
+ ...(ngDevMode ? [{ debugName: "#syncFromContext" }] : /* istanbul ignore next */ []));
1299
+ }
1300
+ #syncFromContext;
1301
+ onValueChange(value) {
1302
+ const withOrder = normalizeSafetyPlanSectionBuilderValue({
1303
+ sections: value.sections.map((section, index) => ({
1304
+ ...section,
1305
+ sortOrder: index + 1,
1306
+ })),
1307
+ });
1308
+ this.committedValue.set(withOrder);
1309
+ this.updateRootContextField('sectionStructure', withOrder);
1310
+ this.updateRootContextField('sections', withOrder);
1311
+ this.recompute();
1312
+ }
1313
+ getWizardStepContext() {
1314
+ const value = this.committedValue();
1315
+ if (!value) {
1316
+ return null;
1317
+ }
1318
+ const cloned = cloneDeep(value);
1319
+ return { sectionStructure: cloned, sections: cloned };
1320
+ }
1321
+ hasUnsavedChanges() {
1322
+ return this.isDirty();
1323
+ }
1324
+ async getPrimaryMenuItems() {
1325
+ if (!this.isDraftPlan() || this.isLoadingStructure()) {
1326
+ return [];
1327
+ }
1328
+ const actions = [
1329
+ {
1330
+ title: '@workplacesafety-management:safety-plan-sections.entities.safety-plan-section.actions.add-section.title',
1331
+ icon: 'fa-light fa-layer-group',
1332
+ zone: 'header',
1333
+ priority: 'primary',
1334
+ color: 'primary',
1335
+ command: { name: SAFETY_PLAN_SECTIONS_ADD_SECTION_COMMAND, options: {} },
1336
+ },
1337
+ {
1338
+ title: '@workplacesafety-management:safety-plans.actions.change-plan-type.title',
1339
+ icon: 'fa-light fa-arrow-right-arrow-left',
1340
+ zone: 'header',
1341
+ priority: 'secondary',
1342
+ color: 'default',
1343
+ visible: this.isDraftPlan() && (!!this.planTypeId() || !!this.safetyPlanId()),
1344
+ command: { name: SAFETY_PLAN_SECTIONS_CHANGE_PLAN_TYPE_COMMAND, options: {} },
1345
+ },
1346
+ ];
1347
+ if (this.safetyPlanId()) {
1348
+ actions.push({
1349
+ title: '@general:actions.discard.title',
1350
+ icon: 'fa-light fa-rotate-left',
1351
+ zone: 'footer',
1352
+ priority: 'secondary',
1353
+ color: 'default',
1354
+ visible: this.isDirty(),
1355
+ command: { name: SAFETY_PLAN_SECTIONS_DISCARD_COMMAND, options: {} },
1356
+ }, {
1357
+ title: '@general:actions.save.title',
1358
+ icon: 'fa-light fa-floppy-disk',
1359
+ zone: 'footer',
1360
+ priority: 'primary',
1361
+ color: 'primary',
1362
+ visible: this.isDirty(),
1363
+ command: { name: SAFETY_PLAN_SECTIONS_SAVE_COMMAND, options: {} },
1364
+ });
1365
+ }
1366
+ return actions;
1367
+ }
1368
+ async execute(command) {
1369
+ if (command.name === SAFETY_PLAN_SECTIONS_ADD_SECTION_COMMAND) {
1370
+ await this.builderRef()?.addSection();
1371
+ return;
1372
+ }
1373
+ if (command.name === SAFETY_PLAN_SECTIONS_CHANGE_PLAN_TYPE_COMMAND) {
1374
+ await this.handleChangePlanType();
1375
+ return;
1376
+ }
1377
+ if (command.name === SAFETY_PLAN_SECTIONS_DISCARD_COMMAND) {
1378
+ this.handleDiscard();
1379
+ return;
1380
+ }
1381
+ if (command.name !== SAFETY_PLAN_SECTIONS_SAVE_COMMAND)
1382
+ return;
1383
+ await this.handleSave({ silent: false });
1384
+ }
1385
+ async handleChangePlanType() {
1386
+ if (!this.isDraftPlan()) {
1387
+ this.toastService.show({
1388
+ color: 'warning',
1389
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1390
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.draft-only'),
1391
+ });
1392
+ return;
1393
+ }
1394
+ const selection = await this.promptChangePlanTypeDialog();
1395
+ if (!selection) {
1396
+ return;
1397
+ }
1398
+ const currentTypeId = this.planTypeId();
1399
+ if (currentTypeId && currentTypeId === selection.safetyPlanTypeId) {
1400
+ this.toastService.show({
1401
+ color: 'warning',
1402
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1403
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.same-plan-type'),
1404
+ });
1405
+ return;
1406
+ }
1407
+ const confirmed = await this.confirmChangePlanType();
1408
+ if (!confirmed) {
1409
+ return;
1410
+ }
1411
+ const planId = this.safetyPlanId();
1412
+ this.isLoadingStructure.set(true);
1413
+ try {
1414
+ if (planId) {
1415
+ await this.applyPersistedPlanTypeChange(planId, selection);
1416
+ }
1417
+ else {
1418
+ await this.applyWizardPlanTypeChange(selection);
1419
+ }
1420
+ }
1421
+ catch {
1422
+ this.toastService.show({
1423
+ color: 'danger',
1424
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1425
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.change-plan-type-failed'),
1426
+ });
1427
+ }
1428
+ finally {
1429
+ this.isLoadingStructure.set(false);
1430
+ }
1431
+ }
1432
+ async promptChangePlanTypeDialog() {
1433
+ const ctx = this.rootContext() ?? {};
1434
+ const currentType = ctx['safetyPlanType'] ?? {};
1435
+ const currentTypeId = this.planTypeId() ?? '';
1436
+ const result = await this.layoutBuilder
1437
+ .create()
1438
+ .dialog((dialog) => {
1439
+ dialog
1440
+ .setTitle('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.title')
1441
+ .setSize('md')
1442
+ .setCloseButton(true)
1443
+ .setContext({
1444
+ safetyPlanTypeId: currentTypeId,
1445
+ safetyPlanType: currentTypeId
1446
+ ? {
1447
+ id: currentTypeId,
1448
+ title: typeof currentType.title === 'string' ? currentType.title : undefined,
1449
+ code: typeof currentType.code === 'string' ? currentType.code : undefined,
1450
+ }
1451
+ : undefined,
1452
+ })
1453
+ .content((flex) => {
1454
+ flex
1455
+ .setDirection('column')
1456
+ .setGap('12px')
1457
+ .formField('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.fields.plan-type.title', (field) => {
1458
+ field.path('safetyPlanTypeId');
1459
+ field.lookupBox({
1460
+ entity: SAFETY_PLAN_TYPE_ENTITY,
1461
+ look: 'select',
1462
+ searchable: true,
1463
+ textField: 'title',
1464
+ valueField: 'id',
1465
+ columns: ['code', 'title'],
1466
+ expose: [
1467
+ { source: 'id', target: 'safetyPlanType.id' },
1468
+ { source: 'title', target: 'safetyPlanType.title' },
1469
+ { source: 'code', target: 'safetyPlanType.code' },
1470
+ ],
1471
+ validations: [{ rule: 'required' }],
1472
+ });
1473
+ });
1474
+ })
1475
+ .setActions((actions) => {
1476
+ actions
1477
+ .cancel('@general:actions.cancel.title')
1478
+ .submit('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.actions.continue');
1479
+ })
1480
+ .onAction(async (dialogRef) => {
1481
+ if (dialogRef.action() === 'cancel') {
1482
+ return null;
1483
+ }
1484
+ const context = dialogRef.context();
1485
+ const safetyPlanTypeId = resolveEntityRefId(context.safetyPlanTypeId);
1486
+ if (!safetyPlanTypeId) {
1487
+ throw new Error((await this.translateService.translateAsync('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.fields.plan-type.placeholder')) || 'Select a Plan Type');
1488
+ }
1489
+ return {
1490
+ safetyPlanTypeId,
1491
+ safetyPlanType: context.safetyPlanType?.id
1492
+ ? context.safetyPlanType
1493
+ : { id: safetyPlanTypeId },
1494
+ };
1495
+ });
1496
+ })
1497
+ .show();
1498
+ return resolveLayoutBuilderDialogResult(result);
1499
+ }
1500
+ async confirmChangePlanType() {
1501
+ const title = (await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.confirm-change-plan-type.title')) || 'Change Plan Type?';
1502
+ const message = (await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.confirm-change-plan-type.message')) ||
1503
+ 'Changing the Plan Type will discard all existing sections and their selected values. The default sections for the new Plan Type will be loaded. Do you want to continue?';
1504
+ const dialogResult = await this.dialogService.confirm(title, message, 'danger', 'horizontal');
1505
+ return dialogResult.result === true;
1506
+ }
1507
+ async applyPersistedPlanTypeChange(planId, selection) {
1508
+ const payload = await this.hooks.runAsync(AXPRuntimeRemoteHookKeys.CommandExecute, {
1509
+ name: AXMWorkplaceSafetyManagementCommandKeys.SafetyPlan.ChangeType,
1510
+ input: {
1511
+ id: planId,
1512
+ safetyPlanTypeId: selection.safetyPlanTypeId,
1513
+ safetyPlanType: selection.safetyPlanType,
1514
+ },
1515
+ });
1516
+ if (!payload.handled || !payload.result?.success) {
1517
+ const text = payload.result?.message?.text;
1518
+ this.toastService.show({
1519
+ color: 'danger',
1520
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1521
+ content: typeof text === 'string' && text.startsWith('@')
1522
+ ? await this.translateService.translateAsync(text)
1523
+ : await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.change-plan-type-failed'),
1524
+ });
1525
+ return;
1526
+ }
1527
+ const data = payload.result.data;
1528
+ const nextTypeId = data?.safetyPlanTypeId ?? selection.safetyPlanTypeId;
1529
+ const nextType = data?.safetyPlanType ?? selection.safetyPlanType ?? { id: nextTypeId };
1530
+ const repeatableSectionTypeIds = await this.loadRepeatableSectionTypeIds();
1531
+ const saved = dedupeSafetyPlanSectionStructure(safetyPlanSectionsToBuilderValue((data?.sections ?? [])), { repeatableSectionTypeIds });
1532
+ this.planTypeOverride.set(nextTypeId);
1533
+ this.updateRootContextField('safetyPlanTypeId', nextTypeId);
1534
+ this.updateRootContextField('safetyPlanType', nextType);
1535
+ this.appliedPlanTypeId.set(nextTypeId);
1536
+ this.lastSyncedKey.set(`${planId}|${nextTypeId}`);
1537
+ this.inflightSyncKey.set(null);
1538
+ this.applyStructure(saved, { asBaseline: true });
1539
+ this.builderRef()?.syncAfterSave(saved);
1540
+ this.recompute();
1541
+ this.toastService.show({
1542
+ color: 'success',
1543
+ title: await this.translateService.translateAsync('@general:messages.generic.success.title'),
1544
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.success'),
1545
+ });
1546
+ }
1547
+ async applyWizardPlanTypeChange(selection) {
1548
+ const repeatableSectionTypeIds = await this.loadRepeatableSectionTypeIds();
1549
+ const defaults = await this.loadDefaultsForPlanType(selection.safetyPlanTypeId, repeatableSectionTypeIds);
1550
+ this.planTypeOverride.set(selection.safetyPlanTypeId);
1551
+ this.updateRootContextField('safetyPlanTypeId', selection.safetyPlanTypeId);
1552
+ this.updateRootContextField('safetyPlanType', selection.safetyPlanType ?? { id: selection.safetyPlanTypeId });
1553
+ this.appliedPlanTypeId.set(selection.safetyPlanTypeId);
1554
+ this.lastSyncedKey.set(`|${selection.safetyPlanTypeId}`);
1555
+ this.inflightSyncKey.set(null);
1556
+ this.applyStructure(defaults, { asBaseline: true });
1557
+ this.builderRef()?.syncAfterSave(defaults);
1558
+ this.recompute();
1559
+ this.toastService.show({
1560
+ color: 'success',
1561
+ title: await this.translateService.translateAsync('@general:messages.generic.success.title'),
1562
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.actions.change-plan-type.dialog.success'),
1563
+ });
1564
+ }
1565
+ async handleSave(options) {
1566
+ const silent = options?.silent === true;
1567
+ const id = this.safetyPlanId();
1568
+ const value = this.committedValue() ?? { sections: [] };
1569
+ if (!id) {
1570
+ if (!silent) {
1571
+ this.toastService.show({
1572
+ color: 'danger',
1573
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1574
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.entities.safety-plan.pages.sections.save-plan-first'),
1575
+ });
1576
+ }
1577
+ return;
1578
+ }
1579
+ const result = await this.commandService.execute(SAFETY_PLAN_SECTIONS_SAVE_COMMAND, { id, value });
1580
+ if (result?.success) {
1581
+ // Always reload from backend after save so UI never keeps a stale/appended local list.
1582
+ const repeatableSectionTypeIds = await this.loadRepeatableSectionTypeIds();
1583
+ const saved = await this.fetchSectionsFromBackend(id, repeatableSectionTypeIds);
1584
+ this.applyStructure(saved, { asBaseline: true });
1585
+ this.lastSyncedKey.set(`${id}|${this.planTypeId() ?? ''}`);
1586
+ this.inflightSyncKey.set(null);
1587
+ this.builderRef()?.syncAfterSave(saved);
1588
+ this.recompute();
1589
+ if (!silent) {
1590
+ this.toastService.show({
1591
+ color: 'success',
1592
+ title: await this.translateService.translateAsync('@general:messages.generic.success.title'),
1593
+ content: await this.translateService.translateAsync('@general:messages.generic.success.description'),
1594
+ });
1595
+ }
1596
+ return;
1597
+ }
1598
+ this.toastService.show({
1599
+ color: 'danger',
1600
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1601
+ content: typeof result?.message?.text === 'string' && result.message.text.startsWith('@')
1602
+ ? await this.translateService.translateAsync(result.message.text)
1603
+ : await this.translateService.translateAsync('@general:messages.entity.update-failed'),
1604
+ });
1605
+ }
1606
+ recompute() {
1607
+ super.recompute();
1608
+ const layout = this.hostPageLayout;
1609
+ layout?.recompute?.();
1610
+ }
1611
+ handleDiscard() {
1612
+ const baseline = this.baselineValue() ?? { sections: [] };
1613
+ this.committedValue.set(baseline);
1614
+ this.updateRootContextField('sectionStructure', baseline);
1615
+ this.updateRootContextField('sections', baseline);
1616
+ this.builderRef()?.resetAfterDiscard();
1617
+ this.recompute();
1618
+ }
1619
+ applyStructure(value, options) {
1620
+ const normalized = normalizeSafetyPlanSectionBuilderValue(value);
1621
+ if (options.asBaseline) {
1622
+ this.baselineValue.set(normalized);
1623
+ }
1624
+ this.committedValue.set(normalized);
1625
+ // Keep both form fields in sync with the same replaced value (never merge/append).
1626
+ this.updateRootContextField('sectionStructure', normalized);
1627
+ this.updateRootContextField('sections', normalized);
1628
+ // Defer host recompute so it cannot re-enter the sync effect synchronously.
1629
+ queueMicrotask(() => {
1630
+ untracked(() => this.recompute());
1631
+ });
1632
+ }
1633
+ /** Query plan sections from storage and return a deduped builder value (replace, never append). */
1634
+ async fetchSectionsFromBackend(planId, repeatableSectionTypeIds = []) {
1635
+ const result = await this.entityService.withEntity(SAFETY_PLAN_SECTION_ENTITY).data().query({
1636
+ skip: 0,
1637
+ take: 1000,
1638
+ filter: {
1639
+ logic: 'and',
1640
+ filters: [{ field: 'safetyPlanId', operator: { type: 'equal' }, value: planId }],
1641
+ },
1642
+ });
1643
+ const rawLoaded = safetyPlanSectionsToBuilderValue((result.items ?? []));
1644
+ const deduped = dedupeSafetyPlanSectionStructure(rawLoaded, { repeatableSectionTypeIds });
1645
+ const workStepsResult = await this.entityService.withEntity(SAFETY_PLAN_WORK_STEP_ENTITY).data().query({
1646
+ skip: 0,
1647
+ take: 2000,
1648
+ filter: {
1649
+ logic: 'and',
1650
+ filters: [{ field: 'safetyPlanId', operator: { type: 'equal' }, value: planId }],
1651
+ },
1652
+ });
1653
+ const columnsByWorkStepTypeId = await this.loadWorkStepTypeColumns(deduped.sections
1654
+ .map((section) => section.workStepTypeId)
1655
+ .filter((id) => typeof id === 'string' && id.length > 0));
1656
+ return hydrateStepTableSectionsWithWorkSteps(deduped, (workStepsResult.items ?? []), columnsByWorkStepTypeId);
1657
+ }
1658
+ async loadWorkStepTypeColumns(workStepTypeIds) {
1659
+ const uniqueIds = [...new Set(workStepTypeIds)];
1660
+ const columnsById = new Map();
1661
+ if (!uniqueIds.length) {
1662
+ return columnsById;
1663
+ }
1664
+ const entityKey = `${RootConfig.module.name}.${RootConfig.entities.workStepType.name}`;
1665
+ const accessor = this.entityService.withEntity(entityKey);
1666
+ await Promise.all(uniqueIds.map(async (id) => {
1667
+ try {
1668
+ const row = await accessor.data().byKey(id);
1669
+ if (!row) {
1670
+ return;
1671
+ }
1672
+ const record = row;
1673
+ columnsById.set(id, normalizeStepTableColumns(record.columns));
1674
+ }
1675
+ catch {
1676
+ // Leave missing types without columns; hydrate falls back to row snapshots.
1677
+ }
1678
+ }));
1679
+ return columnsById;
1680
+ }
1681
+ async loadRepeatableSectionTypeIds() {
1682
+ const builtIn = collectBuiltInRepeatableSectionTypeIds();
1683
+ try {
1684
+ const result = await this.entityService.withEntity(SECTION_TYPE_ENTITY).data().query({
1685
+ skip: 0,
1686
+ take: 1000,
1687
+ });
1688
+ const fromQuery = (result.items ?? [])
1689
+ .filter((row) => row.isRepeatable === true)
1690
+ .map((row) => resolveEntityRefId(row.id))
1691
+ .filter((id) => !!id);
1692
+ return [...new Set([...fromQuery, ...builtIn])];
1693
+ }
1694
+ catch {
1695
+ return builtIn;
1696
+ }
1697
+ }
1698
+ async loadDefaultsForPlanType(typeId, repeatableSectionTypeIds = []) {
1699
+ const result = await this.entityService.withEntity(SAFETY_PLAN_TYPE_SECTION_ENTITY).data().query({
1700
+ skip: 0,
1701
+ take: 1000,
1702
+ sort: [{ field: 'sortOrder', dir: 'asc' }],
1703
+ filter: {
1704
+ logic: 'and',
1705
+ filters: [
1706
+ { field: 'safetyPlanTypeId', operator: { type: 'equal' }, value: typeId },
1707
+ { field: 'isArchived', operator: { type: 'equal' }, value: false },
1708
+ ],
1709
+ },
1710
+ });
1711
+ const typeSections = (result.items ?? []).filter((row) => resolveEntityRefId(row.safetyPlanTypeId) === typeId);
1712
+ const structure = dedupeSafetyPlanSectionStructure(buildSectionStructureFromTypeSections(typeSections, {
1713
+ createId: () => AXPDataGenerator.uuid(),
1714
+ }), { repeatableSectionTypeIds });
1715
+ return toBuilderValueFromStructure(await this.enrichSectionTitlesFromSectionTypes(structure));
1716
+ }
1717
+ /**
1718
+ * Fills missing section titles from SectionType records when type-section snapshots omit them.
1719
+ */
1720
+ async enrichSectionTitlesFromSectionTypes(structure) {
1721
+ const missingIds = [
1722
+ ...new Set(structure.sections
1723
+ .filter((section) => !section.sectionType?.title || section.title === section.sectionTypeId)
1724
+ .map((section) => section.sectionTypeId)
1725
+ .filter((id) => !!id)),
1726
+ ];
1727
+ if (!missingIds.length) {
1728
+ return structure;
1729
+ }
1730
+ const sectionTypeEntity = `${RootConfig.module.name}.${RootConfig.entities.sectionType.name}`;
1731
+ const result = await this.entityService.withEntity(sectionTypeEntity).data().query({
1732
+ skip: 0,
1733
+ take: Math.max(missingIds.length, 1),
1734
+ filter: {
1735
+ logic: 'or',
1736
+ filters: missingIds.map((id) => ({
1737
+ field: 'id',
1738
+ operator: { type: 'equal' },
1739
+ value: id,
1740
+ })),
1741
+ },
1742
+ });
1743
+ const byId = new Map();
1744
+ for (const row of result.items ?? []) {
1745
+ const record = row;
1746
+ const id = resolveEntityRefId(record['id']);
1747
+ if (!id)
1748
+ continue;
1749
+ const title = record['title'];
1750
+ const code = record['code'];
1751
+ byId.set(id, {
1752
+ title: typeof title === 'string' ? title : undefined,
1753
+ code: typeof code === 'string' ? code : undefined,
1754
+ });
1755
+ }
1756
+ return {
1757
+ sections: structure.sections.map((section) => {
1758
+ const meta = byId.get(section.sectionTypeId);
1759
+ if (!meta?.title && !meta?.code) {
1760
+ return section;
1761
+ }
1762
+ const title = (meta.title ?? meta.code ?? section.title).trim();
1763
+ return {
1764
+ ...section,
1765
+ title,
1766
+ sectionType: {
1767
+ id: section.sectionTypeId,
1768
+ title: meta.title ?? section.sectionType?.title,
1769
+ code: meta.code ?? section.sectionType?.code,
1770
+ },
1771
+ };
1772
+ }),
1773
+ };
1774
+ }
1775
+ async confirmReplaceStructure() {
1776
+ const title = (await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.confirm-change-plan-type.title')) || 'Change Plan Type?';
1777
+ const message = (await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.confirm-change-plan-type.message')) ||
1778
+ 'Changing the Plan Type will discard all existing sections and their selected values. The default sections for the new Plan Type will be loaded. Do you want to continue?';
1779
+ const dialogResult = await this.dialogService.confirm(title, message, 'danger', 'horizontal');
1780
+ return dialogResult.result === true;
1781
+ }
1782
+ async showStructureLoadError() {
1783
+ this.toastService.show({
1784
+ color: 'danger',
1785
+ title: await this.translateService.translateAsync('@general:messages.generic.error.title'),
1786
+ content: await this.translateService.translateAsync('@workplacesafety-management:safety-plans.messages.section-structure.load-failed'),
1787
+ });
1788
+ }
1789
+ updateRootContextField(field, value) {
1790
+ const ctx = this.rootContext();
1791
+ if (!ctx || typeof ctx !== 'object') {
1792
+ return;
1793
+ }
1794
+ ctx[field] = cloneDeep(value);
1795
+ }
1796
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionsPageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1797
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMSafetyPlanSectionsPageComponent, isStandalone: true, selector: "axm-safety-plan-sections-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block w-full min-h-0" }, providers: [
1798
+ {
1799
+ provide: AXPPageLayoutBase,
1800
+ useExisting: AXMSafetyPlanSectionsPageComponent,
1801
+ },
1802
+ ], viewQueries: [{ propertyName: "builderRef", first: true, predicate: ["builderRef"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1803
+ @if (isLoadingStructure()) {
1804
+ <p class="text-muted p-4">
1805
+ {{
1806
+ '@workplacesafety-management:safety-plans.entities.safety-plan.pages.sections.loading'
1807
+ | translate
1808
+ | async
1809
+ }}
1810
+ </p>
1811
+ } @else if (safetyPlanId() || hasPlanType()) {
1812
+ <axm-safety-plan-section-builder
1813
+ #builderRef
1814
+ [value]="committedValue()"
1815
+ [safetyPlanId]="safetyPlanId() ?? undefined"
1816
+ [options]="builderOptions()"
1817
+ (valueChange)="onValueChange($event)"
1818
+ />
1819
+ } @else {
1820
+ <p class="text-muted p-4">
1821
+ {{
1822
+ '@workplacesafety-management:safety-plans.entities.safety-plan.pages.sections.select-plan-type-first'
1823
+ | translate
1824
+ | async
1825
+ }}
1826
+ </p>
1827
+ }
1828
+ `, isInline: true, dependencies: [{ kind: "component", type: AXMSafetyPlanSectionBuilderComponent, selector: "axm-safety-plan-section-builder", inputs: ["value", "safetyPlanId", "options"], outputs: ["valueChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i3$1.AXTranslatorPipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1829
+ }
1830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionsPageComponent, decorators: [{
1831
+ type: Component,
1832
+ args: [{
1833
+ selector: 'axm-safety-plan-sections-page',
1834
+ standalone: true,
1835
+ imports: [AXMSafetyPlanSectionBuilderComponent, AXTranslationModule, AsyncPipe],
1836
+ template: `
1837
+ @if (isLoadingStructure()) {
1838
+ <p class="text-muted p-4">
1839
+ {{
1840
+ '@workplacesafety-management:safety-plans.entities.safety-plan.pages.sections.loading'
1841
+ | translate
1842
+ | async
1843
+ }}
1844
+ </p>
1845
+ } @else if (safetyPlanId() || hasPlanType()) {
1846
+ <axm-safety-plan-section-builder
1847
+ #builderRef
1848
+ [value]="committedValue()"
1849
+ [safetyPlanId]="safetyPlanId() ?? undefined"
1850
+ [options]="builderOptions()"
1851
+ (valueChange)="onValueChange($event)"
1852
+ />
1853
+ } @else {
1854
+ <p class="text-muted p-4">
1855
+ {{
1856
+ '@workplacesafety-management:safety-plans.entities.safety-plan.pages.sections.select-plan-type-first'
1857
+ | translate
1858
+ | async
1859
+ }}
1860
+ </p>
1861
+ }
1862
+ `,
1863
+ host: {
1864
+ class: 'block w-full min-h-0',
1865
+ },
1866
+ changeDetection: ChangeDetectionStrategy.OnPush,
1867
+ providers: [
1868
+ {
1869
+ provide: AXPPageLayoutBase,
1870
+ useExisting: AXMSafetyPlanSectionsPageComponent,
1871
+ },
1872
+ ],
1873
+ }]
1874
+ }], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], builderRef: [{ type: i0.ViewChild, args: ['builderRef', { isSignal: true }] }] } });
1875
+
1876
+ var safetyPlanSectionsPage_component = /*#__PURE__*/Object.freeze({
1877
+ __proto__: null,
1878
+ AXMSafetyPlanSectionsPageComponent: AXMSafetyPlanSectionsPageComponent
1879
+ });
1880
+
1881
+ //#endregion
1882
+ //#region ---- Safety plan sections page provider ----
1883
+ class AXMSafetyPlanSectionsPageComponentProvider {
1884
+ async components() {
1885
+ return [
1886
+ {
1887
+ key: SAFETY_PLAN_SECTIONS_PAGE_COMPONENT_KEY,
1888
+ loader: () => Promise.resolve().then(function () { return safetyPlanSectionsPage_component; }).then((m) => m.AXMSafetyPlanSectionsPageComponent),
1889
+ },
1890
+ ];
1891
+ }
1892
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionsPageComponentProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1893
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionsPageComponentProvider }); }
1894
+ }
1895
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSafetyPlanSectionsPageComponentProvider, decorators: [{
1896
+ type: Injectable
1897
+ }] });
1898
+
1899
+ export { AXMSafetyPlanSectionBuilderComponent, AXMSafetyPlanSectionsPageComponent, AXMSafetyPlanSectionsPageComponentProvider, SAFETY_PLAN_SECTIONS_SAVE_COMMAND };
1900
+ //# sourceMappingURL=acorex-platform-module-workplacesafety-management-client-index-kF37zgd4.mjs.map