@sanity/workflow-studio-plugin 0.1.0 → 0.3.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,1826 @@
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"), 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 parsedRef = workflowEngine.tryParseGdr(doc.id), resource = parsedRef ? workflowEngine.resourceFromParsed(parsedRef) : void 0, local = index.isLocalContentResource(resource, 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(ui.Button, {
388
+ "aria-label": "Filter",
389
+ fontSize: 1,
390
+ icon: Filter.FilterIcon,
391
+ mode: "bleed",
392
+ onClick: () => setOpen(v => !v),
393
+ padding: 2,
394
+ selected: active || open
395
+ })
396
+ });
397
+ }
398
+
399
+ function FilterPanel({filters: filters, options: options, onChange: onChange}) {
400
+ const [openList, setOpenList] = react.useState(null), toggle = (key, value) => onChange(toggledValue({
401
+ filters: filters,
402
+ key: key,
403
+ value: value
404
+ })), fieldProps = category => ({
405
+ open: openList === category,
406
+ onOpen: next => setOpenList(next ? category : null)
407
+ });
408
+ /* @__PURE__ */
409
+ return jsxRuntime.jsx(ui.Box, {
410
+ padding: 3,
411
+ style: {
412
+ width: 360
413
+ },
414
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
415
+ gap: 4,
416
+ children: [
417
+ /* @__PURE__ */ jsxRuntime.jsx(DocumentField, {
418
+ ...fieldProps("document"),
419
+ activeIds: filters.documentIds ?? [],
420
+ documents: options.documents,
421
+ onToggle: id => toggle("documentIds", id)
422
+ }),
423
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
424
+ ...fieldProps("status"),
425
+ activeKeys: filters.statuses ?? [],
426
+ label: "Status",
427
+ onToggle: key => toggle("statuses", key),
428
+ pillLabel: key => STATUS_LABEL[key],
429
+ placeholder: "Any status",
430
+ rows: statusRows()
431
+ }),
432
+ /* @__PURE__ */ jsxRuntime.jsx(AssigneeField, {
433
+ ...fieldProps("assignee"),
434
+ activeIds: filters.assigneeIds ?? [],
435
+ onToggle: id => toggle("assigneeIds", id)
436
+ }),
437
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
438
+ ...fieldProps("workflow"),
439
+ activeKeys: filters.workflows ?? [],
440
+ label: "Workflow",
441
+ onToggle: key => toggle("workflows", key),
442
+ pillLabel: key => options.workflows.find(w => w.name === key)?.title ?? key,
443
+ placeholder: "Any workflow",
444
+ rows: options.workflows.map(w => ({
445
+ key: w.name,
446
+ text: w.title
447
+ }))
448
+ }),
449
+ /* @__PURE__ */ jsxRuntime.jsx(DateField, {
450
+ ...fieldProps("date"),
451
+ date: filters.date,
452
+ onChange: next => {
453
+ const {date: _cleared, ...rest} = filters;
454
+ onChange(next === void 0 ? rest : {
455
+ ...rest,
456
+ date: next
457
+ });
458
+ }
459
+ }), hasActiveFilters(filters) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
460
+ justify: "flex-end",
461
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
462
+ fontSize: 1,
463
+ mode: "ghost",
464
+ onClick: () => onChange({}),
465
+ padding: 2,
466
+ text: "Clear filters"
467
+ })
468
+ }) : null ]
469
+ })
470
+ });
471
+ }
472
+
473
+ const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
474
+ key: key,
475
+ text: STATUS_LABEL[key]
476
+ }));
477
+
478
+ function FieldLabel({text: text}) {
479
+ /* @__PURE__ */
480
+ return jsxRuntime.jsx(ui.Text, {
481
+ muted: !0,
482
+ size: 1,
483
+ weight: "medium",
484
+ children: text
485
+ });
486
+ }
487
+
488
+ function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
489
+ const rootRef = react.useRef(null), inputRef = react.useRef(null);
490
+ /* @__PURE__ */
491
+ return jsxRuntime.jsxs(ui.Stack, {
492
+ gap: 2,
493
+ children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
495
+ text: label
496
+ }),
497
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
498
+ ref: rootRef,
499
+ style: {
500
+ position: "relative"
501
+ },
502
+ children: [
503
+ /* @__PURE__ */ jsxRuntime.jsx(TagBox, {
504
+ inputRef: inputRef,
505
+ label: label,
506
+ onBlur: event => {
507
+ rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
508
+ },
509
+ onClearPill: onClearPill,
510
+ onFocus: onFocus,
511
+ onQuery: onQuery,
512
+ pills: pills,
513
+ placeholder: placeholder,
514
+ query: query
515
+ }),
516
+ /* @__PURE__ */ jsxRuntime.jsx(FieldOverlay, {
517
+ open: open,
518
+ children: children
519
+ }) ]
520
+ }) ]
521
+ });
522
+ }
523
+
524
+ function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches."}) {
525
+ 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);
526
+ /* @__PURE__ */
527
+ return jsxRuntime.jsx(FilterField, {
528
+ label: label,
529
+ onClearPill: onToggle,
530
+ onFocus: () => {
531
+ setQuery(""), onOpen(!0);
532
+ },
533
+ onOpen: onOpen,
534
+ onQuery: setQuery,
535
+ open: open,
536
+ pills: activeKeys.map(key => ({
537
+ key: key,
538
+ label: pillLabel(key)
539
+ })),
540
+ placeholder: placeholder,
541
+ query: open ? query : "",
542
+ children: /* @__PURE__ */ jsxRuntime.jsx(OptionsList, {
543
+ activeKeys: activeKeys,
544
+ emptyNote: emptyNote,
545
+ hits: hits,
546
+ onToggle: onToggle,
547
+ shown: shown
548
+ })
549
+ });
550
+ }
551
+
552
+ function FieldOverlay({open: open, children: children}) {
553
+ return open ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
554
+ radius: 2,
555
+ shadow: 3,
556
+ style: {
557
+ insetInline: 0,
558
+ position: "absolute",
559
+ top: "calc(100% + 4px)",
560
+ zIndex: 2
561
+ },
562
+ children: children
563
+ }) : null;
564
+ }
565
+
566
+ function useTagInputStyle() {
567
+ const {font: font} = ui.useTheme_v2(), textSize = font.text.sizes[1];
568
+ if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
569
+ return {
570
+ background: "transparent",
571
+ border: "none",
572
+ color: "var(--card-fg-color)",
573
+ fontFamily: font.text.family,
574
+ fontSize: textSize.fontSize,
575
+ outline: "none",
576
+ padding: "4px 6px",
577
+ width: "100%"
578
+ };
579
+ }
580
+
581
+ function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
582
+ const inputStyle = useTagInputStyle();
583
+ /* @__PURE__ */
584
+ return jsxRuntime.jsx(ui.Card, {
585
+ border: !0,
586
+ onClick: () => inputRef.current?.focus(),
587
+ padding: 1,
588
+ radius: 2,
589
+ style: {
590
+ cursor: "text"
591
+ },
592
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
593
+ align: "center",
594
+ gap: 1,
595
+ wrap: "wrap",
596
+ children: [ pills.map(pill => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
597
+ label: pill.label,
598
+ onClear: () => onClearPill(pill.key)
599
+ }, pill.key)),
600
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
601
+ flex: 1,
602
+ style: {
603
+ minWidth: 90
604
+ },
605
+ children: /* @__PURE__ */ jsxRuntime.jsx("input", {
606
+ "aria-label": label,
607
+ onBlur: onBlur,
608
+ onChange: event => onQuery(event.currentTarget.value),
609
+ onFocus: onFocus,
610
+ placeholder: pills.length === 0 ? placeholder : void 0,
611
+ ref: inputRef,
612
+ style: inputStyle,
613
+ value: query
614
+ })
615
+ }) ]
616
+ })
617
+ });
618
+ }
619
+
620
+ function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
621
+ /* @__PURE__ */
622
+ return jsxRuntime.jsxs(ui.Stack, {
623
+ space: 1,
624
+ children: [ hits.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
625
+ padding: 2,
626
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
627
+ text: emptyNote
628
+ })
629
+ }) : null, shown.map(row => /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
630
+ active: activeKeys.includes(row.key),
631
+ onPick: () => onToggle(row.key),
632
+ children: row.node ?? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
633
+ size: 1,
634
+ children: row.text
635
+ })
636
+ }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
637
+ padding: 2,
638
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
639
+ text: `…and ${hits.length - shown.length} more — keep typing to narrow`
640
+ })
641
+ }) : null ]
642
+ });
643
+ }
644
+
645
+ function FilterPill({label: label, onClear: onClear}) {
646
+ /* @__PURE__ */
647
+ return jsxRuntime.jsx(ui.Badge, {
648
+ fontSize: 1,
649
+ mode: "outline",
650
+ radius: "full",
651
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
652
+ align: "center",
653
+ gap: 1,
654
+ children: [ label,
655
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
656
+ "aria-label": `Clear ${label}`,
657
+ fontSize: 0,
658
+ icon: Close.CloseIcon,
659
+ mode: "bleed",
660
+ onClick: onClear,
661
+ onMouseDown: event => event.preventDefault(),
662
+ padding: 1
663
+ }) ]
664
+ })
665
+ });
666
+ }
667
+
668
+ function OptionRow({active: active, onPick: onPick, children: children}) {
669
+ /* @__PURE__ */
670
+ return jsxRuntime.jsx(ui.Card, {
671
+ as: "button",
672
+ onMouseDown: event => {
673
+ event.preventDefault(), onPick();
674
+ },
675
+ padding: 2,
676
+ radius: 2,
677
+ style: {
678
+ cursor: "pointer",
679
+ textAlign: "left"
680
+ },
681
+ tone: active ? "primary" : "default",
682
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
683
+ align: "center",
684
+ gap: 2,
685
+ children: [
686
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
687
+ flex: 1,
688
+ children: children
689
+ }), active ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
690
+ size: 1,
691
+ children: /* @__PURE__ */ jsxRuntime.jsx(Checkmark.CheckmarkIcon, {})
692
+ }) : null ]
693
+ })
694
+ });
695
+ }
696
+
697
+ function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
698
+ 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 => ({
699
+ key: member.id,
700
+ text: `${member.displayName} ${member.email ?? ""}`.trim(),
701
+ node: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
702
+ align: "center",
703
+ gap: 2,
704
+ children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx(index.MemberAvatar, {
706
+ imageUrl: member.imageUrl,
707
+ name: member.displayName
708
+ }),
709
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
710
+ size: 1,
711
+ children: member.displayName
712
+ }) ]
713
+ })
714
+ }));
715
+ /* @__PURE__ */
716
+ return jsxRuntime.jsx(TagField, {
717
+ activeKeys: activeIds,
718
+ label: "Assignee",
719
+ onOpen: onOpen,
720
+ onToggle: onToggle,
721
+ open: open,
722
+ pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
723
+ placeholder: "Anyone",
724
+ rows: rows
725
+ });
726
+ }
727
+
728
+ function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
729
+ const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
730
+ if (documents.length === 0) return null;
731
+ const rows = documents.map(doc => ({
732
+ key: doc.id,
733
+ text: `${titles.get(doc.id) ?? ""} ${doc.type} ${workflowEngine.extractDocumentId(doc.id)}`,
734
+ node: /* @__PURE__ */ jsxRuntime.jsx(index.DocRefFace, {
735
+ gdr: doc
736
+ })
737
+ }));
738
+ /* @__PURE__ */
739
+ return jsxRuntime.jsx(TagField, {
740
+ activeKeys: activeIds,
741
+ emptyNote: "No documents match.",
742
+ label: "Document",
743
+ onOpen: onOpen,
744
+ onToggle: onToggle,
745
+ open: open,
746
+ pillLabel: id => labels.get(id) ?? PENDING_LABEL,
747
+ placeholder: "Any document",
748
+ rows: rows
749
+ });
750
+ }
751
+
752
+ function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
753
+ const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
754
+ onChange(next), onOpen(!1);
755
+ };
756
+ /* @__PURE__ */
757
+ return jsxRuntime.jsx(FilterField, {
758
+ label: "Date",
759
+ onClearPill: () => onChange(void 0),
760
+ onFocus: () => onOpen(!0),
761
+ onOpen: onOpen,
762
+ onQuery: () => {},
763
+ open: open,
764
+ pills: date === void 0 ? [] : [ {
765
+ key: "date",
766
+ label: dateLabel(date)
767
+ } ],
768
+ placeholder: "Any time",
769
+ query: "",
770
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
771
+ space: 1,
772
+ children: [
773
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
774
+ active: date === "past",
775
+ onPick: () => pick("past"),
776
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
777
+ size: 1,
778
+ children: "In the past"
779
+ })
780
+ }),
781
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
782
+ active: date === "none",
783
+ onPick: () => pick("none"),
784
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
785
+ size: 1,
786
+ children: "No date"
787
+ })
788
+ }),
789
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
790
+ paddingTop: 1,
791
+ paddingX: 2,
792
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
793
+ muted: !0,
794
+ size: 1,
795
+ children: "Before…"
796
+ })
797
+ }),
798
+ /* @__PURE__ */ jsxRuntime.jsx(index.DatePicker, {
799
+ onSelect: next => pick({
800
+ before: endOfDayIso(next)
801
+ }),
802
+ value: pickedDate
803
+ }) ]
804
+ })
805
+ });
806
+ }
807
+
808
+ const endOfDayIso = date => endOfDay.endOfDay(date).toISOString();
809
+
810
+ function schemaTypeTitle(schema, type) {
811
+ return schema.get(type)?.title;
812
+ }
813
+
814
+ function useDocumentChipLabels(documentIds, documents) {
815
+ const schema = sanity.useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
816
+ for (const doc of activeDocs) {
817
+ if (!resolved.has(doc.id)) continue;
818
+ const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? workflowEngine.extractDocumentId(doc.id);
819
+ labels.set(doc.id, label);
820
+ }
821
+ return labels;
822
+ }
823
+
824
+ function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
825
+ const displays = index.useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = index.useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
826
+ filters: filters,
827
+ options: options,
828
+ assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
829
+ documentTitles: documentTitles
830
+ });
831
+ return chips.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
832
+ align: "center",
833
+ gap: 2,
834
+ wrap: "wrap",
835
+ children: chips.map(chip => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
836
+ label: chip.label,
837
+ onClear: () => onChange(withoutChip(filters, chip))
838
+ }, `${chip.key}:${chip.value ?? ""}`))
839
+ });
840
+ }
841
+
842
+ const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
843
+ includeCompleted: !0,
844
+ limit: INSTANCE_CAP
845
+ };
846
+
847
+ function useToolInstances() {
848
+ const {engine: engine} = index.useWorkflowContext(), {instances: instances, loading: loading, invalid: invalid} = workflowStudio.useWorkflowInstances({
849
+ engine: engine,
850
+ filter: NEWEST_INSTANCES
851
+ });
852
+ return react.useMemo(() => {
853
+ const rows = instances ?? [];
854
+ return {
855
+ inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
856
+ settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
857
+ truncated: rows.length === INSTANCE_CAP,
858
+ loading: loading,
859
+ invalid: invalid
860
+ };
861
+ }, [ instances, loading, invalid ]);
862
+ }
863
+
864
+ function toFilterRows(instances) {
865
+ return instances.map(instance => ({
866
+ instance: instance,
867
+ workflowTitle: index.instanceTitle(instance, index.definitionSnapshotOf(instance)),
868
+ documents: documentRefsOf(instance)
869
+ }));
870
+ }
871
+
872
+ function cutInstances(args) {
873
+ const {rows: rows, filters: filters, taskArmIds: taskArmIds} = args, kept = applyInstanceFilters({
874
+ rows: rows,
875
+ filters: filters
876
+ }).filter(instance => instanceStatusMatches(instance, filters.statuses));
877
+ return taskArmIds === null ? kept : kept.filter(instance => taskArmIds.has(instance._id));
878
+ }
879
+
880
+ function WorkflowsDashboard({instances: instances, onOpenInstance: onOpenInstance}) {
881
+ 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({
882
+ instances: [ ...inFlight, ...settled ],
883
+ identity: identity,
884
+ filters: filters,
885
+ now: /* @__PURE__ */ new Date
886
+ }), [ inFlight, settled, identity, filters ]), visibleInFlight = react.useMemo(() => cutInstances({
887
+ rows: inFlightRows,
888
+ filters: filters,
889
+ taskArmIds: taskArmIds
890
+ }), [ inFlightRows, filters, taskArmIds ]), visibleSettled = react.useMemo(() => cutInstances({
891
+ rows: settledRows,
892
+ filters: filters,
893
+ taskArmIds: taskArmIds
894
+ }), [ settledRows, filters, taskArmIds ]);
895
+ return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
896
+ label: "Loading workflows…"
897
+ }) : inFlight.length === 0 && settled.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
898
+ text: "No workflows yet — start one with “New workflow”."
899
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
900
+ gap: 2,
901
+ children: [
902
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
903
+ align: "center",
904
+ gap: 2,
905
+ justify: "flex-end",
906
+ children: [
907
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
908
+ filters: filters,
909
+ onChange: setFilters,
910
+ options: options
911
+ }),
912
+ /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
913
+ filters: filters,
914
+ onChange: setFilters,
915
+ options: options
916
+ }) ]
917
+ }), visibleInFlight.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
918
+ text: "No running workflows match."
919
+ }) : /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
920
+ instances: visibleInFlight,
921
+ onOpenInstance: onOpenInstance
922
+ }),
923
+ /* @__PURE__ */ jsxRuntime.jsx(SettledRows, {
924
+ defaultShown: filters.statuses?.includes("completed") ?? !1,
925
+ instances: visibleSettled,
926
+ onOpenInstance: onOpenInstance
927
+ }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
928
+ text: `Showing the latest ${INSTANCE_CAP} workflows.`
929
+ }) : null ]
930
+ });
931
+ }
932
+
933
+ function InstanceRows({instances: instances, onOpenInstance: onOpenInstance}) {
934
+ /* @__PURE__ */
935
+ return jsxRuntime.jsx(jsxRuntime.Fragment, {
936
+ children: instances.map(instance => /* @__PURE__ */ jsxRuntime.jsx(InstanceRow, {
937
+ instance: instance,
938
+ onOpen: () => onOpenInstance(instance)
939
+ }, instance._id))
940
+ });
941
+ }
942
+
943
+ function SettledRows({instances: instances, onOpenInstance: onOpenInstance, defaultShown: defaultShown}) {
944
+ const [toggled, setToggled] = react.useState(void 0), shown = toggled ?? defaultShown;
945
+ return instances.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
946
+ gap: 2,
947
+ marginTop: 3,
948
+ children: [
949
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
950
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
951
+ fontSize: 1,
952
+ icon: shown ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
953
+ mode: "bleed",
954
+ onClick: () => setToggled(!shown),
955
+ padding: 2,
956
+ text: `${shown ? "Hide" : "Show"} ${instances.length} finished`
957
+ })
958
+ }), shown ? /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
959
+ instances: instances,
960
+ onOpenInstance: onOpenInstance
961
+ }) : null ]
962
+ });
963
+ }
964
+
965
+ function InstanceRow({instance: instance, onOpen: onOpen}) {
966
+ const definition = react.useMemo(() => index.definitionSnapshotOf(instance), [ instance ]);
967
+ /* @__PURE__ */
968
+ return jsxRuntime.jsxs(ui.Flex, {
969
+ align: "center",
970
+ gap: 2,
971
+ children: [
972
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
973
+ as: "button",
974
+ flex: 1,
975
+ onClick: onOpen,
976
+ padding: 3,
977
+ paddingLeft: 4,
978
+ radius: 2,
979
+ style: {
980
+ cursor: "pointer",
981
+ textAlign: "left"
982
+ },
983
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
984
+ align: "center",
985
+ gap: 3,
986
+ children: [
987
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
988
+ size: 1,
989
+ weight: "medium",
990
+ children: index.instanceTitle(instance, definition)
991
+ }),
992
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
993
+ muted: !0,
994
+ size: 1,
995
+ children: [ "Started ", index.formatDate(instance.startedAt) ]
996
+ }),
997
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
998
+ flex: 1
999
+ }),
1000
+ /* @__PURE__ */ jsxRuntime.jsx(index.InstanceStatusBadge, {
1001
+ definition: definition,
1002
+ instance: instance
1003
+ }),
1004
+ /* @__PURE__ */ jsxRuntime.jsx(index.PendingWorkBadge, {
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(ui.Button, {
1160
+ "aria-label": "Back to workflows",
1161
+ fontSize: 1,
1162
+ icon: ArrowLeft.ArrowLeftIcon,
1163
+ mode: "bleed",
1164
+ onClick: onBack,
1165
+ padding: 2
1166
+ }),
1167
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1168
+ gap: 2,
1169
+ children: [
1170
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1171
+ size: 1,
1172
+ weight: "semibold",
1173
+ children: index.instanceTitle(instance, definition)
1174
+ }),
1175
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1176
+ muted: !0,
1177
+ size: 1,
1178
+ children: [ "Started ", index.formatDate(instance.startedAt) ]
1179
+ }) ]
1180
+ }), workflowEngine.terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsxRuntime.jsx(index.StageChip, {
1181
+ title: index.stageTitle(definition, instance.currentStage)
1182
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.InstanceStatusBadge, {
1183
+ definition: definition,
1184
+ instance: instance
1185
+ }),
1186
+ /* @__PURE__ */ jsxRuntime.jsx(index.PendingWorkBadge, {
1187
+ instance: instance
1188
+ }),
1189
+ /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
1190
+ busy: !ready || index.isEvaluationStale(entry)
1191
+ }) ]
1192
+ })
1193
+ });
1194
+ }
1195
+
1196
+ function StudioDocumentsSection({instance: instance}) {
1197
+ const refs = documentRefsOf(instance);
1198
+ return refs.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1199
+ gap: 3,
1200
+ marginTop: 3,
1201
+ children: [
1202
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1203
+ muted: !0,
1204
+ size: 1,
1205
+ weight: "medium",
1206
+ children: refs.length === 1 ? "Studio document" : "Studio documents"
1207
+ }),
1208
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1209
+ gap: 2,
1210
+ children: refs.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
1211
+ gdr: ref
1212
+ }, ref.id))
1213
+ }) ]
1214
+ });
1215
+ }
1216
+
1217
+ function StageDiagramSection({entry: entry, definition: definition}) {
1218
+ const {instance: instance} = entry;
1219
+ return definition ?
1220
+ /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
1221
+ currentStage: workflowEngine.terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
1222
+ definition: definition,
1223
+ evaluation: entry.evaluation,
1224
+ explain: !0,
1225
+ guardCount: entry.guards?.length,
1226
+ height: 340,
1227
+ history: instance.history
1228
+ }, instance._id) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1229
+ border: !0,
1230
+ marginTop: 3,
1231
+ padding: 3,
1232
+ radius: 2,
1233
+ tone: "caution",
1234
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1235
+ muted: !0,
1236
+ size: 1,
1237
+ children: "Couldn’t load this workflow’s definition — the stage diagram is unavailable and titles fall back to raw names."
1238
+ })
1239
+ });
1240
+ }
1241
+
1242
+ function useStartableDefinitions() {
1243
+ const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(void 0), [error, setError] = react.useState(void 0);
1244
+ return react.useEffect(() => {
1245
+ let cancelled = !1;
1246
+ return (async () => {
1247
+ const rows = await index.readDeployedDefinitions(engine), startable = workflowEngine.latestDeployedDefinitions(rows).filter(row => workflowEngine.isStartableDefinition(row)).map(row => ({
1248
+ name: row.name,
1249
+ title: row.title ?? row.name,
1250
+ description: row.description
1251
+ }));
1252
+ cancelled || (setDefinitions(startable), setError(void 0));
1253
+ })().catch(err => {
1254
+ cancelled || setError(index.describeError(err));
1255
+ }), () => {
1256
+ cancelled = !0;
1257
+ };
1258
+ }, [ engine ]), {
1259
+ definitions: definitions,
1260
+ error: error
1261
+ };
1262
+ }
1263
+
1264
+ function NewWorkflowButton() {
1265
+ const {openStartDialog: openStartDialog} = index.useWorkflowContext(), [open, setOpen] = react.useState(!1);
1266
+ /* @__PURE__ */
1267
+ return jsxRuntime.jsx(index.DismissablePopover, {
1268
+ content: open ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionPickerPanel, {
1269
+ onPick: definition => {
1270
+ setOpen(!1), openStartDialog({
1271
+ definition: definition.name,
1272
+ label: definition.title
1273
+ });
1274
+ }
1275
+ }) : null,
1276
+ onDismiss: () => setOpen(!1),
1277
+ open: open,
1278
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1279
+ fontSize: 1,
1280
+ icon: Add.AddIcon,
1281
+ onClick: () => setOpen(v => !v),
1282
+ padding: 2,
1283
+ selected: open,
1284
+ text: "New workflow",
1285
+ tone: "primary"
1286
+ })
1287
+ });
1288
+ }
1289
+
1290
+ function DefinitionPickerPanel({onPick: onPick}) {
1291
+ const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = react.useState(""), visible = react.useMemo(() => {
1292
+ const q = query.trim().toLowerCase();
1293
+ return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
1294
+ }, [ definitions, query ]);
1295
+ /* @__PURE__ */
1296
+ return jsxRuntime.jsx(ui.Box, {
1297
+ padding: 2,
1298
+ style: {
1299
+ width: 340
1300
+ },
1301
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1302
+ gap: 2,
1303
+ children: [
1304
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1305
+ autoFocus: !0,
1306
+ fontSize: 1,
1307
+ icon: Search.SearchIcon,
1308
+ onChange: event => setQuery(event.currentTarget.value),
1309
+ placeholder: "Filter workflows…",
1310
+ value: query
1311
+ }), error ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1312
+ text: `Could not load the deployed workflows: ${error}`
1313
+ }) : null, !definitions && !error ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1314
+ label: "Loading workflows…"
1315
+ }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1316
+ text: "No startable workflows are deployed."
1317
+ }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1318
+ text: "No workflows match."
1319
+ }) : null,
1320
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1321
+ gap: 1,
1322
+ style: {
1323
+ maxHeight: 320,
1324
+ overflowY: "auto"
1325
+ },
1326
+ children: visible.map(definition => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1327
+ as: "button",
1328
+ onClick: () => onPick(definition),
1329
+ padding: 3,
1330
+ radius: 2,
1331
+ style: {
1332
+ cursor: "pointer",
1333
+ textAlign: "left"
1334
+ },
1335
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1336
+ gap: 2,
1337
+ children: [
1338
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1339
+ size: 1,
1340
+ weight: "medium",
1341
+ children: definition.title
1342
+ }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1343
+ muted: !0,
1344
+ size: 1,
1345
+ textOverflow: "ellipsis",
1346
+ children: definition.description
1347
+ }) : null ]
1348
+ })
1349
+ }, definition.name))
1350
+ }) ]
1351
+ })
1352
+ });
1353
+ }
1354
+
1355
+ function ToolActivityDialog({target: target, onClose: onClose}) {
1356
+ const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
1357
+ return entry && (resolvedOnce.current = !0), react.useEffect(() => {
1358
+ resolvedOnce.current && index.openActivityGone({
1359
+ entry: entry,
1360
+ target: target
1361
+ }) && onClose();
1362
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1363
+ header: "Workflow unavailable",
1364
+ id: "workflow-activity-detail",
1365
+ onClose: onClose,
1366
+ width: 1,
1367
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1368
+ padding: 4,
1369
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1370
+ invalid: entry.invalid
1371
+ })
1372
+ })
1373
+ }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1374
+ activityName: target.activityName,
1375
+ breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
1376
+ definition: definition,
1377
+ document: documentRefsOf(entry.instance)[0],
1378
+ entry: entry,
1379
+ onClose: onClose
1380
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1381
+ header: "Loading activity…",
1382
+ id: "workflow-activity-detail",
1383
+ onClose: onClose,
1384
+ width: 1,
1385
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1386
+ padding: 4,
1387
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1388
+ label: "Fetching the live workflow state…"
1389
+ })
1390
+ })
1391
+ });
1392
+ }
1393
+
1394
+ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1395
+ return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1396
+ text: "No tasks match — adjust the filters, or start a workflow."
1397
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1398
+ gap: 2,
1399
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(DocumentTaskGroup, {
1400
+ group: group,
1401
+ onOpenTask: onOpenTask,
1402
+ onOpenWorkflow: onOpenWorkflow
1403
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1404
+ header: /* @__PURE__ */ jsxRuntime.jsx(GroupTitle, {
1405
+ text: "Workflows without Studio documents"
1406
+ }),
1407
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1408
+ gap: 2,
1409
+ paddingLeft: 2,
1410
+ children: groups.withoutDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1411
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1412
+ children: [
1413
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1414
+ muted: !0,
1415
+ size: 1,
1416
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.StageGlyph, {})
1417
+ }),
1418
+ /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1419
+ group: group
1420
+ }),
1421
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1422
+ flex: 1
1423
+ }),
1424
+ /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1425
+ group: group,
1426
+ onOpenWorkflow: onOpenWorkflow
1427
+ }) ]
1428
+ }),
1429
+ children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1430
+ onOpenTask: onOpenTask,
1431
+ rows: group.rows
1432
+ })
1433
+ }, group.key))
1434
+ })
1435
+ }) : null ]
1436
+ });
1437
+ }
1438
+
1439
+ function GroupTitle({text: text}) {
1440
+ /* @__PURE__ */
1441
+ return jsxRuntime.jsx(ui.Text, {
1442
+ size: 1,
1443
+ weight: "semibold",
1444
+ children: text
1445
+ });
1446
+ }
1447
+
1448
+ function WorkflowBadgeTitle({group: group}) {
1449
+ const badge = /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1450
+ fontSize: 1,
1451
+ mode: "outline",
1452
+ children: group.workflowTitle
1453
+ });
1454
+ return group.workflowDescription ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1455
+ text: group.workflowDescription,
1456
+ children: badge
1457
+ }) : badge;
1458
+ }
1459
+
1460
+ function CollapsibleGroup({header: header, children: children}) {
1461
+ const [open, setOpen] = react.useState(!0);
1462
+ /* @__PURE__ */
1463
+ return jsxRuntime.jsxs(ui.Stack, {
1464
+ gap: 2,
1465
+ children: [
1466
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1467
+ paddingX: 2,
1468
+ paddingY: 2,
1469
+ radius: 2,
1470
+ tone: "transparent",
1471
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1472
+ align: "center",
1473
+ gap: 2,
1474
+ children: [
1475
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1476
+ "aria-label": open ? "Collapse" : "Expand",
1477
+ fontSize: 1,
1478
+ icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
1479
+ mode: "bleed",
1480
+ onClick: () => setOpen(v => !v),
1481
+ padding: 1
1482
+ }), header ]
1483
+ })
1484
+ }), open ? children : null ]
1485
+ });
1486
+ }
1487
+
1488
+ function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1489
+ const document = group.document;
1490
+ return document ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1491
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1492
+ children: [
1493
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1494
+ flex: "none",
1495
+ style: {
1496
+ maxWidth: "60%"
1497
+ },
1498
+ children: /* @__PURE__ */ jsxRuntime.jsx(RefChips, {
1499
+ max: 2,
1500
+ refs: group.documents
1501
+ })
1502
+ }),
1503
+ /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1504
+ group: group
1505
+ }),
1506
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1507
+ flex: 1
1508
+ }),
1509
+ /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1510
+ document: document,
1511
+ group: group,
1512
+ onOpenWorkflow: onOpenWorkflow
1513
+ }) ]
1514
+ }),
1515
+ children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1516
+ onOpenTask: onOpenTask,
1517
+ rows: group.rows
1518
+ })
1519
+ }) : null;
1520
+ }
1521
+
1522
+ function GroupMenu({group: group, document: document, onOpenWorkflow: onOpenWorkflow}) {
1523
+ const router$1 = router.useRouter();
1524
+ /* @__PURE__ */
1525
+ return jsxRuntime.jsx(ui.MenuButton, {
1526
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1527
+ "aria-label": "Group actions",
1528
+ fontSize: 1,
1529
+ icon: EllipsisHorizontal.EllipsisHorizontalIcon,
1530
+ mode: "bleed",
1531
+ padding: 2
1532
+ }),
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
+ }), document ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1540
+ onClick: () => router$1.navigateIntent("edit", {
1541
+ id: workflowEngine.extractDocumentId(document.id),
1542
+ type: document.type
1543
+ }),
1544
+ text: "Open document"
1545
+ }) : null ]
1546
+ }),
1547
+ popover: {
1548
+ portal: !0
1549
+ }
1550
+ });
1551
+ }
1552
+
1553
+ function TaskRows({rows: rows, onOpenTask: onOpenTask}) {
1554
+ /* @__PURE__ */
1555
+ return jsxRuntime.jsx(ui.Stack, {
1556
+ children: rows.map(row => /* @__PURE__ */ jsxRuntime.jsx(TaskRowButton, {
1557
+ onOpen: () => onOpenTask({
1558
+ instanceId: row.instanceId,
1559
+ stage: row.stage,
1560
+ activityName: row.activityName
1561
+ }),
1562
+ row: row
1563
+ }, `${row.instanceId}:${row.activityName}`))
1564
+ });
1565
+ }
1566
+
1567
+ function RowDate({dates: dates}) {
1568
+ const first = dates.find(value => index.parseStoredDateValue(value) !== void 0);
1569
+ return first === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1570
+ muted: !0,
1571
+ size: 1,
1572
+ children: index.formatDate(first)
1573
+ });
1574
+ }
1575
+
1576
+ function RowTitle({row: row}) {
1577
+ const title = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1578
+ size: 1,
1579
+ style: row.open ? void 0 : {
1580
+ opacity: .66
1581
+ },
1582
+ weight: "medium",
1583
+ children: row.title
1584
+ });
1585
+ return row.description ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1586
+ text: row.description,
1587
+ children: title
1588
+ }) : title;
1589
+ }
1590
+
1591
+ function RowBreadcrumb({breadcrumb: breadcrumb}) {
1592
+ return breadcrumb ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1593
+ align: "center",
1594
+ gap: 2,
1595
+ children: [
1596
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1597
+ muted: !0,
1598
+ size: 1,
1599
+ children: breadcrumb
1600
+ }),
1601
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1602
+ muted: !0,
1603
+ size: 1,
1604
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight.ChevronRightIcon, {})
1605
+ }) ]
1606
+ }) : null;
1607
+ }
1608
+
1609
+ function TaskRowButton({row: row, onOpen: onOpen}) {
1610
+ /* @__PURE__ */
1611
+ return jsxRuntime.jsx(ui.Card, {
1612
+ as: "button",
1613
+ onClick: onOpen,
1614
+ padding: 3,
1615
+ paddingLeft: 4,
1616
+ radius: 2,
1617
+ style: {
1618
+ cursor: "pointer",
1619
+ textAlign: "left"
1620
+ },
1621
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1622
+ align: "center",
1623
+ gap: 3,
1624
+ children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx(index.ActivityStatusIcon, {
1626
+ status: row.status
1627
+ }),
1628
+ /* @__PURE__ */ jsxRuntime.jsx(RowBreadcrumb, {
1629
+ breadcrumb: row.breadcrumb
1630
+ }),
1631
+ /* @__PURE__ */ jsxRuntime.jsx(RowTitle, {
1632
+ row: row
1633
+ }),
1634
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1635
+ fontSize: 1,
1636
+ mode: "outline",
1637
+ tone: "default",
1638
+ children: row.stageTitle
1639
+ }),
1640
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1641
+ flex: 1
1642
+ }),
1643
+ /* @__PURE__ */ jsxRuntime.jsx(RowDate, {
1644
+ dates: row.dates
1645
+ }), row.assigneeIds.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.UserAvatarGroup, {
1646
+ ids: row.assigneeIds
1647
+ }) : null ]
1648
+ })
1649
+ });
1650
+ }
1651
+
1652
+ function TasksTab({instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated}) {
1653
+ const [segment, setSegment] = react.useState("mine"), [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
1654
+ instances: instances,
1655
+ identity: identity
1656
+ }), [ instances, identity ]), options = react.useMemo(() => taskFilterOptions(groups), [ groups ]), visible = react.useMemo(() => {
1657
+ const segmented = segment === "mine" ? onlyMyTasks(groups) : groups;
1658
+ return applyTaskFilters({
1659
+ groups: segmented,
1660
+ filters: filters,
1661
+ now: /* @__PURE__ */ new Date
1662
+ });
1663
+ }, [ groups, segment, filters ]);
1664
+ return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1665
+ label: "Loading tasks…"
1666
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1667
+ children: [
1668
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1669
+ align: "center",
1670
+ gap: 2,
1671
+ paddingBottom: 3,
1672
+ children: [
1673
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1674
+ gap: 1,
1675
+ children: [
1676
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1677
+ "aria-controls": "workflow-tasks-panel",
1678
+ fontSize: 1,
1679
+ id: "workflow-tasks-mine",
1680
+ label: "My tasks",
1681
+ onClick: () => setSegment("mine"),
1682
+ padding: 2,
1683
+ selected: segment === "mine"
1684
+ }),
1685
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1686
+ "aria-controls": "workflow-tasks-panel",
1687
+ fontSize: 1,
1688
+ id: "workflow-tasks-all",
1689
+ label: "All tasks",
1690
+ onClick: () => setSegment("all"),
1691
+ padding: 2,
1692
+ selected: segment === "all"
1693
+ }) ]
1694
+ }),
1695
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1696
+ flex: 1
1697
+ }),
1698
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
1699
+ filters: filters,
1700
+ onChange: setFilters,
1701
+ options: options
1702
+ }),
1703
+ /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
1704
+ filters: filters,
1705
+ onChange: setFilters,
1706
+ options: options
1707
+ }) ]
1708
+ }),
1709
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1710
+ id: "workflow-tasks-panel",
1711
+ children: [
1712
+ /* @__PURE__ */ jsxRuntime.jsx(TaskGroupList, {
1713
+ groups: visible,
1714
+ onOpenTask: onOpenTask,
1715
+ onOpenWorkflow: onOpenWorkflow
1716
+ }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1717
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1718
+ }) : null ]
1719
+ }) ]
1720
+ });
1721
+ }
1722
+
1723
+ function WorkflowsToolRoot() {
1724
+ const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), instanceId = typeof router$1.state.instanceId == "string" ? router$1.state.instanceId : void 0;
1725
+ return instanceId ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
1726
+ instanceId: instanceId,
1727
+ onBack: () => router$1.navigate({})
1728
+ }, instanceId) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsHome, {
1729
+ onOpenInstance: instance => {
1730
+ typeof instance != "string" && seedInstance(instance), router$1.navigate({
1731
+ instanceId: typeof instance == "string" ? instance : instance._id
1732
+ });
1733
+ }
1734
+ });
1735
+ }
1736
+
1737
+ function ToolPanel({identity: identity, instances: instances, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab}) {
1738
+ return instances.invalid ? /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1739
+ invalid: instances.invalid
1740
+ }) : tab === "tasks" ? /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
1741
+ identity: identity,
1742
+ instances: [ ...instances.inFlight, ...instances.settled ],
1743
+ loading: instances.loading,
1744
+ onOpenTask: onOpenTask,
1745
+ onOpenWorkflow: onOpenInstance,
1746
+ truncated: instances.truncated
1747
+ }) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsDashboard, {
1748
+ instances: instances,
1749
+ onOpenInstance: onOpenInstance
1750
+ });
1751
+ }
1752
+
1753
+ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
1754
+ const [tab, setTab] = react.useState("tasks"), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null);
1755
+ /* @__PURE__ */
1756
+ return jsxRuntime.jsxs(ui.Card, {
1757
+ height: "fill",
1758
+ overflow: "auto",
1759
+ children: [
1760
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1761
+ padding: 4,
1762
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, {
1763
+ width: 2,
1764
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1765
+ gap: 4,
1766
+ children: [
1767
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1768
+ align: "center",
1769
+ gap: 3,
1770
+ children: [
1771
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1772
+ size: 2,
1773
+ weight: "semibold",
1774
+ children: "Workflows"
1775
+ }),
1776
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1777
+ flex: 1
1778
+ }),
1779
+ /* @__PURE__ */ jsxRuntime.jsx(NewWorkflowButton, {}) ]
1780
+ }),
1781
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1782
+ gap: 1,
1783
+ children: [
1784
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1785
+ "aria-controls": "workflows-tool-panel",
1786
+ fontSize: 1,
1787
+ id: "workflows-tool-tab-tasks",
1788
+ label: "Tasks",
1789
+ onClick: () => setTab("tasks"),
1790
+ padding: 2,
1791
+ selected: tab === "tasks"
1792
+ }),
1793
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1794
+ "aria-controls": "workflows-tool-panel",
1795
+ fontSize: 1,
1796
+ id: "workflows-tool-tab-workflows",
1797
+ label: "All workflows",
1798
+ onClick: () => setTab("workflows"),
1799
+ padding: 2,
1800
+ selected: tab === "workflows"
1801
+ }) ]
1802
+ }),
1803
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1804
+ id: "workflows-tool-panel",
1805
+ children: /* @__PURE__ */ jsxRuntime.jsx(ToolPanel, {
1806
+ identity: identity,
1807
+ instances: instances,
1808
+ onOpenInstance: target => {
1809
+ if (typeof target != "string") return onOpenInstance(target);
1810
+ const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1811
+ onOpenInstance(held ?? target);
1812
+ },
1813
+ onOpenTask: setOpenTask,
1814
+ tab: tab
1815
+ })
1816
+ }) ]
1817
+ })
1818
+ })
1819
+ }), openTask ? /* @__PURE__ */ jsxRuntime.jsx(ToolActivityDialog, {
1820
+ onClose: () => setOpenTask(null),
1821
+ target: openTask
1822
+ }) : null ]
1823
+ });
1824
+ }
1825
+
1826
+ exports.default = WorkflowsToolRoot;