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