@masterteam/work-center 0.0.64 → 0.0.66
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.
|
@@ -1481,6 +1481,15 @@ class WorkCenterEscalationInstanceType {
|
|
|
1481
1481
|
: isFinal
|
|
1482
1482
|
? this.transloco.translate('workCenter.preview.final')
|
|
1483
1483
|
: null,
|
|
1484
|
+
// Semantic color for the badge — every step state renders with the
|
|
1485
|
+
// same pill and is told apart by color alone.
|
|
1486
|
+
badgeTone: isClosed
|
|
1487
|
+
? 'closed'
|
|
1488
|
+
: isInitial
|
|
1489
|
+
? 'initial'
|
|
1490
|
+
: isFinal
|
|
1491
|
+
? 'final'
|
|
1492
|
+
: 'accent',
|
|
1484
1493
|
style: 'detail',
|
|
1485
1494
|
current: currentIds.has(String(step.id)),
|
|
1486
1495
|
};
|
|
@@ -1493,6 +1502,7 @@ class WorkCenterEscalationInstanceType {
|
|
|
1493
1502
|
color: 'color',
|
|
1494
1503
|
subtitle: 'subtitle',
|
|
1495
1504
|
badge: 'badge',
|
|
1505
|
+
badgeTone: 'badgeTone',
|
|
1496
1506
|
status: 'status',
|
|
1497
1507
|
style: 'style',
|
|
1498
1508
|
current: 'current',
|
|
@@ -1742,6 +1752,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1742
1752
|
], template: "<div\r\n class=\"mt-modal-content flex h-full min-h-0 flex-col gap-4 p-4 lg:overflow-hidden\"\r\n *transloco=\"let t; prefix: 'workCenter'\"\r\n>\r\n <div\r\n class=\"flex flex-1 min-h-0 flex-col gap-4 lg:grid lg:grid-cols-[13rem_minmax(0,1fr)] lg:gap-6\"\r\n >\r\n @if (tabOptions().length > 1) {\r\n <mt-tabs\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n mode=\"vertical\"\r\n size=\"small\"\r\n />\r\n }\r\n\r\n <div class=\"min-w-0 flex-1 min-h-0 lg:overflow-hidden\">\r\n <div\r\n class=\"h-full min-h-0 lg:overflow-hidden\"\r\n [hidden]=\"activeTab() !== 'details'\"\r\n >\r\n <div\r\n class=\"min-w-0 h-full min-h-0 lg:overflow-y-auto lg:overscroll-contain lg:pr-2\"\r\n >\r\n @if (canRenderForm()) {\r\n <mt-client-form\r\n [moduleKey]=\"moduleKey()\"\r\n [operationKey]=\"operationKey()\"\r\n [moduleId]=\"formModuleId()\"\r\n [levelId]=\"formLevelId()\"\r\n [levelDataId]=\"formLevelDataId()\"\r\n [moduleDataId]=\"moduleDataId()\"\r\n [readonly]=\"true\"\r\n [lookups]=\"formLookups()\"\r\n />\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 {{ t(\"context.moduleFormUnavailable\") }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (!readOnly()) {\r\n <div\r\n class=\"h-full min-h-0 overflow-hidden\"\r\n [hidden]=\"activeTab() !== 'discussion'\"\r\n >\r\n @if (canRenderDiscussion()) {\r\n <div\r\n class=\"h-[32rem] min-h-[22rem] overflow-hidden rounded-lg border border-surface-200 bg-surface-50 lg:h-full lg:min-h-0\"\r\n >\r\n <mt-discussion-thread\r\n [moduleType]=\"discussionModuleType()\"\r\n [recordId]=\"resolvedInstanceId()\"\r\n [mentionSearchEndpoint]=\"'Identity/users'\"\r\n [mentionSearchParam]=\"'query'\"\r\n [mentionSearchDataPath]=\"'data'\"\r\n [uploadEndpoint]=\"'uploader'\"\r\n [attachmentDownloadEndpoint]=\"'uploader'\"\r\n [showParticipants]=\"true\"\r\n [autoMarkRead]=\"true\"\r\n [refreshIntervalMs]=\"0\"\r\n [styleClass]=\"'h-full'\"\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 {{ t(\"context.moduleDiscussionUnavailable\") }}\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</div>\r\n" }]
|
|
1743
1753
|
}], ctorParameters: () => [], propDecorators: { details: [{ type: i0.Input, args: [{ isSignal: true, alias: "details", required: true }] }], lookups: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookups", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], clientForm: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ClientForm), { isSignal: true }] }] } });
|
|
1744
1754
|
|
|
1755
|
+
/**
|
|
1756
|
+
* Approval-history row projection for the process preview.
|
|
1757
|
+
*
|
|
1758
|
+
* The runtime stores one `step` record per *target user*, so a group step
|
|
1759
|
+
* fans out into one record per member, and every workflow attempt (return →
|
|
1760
|
+
* resubmit) creates a fresh fan-out for the same step schema. The table shows
|
|
1761
|
+
* one row per attempt: sibling member records collapse, but separate attempts
|
|
1762
|
+
* — and the completed history they carry — are preserved.
|
|
1763
|
+
*/
|
|
1764
|
+
const PENDING = 'pending';
|
|
1765
|
+
const TERMINATED = 'terminated';
|
|
1766
|
+
/** Statuses whose actor is not the assigned group/role, so the actor wins. */
|
|
1767
|
+
const ACTOR_OVERRIDES_TARGET = new Set([TERMINATED]);
|
|
1768
|
+
function buildApprovalRows(steps, options) {
|
|
1769
|
+
const { resolveDisplayName } = options;
|
|
1770
|
+
const currentIds = new Set((options.currentStepIds ?? []).map((id) => String(id)));
|
|
1771
|
+
const rows = [];
|
|
1772
|
+
for (const run of splitIntoRuns(steps)) {
|
|
1773
|
+
const groupName = resolveGroupName(run[0].target, resolveDisplayName);
|
|
1774
|
+
if (groupName) {
|
|
1775
|
+
rows.push(buildGroupRow(run, groupName, currentIds, resolveDisplayName));
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
// Non-group targets keep one row per record, minus the pending siblings
|
|
1779
|
+
// that were superseded when another member of the same fan-out acted.
|
|
1780
|
+
const runHasAction = run.some(hasAction);
|
|
1781
|
+
for (const step of run) {
|
|
1782
|
+
if (runHasAction &&
|
|
1783
|
+
run.length > 1 &&
|
|
1784
|
+
isSupersededPending(step, currentIds)) {
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
rows.push({
|
|
1788
|
+
stepName: resolveStepName(step, resolveDisplayName),
|
|
1789
|
+
status: step.status,
|
|
1790
|
+
user: {
|
|
1791
|
+
kind: 'user',
|
|
1792
|
+
value: step.actionUserInfo ?? step.targetUser ?? null,
|
|
1793
|
+
},
|
|
1794
|
+
createdAt: step.createdAt ?? '',
|
|
1795
|
+
actionDate: step.actionDate ?? '',
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return rows;
|
|
1800
|
+
}
|
|
1801
|
+
function buildGroupRow(run, groupName, currentIds, resolveDisplayName) {
|
|
1802
|
+
const pendingOnGroup = run.find((step) => isCurrent(step, currentIds));
|
|
1803
|
+
// While the attempt is open the row belongs to the group as a whole; once it
|
|
1804
|
+
// closes, the record that carries the outcome represents it.
|
|
1805
|
+
const repr = pendingOnGroup ?? resolveOutcome(run);
|
|
1806
|
+
const actor = repr.actionUserInfo;
|
|
1807
|
+
const showActor = !pendingOnGroup && !!actor && ACTOR_OVERRIDES_TARGET.has(statusKey(repr));
|
|
1808
|
+
return {
|
|
1809
|
+
stepName: resolveStepName(repr, resolveDisplayName),
|
|
1810
|
+
status: repr.status,
|
|
1811
|
+
user: showActor
|
|
1812
|
+
? { kind: 'user', value: actor }
|
|
1813
|
+
: { kind: 'text', value: groupName },
|
|
1814
|
+
createdAt: run[0].createdAt ?? '',
|
|
1815
|
+
actionDate: pendingOnGroup ? '' : (repr.actionDate ?? ''),
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Consecutive records sharing a step schema + target belong to the same
|
|
1820
|
+
* attempt. The API orders steps by creation, so a later attempt on the same
|
|
1821
|
+
* step is always separated by the records that sent the request back.
|
|
1822
|
+
*/
|
|
1823
|
+
function splitIntoRuns(steps) {
|
|
1824
|
+
const runs = [];
|
|
1825
|
+
let currentKey = null;
|
|
1826
|
+
for (const step of steps) {
|
|
1827
|
+
const key = runKey(step);
|
|
1828
|
+
if (key !== currentKey || runs.length === 0) {
|
|
1829
|
+
runs.push([step]);
|
|
1830
|
+
currentKey = key;
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
runs[runs.length - 1].push(step);
|
|
1834
|
+
}
|
|
1835
|
+
return runs;
|
|
1836
|
+
}
|
|
1837
|
+
function runKey(step) {
|
|
1838
|
+
return `${step.stepSchemaId ?? ''}|${step.target?.groupKey ?? ''}`;
|
|
1839
|
+
}
|
|
1840
|
+
/** The record holding the attempt's outcome (Returned/Rejected/Terminated…). */
|
|
1841
|
+
function resolveOutcome(run) {
|
|
1842
|
+
for (let index = run.length - 1; index >= 0; index -= 1) {
|
|
1843
|
+
const step = run[index];
|
|
1844
|
+
if (statusKey(step) !== PENDING && statusKey(step) !== '') {
|
|
1845
|
+
return step;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return findLast(run, hasAction) ?? run[run.length - 1];
|
|
1849
|
+
}
|
|
1850
|
+
function isCurrent(step, currentIds) {
|
|
1851
|
+
if (typeof step.isCurrent === 'boolean') {
|
|
1852
|
+
return step.isCurrent;
|
|
1853
|
+
}
|
|
1854
|
+
if (currentIds.size > 0) {
|
|
1855
|
+
return currentIds.has(String(step.stepId ?? ''));
|
|
1856
|
+
}
|
|
1857
|
+
return step.isActive !== false && statusKey(step) === PENDING;
|
|
1858
|
+
}
|
|
1859
|
+
/** A pending record left behind after a sibling acted — never actionable. */
|
|
1860
|
+
function isSupersededPending(step, currentIds) {
|
|
1861
|
+
return (statusKey(step) === PENDING &&
|
|
1862
|
+
!hasAction(step) &&
|
|
1863
|
+
!isCurrent(step, currentIds));
|
|
1864
|
+
}
|
|
1865
|
+
function hasAction(step) {
|
|
1866
|
+
if (step.actionUserInfo) {
|
|
1867
|
+
return true;
|
|
1868
|
+
}
|
|
1869
|
+
const key = statusKey(step);
|
|
1870
|
+
return key !== '' && key !== PENDING;
|
|
1871
|
+
}
|
|
1872
|
+
/** Status comes off the lookup key, never the localized display text. */
|
|
1873
|
+
function statusKey(step) {
|
|
1874
|
+
const status = step.status;
|
|
1875
|
+
if (typeof status === 'string') {
|
|
1876
|
+
return status.trim().toLowerCase();
|
|
1877
|
+
}
|
|
1878
|
+
if (status && typeof status === 'object') {
|
|
1879
|
+
const key = status['key'];
|
|
1880
|
+
if (typeof key === 'string') {
|
|
1881
|
+
return key.trim().toLowerCase();
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return '';
|
|
1885
|
+
}
|
|
1886
|
+
function resolveStepName(step, resolveDisplayName) {
|
|
1887
|
+
return resolveDisplayName(step.stepName) || String(step.stepId ?? '--');
|
|
1888
|
+
}
|
|
1889
|
+
function resolveGroupName(target, resolveDisplayName) {
|
|
1890
|
+
if (!target || target.type !== 'Group' || !target.group) {
|
|
1891
|
+
return '';
|
|
1892
|
+
}
|
|
1893
|
+
return (resolveDisplayName(target.displayName) ||
|
|
1894
|
+
resolveDisplayName(target.group.name));
|
|
1895
|
+
}
|
|
1896
|
+
function findLast(items, predicate) {
|
|
1897
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
1898
|
+
if (predicate(items[index])) {
|
|
1899
|
+
return items[index];
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return undefined;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1745
1905
|
class WorkCenterProcessPreview {
|
|
1746
1906
|
http = inject(HttpClient);
|
|
1747
1907
|
transloco = inject(TranslocoService);
|
|
@@ -1779,42 +1939,20 @@ class WorkCenterProcessPreview {
|
|
|
1779
1939
|
preview = signal(null, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
|
|
1780
1940
|
canRenderPreview = computed(() => (this.requestId() ?? 0) > 0, ...(ngDevMode ? [{ debugName: "canRenderPreview" }] : /* istanbul ignore next */ []));
|
|
1781
1941
|
approvalRows = computed(() => {
|
|
1782
|
-
const
|
|
1783
|
-
const rows = []
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const repr = siblings.find((s) => s.actionUserInfo) ?? step;
|
|
1797
|
-
rows.push({
|
|
1798
|
-
stepName: this.resolveDisplayName(repr.stepName) ||
|
|
1799
|
-
String(repr.stepId ?? '--'),
|
|
1800
|
-
status: buildEntity('Status', 'Status', repr.status),
|
|
1801
|
-
user: buildEntity('User', 'Text', groupName),
|
|
1802
|
-
createdAt: buildEntity('Initiation Date', 'DateTime', repr.createdAt ?? ''),
|
|
1803
|
-
actionDate: buildEntity('Action Date', 'DateTime', repr.actionDate ?? ''),
|
|
1804
|
-
});
|
|
1805
|
-
}
|
|
1806
|
-
else {
|
|
1807
|
-
rows.push({
|
|
1808
|
-
stepName: this.resolveDisplayName(step.stepName) ||
|
|
1809
|
-
String(step.stepId ?? '--'),
|
|
1810
|
-
status: buildEntity('Status', 'Status', step.status),
|
|
1811
|
-
user: buildEntity('User', 'User', (step.actionUserInfo ?? step.targetUser ?? null)),
|
|
1812
|
-
createdAt: buildEntity('Initiation Date', 'DateTime', step.createdAt ?? ''),
|
|
1813
|
-
actionDate: buildEntity('Action Date', 'DateTime', step.actionDate ?? ''),
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
return rows;
|
|
1942
|
+
const preview = this.preview();
|
|
1943
|
+
const rows = buildApprovalRows(preview?.steps ?? [], {
|
|
1944
|
+
currentStepIds: preview?.currentStepIds,
|
|
1945
|
+
resolveDisplayName: (value) => this.resolveDisplayName(value),
|
|
1946
|
+
});
|
|
1947
|
+
return rows.map((row) => ({
|
|
1948
|
+
stepName: row.stepName,
|
|
1949
|
+
status: buildEntity('Status', 'Status', row.status),
|
|
1950
|
+
user: row.user.kind === 'text'
|
|
1951
|
+
? buildEntity('User', 'Text', row.user.value)
|
|
1952
|
+
: buildEntity('User', 'User', row.user.value),
|
|
1953
|
+
createdAt: buildEntity('Initiation Date', 'DateTime', row.createdAt),
|
|
1954
|
+
actionDate: buildEntity('Action Date', 'DateTime', row.actionDate),
|
|
1955
|
+
}));
|
|
1818
1956
|
}, ...(ngDevMode ? [{ debugName: "approvalRows" }] : /* istanbul ignore next */ []));
|
|
1819
1957
|
hasApprovals = computed(() => this.approvalRows().length > 0, ...(ngDevMode ? [{ debugName: "hasApprovals" }] : /* istanbul ignore next */ []));
|
|
1820
1958
|
schemaNodes = computed(() => {
|
|
@@ -1869,6 +2007,9 @@ class WorkCenterProcessPreview {
|
|
|
1869
2007
|
: isFinal
|
|
1870
2008
|
? this.transloco.translate('workCenter.preview.final')
|
|
1871
2009
|
: null,
|
|
2010
|
+
// Semantic color for the badge — every step state renders with the
|
|
2011
|
+
// same pill and is told apart by color alone.
|
|
2012
|
+
badgeTone: step.isInitial ? 'initial' : isFinal ? 'final' : 'accent',
|
|
1872
2013
|
status: step.status ?? null,
|
|
1873
2014
|
style: isAppAction || isApprovalCommit ? 'icon' : 'detail',
|
|
1874
2015
|
current: currentStepIds.has(String(step.id)),
|
|
@@ -1889,6 +2030,7 @@ class WorkCenterProcessPreview {
|
|
|
1889
2030
|
color: 'color',
|
|
1890
2031
|
subtitle: 'subtitle',
|
|
1891
2032
|
badge: 'badge',
|
|
2033
|
+
badgeTone: 'badgeTone',
|
|
1892
2034
|
status: 'status',
|
|
1893
2035
|
style: 'style',
|
|
1894
2036
|
current: 'current',
|
|
@@ -1909,13 +2051,6 @@ class WorkCenterProcessPreview {
|
|
|
1909
2051
|
ngOnDestroy() {
|
|
1910
2052
|
this.loadSub?.unsubscribe();
|
|
1911
2053
|
}
|
|
1912
|
-
resolveGroupName(target) {
|
|
1913
|
-
if (!target || target.type !== 'Group' || !target.group) {
|
|
1914
|
-
return '';
|
|
1915
|
-
}
|
|
1916
|
-
return (this.resolveDisplayName(target.displayName) ||
|
|
1917
|
-
this.resolveDisplayName(target.group.name));
|
|
1918
|
-
}
|
|
1919
2054
|
resolveDisplayName(name) {
|
|
1920
2055
|
if (typeof name === 'string') {
|
|
1921
2056
|
return name;
|