nucleus-core-ts 0.10.141 → 0.10.142
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/dist/.build-ok +1 -1
- package/dist/dist/index.d.ts +1 -1
- package/dist/fe/components/VerificationFlowPage/components/PendingTab.d.ts +7 -2
- package/dist/fe/components/VerificationFlowPage/components/PendingTab.js +76 -23
- package/dist/fe/components/VerificationFlowPage/components/VerificationEntityList.d.ts +22 -2
- package/dist/fe/components/VerificationFlowPage/components/VerificationEntityList.js +127 -23
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.d.ts +1 -1
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.js +61 -7
- package/dist/fe/components/VerificationFlowPage/components/decisionPlan.d.ts +20 -0
- package/dist/fe/components/VerificationFlowPage/components/decisionPlan.js +39 -0
- package/dist/fe/components/VerificationFlowPage/components/recordLabels.d.ts +41 -0
- package/dist/fe/components/VerificationFlowPage/components/recordLabels.js +58 -0
- package/dist/fe/components/VerificationFlowPage/components/supersededCard.d.ts +14 -0
- package/dist/fe/components/VerificationFlowPage/components/supersededCard.js +17 -0
- package/dist/fe/components/VerificationFlowPage/components/useEntityLabels.d.ts +12 -0
- package/dist/fe/components/VerificationFlowPage/components/useEntityLabels.js +40 -0
- package/dist/fe/components/VerificationFlowPage/index.d.ts +1 -1
- package/dist/fe/components/VerificationFlowPage/labels.d.ts +60 -0
- package/dist/fe/components/VerificationFlowPage/labels.js +20 -2
- package/dist/fe/components/VerificationFlowPage/types/index.d.ts +40 -0
- package/dist/fe/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/server.js +6 -6
- package/dist/src/Client/ApiCaller/types.d.ts +29 -0
- package/dist/src/Nucleus/routes/storage/cachePolicy.d.ts +37 -0
- package/dist/src/Nucleus/routes/storage/cdn.d.ts +21 -0
- package/dist/src/Nucleus/routes/storage/file-record.d.ts +12 -0
- package/dist/src/Nucleus/routes/storage/serveGuard.d.ts +60 -0
- package/dist/src/Services/Notification/index.d.ts +1 -0
- package/dist/src/Services/Notification/source.d.ts +22 -0
- package/dist/src/Services/Verification/cancelPhase.d.ts +76 -0
- package/dist/src/Services/Verification/index.d.ts +22 -4
- package/dist/src/Services/Verification/reviewSteps.d.ts +21 -0
- package/dist/src/Services/Verification/startPhase.d.ts +26 -0
- package/dist/src/Services/Verification/supersession.d.ts +63 -0
- package/dist/src/Services/Verification/types.d.ts +61 -2
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.142
|
package/dist/dist/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ declare function parseCron2(expression: any): {
|
|
|
59
59
|
};
|
|
60
60
|
error?: undefined;
|
|
61
61
|
};
|
|
62
|
-
export function matchesCron(fields: any,
|
|
62
|
+
export function matchesCron(fields: any, when2: any, timeZone: any): any;
|
|
63
63
|
export function isDue(input: any): {
|
|
64
64
|
due: boolean;
|
|
65
65
|
error: string | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { VerificationFlowLabels } from '../labels';
|
|
3
|
-
import type { VerificationDecideAction, VerificationPendingAction, VerificationSignatureUploadAction } from '../types';
|
|
3
|
+
import type { EntityLabel, VerificationDecideAction, VerificationPendingAction, VerificationSignatureUploadAction } from '../types';
|
|
4
4
|
interface PendingTabProps {
|
|
5
5
|
pendingAction: VerificationPendingAction;
|
|
6
6
|
decideAction: VerificationDecideAction;
|
|
@@ -12,6 +12,11 @@ interface PendingTabProps {
|
|
|
12
12
|
/** Names the record a decision is about. See `VerificationFlowPageProps`. */
|
|
13
13
|
describeEntity?: (entityName: string, entityId: string) => string | undefined;
|
|
14
14
|
entityHref?: (entityName: string, entityId: string) => string | undefined;
|
|
15
|
+
/** Looks the records up by name; see `VerificationFlowPageProps`. */
|
|
16
|
+
labelEntities?: (refs: {
|
|
17
|
+
entity_name: string;
|
|
18
|
+
entity_id: string;
|
|
19
|
+
}[]) => Promise<EntityLabel[]>;
|
|
15
20
|
}
|
|
16
|
-
export declare function PendingTab({ pendingAction, decideAction, uploadSignatureAction, onDecisionMade, labels, describeEntity, entityHref, }: PendingTabProps): ReactElement;
|
|
21
|
+
export declare function PendingTab({ pendingAction, decideAction, uploadSignatureAction, onDecisionMade, labels, describeEntity, entityHref, labelEntities, }: PendingTabProps): ReactElement;
|
|
17
22
|
export {};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
4
4
|
import { DEFAULT_VERIFICATION_FLOW_LABELS, refusalSentence } from '../labels';
|
|
5
5
|
import { useVerificationFlowStore } from '../store';
|
|
6
6
|
import { getActiveTheme } from '../theme/store';
|
|
7
7
|
import { entityWord, isRevisionStep, planDecision, returnedSentence, showsLastRoundWarning, signatureFileType } from './decisionPlan';
|
|
8
|
-
|
|
8
|
+
import { hostRecordLabel, matchesSearch } from './recordLabels';
|
|
9
|
+
import { approvalOffered, supersededSentence } from './supersededCard';
|
|
10
|
+
import { useEntityLabels } from './useEntityLabels';
|
|
11
|
+
export function PendingTab({ pendingAction, decideAction, uploadSignatureAction, onDecisionMade, labels, describeEntity, entityHref, labelEntities }) {
|
|
9
12
|
/*
|
|
10
13
|
* Local, not in the store: a chosen file must not survive cancelling out of
|
|
11
14
|
* its card. Keyed by instance because it was one slot for the whole tab: a
|
|
@@ -52,6 +55,13 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
52
55
|
...labels
|
|
53
56
|
};
|
|
54
57
|
const store = useVerificationFlowStore();
|
|
58
|
+
/*
|
|
59
|
+
* What the reviewer typed to narrow the queue, and the names it is matched
|
|
60
|
+
* against. Measured in the audit: fourteen cards reading
|
|
61
|
+
* "Sayfa Versiyonları · 9f769e4b", no search, no order — the one about a
|
|
62
|
+
* given medicine was found by opening every "Open the record".
|
|
63
|
+
*/ const [query, setQuery] = useState('');
|
|
64
|
+
const resolvedLabels = useEntityLabels(labelEntities, store.pendingItems);
|
|
55
65
|
const loadPending = useEffectEvent(()=>{
|
|
56
66
|
/*
|
|
57
67
|
* Found in review: the spinner below replaces the whole list, so every
|
|
@@ -306,7 +316,8 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
306
316
|
* `page_versions/9f769e4b-…` and nothing else, so a reviewer signed a leaflet
|
|
307
317
|
* version they had no way to read. `describeEntity` lets the host supply the
|
|
308
318
|
* name; the href, when there is a screen, lets them open it.
|
|
309
|
-
*/ const
|
|
319
|
+
*/ const nameOf = (item)=>hostRecordLabel(item, resolvedLabels, describeEntity) ?? `${entityWord(say, item.entity_name)}/${item.entity_id}`;
|
|
320
|
+
const renderRecord = (item)=>/*#__PURE__*/ _jsxs("span", {
|
|
310
321
|
className: theme.pending.card.recordRow,
|
|
311
322
|
children: [
|
|
312
323
|
/*#__PURE__*/ _jsx("span", {
|
|
@@ -315,7 +326,7 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
315
326
|
}),
|
|
316
327
|
/*#__PURE__*/ _jsx("span", {
|
|
317
328
|
className: theme.pending.card.recordValue,
|
|
318
|
-
children:
|
|
329
|
+
children: nameOf(item)
|
|
319
330
|
}),
|
|
320
331
|
entityHref?.(item.entity_name, item.entity_id) ? /*#__PURE__*/ _jsx("a", {
|
|
321
332
|
href: entityHref(item.entity_name, item.entity_id),
|
|
@@ -326,6 +337,18 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
326
337
|
}) : null
|
|
327
338
|
]
|
|
328
339
|
});
|
|
340
|
+
/*
|
|
341
|
+
* A record the host says was replaced by a newer one: said on the card,
|
|
342
|
+
* before anything is pressed, because the engine will refuse its approval.
|
|
343
|
+
* See `supersededCard`.
|
|
344
|
+
*/ const renderSuperseded = (item)=>{
|
|
345
|
+
const text = supersededSentence(say, item);
|
|
346
|
+
return text ? /*#__PURE__*/ _jsx("p", {
|
|
347
|
+
className: theme.pending.card.lastRoundWarning,
|
|
348
|
+
role: "note",
|
|
349
|
+
children: text
|
|
350
|
+
}) : null;
|
|
351
|
+
};
|
|
329
352
|
const renderSignaturePicker = (item, onPick)=>item.require_signature ? /*#__PURE__*/ _jsxs("label", {
|
|
330
353
|
className: "flex flex-col gap-1 text-xs",
|
|
331
354
|
children: [
|
|
@@ -385,7 +408,8 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
385
408
|
className: theme.pending.card.decisionMeta,
|
|
386
409
|
children: returned
|
|
387
410
|
}) : null,
|
|
388
|
-
renderRecord(item)
|
|
411
|
+
renderRecord(item),
|
|
412
|
+
renderSuperseded(item)
|
|
389
413
|
]
|
|
390
414
|
}),
|
|
391
415
|
confirming ? /*#__PURE__*/ _jsxs("div", {
|
|
@@ -426,17 +450,21 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
426
450
|
}) : /*#__PURE__*/ _jsxs("div", {
|
|
427
451
|
className: theme.pending.card.actions,
|
|
428
452
|
children: [
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
453
|
+
approvalOffered(item) ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
454
|
+
children: [
|
|
455
|
+
renderSignaturePicker(item, ()=>enterCard(item)),
|
|
456
|
+
/*#__PURE__*/ _jsx("button", {
|
|
457
|
+
type: "button",
|
|
458
|
+
onClick: ()=>{
|
|
459
|
+
enterCard(item);
|
|
460
|
+
handleDecide(item, 'approved');
|
|
461
|
+
},
|
|
462
|
+
"aria-describedby": `resubmit-hint-${item.instance_id}`,
|
|
463
|
+
className: theme.pending.approveButton,
|
|
464
|
+
children: firstRound ? say.submitForReview : say.resubmit
|
|
465
|
+
})
|
|
466
|
+
]
|
|
467
|
+
}) : null,
|
|
440
468
|
/*#__PURE__*/ _jsx("button", {
|
|
441
469
|
type: "button",
|
|
442
470
|
id: `withdraw-${item.instance_id}`,
|
|
@@ -450,11 +478,11 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
450
478
|
className: theme.pending.cancelButton,
|
|
451
479
|
children: say.withdrawSubmission
|
|
452
480
|
}),
|
|
453
|
-
/*#__PURE__*/ _jsx("p", {
|
|
481
|
+
approvalOffered(item) ? /*#__PURE__*/ _jsx("p", {
|
|
454
482
|
id: `resubmit-hint-${item.instance_id}`,
|
|
455
483
|
className: theme.pending.card.decisionMeta,
|
|
456
484
|
children: firstRound ? say.submitForReviewHint : say.resubmitHint
|
|
457
|
-
})
|
|
485
|
+
}) : null
|
|
458
486
|
]
|
|
459
487
|
})
|
|
460
488
|
]
|
|
@@ -481,7 +509,8 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
481
509
|
say.previousRoundReason(item.sent_back.reason)
|
|
482
510
|
]
|
|
483
511
|
}) : null,
|
|
484
|
-
renderRecord(item)
|
|
512
|
+
renderRecord(item),
|
|
513
|
+
renderSuperseded(item)
|
|
485
514
|
]
|
|
486
515
|
}),
|
|
487
516
|
store.decidingId === item.instance_id ? /*#__PURE__*/ _jsxs("div", {
|
|
@@ -499,7 +528,7 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
499
528
|
className: theme.pending.card.lastRoundWarning,
|
|
500
529
|
children: say.pendingLastRoundWarning
|
|
501
530
|
}) : null,
|
|
502
|
-
renderSignaturePicker(item),
|
|
531
|
+
approvalOffered(item) ? renderSignaturePicker(item) : null,
|
|
503
532
|
/*#__PURE__*/ _jsx("input", {
|
|
504
533
|
type: "text",
|
|
505
534
|
placeholder: say.reasonPlaceholder,
|
|
@@ -508,12 +537,12 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
508
537
|
onChange: (e)=>store.setDecisionReason(e.target.value),
|
|
509
538
|
className: theme.pending.input
|
|
510
539
|
}),
|
|
511
|
-
/*#__PURE__*/ _jsx("button", {
|
|
540
|
+
approvalOffered(item) ? /*#__PURE__*/ _jsx("button", {
|
|
512
541
|
type: "button",
|
|
513
542
|
onClick: ()=>handleDecide(item, 'approved'),
|
|
514
543
|
className: theme.pending.approveButton,
|
|
515
544
|
children: say.approve
|
|
516
|
-
}),
|
|
545
|
+
}) : null,
|
|
517
546
|
/*#__PURE__*/ _jsx("button", {
|
|
518
547
|
type: "button",
|
|
519
548
|
onClick: ()=>handleDecide(item, 'rejected'),
|
|
@@ -544,6 +573,17 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
544
573
|
})
|
|
545
574
|
]
|
|
546
575
|
});
|
|
576
|
+
/*
|
|
577
|
+
* The card being acted on stays whatever is typed: hiding it mid-decision
|
|
578
|
+
* would also hide the refusal written under it.
|
|
579
|
+
*/ const shown = store.pendingItems.filter((item)=>item.instance_id === store.decidingId || matchesSearch([
|
|
580
|
+
nameOf(item),
|
|
581
|
+
entityWord(say, item.entity_name),
|
|
582
|
+
item.entity_id,
|
|
583
|
+
item.flow_name,
|
|
584
|
+
item.step_name
|
|
585
|
+
], query));
|
|
586
|
+
const searching = query.trim() !== '';
|
|
547
587
|
return /*#__PURE__*/ _jsxs("div", {
|
|
548
588
|
className: theme.pending.container,
|
|
549
589
|
children: [
|
|
@@ -551,7 +591,20 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
551
591
|
className: theme.pending.title,
|
|
552
592
|
children: say.pendingApprovals(store.pendingItems.length)
|
|
553
593
|
}),
|
|
554
|
-
store.pendingItems.
|
|
594
|
+
store.pendingItems.length > 1 ? /*#__PURE__*/ _jsx("input", {
|
|
595
|
+
type: "search",
|
|
596
|
+
value: query,
|
|
597
|
+
onChange: (e)=>setQuery(e.target.value),
|
|
598
|
+
placeholder: say.pendingSearchLabel,
|
|
599
|
+
"aria-label": say.pendingSearchLabel,
|
|
600
|
+
className: theme.pending.input
|
|
601
|
+
}) : null,
|
|
602
|
+
searching ? /*#__PURE__*/ _jsx("p", {
|
|
603
|
+
className: theme.pending.card.decisionMeta,
|
|
604
|
+
role: "status",
|
|
605
|
+
children: shown.length === 0 ? say.pendingSearchNoMatch(query.trim()) : say.pendingSearchShowing(shown.length, store.pendingItems.length)
|
|
606
|
+
}) : null,
|
|
607
|
+
shown.map((item)=>/*#__PURE__*/ _jsxs("div", {
|
|
555
608
|
className: theme.pending.card.container,
|
|
556
609
|
children: [
|
|
557
610
|
isRevisionStep(item) ? renderRevisionCard(item) : renderReviewCard(item),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VerificationFlowLabels } from '../labels';
|
|
2
|
-
import type { VerificationBulkStatusItem, VerificationCancelAction, VerificationEntityStatusesAction, VerificationStartForEntityAction } from '../types';
|
|
2
|
+
import type { EntityLabel, VerificationBulkStatusItem, VerificationCancelAction, VerificationEntityStatusesAction, VerificationStartForEntityAction } from '../types';
|
|
3
3
|
export type VerificationEntityListTheme = {
|
|
4
4
|
container: string;
|
|
5
5
|
header: string;
|
|
@@ -26,6 +26,15 @@ export type VerificationEntityListTheme = {
|
|
|
26
26
|
progressSection: string;
|
|
27
27
|
progressBar: string;
|
|
28
28
|
progressFill: string;
|
|
29
|
+
/**
|
|
30
|
+
* The fill of a chain that stopped. It fell through to `bg-white/20`, which
|
|
31
|
+
* on the light track (`bg-gray-200`) is invisible: a chain cancelled at 3/3
|
|
32
|
+
* showed an empty bar beside "3/3".
|
|
33
|
+
*/
|
|
34
|
+
progressCancelled: string;
|
|
35
|
+
/** The search box above the rows, and the line saying what it found. */
|
|
36
|
+
searchInput: string;
|
|
37
|
+
searchNote: string;
|
|
29
38
|
progressLabel: string;
|
|
30
39
|
/** "Round 2 (at most 3)" under the bar, which otherwise reads backwards. */
|
|
31
40
|
roundLabel: string;
|
|
@@ -68,5 +77,16 @@ export type VerificationEntityListProps = {
|
|
|
68
77
|
* its first review still starts from the record's own screen.
|
|
69
78
|
*/
|
|
70
79
|
startForEntityAction?: VerificationStartForEntityAction;
|
|
80
|
+
/**
|
|
81
|
+
* The same naming hooks the queue has. Rows printed the table's word and a
|
|
82
|
+
* full uuid, so an operator looking for one medicine's review read every
|
|
83
|
+
* row's id. See `VerificationFlowPageProps`.
|
|
84
|
+
*/
|
|
85
|
+
describeEntity?: (entityName: string, entityId: string) => string | undefined;
|
|
86
|
+
entityHref?: (entityName: string, entityId: string) => string | undefined;
|
|
87
|
+
labelEntities?: (refs: {
|
|
88
|
+
entity_name: string;
|
|
89
|
+
entity_id: string;
|
|
90
|
+
}[]) => Promise<EntityLabel[]>;
|
|
71
91
|
};
|
|
72
|
-
export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, cancelAction, startForEntityAction, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
92
|
+
export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, cancelAction, startForEntityAction, describeEntity, entityHref, labelEntities, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
4
4
|
import { DEFAULT_VERIFICATION_FLOW_LABELS, refusalSentence } from '../labels';
|
|
5
|
-
import { chainRoundLabel, chainStatusWord, entityWord, listStatusLabel, rowForStartNote } from './decisionPlan';
|
|
5
|
+
import { chainProgress, chainRoundLabel, chainStatusWord, entityWord, listStatusLabel, rowForStartNote } from './decisionPlan';
|
|
6
|
+
import { hostRecordLabel, matchesSearch } from './recordLabels';
|
|
7
|
+
import { useEntityLabels } from './useEntityLabels';
|
|
6
8
|
const darkTheme = {
|
|
7
9
|
container: 'space-y-4',
|
|
8
10
|
header: 'space-y-1',
|
|
@@ -28,6 +30,9 @@ const darkTheme = {
|
|
|
28
30
|
progressSection: 'flex items-center gap-2.5 shrink-0 w-[140px]',
|
|
29
31
|
progressBar: 'flex-1 h-1.5 rounded-full bg-white/8 overflow-hidden',
|
|
30
32
|
progressFill: 'h-full rounded-full transition-all duration-500',
|
|
33
|
+
progressCancelled: 'bg-white/20',
|
|
34
|
+
searchInput: 'w-full rounded-lg border border-white/[0.08] bg-white/[0.04] px-3 py-2 text-xs text-white/80 outline-none placeholder:text-white/30 focus:border-white/20',
|
|
35
|
+
searchNote: 'text-[11px] leading-relaxed text-white/45',
|
|
31
36
|
progressLabel: 'text-[10px] text-white/40 font-medium whitespace-nowrap w-[45px] text-right',
|
|
32
37
|
roundLabel: 'text-[10px] text-white/45 whitespace-nowrap',
|
|
33
38
|
metaSection: 'flex items-center gap-4 shrink-0',
|
|
@@ -67,6 +72,9 @@ const lightTheme = {
|
|
|
67
72
|
progressSection: 'flex items-center gap-2.5 shrink-0 w-[140px]',
|
|
68
73
|
progressBar: 'flex-1 h-1.5 rounded-full bg-gray-200 overflow-hidden',
|
|
69
74
|
progressFill: 'h-full rounded-full transition-all duration-500',
|
|
75
|
+
progressCancelled: 'bg-gray-400',
|
|
76
|
+
searchInput: 'w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs text-gray-800 outline-none placeholder:text-gray-400 focus:border-gray-400',
|
|
77
|
+
searchNote: 'text-[11px] leading-relaxed text-gray-500',
|
|
70
78
|
progressLabel: 'text-[10px] text-gray-500 font-medium whitespace-nowrap w-[45px] text-right',
|
|
71
79
|
roundLabel: 'text-[10px] text-gray-500 whitespace-nowrap',
|
|
72
80
|
metaSection: 'flex items-center gap-4 shrink-0',
|
|
@@ -88,7 +96,7 @@ const lightTheme = {
|
|
|
88
96
|
'rejected',
|
|
89
97
|
'cancelled'
|
|
90
98
|
];
|
|
91
|
-
export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels, cancelAction, startForEntityAction }) {
|
|
99
|
+
export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels, cancelAction, startForEntityAction, describeEntity, entityHref, labelEntities }) {
|
|
92
100
|
const baseTheme = themeMode === 'light' ? lightTheme : darkTheme;
|
|
93
101
|
const t = {
|
|
94
102
|
...baseTheme,
|
|
@@ -107,6 +115,15 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
107
115
|
// Which row is being sent back into review, and what it was told.
|
|
108
116
|
const [startingId, setStartingId] = useState(null);
|
|
109
117
|
const [startNote, setStartNote] = useState(null);
|
|
118
|
+
// What the reader typed to narrow the rows on screen; see `shownItems`.
|
|
119
|
+
const [query, setQuery] = useState('');
|
|
120
|
+
/*
|
|
121
|
+
* The list could not be READ. Measured in the audit: a reviewer opening this
|
|
122
|
+
* tab got `403 operator privileges required`, the cached state went to
|
|
123
|
+
* `data: null`, and the list read that as "0 records" and "No verification
|
|
124
|
+
* records found" while dozens of chains existed. `forbidden` is the server
|
|
125
|
+
* refusing this reader, which is said differently from a failure.
|
|
126
|
+
*/ const [loadFailure, setLoadFailure] = useState(null);
|
|
110
127
|
/*
|
|
111
128
|
* Counts list reloads, so an answer can tell whether the list it was asked
|
|
112
129
|
* about is still the one on screen. See `startAgain`.
|
|
@@ -181,6 +198,7 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
181
198
|
* under a row through tab switches and page turns, long after the row it
|
|
182
199
|
* described had changed state.
|
|
183
200
|
*/ setStartNote(null);
|
|
201
|
+
setLoadFailure(null);
|
|
184
202
|
loadGeneration.current += 1;
|
|
185
203
|
entityStatusesAction.start({
|
|
186
204
|
payload: {
|
|
@@ -188,6 +206,20 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
188
206
|
status: status === 'all' ? undefined : status,
|
|
189
207
|
page: page || 1,
|
|
190
208
|
limit: pageSize
|
|
209
|
+
},
|
|
210
|
+
onAfterHandle: (res)=>{
|
|
211
|
+
const r = res;
|
|
212
|
+
if (r?.success === false) setLoadFailure({
|
|
213
|
+
forbidden: false,
|
|
214
|
+
message: r.message
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
onErrorHandle: (err, code)=>{
|
|
218
|
+
const e = err;
|
|
219
|
+
setLoadFailure({
|
|
220
|
+
forbidden: code === 403,
|
|
221
|
+
message: e?.message
|
|
222
|
+
});
|
|
191
223
|
}
|
|
192
224
|
});
|
|
193
225
|
});
|
|
@@ -243,6 +275,26 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
243
275
|
const items = responseData?.data?.items || [];
|
|
244
276
|
const total = responseData?.data?.total || 0;
|
|
245
277
|
const totalPages = Math.ceil(total / pageSize) || 1;
|
|
278
|
+
// Whose chains these are; see `BulkStatusResult.scope`. Absent on an older engine.
|
|
279
|
+
const scope = responseData?.data?.scope;
|
|
280
|
+
const resolvedLabels = useEntityLabels(labelEntities, items);
|
|
281
|
+
/*
|
|
282
|
+
* The rows that match what was typed, by the host's name for the record,
|
|
283
|
+
* the entity word, the id, the flow, the step and the status word. Only the
|
|
284
|
+
* page on screen: the server pages the list and only the host knows the
|
|
285
|
+
* names, so a search across pages would be a claim this list cannot back —
|
|
286
|
+
* the line under the box says so when there are more pages. A row whose
|
|
287
|
+
* cancel is open stays, so the question and its answer are not hidden
|
|
288
|
+
* mid-decision.
|
|
289
|
+
*/ const searching = query.trim() !== '';
|
|
290
|
+
const shownItems = searching ? items.filter((item)=>item.instance_id === cancellingId || matchesSearch([
|
|
291
|
+
hostRecordLabel(item, resolvedLabels, describeEntity),
|
|
292
|
+
entityWord(say, item.entity_name),
|
|
293
|
+
item.entity_id,
|
|
294
|
+
item.flow_name,
|
|
295
|
+
item.current_step_name,
|
|
296
|
+
chainStatusWord(say, item)
|
|
297
|
+
], query)) : items;
|
|
246
298
|
const getStatusBadgeClass = (item)=>{
|
|
247
299
|
if (item.status === 'active' && item.current_step_kind === 'revision') return t.statusRevision;
|
|
248
300
|
switch(item.status){
|
|
@@ -267,7 +319,8 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
267
319
|
case 'active':
|
|
268
320
|
return 'bg-gradient-to-r from-amber-400 to-cyan-400';
|
|
269
321
|
default:
|
|
270
|
-
|
|
322
|
+
// Cancelled, withdrawn, or a status this list does not know.
|
|
323
|
+
return t.progressCancelled;
|
|
271
324
|
}
|
|
272
325
|
};
|
|
273
326
|
/*
|
|
@@ -279,14 +332,10 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
279
332
|
* region that is always mounted (so it is announced), shown on the record's
|
|
280
333
|
* row when that row is on this page (`rowForStartNote`) and above the list
|
|
281
334
|
* when it is not.
|
|
282
|
-
*/
|
|
335
|
+
*/ // The rows actually drawn: a note for a row the search hid is shown above.
|
|
336
|
+
const noteRowId = isPending ? null : rowForStartNote(shownItems, startNote);
|
|
283
337
|
const noteShownAbove = startNote !== null && !isPending && noteRowId === null;
|
|
284
338
|
const noteClass = themeMode === 'light' ? 'text-[10px] leading-relaxed text-gray-500' : 'text-[10px] leading-relaxed text-white/45';
|
|
285
|
-
const calculateProgress = (item)=>{
|
|
286
|
-
if (item.status === 'completed') return 100;
|
|
287
|
-
if (item.total_steps === 0) return 0;
|
|
288
|
-
return Math.round((item.current_step_order - 1) / item.total_steps * 100);
|
|
289
|
-
};
|
|
290
339
|
return /*#__PURE__*/ _jsxs("div", {
|
|
291
340
|
className: t.container,
|
|
292
341
|
children: [
|
|
@@ -297,9 +346,9 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
297
346
|
className: t.title,
|
|
298
347
|
children: say.verificationStatusTitle
|
|
299
348
|
}),
|
|
300
|
-
/*#__PURE__*/ _jsx("p", {
|
|
349
|
+
loadFailure ? null : /*#__PURE__*/ _jsx("p", {
|
|
301
350
|
className: t.description,
|
|
302
|
-
children: say.recordCount(total, entityWord(say, entityName))
|
|
351
|
+
children: scope === 'started_by_me' ? say.recordCountStartedByMe(total, entityWord(say, entityName)) : say.recordCount(total, entityWord(say, entityName))
|
|
303
352
|
})
|
|
304
353
|
]
|
|
305
354
|
}),
|
|
@@ -312,6 +361,27 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
312
361
|
children: listStatusLabel(say, tab)
|
|
313
362
|
}, tab))
|
|
314
363
|
}),
|
|
364
|
+
!loadFailure && (items.length > 1 || searching) ? /*#__PURE__*/ _jsxs("div", {
|
|
365
|
+
className: "flex flex-col gap-1",
|
|
366
|
+
children: [
|
|
367
|
+
/*#__PURE__*/ _jsx("input", {
|
|
368
|
+
type: "search",
|
|
369
|
+
value: query,
|
|
370
|
+
onChange: (e)=>setQuery(e.target.value),
|
|
371
|
+
placeholder: say.statusesSearchLabel,
|
|
372
|
+
"aria-label": say.statusesSearchLabel,
|
|
373
|
+
className: t.searchInput
|
|
374
|
+
}),
|
|
375
|
+
searching ? /*#__PURE__*/ _jsxs("p", {
|
|
376
|
+
className: t.searchNote,
|
|
377
|
+
role: "status",
|
|
378
|
+
children: [
|
|
379
|
+
shownItems.length === 0 ? say.statusesSearchNoMatch(query.trim()) : say.statusesSearchShowing(shownItems.length, items.length),
|
|
380
|
+
totalPages > 1 ? ` ${say.statusesSearchPageOnly(currentPage, totalPages)}` : null
|
|
381
|
+
]
|
|
382
|
+
}) : null
|
|
383
|
+
]
|
|
384
|
+
}) : null,
|
|
315
385
|
/*#__PURE__*/ _jsx("p", {
|
|
316
386
|
role: "status",
|
|
317
387
|
className: noteShownAbove ? noteClass : 'sr-only',
|
|
@@ -322,6 +392,33 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
322
392
|
children: /*#__PURE__*/ _jsx("div", {
|
|
323
393
|
className: `w-6 h-6 rounded-full border-2 animate-spin ${themeMode === 'light' ? 'border-gray-200 border-t-blue-500' : 'border-white/10 border-t-cyan-400'}`
|
|
324
394
|
})
|
|
395
|
+
}) : loadFailure ? /*#__PURE__*/ _jsxs("div", {
|
|
396
|
+
className: t.emptyState,
|
|
397
|
+
role: "alert",
|
|
398
|
+
children: [
|
|
399
|
+
/*#__PURE__*/ _jsx("p", {
|
|
400
|
+
className: t.emptyText,
|
|
401
|
+
children: loadFailure.forbidden ? say.statusesForbidden : say.statusesLoadFailedTitle
|
|
402
|
+
}),
|
|
403
|
+
loadFailure.forbidden ? null : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
404
|
+
children: [
|
|
405
|
+
/*#__PURE__*/ _jsx("p", {
|
|
406
|
+
className: t.emptyText,
|
|
407
|
+
children: say.statusesLoadFailedBody
|
|
408
|
+
}),
|
|
409
|
+
loadFailure.message ? /*#__PURE__*/ _jsx("p", {
|
|
410
|
+
className: t.entityId,
|
|
411
|
+
children: loadFailure.message
|
|
412
|
+
}) : null,
|
|
413
|
+
/*#__PURE__*/ _jsx("button", {
|
|
414
|
+
type: "button",
|
|
415
|
+
className: t.pageButton,
|
|
416
|
+
onClick: ()=>reloadCurrent(),
|
|
417
|
+
children: say.retry
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
})
|
|
421
|
+
]
|
|
325
422
|
}) : items.length === 0 ? /*#__PURE__*/ _jsxs("div", {
|
|
326
423
|
className: t.emptyState,
|
|
327
424
|
children: [
|
|
@@ -345,14 +442,16 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
345
442
|
}),
|
|
346
443
|
/*#__PURE__*/ _jsx("p", {
|
|
347
444
|
className: t.emptyText,
|
|
348
|
-
children: activeTab === 'all' ? say.noRecords : say.noRecordsForStatus(listStatusLabel(say, activeTab))
|
|
445
|
+
children: scope === 'started_by_me' ? activeTab === 'all' ? say.noRecordsStartedByMe : say.noRecordsStartedByMeForStatus(listStatusLabel(say, activeTab)) : activeTab === 'all' ? say.noRecords : say.noRecordsForStatus(listStatusLabel(say, activeTab))
|
|
349
446
|
})
|
|
350
447
|
]
|
|
351
448
|
}) : /*#__PURE__*/ _jsx("div", {
|
|
352
449
|
className: t.list,
|
|
353
|
-
children:
|
|
354
|
-
|
|
450
|
+
children: shownItems.map((item)=>{
|
|
451
|
+
// In review steps; see `chainProgress`.
|
|
452
|
+
const progress = chainProgress(item);
|
|
355
453
|
const round = chainRoundLabel(say, item);
|
|
454
|
+
const href = entityHref?.(item.entity_name, item.entity_id);
|
|
356
455
|
return /*#__PURE__*/ _jsxs("article", {
|
|
357
456
|
className: `${t.listItem} ${onEntityClick ? t.listItemHover : ''} ${onEntityClick ? 'cursor-pointer' : ''}`,
|
|
358
457
|
onClick: ()=>onEntityClick?.(item),
|
|
@@ -367,7 +466,7 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
367
466
|
children: [
|
|
368
467
|
/*#__PURE__*/ _jsx("p", {
|
|
369
468
|
className: t.entityName,
|
|
370
|
-
children: entityWord(say, item.entity_name)
|
|
469
|
+
children: hostRecordLabel(item, resolvedLabels, describeEntity) ?? entityWord(say, item.entity_name)
|
|
371
470
|
}),
|
|
372
471
|
/*#__PURE__*/ _jsx("p", {
|
|
373
472
|
className: t.entityId,
|
|
@@ -377,6 +476,15 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
377
476
|
className: t.flowName,
|
|
378
477
|
children: item.flow_name
|
|
379
478
|
}),
|
|
479
|
+
href ? /*#__PURE__*/ _jsx("a", {
|
|
480
|
+
href: href,
|
|
481
|
+
target: "_blank",
|
|
482
|
+
rel: "noopener noreferrer",
|
|
483
|
+
onClick: (e)=>e.stopPropagation(),
|
|
484
|
+
onKeyDown: (e)=>e.stopPropagation(),
|
|
485
|
+
className: themeMode === 'light' ? 'text-[11px] font-medium text-blue-600 hover:underline' : 'text-[11px] font-medium text-cyan-300 hover:underline',
|
|
486
|
+
children: say.openRecord
|
|
487
|
+
}) : null,
|
|
380
488
|
noteRowId === item.instance_id && startNote ? /*#__PURE__*/ _jsx("p", {
|
|
381
489
|
"aria-hidden": "true",
|
|
382
490
|
className: noteClass,
|
|
@@ -395,17 +503,13 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
395
503
|
children: /*#__PURE__*/ _jsx("div", {
|
|
396
504
|
className: `${t.progressFill} ${getProgressColor(item.status)}`,
|
|
397
505
|
style: {
|
|
398
|
-
width: `${progress}%`
|
|
506
|
+
width: `${progress.percent}%`
|
|
399
507
|
}
|
|
400
508
|
})
|
|
401
509
|
}),
|
|
402
|
-
/*#__PURE__*/
|
|
510
|
+
/*#__PURE__*/ _jsx("span", {
|
|
403
511
|
className: t.progressLabel,
|
|
404
|
-
children:
|
|
405
|
-
item.current_step_order,
|
|
406
|
-
"/",
|
|
407
|
-
item.total_steps
|
|
408
|
-
]
|
|
512
|
+
children: progress.label
|
|
409
513
|
})
|
|
410
514
|
]
|
|
411
515
|
}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '@xyflow/react/dist/style.css';
|
|
2
2
|
import type { ReactElement } from 'react';
|
|
3
3
|
import type { VerificationFlowPageProps } from '../types';
|
|
4
|
-
export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, startForEntityAction, authoringModel, flowFeasibilityAction, manageRolesHref, }: VerificationFlowPageProps): ReactElement;
|
|
4
|
+
export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, startForEntityAction, authoringModel, flowFeasibilityAction, manageRolesHref, labelEntities, canManageFlows, }: VerificationFlowPageProps): ReactElement;
|