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