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