@mdxui/do 2.1.1 → 3.0.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.
Files changed (71) hide show
  1. package/README.md +253 -266
  2. package/dist/{agents-xcIn2dUB.d.ts → agents-2_r9e9i7.d.ts} +213 -2
  3. package/dist/app/index.d.ts +347 -0
  4. package/dist/app/index.js +13 -0
  5. package/dist/app/index.js.map +1 -0
  6. package/dist/chunk-4KXVN3EQ.js +56 -0
  7. package/dist/chunk-4KXVN3EQ.js.map +1 -0
  8. package/dist/chunk-5AWTQDRF.js +76 -0
  9. package/dist/chunk-5AWTQDRF.js.map +1 -0
  10. package/dist/chunk-EQVOEEQO.js +95 -0
  11. package/dist/chunk-EQVOEEQO.js.map +1 -0
  12. package/dist/chunk-FO3N7SXV.js +469 -0
  13. package/dist/chunk-FO3N7SXV.js.map +1 -0
  14. package/dist/chunk-IESVTECE.js +536 -0
  15. package/dist/chunk-IESVTECE.js.map +1 -0
  16. package/dist/chunk-JWKIONEO.js +234 -0
  17. package/dist/chunk-JWKIONEO.js.map +1 -0
  18. package/dist/chunk-NTSEARBC.js +715 -0
  19. package/dist/chunk-NTSEARBC.js.map +1 -0
  20. package/dist/chunk-OWEAW4U6.js +116 -0
  21. package/dist/chunk-OWEAW4U6.js.map +1 -0
  22. package/dist/chunk-VRLUXCLD.js +31 -0
  23. package/dist/chunk-VRLUXCLD.js.map +1 -0
  24. package/dist/chunk-Y52IEYVM.js +131 -0
  25. package/dist/chunk-Y52IEYVM.js.map +1 -0
  26. package/dist/chunk-YGIBMNRH.js +1991 -0
  27. package/dist/chunk-YGIBMNRH.js.map +1 -0
  28. package/dist/components/index.d.ts +1 -738
  29. package/dist/components/index.js +2 -6
  30. package/dist/config-CmZBQQaT.d.ts +122 -0
  31. package/dist/{do-CaQVueZw.d.ts → do-C-t9UgjT.d.ts} +31 -33
  32. package/dist/errors-B4Oyyj4Z.d.ts +346 -0
  33. package/dist/hooks/index.d.ts +428 -696
  34. package/dist/hooks/index.js +6 -4
  35. package/dist/hooks/things/index.d.ts +298 -0
  36. package/dist/hooks/things/index.js +8 -0
  37. package/dist/hooks/things/index.js.map +1 -0
  38. package/dist/index.d.ts +21 -1010
  39. package/dist/index.js +11 -839
  40. package/dist/index.js.map +1 -1
  41. package/dist/lib/index.d.ts +100 -0
  42. package/dist/lib/index.js +6 -0
  43. package/dist/lib/index.js.map +1 -0
  44. package/dist/providers/index.d.ts +244 -32
  45. package/dist/providers/index.js +3 -2
  46. package/dist/query-keys-BC901wog.d.ts +153 -0
  47. package/dist/schemas/index.d.ts +1 -1
  48. package/dist/schemas/index.js +2 -2
  49. package/dist/schemas/index.js.map +1 -1
  50. package/dist/{thing-DtI25yZh.d.ts → thing-BVhCTzOi.d.ts} +4 -4
  51. package/dist/types/index.d.ts +251 -216
  52. package/dist/types/index.js +1 -2
  53. package/dist/views/index.d.ts +131 -0
  54. package/dist/views/index.js +11 -0
  55. package/dist/views/index.js.map +1 -0
  56. package/package.json +39 -17
  57. package/dist/__test-utils__/index.d.ts +0 -399
  58. package/dist/__test-utils__/index.js +0 -34641
  59. package/dist/__test-utils__/index.js.map +0 -1
  60. package/dist/chunk-EEDMN7UF.js +0 -1351
  61. package/dist/chunk-EEDMN7UF.js.map +0 -1
  62. package/dist/chunk-G3PMV62Z.js +0 -33
  63. package/dist/chunk-G3PMV62Z.js.map +0 -1
  64. package/dist/chunk-NXPXL5NA.js +0 -3789
  65. package/dist/chunk-NXPXL5NA.js.map +0 -1
  66. package/dist/chunk-PC5FJY6M.js +0 -20
  67. package/dist/chunk-PC5FJY6M.js.map +0 -1
  68. package/dist/chunk-XF6LKY2M.js +0 -445
  69. package/dist/chunk-XF6LKY2M.js.map +0 -1
  70. package/dist/magic-string.es-J7BYFTTJ.js +0 -1307
  71. package/dist/magic-string.es-J7BYFTTJ.js.map +0 -1
