@masterteam/work-center 0.0.84 → 0.0.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/masterteam-work-center-work-center-process-preview-qG9T9HoP.mjs +538 -0
- package/fesm2022/masterteam-work-center-work-center-process-preview-qG9T9HoP.mjs.map +1 -0
- package/fesm2022/masterteam-work-center.mjs +66 -595
- package/fesm2022/masterteam-work-center.mjs.map +1 -1
- package/package.json +5 -5
- package/types/masterteam-work-center.d.ts +1 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { inject, input, computed, signal, effect, untracked, Component } from '@angular/core';
|
|
5
|
+
import { TranslocoService } from '@jsverse/transloco';
|
|
6
|
+
import { Table } from '@masterteam/components/table';
|
|
7
|
+
import { StructureBuilder } from '@masterteam/structure-builder';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Approval-history row projection for the process preview.
|
|
11
|
+
*
|
|
12
|
+
* The runtime stores one `step` record per *target user*, so a group step
|
|
13
|
+
* fans out into one record per member, and every workflow attempt (return →
|
|
14
|
+
* resubmit) creates a fresh fan-out for the same step schema. The table shows
|
|
15
|
+
* one row per attempt: sibling member records collapse, but separate attempts
|
|
16
|
+
* — and the completed history they carry — are preserved.
|
|
17
|
+
*/
|
|
18
|
+
const PENDING$1 = 'pending';
|
|
19
|
+
const TERMINATED = 'terminated';
|
|
20
|
+
/** Statuses whose actor is not the assigned group/role, so the actor wins. */
|
|
21
|
+
const ACTOR_OVERRIDES_TARGET = new Set([TERMINATED]);
|
|
22
|
+
function buildApprovalRows(steps, options) {
|
|
23
|
+
const { resolveDisplayName } = options;
|
|
24
|
+
const currentIds = new Set((options.currentStepIds ?? []).map((id) => String(id)));
|
|
25
|
+
const rows = [];
|
|
26
|
+
for (const run of splitIntoRuns(steps)) {
|
|
27
|
+
const groupName = resolveGroupName(run[0].target, resolveDisplayName);
|
|
28
|
+
if (groupName) {
|
|
29
|
+
rows.push(buildGroupRow(run, groupName, currentIds, resolveDisplayName));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Non-group targets keep one row per record, minus the pending siblings
|
|
33
|
+
// that were superseded when another member of the same fan-out acted.
|
|
34
|
+
const runHasAction = run.some(hasAction);
|
|
35
|
+
for (const step of run) {
|
|
36
|
+
if (runHasAction &&
|
|
37
|
+
run.length > 1 &&
|
|
38
|
+
isSupersededPending(step, currentIds)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
rows.push({
|
|
42
|
+
stepName: resolveStepName(step, resolveDisplayName),
|
|
43
|
+
status: step.status,
|
|
44
|
+
user: {
|
|
45
|
+
kind: 'user',
|
|
46
|
+
value: step.actionUserInfo ?? step.targetUser ?? null,
|
|
47
|
+
},
|
|
48
|
+
createdAt: step.createdAt ?? '',
|
|
49
|
+
actionDate: step.actionDate ?? '',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
function buildGroupRow(run, groupName, currentIds, resolveDisplayName) {
|
|
56
|
+
const pendingOnGroup = run.find((step) => isCurrent(step, currentIds));
|
|
57
|
+
// While the attempt is open the row belongs to the group as a whole; once it
|
|
58
|
+
// closes, the record that carries the outcome represents it.
|
|
59
|
+
const repr = pendingOnGroup ?? resolveOutcome(run);
|
|
60
|
+
const actor = repr.actionUserInfo;
|
|
61
|
+
const showActor = !pendingOnGroup && !!actor && ACTOR_OVERRIDES_TARGET.has(statusKey(repr));
|
|
62
|
+
return {
|
|
63
|
+
stepName: resolveStepName(repr, resolveDisplayName),
|
|
64
|
+
status: repr.status,
|
|
65
|
+
user: showActor
|
|
66
|
+
? { kind: 'user', value: actor }
|
|
67
|
+
: { kind: 'text', value: groupName },
|
|
68
|
+
createdAt: run[0].createdAt ?? '',
|
|
69
|
+
actionDate: pendingOnGroup ? '' : (repr.actionDate ?? ''),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Consecutive records sharing a step schema + target belong to the same
|
|
74
|
+
* attempt. The API orders steps by creation, so a later attempt on the same
|
|
75
|
+
* step is always separated by the records that sent the request back.
|
|
76
|
+
*/
|
|
77
|
+
function splitIntoRuns(steps) {
|
|
78
|
+
const runs = [];
|
|
79
|
+
let currentKey = null;
|
|
80
|
+
for (const step of steps) {
|
|
81
|
+
const key = runKey(step);
|
|
82
|
+
if (key !== currentKey || runs.length === 0) {
|
|
83
|
+
runs.push([step]);
|
|
84
|
+
currentKey = key;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
runs[runs.length - 1].push(step);
|
|
88
|
+
}
|
|
89
|
+
return runs;
|
|
90
|
+
}
|
|
91
|
+
function runKey(step) {
|
|
92
|
+
return `${step.stepSchemaId ?? ''}|${step.target?.groupKey ?? ''}`;
|
|
93
|
+
}
|
|
94
|
+
/** The record holding the attempt's outcome (Returned/Rejected/Terminated…). */
|
|
95
|
+
function resolveOutcome(run) {
|
|
96
|
+
for (let index = run.length - 1; index >= 0; index -= 1) {
|
|
97
|
+
const step = run[index];
|
|
98
|
+
if (statusKey(step) !== PENDING$1 && statusKey(step) !== '') {
|
|
99
|
+
return step;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return findLast(run, hasAction) ?? run[run.length - 1];
|
|
103
|
+
}
|
|
104
|
+
function isCurrent(step, currentIds) {
|
|
105
|
+
if (typeof step.isCurrent === 'boolean') {
|
|
106
|
+
return step.isCurrent;
|
|
107
|
+
}
|
|
108
|
+
if (currentIds.size > 0) {
|
|
109
|
+
return currentIds.has(String(step.stepId ?? ''));
|
|
110
|
+
}
|
|
111
|
+
return step.isActive !== false && statusKey(step) === PENDING$1;
|
|
112
|
+
}
|
|
113
|
+
/** A pending record left behind after a sibling acted — never actionable. */
|
|
114
|
+
function isSupersededPending(step, currentIds) {
|
|
115
|
+
return (statusKey(step) === PENDING$1 &&
|
|
116
|
+
!hasAction(step) &&
|
|
117
|
+
!isCurrent(step, currentIds));
|
|
118
|
+
}
|
|
119
|
+
function hasAction(step) {
|
|
120
|
+
if (step.actionUserInfo) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
const key = statusKey(step);
|
|
124
|
+
return key !== '' && key !== PENDING$1;
|
|
125
|
+
}
|
|
126
|
+
/** Status comes off the lookup key, never the localized display text. */
|
|
127
|
+
function statusKey(step) {
|
|
128
|
+
const status = step.status;
|
|
129
|
+
if (typeof status === 'string') {
|
|
130
|
+
return status.trim().toLowerCase();
|
|
131
|
+
}
|
|
132
|
+
if (status && typeof status === 'object') {
|
|
133
|
+
const key = status['key'];
|
|
134
|
+
if (typeof key === 'string') {
|
|
135
|
+
return key.trim().toLowerCase();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
function resolveStepName(step, resolveDisplayName) {
|
|
141
|
+
return resolveDisplayName(step.stepName) || String(step.stepId ?? '--');
|
|
142
|
+
}
|
|
143
|
+
function resolveGroupName(target, resolveDisplayName) {
|
|
144
|
+
if (!target || target.type !== 'Group' || !target.group) {
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
return (resolveDisplayName(target.displayName) ||
|
|
148
|
+
resolveDisplayName(target.group.name));
|
|
149
|
+
}
|
|
150
|
+
function findLast(items, predicate) {
|
|
151
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
152
|
+
if (predicate(items[index])) {
|
|
153
|
+
return items[index];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Status projection for the process-preview diagram.
|
|
161
|
+
*
|
|
162
|
+
* The schema view colors every step by where it stands in the run — the icon
|
|
163
|
+
* and subtitle already say what *kind* of step it is, so color is free to
|
|
164
|
+
* carry progress. One color per state, reusing the workflow badge tones from
|
|
165
|
+
* `@masterteam/structure-builder`.
|
|
166
|
+
*
|
|
167
|
+
* The runtime stores one `step` record per target user, so a group step fans
|
|
168
|
+
* out into several records and every attempt (return → resubmit) creates a
|
|
169
|
+
* fresh fan-out. A schema node shows the state of its *latest* attempt.
|
|
170
|
+
*/
|
|
171
|
+
/** Node accent color per state — every outcome gets its own hue. */
|
|
172
|
+
const PROCESS_STEP_STATUS_COLORS = {
|
|
173
|
+
approved: '#059669', // green — approved
|
|
174
|
+
current: '#d97706', // amber — running right now
|
|
175
|
+
noAction: '#0891b2', // cyan — closed without a decision from this target
|
|
176
|
+
notStarted: '#2563eb', // blue — not reached yet
|
|
177
|
+
rejected: '#dc2626', // red — rejected
|
|
178
|
+
resubmitted: '#7c3aed', // violet — sent back, then pushed forward again
|
|
179
|
+
returned: '#6b7280', // gray — sent back
|
|
180
|
+
terminated: '#334155', // slate — the run was killed outright
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* `NodeBadgeTone` per state, so the pill matches the node color. The tones the
|
|
184
|
+
* structure-builder names cover the original five; the rest use `accent`, which
|
|
185
|
+
* has no tone rule of its own and therefore inherits the node color exactly.
|
|
186
|
+
*/
|
|
187
|
+
const PROCESS_STEP_STATUS_BADGE_TONES = {
|
|
188
|
+
approved: 'final',
|
|
189
|
+
current: 'current',
|
|
190
|
+
noAction: 'accent',
|
|
191
|
+
notStarted: 'initial',
|
|
192
|
+
rejected: 'closed',
|
|
193
|
+
resubmitted: 'accent',
|
|
194
|
+
returned: 'neutral',
|
|
195
|
+
terminated: 'accent',
|
|
196
|
+
};
|
|
197
|
+
const PENDING = 'pending';
|
|
198
|
+
const CURRENT_VIEW = { status: 'current', label: '' };
|
|
199
|
+
const NOT_STARTED_VIEW = {
|
|
200
|
+
status: 'notStarted',
|
|
201
|
+
label: '',
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Decided status keys → visual state. A decided status that isn't listed reads
|
|
205
|
+
* as `noAction` — it closed, but not by a decision we can name, so the color
|
|
206
|
+
* must not claim one. The badge still shows the backend's own wording.
|
|
207
|
+
*/
|
|
208
|
+
const DECIDED_STATUS_STATES = {
|
|
209
|
+
approved: 'approved',
|
|
210
|
+
noaction: 'noAction',
|
|
211
|
+
rejected: 'rejected',
|
|
212
|
+
resubmitted: 'resubmitted',
|
|
213
|
+
returned: 'returned',
|
|
214
|
+
terminated: 'terminated',
|
|
215
|
+
};
|
|
216
|
+
const UNCLASSIFIED_DECIDED_STATE = 'noAction';
|
|
217
|
+
/**
|
|
218
|
+
* Inside one attempt the strongest outcome represents the step, so a group
|
|
219
|
+
* fan-out where one member rejected reads as rejected while the siblings who
|
|
220
|
+
* never acted read as `noAction`.
|
|
221
|
+
*/
|
|
222
|
+
const OUTCOME_PRECEDENCE = [
|
|
223
|
+
'terminated',
|
|
224
|
+
'rejected',
|
|
225
|
+
'returned',
|
|
226
|
+
'resubmitted',
|
|
227
|
+
'approved',
|
|
228
|
+
'noAction',
|
|
229
|
+
];
|
|
230
|
+
/**
|
|
231
|
+
* Maps every schema-step id the runtime has touched to its visual state.
|
|
232
|
+
* Steps missing from the result were never reached — render them as
|
|
233
|
+
* {@link NOT_STARTED_VIEW}.
|
|
234
|
+
*/
|
|
235
|
+
function buildSchemaStepStatuses(steps, options) {
|
|
236
|
+
const currentIds = new Set((options.currentSchemaIds ?? []).map((id) => String(id)));
|
|
237
|
+
const views = new Map();
|
|
238
|
+
// Later attempts overwrite earlier ones, so the last run on a step wins.
|
|
239
|
+
for (const run of splitBySchemaStep(steps)) {
|
|
240
|
+
const schemaId = String(run[0].stepSchemaId ?? '');
|
|
241
|
+
if (!schemaId) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
views.set(schemaId, resolveRunStatus(run, currentIds, options));
|
|
245
|
+
}
|
|
246
|
+
// A step can be current before any record closes — the flag always wins.
|
|
247
|
+
for (const id of currentIds) {
|
|
248
|
+
views.set(id, CURRENT_VIEW);
|
|
249
|
+
}
|
|
250
|
+
return views;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Reads a state straight off a schema step's own `status`, for schemas that
|
|
254
|
+
* carry it without a matching runtime record.
|
|
255
|
+
*/
|
|
256
|
+
function resolveStatusView(status, resolveDisplayName) {
|
|
257
|
+
const key = normalizeStatusKey(status);
|
|
258
|
+
if (key === '' || key === PENDING) {
|
|
259
|
+
return NOT_STARTED_VIEW;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
status: DECIDED_STATUS_STATES[key] ?? UNCLASSIFIED_DECIDED_STATE,
|
|
263
|
+
label: resolveStatusLabel(status, resolveDisplayName),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function resolveRunStatus(run, currentIds, options) {
|
|
267
|
+
if (currentIds.has(String(run[0].stepSchemaId ?? '')) ||
|
|
268
|
+
run.some((step) => step.isCurrent === true)) {
|
|
269
|
+
return CURRENT_VIEW;
|
|
270
|
+
}
|
|
271
|
+
const decided = run
|
|
272
|
+
.map((step) => ({ step, state: decidedState(step) }))
|
|
273
|
+
.filter((entry) => entry.state !== null);
|
|
274
|
+
if (decided.length === 0) {
|
|
275
|
+
return NOT_STARTED_VIEW;
|
|
276
|
+
}
|
|
277
|
+
const outcome = OUTCOME_PRECEDENCE.map((state) => decided.find((entry) => entry.state === state)).find((entry) => !!entry) ?? decided[decided.length - 1];
|
|
278
|
+
return {
|
|
279
|
+
status: outcome.state,
|
|
280
|
+
label: resolveStatusLabel(outcome.step.status, options.resolveDisplayName),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function decidedState(step) {
|
|
284
|
+
const key = statusKey(step);
|
|
285
|
+
if (key === '' || key === PENDING) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return DECIDED_STATUS_STATES[key] ?? UNCLASSIFIED_DECIDED_STATE;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Consecutive records sharing a schema step belong to the same attempt. The
|
|
292
|
+
* API orders steps by creation, so a later attempt is always separated by the
|
|
293
|
+
* records that sent the request back.
|
|
294
|
+
*/
|
|
295
|
+
function splitBySchemaStep(steps) {
|
|
296
|
+
const runs = [];
|
|
297
|
+
let currentKey = null;
|
|
298
|
+
for (const step of steps) {
|
|
299
|
+
const key = String(step.stepSchemaId ?? '');
|
|
300
|
+
if (key !== currentKey || runs.length === 0) {
|
|
301
|
+
runs.push([step]);
|
|
302
|
+
currentKey = key;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
runs[runs.length - 1].push(step);
|
|
306
|
+
}
|
|
307
|
+
return runs;
|
|
308
|
+
}
|
|
309
|
+
/** Status comes off the lookup key, never the localized display text. */
|
|
310
|
+
function normalizeStatusKey(status) {
|
|
311
|
+
if (typeof status === 'string') {
|
|
312
|
+
return status.trim().toLowerCase();
|
|
313
|
+
}
|
|
314
|
+
if (status && typeof status === 'object') {
|
|
315
|
+
const key = status['key'];
|
|
316
|
+
if (typeof key === 'string') {
|
|
317
|
+
return key.trim().toLowerCase();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return '';
|
|
321
|
+
}
|
|
322
|
+
function resolveStatusLabel(status, resolveDisplayName) {
|
|
323
|
+
return typeof status === 'string' ? status : resolveDisplayName(status);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
class WorkCenterProcessPreview {
|
|
327
|
+
http = inject(HttpClient);
|
|
328
|
+
transloco = inject(TranslocoService);
|
|
329
|
+
loadSub;
|
|
330
|
+
requestId = input(null, ...(ngDevMode ? [{ debugName: "requestId" }] : /* istanbul ignore next */ []));
|
|
331
|
+
view = input('approvals', ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
332
|
+
approvalColumns = computed(() => [
|
|
333
|
+
{
|
|
334
|
+
key: 'stepName',
|
|
335
|
+
label: this.transloco.translate('workCenter.preview.name'),
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
key: 'status',
|
|
339
|
+
label: this.transloco.translate('workCenter.preview.status'),
|
|
340
|
+
type: 'entity',
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
key: 'user',
|
|
344
|
+
label: this.transloco.translate('workCenter.preview.userOrGroup'),
|
|
345
|
+
type: 'entity',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
key: 'createdAt',
|
|
349
|
+
label: this.transloco.translate('workCenter.preview.initiationDate'),
|
|
350
|
+
type: 'entity',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
key: 'actionDate',
|
|
354
|
+
label: this.transloco.translate('workCenter.preview.actionDate'),
|
|
355
|
+
type: 'entity',
|
|
356
|
+
},
|
|
357
|
+
], ...(ngDevMode ? [{ debugName: "approvalColumns" }] : /* istanbul ignore next */ []));
|
|
358
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
359
|
+
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
360
|
+
preview = signal(null, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
|
|
361
|
+
canRenderPreview = computed(() => (this.requestId() ?? 0) > 0, ...(ngDevMode ? [{ debugName: "canRenderPreview" }] : /* istanbul ignore next */ []));
|
|
362
|
+
approvalRows = computed(() => {
|
|
363
|
+
const preview = this.preview();
|
|
364
|
+
const rows = buildApprovalRows(preview?.steps ?? [], {
|
|
365
|
+
currentStepIds: preview?.currentStepIds,
|
|
366
|
+
resolveDisplayName: (value) => this.resolveDisplayName(value),
|
|
367
|
+
});
|
|
368
|
+
return rows.map((row) => ({
|
|
369
|
+
stepName: row.stepName,
|
|
370
|
+
status: buildEntity('Status', 'Status', row.status),
|
|
371
|
+
user: row.user.kind === 'text'
|
|
372
|
+
? buildEntity('User', 'Text', row.user.value)
|
|
373
|
+
: buildEntity('User', 'User', row.user.value),
|
|
374
|
+
createdAt: buildEntity('Initiation Date', 'DateTime', row.createdAt),
|
|
375
|
+
actionDate: buildEntity('Action Date', 'DateTime', row.actionDate),
|
|
376
|
+
}));
|
|
377
|
+
}, ...(ngDevMode ? [{ debugName: "approvalRows" }] : /* istanbul ignore next */ []));
|
|
378
|
+
hasApprovals = computed(() => this.approvalRows().length > 0, ...(ngDevMode ? [{ debugName: "hasApprovals" }] : /* istanbul ignore next */ []));
|
|
379
|
+
schemaNodes = computed(() => {
|
|
380
|
+
const preview = this.preview();
|
|
381
|
+
const steps = preview?.schema?.stepsSchema ?? [];
|
|
382
|
+
const connections = preview?.schema?.connections ?? [];
|
|
383
|
+
const sourceIds = new Set(connections.map((c) => String(c.source)));
|
|
384
|
+
// Where each step stands in the run — the color of a node is its state,
|
|
385
|
+
// not its type (the icon and subtitle already carry the type).
|
|
386
|
+
const statusViews = buildSchemaStepStatuses(preview?.steps ?? [], {
|
|
387
|
+
currentSchemaIds: preview?.currentStepSchemaIds,
|
|
388
|
+
resolveDisplayName: (value) => this.resolveDisplayName(value),
|
|
389
|
+
});
|
|
390
|
+
return steps.map((step) => {
|
|
391
|
+
const isAppAction = step.type === 'AppAction';
|
|
392
|
+
const isApprovalCommit = step.type === 'ApprovalCommit' || step.systemKind === 'ApprovalCommit';
|
|
393
|
+
// Treat a step as final if explicitly flagged or if it has no outgoing
|
|
394
|
+
// connection — the latter catches schemas where `isFinal` isn't set.
|
|
395
|
+
const isFinal = !isApprovalCommit &&
|
|
396
|
+
(step.isFinal === true || !sourceIds.has(String(step.id)));
|
|
397
|
+
const stepStatus = statusViews.get(String(step.id)) ??
|
|
398
|
+
resolveStatusView(step.status, (value) => this.resolveDisplayName(value));
|
|
399
|
+
const isCurrent = stepStatus.status === 'current';
|
|
400
|
+
return {
|
|
401
|
+
id: String(step.id),
|
|
402
|
+
name: isFinal
|
|
403
|
+
? this.transloco.translate('workCenter.preview.end')
|
|
404
|
+
: this.resolveDisplayName(step.name) || String(step.id),
|
|
405
|
+
color: PROCESS_STEP_STATUS_COLORS[stepStatus.status],
|
|
406
|
+
icon: isApprovalCommit
|
|
407
|
+
? 'general.check-verified-01'
|
|
408
|
+
: isAppAction
|
|
409
|
+
? 'general.zap'
|
|
410
|
+
: isFinal
|
|
411
|
+
? 'map.flag-04'
|
|
412
|
+
: step.isInitial
|
|
413
|
+
? 'map.flag-04'
|
|
414
|
+
: 'file.clipboard-check',
|
|
415
|
+
subtitle: isApprovalCommit
|
|
416
|
+
? this.transloco.translate('workCenter.preview.approvalCommitSubtitle')
|
|
417
|
+
: step.isInitial
|
|
418
|
+
? this.transloco.translate('workCenter.preview.start')
|
|
419
|
+
: isFinal
|
|
420
|
+
? this.transloco.translate('workCenter.preview.end')
|
|
421
|
+
: isAppAction
|
|
422
|
+
? this.transloco.translate('workCenter.preview.appAction')
|
|
423
|
+
: this.transloco.translate('workCenter.preview.formStep'),
|
|
424
|
+
// The badge names the state the color stands for, preferring the
|
|
425
|
+
// backend's own wording. The current step keeps the built-in
|
|
426
|
+
// "Current" pill instead of a second badge.
|
|
427
|
+
badge: isCurrent
|
|
428
|
+
? null
|
|
429
|
+
: stepStatus.label ||
|
|
430
|
+
this.transloco.translate(`workCenter.preview.stepStatus.${stepStatus.status}`),
|
|
431
|
+
// Semantic color for the badge — every step state renders with the
|
|
432
|
+
// same pill and is told apart by color alone.
|
|
433
|
+
badgeTone: PROCESS_STEP_STATUS_BADGE_TONES[stepStatus.status],
|
|
434
|
+
status: step.status ?? null,
|
|
435
|
+
style: isAppAction || isApprovalCommit ? 'icon' : 'detail',
|
|
436
|
+
current: isCurrent,
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
}, ...(ngDevMode ? [{ debugName: "schemaNodes" }] : /* istanbul ignore next */ []));
|
|
440
|
+
schemaConnections = computed(() => (this.preview()?.schema?.connections ?? []).map((connection, index) => ({
|
|
441
|
+
id: String(connection.id ?? `${connection.source}-${connection.target}-${index}`),
|
|
442
|
+
from: String(connection.source),
|
|
443
|
+
to: String(connection.target),
|
|
444
|
+
state: connection.state,
|
|
445
|
+
})), ...(ngDevMode ? [{ debugName: "schemaConnections" }] : /* istanbul ignore next */ []));
|
|
446
|
+
hasSchema = computed(() => this.schemaNodes().length > 0, ...(ngDevMode ? [{ debugName: "hasSchema" }] : /* istanbul ignore next */ []));
|
|
447
|
+
previewNodeFields = {
|
|
448
|
+
id: 'id',
|
|
449
|
+
name: 'name',
|
|
450
|
+
icon: 'icon',
|
|
451
|
+
color: 'color',
|
|
452
|
+
subtitle: 'subtitle',
|
|
453
|
+
badge: 'badge',
|
|
454
|
+
badgeTone: 'badgeTone',
|
|
455
|
+
status: 'status',
|
|
456
|
+
style: 'style',
|
|
457
|
+
current: 'current',
|
|
458
|
+
};
|
|
459
|
+
constructor() {
|
|
460
|
+
effect(() => {
|
|
461
|
+
const requestId = this.requestId() ?? 0;
|
|
462
|
+
if (requestId > 0) {
|
|
463
|
+
untracked(() => this.loadPreview(requestId));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
this.loadSub?.unsubscribe();
|
|
467
|
+
this.loading.set(false);
|
|
468
|
+
this.error.set(null);
|
|
469
|
+
this.preview.set(null);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
ngOnDestroy() {
|
|
473
|
+
this.loadSub?.unsubscribe();
|
|
474
|
+
}
|
|
475
|
+
resolveDisplayName(name) {
|
|
476
|
+
if (typeof name === 'string') {
|
|
477
|
+
return name;
|
|
478
|
+
}
|
|
479
|
+
if (typeof name === 'number' || typeof name === 'boolean') {
|
|
480
|
+
return String(name);
|
|
481
|
+
}
|
|
482
|
+
if (!name || typeof name !== 'object') {
|
|
483
|
+
return '';
|
|
484
|
+
}
|
|
485
|
+
const map = name;
|
|
486
|
+
const locale = this.transloco.getActiveLang();
|
|
487
|
+
const candidates = [map['display'], map[locale], map['en'], map['ar']];
|
|
488
|
+
for (const candidate of candidates) {
|
|
489
|
+
if (typeof candidate === 'string' && candidate) {
|
|
490
|
+
return candidate;
|
|
491
|
+
}
|
|
492
|
+
if (typeof candidate === 'number' || typeof candidate === 'boolean') {
|
|
493
|
+
return String(candidate);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const fallback = Object.values(map).find((value) => typeof value === 'string' && value);
|
|
497
|
+
return typeof fallback === 'string' ? fallback : '';
|
|
498
|
+
}
|
|
499
|
+
loadPreview(requestId) {
|
|
500
|
+
this.loadSub?.unsubscribe();
|
|
501
|
+
this.loading.set(true);
|
|
502
|
+
this.error.set(null);
|
|
503
|
+
this.loadSub = this.http
|
|
504
|
+
.get(`processes/${requestId}/preview`)
|
|
505
|
+
.subscribe({
|
|
506
|
+
next: (response) => {
|
|
507
|
+
this.loading.set(false);
|
|
508
|
+
this.preview.set(response.data ?? null);
|
|
509
|
+
},
|
|
510
|
+
error: (error) => {
|
|
511
|
+
this.loading.set(false);
|
|
512
|
+
this.preview.set(null);
|
|
513
|
+
this.error.set(error?.error?.message ??
|
|
514
|
+
error?.message ??
|
|
515
|
+
this.transloco.translate('workCenter.preview.loadFailed'));
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: WorkCenterProcessPreview, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
520
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: WorkCenterProcessPreview, isStandalone: true, selector: "mt-work-center-process-preview", inputs: { requestId: { classPropertyName: "requestId", publicName: "requestId", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (!canRenderPreview()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process preview is not available for this item yet.\r\n </p>\r\n </div>\r\n} @else if (loading()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"text-sm text-surface-500\">Loading process preview...</p>\r\n </div>\r\n} @else if (error()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-red-300 bg-red-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-red-600\">{{ error() }}</p>\r\n </div>\r\n} @else {\r\n @if (view() === \"schema\") {\r\n @if (hasSchema()) {\r\n <div class=\"wc-process-preview-schema h-[70vh] overflow-hidden\">\r\n <mt-structure-builder\r\n class=\"h-full\"\r\n [layoutDirection]=\"'LR'\"\r\n [readonly]=\"true\"\r\n [structureMode]=\"'workflow'\"\r\n [nodeFields]=\"previewNodeFields\"\r\n [nodes]=\"schemaNodes()\"\r\n [connections]=\"schemaConnections()\"\r\n />\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process steps are not available for this item yet.\r\n </p>\r\n </div>\r\n }\r\n } @else if (hasApprovals()) {\r\n <div class=\"max-h-[70vh] overflow-y-auto\">\r\n <mt-table\r\n noCard\r\n [data]=\"approvalRows()\"\r\n [columns]=\"approvalColumns()\"\r\n storageKey=\"work-center-process-preview-table\"\r\n [showFilters]=\"false\"\r\n [generalSearch]=\"false\"\r\n [clickableRows]=\"false\"\r\n />\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process approvals data is not available for this item yet.\r\n </p>\r\n </div>\r\n }\r\n}\r\n", styles: [":host ::ng-deep .wc-process-preview-schema .wf-detail-row{align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "component", type: StructureBuilder, selector: "mt-structure-builder", inputs: ["availableNodes", "availableNodesLabel", "nodeForm", "nodeDialogFooterConfig", "connectionForm", "connectionFormulaSchemaId", "connectionFormulaConfig", "connectionGuard", "nodeActions", "nodeFields", "isAutoLayout", "readonly", "structureMode", "nodeStyle", "addModalType", "updateModalType", "addModalStyleClass", "updateModalStyleClass", "addModalHeader", "updateModalHeader", "appendTo", "availableTabsClass", "layoutDirection", "nodes", "connections", "nodeTemplate"], outputs: ["nodeActionsEvent", "action", "nodesChange", "connectionsChange"] }] });
|
|
521
|
+
}
|
|
522
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: WorkCenterProcessPreview, decorators: [{
|
|
523
|
+
type: Component,
|
|
524
|
+
args: [{ selector: 'mt-work-center-process-preview', standalone: true, imports: [CommonModule, Table, StructureBuilder], template: "@if (!canRenderPreview()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process preview is not available for this item yet.\r\n </p>\r\n </div>\r\n} @else if (loading()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"text-sm text-surface-500\">Loading process preview...</p>\r\n </div>\r\n} @else if (error()) {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-red-300 bg-red-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-red-600\">{{ error() }}</p>\r\n </div>\r\n} @else {\r\n @if (view() === \"schema\") {\r\n @if (hasSchema()) {\r\n <div class=\"wc-process-preview-schema h-[70vh] overflow-hidden\">\r\n <mt-structure-builder\r\n class=\"h-full\"\r\n [layoutDirection]=\"'LR'\"\r\n [readonly]=\"true\"\r\n [structureMode]=\"'workflow'\"\r\n [nodeFields]=\"previewNodeFields\"\r\n [nodes]=\"schemaNodes()\"\r\n [connections]=\"schemaConnections()\"\r\n />\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process steps are not available for this item yet.\r\n </p>\r\n </div>\r\n }\r\n } @else if (hasApprovals()) {\r\n <div class=\"max-h-[70vh] overflow-y-auto\">\r\n <mt-table\r\n noCard\r\n [data]=\"approvalRows()\"\r\n [columns]=\"approvalColumns()\"\r\n storageKey=\"work-center-process-preview-table\"\r\n [showFilters]=\"false\"\r\n [generalSearch]=\"false\"\r\n [clickableRows]=\"false\"\r\n />\r\n </div>\r\n } @else {\r\n <div\r\n class=\"flex min-h-[22rem] items-center justify-center rounded-lg border border-dashed border-surface-300 bg-surface-50 p-6\"\r\n >\r\n <p class=\"max-w-md text-center text-sm text-surface-500\">\r\n Process approvals data is not available for this item yet.\r\n </p>\r\n </div>\r\n }\r\n}\r\n", styles: [":host ::ng-deep .wc-process-preview-schema .wf-detail-row{align-items:center}\n"] }]
|
|
525
|
+
}], ctorParameters: () => [], propDecorators: { requestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestId", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }] } });
|
|
526
|
+
function buildEntity(name, viewType, value) {
|
|
527
|
+
return {
|
|
528
|
+
name,
|
|
529
|
+
viewType,
|
|
530
|
+
value: value ?? '',
|
|
531
|
+
configuration: {
|
|
532
|
+
hideName: true,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export { WorkCenterProcessPreview };
|
|
538
|
+
//# sourceMappingURL=masterteam-work-center-work-center-process-preview-qG9T9HoP.mjs.map
|