@sanity/workflow-studio-plugin 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1825 @@
1
+ "use strict";
2
+
3
+ var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), sanity = require("sanity"), workflowStudio = require("@sanity/workflow-studio"), ArrowLeft = require("@sanity/icons/ArrowLeft"), workflowDiagram = require("@sanity/workflow-diagram"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal");
4
+
5
+ function MutedNote({text: text}) {
6
+ /* @__PURE__ */
7
+ return jsxRuntime.jsx(ui.Box, {
8
+ paddingY: 3,
9
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
10
+ muted: !0,
11
+ size: 1,
12
+ children: text
13
+ })
14
+ });
15
+ }
16
+
17
+ function documentRefsOf(instance) {
18
+ const seen = /* @__PURE__ */ new Map;
19
+ for (const ref of workflowEngine.entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
20
+ return [ ...seen.values() ];
21
+ }
22
+
23
+ function RefChips({refs: refs, max: max = 2}) {
24
+ if (refs.length === 0) return null;
25
+ const shown = refs.slice(0, max), overflow = refs.length - shown.length;
26
+ /* @__PURE__ */
27
+ return jsxRuntime.jsxs(ui.Flex, {
28
+ align: "center",
29
+ gap: 3,
30
+ wrap: "wrap",
31
+ children: [ shown.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
32
+ gdr: ref,
33
+ layout: "inline"
34
+ }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
35
+ muted: !0,
36
+ size: 1,
37
+ children: [ "+", overflow, " more" ]
38
+ }) : null ]
39
+ });
40
+ }
41
+
42
+ function DocRefChips({instance: instance, max: max = 2}) {
43
+ const refs = react.useMemo(() => documentRefsOf(instance), [ instance ]);
44
+ /* @__PURE__ */
45
+ return jsxRuntime.jsx(RefChips, {
46
+ max: max,
47
+ refs: refs
48
+ });
49
+ }
50
+
51
+ function datesOf(state) {
52
+ return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
53
+ }
54
+
55
+ function spawnOrigins(instances) {
56
+ const origins = /* @__PURE__ */ new Map;
57
+ for (const parent of instances) for (const row of parent.subworkflows ?? []) origins.set(workflowEngine.extractDocumentId(row.ref.id), {
58
+ parent: parent,
59
+ activityName: row.activity,
60
+ cohortStage: row.cohortStage
61
+ });
62
+ return origins;
63
+ }
64
+
65
+ function rootOf(instance, byId) {
66
+ const rootRef = instance.ancestors[0];
67
+ return rootRef ? byId.get(workflowEngine.extractDocumentId(rootRef.id)) ?? instance : instance;
68
+ }
69
+
70
+ function taskRowsOf(args) {
71
+ const {instance: instance, breadcrumb: breadcrumb, identity: identity} = args, open = workflowEngine.findOpenStageEntry(instance);
72
+ if (!open) return [];
73
+ const definition = index.definitionSnapshotOf(instance), state = index.stateByActivity(instance), rows = (open.activities ?? []).map(activity => {
74
+ const fields = state.get(activity.name) ?? [], declared = index.findActivity({
75
+ definition: definition,
76
+ stageName: open.name,
77
+ activityName: activity.name
78
+ });
79
+ return {
80
+ instanceId: instance._id,
81
+ activityName: activity.name,
82
+ title: declared.title,
83
+ description: declared.description,
84
+ breadcrumb: breadcrumb,
85
+ stage: open.name,
86
+ stageTitle: index.stageTitle(definition, open.name),
87
+ status: activity.status,
88
+ open: index.isOpenActivityStatus(activity.status),
89
+ dates: datesOf(fields),
90
+ assigneeIds: index.assigneeUserIdsOf(fields),
91
+ assignedToMe: identity !== void 0 && index.isActivityAssignedTo({
92
+ state: fields,
93
+ who: identity
94
+ })
95
+ };
96
+ });
97
+ return [ ...rows.filter(r => r.open), ...rows.filter(r => !r.open) ];
98
+ }
99
+
100
+ function deriveTaskGroups(args) {
101
+ const {instances: instances, identity: identity} = args, byId = new Map(instances.map(instance => [ instance._id, instance ])), origins = spawnOrigins(instances), groups = /* @__PURE__ */ new Map, ordered = [ ...instances ].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
102
+ for (const instance of ordered) {
103
+ const origin = origins.get(instance._id), breadcrumb = origin ? index.findActivity({
104
+ definition: index.definitionSnapshotOf(origin.parent),
105
+ stageName: origin.cohortStage,
106
+ activityName: origin.activityName
107
+ }).title : void 0, rows = taskRowsOf({
108
+ instance: instance,
109
+ breadcrumb: breadcrumb,
110
+ identity: identity
111
+ });
112
+ if (rows.length === 0) continue;
113
+ const root = rootOf(instance, byId), documents = documentRefsOf(root), document = documents[0], key = document ? `doc:${document.id}` : `def:${root.definition}`, held = groups.get(key);
114
+ if (held) {
115
+ held.rows.push(...rows);
116
+ continue;
117
+ }
118
+ const definition = index.definitionSnapshotOf(root);
119
+ groups.set(key, {
120
+ group: {
121
+ key: key,
122
+ document: document,
123
+ documents: documents,
124
+ instanceId: root._id,
125
+ workflowName: root.definition,
126
+ workflowTitle: index.instanceTitle(root, definition),
127
+ workflowDescription: definition?.description
128
+ },
129
+ rows: rows
130
+ });
131
+ }
132
+ const assembled = [ ...groups.values() ].map(({group: group, rows: rows}) => ({
133
+ ...group,
134
+ rows: rows
135
+ }));
136
+ return {
137
+ withDocuments: assembled.filter(g => g.document !== void 0),
138
+ withoutDocuments: assembled.filter(g => g.document === void 0)
139
+ };
140
+ }
141
+
142
+ function onlyMyTasks(groups) {
143
+ const cut = list => list.map(group => ({
144
+ ...group,
145
+ rows: group.rows.filter(row => row.assignedToMe)
146
+ })).filter(group => group.rows.length > 0);
147
+ return {
148
+ withDocuments: cut(groups.withDocuments),
149
+ withoutDocuments: cut(groups.withoutDocuments)
150
+ };
151
+ }
152
+
153
+ function taskFilterOptions(groups) {
154
+ const all = [ ...groups.withDocuments, ...groups.withoutDocuments ], workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
155
+ for (const group of all) workflows.set(group.workflowName, group.workflowTitle),
156
+ group.document && documents.set(group.document.id, group.document);
157
+ return {
158
+ workflows: [ ...workflows ].map(([name, title]) => ({
159
+ name: name,
160
+ title: title
161
+ })),
162
+ documents: [ ...documents.values() ]
163
+ };
164
+ }
165
+
166
+ function hasActiveFilters(filters) {
167
+ return Object.values(filters).some(value => value !== void 0 && (!Array.isArray(value) || value.length > 0));
168
+ }
169
+
170
+ function toggledValue(args) {
171
+ const {filters: filters, key: key, value: value} = args, current = filters[key] ?? [], next = current.includes(value) ? current.filter(v => v !== value) : [ ...current, value ];
172
+ if (next.length > 0) return {
173
+ ...filters,
174
+ [key]: next
175
+ };
176
+ const {[key]: _emptied, ...rest} = filters;
177
+ return rest;
178
+ }
179
+
180
+ const STATUS_LABEL = {
181
+ open: "Open",
182
+ completed: "Completed"
183
+ }, DATE_PRESET_LABEL = {
184
+ past: "In the past",
185
+ none: "No date"
186
+ };
187
+
188
+ function dateLabel(date) {
189
+ return typeof date == "string" ? DATE_PRESET_LABEL[date] : `Before ${index.formatDate(date.before)}`;
190
+ }
191
+
192
+ function withoutChip(filters, chip) {
193
+ if (chip.key === "date") {
194
+ const {date: _cleared, ...rest} = filters;
195
+ return rest;
196
+ }
197
+ return chip.value === void 0 ? filters : toggledValue({
198
+ filters: filters,
199
+ key: chip.key,
200
+ value: chip.value
201
+ });
202
+ }
203
+
204
+ function chipsFor(args) {
205
+ const {key: key, values: values, labelOf: labelOf} = args;
206
+ return (values ?? []).map(value => ({
207
+ key: key,
208
+ value: value,
209
+ label: labelOf(value)
210
+ }));
211
+ }
212
+
213
+ function shownHits(hits, cap) {
214
+ return hits.length <= cap + 1 ? [ ...hits ] : hits.slice(0, cap);
215
+ }
216
+
217
+ const PENDING_LABEL = "…";
218
+
219
+ function activeFilterChips(args) {
220
+ const {filters: filters, options: options, assigneeNames: assigneeNames, documentTitles: documentTitles} = args, chips = [ ...chipsFor({
221
+ key: "documentIds",
222
+ values: filters.documentIds,
223
+ labelOf: id => documentTitles?.get(id) ?? PENDING_LABEL
224
+ }), ...chipsFor({
225
+ key: "statuses",
226
+ values: filters.statuses,
227
+ labelOf: status => STATUS_LABEL[status]
228
+ }), ...chipsFor({
229
+ key: "assigneeIds",
230
+ values: filters.assigneeIds,
231
+ labelOf: id => assigneeNames?.get(id) ?? PENDING_LABEL
232
+ }), ...chipsFor({
233
+ key: "workflows",
234
+ values: filters.workflows,
235
+ labelOf: name => options.workflows.find(w => w.name === name)?.title ?? name
236
+ }) ];
237
+ return filters.date && chips.push({
238
+ key: "date",
239
+ label: dateLabel(filters.date)
240
+ }), chips;
241
+ }
242
+
243
+ function dateMatches(args) {
244
+ const {row: row, date: date, now: now} = args, parsed = row.dates.map(value => index.parseStoredDateValue(value)?.getTime()).filter(at => at !== void 0);
245
+ return date === "none" ? parsed.length === 0 : date === "past" ? parsed.some(at => at < now.getTime()) : parsed.some(at => at <= Date.parse(date.before));
246
+ }
247
+
248
+ function activeArm(values) {
249
+ return values !== void 0 && values.length > 0 ? values : void 0;
250
+ }
251
+
252
+ function statusArm(row, statuses) {
253
+ const arm = activeArm(statuses);
254
+ if (!arm) return !0;
255
+ const rowStatus = row.open ? "open" : "completed";
256
+ return arm.includes(rowStatus);
257
+ }
258
+
259
+ function assigneeArm(row, assigneeIds) {
260
+ const arm = activeArm(assigneeIds);
261
+ return arm ? arm.some(id => row.assigneeIds.includes(id)) : !0;
262
+ }
263
+
264
+ function rowMatches(args) {
265
+ const {row: row, filters: filters, now: now} = args;
266
+ return !(!statusArm(row, filters.statuses) || !assigneeArm(row, filters.assigneeIds) || filters.date !== void 0 && !dateMatches({
267
+ row: row,
268
+ date: filters.date,
269
+ now: now
270
+ }));
271
+ }
272
+
273
+ function groupMatches(group, filters) {
274
+ const workflows = activeArm(filters.workflows);
275
+ if (workflows && !workflows.includes(group.workflowName)) return !1;
276
+ const documentIds = activeArm(filters.documentIds);
277
+ return !(documentIds && (!group.document || !documentIds.includes(group.document.id)));
278
+ }
279
+
280
+ function applyTaskFilters(args) {
281
+ const {groups: groups, filters: filters, now: now} = args, cut = list => list.filter(group => groupMatches(group, filters)).map(group => ({
282
+ ...group,
283
+ rows: group.rows.filter(row => rowMatches({
284
+ row: row,
285
+ filters: filters,
286
+ now: now
287
+ }))
288
+ })).filter(group => group.rows.length > 0);
289
+ return {
290
+ withDocuments: cut(groups.withDocuments),
291
+ withoutDocuments: cut(groups.withoutDocuments)
292
+ };
293
+ }
294
+
295
+ function instanceFilterOptions(rows) {
296
+ const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
297
+ for (const row of rows) {
298
+ workflows.set(row.instance.definition, row.workflowTitle);
299
+ for (const doc of row.documents) documents.set(doc.id, doc);
300
+ }
301
+ return {
302
+ workflows: [ ...workflows ].map(([name, title]) => ({
303
+ name: name,
304
+ title: title
305
+ })),
306
+ documents: [ ...documents.values() ]
307
+ };
308
+ }
309
+
310
+ function applyInstanceFilters(args) {
311
+ const {rows: rows, filters: filters} = args, workflows = activeArm(filters.workflows), documentIds = activeArm(filters.documentIds);
312
+ return rows.filter(row => !workflows || workflows.includes(row.instance.definition)).filter(row => !documentIds || row.documents.some(doc => documentIds.includes(doc.id))).map(row => row.instance);
313
+ }
314
+
315
+ function hasTaskArms(filters) {
316
+ return (filters.assigneeIds?.length ?? 0) > 0 || filters.date !== void 0;
317
+ }
318
+
319
+ function instanceStatusMatches(instance, statuses) {
320
+ const arm = activeArm(statuses);
321
+ if (!arm) return !0;
322
+ const state = workflowEngine.terminalState(instance);
323
+ return state === "in-flight" ? arm.includes("open") : state === "completed" && arm.includes("completed");
324
+ }
325
+
326
+ function instanceIdsMatchingTaskArms(args) {
327
+ const {instances: instances, identity: identity, filters: filters, now: now} = args;
328
+ if (!hasTaskArms(filters)) return null;
329
+ const groups = deriveTaskGroups({
330
+ instances: instances,
331
+ identity: identity
332
+ }), {statuses: _workflowLevel, ...taskArms} = filters, cut = applyTaskFilters({
333
+ groups: groups,
334
+ filters: taskArms,
335
+ now: now
336
+ }), ids = /* @__PURE__ */ new Set;
337
+ for (const group of [ ...cut.withDocuments, ...cut.withoutDocuments ]) for (const row of group.rows) ids.add(row.instanceId);
338
+ return ids;
339
+ }
340
+
341
+ const NOTHING = {
342
+ titles: /* @__PURE__ */ new Map,
343
+ resolved: /* @__PURE__ */ new Set
344
+ };
345
+
346
+ function useDocPreviewTitles(documents) {
347
+ const previewStore = sanity.useDocumentPreviewStore(), schema = sanity.useSchema(), {binding: binding} = index.useWorkflowContext(), [state, setState] = react.useState(NOTHING), key = documents.map(doc => doc.id).join("|");
348
+ return react.useEffect(() => {
349
+ const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, publish = () => setState({
350
+ titles: new Map(titles),
351
+ resolved: new Set(resolved)
352
+ }), subscriptions = documents.flatMap(doc => {
353
+ const {parsed: parsedRef, local: local} = index.gdrLocality(doc.id, binding.contentResource), schemaType = schema.get(doc.type);
354
+ return !parsedRef || !local || !schemaType ? (resolved.add(doc.id), []) : [ previewStore.observeForPreview({
355
+ _type: "reference",
356
+ _ref: parsedRef.documentId
357
+ }, schemaType).subscribe({
358
+ next: event => {
359
+ resolved.add(doc.id);
360
+ const title = event.snapshot?.title;
361
+ typeof title == "string" && titles.set(doc.id, title), publish();
362
+ },
363
+ error: err => {
364
+ console.error(`[workflow-studio-plugin] preview for "${doc.id}" failed:`, err),
365
+ resolved.add(doc.id), publish();
366
+ }
367
+ }) ];
368
+ });
369
+ return publish(), () => subscriptions.forEach(sub => sub.unsubscribe());
370
+ }, [ key, previewStore, schema, binding.contentResource.id ]), state;
371
+ }
372
+
373
+ const HITS_CAP = 6;
374
+
375
+ function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
376
+ const [open, setOpen] = react.useState(!1), active = hasActiveFilters(filters);
377
+ /* @__PURE__ */
378
+ return jsxRuntime.jsx(index.DismissablePopover, {
379
+ content: open ? /* @__PURE__ */ jsxRuntime.jsx(FilterPanel, {
380
+ filters: filters,
381
+ onChange: onChange,
382
+ options: options
383
+ }) : null,
384
+ onDismiss: () => setOpen(!1),
385
+ open: open,
386
+ overflow: "visible",
387
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
388
+ disabled: open,
389
+ text: "Filter",
390
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
391
+ "aria-label": "Filter",
392
+ fontSize: 1,
393
+ icon: Filter.FilterIcon,
394
+ mode: "bleed",
395
+ onClick: () => setOpen(v => !v),
396
+ padding: 2,
397
+ selected: active || open
398
+ })
399
+ })
400
+ });
401
+ }
402
+
403
+ function FilterPanel({filters: filters, options: options, onChange: onChange}) {
404
+ const [openList, setOpenList] = react.useState(null), toggle = (key, value) => onChange(toggledValue({
405
+ filters: filters,
406
+ key: key,
407
+ value: value
408
+ })), fieldProps = category => ({
409
+ open: openList === category,
410
+ onOpen: next => setOpenList(next ? category : null)
411
+ });
412
+ /* @__PURE__ */
413
+ return jsxRuntime.jsx(ui.Box, {
414
+ padding: 3,
415
+ style: {
416
+ width: 360
417
+ },
418
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
419
+ gap: 4,
420
+ children: [
421
+ /* @__PURE__ */ jsxRuntime.jsx(DocumentField, {
422
+ ...fieldProps("document"),
423
+ activeIds: filters.documentIds ?? [],
424
+ documents: options.documents,
425
+ onToggle: id => toggle("documentIds", id)
426
+ }),
427
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
428
+ ...fieldProps("status"),
429
+ activeKeys: filters.statuses ?? [],
430
+ label: "Status",
431
+ onToggle: key => toggle("statuses", key),
432
+ pillLabel: key => STATUS_LABEL[key],
433
+ placeholder: "Any status",
434
+ rows: statusRows()
435
+ }),
436
+ /* @__PURE__ */ jsxRuntime.jsx(AssigneeField, {
437
+ ...fieldProps("assignee"),
438
+ activeIds: filters.assigneeIds ?? [],
439
+ onToggle: id => toggle("assigneeIds", id)
440
+ }),
441
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
442
+ ...fieldProps("workflow"),
443
+ activeKeys: filters.workflows ?? [],
444
+ label: "Workflow",
445
+ onToggle: key => toggle("workflows", key),
446
+ pillLabel: key => options.workflows.find(w => w.name === key)?.title ?? key,
447
+ placeholder: "Any workflow",
448
+ rows: options.workflows.map(w => ({
449
+ key: w.name,
450
+ text: w.title
451
+ }))
452
+ }),
453
+ /* @__PURE__ */ jsxRuntime.jsx(DateField, {
454
+ ...fieldProps("date"),
455
+ date: filters.date,
456
+ onChange: next => {
457
+ const {date: _cleared, ...rest} = filters;
458
+ onChange(next === void 0 ? rest : {
459
+ ...rest,
460
+ date: next
461
+ });
462
+ }
463
+ }), hasActiveFilters(filters) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
464
+ justify: "flex-end",
465
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
466
+ fontSize: 1,
467
+ mode: "ghost",
468
+ onClick: () => onChange({}),
469
+ padding: 2,
470
+ text: "Clear filters"
471
+ })
472
+ }) : null ]
473
+ })
474
+ });
475
+ }
476
+
477
+ const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
478
+ key: key,
479
+ text: STATUS_LABEL[key]
480
+ }));
481
+
482
+ function FieldLabel({text: text}) {
483
+ /* @__PURE__ */
484
+ return jsxRuntime.jsx(ui.Text, {
485
+ muted: !0,
486
+ size: 1,
487
+ weight: "medium",
488
+ children: text
489
+ });
490
+ }
491
+
492
+ function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
493
+ const rootRef = react.useRef(null), inputRef = react.useRef(null);
494
+ /* @__PURE__ */
495
+ return jsxRuntime.jsxs(ui.Stack, {
496
+ gap: 2,
497
+ children: [
498
+ /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
499
+ text: label
500
+ }),
501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
502
+ ref: rootRef,
503
+ style: {
504
+ position: "relative"
505
+ },
506
+ children: [
507
+ /* @__PURE__ */ jsxRuntime.jsx(TagBox, {
508
+ inputRef: inputRef,
509
+ label: label,
510
+ onBlur: event => {
511
+ rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
512
+ },
513
+ onClearPill: onClearPill,
514
+ onFocus: onFocus,
515
+ onQuery: onQuery,
516
+ pills: pills,
517
+ placeholder: placeholder,
518
+ query: query
519
+ }),
520
+ /* @__PURE__ */ jsxRuntime.jsx(FieldOverlay, {
521
+ open: open,
522
+ children: children
523
+ }) ]
524
+ }) ]
525
+ });
526
+ }
527
+
528
+ function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches"}) {
529
+ const [query, setQuery] = react.useState(""), q = query.trim().toLowerCase(), hits = q ? rows.filter(row => row.text.toLowerCase().includes(q)) : rows, shown = shownHits(hits, HITS_CAP);
530
+ /* @__PURE__ */
531
+ return jsxRuntime.jsx(FilterField, {
532
+ label: label,
533
+ onClearPill: onToggle,
534
+ onFocus: () => {
535
+ setQuery(""), onOpen(!0);
536
+ },
537
+ onOpen: onOpen,
538
+ onQuery: setQuery,
539
+ open: open,
540
+ pills: activeKeys.map(key => ({
541
+ key: key,
542
+ label: pillLabel(key)
543
+ })),
544
+ placeholder: placeholder,
545
+ query: open ? query : "",
546
+ children: /* @__PURE__ */ jsxRuntime.jsx(OptionsList, {
547
+ activeKeys: activeKeys,
548
+ emptyNote: emptyNote,
549
+ hits: hits,
550
+ onToggle: onToggle,
551
+ shown: shown
552
+ })
553
+ });
554
+ }
555
+
556
+ function FieldOverlay({open: open, children: children}) {
557
+ return open ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
558
+ radius: 2,
559
+ shadow: 3,
560
+ style: {
561
+ insetInline: 0,
562
+ position: "absolute",
563
+ top: "calc(100% + 4px)",
564
+ zIndex: 2
565
+ },
566
+ children: children
567
+ }) : null;
568
+ }
569
+
570
+ function useTagInputStyle() {
571
+ const {font: font} = ui.useTheme_v2(), textSize = font.text.sizes[1];
572
+ if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
573
+ return {
574
+ background: "transparent",
575
+ border: "none",
576
+ color: "var(--card-fg-color)",
577
+ fontFamily: font.text.family,
578
+ fontSize: textSize.fontSize,
579
+ outline: "none",
580
+ padding: "4px 6px",
581
+ width: "100%"
582
+ };
583
+ }
584
+
585
+ function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
586
+ const inputStyle = useTagInputStyle();
587
+ /* @__PURE__ */
588
+ return jsxRuntime.jsx(ui.Card, {
589
+ border: !0,
590
+ onClick: () => inputRef.current?.focus(),
591
+ padding: 1,
592
+ radius: 2,
593
+ style: {
594
+ cursor: "text"
595
+ },
596
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
597
+ align: "center",
598
+ gap: 1,
599
+ wrap: "wrap",
600
+ children: [ pills.map(pill => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
601
+ label: pill.label,
602
+ onClear: () => onClearPill(pill.key)
603
+ }, pill.key)),
604
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
605
+ flex: 1,
606
+ style: {
607
+ minWidth: 90
608
+ },
609
+ children: /* @__PURE__ */ jsxRuntime.jsx("input", {
610
+ "aria-label": label,
611
+ onBlur: onBlur,
612
+ onChange: event => onQuery(event.currentTarget.value),
613
+ onFocus: onFocus,
614
+ placeholder: pills.length === 0 ? placeholder : void 0,
615
+ ref: inputRef,
616
+ style: inputStyle,
617
+ value: query
618
+ })
619
+ }) ]
620
+ })
621
+ });
622
+ }
623
+
624
+ function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
625
+ /* @__PURE__ */
626
+ return jsxRuntime.jsxs(ui.Stack, {
627
+ space: 1,
628
+ children: [ hits.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
629
+ padding: 2,
630
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
631
+ text: emptyNote
632
+ })
633
+ }) : null, shown.map(row => /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
634
+ active: activeKeys.includes(row.key),
635
+ onPick: () => onToggle(row.key),
636
+ children: row.node ?? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
637
+ size: 1,
638
+ children: row.text
639
+ })
640
+ }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
641
+ padding: 2,
642
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
643
+ text: `…and ${hits.length - shown.length} more — keep typing to narrow`
644
+ })
645
+ }) : null ]
646
+ });
647
+ }
648
+
649
+ function FilterPill({label: label, onClear: onClear}) {
650
+ /* @__PURE__ */
651
+ return jsxRuntime.jsx(ui.Badge, {
652
+ fontSize: 1,
653
+ mode: "outline",
654
+ radius: "full",
655
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
656
+ align: "center",
657
+ gap: 1,
658
+ children: [ label,
659
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
660
+ "aria-label": `Clear ${label}`,
661
+ fontSize: 0,
662
+ icon: Close.CloseIcon,
663
+ mode: "bleed",
664
+ onClick: onClear,
665
+ onMouseDown: event => event.preventDefault(),
666
+ padding: 1
667
+ }) ]
668
+ })
669
+ });
670
+ }
671
+
672
+ function OptionRow({active: active, onPick: onPick, children: children}) {
673
+ /* @__PURE__ */
674
+ return jsxRuntime.jsx(ui.Card, {
675
+ as: "button",
676
+ onMouseDown: event => {
677
+ event.preventDefault(), onPick();
678
+ },
679
+ padding: 2,
680
+ radius: 2,
681
+ style: {
682
+ cursor: "pointer",
683
+ textAlign: "left"
684
+ },
685
+ tone: active ? "primary" : "default",
686
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
687
+ align: "center",
688
+ gap: 2,
689
+ children: [
690
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
691
+ flex: 1,
692
+ children: children
693
+ }), active ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
694
+ size: 1,
695
+ children: /* @__PURE__ */ jsxRuntime.jsx(Checkmark.CheckmarkIcon, {})
696
+ }) : null ]
697
+ })
698
+ });
699
+ }
700
+
701
+ function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
702
+ const {members: members, loading: loading} = index.useProjectMembers(), displays = index.useUserDisplays(activeIds), names = new Map(displays.map(d => [ d.id, d.name ])), rows = members.map(member => ({
703
+ key: member.id,
704
+ text: `${member.displayName} ${member.email ?? ""}`.trim(),
705
+ node: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
706
+ align: "center",
707
+ gap: 2,
708
+ children: [
709
+ /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.MemberAvatar, {
710
+ imageUrl: member.imageUrl,
711
+ name: member.displayName
712
+ }),
713
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
714
+ size: 1,
715
+ children: member.displayName
716
+ }) ]
717
+ })
718
+ }));
719
+ /* @__PURE__ */
720
+ return jsxRuntime.jsx(TagField, {
721
+ activeKeys: activeIds,
722
+ label: "Assignee",
723
+ onOpen: onOpen,
724
+ onToggle: onToggle,
725
+ open: open,
726
+ pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
727
+ placeholder: "Anyone",
728
+ rows: rows
729
+ });
730
+ }
731
+
732
+ function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
733
+ const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
734
+ if (documents.length === 0) return null;
735
+ const rows = documents.map(doc => ({
736
+ key: doc.id,
737
+ text: `${titles.get(doc.id) ?? ""} ${doc.type} ${workflowEngine.extractDocumentId(doc.id)}`,
738
+ node: /* @__PURE__ */ jsxRuntime.jsx(index.DocRefFace, {
739
+ gdr: doc
740
+ })
741
+ }));
742
+ /* @__PURE__ */
743
+ return jsxRuntime.jsx(TagField, {
744
+ activeKeys: activeIds,
745
+ emptyNote: "No documents match",
746
+ label: "Document",
747
+ onOpen: onOpen,
748
+ onToggle: onToggle,
749
+ open: open,
750
+ pillLabel: id => labels.get(id) ?? PENDING_LABEL,
751
+ placeholder: "Any document",
752
+ rows: rows
753
+ });
754
+ }
755
+
756
+ function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
757
+ const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
758
+ onChange(next), onOpen(!1);
759
+ };
760
+ /* @__PURE__ */
761
+ return jsxRuntime.jsx(FilterField, {
762
+ label: "Date",
763
+ onClearPill: () => onChange(void 0),
764
+ onFocus: () => onOpen(!0),
765
+ onOpen: onOpen,
766
+ onQuery: () => {},
767
+ open: open,
768
+ pills: date === void 0 ? [] : [ {
769
+ key: "date",
770
+ label: dateLabel(date)
771
+ } ],
772
+ placeholder: "Any time",
773
+ query: "",
774
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
775
+ space: 1,
776
+ children: [
777
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
778
+ active: date === "past",
779
+ onPick: () => pick("past"),
780
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
781
+ size: 1,
782
+ children: "In the past"
783
+ })
784
+ }),
785
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
786
+ active: date === "none",
787
+ onPick: () => pick("none"),
788
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
789
+ size: 1,
790
+ children: "No date"
791
+ })
792
+ }),
793
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
794
+ paddingTop: 1,
795
+ paddingX: 2,
796
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
797
+ muted: !0,
798
+ size: 1,
799
+ children: "Before…"
800
+ })
801
+ }),
802
+ /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.DatePicker, {
803
+ onSelect: next => pick({
804
+ before: endOfDayIso(next)
805
+ }),
806
+ value: pickedDate
807
+ }) ]
808
+ })
809
+ });
810
+ }
811
+
812
+ const endOfDayIso = date => endOfDay.endOfDay(date).toISOString();
813
+
814
+ function schemaTypeTitle(schema, type) {
815
+ return schema.get(type)?.title;
816
+ }
817
+
818
+ function useDocumentChipLabels(documentIds, documents) {
819
+ const schema = sanity.useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
820
+ for (const doc of activeDocs) {
821
+ if (!resolved.has(doc.id)) continue;
822
+ const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? workflowEngine.extractDocumentId(doc.id);
823
+ labels.set(doc.id, label);
824
+ }
825
+ return labels;
826
+ }
827
+
828
+ function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
829
+ const displays = index.useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = index.useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
830
+ filters: filters,
831
+ options: options,
832
+ assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
833
+ documentTitles: documentTitles
834
+ });
835
+ return chips.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
836
+ align: "center",
837
+ gap: 2,
838
+ wrap: "wrap",
839
+ children: chips.map(chip => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
840
+ label: chip.label,
841
+ onClear: () => onChange(withoutChip(filters, chip))
842
+ }, `${chip.key}:${chip.value ?? ""}`))
843
+ });
844
+ }
845
+
846
+ const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
847
+ includeCompleted: !0,
848
+ limit: INSTANCE_CAP
849
+ };
850
+
851
+ function useToolInstances() {
852
+ const {engine: engine} = index.useWorkflowContext(), {instances: instances, loading: loading, invalid: invalid} = workflowStudio.useWorkflowInstances({
853
+ engine: engine,
854
+ filter: NEWEST_INSTANCES
855
+ });
856
+ return react.useMemo(() => {
857
+ const rows = instances ?? [];
858
+ return {
859
+ inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
860
+ settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
861
+ truncated: rows.length === INSTANCE_CAP,
862
+ loading: loading,
863
+ invalid: invalid
864
+ };
865
+ }, [ instances, loading, invalid ]);
866
+ }
867
+
868
+ function toFilterRows(instances) {
869
+ return instances.map(instance => ({
870
+ instance: instance,
871
+ workflowTitle: index.instanceTitle(instance, index.definitionSnapshotOf(instance)),
872
+ documents: documentRefsOf(instance)
873
+ }));
874
+ }
875
+
876
+ function cutInstances(args) {
877
+ const {rows: rows, filters: filters, taskArmIds: taskArmIds} = args, kept = applyInstanceFilters({
878
+ rows: rows,
879
+ filters: filters
880
+ }).filter(instance => instanceStatusMatches(instance, filters.statuses));
881
+ return taskArmIds === null ? kept : kept.filter(instance => taskArmIds.has(instance._id));
882
+ }
883
+
884
+ function WorkflowsDashboard({instances: instances, onOpenInstance: onOpenInstance}) {
885
+ const {inFlight: inFlight, settled: settled, truncated: truncated, loading: loading} = instances, [filters, setFilters] = react.useState({}), identity = index.useAssignmentIdentity(), inFlightRows = react.useMemo(() => toFilterRows(inFlight), [ inFlight ]), settledRows = react.useMemo(() => toFilterRows(settled), [ settled ]), options = react.useMemo(() => instanceFilterOptions([ ...inFlightRows, ...settledRows ]), [ inFlightRows, settledRows ]), taskArmIds = react.useMemo(() => instanceIdsMatchingTaskArms({
886
+ instances: [ ...inFlight, ...settled ],
887
+ identity: identity,
888
+ filters: filters,
889
+ now: /* @__PURE__ */ new Date
890
+ }), [ inFlight, settled, identity, filters ]), visibleInFlight = react.useMemo(() => cutInstances({
891
+ rows: inFlightRows,
892
+ filters: filters,
893
+ taskArmIds: taskArmIds
894
+ }), [ inFlightRows, filters, taskArmIds ]), visibleSettled = react.useMemo(() => cutInstances({
895
+ rows: settledRows,
896
+ filters: filters,
897
+ taskArmIds: taskArmIds
898
+ }), [ settledRows, filters, taskArmIds ]);
899
+ return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
900
+ label: "Loading workflows…"
901
+ }) : inFlight.length === 0 && settled.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
902
+ text: "No workflows yet — start one with “New workflow”"
903
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
904
+ gap: 2,
905
+ children: [
906
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
907
+ align: "center",
908
+ gap: 2,
909
+ justify: "flex-end",
910
+ children: [
911
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
912
+ filters: filters,
913
+ onChange: setFilters,
914
+ options: options
915
+ }),
916
+ /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
917
+ filters: filters,
918
+ onChange: setFilters,
919
+ options: options
920
+ }) ]
921
+ }), visibleInFlight.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
922
+ text: "No running workflows match"
923
+ }) : /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
924
+ instances: visibleInFlight,
925
+ onOpenInstance: onOpenInstance
926
+ }),
927
+ /* @__PURE__ */ jsxRuntime.jsx(SettledRows, {
928
+ defaultShown: filters.statuses?.includes("completed") ?? !1,
929
+ instances: visibleSettled,
930
+ onOpenInstance: onOpenInstance
931
+ }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
932
+ text: `Showing the latest ${INSTANCE_CAP} workflows.`
933
+ }) : null ]
934
+ });
935
+ }
936
+
937
+ function InstanceRows({instances: instances, onOpenInstance: onOpenInstance}) {
938
+ /* @__PURE__ */
939
+ return jsxRuntime.jsx(jsxRuntime.Fragment, {
940
+ children: instances.map(instance => /* @__PURE__ */ jsxRuntime.jsx(InstanceRow, {
941
+ instance: instance,
942
+ onOpen: () => onOpenInstance(instance)
943
+ }, instance._id))
944
+ });
945
+ }
946
+
947
+ function SettledRows({instances: instances, onOpenInstance: onOpenInstance, defaultShown: defaultShown}) {
948
+ const [toggled, setToggled] = react.useState(void 0), shown = toggled ?? defaultShown;
949
+ return instances.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
950
+ gap: 2,
951
+ marginTop: 3,
952
+ children: [
953
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
954
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
955
+ fontSize: 1,
956
+ icon: shown ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
957
+ mode: "bleed",
958
+ onClick: () => setToggled(!shown),
959
+ padding: 2,
960
+ text: `${shown ? "Hide" : "Show"} ${instances.length} finished`
961
+ })
962
+ }), shown ? /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
963
+ instances: instances,
964
+ onOpenInstance: onOpenInstance
965
+ }) : null ]
966
+ });
967
+ }
968
+
969
+ function InstanceRow({instance: instance, onOpen: onOpen}) {
970
+ const definition = react.useMemo(() => index.definitionSnapshotOf(instance), [ instance ]);
971
+ /* @__PURE__ */
972
+ return jsxRuntime.jsxs(ui.Flex, {
973
+ align: "center",
974
+ gap: 2,
975
+ children: [
976
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
977
+ as: "button",
978
+ flex: 1,
979
+ onClick: onOpen,
980
+ padding: 3,
981
+ paddingLeft: 4,
982
+ radius: 2,
983
+ style: {
984
+ cursor: "pointer",
985
+ textAlign: "left"
986
+ },
987
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
988
+ align: "center",
989
+ gap: 3,
990
+ children: [
991
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
992
+ size: 1,
993
+ weight: "medium",
994
+ children: index.instanceTitle(instance, definition)
995
+ }),
996
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
997
+ muted: !0,
998
+ size: 1,
999
+ children: [ "Started ", index.formatDate(instance.startedAt) ]
1000
+ }),
1001
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1002
+ flex: 1
1003
+ }),
1004
+ /* @__PURE__ */ jsxRuntime.jsx(index.AbortedBadge, {
1005
+ instance: instance
1006
+ }) ]
1007
+ })
1008
+ }),
1009
+ /* @__PURE__ */ jsxRuntime.jsx(DocRefChips, {
1010
+ instance: instance
1011
+ }) ]
1012
+ });
1013
+ }
1014
+
1015
+ function SpinnerSlot({busy: busy}) {
1016
+ /* @__PURE__ */
1017
+ return jsxRuntime.jsx(ui.Flex, {
1018
+ align: "center",
1019
+ flex: "none",
1020
+ justify: "center",
1021
+ style: {
1022
+ width: 19,
1023
+ height: 19
1024
+ },
1025
+ children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
1026
+ muted: !0
1027
+ }) : null
1028
+ });
1029
+ }
1030
+
1031
+ function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
1032
+ return index.instanceNotice(entry) || /* @__PURE__ */ jsxRuntime.jsx(index.StaleLock, {
1033
+ stale: index.isEvaluationStale(entry),
1034
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1035
+ children: [
1036
+ /* @__PURE__ */ jsxRuntime.jsx(index.ActivitiesList, {
1037
+ entry: entry,
1038
+ onOpenActivity: onOpenActivity
1039
+ }),
1040
+ /* @__PURE__ */ jsxRuntime.jsx(index.TodoItemsList, {
1041
+ entry: entry
1042
+ }) ]
1043
+ })
1044
+ });
1045
+ }
1046
+
1047
+ function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
1048
+ const entry = index.useWorkflowInstanceEntry(instanceId), [graceOver, setGraceOver] = react.useState(!1);
1049
+ return react.useEffect(() => {
1050
+ const timer = setTimeout(() => setGraceOver(!0), 2500);
1051
+ return () => clearTimeout(timer);
1052
+ }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1053
+ padding: 4,
1054
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1055
+ invalid: entry.invalid
1056
+ })
1057
+ }) : entry ? /* @__PURE__ */ jsxRuntime.jsx(InstanceDetailPanel, {
1058
+ entry: entry,
1059
+ onBack: onBack
1060
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1061
+ padding: 4,
1062
+ children: graceOver ? /* @__PURE__ */ jsxRuntime.jsx(NotFound, {
1063
+ id: instanceId,
1064
+ onBack: onBack
1065
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1066
+ label: "Loading workflow…"
1067
+ })
1068
+ });
1069
+ }
1070
+
1071
+ function NotFound({id: id, onBack: onBack}) {
1072
+ /* @__PURE__ */
1073
+ return jsxRuntime.jsxs(ui.Stack, {
1074
+ gap: 4,
1075
+ children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1077
+ muted: !0,
1078
+ size: 1,
1079
+ children: [ "No workflow with id “", id, "” — it may have been deleted." ]
1080
+ }),
1081
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1082
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1083
+ fontSize: 1,
1084
+ icon: ArrowLeft.ArrowLeftIcon,
1085
+ mode: "ghost",
1086
+ onClick: onBack,
1087
+ padding: 2,
1088
+ text: "Back to workflows"
1089
+ })
1090
+ }) ]
1091
+ });
1092
+ }
1093
+
1094
+ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
1095
+ const definition = index.useDefinition(entry), [openActivity, setOpenActivity] = react.useState(null), {instance: instance} = entry;
1096
+ return react.useEffect(() => {
1097
+ openActivity && index.openActivityGone({
1098
+ entry: entry,
1099
+ target: openActivity
1100
+ }) && setOpenActivity(null);
1101
+ }, [ entry, openActivity ]), /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1102
+ direction: "column",
1103
+ height: "fill",
1104
+ overflow: "hidden",
1105
+ children: [
1106
+ /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, {
1107
+ definition: definition,
1108
+ entry: entry,
1109
+ onBack: onBack
1110
+ }),
1111
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1112
+ flex: 1,
1113
+ overflow: "auto",
1114
+ paddingX: 4,
1115
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1116
+ gap: 4,
1117
+ paddingBottom: 5,
1118
+ children: [
1119
+ /* @__PURE__ */ jsxRuntime.jsx(StudioDocumentsSection, {
1120
+ instance: instance
1121
+ }),
1122
+ /* @__PURE__ */ jsxRuntime.jsx(StageDiagramSection, {
1123
+ definition: definition,
1124
+ entry: entry
1125
+ }),
1126
+ /* @__PURE__ */ jsxRuntime.jsx(index.GroupHeading, {
1127
+ title: "All tasks"
1128
+ }),
1129
+ /* @__PURE__ */ jsxRuntime.jsx(InstanceTaskLists, {
1130
+ entry: entry,
1131
+ onOpenActivity: name => setOpenActivity({
1132
+ stage: instance.currentStage,
1133
+ activityName: name
1134
+ })
1135
+ }) ]
1136
+ })
1137
+ }), openActivity ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1138
+ activityName: openActivity.activityName,
1139
+ breadcrumb: index.instanceBreadcrumb(instance, definition),
1140
+ definition: definition,
1141
+ entry: entry,
1142
+ onClose: () => setOpenActivity(null)
1143
+ }) : null ]
1144
+ });
1145
+ }
1146
+
1147
+ function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
1148
+ const {instance: instance, ready: ready} = entry;
1149
+ /* @__PURE__ */
1150
+ return jsxRuntime.jsx(ui.Card, {
1151
+ flex: "none",
1152
+ paddingX: 4,
1153
+ paddingY: 3,
1154
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1155
+ align: "center",
1156
+ gap: 3,
1157
+ wrap: "wrap",
1158
+ children: [
1159
+ /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1160
+ text: "Back to workflows",
1161
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1162
+ "aria-label": "Back to workflows",
1163
+ fontSize: 1,
1164
+ icon: ArrowLeft.ArrowLeftIcon,
1165
+ mode: "bleed",
1166
+ onClick: onBack,
1167
+ padding: 2
1168
+ })
1169
+ }),
1170
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1171
+ gap: 2,
1172
+ children: [
1173
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1174
+ size: 1,
1175
+ weight: "semibold",
1176
+ children: index.instanceTitle(instance, definition)
1177
+ }),
1178
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1179
+ muted: !0,
1180
+ size: 1,
1181
+ children: [ "Started ", index.formatDate(instance.startedAt) ]
1182
+ }) ]
1183
+ }), workflowEngine.terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsxRuntime.jsx(index.StageChip, {
1184
+ title: index.stageTitle(definition, instance.currentStage)
1185
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.AbortedBadge, {
1186
+ instance: instance
1187
+ }),
1188
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
1189
+ busy: !ready || index.isEvaluationStale(entry)
1190
+ }) ]
1191
+ })
1192
+ });
1193
+ }
1194
+
1195
+ function StudioDocumentsSection({instance: instance}) {
1196
+ const refs = documentRefsOf(instance);
1197
+ return refs.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1198
+ gap: 3,
1199
+ marginTop: 3,
1200
+ children: [
1201
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1202
+ muted: !0,
1203
+ size: 1,
1204
+ weight: "medium",
1205
+ children: refs.length === 1 ? "Studio document" : "Studio documents"
1206
+ }),
1207
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1208
+ gap: 2,
1209
+ children: refs.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
1210
+ gdr: ref
1211
+ }, ref.id))
1212
+ }) ]
1213
+ });
1214
+ }
1215
+
1216
+ function StageDiagramSection({entry: entry, definition: definition}) {
1217
+ const {instance: instance} = entry;
1218
+ return definition ?
1219
+ /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
1220
+ currentStage: workflowEngine.terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
1221
+ definition: definition,
1222
+ evaluation: entry.evaluation,
1223
+ explain: !0,
1224
+ guardCount: entry.guards?.length,
1225
+ height: 340,
1226
+ history: instance.history
1227
+ }, instance._id) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1228
+ border: !0,
1229
+ marginTop: 3,
1230
+ padding: 3,
1231
+ radius: 2,
1232
+ tone: "caution",
1233
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1234
+ muted: !0,
1235
+ size: 1,
1236
+ children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
1237
+ })
1238
+ });
1239
+ }
1240
+
1241
+ function useStartableDefinitions() {
1242
+ const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(void 0), [error, setError] = react.useState(void 0);
1243
+ return react.useEffect(() => {
1244
+ let cancelled = !1;
1245
+ return (async () => {
1246
+ const rows = await index.readDeployedDefinitions(engine), startable = workflowEngine.latestDeployedDefinitions(rows).filter(row => workflowEngine.isStartableDefinition(row)).map(row => ({
1247
+ name: row.name,
1248
+ title: row.title ?? row.name,
1249
+ description: row.description
1250
+ }));
1251
+ cancelled || (setDefinitions(startable), setError(void 0));
1252
+ })().catch(err => {
1253
+ cancelled || setError(index.describeError(err));
1254
+ }), () => {
1255
+ cancelled = !0;
1256
+ };
1257
+ }, [ engine ]), {
1258
+ definitions: definitions,
1259
+ error: error
1260
+ };
1261
+ }
1262
+
1263
+ function NewWorkflowButton() {
1264
+ const {openStartDialog: openStartDialog} = index.useWorkflowContext(), [open, setOpen] = react.useState(!1);
1265
+ /* @__PURE__ */
1266
+ return jsxRuntime.jsx(index.DismissablePopover, {
1267
+ content: open ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionPickerPanel, {
1268
+ onPick: definition => {
1269
+ setOpen(!1), openStartDialog({
1270
+ definition: definition.name,
1271
+ label: definition.title
1272
+ });
1273
+ }
1274
+ }) : null,
1275
+ onDismiss: () => setOpen(!1),
1276
+ open: open,
1277
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1278
+ fontSize: 1,
1279
+ icon: Add.AddIcon,
1280
+ onClick: () => setOpen(v => !v),
1281
+ padding: 2,
1282
+ selected: open,
1283
+ text: "New workflow",
1284
+ tone: "primary"
1285
+ })
1286
+ });
1287
+ }
1288
+
1289
+ function DefinitionPickerPanel({onPick: onPick}) {
1290
+ const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = react.useState(""), visible = react.useMemo(() => {
1291
+ const q = query.trim().toLowerCase();
1292
+ return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
1293
+ }, [ definitions, query ]);
1294
+ /* @__PURE__ */
1295
+ return jsxRuntime.jsx(ui.Box, {
1296
+ padding: 2,
1297
+ style: {
1298
+ width: 340
1299
+ },
1300
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1301
+ gap: 2,
1302
+ children: [
1303
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1304
+ autoFocus: !0,
1305
+ fontSize: 1,
1306
+ icon: Search.SearchIcon,
1307
+ onChange: event => setQuery(event.currentTarget.value),
1308
+ placeholder: "Filter workflows…",
1309
+ value: query
1310
+ }), error ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1311
+ text: `Could not load the deployed workflows: ${error}`
1312
+ }) : null, !definitions && !error ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1313
+ label: "Loading workflows…"
1314
+ }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1315
+ text: "No startable workflows are deployed"
1316
+ }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1317
+ text: "No workflows match"
1318
+ }) : null,
1319
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1320
+ gap: 1,
1321
+ style: {
1322
+ maxHeight: 320,
1323
+ overflowY: "auto"
1324
+ },
1325
+ children: visible.map(definition => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1326
+ as: "button",
1327
+ onClick: () => onPick(definition),
1328
+ padding: 3,
1329
+ radius: 2,
1330
+ style: {
1331
+ cursor: "pointer",
1332
+ textAlign: "left"
1333
+ },
1334
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1335
+ gap: 2,
1336
+ children: [
1337
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1338
+ size: 1,
1339
+ weight: "medium",
1340
+ children: definition.title
1341
+ }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1342
+ muted: !0,
1343
+ size: 1,
1344
+ textOverflow: "ellipsis",
1345
+ children: definition.description
1346
+ }) : null ]
1347
+ })
1348
+ }, definition.name))
1349
+ }) ]
1350
+ })
1351
+ });
1352
+ }
1353
+
1354
+ function ToolActivityDialog({target: target, onClose: onClose}) {
1355
+ const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
1356
+ return entry && (resolvedOnce.current = !0), react.useEffect(() => {
1357
+ resolvedOnce.current && index.openActivityGone({
1358
+ entry: entry,
1359
+ target: target
1360
+ }) && onClose();
1361
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1362
+ header: "Workflow unavailable",
1363
+ id: "workflow-activity-detail",
1364
+ onClose: onClose,
1365
+ width: 1,
1366
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1367
+ padding: 4,
1368
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1369
+ invalid: entry.invalid
1370
+ })
1371
+ })
1372
+ }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1373
+ activityName: target.activityName,
1374
+ breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
1375
+ definition: definition,
1376
+ document: documentRefsOf(entry.instance)[0],
1377
+ entry: entry,
1378
+ onClose: onClose
1379
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1380
+ header: "Loading activity…",
1381
+ id: "workflow-activity-detail",
1382
+ onClose: onClose,
1383
+ width: 1,
1384
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1385
+ padding: 4,
1386
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1387
+ label: "Fetching the live workflow state…"
1388
+ })
1389
+ })
1390
+ });
1391
+ }
1392
+
1393
+ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1394
+ return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1395
+ text: "No tasks match — adjust the filters, or start a workflow"
1396
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1397
+ gap: 2,
1398
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(DocumentTaskGroup, {
1399
+ group: group,
1400
+ onOpenTask: onOpenTask,
1401
+ onOpenWorkflow: onOpenWorkflow
1402
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1403
+ header: /* @__PURE__ */ jsxRuntime.jsx(GroupTitle, {
1404
+ text: "Workflows without Studio documents"
1405
+ }),
1406
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1407
+ gap: 2,
1408
+ paddingLeft: 2,
1409
+ children: groups.withoutDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1410
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1411
+ children: [
1412
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1413
+ muted: !0,
1414
+ size: 1,
1415
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.StageGlyph, {})
1416
+ }),
1417
+ /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1418
+ group: group
1419
+ }),
1420
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1421
+ flex: 1
1422
+ }),
1423
+ /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1424
+ group: group,
1425
+ onOpenWorkflow: onOpenWorkflow
1426
+ }) ]
1427
+ }),
1428
+ children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1429
+ onOpenTask: onOpenTask,
1430
+ rows: group.rows
1431
+ })
1432
+ }, group.key))
1433
+ })
1434
+ }) : null ]
1435
+ });
1436
+ }
1437
+
1438
+ function GroupTitle({text: text}) {
1439
+ /* @__PURE__ */
1440
+ return jsxRuntime.jsx(ui.Text, {
1441
+ size: 1,
1442
+ weight: "semibold",
1443
+ children: text
1444
+ });
1445
+ }
1446
+
1447
+ function WorkflowBadgeTitle({group: group}) {
1448
+ const badge = /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1449
+ fontSize: 1,
1450
+ mode: "outline",
1451
+ children: group.workflowTitle
1452
+ });
1453
+ return group.workflowDescription ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1454
+ text: group.workflowDescription,
1455
+ children: badge
1456
+ }) : badge;
1457
+ }
1458
+
1459
+ function CollapsibleGroup({header: header, children: children}) {
1460
+ const [open, setOpen] = react.useState(!0);
1461
+ /* @__PURE__ */
1462
+ return jsxRuntime.jsxs(ui.Stack, {
1463
+ gap: 2,
1464
+ children: [
1465
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1466
+ paddingX: 2,
1467
+ paddingY: 2,
1468
+ radius: 2,
1469
+ tone: "transparent",
1470
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1471
+ align: "center",
1472
+ gap: 2,
1473
+ children: [
1474
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1475
+ "aria-label": open ? "Collapse" : "Expand",
1476
+ fontSize: 1,
1477
+ icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
1478
+ mode: "bleed",
1479
+ onClick: () => setOpen(v => !v),
1480
+ padding: 1
1481
+ }), header ]
1482
+ })
1483
+ }), open ? children : null ]
1484
+ });
1485
+ }
1486
+
1487
+ function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1488
+ const document = group.document;
1489
+ return document ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1490
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1491
+ children: [
1492
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1493
+ flex: "none",
1494
+ style: {
1495
+ maxWidth: "60%"
1496
+ },
1497
+ children: /* @__PURE__ */ jsxRuntime.jsx(RefChips, {
1498
+ max: 2,
1499
+ refs: group.documents
1500
+ })
1501
+ }),
1502
+ /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1503
+ group: group
1504
+ }),
1505
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1506
+ flex: 1
1507
+ }),
1508
+ /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1509
+ document: document,
1510
+ group: group,
1511
+ onOpenWorkflow: onOpenWorkflow
1512
+ }) ]
1513
+ }),
1514
+ children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1515
+ onOpenTask: onOpenTask,
1516
+ rows: group.rows
1517
+ })
1518
+ }) : null;
1519
+ }
1520
+
1521
+ function GroupMenu({group: group, document: document, onOpenWorkflow: onOpenWorkflow}) {
1522
+ const router$1 = router.useRouter(), state = index.useDocLink(document);
1523
+ /* @__PURE__ */
1524
+ return jsxRuntime.jsx(index.HintedMenuButton, {
1525
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1526
+ "aria-label": "Group actions",
1527
+ fontSize: 1,
1528
+ icon: EllipsisHorizontal.EllipsisHorizontalIcon,
1529
+ mode: "bleed",
1530
+ padding: 2
1531
+ }),
1532
+ hint: "Group actions",
1533
+ id: `task-group-menu-${group.key}`,
1534
+ menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, {
1535
+ children: [
1536
+ /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1537
+ onClick: () => onOpenWorkflow(group.instanceId),
1538
+ text: "View workflow"
1539
+ }), state.kind === "linked" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1540
+ onClick: () => router$1.navigateIntent("edit", {
1541
+ id: state.bareId
1542
+ }),
1543
+ text: "Open document"
1544
+ }) : null ]
1545
+ }),
1546
+ popover: {
1547
+ portal: !0
1548
+ }
1549
+ });
1550
+ }
1551
+
1552
+ function TaskRows({rows: rows, onOpenTask: onOpenTask}) {
1553
+ /* @__PURE__ */
1554
+ return jsxRuntime.jsx(ui.Stack, {
1555
+ children: rows.map(row => /* @__PURE__ */ jsxRuntime.jsx(TaskRowButton, {
1556
+ onOpen: () => onOpenTask({
1557
+ instanceId: row.instanceId,
1558
+ stage: row.stage,
1559
+ activityName: row.activityName
1560
+ }),
1561
+ row: row
1562
+ }, `${row.instanceId}:${row.activityName}`))
1563
+ });
1564
+ }
1565
+
1566
+ function RowDate({dates: dates}) {
1567
+ const first = dates.find(value => index.parseStoredDateValue(value) !== void 0);
1568
+ return first === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1569
+ muted: !0,
1570
+ size: 1,
1571
+ children: index.formatDate(first)
1572
+ });
1573
+ }
1574
+
1575
+ function RowTitle({row: row}) {
1576
+ const title = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1577
+ size: 1,
1578
+ style: row.open ? void 0 : {
1579
+ opacity: .66
1580
+ },
1581
+ weight: "medium",
1582
+ children: row.title
1583
+ });
1584
+ return row.description ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1585
+ text: row.description,
1586
+ children: title
1587
+ }) : title;
1588
+ }
1589
+
1590
+ function RowBreadcrumb({breadcrumb: breadcrumb}) {
1591
+ return breadcrumb ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1592
+ align: "center",
1593
+ gap: 2,
1594
+ children: [
1595
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1596
+ muted: !0,
1597
+ size: 1,
1598
+ children: breadcrumb
1599
+ }),
1600
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1601
+ muted: !0,
1602
+ size: 1,
1603
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight.ChevronRightIcon, {})
1604
+ }) ]
1605
+ }) : null;
1606
+ }
1607
+
1608
+ function TaskRowButton({row: row, onOpen: onOpen}) {
1609
+ /* @__PURE__ */
1610
+ return jsxRuntime.jsx(ui.Card, {
1611
+ as: "button",
1612
+ onClick: onOpen,
1613
+ padding: 3,
1614
+ paddingLeft: 4,
1615
+ radius: 2,
1616
+ style: {
1617
+ cursor: "pointer",
1618
+ textAlign: "left"
1619
+ },
1620
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1621
+ align: "center",
1622
+ gap: 3,
1623
+ children: [
1624
+ /* @__PURE__ */ jsxRuntime.jsx(index.ActivityStatusIcon, {
1625
+ status: row.status
1626
+ }),
1627
+ /* @__PURE__ */ jsxRuntime.jsx(RowBreadcrumb, {
1628
+ breadcrumb: row.breadcrumb
1629
+ }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx(RowTitle, {
1631
+ row: row
1632
+ }),
1633
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1634
+ fontSize: 1,
1635
+ mode: "outline",
1636
+ tone: "default",
1637
+ children: row.stageTitle
1638
+ }),
1639
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1640
+ flex: 1
1641
+ }),
1642
+ /* @__PURE__ */ jsxRuntime.jsx(RowDate, {
1643
+ dates: row.dates
1644
+ }), row.assigneeIds.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.UserAvatarGroup, {
1645
+ ids: row.assigneeIds
1646
+ }) : null ]
1647
+ })
1648
+ });
1649
+ }
1650
+
1651
+ function TasksTab({instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated}) {
1652
+ const [segment, setSegment] = react.useState("mine"), [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
1653
+ instances: instances,
1654
+ identity: identity
1655
+ }), [ instances, identity ]), options = react.useMemo(() => taskFilterOptions(groups), [ groups ]), visible = react.useMemo(() => {
1656
+ const segmented = segment === "mine" ? onlyMyTasks(groups) : groups;
1657
+ return applyTaskFilters({
1658
+ groups: segmented,
1659
+ filters: filters,
1660
+ now: /* @__PURE__ */ new Date
1661
+ });
1662
+ }, [ groups, segment, filters ]);
1663
+ return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1664
+ label: "Loading tasks…"
1665
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1666
+ children: [
1667
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1668
+ align: "center",
1669
+ gap: 2,
1670
+ paddingBottom: 3,
1671
+ children: [
1672
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1673
+ gap: 1,
1674
+ children: [
1675
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1676
+ "aria-controls": "workflow-tasks-panel",
1677
+ fontSize: 1,
1678
+ id: "workflow-tasks-mine",
1679
+ label: "My tasks",
1680
+ onClick: () => setSegment("mine"),
1681
+ padding: 2,
1682
+ selected: segment === "mine"
1683
+ }),
1684
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1685
+ "aria-controls": "workflow-tasks-panel",
1686
+ fontSize: 1,
1687
+ id: "workflow-tasks-all",
1688
+ label: "All tasks",
1689
+ onClick: () => setSegment("all"),
1690
+ padding: 2,
1691
+ selected: segment === "all"
1692
+ }) ]
1693
+ }),
1694
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1695
+ flex: 1
1696
+ }),
1697
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
1698
+ filters: filters,
1699
+ onChange: setFilters,
1700
+ options: options
1701
+ }),
1702
+ /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
1703
+ filters: filters,
1704
+ onChange: setFilters,
1705
+ options: options
1706
+ }) ]
1707
+ }),
1708
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1709
+ id: "workflow-tasks-panel",
1710
+ children: [
1711
+ /* @__PURE__ */ jsxRuntime.jsx(TaskGroupList, {
1712
+ groups: visible,
1713
+ onOpenTask: onOpenTask,
1714
+ onOpenWorkflow: onOpenWorkflow
1715
+ }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1716
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1717
+ }) : null ]
1718
+ }) ]
1719
+ });
1720
+ }
1721
+
1722
+ function WorkflowsToolRoot() {
1723
+ const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), instanceId = typeof router$1.state.instanceId == "string" ? router$1.state.instanceId : void 0;
1724
+ return instanceId ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
1725
+ instanceId: instanceId,
1726
+ onBack: () => router$1.navigate({})
1727
+ }, instanceId) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsHome, {
1728
+ onOpenInstance: instance => {
1729
+ typeof instance != "string" && seedInstance(instance), router$1.navigate({
1730
+ instanceId: typeof instance == "string" ? instance : instance._id
1731
+ });
1732
+ }
1733
+ });
1734
+ }
1735
+
1736
+ function ToolPanel({identity: identity, instances: instances, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab}) {
1737
+ return instances.invalid ? /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1738
+ invalid: instances.invalid
1739
+ }) : tab === "tasks" ? /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
1740
+ identity: identity,
1741
+ instances: [ ...instances.inFlight, ...instances.settled ],
1742
+ loading: instances.loading,
1743
+ onOpenTask: onOpenTask,
1744
+ onOpenWorkflow: onOpenInstance,
1745
+ truncated: instances.truncated
1746
+ }) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsDashboard, {
1747
+ instances: instances,
1748
+ onOpenInstance: onOpenInstance
1749
+ });
1750
+ }
1751
+
1752
+ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
1753
+ const [tab, setTab] = react.useState("tasks"), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null);
1754
+ /* @__PURE__ */
1755
+ return jsxRuntime.jsxs(ui.Card, {
1756
+ height: "fill",
1757
+ overflow: "auto",
1758
+ children: [
1759
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1760
+ padding: 4,
1761
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, {
1762
+ width: 2,
1763
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1764
+ gap: 4,
1765
+ children: [
1766
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1767
+ align: "center",
1768
+ gap: 3,
1769
+ children: [
1770
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1771
+ size: 2,
1772
+ weight: "semibold",
1773
+ children: "Workflows"
1774
+ }),
1775
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1776
+ flex: 1
1777
+ }),
1778
+ /* @__PURE__ */ jsxRuntime.jsx(NewWorkflowButton, {}) ]
1779
+ }),
1780
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1781
+ gap: 1,
1782
+ children: [
1783
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1784
+ "aria-controls": "workflows-tool-panel",
1785
+ fontSize: 1,
1786
+ id: "workflows-tool-tab-tasks",
1787
+ label: "Tasks",
1788
+ onClick: () => setTab("tasks"),
1789
+ padding: 2,
1790
+ selected: tab === "tasks"
1791
+ }),
1792
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1793
+ "aria-controls": "workflows-tool-panel",
1794
+ fontSize: 1,
1795
+ id: "workflows-tool-tab-workflows",
1796
+ label: "All workflows",
1797
+ onClick: () => setTab("workflows"),
1798
+ padding: 2,
1799
+ selected: tab === "workflows"
1800
+ }) ]
1801
+ }),
1802
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1803
+ id: "workflows-tool-panel",
1804
+ children: /* @__PURE__ */ jsxRuntime.jsx(ToolPanel, {
1805
+ identity: identity,
1806
+ instances: instances,
1807
+ onOpenInstance: target => {
1808
+ if (typeof target != "string") return onOpenInstance(target);
1809
+ const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1810
+ onOpenInstance(held ?? target);
1811
+ },
1812
+ onOpenTask: setOpenTask,
1813
+ tab: tab
1814
+ })
1815
+ }) ]
1816
+ })
1817
+ })
1818
+ }), openTask ? /* @__PURE__ */ jsxRuntime.jsx(ToolActivityDialog, {
1819
+ onClose: () => setOpenTask(null),
1820
+ target: openTask
1821
+ }) : null ]
1822
+ });
1823
+ }
1824
+
1825
+ exports.default = WorkflowsToolRoot;