@@ -1,3789 +0,0 @@
1
- import { useThings, useCreateThing } from './chunk-EEDMN7UF.js';
2
- import { cn, formatNumber, truncate, formatRelativeTime, formatDateTime, formatDuration, useDO, useDataProviderSafe } from './chunk-XF6LKY2M.js';
3
- import * as React9 from 'react';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { LayoutDashboard, Database, GitBranch, Workflow, Bot, Zap, Plug, Users, Building, Key, Settings, Menu, Search, Bell, User, ChevronDown, TrendingUp, TrendingDown, Filter, Trash2, Plus, MoreHorizontal, Eye, Edit, History, Play, CheckCircle2, RefreshCw, StopCircle, XCircle, Loader2, Clock, AlertCircle, ChevronRight, X, ThumbsUp, ThumbsDown, Coins, ChevronLeft, WifiOff, Wifi, Activity } from 'lucide-react';
6
-
7
- var ShellContext = React9.createContext(null);
8
- function useShell() {
9
- const context = React9.useContext(ShellContext);
10
- if (!context) {
11
- throw new Error("useShell must be used within a DOShell");
12
- }
13
- return context;
14
- }
15
- function DOShell({
16
- sidebar,
17
- header,
18
- children,
19
- defaultSidebarOpen = true,
20
- sidebarOpen: controlledSidebarOpen,
21
- onSidebarOpenChange,
22
- className
23
- }) {
24
- const [internalSidebarOpen, setInternalSidebarOpen] = React9.useState(defaultSidebarOpen);
25
- const sidebarOpen = controlledSidebarOpen ?? internalSidebarOpen;
26
- const setSidebarOpen = onSidebarOpenChange ?? setInternalSidebarOpen;
27
- const contextValue = React9.useMemo(
28
- () => ({
29
- sidebarOpen,
30
- setSidebarOpen,
31
- toggleSidebar: () => setSidebarOpen(!sidebarOpen)
32
- }),
33
- [sidebarOpen, setSidebarOpen]
34
- );
35
- return /* @__PURE__ */ jsx(ShellContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
36
- "div",
37
- {
38
- "data-testid": "do-shell",
39
- className: cn("flex h-screen w-full overflow-hidden bg-background", className),
40
- children: [
41
- sidebar && /* @__PURE__ */ jsx(
42
- "aside",
43
- {
44
- "data-testid": "do-sidebar",
45
- className: cn(
46
- "flex h-full flex-col border-r border-border bg-muted/30 transition-all duration-300",
47
- sidebarOpen ? "w-64" : "w-0 overflow-hidden"
48
- ),
49
- children: sidebar
50
- }
51
- ),
52
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
53
- header && /* @__PURE__ */ jsx(
54
- "header",
55
- {
56
- "data-testid": "do-header",
57
- className: "flex h-14 shrink-0 items-center border-b border-border bg-background px-4",
58
- children: header
59
- }
60
- ),
61
- /* @__PURE__ */ jsx("main", { "data-testid": "do-main", className: "flex-1 overflow-auto", children })
62
- ] })
63
- ]
64
- }
65
- ) });
66
- }
67
- var defaultNavItems = [
68
- {
69
- id: "dashboard",
70
- label: "Dashboard",
71
- icon: LayoutDashboard,
72
- href: "/"
73
- },
74
- {
75
- id: "things",
76
- label: "Things",
77
- icon: Database,
78
- href: "/things"
79
- },
80
- {
81
- id: "relationships",
82
- label: "Relationships",
83
- icon: GitBranch,
84
- href: "/relationships"
85
- },
86
- {
87
- id: "workflows",
88
- label: "Workflows",
89
- icon: Workflow,
90
- href: "/workflows"
91
- },
92
- {
93
- id: "agents",
94
- label: "Agents",
95
- icon: Bot,
96
- href: "/agents"
97
- },
98
- {
99
- id: "events",
100
- label: "Events",
101
- icon: Zap,
102
- href: "/events"
103
- },
104
- {
105
- id: "integrations",
106
- label: "Integrations",
107
- icon: Plug,
108
- href: "/integrations"
109
- },
110
- {
111
- id: "settings",
112
- label: "Settings",
113
- icon: Settings,
114
- children: [
115
- {
116
- id: "users",
117
- label: "Users",
118
- icon: Users,
119
- href: "/settings/users"
120
- },
121
- {
122
- id: "organizations",
123
- label: "Organizations",
124
- icon: Building,
125
- href: "/settings/organizations"
126
- },
127
- {
128
- id: "api-keys",
129
- label: "API Keys",
130
- icon: Key,
131
- href: "/settings/api-keys"
132
- }
133
- ]
134
- }
135
- ];
136
- function DOSidebar({
137
- header,
138
- items = defaultNavItems,
139
- activeItem,
140
- footer,
141
- onItemClick,
142
- className
143
- }) {
144
- const [expandedItems, setExpandedItems] = React9.useState(/* @__PURE__ */ new Set());
145
- const toggleExpanded = (id) => {
146
- setExpandedItems((prev) => {
147
- const next = new Set(prev);
148
- if (next.has(id)) {
149
- next.delete(id);
150
- } else {
151
- next.add(id);
152
- }
153
- return next;
154
- });
155
- };
156
- const renderNavItem = (item, depth = 0) => {
157
- const Icon = item.icon;
158
- const hasChildren = item.children && item.children.length > 0;
159
- const isExpanded = expandedItems.has(item.id);
160
- const isActive = activeItem === item.id;
161
- return /* @__PURE__ */ jsxs("div", { children: [
162
- /* @__PURE__ */ jsxs(
163
- "button",
164
- {
165
- "data-testid": `nav-${item.id}`,
166
- "data-active": isActive ? "true" : void 0,
167
- onClick: () => {
168
- if (hasChildren) {
169
- toggleExpanded(item.id);
170
- } else {
171
- onItemClick?.(item);
172
- }
173
- },
174
- disabled: item.disabled,
175
- className: cn(
176
- "flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
177
- "hover:bg-accent hover:text-accent-foreground",
178
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
179
- isActive && "bg-accent text-accent-foreground",
180
- item.disabled && "cursor-not-allowed opacity-50",
181
- depth > 0 && "ml-6"
182
- ),
183
- children: [
184
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }),
185
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: item.label }),
186
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary px-2 py-0.5 text-xs text-primary-foreground", children: item.badge }),
187
- hasChildren && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
188
- ]
189
- }
190
- ),
191
- hasChildren && isExpanded && /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-1", children: item.children.map((child) => renderNavItem(child, depth + 1)) })
192
- ] }, item.id);
193
- };
194
- return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full flex-col", className), children: [
195
- header && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-border p-4", children: header }),
196
- /* @__PURE__ */ jsx("nav", { className: "flex-1 space-y-1 overflow-auto p-4", children: items.map((item) => renderNavItem(item)) }),
197
- footer && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border p-4", children: footer })
198
- ] });
199
- }
200
- function DOSidebarHeader({ logo, title = ".do Admin", subtitle, className }) {
201
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", className), children: [
202
- logo && /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground", children: typeof logo === "string" ? /* @__PURE__ */ jsx("img", { src: logo, alt: "", className: "h-6 w-6" }) : logo }),
203
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
204
- /* @__PURE__ */ jsx("div", { className: "truncate font-semibold", children: title }),
205
- subtitle && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: subtitle })
206
- ] })
207
- ] });
208
- }
209
- function DOHeader({
210
- title,
211
- breadcrumbs,
212
- showSearch = true,
213
- searchPlaceholder = "Search...",
214
- searchValue,
215
- onSearchChange,
216
- actions,
217
- user,
218
- onUserMenuClick,
219
- className
220
- }) {
221
- const shell = useShell();
222
- const [isUserMenuOpen, setIsUserMenuOpen] = React9.useState(false);
223
- return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full items-center gap-4", className), children: [
224
- /* @__PURE__ */ jsx(
225
- "button",
226
- {
227
- "data-testid": "sidebar-toggle",
228
- onClick: shell.toggleSidebar,
229
- className: "flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
230
- "aria-label": shell.sidebarOpen ? "Close sidebar" : "Open sidebar",
231
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
232
- }
233
- ),
234
- /* @__PURE__ */ jsx("div", { className: "flex-1", children: breadcrumbs && breadcrumbs.length > 0 ? /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1 text-sm", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsxs(React9.Fragment, { children: [
235
- index > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "/" }),
236
- crumb.href ? /* @__PURE__ */ jsx("a", { href: crumb.href, className: "text-muted-foreground hover:text-foreground", children: crumb.label }) : /* @__PURE__ */ jsx("span", { className: "font-medium", children: crumb.label })
237
- ] }, index)) }) : title ? /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: title }) : null }),
238
- showSearch && /* @__PURE__ */ jsxs("div", { className: "relative w-64", children: [
239
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
240
- /* @__PURE__ */ jsx(
241
- "input",
242
- {
243
- "data-testid": "header-search-input",
244
- type: "text",
245
- placeholder: searchPlaceholder,
246
- value: searchValue,
247
- onChange: (e) => onSearchChange?.(e.target.value),
248
- className: cn(
249
- "h-9 w-full rounded-md border border-input bg-background pl-9 pr-4 text-sm",
250
- "placeholder:text-muted-foreground",
251
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
252
- )
253
- }
254
- )
255
- ] }),
256
- actions,
257
- /* @__PURE__ */ jsxs(
258
- "button",
259
- {
260
- "data-testid": "notifications-button",
261
- className: "relative flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
262
- children: [
263
- /* @__PURE__ */ jsx(Bell, { className: "h-5 w-5" }),
264
- /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
265
- ]
266
- }
267
- ),
268
- user && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
269
- /* @__PURE__ */ jsxs(
270
- "button",
271
- {
272
- "data-testid": "user-menu",
273
- onClick: () => setIsUserMenuOpen(!isUserMenuOpen),
274
- className: "flex items-center gap-2 rounded-md px-2 py-1 hover:bg-accent",
275
- children: [
276
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-muted", children: user.avatar ? /* @__PURE__ */ jsx("img", { src: user.avatar, alt: "", className: "h-8 w-8 rounded-full" }) : /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) }),
277
- /* @__PURE__ */ jsxs("div", { className: "hidden text-left sm:block", children: [
278
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: user.name }),
279
- user.email && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: user.email })
280
- ] }),
281
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })
282
- ]
283
- }
284
- ),
285
- isUserMenuOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
286
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: () => setIsUserMenuOpen(false) }),
287
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full z-50 mt-2 w-48 rounded-md border border-border bg-popover py-1 shadow-lg", children: [
288
- /* @__PURE__ */ jsx(
289
- "button",
290
- {
291
- "data-testid": "user-menu-profile",
292
- onClick: () => {
293
- onUserMenuClick?.("profile");
294
- setIsUserMenuOpen(false);
295
- },
296
- className: "flex w-full items-center px-4 py-2 text-sm hover:bg-accent",
297
- children: "Profile"
298
- }
299
- ),
300
- /* @__PURE__ */ jsx(
301
- "button",
302
- {
303
- "data-testid": "user-menu-settings",
304
- onClick: () => {
305
- onUserMenuClick?.("settings");
306
- setIsUserMenuOpen(false);
307
- },
308
- className: "flex w-full items-center px-4 py-2 text-sm hover:bg-accent",
309
- children: "Settings"
310
- }
311
- ),
312
- /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
313
- /* @__PURE__ */ jsx(
314
- "button",
315
- {
316
- "data-testid": "user-menu-logout",
317
- onClick: () => {
318
- onUserMenuClick?.("logout");
319
- setIsUserMenuOpen(false);
320
- },
321
- className: "flex w-full items-center px-4 py-2 text-sm text-destructive hover:bg-accent",
322
- children: "Log out"
323
- }
324
- )
325
- ] })
326
- ] })
327
- ] })
328
- ] });
329
- }
330
- function SyncStatusIndicator({
331
- state = "connected",
332
- showLabel = false,
333
- label,
334
- className
335
- }) {
336
- const getStatusConfig = () => {
337
- switch (state) {
338
- case "connected":
339
- return {
340
- icon: Wifi,
341
- color: "text-green-500",
342
- defaultLabel: "Connected"
343
- };
344
- case "disconnected":
345
- return {
346
- icon: WifiOff,
347
- color: "text-destructive",
348
- defaultLabel: "Disconnected"
349
- };
350
- case "syncing":
351
- return {
352
- icon: RefreshCw,
353
- color: "text-yellow-500",
354
- defaultLabel: "Syncing...",
355
- animate: true
356
- };
357
- }
358
- };
359
- const config = getStatusConfig();
360
- const Icon = config.icon;
361
- const displayLabel = label ?? config.defaultLabel;
362
- return /* @__PURE__ */ jsxs(
363
- "div",
364
- {
365
- "data-testid": "sync-status",
366
- "data-sync-state": state,
367
- className: cn("flex items-center gap-2", className),
368
- children: [
369
- /* @__PURE__ */ jsx(
370
- Icon,
371
- {
372
- className: cn(
373
- "h-4 w-4",
374
- config.color,
375
- "animate" in config && config.animate && "animate-spin"
376
- )
377
- }
378
- ),
379
- showLabel && /* @__PURE__ */ jsx("span", { className: cn("text-sm", config.color), children: displayLabel })
380
- ]
381
- }
382
- );
383
- }
384
- var defaultStats = [
385
- { title: "Things", value: 0, icon: Database, color: "primary" },
386
- { title: "Relationships", value: 0, icon: GitBranch, color: "default" },
387
- { title: "Workflows", value: 0, icon: Workflow, color: "success" },
388
- { title: "Agents", value: 0, icon: Bot, color: "warning" },
389
- { title: "Events Today", value: 0, icon: Zap, color: "destructive" }
390
- ];
391
- var colorClasses = {
392
- default: "bg-muted text-muted-foreground",
393
- primary: "bg-primary/10 text-primary",
394
- success: "bg-green-500/10 text-green-600 dark:text-green-400",
395
- warning: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
396
- destructive: "bg-destructive/10 text-destructive"
397
- };
398
- function StatsCards({ stats, loading = false, onCardClick, className }) {
399
- return /* @__PURE__ */ jsx("div", { className: cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5", className), children: stats.map((stat, index) => {
400
- const Icon = stat.icon;
401
- const trend = stat.previousValue !== void 0 ? (stat.value - stat.previousValue) / stat.previousValue * 100 : void 0;
402
- const isPositive = trend !== void 0 && trend >= 0;
403
- return /* @__PURE__ */ jsxs(
404
- "button",
405
- {
406
- onClick: () => onCardClick?.(stat),
407
- className: cn(
408
- "flex flex-col rounded-lg border border-border bg-card p-4 text-left transition-colors",
409
- "hover:bg-accent/50",
410
- loading && "animate-pulse"
411
- ),
412
- children: [
413
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
414
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-muted-foreground", children: stat.title }),
415
- Icon && /* @__PURE__ */ jsx("div", { className: cn("rounded-md p-2", colorClasses[stat.color || "default"]), children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) })
416
- ] }),
417
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-baseline gap-2", children: [
418
- /* @__PURE__ */ jsx("span", { className: "text-2xl font-bold", children: formatNumber(stat.value) }),
419
- trend !== void 0 && /* @__PURE__ */ jsxs(
420
- "span",
421
- {
422
- className: cn(
423
- "flex items-center text-xs font-medium",
424
- isPositive ? "text-green-600" : "text-destructive"
425
- ),
426
- children: [
427
- isPositive ? /* @__PURE__ */ jsx(TrendingUp, { className: "mr-0.5 h-3 w-3" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "mr-0.5 h-3 w-3" }),
428
- Math.abs(trend).toFixed(1),
429
- "%"
430
- ]
431
- }
432
- )
433
- ] })
434
- ]
435
- },
436
- index
437
- );
438
- }) });
439
- }
440
- function ThingsList({
441
- things,
442
- total,
443
- loading = false,
444
- selectedIds = [],
445
- filter,
446
- sort,
447
- pagination = { page: 1, perPage: 20 },
448
- availableTypes = [],
449
- onFilterChange,
450
- onSortChange,
451
- onPaginationChange,
452
- onThingClick,
453
- onThingDoubleClick,
454
- onThingSelect,
455
- onAction,
456
- onCreate,
457
- className
458
- }) {
459
- const [searchValue, setSearchValue] = React9.useState(filter?.nameSearch || "");
460
- const [showFilters, setShowFilters] = React9.useState(false);
461
- const [openMenuId, setOpenMenuId] = React9.useState(null);
462
- const [selectedRowId, setSelectedRowId] = React9.useState(null);
463
- const totalPages = Math.ceil(total / pagination.perPage) || 1;
464
- const currentTypeFilter = filter?.type ? Array.isArray(filter.type) ? filter.type[0] : filter.type : "";
465
- const handleSearch = (value) => {
466
- setSearchValue(value);
467
- onFilterChange?.({ ...filter, nameSearch: value || void 0 });
468
- };
469
- const handleSearchKeyDown = (e) => {
470
- if (e.key === "Enter") {
471
- onFilterChange?.({ ...filter, nameSearch: searchValue || void 0 });
472
- }
473
- };
474
- const handleClearSearch = () => {
475
- setSearchValue("");
476
- onFilterChange?.({ ...filter, nameSearch: void 0 });
477
- };
478
- const handleTypeFilter = (type) => {
479
- onFilterChange?.({ ...filter, type: type || void 0 });
480
- };
481
- const handleSort = (field) => {
482
- const newOrder = sort?.field === field && sort.order === "asc" ? "desc" : "asc";
483
- onSortChange?.({ field, order: newOrder });
484
- };
485
- const handleRowClick = (thing) => {
486
- setSelectedRowId(thing.id);
487
- onThingClick?.(thing);
488
- };
489
- const handleRowDoubleClick = (thing) => {
490
- onThingDoubleClick?.(thing);
491
- onAction?.(thing, "edit");
492
- };
493
- React9.useEffect(() => {
494
- const handleKeyDown = (e) => {
495
- if (!selectedRowId) return;
496
- const selectedThing = things.find((t) => t.id === selectedRowId);
497
- if (!selectedThing) return;
498
- if (e.key === "e") {
499
- e.preventDefault();
500
- onAction?.(selectedThing, "edit");
501
- } else if (e.key === "Delete" || e.key === "Backspace") {
502
- e.preventDefault();
503
- onAction?.(selectedThing, "delete");
504
- } else if (e.key === "Enter") {
505
- e.preventDefault();
506
- onAction?.(selectedThing, "edit");
507
- }
508
- };
509
- document.addEventListener("keydown", handleKeyDown);
510
- return () => document.removeEventListener("keydown", handleKeyDown);
511
- }, [selectedRowId, things, onAction]);
512
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
513
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border p-4", children: [
514
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
515
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
516
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
517
- /* @__PURE__ */ jsx(
518
- "input",
519
- {
520
- type: "text",
521
- "data-testid": "search-input",
522
- placeholder: "Search things...",
523
- value: searchValue,
524
- onChange: (e) => handleSearch(e.target.value),
525
- onKeyDown: handleSearchKeyDown,
526
- className: cn(
527
- "h-9 w-64 rounded-md border border-input bg-background pl-9 pr-8 text-sm",
528
- "placeholder:text-muted-foreground",
529
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
530
- )
531
- }
532
- ),
533
- searchValue && /* @__PURE__ */ jsx(
534
- "button",
535
- {
536
- "data-testid": "clear-search",
537
- onClick: handleClearSearch,
538
- className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
539
- children: /* @__PURE__ */ jsxs(
540
- "svg",
541
- {
542
- xmlns: "http://www.w3.org/2000/svg",
543
- width: "14",
544
- height: "14",
545
- viewBox: "0 0 24 24",
546
- fill: "none",
547
- stroke: "currentColor",
548
- strokeWidth: "2",
549
- children: [
550
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
551
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
552
- ]
553
- }
554
- )
555
- }
556
- )
557
- ] }),
558
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
559
- /* @__PURE__ */ jsxs(
560
- "select",
561
- {
562
- "data-testid": "type-filter",
563
- value: currentTypeFilter,
564
- onChange: (e) => handleTypeFilter(e.target.value),
565
- className: cn(
566
- "h-9 appearance-none rounded-md border border-input bg-background pl-3 pr-8 text-sm",
567
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
568
- ),
569
- children: [
570
- /* @__PURE__ */ jsx("option", { value: "", children: "All Types" }),
571
- availableTypes.map((type) => /* @__PURE__ */ jsx("option", { value: type, "data-testid": `type-option-${type}`, children: type }, type))
572
- ]
573
- }
574
- ),
575
- /* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground pointer-events-none" })
576
- ] }),
577
- /* @__PURE__ */ jsxs(
578
- "button",
579
- {
580
- onClick: () => setShowFilters(!showFilters),
581
- className: cn(
582
- "flex h-9 items-center gap-2 rounded-md border border-input px-3 text-sm",
583
- "hover:bg-accent",
584
- showFilters && "bg-accent"
585
- ),
586
- children: [
587
- /* @__PURE__ */ jsx(Filter, { className: "h-4 w-4" }),
588
- "Filters"
589
- ]
590
- }
591
- ),
592
- selectedIds.length > 0 && /* @__PURE__ */ jsxs(
593
- "span",
594
- {
595
- "data-testid": "selection-count",
596
- className: "text-sm text-muted-foreground",
597
- children: [
598
- selectedIds.length,
599
- " selected"
600
- ]
601
- }
602
- ),
603
- selectedIds.length > 0 && /* @__PURE__ */ jsxs(
604
- "button",
605
- {
606
- "data-testid": "bulk-delete-btn",
607
- onClick: () => {
608
- const selectedThing = things.find((t) => selectedIds.includes(t.id));
609
- if (selectedThing) {
610
- onAction?.(selectedThing, "delete");
611
- }
612
- },
613
- className: cn(
614
- "flex h-9 items-center gap-2 rounded-md px-3 text-sm",
615
- "bg-destructive text-destructive-foreground hover:bg-destructive/90"
616
- ),
617
- children: [
618
- /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
619
- "Delete"
620
- ]
621
- }
622
- )
623
- ] }),
624
- onCreate && /* @__PURE__ */ jsxs(
625
- "button",
626
- {
627
- "data-testid": "create-thing-btn",
628
- onClick: onCreate,
629
- className: cn(
630
- "flex h-9 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground",
631
- "hover:bg-primary/90",
632
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
633
- ),
634
- children: [
635
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
636
- "Create Thing"
637
- ]
638
- }
639
- )
640
- ] }),
641
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxs("table", { "data-testid": "things-table", className: "w-full", children: [
642
- /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { children: [
643
- /* @__PURE__ */ jsx("th", { className: "w-8 p-4", children: /* @__PURE__ */ jsx(
644
- "input",
645
- {
646
- type: "checkbox",
647
- "data-testid": "select-all-checkbox",
648
- className: "h-4 w-4 rounded border-input",
649
- checked: selectedIds.length > 0 && selectedIds.length === things.length,
650
- onChange: (e) => {
651
- things.forEach((thing) => onThingSelect?.(thing, e.target.checked));
652
- }
653
- }
654
- ) }),
655
- /* @__PURE__ */ jsx(
656
- "th",
657
- {
658
- "data-testid": "header-name",
659
- className: "cursor-pointer p-4 text-left text-sm font-medium text-muted-foreground hover:text-foreground",
660
- onClick: () => handleSort("name"),
661
- children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
662
- "Name",
663
- sort?.field === "name" && /* @__PURE__ */ jsx("span", { "data-testid": "sort-indicator", children: sort.order === "asc" ? "\u2191" : "\u2193" })
664
- ] })
665
- }
666
- ),
667
- /* @__PURE__ */ jsx(
668
- "th",
669
- {
670
- "data-testid": "header-type",
671
- className: "cursor-pointer p-4 text-left text-sm font-medium text-muted-foreground hover:text-foreground",
672
- onClick: () => handleSort("type"),
673
- children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
674
- "Type",
675
- sort?.field === "type" && /* @__PURE__ */ jsx("span", { "data-testid": "sort-indicator", children: sort.order === "asc" ? "\u2191" : "\u2193" })
676
- ] })
677
- }
678
- ),
679
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Namespace" }),
680
- /* @__PURE__ */ jsx(
681
- "th",
682
- {
683
- "data-testid": "header-updated",
684
- className: "cursor-pointer p-4 text-left text-sm font-medium text-muted-foreground hover:text-foreground",
685
- onClick: () => handleSort("updatedAt"),
686
- children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
687
- "Updated",
688
- sort?.field === "updatedAt" && /* @__PURE__ */ jsx("span", { "data-testid": "sort-indicator", children: sort.order === "asc" ? "\u2191" : "\u2193" })
689
- ] })
690
- }
691
- ),
692
- /* @__PURE__ */ jsx("th", { className: "w-12 p-4" })
693
- ] }) }),
694
- /* @__PURE__ */ jsx("tbody", { children: loading ? (
695
- // Loading skeleton
696
- Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "animate-pulse", children: [
697
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted" }) }),
698
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted" }) }),
699
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted" }) }),
700
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted" }) }),
701
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-28 rounded bg-muted" }) }),
702
- /* @__PURE__ */ jsx("td", { className: "p-4" })
703
- ] }, i))
704
- ) : things.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 6, children: /* @__PURE__ */ jsxs(
705
- "div",
706
- {
707
- "data-testid": "things-empty-state",
708
- className: "flex flex-col items-center justify-center gap-2 p-8 text-center",
709
- children: [
710
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No things found" }),
711
- onCreate && /* @__PURE__ */ jsx(
712
- "button",
713
- {
714
- onClick: onCreate,
715
- className: "text-sm text-primary hover:underline",
716
- children: "Create your first Thing"
717
- }
718
- )
719
- ]
720
- }
721
- ) }) }) : things.map((thing) => /* @__PURE__ */ jsxs(
722
- "tr",
723
- {
724
- "data-testid": "things-row",
725
- "data-selected": selectedRowId === thing.id ? "true" : void 0,
726
- className: cn(
727
- "cursor-pointer border-b border-border",
728
- "hover:bg-muted/50",
729
- selectedRowId === thing.id && "bg-muted"
730
- ),
731
- onClick: () => handleRowClick(thing),
732
- onDoubleClick: () => handleRowDoubleClick(thing),
733
- children: [
734
- /* @__PURE__ */ jsx("td", { className: "p-4", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
735
- "input",
736
- {
737
- type: "checkbox",
738
- "data-testid": "row-checkbox",
739
- className: "h-4 w-4 rounded border-input",
740
- checked: selectedIds.includes(thing.id),
741
- onChange: (e) => onThingSelect?.(thing, e.target.checked)
742
- }
743
- ) }),
744
- /* @__PURE__ */ jsxs("td", { className: "p-4", "data-testid": "thing-name-cell", children: [
745
- /* @__PURE__ */ jsx("div", { className: "font-medium", children: thing.name || thing.id }),
746
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: truncate(thing.id, 30) })
747
- ] }),
748
- /* @__PURE__ */ jsx("td", { className: "p-4", "data-testid": "thing-type-cell", children: /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-1 text-xs font-medium", children: thing.type }) }),
749
- /* @__PURE__ */ jsx("td", { className: "p-4 text-sm text-muted-foreground", children: thing.ns }),
750
- /* @__PURE__ */ jsx("td", { className: "p-4 text-sm text-muted-foreground", children: formatRelativeTime(thing.updatedAt) }),
751
- /* @__PURE__ */ jsx("td", { className: "p-4", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
752
- /* @__PURE__ */ jsx(
753
- "button",
754
- {
755
- "data-testid": "row-actions-menu",
756
- onClick: () => setOpenMenuId(openMenuId === thing.id ? null : thing.id),
757
- className: "flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
758
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" })
759
- }
760
- ),
761
- openMenuId === thing.id && /* @__PURE__ */ jsxs(Fragment, { children: [
762
- /* @__PURE__ */ jsx(
763
- "div",
764
- {
765
- className: "fixed inset-0 z-40",
766
- onClick: () => setOpenMenuId(null)
767
- }
768
- ),
769
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full z-50 mt-1 w-36 rounded-md border border-border bg-popover py-1 shadow-lg", children: [
770
- /* @__PURE__ */ jsxs(
771
- "button",
772
- {
773
- onClick: () => {
774
- onAction?.(thing, "view");
775
- setOpenMenuId(null);
776
- },
777
- className: "flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent",
778
- children: [
779
- /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }),
780
- " View"
781
- ]
782
- }
783
- ),
784
- /* @__PURE__ */ jsxs(
785
- "button",
786
- {
787
- "data-testid": "edit-btn",
788
- onClick: () => {
789
- onAction?.(thing, "edit");
790
- setOpenMenuId(null);
791
- },
792
- className: "flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent",
793
- children: [
794
- /* @__PURE__ */ jsx(Edit, { className: "h-4 w-4" }),
795
- " Edit"
796
- ]
797
- }
798
- ),
799
- /* @__PURE__ */ jsxs(
800
- "button",
801
- {
802
- onClick: () => {
803
- onAction?.(thing, "history");
804
- setOpenMenuId(null);
805
- },
806
- className: "flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent",
807
- children: [
808
- /* @__PURE__ */ jsx(History, { className: "h-4 w-4" }),
809
- " History"
810
- ]
811
- }
812
- ),
813
- /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
814
- /* @__PURE__ */ jsxs(
815
- "button",
816
- {
817
- "data-testid": "delete-btn",
818
- onClick: () => {
819
- onAction?.(thing, "delete");
820
- setOpenMenuId(null);
821
- },
822
- className: "flex w-full items-center gap-2 px-3 py-2 text-sm text-destructive hover:bg-accent",
823
- children: [
824
- /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
825
- " Delete"
826
- ]
827
- }
828
- )
829
- ] })
830
- ] })
831
- ] }) })
832
- ]
833
- },
834
- `${thing.ns}/${thing.type}/${thing.id}`
835
- )) })
836
- ] }) }),
837
- /* @__PURE__ */ jsxs(
838
- "div",
839
- {
840
- "data-testid": "pagination",
841
- className: "flex items-center justify-between border-t border-border p-4",
842
- children: [
843
- /* @__PURE__ */ jsx("div", { "data-testid": "page-info", className: "text-sm text-muted-foreground", children: total > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
844
- "Showing ",
845
- (pagination.page - 1) * pagination.perPage + 1,
846
- " to",
847
- " ",
848
- Math.min(pagination.page * pagination.perPage, total),
849
- " of ",
850
- total,
851
- " things",
852
- " - ",
853
- "Page ",
854
- pagination.page,
855
- " of ",
856
- totalPages
857
- ] }) : "No results" }),
858
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
859
- /* @__PURE__ */ jsx(
860
- "button",
861
- {
862
- "data-testid": "prev-page",
863
- onClick: () => onPaginationChange?.({ ...pagination, page: pagination.page - 1 }),
864
- disabled: pagination.page <= 1,
865
- className: cn(
866
- "h-8 rounded-md border border-input px-3 text-sm",
867
- "hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed"
868
- ),
869
- children: "Previous"
870
- }
871
- ),
872
- /* @__PURE__ */ jsx(
873
- "button",
874
- {
875
- "data-testid": "next-page",
876
- onClick: () => onPaginationChange?.({ ...pagination, page: pagination.page + 1 }),
877
- disabled: pagination.page >= totalPages,
878
- className: cn(
879
- "h-8 rounded-md border border-input px-3 text-sm",
880
- "hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed"
881
- ),
882
- children: "Next"
883
- }
884
- )
885
- ] })
886
- ]
887
- }
888
- )
889
- ] });
890
- }
891
- function ThingForm({
892
- thing,
893
- onSubmit,
894
- onCancel,
895
- isSubmitting = false,
896
- className
897
- }) {
898
- const isEditMode = Boolean(thing);
899
- const [formData, setFormData] = React9.useState({
900
- type: thing?.type ?? "",
901
- name: thing?.name ?? "",
902
- description: thing?.data?.description ?? ""
903
- });
904
- const [errors, setErrors] = React9.useState({});
905
- const [hasSubmitted, setHasSubmitted] = React9.useState(false);
906
- const typeInputRef = React9.useRef(null);
907
- React9.useEffect(() => {
908
- typeInputRef.current?.focus();
909
- }, []);
910
- const validateForm = (data) => {
911
- const newErrors = {};
912
- const trimmedType = data.type.trim();
913
- if (!trimmedType) {
914
- newErrors.type = "Type is required";
915
- }
916
- const trimmedName = data.name.trim();
917
- if (!trimmedName) {
918
- newErrors.name = "Name is required";
919
- }
920
- return newErrors;
921
- };
922
- const handleChange = (field, value) => {
923
- setFormData((prev) => ({ ...prev, [field]: value }));
924
- if (hasSubmitted) {
925
- const newData = { ...formData, [field]: value };
926
- const newErrors = validateForm(newData);
927
- setErrors((prev) => ({
928
- ...prev,
929
- [field]: newErrors[field]
930
- }));
931
- }
932
- };
933
- const handleSubmit = async (e) => {
934
- e.preventDefault();
935
- setHasSubmitted(true);
936
- const validationErrors = validateForm(formData);
937
- setErrors(validationErrors);
938
- if (Object.keys(validationErrors).length > 0) {
939
- return;
940
- }
941
- const trimmedData = {
942
- type: formData.type.trim(),
943
- name: formData.name.trim(),
944
- description: formData.description.trim()
945
- };
946
- await onSubmit(trimmedData);
947
- };
948
- const handleKeyDown = (e) => {
949
- if (e.key === "Enter" && !e.shiftKey) {
950
- e.preventDefault();
951
- handleSubmit(e);
952
- }
953
- };
954
- return /* @__PURE__ */ jsxs(
955
- "form",
956
- {
957
- "data-testid": "thing-form",
958
- onSubmit: handleSubmit,
959
- className: cn("flex flex-col gap-4", className),
960
- children: [
961
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
962
- /* @__PURE__ */ jsxs(
963
- "label",
964
- {
965
- htmlFor: "thing-type",
966
- className: "text-sm font-medium text-foreground",
967
- children: [
968
- "Type ",
969
- /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
970
- ]
971
- }
972
- ),
973
- /* @__PURE__ */ jsx(
974
- "input",
975
- {
976
- ref: typeInputRef,
977
- id: "thing-type",
978
- "data-testid": "thing-type-input",
979
- type: "text",
980
- value: formData.type,
981
- onChange: (e) => handleChange("type", e.target.value),
982
- onKeyDown: handleKeyDown,
983
- disabled: isEditMode || isSubmitting,
984
- placeholder: "e.g., Task, Project, Person",
985
- "aria-invalid": Boolean(errors.type),
986
- "aria-describedby": errors.type ? "type-error" : void 0,
987
- className: cn(
988
- "h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm transition-colors",
989
- "placeholder:text-muted-foreground",
990
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
991
- "disabled:cursor-not-allowed disabled:opacity-50",
992
- errors.type ? "border-destructive focus-visible:ring-destructive" : "border-input",
993
- isEditMode && "bg-muted"
994
- )
995
- }
996
- ),
997
- errors.type && /* @__PURE__ */ jsx(
998
- "p",
999
- {
1000
- id: "type-error",
1001
- "data-testid": "field-error-type",
1002
- className: "text-sm text-destructive",
1003
- children: errors.type
1004
- }
1005
- )
1006
- ] }),
1007
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1008
- /* @__PURE__ */ jsxs(
1009
- "label",
1010
- {
1011
- htmlFor: "thing-name",
1012
- className: "text-sm font-medium text-foreground",
1013
- children: [
1014
- "Name ",
1015
- /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
1016
- ]
1017
- }
1018
- ),
1019
- /* @__PURE__ */ jsx(
1020
- "input",
1021
- {
1022
- id: "thing-name",
1023
- "data-testid": "thing-name-input",
1024
- type: "text",
1025
- value: formData.name,
1026
- onChange: (e) => handleChange("name", e.target.value),
1027
- onKeyDown: handleKeyDown,
1028
- disabled: isSubmitting,
1029
- placeholder: "Enter a name",
1030
- "aria-invalid": Boolean(errors.name),
1031
- "aria-describedby": errors.name ? "name-error" : void 0,
1032
- className: cn(
1033
- "h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm transition-colors",
1034
- "placeholder:text-muted-foreground",
1035
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1036
- "disabled:cursor-not-allowed disabled:opacity-50",
1037
- errors.name ? "border-destructive focus-visible:ring-destructive" : "border-input"
1038
- )
1039
- }
1040
- ),
1041
- errors.name && /* @__PURE__ */ jsx(
1042
- "p",
1043
- {
1044
- id: "name-error",
1045
- "data-testid": "field-error-name",
1046
- className: "text-sm text-destructive",
1047
- children: errors.name
1048
- }
1049
- )
1050
- ] }),
1051
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1052
- /* @__PURE__ */ jsx(
1053
- "label",
1054
- {
1055
- htmlFor: "thing-description",
1056
- className: "text-sm font-medium text-foreground",
1057
- children: "Description"
1058
- }
1059
- ),
1060
- /* @__PURE__ */ jsx(
1061
- "textarea",
1062
- {
1063
- id: "thing-description",
1064
- "data-testid": "thing-description-input",
1065
- value: formData.description,
1066
- onChange: (e) => handleChange("description", e.target.value),
1067
- disabled: isSubmitting,
1068
- placeholder: "Optional description",
1069
- rows: 3,
1070
- className: cn(
1071
- "w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-colors",
1072
- "placeholder:text-muted-foreground",
1073
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1074
- "disabled:cursor-not-allowed disabled:opacity-50",
1075
- "border-input resize-none"
1076
- )
1077
- }
1078
- )
1079
- ] }),
1080
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
1081
- /* @__PURE__ */ jsx(
1082
- "button",
1083
- {
1084
- type: "button",
1085
- "data-testid": "thing-cancel-btn",
1086
- onClick: onCancel,
1087
- disabled: isSubmitting,
1088
- className: cn(
1089
- "h-9 rounded-md px-4 text-sm font-medium",
1090
- "border border-input bg-background",
1091
- "hover:bg-accent hover:text-accent-foreground",
1092
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1093
- "disabled:cursor-not-allowed disabled:opacity-50"
1094
- ),
1095
- children: "Cancel"
1096
- }
1097
- ),
1098
- /* @__PURE__ */ jsx(
1099
- "button",
1100
- {
1101
- type: "submit",
1102
- "data-testid": "thing-submit-btn",
1103
- disabled: isSubmitting,
1104
- className: cn(
1105
- "h-9 rounded-md px-4 text-sm font-medium",
1106
- "bg-primary text-primary-foreground",
1107
- "hover:bg-primary/90",
1108
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1109
- "disabled:cursor-not-allowed disabled:opacity-50"
1110
- ),
1111
- children: isSubmitting ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1112
- /* @__PURE__ */ jsx(
1113
- "span",
1114
- {
1115
- "data-testid": "submit-loading",
1116
- className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
1117
- }
1118
- ),
1119
- isEditMode ? "Saving..." : "Creating..."
1120
- ] }) : isEditMode ? "Save" : "Create"
1121
- }
1122
- )
1123
- ] })
1124
- ]
1125
- }
1126
- );
1127
- }
1128
- function ThingFormDialog({
1129
- open,
1130
- thing,
1131
- onClose,
1132
- onSubmit,
1133
- isSubmitting = false
1134
- }) {
1135
- const isEditMode = Boolean(thing);
1136
- const dialogRef = React9.useRef(null);
1137
- React9.useEffect(() => {
1138
- const handleKeyDown = (e) => {
1139
- if (e.key === "Escape" && open && !isSubmitting) {
1140
- onClose();
1141
- }
1142
- };
1143
- document.addEventListener("keydown", handleKeyDown);
1144
- return () => document.removeEventListener("keydown", handleKeyDown);
1145
- }, [open, isSubmitting, onClose]);
1146
- React9.useEffect(() => {
1147
- if (open) {
1148
- document.body.style.overflow = "hidden";
1149
- } else {
1150
- document.body.style.overflow = "";
1151
- }
1152
- return () => {
1153
- document.body.style.overflow = "";
1154
- };
1155
- }, [open]);
1156
- React9.useEffect(() => {
1157
- if (open && dialogRef.current) {
1158
- const focusableElements = dialogRef.current.querySelectorAll(
1159
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1160
- );
1161
- const firstElement = focusableElements[0];
1162
- const lastElement = focusableElements[focusableElements.length - 1];
1163
- const handleTabKey = (e) => {
1164
- if (e.key === "Tab") {
1165
- if (e.shiftKey && document.activeElement === firstElement) {
1166
- e.preventDefault();
1167
- lastElement?.focus();
1168
- } else if (!e.shiftKey && document.activeElement === lastElement) {
1169
- e.preventDefault();
1170
- firstElement?.focus();
1171
- }
1172
- }
1173
- };
1174
- document.addEventListener("keydown", handleTabKey);
1175
- return () => document.removeEventListener("keydown", handleTabKey);
1176
- }
1177
- }, [open]);
1178
- if (!open) {
1179
- return null;
1180
- }
1181
- const handleBackdropClick = (e) => {
1182
- if (e.target === e.currentTarget && !isSubmitting) {
1183
- onClose();
1184
- }
1185
- };
1186
- return /* @__PURE__ */ jsx(
1187
- "div",
1188
- {
1189
- "data-testid": "dialog-backdrop",
1190
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50",
1191
- onClick: handleBackdropClick,
1192
- role: "presentation",
1193
- children: /* @__PURE__ */ jsxs(
1194
- "div",
1195
- {
1196
- ref: dialogRef,
1197
- "data-testid": "thing-form-dialog",
1198
- role: "dialog",
1199
- "aria-modal": "true",
1200
- "aria-labelledby": "dialog-title",
1201
- className: cn(
1202
- "relative w-full max-w-md rounded-lg border bg-background p-6 shadow-lg",
1203
- "animate-in fade-in-0 zoom-in-95 duration-200"
1204
- ),
1205
- children: [
1206
- /* @__PURE__ */ jsx(
1207
- "button",
1208
- {
1209
- type: "button",
1210
- onClick: onClose,
1211
- disabled: isSubmitting,
1212
- className: cn(
1213
- "absolute right-4 top-4 rounded-sm opacity-70 transition-opacity",
1214
- "hover:opacity-100",
1215
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1216
- "disabled:cursor-not-allowed disabled:opacity-50"
1217
- ),
1218
- "aria-label": "Close dialog",
1219
- children: /* @__PURE__ */ jsxs(
1220
- "svg",
1221
- {
1222
- xmlns: "http://www.w3.org/2000/svg",
1223
- width: "16",
1224
- height: "16",
1225
- viewBox: "0 0 24 24",
1226
- fill: "none",
1227
- stroke: "currentColor",
1228
- strokeWidth: "2",
1229
- strokeLinecap: "round",
1230
- strokeLinejoin: "round",
1231
- children: [
1232
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1233
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1234
- ]
1235
- }
1236
- )
1237
- }
1238
- ),
1239
- /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1240
- /* @__PURE__ */ jsx(
1241
- "h2",
1242
- {
1243
- id: "dialog-title",
1244
- "data-testid": "dialog-title",
1245
- className: "text-lg font-semibold text-foreground",
1246
- children: isEditMode ? "Edit Thing" : "Create New Thing"
1247
- }
1248
- ),
1249
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: isEditMode ? "Update the details of this Thing." : "Fill in the details to create a new Thing." })
1250
- ] }),
1251
- /* @__PURE__ */ jsx(
1252
- ThingForm,
1253
- {
1254
- thing,
1255
- onSubmit,
1256
- onCancel: onClose,
1257
- isSubmitting
1258
- }
1259
- )
1260
- ]
1261
- }
1262
- )
1263
- }
1264
- );
1265
- }
1266
- function ConfirmDialog({
1267
- open,
1268
- title,
1269
- message,
1270
- confirmText = "Confirm",
1271
- cancelText = "Cancel",
1272
- destructive = false,
1273
- onConfirm,
1274
- onCancel,
1275
- isConfirming = false
1276
- }) {
1277
- const dialogRef = React9.useRef(null);
1278
- const confirmButtonRef = React9.useRef(null);
1279
- React9.useEffect(() => {
1280
- const handleKeyDown = (e) => {
1281
- if (e.key === "Escape" && open && !isConfirming) {
1282
- onCancel();
1283
- }
1284
- };
1285
- document.addEventListener("keydown", handleKeyDown);
1286
- return () => document.removeEventListener("keydown", handleKeyDown);
1287
- }, [open, isConfirming, onCancel]);
1288
- React9.useEffect(() => {
1289
- const handleKeyDown = (e) => {
1290
- if (e.key === "Enter" && open && !isConfirming) {
1291
- e.preventDefault();
1292
- onConfirm();
1293
- }
1294
- };
1295
- document.addEventListener("keydown", handleKeyDown);
1296
- return () => document.removeEventListener("keydown", handleKeyDown);
1297
- }, [open, isConfirming, onConfirm]);
1298
- React9.useEffect(() => {
1299
- if (open) {
1300
- document.body.style.overflow = "hidden";
1301
- } else {
1302
- document.body.style.overflow = "";
1303
- }
1304
- return () => {
1305
- document.body.style.overflow = "";
1306
- };
1307
- }, [open]);
1308
- React9.useEffect(() => {
1309
- if (open && confirmButtonRef.current) {
1310
- confirmButtonRef.current.focus();
1311
- }
1312
- }, [open]);
1313
- React9.useEffect(() => {
1314
- if (open && dialogRef.current) {
1315
- const focusableElements = dialogRef.current.querySelectorAll(
1316
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1317
- );
1318
- const firstElement = focusableElements[0];
1319
- const lastElement = focusableElements[focusableElements.length - 1];
1320
- const handleTabKey = (e) => {
1321
- if (e.key === "Tab") {
1322
- if (e.shiftKey && document.activeElement === firstElement) {
1323
- e.preventDefault();
1324
- lastElement?.focus();
1325
- } else if (!e.shiftKey && document.activeElement === lastElement) {
1326
- e.preventDefault();
1327
- firstElement?.focus();
1328
- }
1329
- }
1330
- };
1331
- document.addEventListener("keydown", handleTabKey);
1332
- return () => document.removeEventListener("keydown", handleTabKey);
1333
- }
1334
- }, [open]);
1335
- if (!open) {
1336
- return null;
1337
- }
1338
- const handleBackdropClick = (e) => {
1339
- if (e.target === e.currentTarget && !isConfirming) {
1340
- onCancel();
1341
- }
1342
- };
1343
- return /* @__PURE__ */ jsx(
1344
- "div",
1345
- {
1346
- "data-testid": "dialog-backdrop",
1347
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50",
1348
- onClick: handleBackdropClick,
1349
- role: "presentation",
1350
- children: /* @__PURE__ */ jsxs(
1351
- "div",
1352
- {
1353
- ref: dialogRef,
1354
- "data-testid": "confirm-dialog",
1355
- role: "alertdialog",
1356
- "aria-modal": "true",
1357
- "aria-labelledby": "confirm-dialog-title",
1358
- "aria-describedby": "confirm-dialog-description",
1359
- className: cn(
1360
- "relative w-full max-w-md rounded-lg border bg-background p-6 shadow-lg",
1361
- "animate-in fade-in-0 zoom-in-95 duration-200"
1362
- ),
1363
- children: [
1364
- /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1365
- /* @__PURE__ */ jsx(
1366
- "h2",
1367
- {
1368
- id: "confirm-dialog-title",
1369
- className: "text-lg font-semibold text-foreground",
1370
- children: title
1371
- }
1372
- ),
1373
- /* @__PURE__ */ jsx(
1374
- "p",
1375
- {
1376
- id: "confirm-dialog-description",
1377
- className: "mt-2 text-sm text-muted-foreground",
1378
- children: message
1379
- }
1380
- )
1381
- ] }),
1382
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
1383
- /* @__PURE__ */ jsx(
1384
- "button",
1385
- {
1386
- type: "button",
1387
- "data-testid": "confirm-dialog-cancel",
1388
- onClick: onCancel,
1389
- disabled: isConfirming,
1390
- className: cn(
1391
- "h-9 rounded-md px-4 text-sm font-medium",
1392
- "border border-input bg-background",
1393
- "hover:bg-accent hover:text-accent-foreground",
1394
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1395
- "disabled:cursor-not-allowed disabled:opacity-50"
1396
- ),
1397
- children: cancelText
1398
- }
1399
- ),
1400
- /* @__PURE__ */ jsx(
1401
- "button",
1402
- {
1403
- ref: confirmButtonRef,
1404
- type: "button",
1405
- "data-testid": "confirm-dialog-confirm",
1406
- "data-variant": destructive ? "destructive" : "default",
1407
- onClick: onConfirm,
1408
- disabled: isConfirming,
1409
- className: cn(
1410
- "h-9 rounded-md px-4 text-sm font-medium",
1411
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1412
- "disabled:cursor-not-allowed disabled:opacity-50",
1413
- destructive ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "bg-primary text-primary-foreground hover:bg-primary/90"
1414
- ),
1415
- children: isConfirming ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1416
- /* @__PURE__ */ jsx(
1417
- "span",
1418
- {
1419
- "data-testid": "confirm-loading",
1420
- className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
1421
- }
1422
- ),
1423
- confirmText,
1424
- "..."
1425
- ] }) : confirmText
1426
- }
1427
- )
1428
- ] })
1429
- ]
1430
- }
1431
- )
1432
- }
1433
- );
1434
- }
1435
- function ToastNotification({ toast, onDismiss }) {
1436
- const iconsByType = {
1437
- success: /* @__PURE__ */ jsx(
1438
- "svg",
1439
- {
1440
- xmlns: "http://www.w3.org/2000/svg",
1441
- width: "16",
1442
- height: "16",
1443
- viewBox: "0 0 24 24",
1444
- fill: "none",
1445
- stroke: "currentColor",
1446
- strokeWidth: "2",
1447
- strokeLinecap: "round",
1448
- strokeLinejoin: "round",
1449
- children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
1450
- }
1451
- ),
1452
- error: /* @__PURE__ */ jsxs(
1453
- "svg",
1454
- {
1455
- xmlns: "http://www.w3.org/2000/svg",
1456
- width: "16",
1457
- height: "16",
1458
- viewBox: "0 0 24 24",
1459
- fill: "none",
1460
- stroke: "currentColor",
1461
- strokeWidth: "2",
1462
- strokeLinecap: "round",
1463
- strokeLinejoin: "round",
1464
- children: [
1465
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1466
- /* @__PURE__ */ jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
1467
- /* @__PURE__ */ jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
1468
- ]
1469
- }
1470
- ),
1471
- info: /* @__PURE__ */ jsxs(
1472
- "svg",
1473
- {
1474
- xmlns: "http://www.w3.org/2000/svg",
1475
- width: "16",
1476
- height: "16",
1477
- viewBox: "0 0 24 24",
1478
- fill: "none",
1479
- stroke: "currentColor",
1480
- strokeWidth: "2",
1481
- strokeLinecap: "round",
1482
- strokeLinejoin: "round",
1483
- children: [
1484
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1485
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
1486
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
1487
- ]
1488
- }
1489
- ),
1490
- warning: /* @__PURE__ */ jsxs(
1491
- "svg",
1492
- {
1493
- xmlns: "http://www.w3.org/2000/svg",
1494
- width: "16",
1495
- height: "16",
1496
- viewBox: "0 0 24 24",
1497
- fill: "none",
1498
- stroke: "currentColor",
1499
- strokeWidth: "2",
1500
- strokeLinecap: "round",
1501
- strokeLinejoin: "round",
1502
- children: [
1503
- /* @__PURE__ */ jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
1504
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
1505
- /* @__PURE__ */ jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
1506
- ]
1507
- }
1508
- )
1509
- };
1510
- const stylesByType = {
1511
- success: "border-green-500/50 bg-green-50 text-green-900 dark:bg-green-900/20 dark:text-green-100",
1512
- error: "border-destructive/50 bg-destructive/10 text-destructive dark:bg-destructive/20",
1513
- info: "border-blue-500/50 bg-blue-50 text-blue-900 dark:bg-blue-900/20 dark:text-blue-100",
1514
- warning: "border-yellow-500/50 bg-yellow-50 text-yellow-900 dark:bg-yellow-900/20 dark:text-yellow-100"
1515
- };
1516
- return /* @__PURE__ */ jsxs(
1517
- "div",
1518
- {
1519
- "data-testid": `toast-${toast.type}`,
1520
- role: "alert",
1521
- className: cn(
1522
- "pointer-events-auto flex items-center gap-3 rounded-lg border p-4 shadow-lg",
1523
- "animate-in slide-in-from-top-5 fade-in duration-300",
1524
- stylesByType[toast.type]
1525
- ),
1526
- children: [
1527
- /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: iconsByType[toast.type] }),
1528
- /* @__PURE__ */ jsx("p", { className: "flex-1 text-sm", children: toast.message }),
1529
- toast.action && /* @__PURE__ */ jsx(
1530
- "button",
1531
- {
1532
- "data-testid": "undo-btn",
1533
- onClick: () => {
1534
- toast.action?.onClick();
1535
- onDismiss(toast.id);
1536
- },
1537
- className: "flex-shrink-0 text-sm font-medium underline-offset-4 hover:underline",
1538
- children: toast.action.label
1539
- }
1540
- ),
1541
- /* @__PURE__ */ jsx(
1542
- "button",
1543
- {
1544
- onClick: () => onDismiss(toast.id),
1545
- className: "flex-shrink-0 rounded-sm opacity-70 hover:opacity-100",
1546
- "aria-label": "Dismiss notification",
1547
- children: /* @__PURE__ */ jsxs(
1548
- "svg",
1549
- {
1550
- xmlns: "http://www.w3.org/2000/svg",
1551
- width: "14",
1552
- height: "14",
1553
- viewBox: "0 0 24 24",
1554
- fill: "none",
1555
- stroke: "currentColor",
1556
- strokeWidth: "2",
1557
- strokeLinecap: "round",
1558
- strokeLinejoin: "round",
1559
- children: [
1560
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1561
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1562
- ]
1563
- }
1564
- )
1565
- }
1566
- )
1567
- ]
1568
- }
1569
- );
1570
- }
1571
- function ToastContainer({
1572
- toasts,
1573
- onDismiss,
1574
- position = "top-right"
1575
- }) {
1576
- const positionClasses = {
1577
- "top-right": "top-4 right-4",
1578
- "top-left": "top-4 left-4",
1579
- "bottom-right": "bottom-4 right-4",
1580
- "bottom-left": "bottom-4 left-4",
1581
- "top-center": "top-4 left-1/2 -translate-x-1/2"
1582
- };
1583
- if (toasts.length === 0) {
1584
- return null;
1585
- }
1586
- return /* @__PURE__ */ jsx(
1587
- "div",
1588
- {
1589
- "data-testid": "toast-container",
1590
- className: cn(
1591
- "fixed z-50 flex flex-col gap-2 pointer-events-none",
1592
- positionClasses[position]
1593
- ),
1594
- children: toasts.map((toast) => /* @__PURE__ */ jsx(
1595
- ToastNotification,
1596
- {
1597
- toast,
1598
- onDismiss
1599
- },
1600
- toast.id
1601
- ))
1602
- }
1603
- );
1604
- }
1605
- var ToastContext = React9.createContext(null);
1606
- function useToast() {
1607
- const context = React9.useContext(ToastContext);
1608
- if (!context) {
1609
- throw new Error("useToast must be used within a ToastProvider");
1610
- }
1611
- return context;
1612
- }
1613
- function ToastProvider({
1614
- children,
1615
- position = "top-right",
1616
- defaultDuration = 5e3
1617
- }) {
1618
- const [toasts, setToasts] = React9.useState([]);
1619
- const removeToast = React9.useCallback((id) => {
1620
- setToasts((prev) => prev.filter((t) => t.id !== id));
1621
- }, []);
1622
- const addToast = React9.useCallback(
1623
- (type, message, options) => {
1624
- const id = crypto.randomUUID();
1625
- const duration = options?.duration ?? defaultDuration;
1626
- setToasts((prev) => [...prev, { id, type, message, ...options }]);
1627
- if (duration > 0) {
1628
- setTimeout(() => removeToast(id), duration);
1629
- }
1630
- },
1631
- [defaultDuration, removeToast]
1632
- );
1633
- const contextValue = React9.useMemo(
1634
- () => ({ addToast, removeToast }),
1635
- [addToast, removeToast]
1636
- );
1637
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: contextValue, children: [
1638
- children,
1639
- /* @__PURE__ */ jsx(ToastContainer, { toasts, onDismiss: removeToast, position })
1640
- ] });
1641
- }
1642
- function ThingsPageInternal({
1643
- initialFilter,
1644
- initialSort,
1645
- initialPagination = { page: 1, perPage: 20 },
1646
- availableTypes = ["Task", "Project", "Person", "Organization", "Document", "Event"],
1647
- onParamsChange,
1648
- className
1649
- }) {
1650
- const { namespace, config } = useDO();
1651
- const { addToast } = useToast();
1652
- const dataProvider = useDataProviderSafe();
1653
- const [filter, setFilter] = React9.useState(initialFilter ?? { ns: namespace });
1654
- const [sort, setSort] = React9.useState(initialSort);
1655
- const [pagination, setPagination] = React9.useState(initialPagination);
1656
- const [selectedIds, setSelectedIds] = React9.useState([]);
1657
- const [createDialogOpen, setCreateDialogOpen] = React9.useState(false);
1658
- const [editDialogOpen, setEditDialogOpen] = React9.useState(false);
1659
- const [deleteDialogOpen, setDeleteDialogOpen] = React9.useState(false);
1660
- const [selectedThing, setSelectedThing] = React9.useState(null);
1661
- const [isSubmitting, setIsSubmitting] = React9.useState(false);
1662
- const [isDeleting, setIsDeleting] = React9.useState(false);
1663
- const { data: queryResult, isLoading, refetch } = useThings(filter, sort, pagination);
1664
- const createThing = useCreateThing();
1665
- const handleFilterChange = (newFilter) => {
1666
- setFilter(newFilter);
1667
- setPagination({ ...pagination, page: 1 });
1668
- onParamsChange?.({ filter: newFilter, sort, pagination: { ...pagination, page: 1 } });
1669
- };
1670
- const handleSortChange = (newSort) => {
1671
- setSort(newSort);
1672
- onParamsChange?.({ filter, sort: newSort, pagination });
1673
- };
1674
- const handlePaginationChange = (newPagination) => {
1675
- setPagination(newPagination);
1676
- onParamsChange?.({ filter, sort, pagination: newPagination });
1677
- };
1678
- const handleThingSelect = (thing, selected) => {
1679
- setSelectedIds(
1680
- (prev) => selected ? [...prev, thing.id] : prev.filter((id) => id !== thing.id)
1681
- );
1682
- };
1683
- const handleAction = (thing, action) => {
1684
- setSelectedThing(thing);
1685
- switch (action) {
1686
- case "edit":
1687
- setEditDialogOpen(true);
1688
- break;
1689
- case "delete":
1690
- setDeleteDialogOpen(true);
1691
- break;
1692
- case "view":
1693
- console.log("View thing:", thing);
1694
- break;
1695
- case "history":
1696
- console.log("View history:", thing);
1697
- break;
1698
- }
1699
- };
1700
- const handleCreate = async (data) => {
1701
- setIsSubmitting(true);
1702
- try {
1703
- await createThing.mutateAsync({
1704
- ns: namespace,
1705
- type: data.type,
1706
- name: data.name,
1707
- data: data.description ? { description: data.description } : {}
1708
- });
1709
- setCreateDialogOpen(false);
1710
- addToast("success", `${data.name} created successfully`);
1711
- refetch();
1712
- } catch (error) {
1713
- const message = error instanceof Error ? error.message : "Failed to create";
1714
- addToast("error", message);
1715
- } finally {
1716
- setIsSubmitting(false);
1717
- }
1718
- };
1719
- const handleEdit = async (data) => {
1720
- if (!selectedThing) return;
1721
- setIsSubmitting(true);
1722
- try {
1723
- if (dataProvider) {
1724
- const updateData = {
1725
- name: data.name,
1726
- data: data.description ? { description: data.description } : void 0
1727
- };
1728
- await dataProvider.update("Thing", {
1729
- id: selectedThing.id,
1730
- data: {
1731
- ns: selectedThing.ns,
1732
- type: selectedThing.type,
1733
- ...updateData
1734
- }
1735
- });
1736
- } else {
1737
- const response = await fetch(
1738
- `${config.apiEndpoint}/things/${encodeURIComponent(selectedThing.ns)}/${encodeURIComponent(selectedThing.type)}/${encodeURIComponent(selectedThing.id)}`,
1739
- {
1740
- method: "PATCH",
1741
- headers: {
1742
- "Content-Type": "application/json",
1743
- ...config.authToken ? { Authorization: `Bearer ${config.authToken}` } : {}
1744
- },
1745
- body: JSON.stringify({
1746
- name: data.name,
1747
- data: data.description ? { description: data.description } : void 0
1748
- })
1749
- }
1750
- );
1751
- if (!response.ok) throw new Error("Failed to update thing");
1752
- }
1753
- setEditDialogOpen(false);
1754
- setSelectedThing(null);
1755
- addToast("success", `${data.name} updated successfully`);
1756
- refetch();
1757
- } catch (error) {
1758
- const message = error instanceof Error ? error.message : "Failed to update";
1759
- addToast("error", message);
1760
- } finally {
1761
- setIsSubmitting(false);
1762
- }
1763
- };
1764
- const handleDelete = async () => {
1765
- if (!selectedThing) return;
1766
- setIsDeleting(true);
1767
- try {
1768
- if (dataProvider) {
1769
- await dataProvider.delete("Thing", { id: selectedThing.id });
1770
- } else {
1771
- const response = await fetch(
1772
- `${config.apiEndpoint}/things/${encodeURIComponent(selectedThing.ns)}/${encodeURIComponent(selectedThing.type)}/${encodeURIComponent(selectedThing.id)}`,
1773
- {
1774
- method: "DELETE",
1775
- headers: config.authToken ? { Authorization: `Bearer ${config.authToken}` } : {}
1776
- }
1777
- );
1778
- if (!response.ok) throw new Error("Failed to delete thing");
1779
- }
1780
- setDeleteDialogOpen(false);
1781
- const deletedName = selectedThing.name;
1782
- setSelectedThing(null);
1783
- setSelectedIds((prev) => prev.filter((id) => id !== selectedThing.id));
1784
- addToast("success", `${deletedName} deleted`, {
1785
- action: {
1786
- label: "Undo",
1787
- onClick: () => {
1788
- console.log("Undo delete");
1789
- }
1790
- }
1791
- });
1792
- refetch();
1793
- } catch (error) {
1794
- const message = error instanceof Error ? error.message : "Failed to delete";
1795
- addToast("error", message);
1796
- } finally {
1797
- setIsDeleting(false);
1798
- }
1799
- };
1800
- return /* @__PURE__ */ jsxs("div", { className, children: [
1801
- /* @__PURE__ */ jsx(
1802
- ThingsList,
1803
- {
1804
- things: queryResult?.data ?? [],
1805
- total: queryResult?.total ?? 0,
1806
- loading: isLoading,
1807
- selectedIds,
1808
- filter,
1809
- sort,
1810
- pagination,
1811
- availableTypes,
1812
- onFilterChange: handleFilterChange,
1813
- onSortChange: handleSortChange,
1814
- onPaginationChange: handlePaginationChange,
1815
- onThingSelect: handleThingSelect,
1816
- onAction: handleAction,
1817
- onCreate: () => setCreateDialogOpen(true)
1818
- }
1819
- ),
1820
- /* @__PURE__ */ jsx(
1821
- ThingFormDialog,
1822
- {
1823
- open: createDialogOpen,
1824
- onClose: () => setCreateDialogOpen(false),
1825
- onSubmit: handleCreate,
1826
- isSubmitting
1827
- }
1828
- ),
1829
- /* @__PURE__ */ jsx(
1830
- ThingFormDialog,
1831
- {
1832
- open: editDialogOpen,
1833
- thing: selectedThing ?? void 0,
1834
- onClose: () => {
1835
- setEditDialogOpen(false);
1836
- setSelectedThing(null);
1837
- },
1838
- onSubmit: handleEdit,
1839
- isSubmitting
1840
- }
1841
- ),
1842
- /* @__PURE__ */ jsx(
1843
- ConfirmDialog,
1844
- {
1845
- open: deleteDialogOpen,
1846
- title: "Delete Thing",
1847
- message: `Are you sure you want to delete "${selectedThing?.name}"? This action cannot be undone.`,
1848
- confirmText: "Delete",
1849
- destructive: true,
1850
- onConfirm: handleDelete,
1851
- onCancel: () => {
1852
- setDeleteDialogOpen(false);
1853
- setSelectedThing(null);
1854
- },
1855
- isConfirming: isDeleting
1856
- }
1857
- )
1858
- ] });
1859
- }
1860
- function ThingsPage(props) {
1861
- return /* @__PURE__ */ jsx(ToastProvider, { position: "top-right", children: /* @__PURE__ */ jsx(ThingsPageInternal, { ...props }) });
1862
- }
1863
- var STATUS_COLORS = {
1864
- active: "bg-green-500/10 text-green-600 dark:text-green-400",
1865
- draft: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
1866
- deprecated: "bg-orange-500/10 text-orange-600 dark:text-orange-400",
1867
- archived: "bg-muted text-muted-foreground"
1868
- };
1869
- function WorkflowsList({
1870
- workflows,
1871
- total,
1872
- loading = false,
1873
- filter,
1874
- onFilterChange,
1875
- onWorkflowClick,
1876
- onTriggerWorkflow,
1877
- onCreate,
1878
- className
1879
- }) {
1880
- const [searchValue, setSearchValue] = React9.useState(filter?.nameSearch || "");
1881
- const [openMenuId, setOpenMenuId] = React9.useState(null);
1882
- const handleSearch = (value) => {
1883
- setSearchValue(value);
1884
- onFilterChange?.({ ...filter, nameSearch: value || void 0 });
1885
- };
1886
- const handleStatusFilter = (e) => {
1887
- const value = e.target.value;
1888
- if (value === "all") {
1889
- onFilterChange?.({ ...filter, status: void 0 });
1890
- } else {
1891
- onFilterChange?.({ ...filter, status: [value] });
1892
- }
1893
- };
1894
- if (!loading && workflows.length === 0) {
1895
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
1896
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border p-4", children: [
1897
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1898
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1899
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1900
- /* @__PURE__ */ jsx(
1901
- "input",
1902
- {
1903
- type: "text",
1904
- placeholder: "Search workflows...",
1905
- value: searchValue,
1906
- onChange: (e) => handleSearch(e.target.value),
1907
- className: cn(
1908
- "h-9 w-64 rounded-md border border-input bg-background pl-9 pr-4 text-sm",
1909
- "placeholder:text-muted-foreground",
1910
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1911
- )
1912
- }
1913
- )
1914
- ] }),
1915
- /* @__PURE__ */ jsxs(
1916
- "select",
1917
- {
1918
- "data-testid": "status-filter",
1919
- value: filter?.status?.[0] || "all",
1920
- onChange: handleStatusFilter,
1921
- className: cn(
1922
- "h-9 rounded-md border border-input bg-background px-3 text-sm",
1923
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1924
- ),
1925
- children: [
1926
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Statuses" }),
1927
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
1928
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
1929
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" }),
1930
- /* @__PURE__ */ jsx("option", { value: "archived", children: "Archived" })
1931
- ]
1932
- }
1933
- )
1934
- ] }),
1935
- onCreate && /* @__PURE__ */ jsxs(
1936
- "button",
1937
- {
1938
- "data-testid": "create-workflow-btn",
1939
- onClick: onCreate,
1940
- className: cn(
1941
- "flex h-9 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground",
1942
- "hover:bg-primary/90"
1943
- ),
1944
- children: [
1945
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
1946
- "Create Workflow"
1947
- ]
1948
- }
1949
- )
1950
- ] }),
1951
- /* @__PURE__ */ jsxs(
1952
- "div",
1953
- {
1954
- "data-testid": "workflows-empty-state",
1955
- className: "flex flex-1 flex-col items-center justify-center p-12 text-center",
1956
- children: [
1957
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(Play, { className: "h-8 w-8 text-muted-foreground" }) }),
1958
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "No workflows found" }),
1959
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-4 max-w-sm", children: "Workflows orchestrate multi-step business processes. Create your first workflow to get started." }),
1960
- onCreate && /* @__PURE__ */ jsxs(
1961
- "button",
1962
- {
1963
- onClick: onCreate,
1964
- className: cn(
1965
- "flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
1966
- "hover:bg-primary/90"
1967
- ),
1968
- children: [
1969
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
1970
- "Create Workflow"
1971
- ]
1972
- }
1973
- )
1974
- ]
1975
- }
1976
- )
1977
- ] });
1978
- }
1979
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
1980
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border p-4", children: [
1981
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1982
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1983
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
1984
- /* @__PURE__ */ jsx(
1985
- "input",
1986
- {
1987
- type: "text",
1988
- placeholder: "Search workflows...",
1989
- value: searchValue,
1990
- onChange: (e) => handleSearch(e.target.value),
1991
- className: cn(
1992
- "h-9 w-64 rounded-md border border-input bg-background pl-9 pr-4 text-sm",
1993
- "placeholder:text-muted-foreground",
1994
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1995
- )
1996
- }
1997
- )
1998
- ] }),
1999
- /* @__PURE__ */ jsxs(
2000
- "select",
2001
- {
2002
- "data-testid": "status-filter",
2003
- value: filter?.status?.[0] || "all",
2004
- onChange: handleStatusFilter,
2005
- className: cn(
2006
- "h-9 rounded-md border border-input bg-background px-3 text-sm",
2007
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2008
- ),
2009
- children: [
2010
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Statuses" }),
2011
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
2012
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
2013
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" }),
2014
- /* @__PURE__ */ jsx("option", { value: "archived", children: "Archived" })
2015
- ]
2016
- }
2017
- )
2018
- ] }),
2019
- onCreate && /* @__PURE__ */ jsxs(
2020
- "button",
2021
- {
2022
- "data-testid": "create-workflow-btn",
2023
- onClick: onCreate,
2024
- className: cn(
2025
- "flex h-9 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground",
2026
- "hover:bg-primary/90"
2027
- ),
2028
- children: [
2029
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
2030
- "Create Workflow"
2031
- ]
2032
- }
2033
- )
2034
- ] }),
2035
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxs("table", { "data-testid": "workflows-table", className: "w-full", children: [
2036
- /* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { children: [
2037
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Name" }),
2038
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Status" }),
2039
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Trigger" }),
2040
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Last Execution" }),
2041
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Executions" }),
2042
- /* @__PURE__ */ jsx("th", { className: "w-24 p-4" })
2043
- ] }) }),
2044
- /* @__PURE__ */ jsx("tbody", { children: loading ? (
2045
- // Loading skeleton
2046
- Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "animate-pulse border-b border-border", children: [
2047
- /* @__PURE__ */ jsxs("td", { className: "p-4", children: [
2048
- /* @__PURE__ */ jsx("div", { className: "h-4 w-48 rounded bg-muted mb-1" }),
2049
- /* @__PURE__ */ jsx("div", { className: "h-3 w-64 rounded bg-muted" })
2050
- ] }),
2051
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-6 w-16 rounded bg-muted" }) }),
2052
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted" }) }),
2053
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted" }) }),
2054
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-12 rounded bg-muted" }) }),
2055
- /* @__PURE__ */ jsx("td", { className: "p-4" })
2056
- ] }, i))
2057
- ) : workflows.map((workflow) => /* @__PURE__ */ jsxs(
2058
- "tr",
2059
- {
2060
- "data-testid": "workflow-row",
2061
- className: "cursor-pointer border-b border-border hover:bg-muted/50",
2062
- onClick: () => onWorkflowClick?.(workflow),
2063
- children: [
2064
- /* @__PURE__ */ jsxs("td", { className: "p-4", children: [
2065
- /* @__PURE__ */ jsx("div", { "data-testid": "workflow-name", className: "font-medium", children: workflow.name }),
2066
- /* @__PURE__ */ jsx("div", { "data-testid": "workflow-description", className: "text-sm text-muted-foreground", children: workflow.description })
2067
- ] }),
2068
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx(
2069
- "span",
2070
- {
2071
- "data-testid": "workflow-status-badge",
2072
- className: cn(
2073
- "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
2074
- STATUS_COLORS[workflow.status]
2075
- ),
2076
- children: workflow.status
2077
- }
2078
- ) }),
2079
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-1 text-xs font-medium", children: workflow.trigger.type }) }),
2080
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx(
2081
- "span",
2082
- {
2083
- "data-testid": "workflow-last-execution",
2084
- className: "text-sm text-muted-foreground",
2085
- children: workflow.updatedAt ? formatRelativeTime(workflow.updatedAt) : "Never"
2086
- }
2087
- ) }),
2088
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx(
2089
- "span",
2090
- {
2091
- "data-testid": "workflow-execution-count",
2092
- className: "text-sm",
2093
- children: formatNumber(0)
2094
- }
2095
- ) }),
2096
- /* @__PURE__ */ jsx("td", { className: "p-4", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2097
- workflow.status === "active" && onTriggerWorkflow && /* @__PURE__ */ jsxs(
2098
- "button",
2099
- {
2100
- "data-testid": "trigger-workflow-btn",
2101
- onClick: () => onTriggerWorkflow(workflow),
2102
- className: cn(
2103
- "flex h-8 items-center gap-1 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground",
2104
- "hover:bg-primary/90"
2105
- ),
2106
- children: [
2107
- /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
2108
- "Trigger"
2109
- ]
2110
- }
2111
- ),
2112
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2113
- /* @__PURE__ */ jsx(
2114
- "button",
2115
- {
2116
- onClick: () => setOpenMenuId(openMenuId === workflow.id ? null : workflow.id),
2117
- className: "flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
2118
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" })
2119
- }
2120
- ),
2121
- openMenuId === workflow.id && /* @__PURE__ */ jsxs(Fragment, { children: [
2122
- /* @__PURE__ */ jsx(
2123
- "div",
2124
- {
2125
- className: "fixed inset-0 z-40",
2126
- onClick: () => setOpenMenuId(null)
2127
- }
2128
- ),
2129
- /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full z-50 mt-1 w-36 rounded-md border border-border bg-popover py-1 shadow-lg", children: /* @__PURE__ */ jsxs(
2130
- "button",
2131
- {
2132
- onClick: () => {
2133
- onWorkflowClick?.(workflow);
2134
- setOpenMenuId(null);
2135
- },
2136
- className: "flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent",
2137
- children: [
2138
- /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }),
2139
- " View"
2140
- ]
2141
- }
2142
- ) })
2143
- ] })
2144
- ] })
2145
- ] }) })
2146
- ]
2147
- },
2148
- workflow.id
2149
- )) })
2150
- ] }) }),
2151
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between border-t border-border p-4", children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
2152
- "Showing ",
2153
- workflows.length,
2154
- " of ",
2155
- total,
2156
- " workflows"
2157
- ] }) })
2158
- ] });
2159
- }
2160
- var STATUS_ICONS = {
2161
- pending: Clock,
2162
- running: Loader2,
2163
- completed: CheckCircle2,
2164
- failed: XCircle,
2165
- cancelled: StopCircle,
2166
- compensating: RefreshCw,
2167
- compensated: CheckCircle2
2168
- };
2169
- var STATUS_COLORS2 = {
2170
- pending: "text-yellow-500",
2171
- running: "text-blue-500",
2172
- completed: "text-green-500",
2173
- failed: "text-red-500",
2174
- cancelled: "text-gray-500",
2175
- compensating: "text-orange-500",
2176
- compensated: "text-green-500"
2177
- };
2178
- var STATUS_BG_COLORS = {
2179
- pending: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
2180
- running: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
2181
- completed: "bg-green-500/10 text-green-600 dark:text-green-400",
2182
- failed: "bg-red-500/10 text-red-600 dark:text-red-400",
2183
- cancelled: "bg-muted text-muted-foreground",
2184
- compensating: "bg-orange-500/10 text-orange-600 dark:text-orange-400",
2185
- compensated: "bg-green-500/10 text-green-600 dark:text-green-400"
2186
- };
2187
- function StepItem({
2188
- step,
2189
- expanded,
2190
- onToggle
2191
- }) {
2192
- const StatusIcon = STATUS_ICONS[step.status];
2193
- const isRunning = step.status === "running";
2194
- return /* @__PURE__ */ jsxs(
2195
- "div",
2196
- {
2197
- "data-testid": "step-item",
2198
- "data-status": step.status,
2199
- className: "border border-border rounded-lg overflow-hidden",
2200
- children: [
2201
- /* @__PURE__ */ jsxs(
2202
- "button",
2203
- {
2204
- onClick: onToggle,
2205
- className: "flex w-full items-center gap-3 p-4 text-left hover:bg-muted/50",
2206
- children: [
2207
- /* @__PURE__ */ jsx(
2208
- StatusIcon,
2209
- {
2210
- className: cn(
2211
- "h-5 w-5",
2212
- STATUS_COLORS2[step.status],
2213
- isRunning && "animate-spin"
2214
- )
2215
- }
2216
- ),
2217
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2218
- /* @__PURE__ */ jsx("div", { className: "font-medium truncate", children: step.stepName }),
2219
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
2220
- "Step ID: ",
2221
- step.stepId
2222
- ] })
2223
- ] }),
2224
- /* @__PURE__ */ jsx("div", { "data-testid": "step-status", className: "text-sm text-muted-foreground", children: step.status }),
2225
- step.durationMs !== void 0 && /* @__PURE__ */ jsx("div", { "data-testid": "step-duration", className: "text-sm text-muted-foreground", children: formatDuration(step.durationMs) }),
2226
- expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
2227
- ]
2228
- }
2229
- ),
2230
- expanded && /* @__PURE__ */ jsxs("div", { "data-testid": "step-details", className: "border-t border-border p-4 bg-muted/30", children: [
2231
- step.error && /* @__PURE__ */ jsxs(
2232
- "div",
2233
- {
2234
- "data-testid": "step-error",
2235
- className: "mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-md",
2236
- children: [
2237
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400 font-medium mb-1", children: [
2238
- /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
2239
- "Error"
2240
- ] }),
2241
- /* @__PURE__ */ jsx("div", { className: "text-sm text-red-600/80 dark:text-red-400/80", children: step.error.message })
2242
- ]
2243
- }
2244
- ),
2245
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
2246
- /* @__PURE__ */ jsxs("div", { children: [
2247
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-2", children: "Input" }),
2248
- /* @__PURE__ */ jsx(
2249
- "pre",
2250
- {
2251
- "data-testid": "step-input",
2252
- className: "text-xs bg-background border border-border rounded-md p-3 overflow-auto max-h-48",
2253
- children: JSON.stringify(step.input, null, 2)
2254
- }
2255
- )
2256
- ] }),
2257
- /* @__PURE__ */ jsxs("div", { children: [
2258
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-2", children: "Output" }),
2259
- /* @__PURE__ */ jsx(
2260
- "pre",
2261
- {
2262
- "data-testid": "step-output",
2263
- className: "text-xs bg-background border border-border rounded-md p-3 overflow-auto max-h-48",
2264
- children: step.output ? JSON.stringify(step.output, null, 2) : "No output yet"
2265
- }
2266
- )
2267
- ] })
2268
- ] }),
2269
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex gap-6 text-sm text-muted-foreground", children: [
2270
- /* @__PURE__ */ jsxs("div", { children: [
2271
- "Started: ",
2272
- formatDateTime(step.startedAt)
2273
- ] }),
2274
- step.completedAt && /* @__PURE__ */ jsxs("div", { children: [
2275
- "Completed: ",
2276
- formatDateTime(step.completedAt)
2277
- ] }),
2278
- step.retryCount > 0 && /* @__PURE__ */ jsxs("div", { children: [
2279
- "Retries: ",
2280
- step.retryCount
2281
- ] })
2282
- ] })
2283
- ] })
2284
- ]
2285
- }
2286
- );
2287
- }
2288
- function WorkflowExecutionView({
2289
- workflow,
2290
- execution,
2291
- executions = [],
2292
- loading = false,
2293
- activeTab = "details",
2294
- onTabChange,
2295
- onExecutionClick,
2296
- onCancelExecution,
2297
- onRetryExecution,
2298
- className
2299
- }) {
2300
- const [expandedSteps, setExpandedSteps] = React9.useState(/* @__PURE__ */ new Set());
2301
- const [showCancelConfirm, setShowCancelConfirm] = React9.useState(false);
2302
- const toggleStep = (stepId) => {
2303
- const newExpanded = new Set(expandedSteps);
2304
- if (newExpanded.has(stepId)) {
2305
- newExpanded.delete(stepId);
2306
- } else {
2307
- newExpanded.add(stepId);
2308
- }
2309
- setExpandedSteps(newExpanded);
2310
- };
2311
- const handleCancel = () => {
2312
- if (execution) {
2313
- onCancelExecution?.(execution.id);
2314
- setShowCancelConfirm(false);
2315
- }
2316
- };
2317
- const canCancel = execution && (execution.status === "running" || execution.status === "pending");
2318
- const canRetry = execution && execution.status === "failed";
2319
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
2320
- execution && /* @__PURE__ */ jsxs("div", { className: "border-b border-border p-4", children: [
2321
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
2322
- /* @__PURE__ */ jsxs("div", { children: [
2323
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: workflow.name }),
2324
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
2325
- "Execution: ",
2326
- execution.id
2327
- ] })
2328
- ] }),
2329
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2330
- /* @__PURE__ */ jsxs(
2331
- "span",
2332
- {
2333
- "data-testid": "execution-status",
2334
- className: cn(
2335
- "inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium",
2336
- STATUS_BG_COLORS[execution.status]
2337
- ),
2338
- children: [
2339
- React9.createElement(STATUS_ICONS[execution.status], {
2340
- className: cn(
2341
- "h-4 w-4",
2342
- execution.status === "running" && "animate-spin"
2343
- )
2344
- }),
2345
- execution.status
2346
- ]
2347
- }
2348
- ),
2349
- canCancel && onCancelExecution && /* @__PURE__ */ jsxs(
2350
- "button",
2351
- {
2352
- "data-testid": "cancel-execution-btn",
2353
- onClick: () => setShowCancelConfirm(true),
2354
- className: cn(
2355
- "flex items-center gap-2 rounded-md border border-destructive px-3 py-1 text-sm font-medium text-destructive",
2356
- "hover:bg-destructive/10"
2357
- ),
2358
- children: [
2359
- /* @__PURE__ */ jsx(StopCircle, { className: "h-4 w-4" }),
2360
- "Cancel"
2361
- ]
2362
- }
2363
- ),
2364
- canRetry && onRetryExecution && /* @__PURE__ */ jsxs(
2365
- "button",
2366
- {
2367
- "data-testid": "retry-execution-btn",
2368
- onClick: () => onRetryExecution(execution),
2369
- className: cn(
2370
- "flex items-center gap-2 rounded-md bg-primary px-3 py-1 text-sm font-medium text-primary-foreground",
2371
- "hover:bg-primary/90"
2372
- ),
2373
- children: [
2374
- /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" }),
2375
- "Retry"
2376
- ]
2377
- }
2378
- )
2379
- ] })
2380
- ] }),
2381
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-6 text-sm text-muted-foreground", children: [
2382
- /* @__PURE__ */ jsxs("div", { "data-testid": "execution-start-time", children: [
2383
- "Started: ",
2384
- formatDateTime(execution.startedAt)
2385
- ] }),
2386
- execution.completedAt && /* @__PURE__ */ jsxs("div", { "data-testid": "execution-end-time", children: [
2387
- "Completed: ",
2388
- formatDateTime(execution.completedAt)
2389
- ] }),
2390
- execution.durationMs !== void 0 && /* @__PURE__ */ jsxs("div", { children: [
2391
- "Duration: ",
2392
- formatDuration(execution.durationMs)
2393
- ] })
2394
- ] }),
2395
- execution.error && /* @__PURE__ */ jsxs(
2396
- "div",
2397
- {
2398
- "data-testid": "execution-error",
2399
- className: "mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-md",
2400
- children: [
2401
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400 font-medium mb-1", children: [
2402
- /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
2403
- "Execution Error"
2404
- ] }),
2405
- /* @__PURE__ */ jsx("div", { className: "text-sm text-red-600/80 dark:text-red-400/80", children: execution.error.message })
2406
- ]
2407
- }
2408
- )
2409
- ] }),
2410
- /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-4 px-4", children: [
2411
- /* @__PURE__ */ jsx(
2412
- "button",
2413
- {
2414
- onClick: () => onTabChange?.("details"),
2415
- className: cn(
2416
- "py-3 text-sm font-medium border-b-2 -mb-px",
2417
- activeTab === "details" ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
2418
- ),
2419
- children: "Details"
2420
- }
2421
- ),
2422
- /* @__PURE__ */ jsx(
2423
- "button",
2424
- {
2425
- "data-testid": "executions-tab",
2426
- onClick: () => onTabChange?.("executions"),
2427
- className: cn(
2428
- "py-3 text-sm font-medium border-b-2 -mb-px",
2429
- activeTab === "executions" ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
2430
- ),
2431
- children: "Executions"
2432
- }
2433
- )
2434
- ] }) }),
2435
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto p-4", children: [
2436
- activeTab === "details" && execution && /* @__PURE__ */ jsxs(Fragment, { children: [
2437
- /* @__PURE__ */ jsxs("div", { "data-testid": "execution-timeline", className: "mb-6", children: [
2438
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-3", children: "Timeline" }),
2439
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 overflow-x-auto pb-2", children: execution.steps.map((step, index) => {
2440
- const StatusIcon = STATUS_ICONS[step.status];
2441
- const widthPercent = execution.durationMs ? Math.max(10, (step.durationMs || 0) / execution.durationMs * 100) : 100 / execution.steps.length;
2442
- return /* @__PURE__ */ jsxs(React9.Fragment, { children: [
2443
- /* @__PURE__ */ jsxs(
2444
- "div",
2445
- {
2446
- className: cn(
2447
- "flex items-center gap-2 px-3 py-2 rounded-md border",
2448
- step.status === "completed" && "border-green-500/30 bg-green-500/5",
2449
- step.status === "running" && "border-blue-500/30 bg-blue-500/5",
2450
- step.status === "failed" && "border-red-500/30 bg-red-500/5",
2451
- step.status === "pending" && "border-border bg-muted/30"
2452
- ),
2453
- style: { minWidth: `${Math.max(widthPercent, 15)}%` },
2454
- children: [
2455
- /* @__PURE__ */ jsx(
2456
- StatusIcon,
2457
- {
2458
- className: cn(
2459
- "h-4 w-4 flex-shrink-0",
2460
- STATUS_COLORS2[step.status],
2461
- step.status === "running" && "animate-spin"
2462
- )
2463
- }
2464
- ),
2465
- /* @__PURE__ */ jsx("span", { className: "text-xs truncate", children: step.stepName })
2466
- ]
2467
- }
2468
- ),
2469
- index < execution.steps.length - 1 && /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" })
2470
- ] }, step.stepId);
2471
- }) })
2472
- ] }),
2473
- /* @__PURE__ */ jsxs("div", { "data-testid": "step-list", className: "space-y-3", children: [
2474
- /* @__PURE__ */ jsxs("div", { className: "text-sm font-medium", children: [
2475
- "Steps (",
2476
- execution.steps.length,
2477
- ")"
2478
- ] }),
2479
- execution.steps.map((step) => /* @__PURE__ */ jsx(
2480
- StepItem,
2481
- {
2482
- step,
2483
- expanded: expandedSteps.has(step.stepId),
2484
- onToggle: () => toggleStep(step.stepId)
2485
- },
2486
- step.stepId
2487
- ))
2488
- ] })
2489
- ] }),
2490
- activeTab === "executions" && /* @__PURE__ */ jsx(Fragment, { children: loading ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "animate-pulse p-4 border border-border rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
2491
- /* @__PURE__ */ jsx("div", { className: "h-6 w-20 rounded bg-muted" }),
2492
- /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted" }),
2493
- /* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted ml-auto" })
2494
- ] }) }, i)) }) : executions.length === 0 ? /* @__PURE__ */ jsxs(
2495
- "div",
2496
- {
2497
- "data-testid": "executions-empty-state",
2498
- className: "flex flex-col items-center justify-center py-12 text-center",
2499
- children: [
2500
- /* @__PURE__ */ jsx(Clock, { className: "h-12 w-12 text-muted-foreground mb-4" }),
2501
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "No executions yet" }),
2502
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "This workflow has not been executed. Trigger it to see execution history." })
2503
- ]
2504
- }
2505
- ) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: executions.map((exec) => {
2506
- const StatusIcon = STATUS_ICONS[exec.status];
2507
- return /* @__PURE__ */ jsxs(
2508
- "div",
2509
- {
2510
- "data-testid": "execution-row",
2511
- onClick: () => onExecutionClick?.(exec),
2512
- className: "flex items-center gap-4 p-4 border border-border rounded-lg cursor-pointer hover:bg-muted/50",
2513
- children: [
2514
- /* @__PURE__ */ jsxs(
2515
- "span",
2516
- {
2517
- "data-testid": "execution-status",
2518
- className: cn(
2519
- "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium",
2520
- STATUS_BG_COLORS[exec.status]
2521
- ),
2522
- children: [
2523
- /* @__PURE__ */ jsx(
2524
- StatusIcon,
2525
- {
2526
- className: cn(
2527
- "h-3.5 w-3.5",
2528
- exec.status === "running" && "animate-spin"
2529
- )
2530
- }
2531
- ),
2532
- exec.status
2533
- ]
2534
- }
2535
- ),
2536
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2537
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium truncate", children: exec.id }),
2538
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
2539
- "Triggered by: ",
2540
- exec.trigger.source
2541
- ] })
2542
- ] }),
2543
- /* @__PURE__ */ jsx(
2544
- "div",
2545
- {
2546
- "data-testid": "execution-timestamp",
2547
- className: "text-sm text-muted-foreground",
2548
- children: formatRelativeTime(exec.startedAt)
2549
- }
2550
- ),
2551
- exec.durationMs !== void 0 && /* @__PURE__ */ jsx(
2552
- "div",
2553
- {
2554
- "data-testid": "execution-duration",
2555
- className: "text-sm text-muted-foreground",
2556
- children: formatDuration(exec.durationMs)
2557
- }
2558
- ),
2559
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
2560
- ]
2561
- },
2562
- exec.id
2563
- );
2564
- }) }) })
2565
- ] }),
2566
- showCancelConfirm && /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
2567
- /* @__PURE__ */ jsx(
2568
- "div",
2569
- {
2570
- className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
2571
- onClick: () => setShowCancelConfirm(false)
2572
- }
2573
- ),
2574
- /* @__PURE__ */ jsxs(
2575
- "div",
2576
- {
2577
- "data-testid": "cancel-confirm-dialog",
2578
- className: "relative z-50 w-full max-w-md rounded-lg border border-border bg-background p-6 shadow-lg",
2579
- children: [
2580
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "Cancel Execution" }),
2581
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-4", children: "Are you sure you want to cancel this workflow execution? This action cannot be undone." }),
2582
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2583
- /* @__PURE__ */ jsx(
2584
- "button",
2585
- {
2586
- "data-testid": "dismiss-cancel-btn",
2587
- onClick: () => setShowCancelConfirm(false),
2588
- className: "px-4 py-2 text-sm font-medium rounded-md hover:bg-accent",
2589
- children: "Keep Running"
2590
- }
2591
- ),
2592
- /* @__PURE__ */ jsx(
2593
- "button",
2594
- {
2595
- "data-testid": "confirm-cancel-btn",
2596
- onClick: handleCancel,
2597
- className: "px-4 py-2 text-sm font-medium rounded-md bg-destructive text-destructive-foreground hover:bg-destructive/90",
2598
- children: "Cancel Execution"
2599
- }
2600
- )
2601
- ] })
2602
- ]
2603
- }
2604
- )
2605
- ] })
2606
- ] });
2607
- }
2608
- function TriggerWorkflowDialog({
2609
- workflow,
2610
- open,
2611
- loading = false,
2612
- onClose,
2613
- onTrigger,
2614
- className
2615
- }) {
2616
- const [inputValue, setInputValue] = React9.useState("{}");
2617
- const [parseError, setParseError] = React9.useState(null);
2618
- React9.useEffect(() => {
2619
- if (open) {
2620
- setInputValue("{}");
2621
- setParseError(null);
2622
- }
2623
- }, [open]);
2624
- const handleInputChange = (value) => {
2625
- setInputValue(value);
2626
- try {
2627
- JSON.parse(value);
2628
- setParseError(null);
2629
- } catch {
2630
- setParseError("Invalid JSON format");
2631
- }
2632
- };
2633
- const handleTrigger = () => {
2634
- try {
2635
- const parsed = JSON.parse(inputValue);
2636
- onTrigger(parsed);
2637
- } catch {
2638
- setParseError("Invalid JSON format");
2639
- }
2640
- };
2641
- const handleKeyDown = (e) => {
2642
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && !parseError && !loading) {
2643
- handleTrigger();
2644
- }
2645
- if (e.key === "Escape") {
2646
- onClose();
2647
- }
2648
- };
2649
- if (!open) return null;
2650
- const manualTrigger = workflow.trigger.type === "manual" ? workflow.trigger : null;
2651
- const hasInputSchema = manualTrigger && "inputSchema" in manualTrigger && manualTrigger.inputSchema;
2652
- return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
2653
- /* @__PURE__ */ jsx(
2654
- "div",
2655
- {
2656
- className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
2657
- onClick: onClose
2658
- }
2659
- ),
2660
- /* @__PURE__ */ jsxs(
2661
- "div",
2662
- {
2663
- "data-testid": "trigger-dialog",
2664
- className: cn(
2665
- "relative z-50 w-full max-w-lg rounded-lg border border-border bg-background shadow-lg",
2666
- className
2667
- ),
2668
- onKeyDown: handleKeyDown,
2669
- children: [
2670
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-6 py-4", children: [
2671
- /* @__PURE__ */ jsxs("div", { children: [
2672
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Trigger Workflow" }),
2673
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: workflow.name })
2674
- ] }),
2675
- /* @__PURE__ */ jsx(
2676
- "button",
2677
- {
2678
- onClick: onClose,
2679
- className: "flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
2680
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
2681
- }
2682
- )
2683
- ] }),
2684
- /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
2685
- /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: workflow.description || "Trigger this workflow manually with optional input parameters." }) }),
2686
- hasInputSchema && manualTrigger && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-muted/50 rounded-md", children: [
2687
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-1", children: "Expected Input Schema" }),
2688
- /* @__PURE__ */ jsx("pre", { className: "text-xs text-muted-foreground overflow-auto", children: JSON.stringify(manualTrigger.inputSchema, null, 2) })
2689
- ] }),
2690
- /* @__PURE__ */ jsxs("div", { children: [
2691
- /* @__PURE__ */ jsx("label", { htmlFor: "trigger-input", className: "text-sm font-medium mb-2 block", children: "Input Parameters (JSON)" }),
2692
- /* @__PURE__ */ jsx(
2693
- "textarea",
2694
- {
2695
- id: "trigger-input",
2696
- "data-testid": "trigger-input",
2697
- value: inputValue,
2698
- onChange: (e) => handleInputChange(e.target.value),
2699
- placeholder: '{"key": "value"}',
2700
- rows: 6,
2701
- className: cn(
2702
- "w-full rounded-md border bg-background px-3 py-2 font-mono text-sm",
2703
- "placeholder:text-muted-foreground",
2704
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2705
- parseError ? "border-destructive" : "border-input"
2706
- )
2707
- }
2708
- ),
2709
- parseError && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-destructive", children: parseError }),
2710
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Press Cmd/Ctrl + Enter to trigger" })
2711
- ] })
2712
- ] }),
2713
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-border px-6 py-4", children: [
2714
- /* @__PURE__ */ jsx(
2715
- "button",
2716
- {
2717
- "data-testid": "trigger-cancel-btn",
2718
- onClick: onClose,
2719
- disabled: loading,
2720
- className: cn(
2721
- "px-4 py-2 text-sm font-medium rounded-md hover:bg-accent",
2722
- "disabled:opacity-50 disabled:cursor-not-allowed"
2723
- ),
2724
- children: "Cancel"
2725
- }
2726
- ),
2727
- /* @__PURE__ */ jsx(
2728
- "button",
2729
- {
2730
- "data-testid": "trigger-confirm-btn",
2731
- onClick: handleTrigger,
2732
- disabled: loading || !!parseError,
2733
- className: cn(
2734
- "flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md",
2735
- "bg-primary text-primary-foreground hover:bg-primary/90",
2736
- "disabled:opacity-50 disabled:cursor-not-allowed"
2737
- ),
2738
- children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
2739
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
2740
- "Triggering..."
2741
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2742
- /* @__PURE__ */ jsx(Play, { className: "h-4 w-4" }),
2743
- "Trigger Workflow"
2744
- ] })
2745
- }
2746
- )
2747
- ] })
2748
- ]
2749
- }
2750
- )
2751
- ] });
2752
- }
2753
- var STATUS_COLORS3 = {
2754
- active: "bg-green-500",
2755
- draft: "bg-yellow-500",
2756
- paused: "bg-orange-500",
2757
- deprecated: "bg-gray-500",
2758
- archived: "bg-gray-400"
2759
- };
2760
- var STATUS_TEXT_COLORS = {
2761
- active: "text-green-600 dark:text-green-400",
2762
- draft: "text-yellow-600 dark:text-yellow-400",
2763
- paused: "text-orange-600 dark:text-orange-400",
2764
- deprecated: "text-gray-600 dark:text-gray-400",
2765
- archived: "text-gray-500"
2766
- };
2767
- function AgentCard({
2768
- agent,
2769
- onClick
2770
- }) {
2771
- const metrics = agent.metrics;
2772
- return /* @__PURE__ */ jsxs(
2773
- "div",
2774
- {
2775
- "data-testid": "agent-card",
2776
- onClick,
2777
- className: cn(
2778
- "flex flex-col rounded-lg border border-border bg-card p-6 cursor-pointer",
2779
- "transition-colors hover:bg-accent/50"
2780
- ),
2781
- children: [
2782
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-4", children: [
2783
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2784
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10", children: /* @__PURE__ */ jsx(Bot, { className: "h-5 w-5 text-primary" }) }),
2785
- /* @__PURE__ */ jsxs("div", { children: [
2786
- /* @__PURE__ */ jsx("h3", { "data-testid": "agent-name", className: "font-semibold", children: agent.name }),
2787
- /* @__PURE__ */ jsx(
2788
- "div",
2789
- {
2790
- "data-testid": "agent-model",
2791
- className: "text-xs text-muted-foreground",
2792
- children: agent.model
2793
- }
2794
- )
2795
- ] })
2796
- ] }),
2797
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2798
- /* @__PURE__ */ jsx(
2799
- "div",
2800
- {
2801
- className: cn("h-2 w-2 rounded-full", STATUS_COLORS3[agent.status])
2802
- }
2803
- ),
2804
- /* @__PURE__ */ jsx(
2805
- "span",
2806
- {
2807
- "data-testid": "agent-status",
2808
- className: cn("text-xs font-medium", STATUS_TEXT_COLORS[agent.status]),
2809
- children: agent.status
2810
- }
2811
- )
2812
- ] })
2813
- ] }),
2814
- /* @__PURE__ */ jsx(
2815
- "p",
2816
- {
2817
- "data-testid": "agent-description",
2818
- className: "text-sm text-muted-foreground mb-4 line-clamp-2",
2819
- children: agent.description
2820
- }
2821
- ),
2822
- /* @__PURE__ */ jsxs(
2823
- "div",
2824
- {
2825
- "data-testid": "agent-metrics",
2826
- className: "grid grid-cols-2 gap-4 mb-4",
2827
- children: [
2828
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2829
- /* @__PURE__ */ jsx(Activity, { className: "h-4 w-4 text-muted-foreground" }),
2830
- /* @__PURE__ */ jsxs("div", { children: [
2831
- /* @__PURE__ */ jsx(
2832
- "div",
2833
- {
2834
- "data-testid": "agent-execution-count",
2835
- className: "text-sm font-medium",
2836
- children: formatNumber(metrics?.totalExecutions || 0)
2837
- }
2838
- ),
2839
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Executions" })
2840
- ] })
2841
- ] }),
2842
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2843
- /* @__PURE__ */ jsx(TrendingUp, { className: "h-4 w-4 text-muted-foreground" }),
2844
- /* @__PURE__ */ jsxs("div", { children: [
2845
- /* @__PURE__ */ jsx(
2846
- "div",
2847
- {
2848
- "data-testid": "agent-success-rate",
2849
- className: "text-sm font-medium",
2850
- children: metrics ? `${Math.round(metrics.successRate * 100)}%` : "0%"
2851
- }
2852
- ),
2853
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Success Rate" })
2854
- ] })
2855
- ] }),
2856
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2857
- /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-muted-foreground" }),
2858
- /* @__PURE__ */ jsxs("div", { children: [
2859
- /* @__PURE__ */ jsx(
2860
- "div",
2861
- {
2862
- "data-testid": "agent-response-time",
2863
- className: "text-sm font-medium",
2864
- children: metrics ? formatDuration(metrics.avgDurationMs) : "0ms"
2865
- }
2866
- ),
2867
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Avg Response" })
2868
- ] })
2869
- ] }),
2870
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2871
- /* @__PURE__ */ jsx(Zap, { className: "h-4 w-4 text-muted-foreground" }),
2872
- /* @__PURE__ */ jsxs("div", { children: [
2873
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: formatNumber(metrics?.avgTokenUsage || 0) }),
2874
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Avg Tokens" })
2875
- ] })
2876
- ] })
2877
- ]
2878
- }
2879
- ),
2880
- /* @__PURE__ */ jsxs("div", { className: "mt-auto pt-4 border-t border-border flex items-center justify-between text-xs text-muted-foreground", children: [
2881
- /* @__PURE__ */ jsx("span", { "data-testid": "agent-last-active", children: metrics?.lastExecutedAt ? `Active ${formatRelativeTime(metrics.lastExecutedAt)}` : "Never executed" }),
2882
- /* @__PURE__ */ jsx("span", { children: agent.type.roleCategory })
2883
- ] })
2884
- ]
2885
- }
2886
- );
2887
- }
2888
- function AgentsGrid({
2889
- agents,
2890
- total,
2891
- loading = false,
2892
- filter,
2893
- onFilterChange,
2894
- onAgentClick,
2895
- onCreate,
2896
- className
2897
- }) {
2898
- const [searchValue, setSearchValue] = React9.useState(filter?.nameSearch || "");
2899
- const handleSearch = (value) => {
2900
- setSearchValue(value);
2901
- onFilterChange?.({ ...filter, nameSearch: value || void 0 });
2902
- };
2903
- const handleStatusFilter = (e) => {
2904
- const value = e.target.value;
2905
- if (value === "all") {
2906
- onFilterChange?.({ ...filter, status: void 0 });
2907
- } else {
2908
- onFilterChange?.({ ...filter, status: [value] });
2909
- }
2910
- };
2911
- if (!loading && agents.length === 0) {
2912
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
2913
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border p-4", children: [
2914
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2915
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2916
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
2917
- /* @__PURE__ */ jsx(
2918
- "input",
2919
- {
2920
- "data-testid": "agents-search",
2921
- type: "text",
2922
- placeholder: "Search agents...",
2923
- value: searchValue,
2924
- onChange: (e) => handleSearch(e.target.value),
2925
- className: cn(
2926
- "h-9 w-64 rounded-md border border-input bg-background pl-9 pr-4 text-sm",
2927
- "placeholder:text-muted-foreground",
2928
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2929
- )
2930
- }
2931
- )
2932
- ] }),
2933
- /* @__PURE__ */ jsxs(
2934
- "select",
2935
- {
2936
- "data-testid": "agent-status-filter",
2937
- value: filter?.status?.[0] || "all",
2938
- onChange: handleStatusFilter,
2939
- className: cn(
2940
- "h-9 rounded-md border border-input bg-background px-3 text-sm",
2941
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2942
- ),
2943
- children: [
2944
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Statuses" }),
2945
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
2946
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
2947
- /* @__PURE__ */ jsx("option", { value: "paused", children: "Paused" }),
2948
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" }),
2949
- /* @__PURE__ */ jsx("option", { value: "archived", children: "Archived" })
2950
- ]
2951
- }
2952
- )
2953
- ] }),
2954
- onCreate && /* @__PURE__ */ jsxs(
2955
- "button",
2956
- {
2957
- "data-testid": "create-agent-btn",
2958
- onClick: onCreate,
2959
- className: cn(
2960
- "flex h-9 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground",
2961
- "hover:bg-primary/90"
2962
- ),
2963
- children: [
2964
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
2965
- "Create Agent"
2966
- ]
2967
- }
2968
- )
2969
- ] }),
2970
- /* @__PURE__ */ jsxs(
2971
- "div",
2972
- {
2973
- "data-testid": "agents-empty-state",
2974
- className: "flex flex-1 flex-col items-center justify-center p-12 text-center",
2975
- children: [
2976
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(Bot, { className: "h-8 w-8 text-muted-foreground" }) }),
2977
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "No agents found" }),
2978
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-4 max-w-sm", children: "Agents are autonomous AI workers that can perform specialized tasks. Create your first agent to get started." }),
2979
- onCreate && /* @__PURE__ */ jsxs(
2980
- "button",
2981
- {
2982
- onClick: onCreate,
2983
- className: cn(
2984
- "flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
2985
- "hover:bg-primary/90"
2986
- ),
2987
- children: [
2988
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
2989
- "Create Agent"
2990
- ]
2991
- }
2992
- )
2993
- ]
2994
- }
2995
- )
2996
- ] });
2997
- }
2998
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
2999
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border p-4", children: [
3000
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3001
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3002
- /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
3003
- /* @__PURE__ */ jsx(
3004
- "input",
3005
- {
3006
- "data-testid": "agents-search",
3007
- type: "text",
3008
- placeholder: "Search agents...",
3009
- value: searchValue,
3010
- onChange: (e) => handleSearch(e.target.value),
3011
- className: cn(
3012
- "h-9 w-64 rounded-md border border-input bg-background pl-9 pr-4 text-sm",
3013
- "placeholder:text-muted-foreground",
3014
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
3015
- )
3016
- }
3017
- )
3018
- ] }),
3019
- /* @__PURE__ */ jsxs(
3020
- "select",
3021
- {
3022
- "data-testid": "agent-status-filter",
3023
- value: filter?.status?.[0] || "all",
3024
- onChange: handleStatusFilter,
3025
- className: cn(
3026
- "h-9 rounded-md border border-input bg-background px-3 text-sm",
3027
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
3028
- ),
3029
- children: [
3030
- /* @__PURE__ */ jsx("option", { value: "all", children: "All Statuses" }),
3031
- /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
3032
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
3033
- /* @__PURE__ */ jsx("option", { value: "paused", children: "Paused" }),
3034
- /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" }),
3035
- /* @__PURE__ */ jsx("option", { value: "archived", children: "Archived" })
3036
- ]
3037
- }
3038
- )
3039
- ] }),
3040
- onCreate && /* @__PURE__ */ jsxs(
3041
- "button",
3042
- {
3043
- "data-testid": "create-agent-btn",
3044
- onClick: onCreate,
3045
- className: cn(
3046
- "flex h-9 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground",
3047
- "hover:bg-primary/90"
3048
- ),
3049
- children: [
3050
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
3051
- "Create Agent"
3052
- ]
3053
- }
3054
- )
3055
- ] }),
3056
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto p-4", children: loading ? /* @__PURE__ */ jsx(
3057
- "div",
3058
- {
3059
- "data-testid": "agents-grid",
3060
- className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
3061
- children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs(
3062
- "div",
3063
- {
3064
- className: "animate-pulse rounded-lg border border-border p-6",
3065
- children: [
3066
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
3067
- /* @__PURE__ */ jsx("div", { className: "h-10 w-10 rounded-lg bg-muted" }),
3068
- /* @__PURE__ */ jsxs("div", { children: [
3069
- /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted mb-1" }),
3070
- /* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted" })
3071
- ] })
3072
- ] }),
3073
- /* @__PURE__ */ jsx("div", { className: "h-10 w-full rounded bg-muted mb-4" }),
3074
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3075
- /* @__PURE__ */ jsx("div", { className: "h-12 rounded bg-muted" }),
3076
- /* @__PURE__ */ jsx("div", { className: "h-12 rounded bg-muted" }),
3077
- /* @__PURE__ */ jsx("div", { className: "h-12 rounded bg-muted" }),
3078
- /* @__PURE__ */ jsx("div", { className: "h-12 rounded bg-muted" })
3079
- ] })
3080
- ]
3081
- },
3082
- i
3083
- ))
3084
- }
3085
- ) : /* @__PURE__ */ jsx(
3086
- "div",
3087
- {
3088
- "data-testid": "agents-grid",
3089
- className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
3090
- children: agents.map((agent) => /* @__PURE__ */ jsx(
3091
- AgentCard,
3092
- {
3093
- agent,
3094
- onClick: () => onAgentClick?.(agent)
3095
- },
3096
- agent.id
3097
- ))
3098
- }
3099
- ) }),
3100
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between border-t border-border p-4", children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
3101
- "Showing ",
3102
- agents.length,
3103
- " of ",
3104
- total,
3105
- " agents"
3106
- ] }) })
3107
- ] });
3108
- }
3109
- function AgentExecutePanel({
3110
- agent,
3111
- executing = false,
3112
- execution,
3113
- error,
3114
- onExecute,
3115
- onClear,
3116
- className
3117
- }) {
3118
- const [showInput, setShowInput] = React9.useState(false);
3119
- const [inputValue, setInputValue] = React9.useState("");
3120
- const textareaRef = React9.useRef(null);
3121
- React9.useEffect(() => {
3122
- if (showInput && textareaRef.current) {
3123
- textareaRef.current.focus();
3124
- }
3125
- }, [showInput]);
3126
- React9.useEffect(() => {
3127
- if (execution?.status === "completed" || execution?.status === "failed") {
3128
- setInputValue("");
3129
- }
3130
- }, [execution?.status]);
3131
- const handleExecuteClick = () => {
3132
- if (!showInput) {
3133
- setShowInput(true);
3134
- return;
3135
- }
3136
- };
3137
- const handleSubmit = () => {
3138
- if (inputValue.trim()) {
3139
- onExecute(inputValue.trim());
3140
- }
3141
- };
3142
- const handleKeyDown = (e) => {
3143
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && inputValue.trim() && !executing) {
3144
- handleSubmit();
3145
- }
3146
- if (e.key === "Escape") {
3147
- setShowInput(false);
3148
- setInputValue("");
3149
- }
3150
- };
3151
- const handleClear = () => {
3152
- setShowInput(false);
3153
- setInputValue("");
3154
- onClear?.();
3155
- };
3156
- const isInputEmpty = !inputValue.trim();
3157
- const showResults = execution || error || executing;
3158
- return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card", className), children: [
3159
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
3160
- /* @__PURE__ */ jsx("h3", { className: "font-medium", children: "Execute Agent" }),
3161
- showResults && /* @__PURE__ */ jsx(
3162
- "button",
3163
- {
3164
- onClick: handleClear,
3165
- className: "flex h-6 w-6 items-center justify-center rounded hover:bg-accent",
3166
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
3167
- }
3168
- )
3169
- ] }),
3170
- /* @__PURE__ */ jsx("div", { className: "p-4", children: !showInput && !showResults ? (
3171
- // Initial state - just the button
3172
- /* @__PURE__ */ jsxs("div", { className: "text-center py-4", children: [
3173
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: "Execute this agent with a task description to see it in action." }),
3174
- /* @__PURE__ */ jsxs(
3175
- "button",
3176
- {
3177
- "data-testid": "execute-agent-btn",
3178
- onClick: handleExecuteClick,
3179
- className: cn(
3180
- "flex items-center gap-2 mx-auto px-4 py-2 rounded-md",
3181
- "bg-primary text-primary-foreground font-medium text-sm",
3182
- "hover:bg-primary/90"
3183
- ),
3184
- children: [
3185
- /* @__PURE__ */ jsx(Play, { className: "h-4 w-4" }),
3186
- "Execute Agent"
3187
- ]
3188
- }
3189
- )
3190
- ] })
3191
- ) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3192
- showInput && !executing && !execution && /* @__PURE__ */ jsxs(Fragment, { children: [
3193
- /* @__PURE__ */ jsxs("div", { children: [
3194
- /* @__PURE__ */ jsx(
3195
- "label",
3196
- {
3197
- htmlFor: "agent-input",
3198
- className: "text-sm font-medium mb-2 block",
3199
- children: "Task Description"
3200
- }
3201
- ),
3202
- /* @__PURE__ */ jsx(
3203
- "textarea",
3204
- {
3205
- ref: textareaRef,
3206
- id: "agent-input",
3207
- "data-testid": "agent-input-textarea",
3208
- value: inputValue,
3209
- onChange: (e) => setInputValue(e.target.value),
3210
- onKeyDown: handleKeyDown,
3211
- placeholder: "Describe the task you want the agent to perform...",
3212
- rows: 4,
3213
- className: cn(
3214
- "w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
3215
- "placeholder:text-muted-foreground",
3216
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
3217
- )
3218
- }
3219
- ),
3220
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Press Cmd/Ctrl + Enter to execute" })
3221
- ] }),
3222
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
3223
- /* @__PURE__ */ jsx(
3224
- "button",
3225
- {
3226
- onClick: () => {
3227
- setShowInput(false);
3228
- setInputValue("");
3229
- },
3230
- className: "px-4 py-2 text-sm font-medium rounded-md hover:bg-accent",
3231
- children: "Cancel"
3232
- }
3233
- ),
3234
- /* @__PURE__ */ jsxs(
3235
- "button",
3236
- {
3237
- "data-testid": "submit-execution-btn",
3238
- onClick: handleSubmit,
3239
- disabled: isInputEmpty,
3240
- className: cn(
3241
- "flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md",
3242
- "bg-primary text-primary-foreground hover:bg-primary/90",
3243
- "disabled:opacity-50 disabled:cursor-not-allowed"
3244
- ),
3245
- children: [
3246
- /* @__PURE__ */ jsx(Play, { className: "h-4 w-4" }),
3247
- "Execute"
3248
- ]
3249
- }
3250
- )
3251
- ] })
3252
- ] }),
3253
- executing && /* @__PURE__ */ jsxs(
3254
- "div",
3255
- {
3256
- "data-testid": "execution-progress",
3257
- className: "flex flex-col items-center justify-center py-8",
3258
- children: [
3259
- /* @__PURE__ */ jsx("div", { "data-testid": "execution-loading", className: "relative mb-4", children: /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-primary" }) }),
3260
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "Executing Agent..." }),
3261
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
3262
- agent.name,
3263
- " is working on your task"
3264
- ] })
3265
- ]
3266
- }
3267
- ),
3268
- error && /* @__PURE__ */ jsxs(
3269
- "div",
3270
- {
3271
- "data-testid": "execution-error",
3272
- className: "p-4 bg-red-500/10 border border-red-500/20 rounded-md",
3273
- children: [
3274
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400 font-medium mb-2", children: [
3275
- /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
3276
- "Execution Failed"
3277
- ] }),
3278
- /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600/80 dark:text-red-400/80", children: error }),
3279
- /* @__PURE__ */ jsxs(
3280
- "button",
3281
- {
3282
- "data-testid": "retry-execution-btn",
3283
- onClick: () => {
3284
- setShowInput(true);
3285
- },
3286
- className: cn(
3287
- "mt-3 flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md",
3288
- "border border-red-500/30 text-red-600 dark:text-red-400",
3289
- "hover:bg-red-500/10"
3290
- ),
3291
- children: [
3292
- /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" }),
3293
- "Try Again"
3294
- ]
3295
- }
3296
- )
3297
- ]
3298
- }
3299
- ),
3300
- error?.toLowerCase().includes("network") && /* @__PURE__ */ jsxs(
3301
- "div",
3302
- {
3303
- "data-testid": "network-error",
3304
- className: "p-4 bg-red-500/10 border border-red-500/20 rounded-md",
3305
- children: [
3306
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-red-600 dark:text-red-400 font-medium mb-2", children: [
3307
- /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
3308
- "Network Error"
3309
- ] }),
3310
- /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600/80 dark:text-red-400/80", children: "Unable to connect. Please check your internet connection and try again." })
3311
- ]
3312
- }
3313
- ),
3314
- execution?.status === "completed" && execution.output && /* @__PURE__ */ jsxs("div", { "data-testid": "execution-result", className: "space-y-4", children: [
3315
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-green-600 dark:text-green-400", children: [
3316
- /* @__PURE__ */ jsx(CheckCircle2, { className: "h-5 w-5" }),
3317
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Execution Complete" })
3318
- ] }),
3319
- /* @__PURE__ */ jsxs("div", { className: "p-4 bg-muted/50 rounded-md", children: [
3320
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-2", children: "Summary" }),
3321
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: execution.output.summary })
3322
- ] }),
3323
- /* @__PURE__ */ jsxs("div", { children: [
3324
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium mb-2", children: "Result" }),
3325
- /* @__PURE__ */ jsx("pre", { className: "text-xs bg-background border border-border rounded-md p-3 overflow-auto max-h-64", children: JSON.stringify(execution.output.result, null, 2) })
3326
- ] }),
3327
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-sm text-muted-foreground", children: [
3328
- /* @__PURE__ */ jsxs("span", { children: [
3329
- "Duration: ",
3330
- execution.durationMs,
3331
- "ms"
3332
- ] }),
3333
- /* @__PURE__ */ jsxs("span", { children: [
3334
- "Tokens: ",
3335
- execution.tokenUsage.totalTokens
3336
- ] }),
3337
- /* @__PURE__ */ jsxs("span", { children: [
3338
- "Iterations: ",
3339
- execution.iterations
3340
- ] })
3341
- ] }),
3342
- /* @__PURE__ */ jsxs(
3343
- "button",
3344
- {
3345
- "data-testid": "execute-agent-btn",
3346
- onClick: () => {
3347
- setShowInput(true);
3348
- onClear?.();
3349
- },
3350
- className: cn(
3351
- "flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md",
3352
- "bg-primary text-primary-foreground hover:bg-primary/90"
3353
- ),
3354
- children: [
3355
- /* @__PURE__ */ jsx(Play, { className: "h-4 w-4" }),
3356
- "Execute Again"
3357
- ]
3358
- }
3359
- )
3360
- ] })
3361
- ] }) })
3362
- ] });
3363
- }
3364
- function AgentFeedbackPanel({
3365
- execution,
3366
- submitting = false,
3367
- onSubmitFeedback,
3368
- className
3369
- }) {
3370
- const [selectedRating, setSelectedRating] = React9.useState(
3371
- execution.feedback ? execution.feedback.rating >= 3 ? "up" : "down" : null
3372
- );
3373
- const [comment, setComment] = React9.useState(execution.feedback?.comment || "");
3374
- const [showCommentBox, setShowCommentBox] = React9.useState(!!execution.feedback);
3375
- const hasSubmittedFeedback = !!execution.feedback;
3376
- const isDisabled = hasSubmittedFeedback || submitting;
3377
- const handleRatingClick = (rating) => {
3378
- if (isDisabled) return;
3379
- if (selectedRating === rating) {
3380
- setSelectedRating(null);
3381
- setShowCommentBox(false);
3382
- } else {
3383
- setSelectedRating(rating);
3384
- setShowCommentBox(true);
3385
- }
3386
- };
3387
- const handleSubmit = () => {
3388
- if (!selectedRating || isDisabled) return;
3389
- onSubmitFeedback({
3390
- rating: selectedRating === "up" ? 5 : 1,
3391
- comment: comment.trim() || void 0
3392
- });
3393
- };
3394
- return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card", className), children: [
3395
- /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-4 py-3", children: [
3396
- /* @__PURE__ */ jsx("h3", { className: "font-medium", children: "Feedback" }),
3397
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hasSubmittedFeedback ? "Thank you for your feedback!" : "Was this response helpful?" })
3398
- ] }),
3399
- /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
3400
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-4", children: [
3401
- /* @__PURE__ */ jsx(
3402
- "button",
3403
- {
3404
- "data-testid": "thumbs-up-btn",
3405
- "data-selected": selectedRating === "up" ? "true" : void 0,
3406
- onClick: () => handleRatingClick("up"),
3407
- disabled: isDisabled,
3408
- className: cn(
3409
- "flex h-10 w-10 items-center justify-center rounded-full border-2 transition-colors",
3410
- selectedRating === "up" ? "border-green-500 bg-green-500/10 text-green-600 dark:text-green-400" : "border-border text-muted-foreground hover:border-green-500/50 hover:text-green-600",
3411
- isDisabled && "opacity-50 cursor-not-allowed hover:border-border hover:text-muted-foreground"
3412
- ),
3413
- children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-5 w-5" })
3414
- }
3415
- ),
3416
- /* @__PURE__ */ jsx(
3417
- "button",
3418
- {
3419
- "data-testid": "thumbs-down-btn",
3420
- "data-selected": selectedRating === "down" ? "true" : void 0,
3421
- onClick: () => handleRatingClick("down"),
3422
- disabled: isDisabled,
3423
- className: cn(
3424
- "flex h-10 w-10 items-center justify-center rounded-full border-2 transition-colors",
3425
- selectedRating === "down" ? "border-red-500 bg-red-500/10 text-red-600 dark:text-red-400" : "border-border text-muted-foreground hover:border-red-500/50 hover:text-red-600",
3426
- isDisabled && "opacity-50 cursor-not-allowed hover:border-border hover:text-muted-foreground"
3427
- ),
3428
- children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-5 w-5" })
3429
- }
3430
- ),
3431
- hasSubmittedFeedback && execution.feedback && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-sm text-muted-foreground", children: [
3432
- "Rated ",
3433
- execution.feedback.rating >= 3 ? "positively" : "negatively"
3434
- ] })
3435
- ] }),
3436
- showCommentBox && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
3437
- /* @__PURE__ */ jsxs("div", { children: [
3438
- /* @__PURE__ */ jsxs(
3439
- "label",
3440
- {
3441
- htmlFor: "feedback-comment",
3442
- className: "text-sm font-medium mb-2 block",
3443
- children: [
3444
- selectedRating === "down" ? "What went wrong?" : "Any additional comments?",
3445
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-normal ml-1", children: "(optional)" })
3446
- ]
3447
- }
3448
- ),
3449
- /* @__PURE__ */ jsx(
3450
- "textarea",
3451
- {
3452
- id: "feedback-comment",
3453
- "data-testid": "feedback-comment",
3454
- value: comment,
3455
- onChange: (e) => setComment(e.target.value),
3456
- placeholder: selectedRating === "down" ? "Help us improve by describing what was incorrect or unhelpful..." : "Tell us what worked well...",
3457
- rows: 3,
3458
- disabled: isDisabled,
3459
- className: cn(
3460
- "w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
3461
- "placeholder:text-muted-foreground",
3462
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3463
- "disabled:opacity-50 disabled:cursor-not-allowed"
3464
- )
3465
- }
3466
- )
3467
- ] }),
3468
- !hasSubmittedFeedback && /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
3469
- "button",
3470
- {
3471
- "data-testid": "submit-feedback-btn",
3472
- onClick: handleSubmit,
3473
- disabled: !selectedRating || submitting,
3474
- className: cn(
3475
- "flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md",
3476
- "bg-primary text-primary-foreground hover:bg-primary/90",
3477
- "disabled:opacity-50 disabled:cursor-not-allowed"
3478
- ),
3479
- children: submitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
3480
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
3481
- "Submitting..."
3482
- ] }) : "Submit Feedback"
3483
- }
3484
- ) })
3485
- ] }),
3486
- hasSubmittedFeedback && execution.feedback?.comment && /* @__PURE__ */ jsxs("div", { className: "mt-2 p-3 bg-muted/50 rounded-md", children: [
3487
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mb-1", children: "Your comment:" }),
3488
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: execution.feedback.comment })
3489
- ] })
3490
- ] })
3491
- ] });
3492
- }
3493
- var STATUS_ICONS2 = {
3494
- pending: Clock,
3495
- running: Loader2,
3496
- completed: CheckCircle2,
3497
- failed: XCircle,
3498
- cancelled: StopCircle,
3499
- "handed-off": ChevronRight,
3500
- "awaiting-human": Clock
3501
- };
3502
- var STATUS_COLORS4 = {
3503
- pending: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400",
3504
- running: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
3505
- completed: "bg-green-500/10 text-green-600 dark:text-green-400",
3506
- failed: "bg-red-500/10 text-red-600 dark:text-red-400",
3507
- cancelled: "bg-muted text-muted-foreground",
3508
- "handed-off": "bg-purple-500/10 text-purple-600 dark:text-purple-400",
3509
- "awaiting-human": "bg-orange-500/10 text-orange-600 dark:text-orange-400"
3510
- };
3511
- function AgentHistoryTable({
3512
- executions,
3513
- total,
3514
- loading = false,
3515
- page = 1,
3516
- perPage = 10,
3517
- onExecutionClick,
3518
- onPageChange,
3519
- className
3520
- }) {
3521
- const totalPages = Math.ceil(total / perPage);
3522
- if (!loading && executions.length === 0) {
3523
- return /* @__PURE__ */ jsx("div", { className: cn("rounded-lg border border-border bg-card", className), children: /* @__PURE__ */ jsxs(
3524
- "div",
3525
- {
3526
- "data-testid": "history-empty-state",
3527
- className: "flex flex-col items-center justify-center py-12 text-center",
3528
- children: [
3529
- /* @__PURE__ */ jsx(Clock, { className: "h-12 w-12 text-muted-foreground mb-4" }),
3530
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "No execution history" }),
3531
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "This agent has not been executed yet. Execute it to see history." })
3532
- ]
3533
- }
3534
- ) });
3535
- }
3536
- return /* @__PURE__ */ jsxs("div", { className: cn("rounded-lg border border-border bg-card", className), children: [
3537
- /* @__PURE__ */ jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsxs("table", { "data-testid": "execution-history-table", className: "w-full", children: [
3538
- /* @__PURE__ */ jsx("thead", { className: "bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { children: [
3539
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Status" }),
3540
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Time" }),
3541
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Duration" }),
3542
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Tokens" }),
3543
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Cost" }),
3544
- /* @__PURE__ */ jsx("th", { className: "p-4 text-left text-sm font-medium text-muted-foreground", children: "Feedback" }),
3545
- /* @__PURE__ */ jsx("th", { className: "w-8 p-4" })
3546
- ] }) }),
3547
- /* @__PURE__ */ jsx("tbody", { children: loading ? (
3548
- // Loading skeleton
3549
- Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "animate-pulse border-b border-border", children: [
3550
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-6 w-20 rounded bg-muted" }) }),
3551
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted" }) }),
3552
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted" }) }),
3553
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted" }) }),
3554
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted" }) }),
3555
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-8 rounded bg-muted" }) }),
3556
- /* @__PURE__ */ jsx("td", { className: "p-4" })
3557
- ] }, i))
3558
- ) : executions.map((execution) => {
3559
- const StatusIcon = STATUS_ICONS2[execution.status];
3560
- const isRunning = execution.status === "running";
3561
- return /* @__PURE__ */ jsxs(
3562
- "tr",
3563
- {
3564
- "data-testid": "execution-row",
3565
- onClick: () => onExecutionClick?.(execution),
3566
- className: "cursor-pointer border-b border-border hover:bg-muted/50",
3567
- children: [
3568
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxs(
3569
- "span",
3570
- {
3571
- "data-testid": "execution-status",
3572
- className: cn(
3573
- "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium",
3574
- STATUS_COLORS4[execution.status]
3575
- ),
3576
- children: [
3577
- /* @__PURE__ */ jsx(
3578
- StatusIcon,
3579
- {
3580
- className: cn("h-3.5 w-3.5", isRunning && "animate-spin")
3581
- }
3582
- ),
3583
- execution.status
3584
- ]
3585
- }
3586
- ) }),
3587
- /* @__PURE__ */ jsxs("td", { className: "p-4", children: [
3588
- /* @__PURE__ */ jsx(
3589
- "div",
3590
- {
3591
- "data-testid": "execution-timestamp",
3592
- className: "text-sm",
3593
- children: formatRelativeTime(execution.startedAt)
3594
- }
3595
- ),
3596
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: formatDateTime(execution.startedAt) })
3597
- ] }),
3598
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx(
3599
- "span",
3600
- {
3601
- "data-testid": "execution-duration",
3602
- className: "text-sm",
3603
- children: execution.durationMs ? formatDuration(execution.durationMs) : "-"
3604
- }
3605
- ) }),
3606
- /* @__PURE__ */ jsxs("td", { className: "p-4", children: [
3607
- /* @__PURE__ */ jsx("div", { "data-testid": "token-usage", className: "text-sm", children: formatNumber(execution.tokenUsage.totalTokens) }),
3608
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs text-muted-foreground", children: [
3609
- /* @__PURE__ */ jsxs("span", { "data-testid": "input-tokens", children: [
3610
- "In: ",
3611
- formatNumber(execution.tokenUsage.inputTokens)
3612
- ] }),
3613
- /* @__PURE__ */ jsxs("span", { "data-testid": "output-tokens", children: [
3614
- "Out: ",
3615
- formatNumber(execution.tokenUsage.outputTokens)
3616
- ] })
3617
- ] })
3618
- ] }),
3619
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsxs(
3620
- "span",
3621
- {
3622
- "data-testid": "execution-cost",
3623
- className: "text-sm flex items-center gap-1",
3624
- children: [
3625
- /* @__PURE__ */ jsx(Coins, { className: "h-3.5 w-3.5 text-muted-foreground" }),
3626
- "$",
3627
- execution.costUsd.toFixed(4)
3628
- ]
3629
- }
3630
- ) }),
3631
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx("div", { "data-testid": "feedback-indicator", children: execution.feedback ? execution.feedback.rating >= 3 ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-4 w-4 text-green-500" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-4 w-4 text-red-500" }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "-" }) }) }),
3632
- /* @__PURE__ */ jsx("td", { className: "p-4", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" }) })
3633
- ]
3634
- },
3635
- execution.id
3636
- );
3637
- }) })
3638
- ] }) }),
3639
- totalPages > 1 && /* @__PURE__ */ jsxs(
3640
- "div",
3641
- {
3642
- "data-testid": "history-pagination",
3643
- className: "flex items-center justify-between border-t border-border p-4",
3644
- children: [
3645
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground", children: [
3646
- "Showing ",
3647
- (page - 1) * perPage + 1,
3648
- " to",
3649
- " ",
3650
- Math.min(page * perPage, total),
3651
- " of ",
3652
- total,
3653
- " executions"
3654
- ] }),
3655
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3656
- /* @__PURE__ */ jsxs(
3657
- "button",
3658
- {
3659
- "data-testid": "pagination-prev",
3660
- onClick: () => onPageChange?.(page - 1),
3661
- disabled: page <= 1,
3662
- className: cn(
3663
- "flex h-8 items-center gap-1 rounded-md border border-input px-3 text-sm",
3664
- "hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed"
3665
- ),
3666
- children: [
3667
- /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
3668
- "Previous"
3669
- ]
3670
- }
3671
- ),
3672
- /* @__PURE__ */ jsxs("span", { className: "text-sm px-2", children: [
3673
- "Page ",
3674
- page,
3675
- " of ",
3676
- totalPages
3677
- ] }),
3678
- /* @__PURE__ */ jsxs(
3679
- "button",
3680
- {
3681
- "data-testid": "pagination-next",
3682
- onClick: () => onPageChange?.(page + 1),
3683
- disabled: page >= totalPages,
3684
- className: cn(
3685
- "flex h-8 items-center gap-1 rounded-md border border-input px-3 text-sm",
3686
- "hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed"
3687
- ),
3688
- children: [
3689
- "Next",
3690
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
3691
- ]
3692
- }
3693
- )
3694
- ] })
3695
- ]
3696
- }
3697
- )
3698
- ] });
3699
- }
3700
- var DOErrorBoundary = class extends React9.Component {
3701
- constructor(props) {
3702
- super(props);
3703
- this.state = { hasError: false, error: null };
3704
- }
3705
- static getDerivedStateFromError(error) {
3706
- return { hasError: true, error };
3707
- }
3708
- componentDidCatch(error, errorInfo) {
3709
- console.error("DOErrorBoundary caught error:", error, errorInfo);
3710
- if (this.props.onError) {
3711
- this.props.onError(error, errorInfo);
3712
- }
3713
- }
3714
- componentDidUpdate(prevProps) {
3715
- if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {
3716
- this.reset();
3717
- }
3718
- }
3719
- reset = () => {
3720
- this.setState({ hasError: false, error: null });
3721
- };
3722
- render() {
3723
- if (this.state.hasError) {
3724
- const { fallback, className } = this.props;
3725
- const error = this.state.error ?? new Error("Unknown error");
3726
- if (typeof fallback === "function") {
3727
- return fallback(error, this.reset);
3728
- }
3729
- if (fallback) {
3730
- return fallback;
3731
- }
3732
- return /* @__PURE__ */ jsx(
3733
- "div",
3734
- {
3735
- className: cn(
3736
- "p-6 rounded-lg bg-destructive/10 border border-destructive/20",
3737
- className
3738
- ),
3739
- role: "alert",
3740
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3741
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 mt-0.5", children: /* @__PURE__ */ jsx(
3742
- "svg",
3743
- {
3744
- className: "h-5 w-5 text-destructive",
3745
- viewBox: "0 0 20 20",
3746
- fill: "currentColor",
3747
- "aria-hidden": "true",
3748
- children: /* @__PURE__ */ jsx(
3749
- "path",
3750
- {
3751
- fillRule: "evenodd",
3752
- d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z",
3753
- clipRule: "evenodd"
3754
- }
3755
- )
3756
- }
3757
- ) }),
3758
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
3759
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-destructive", children: "Something went wrong" }),
3760
- error.message && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: error.message }),
3761
- /* @__PURE__ */ jsx(
3762
- "button",
3763
- {
3764
- onClick: this.reset,
3765
- className: "mt-4 inline-flex items-center rounded-md bg-destructive px-3 py-2 text-sm font-semibold text-destructive-foreground shadow-sm hover:bg-destructive/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-destructive",
3766
- children: "Try again"
3767
- }
3768
- )
3769
- ] })
3770
- ] })
3771
- }
3772
- );
3773
- }
3774
- return this.props.children;
3775
- }
3776
- };
3777
- function useErrorBoundary() {
3778
- const [error, setError] = React9.useState(null);
3779
- if (error) {
3780
- throw error;
3781
- }
3782
- return {
3783
- showBoundary: setError
3784
- };
3785
- }
3786
-
3787
- export { AgentExecutePanel, AgentFeedbackPanel, AgentHistoryTable, AgentsGrid, ConfirmDialog, DOErrorBoundary, DOHeader, DOShell, DOSidebar, DOSidebarHeader, StatsCards, SyncStatusIndicator, ThingForm, ThingFormDialog, ThingsList, ThingsPage, ToastContainer, ToastProvider, TriggerWorkflowDialog, WorkflowExecutionView, WorkflowsList, defaultNavItems, defaultStats, useErrorBoundary, useShell, useToast };
3788
- //# sourceMappingURL=chunk-NXPXL5NA.js.map
3789
- //# sourceMappingURL=chunk-NXPXL5NA.js.map