@burdenoff/microfe-vibecontrols 2026.625.3 → 2026.626.2
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.
- package/dist/components/agents/AgentForm.js +129 -127
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/shared/InstallTabs.js +19 -7
- package/dist/components/shared/InstallTabs.js.map +1 -1
- package/dist/pages/ActionsPage.js +209 -204
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +678 -648
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentGraphPage.js +112 -108
- package/dist/pages/AgentGraphPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +159 -155
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +140 -136
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +249 -244
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +73 -68
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +195 -164
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +80 -75
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +100 -95
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/HealthPage.js +331 -204
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +54 -49
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +159 -129
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +72 -78
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +517 -512
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +1084 -1079
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +107 -103
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +131 -123
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +198 -194
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +77 -71
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +83 -78
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +109 -104
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +222 -217
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +532 -501
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +137 -133
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +329 -324
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIPage.js +149 -144
- package/dist/pages/ai/AIPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,38 +2,39 @@ import { useCreateVibeCalendarTaskMutation as e, useExecuteScheduledJobNowMutati
|
|
|
2
2
|
import { useVibeControls as r, useVibeNavigate as ee } from "../providers/VibeControlsProvider.js";
|
|
3
3
|
import { LoadingState as te } from "../components/shared/LoadingState.js";
|
|
4
4
|
import { VIBE_PERMISSIONS as i } from "../constants/permissions.js";
|
|
5
|
-
import { useTenantContext as
|
|
6
|
-
import { LogsDrawer as
|
|
7
|
-
import { VibeCalendarHeader as
|
|
8
|
-
import { VibeCalMonthView as
|
|
9
|
-
import { VibeCalWeekView as
|
|
10
|
-
import { VibeCalDayView as
|
|
5
|
+
import { useTenantContext as a } from "../hooks/useTenantContext.js";
|
|
6
|
+
import { LogsDrawer as ne } from "../components/vibecalendar/LogsDrawer.js";
|
|
7
|
+
import { VibeCalendarHeader as re } from "../components/vibecalendar/VibeCalendarHeader.js";
|
|
8
|
+
import { VibeCalMonthView as ie } from "../components/vibecalendar/VibeCalMonthView.js";
|
|
9
|
+
import { VibeCalWeekView as ae } from "../components/vibecalendar/VibeCalWeekView.js";
|
|
10
|
+
import { VibeCalDayView as oe } from "../components/vibecalendar/VibeCalDayView.js";
|
|
11
11
|
import { VibeCalAgendaView as se } from "../components/vibecalendar/VibeCalAgendaView.js";
|
|
12
12
|
import { ScheduleTaskModal as ce } from "../components/vibecalendar/ScheduleTaskModal.js";
|
|
13
13
|
import "../components/vibecalendar/index.js";
|
|
14
14
|
import { expandJobsToOccurrences as le } from "../components/vibecalendar/expandOccurrences.js";
|
|
15
15
|
import { useCallback as o, useEffect as s, useMemo as c, useState as l } from "react";
|
|
16
16
|
import { useI18n as ue } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
17
|
-
import { AlertCircle as u, CalendarClock as
|
|
18
|
-
import { useParams as
|
|
19
|
-
import { graphqlFetch as
|
|
20
|
-
import { usePermissions as
|
|
21
|
-
import { jsx as
|
|
17
|
+
import { AlertCircle as u, CalendarClock as d, X as de } from "lucide-react";
|
|
18
|
+
import { useParams as fe, useSearchParams as pe } from "react-router-dom";
|
|
19
|
+
import { graphqlFetch as me } from "@burdenoff/fe-libs/shared/graphql";
|
|
20
|
+
import { usePermissions as he } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
21
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
22
|
+
import { PagePurpose as ge } from "@burdenoff/fe-libs/ui";
|
|
22
23
|
import { AccessDenied as _e } from "@burdenoff/fe-libs/shared/components";
|
|
23
|
-
import { MotionPage as
|
|
24
|
+
import { MotionPage as m } from "@burdenoff/fe-libs/shared/motion";
|
|
24
25
|
//#region src/pages/VibeCalendarPage.tsx
|
|
25
26
|
var ve = "\n query GetWorkspaceScheduledJobsForCalendar($workspaceId: ID!) {\n scheduledJobs(workspaceId: $workspaceId, pagination: { first: 500 }) {\n jobs {\n id\n name\n description\n status\n scheduleType\n cronExpression\n rrule\n interval\n scheduledAt\n nextExecutionAt\n lastExecutionAt\n actionId\n timezone\n startDate\n endDate\n }\n total\n }\n }\n";
|
|
26
|
-
function
|
|
27
|
-
let
|
|
27
|
+
function h() {
|
|
28
|
+
let h = ee(), { workspaceId: g } = r(), { t: _ } = ue(), { viewType: ye } = fe(), [v, y] = pe(), b = v.get("actionId") ?? void 0, { hasPermission: x, isLoading: be, permissions: S } = he(), xe = x(i.SCHEDULE_READ), C = x(i.LOG_READ), w = x(i.CALENDAR_AGENT_TASK_CREATE), T = x(i.CALENDAR_AGENT_TASK_EXECUTE), E = x(i.SCHEDULE_EXECUTE), D = E, O = a(), [k, Se] = l(/* @__PURE__ */ new Date()), [A, Ce] = l(ye || "month"), [we, j] = l(!1);
|
|
28
29
|
s(() => {
|
|
29
|
-
|
|
30
|
-
}, [
|
|
31
|
-
let [
|
|
32
|
-
let n =
|
|
30
|
+
b && j(!0);
|
|
31
|
+
}, [b]);
|
|
32
|
+
let [Te, M] = l(!1), [N, Ee] = l(null), [De, Oe] = l(null), [P, F] = l(null), I = o((e, t) => {
|
|
33
|
+
let n = _(e);
|
|
33
34
|
return n === e ? t : n;
|
|
34
|
-
}, [
|
|
35
|
-
let e = new Date(
|
|
36
|
-
switch (
|
|
35
|
+
}, [_]), L = c(() => {
|
|
36
|
+
let e = new Date(k), t = new Date(k);
|
|
37
|
+
switch (A) {
|
|
37
38
|
case "month":
|
|
38
39
|
e.setDate(1), e.setHours(0, 0, 0, 0), t.setMonth(t.getMonth() + 1, 0), t.setHours(23, 59, 59, 999);
|
|
39
40
|
break;
|
|
@@ -51,16 +52,16 @@ function m() {
|
|
|
51
52
|
startDate: e.toISOString(),
|
|
52
53
|
endDate: t.toISOString()
|
|
53
54
|
};
|
|
54
|
-
}, [
|
|
55
|
-
if (
|
|
55
|
+
}, [k, A]), [R, z] = l([]), [B, V] = l(void 0), [ke, H] = l(!1), [Ae, U] = l(!1), W = o(async () => {
|
|
56
|
+
if (g) {
|
|
56
57
|
U(!0);
|
|
57
58
|
try {
|
|
58
|
-
let e = new URLSearchParams(typeof window > "u" ? "" : window.location.search), t = e.get("org") ?? void 0, n = e.get("tenant") ?? void 0, r = await
|
|
59
|
+
let e = new URLSearchParams(typeof window > "u" ? "" : window.location.search), t = e.get("org") ?? void 0, n = e.get("tenant") ?? void 0, r = await me({
|
|
59
60
|
gateway: "workspace",
|
|
60
61
|
query: ve,
|
|
61
62
|
operationName: "GetWorkspaceScheduledJobsForCalendar",
|
|
62
|
-
variables: { workspaceId:
|
|
63
|
-
workspaceId:
|
|
63
|
+
variables: { workspaceId: g },
|
|
64
|
+
workspaceId: g,
|
|
64
65
|
organizationId: t ?? !0,
|
|
65
66
|
tenantId: n ?? !0
|
|
66
67
|
});
|
|
@@ -72,14 +73,14 @@ function m() {
|
|
|
72
73
|
U(!1);
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
}, [
|
|
76
|
+
}, [g]);
|
|
76
77
|
s(() => {
|
|
77
|
-
if (!
|
|
78
|
+
if (!g) {
|
|
78
79
|
z([]), V(void 0), H(!1);
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
W();
|
|
82
|
-
}, [
|
|
83
|
+
}, [g, W]);
|
|
83
84
|
let { occurrences: G, sourceById: K } = c(() => {
|
|
84
85
|
if (!R.length) return {
|
|
85
86
|
occurrences: [],
|
|
@@ -95,16 +96,16 @@ function m() {
|
|
|
95
96
|
R,
|
|
96
97
|
L.startDate,
|
|
97
98
|
L.endDate
|
|
98
|
-
]), [q, { loading:
|
|
99
|
+
]), [q, { loading: je }] = e(), [J] = n(), [Y] = t(), Me = o((e) => {
|
|
99
100
|
Ce(e);
|
|
100
101
|
}, []), X = o((e) => K.get(e), [K]), Z = o(async (e) => {
|
|
101
102
|
F(null);
|
|
102
103
|
let t = X(e);
|
|
103
|
-
if (t === "scheduledJob" && !
|
|
104
|
+
if (t === "scheduledJob" && !E) {
|
|
104
105
|
F(I("calendar.scheduleExecuteDenied", "You do not have permission to run scheduled jobs (schedule:execute)."));
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
|
-
if (t !== "scheduledJob" && !
|
|
108
|
+
if (t !== "scheduledJob" && !T) {
|
|
108
109
|
F(I("calendar.calendarTaskExecuteDenied", "You do not have permission to run calendar tasks (calendar-agent-task:execute)."));
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
@@ -113,12 +114,12 @@ function m() {
|
|
|
113
114
|
let r = t === "scheduledJob" ? await Y({
|
|
114
115
|
variables: {
|
|
115
116
|
id: e,
|
|
116
|
-
canReadLogs:
|
|
117
|
+
canReadLogs: C
|
|
117
118
|
},
|
|
118
|
-
context:
|
|
119
|
+
context: O
|
|
119
120
|
}) : await J({
|
|
120
121
|
variables: { id: e },
|
|
121
|
-
context:
|
|
122
|
+
context: O
|
|
122
123
|
});
|
|
123
124
|
r.error && (console.error("Trigger mutation returned an Apollo error:", r.error), F(r.error.message || n));
|
|
124
125
|
} catch (e) {
|
|
@@ -128,21 +129,21 @@ function m() {
|
|
|
128
129
|
J,
|
|
129
130
|
Y,
|
|
130
131
|
X,
|
|
131
|
-
|
|
132
|
-
w,
|
|
132
|
+
C,
|
|
133
133
|
T,
|
|
134
|
-
|
|
134
|
+
E,
|
|
135
|
+
O,
|
|
135
136
|
I
|
|
136
137
|
]), Q = o((e) => {
|
|
137
|
-
|
|
138
|
+
Ee(e), Oe(null), M(!0);
|
|
138
139
|
}, []), $ = o((e) => {
|
|
139
|
-
let t = encodeURIComponent(e), n =
|
|
140
|
-
|
|
140
|
+
let t = encodeURIComponent(e), n = v.toString(), r = n ? `?${n}` : "";
|
|
141
|
+
h(`${X(e) === "scheduledJob" ? `/calendar/scheduled-job/${t}` : `/calendar/task/${t}`}${r}`);
|
|
141
142
|
}, [
|
|
142
|
-
|
|
143
|
+
h,
|
|
143
144
|
X,
|
|
144
|
-
|
|
145
|
-
]),
|
|
145
|
+
v
|
|
146
|
+
]), Ne = o(async (e) => {
|
|
146
147
|
try {
|
|
147
148
|
if (await q({ variables: { input: {
|
|
148
149
|
calendarId: e.calendarId || "default",
|
|
@@ -155,9 +156,9 @@ function m() {
|
|
|
155
156
|
interval: e.interval,
|
|
156
157
|
intervalUnit: e.intervalUnit,
|
|
157
158
|
timezone: e.timezone
|
|
158
|
-
} } }), await W(), j(!1),
|
|
159
|
-
let e = new URLSearchParams(
|
|
160
|
-
e.delete("actionId"),
|
|
159
|
+
} } }), await W(), j(!1), v.has("actionId")) {
|
|
160
|
+
let e = new URLSearchParams(v);
|
|
161
|
+
e.delete("actionId"), y(e, { replace: !0 });
|
|
161
162
|
}
|
|
162
163
|
} catch (e) {
|
|
163
164
|
console.error("Failed to create task:", e);
|
|
@@ -165,155 +166,159 @@ function m() {
|
|
|
165
166
|
}, [
|
|
166
167
|
q,
|
|
167
168
|
W,
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
v,
|
|
170
|
+
y
|
|
170
171
|
]);
|
|
171
|
-
if (be) return /* @__PURE__ */
|
|
172
|
+
if (be) return /* @__PURE__ */ f(m, {
|
|
172
173
|
className: "flex flex-col h-full p-4 gap-4",
|
|
173
|
-
children: /* @__PURE__ */
|
|
174
|
+
children: /* @__PURE__ */ f(te, { message: I("calendar.loadingPermissions", "Loading permissions...") })
|
|
174
175
|
});
|
|
175
|
-
if (
|
|
176
|
+
if (S && S.permissionStrings?.length > 0 && !xe) return /* @__PURE__ */ f(m, {
|
|
176
177
|
className: "flex flex-col h-full p-4 gap-4",
|
|
177
|
-
children: /* @__PURE__ */
|
|
178
|
+
children: /* @__PURE__ */ f(_e, {
|
|
178
179
|
title: I("common.accessDeniedTitle", "Access Denied"),
|
|
179
180
|
description: I("calendar.accessDeniedDescription", "You don't have permission to view the Calendar.")
|
|
180
181
|
})
|
|
181
182
|
});
|
|
182
|
-
let
|
|
183
|
-
return /* @__PURE__ */
|
|
183
|
+
let Pe = ke && !B && R.length === 0;
|
|
184
|
+
return /* @__PURE__ */ p(m, {
|
|
184
185
|
className: "flex flex-col h-full p-4 gap-4",
|
|
185
186
|
children: [
|
|
186
|
-
B && R.length === 0 && /* @__PURE__ */
|
|
187
|
+
B && R.length === 0 && /* @__PURE__ */ p("div", {
|
|
187
188
|
className: "flex items-center gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2",
|
|
188
189
|
children: [
|
|
189
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ f(u, {
|
|
190
191
|
size: 16,
|
|
191
192
|
className: "text-status-error-text flex-shrink-0"
|
|
192
193
|
}),
|
|
193
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ f("p", {
|
|
194
195
|
className: "flex-1 text-sm text-status-error-text",
|
|
195
196
|
children: I("calendar.couldNotLoadEvents", "Could not load events. We'll keep trying — you can retry below.")
|
|
196
197
|
}),
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
+
/* @__PURE__ */ f("button", {
|
|
198
199
|
type: "button",
|
|
199
200
|
onClick: () => W(),
|
|
200
|
-
disabled:
|
|
201
|
+
disabled: Ae,
|
|
201
202
|
className: "rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
|
|
202
203
|
children: I("calendar.retry", "Retry")
|
|
203
204
|
})
|
|
204
205
|
]
|
|
205
206
|
}),
|
|
206
|
-
P && /* @__PURE__ */
|
|
207
|
+
P && /* @__PURE__ */ p("div", {
|
|
207
208
|
role: "alert",
|
|
208
209
|
className: "flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
209
210
|
children: [
|
|
210
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ f(u, {
|
|
211
212
|
size: 16,
|
|
212
213
|
className: "mt-0.5 flex-shrink-0"
|
|
213
214
|
}),
|
|
214
|
-
/* @__PURE__ */
|
|
215
|
+
/* @__PURE__ */ f("p", {
|
|
215
216
|
className: "flex-1 break-words",
|
|
216
217
|
children: P
|
|
217
218
|
}),
|
|
218
|
-
/* @__PURE__ */
|
|
219
|
+
/* @__PURE__ */ f("button", {
|
|
219
220
|
type: "button",
|
|
220
221
|
onClick: () => F(null),
|
|
221
222
|
className: "p-0.5 rounded hover:bg-bg-sunken transition-colors",
|
|
222
223
|
"aria-label": I("common.dismiss", "Dismiss"),
|
|
223
|
-
children: /* @__PURE__ */
|
|
224
|
+
children: /* @__PURE__ */ f(de, { size: 14 })
|
|
224
225
|
})
|
|
225
226
|
]
|
|
226
227
|
}),
|
|
227
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ p("div", {
|
|
228
229
|
className: "flex items-center gap-3 mb-2",
|
|
229
|
-
children: [/* @__PURE__ */ d
|
|
230
|
+
children: [/* @__PURE__ */ f(d, {
|
|
230
231
|
size: 24,
|
|
231
232
|
className: "text-text-primary"
|
|
232
|
-
}), /* @__PURE__ */
|
|
233
|
+
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
|
|
233
234
|
className: "text-xl font-bold text-text-primary",
|
|
234
235
|
children: I("nav.vibeCalendar", "Calendar")
|
|
235
|
-
}), /* @__PURE__ */
|
|
236
|
+
}), /* @__PURE__ */ f("p", {
|
|
236
237
|
className: "text-sm text-text-secondary",
|
|
237
238
|
children: I("calendar.description", "Schedule and manage tasks across your agents")
|
|
238
239
|
})] })]
|
|
239
240
|
}),
|
|
240
|
-
|
|
241
|
+
/* @__PURE__ */ f(ge, {
|
|
242
|
+
icon: /* @__PURE__ */ f(d, { className: "size-4" }),
|
|
243
|
+
children: I("calendar.purpose", "The calendar is where you schedule work to run on its own — recurring or one-off tasks that fire actions on your agents at set times (nightly builds, periodic checks, cleanups). Plan it here once and VibeControls runs it for you, so routine jobs happen on time without anyone remembering to kick them off.")
|
|
244
|
+
}),
|
|
245
|
+
Pe && /* @__PURE__ */ p("div", {
|
|
241
246
|
className: "flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border border-border-default bg-bg-surface px-4 py-3",
|
|
242
|
-
children: [/* @__PURE__ */
|
|
247
|
+
children: [/* @__PURE__ */ p("div", {
|
|
243
248
|
className: "flex-1",
|
|
244
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ f("p", {
|
|
245
250
|
className: "text-sm font-medium text-text-primary",
|
|
246
251
|
children: I("calendar.empty.title", "No scheduled tasks yet")
|
|
247
|
-
}), /* @__PURE__ */
|
|
252
|
+
}), /* @__PURE__ */ f("p", {
|
|
248
253
|
className: "text-xs text-text-secondary mt-0.5",
|
|
249
254
|
children: I("calendar.empty.description", "Schedule your first task to start automating actions across your agents.")
|
|
250
255
|
})]
|
|
251
|
-
}),
|
|
256
|
+
}), w && /* @__PURE__ */ f("button", {
|
|
252
257
|
type: "button",
|
|
253
258
|
onClick: () => j(!0),
|
|
254
259
|
className: "px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
255
260
|
children: I("calendar.empty.cta", "Schedule your first task")
|
|
256
261
|
})]
|
|
257
262
|
}),
|
|
258
|
-
/* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ f("div", {
|
|
259
264
|
"data-tour": "calendar-header",
|
|
260
|
-
children: /* @__PURE__ */
|
|
261
|
-
currentDate:
|
|
262
|
-
viewMode:
|
|
265
|
+
children: /* @__PURE__ */ f(re, {
|
|
266
|
+
currentDate: k,
|
|
267
|
+
viewMode: A,
|
|
263
268
|
onDateChange: Se,
|
|
264
|
-
onViewModeChange:
|
|
265
|
-
onCreateTask:
|
|
269
|
+
onViewModeChange: Me,
|
|
270
|
+
onCreateTask: w ? () => j(!0) : void 0
|
|
266
271
|
})
|
|
267
272
|
}),
|
|
268
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ p("div", {
|
|
269
274
|
"data-tour": "calendar-view-container",
|
|
270
275
|
className: "flex-1 overflow-hidden",
|
|
271
276
|
children: [
|
|
272
|
-
|
|
273
|
-
currentDate:
|
|
277
|
+
A === "month" && /* @__PURE__ */ f(ie, {
|
|
278
|
+
currentDate: k,
|
|
274
279
|
occurrences: G,
|
|
275
|
-
onTriggerNow:
|
|
280
|
+
onTriggerNow: D ? Z : void 0,
|
|
276
281
|
onViewLogs: Q,
|
|
277
282
|
onViewDetail: $
|
|
278
283
|
}),
|
|
279
|
-
|
|
280
|
-
currentDate:
|
|
284
|
+
A === "week" && /* @__PURE__ */ f(ae, {
|
|
285
|
+
currentDate: k,
|
|
281
286
|
occurrences: G,
|
|
282
|
-
onTriggerNow:
|
|
287
|
+
onTriggerNow: D ? Z : void 0,
|
|
283
288
|
onViewLogs: Q,
|
|
284
289
|
onViewDetail: $
|
|
285
290
|
}),
|
|
286
|
-
|
|
287
|
-
currentDate:
|
|
291
|
+
A === "day" && /* @__PURE__ */ f(oe, {
|
|
292
|
+
currentDate: k,
|
|
288
293
|
occurrences: G,
|
|
289
|
-
onTriggerNow:
|
|
294
|
+
onTriggerNow: D ? Z : void 0,
|
|
290
295
|
onViewLogs: Q,
|
|
291
296
|
onViewDetail: $
|
|
292
297
|
}),
|
|
293
|
-
|
|
298
|
+
A === "agenda" && /* @__PURE__ */ f(se, {
|
|
294
299
|
occurrences: G,
|
|
295
|
-
onTriggerNow:
|
|
300
|
+
onTriggerNow: D ? Z : void 0,
|
|
296
301
|
onViewLogs: Q,
|
|
297
302
|
onViewDetail: $
|
|
298
303
|
})
|
|
299
304
|
]
|
|
300
305
|
}),
|
|
301
|
-
/* @__PURE__ */
|
|
302
|
-
isOpen:
|
|
306
|
+
/* @__PURE__ */ f(ce, {
|
|
307
|
+
isOpen: we,
|
|
303
308
|
onClose: () => {
|
|
304
|
-
if (j(!1),
|
|
305
|
-
let e = new URLSearchParams(
|
|
306
|
-
e.delete("actionId"),
|
|
309
|
+
if (j(!1), v.has("actionId")) {
|
|
310
|
+
let e = new URLSearchParams(v);
|
|
311
|
+
e.delete("actionId"), y(e, { replace: !0 });
|
|
307
312
|
}
|
|
308
313
|
},
|
|
309
|
-
onSubmit:
|
|
310
|
-
loading:
|
|
311
|
-
prefillActionId:
|
|
314
|
+
onSubmit: Ne,
|
|
315
|
+
loading: je,
|
|
316
|
+
prefillActionId: b
|
|
312
317
|
}),
|
|
313
|
-
/* @__PURE__ */
|
|
314
|
-
isOpen:
|
|
318
|
+
/* @__PURE__ */ f(ne, {
|
|
319
|
+
isOpen: Te,
|
|
315
320
|
onClose: () => M(!1),
|
|
316
|
-
execution:
|
|
321
|
+
execution: De,
|
|
317
322
|
taskTitle: N?.title || "",
|
|
318
323
|
agentId: N?.agentId || ""
|
|
319
324
|
})
|
|
@@ -321,6 +326,6 @@ function m() {
|
|
|
321
326
|
});
|
|
322
327
|
}
|
|
323
328
|
//#endregion
|
|
324
|
-
export {
|
|
329
|
+
export { h as VibeCalendarPage };
|
|
325
330
|
|
|
326
331
|
//# sourceMappingURL=VibeCalendarPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeCalendarPage.js","names":[],"sources":["../../src/pages/VibeCalendarPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { CalendarClock, AlertCircle, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport {\n VibeCalendarHeader,\n VibeCalMonthView,\n VibeCalWeekView,\n VibeCalDayView,\n VibeCalAgendaView,\n ScheduleTaskModal,\n LogsDrawer,\n} from '../components/vibecalendar';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n CalendarViewMode,\n VibeCalendarTask,\n VibeCalendarExecution,\n ScheduleTaskInput,\n} from '../components/vibecalendar';\nimport {\n expandJobsToOccurrences,\n type SchedulerJobLike,\n} from '../components/vibecalendar/expandOccurrences';\nimport {\n useCreateVibeCalendarTaskMutation,\n useExecuteScheduledJobNowMutation,\n useTriggerVibeCalendarTaskNowMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * The /scheduler page writes ScheduledJobs through the WSPACE gateway —\n * `scheduledJobs` is only published by `wspace-scheduler-svc` and is\n * workspace-scoped (the `$workspaceId: ID!` argument is required). The\n * Calendar renders those jobs alongside its own VibeCalendarTasks. We use\n * `graphqlFetch` rather than Apollo to keep the Calendar's local-state\n * timeline decoupled from the global Apollo cache that powers other\n * VibeControls pages.\n */\nconst WORKSPACE_SCHEDULED_JOBS_QUERY = /* GraphQL */ `\n query GetWorkspaceScheduledJobsForCalendar($workspaceId: ID!) {\n scheduledJobs(workspaceId: $workspaceId, pagination: { first: 500 }) {\n jobs {\n id\n name\n description\n status\n scheduleType\n cronExpression\n rrule\n interval\n scheduledAt\n nextExecutionAt\n lastExecutionAt\n actionId\n timezone\n startDate\n endDate\n }\n total\n }\n }\n`;\n\ninterface WorkspaceScheduledJobsResponse {\n scheduledJobs: {\n jobs: SchedulerJobLike[];\n total: number;\n };\n}\n\n/**\n * Calendar Page\n *\n * Main page for scheduling and managing tasks on VibeControls agents.\n * Supports month and agenda views with task creation, on-demand triggering,\n * and log viewing via the LogsDrawer.\n */\nexport function VibeCalendarPage() {\n const navigate = useVibeNavigate();\n const { workspaceId } = useVibeControls();\n const { t } = useI18n();\n const { viewType } = useParams<{ viewType?: string }>();\n const [searchParams, setSearchParams] = useSearchParams();\n // When navigated here from the Actions page via \"Add to calendar\",\n // the URL carries ?actionId=... — open the modal in Action-linked mode.\n const prefillActionId = searchParams.get('actionId') ?? undefined;\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n // The unified-source migration moved the calendar's only list\n // path onto `scheduledJobs(workspaceId)` (the scheduler subgraph) —\n // there's no parallel `vibeCalendarTasks` query firing here\n // anymore. The page-level access gate is therefore\n // `schedule:read` specifically; admitting calendar-task-only\n // readers would let them past the UI only to hit an empty\n // calendar / scheduler RBAC denial on the actual fetch.\n // calendar-agent-task permissions are still consumed downstream\n // (CREATE for the schedule button, EXECUTE for the per-tile ▶\n // branch) but they don't gate the page mount.\n const canList = hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ);\n // Required by the @include gate on `output`/`errorMessage` in\n // both run-now mutations — without this Apollo rejects the\n // operation before it leaves the browser.\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_CREATE);\n // The agenda exclusively renders ScheduledJob tiles in the\n // current unified-source path, so the prop-level gate on\n // `onTriggerNow` is `schedule:execute` — admitting\n // `calendar-agent-task:execute`-only users would show ▶ on every\n // tile just to bounce off a friendly denial banner. Both\n // permissions stay tracked because handleTriggerNow's per-source\n // pre-check uses them defensively in case a future mixed-source\n // render lands here.\n const canExecuteCalendarTask = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_EXECUTE);\n const canExecuteScheduledJob = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n const canExecute = canExecuteScheduledJob;\n\n // URL overrides reach Apollo via per-request headers so tile-▶\n // mutations stay in the same tenant the support deep-link list\n // loaded against.\n const tenantContext = useTenantContext();\n\n const [currentDate, setCurrentDate] = useState(new Date());\n const [viewMode, setViewMode] = useState<CalendarViewMode>(\n (viewType as CalendarViewMode) || 'month'\n );\n const [showCreateModal, setShowCreateModal] = useState(false);\n\n // Auto-open the modal when arriving with an actionId query param.\n useEffect(() => {\n if (prefillActionId) {\n setShowCreateModal(true);\n }\n }, [prefillActionId]);\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedTask, setSelectedTask] = useState<VibeCalendarTask | null>(null);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n const [triggerError, setTriggerError] = useState<string | null>(null);\n\n // Memoized so downstream useCallback deps stay referentially\n // stable across renders.\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n // Calculate date range for current view\n const dateRange = useMemo(() => {\n const start = new Date(currentDate);\n const end = new Date(currentDate);\n\n switch (viewMode) {\n case 'month':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'week':\n start.setDate(start.getDate() - start.getDay());\n start.setHours(0, 0, 0, 0);\n end.setDate(start.getDate() + 6);\n end.setHours(23, 59, 59, 999);\n break;\n case 'day':\n start.setHours(0, 0, 0, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'agenda':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n }\n\n return { startDate: start.toISOString(), endDate: end.toISOString() };\n }, [currentDate, viewMode]);\n\n // Unified calendar view: fetch every ScheduledJob the workspace owns\n // (where /scheduler writes). The calendar renders jobs regardless of how\n // they were created, so all schedule-to-Action tasks show up here as a\n // single source of truth.\n //\n // `scheduledJobs` is published by `wspace-scheduler-svc` and is workspace-\n // scoped — `workspaceId` is required as a query variable. `graphqlFetch`\n // auto-resolves the workspace/org/tenant context from the shell storage\n // and sets the matching headers; URL params (`?org=`, `?tenant=`)\n // override that resolution for the support-tooling deep-link flow.\n const [jobs, setJobs] = useState<SchedulerJobLike[]>([]);\n const [rangeError, setRangeError] = useState<Error | undefined>(undefined);\n const [jobsLoaded, setJobsLoaded] = useState(false);\n const [jobsLoading, setJobsLoading] = useState(false);\n const refetch = useCallback(async () => {\n // Guard: scheduledJobs requires workspaceId — if it isn't resolved yet,\n // skip rather than firing a request that's guaranteed to fail validation.\n // The effect below re-invokes refetch once workspaceId becomes available.\n // (The state-reset on workspace transitions lives in the effect itself,\n // not here, because the effect early-returns when workspaceId is falsy\n // and refetch never gets called in that path.)\n if (!workspaceId) return;\n setJobsLoading(true);\n try {\n // graphqlFetch reads workspace/org/tenant context from storage and sets\n // the proper x-workspace-id / x-org-id / x-tenant-id headers itself —\n // passing `true` opts in to that auto-resolution. URL params override\n // storage for the org/tenant context (used by the support tooling\n // when impersonating a different tenant via deep-link).\n const urlParams = new URLSearchParams(\n typeof window === 'undefined' ? '' : window.location.search\n );\n const orgFromUrl = urlParams.get('org') ?? undefined;\n const tenantFromUrl = urlParams.get('tenant') ?? undefined;\n\n const res = await graphqlFetch<WorkspaceScheduledJobsResponse>({\n gateway: 'workspace',\n query: WORKSPACE_SCHEDULED_JOBS_QUERY,\n operationName: 'GetWorkspaceScheduledJobsForCalendar',\n variables: { workspaceId },\n // Pass the resolved workspaceId STRING (not `true`) so the\n // `x-workspace-id` header matches the GraphQL variable even when\n // ActiveContextStorage is stale / not hydrated yet. Otherwise the\n // gateway can authorize/federate against the previous workspace's\n // context while the resolver receives the new one.\n workspaceId,\n organizationId: orgFromUrl ?? true,\n tenantId: tenantFromUrl ?? true,\n });\n\n if (res.errors && res.errors.length) {\n // Wrap GraphQL/network errors in a generic, UI-safe Error so we\n // never leak raw resolver / federation messages into the banner.\n throw new Error(res.errors[0]?.message ?? 'Unable to load scheduled tasks');\n }\n setJobs(res.data?.scheduledJobs?.jobs ?? []);\n setRangeError(undefined);\n setJobsLoaded(true);\n } catch (err) {\n console.error('[VibeCalendarPage] Failed to load scheduled jobs:', err);\n setRangeError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setJobsLoading(false);\n }\n }, [workspaceId]);\n\n useEffect(() => {\n if (!workspaceId) {\n // Reset on workspace transitions so the empty-state CTA doesn't\n // flash stale data from the previous workspace while the new one\n // is still resolving. Lives here, not in refetch, because refetch\n // is only called from this effect — when workspaceId is falsy,\n // refetch never runs.\n setJobs([]);\n setRangeError(undefined);\n setJobsLoaded(false);\n return;\n }\n void refetch();\n }, [workspaceId, refetch]);\n\n // `sourceById` built in the same pass so handleTriggerNow does\n // an O(1) Map lookup per click — month/agenda views can mint\n // hundreds of occurrences from a single recurring job.\n const { occurrences, sourceById } = useMemo(() => {\n if (!jobs.length) {\n return {\n occurrences: [],\n sourceById: new Map<string, VibeCalendarTask['source']>(),\n };\n }\n const occs = expandJobsToOccurrences(\n jobs,\n new Date(dateRange.startDate),\n new Date(dateRange.endDate)\n );\n const map = new Map<string, VibeCalendarTask['source']>();\n for (const o of occs) {\n // Always populate with an explicit discriminator. Future\n // shapes (e.g. an externalTrigger source) would otherwise\n // collapse into the default branch silently — `undefined`\n // here means \"id no longer in the visible window\", nothing\n // more.\n map.set(o.task.id, o.task.source ?? 'vibeCalendarTask');\n }\n return { occurrences: occs, sourceById: map };\n }, [jobs, dateRange.startDate, dateRange.endDate]);\n\n // Agents are no longer collected in the modal — the scheduler resolves\n // them from the referenced Action at execution time.\n\n // Mutations\n const [createTaskMutation, { loading: createLoading }] = useCreateVibeCalendarTaskMutation();\n const [triggerNowMutation] = useTriggerVibeCalendarTaskNowMutation();\n // Scheduled-job tiles route to the scheduler subgraph rather than\n // the calendar one (different id space, different RBAC resource).\n const [executeScheduledJobNowMutation] = useExecuteScheduledJobNowMutation();\n\n const handleViewModeChange = useCallback((mode: CalendarViewMode) => {\n setViewMode(mode);\n }, []);\n\n // `undefined` return signals \"no longer in the visible window\" —\n // callers should treat that as the safe default `'vibeCalendarTask'`.\n const sourceForTask = useCallback(\n (taskId: string): VibeCalendarTask['source'] => {\n return sourceById.get(taskId);\n },\n [sourceById]\n );\n\n const handleTriggerNow = useCallback(\n async (taskId: string) => {\n // Clear any prior failure banner before re-attempting — same\n // pattern as ScheduledJobDetailPage.handleExecuteNow.\n setTriggerError(null);\n // Branch on the discriminator set by `jobToVibeCalendarTask`.\n // The view passes `onTriggerNow` to every tile when EITHER\n // permission is held (combined `canExecute` gate above), so we\n // pre-check the per-source permission here and surface a\n // friendly inline banner if missing rather than firing the\n // mutation just to bounce off a server-side RBAC denial.\n const source = sourceForTask(taskId);\n if (source === 'scheduledJob' && !canExecuteScheduledJob) {\n setTriggerError(\n tr(\n 'calendar.scheduleExecuteDenied',\n 'You do not have permission to run scheduled jobs (schedule:execute).'\n )\n );\n return;\n }\n if (source !== 'scheduledJob' && !canExecuteCalendarTask) {\n setTriggerError(\n tr(\n 'calendar.calendarTaskExecuteDenied',\n 'You do not have permission to run calendar tasks (calendar-agent-task:execute).'\n )\n );\n return;\n }\n // VibeControlsProvider sets `errorPolicy: 'all'` so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths consistently —\n // denials would otherwise be silently dropped. Same pattern\n // ActionsPage already uses for quota-exhaust handling.\n const fallbackMsg = tr(\n 'calendar.triggerFailedFallback',\n 'Failed to trigger task. Try again or contact support.'\n );\n try {\n const result =\n source === 'scheduledJob'\n ? await executeScheduledJobNowMutation({\n variables: { id: taskId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n })\n : await triggerNowMutation({\n variables: { id: taskId },\n context: tenantContext,\n });\n if (result.error) {\n console.error('Trigger mutation returned an Apollo error:', result.error);\n setTriggerError(result.error.message || fallbackMsg);\n }\n } catch (err) {\n console.error('Failed to trigger task:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setTriggerError(msg);\n }\n },\n [\n triggerNowMutation,\n executeScheduledJobNowMutation,\n sourceForTask,\n canReadExecutionLogs,\n canExecuteCalendarTask,\n canExecuteScheduledJob,\n tenantContext,\n tr,\n ]\n );\n\n const handleViewLogs = useCallback((task: VibeCalendarTask) => {\n setSelectedTask(task);\n setSelectedExecution(null);\n setShowLogsDrawer(true);\n }, []);\n\n const handleViewDetail = useCallback(\n (taskId: string) => {\n // A tile that originated from a ScheduledJob has no row in\n // wspace-calendar-svc, so VibeTaskDetailPage would 404 on it.\n // Route to the matching in-product detail page instead.\n //\n // Carry the existing query string so the support deep-link\n // flow (`?org=…&tenant=…`) keeps its tenant context across\n // the navigation — without it the detail query runs against\n // the default/stored tenant and returns a wrong-context view.\n // Encode the id for defense-in-depth in case the scheduler\n // ever emits ids that contain URL-reserved characters.\n const encoded = encodeURIComponent(taskId);\n const search = searchParams.toString();\n const suffix = search ? `?${search}` : '';\n const path =\n sourceForTask(taskId) === 'scheduledJob'\n ? `/calendar/scheduled-job/${encoded}`\n : `/calendar/task/${encoded}`;\n navigate(`${path}${suffix}`);\n },\n [navigate, sourceForTask, searchParams]\n );\n\n const handleCreateTask = useCallback(\n async (input: ScheduleTaskInput) => {\n try {\n // Calendar entries always delegate execution to a VibeControls\n // Action. The scheduler resolves agent/command/webhook details\n // from the Action definition at run time — we only send\n // actionId + calendar/schedule config from this modal.\n await createTaskMutation({\n variables: {\n input: {\n calendarId: input.calendarId || 'default',\n title: input.title,\n description: input.description,\n actionId: input.actionId,\n scheduleType: input.scheduleType,\n scheduledAt: input.scheduledAt,\n cronExpression: input.cronExpression,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n timezone: input.timezone,\n },\n },\n });\n await refetch();\n setShowCreateModal(false);\n // Drop the actionId query param after successful creation\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n } catch (err) {\n console.error('Failed to create task:', err);\n }\n },\n [createTaskMutation, refetch, searchParams, setSearchParams]\n );\n\n if (permissionsLoading)\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <LoadingState message={tr('calendar.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canList) {\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'calendar.accessDeniedDescription',\n \"You don't have permission to view the Calendar.\"\n )}\n />\n </MotionPage>\n );\n }\n\n // Show the empty-state CTA only after the first successful load returned\n // an empty list — never while loading, and never when an error blocks the\n // distinction between \"no data\" and \"couldn't fetch\".\n const showEmptyState = jobsLoaded && !rangeError && jobs.length === 0;\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n {/* Error banner (non-blocking) — friendly message, never raw GraphQL */}\n {rangeError && jobs.length === 0 && (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2\">\n <AlertCircle size={16} className=\"text-status-error-text flex-shrink-0\" />\n <p className=\"flex-1 text-sm text-status-error-text\">\n {tr(\n 'calendar.couldNotLoadEvents',\n \"Could not load events. We'll keep trying — you can retry below.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={jobsLoading}\n className=\"rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {tr('calendar.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {triggerError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{triggerError}</p>\n <button\n type=\"button\"\n onClick={() => setTriggerError(null)}\n className=\"p-0.5 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-2\">\n <CalendarClock size={24} className=\"text-text-primary\" />\n <div>\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('nav.vibeCalendar', 'Calendar')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('calendar.description', 'Schedule and manage tasks across your agents')}\n </p>\n </div>\n </div>\n\n {/* Empty-state CTA shown when the user has no scheduled tasks yet. */}\n {showEmptyState && (\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border border-border-default bg-bg-surface px-4 py-3\">\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('calendar.empty.title', 'No scheduled tasks yet')}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n 'calendar.empty.description',\n 'Schedule your first task to start automating actions across your agents.'\n )}\n </p>\n </div>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => setShowCreateModal(true)}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('calendar.empty.cta', 'Schedule your first task')}\n </button>\n )}\n </div>\n )}\n\n {/* Calendar Header with nav + view switcher */}\n <div data-tour=\"calendar-header\">\n <VibeCalendarHeader\n currentDate={currentDate}\n viewMode={viewMode}\n onDateChange={setCurrentDate}\n onViewModeChange={handleViewModeChange}\n onCreateTask={canCreate ? () => setShowCreateModal(true) : undefined}\n />\n </div>\n\n {/* Calendar Body */}\n <div data-tour=\"calendar-view-container\" className=\"flex-1 overflow-hidden\">\n {viewMode === 'month' && (\n <VibeCalMonthView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'week' && (\n <VibeCalWeekView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'day' && (\n <VibeCalDayView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'agenda' && (\n <VibeCalAgendaView\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n </div>\n\n {/* Schedule Task Modal */}\n <ScheduleTaskModal\n isOpen={showCreateModal}\n onClose={() => {\n setShowCreateModal(false);\n // Strip the actionId query param on dismiss so reopening the\n // modal without a fresh navigation doesn't silently resume\n // Action-linked mode.\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n }}\n onSubmit={handleCreateTask}\n loading={createLoading}\n prefillActionId={prefillActionId}\n />\n\n {/* Logs Drawer */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={selectedTask?.title || ''}\n agentId={selectedTask?.agentId || ''}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeCalendarPage.js","names":[],"sources":["../../src/pages/VibeCalendarPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback, useEffect } from 'react';\nimport { useParams, useSearchParams } from 'react-router-dom';\nimport { CalendarClock, AlertCircle, X } from 'lucide-react';\nimport { MotionPage } from '@burdenoff/fe-libs/shared/motion';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { LoadingState } from '@/components/shared/LoadingState';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport {\n VibeCalendarHeader,\n VibeCalMonthView,\n VibeCalWeekView,\n VibeCalDayView,\n VibeCalAgendaView,\n ScheduleTaskModal,\n LogsDrawer,\n} from '../components/vibecalendar';\nimport { useVibeNavigate, useVibeControls } from '@/providers/VibeControlsProvider';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type {\n CalendarViewMode,\n VibeCalendarTask,\n VibeCalendarExecution,\n ScheduleTaskInput,\n} from '../components/vibecalendar';\nimport {\n expandJobsToOccurrences,\n type SchedulerJobLike,\n} from '../components/vibecalendar/expandOccurrences';\nimport {\n useCreateVibeCalendarTaskMutation,\n useExecuteScheduledJobNowMutation,\n useTriggerVibeCalendarTaskNowMutation,\n} from '@/generated/wspace-operations';\n\n/**\n * The /scheduler page writes ScheduledJobs through the WSPACE gateway —\n * `scheduledJobs` is only published by `wspace-scheduler-svc` and is\n * workspace-scoped (the `$workspaceId: ID!` argument is required). The\n * Calendar renders those jobs alongside its own VibeCalendarTasks. We use\n * `graphqlFetch` rather than Apollo to keep the Calendar's local-state\n * timeline decoupled from the global Apollo cache that powers other\n * VibeControls pages.\n */\nconst WORKSPACE_SCHEDULED_JOBS_QUERY = /* GraphQL */ `\n query GetWorkspaceScheduledJobsForCalendar($workspaceId: ID!) {\n scheduledJobs(workspaceId: $workspaceId, pagination: { first: 500 }) {\n jobs {\n id\n name\n description\n status\n scheduleType\n cronExpression\n rrule\n interval\n scheduledAt\n nextExecutionAt\n lastExecutionAt\n actionId\n timezone\n startDate\n endDate\n }\n total\n }\n }\n`;\n\ninterface WorkspaceScheduledJobsResponse {\n scheduledJobs: {\n jobs: SchedulerJobLike[];\n total: number;\n };\n}\n\n/**\n * Calendar Page\n *\n * Main page for scheduling and managing tasks on VibeControls agents.\n * Supports month and agenda views with task creation, on-demand triggering,\n * and log viewing via the LogsDrawer.\n */\nexport function VibeCalendarPage() {\n const navigate = useVibeNavigate();\n const { workspaceId } = useVibeControls();\n const { t } = useI18n();\n const { viewType } = useParams<{ viewType?: string }>();\n const [searchParams, setSearchParams] = useSearchParams();\n // When navigated here from the Actions page via \"Add to calendar\",\n // the URL carries ?actionId=... — open the modal in Action-linked mode.\n const prefillActionId = searchParams.get('actionId') ?? undefined;\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n // The unified-source migration moved the calendar's only list\n // path onto `scheduledJobs(workspaceId)` (the scheduler subgraph) —\n // there's no parallel `vibeCalendarTasks` query firing here\n // anymore. The page-level access gate is therefore\n // `schedule:read` specifically; admitting calendar-task-only\n // readers would let them past the UI only to hit an empty\n // calendar / scheduler RBAC denial on the actual fetch.\n // calendar-agent-task permissions are still consumed downstream\n // (CREATE for the schedule button, EXECUTE for the per-tile ▶\n // branch) but they don't gate the page mount.\n const canList = hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ);\n // Required by the @include gate on `output`/`errorMessage` in\n // both run-now mutations — without this Apollo rejects the\n // operation before it leaves the browser.\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canCreate = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_CREATE);\n // The agenda exclusively renders ScheduledJob tiles in the\n // current unified-source path, so the prop-level gate on\n // `onTriggerNow` is `schedule:execute` — admitting\n // `calendar-agent-task:execute`-only users would show ▶ on every\n // tile just to bounce off a friendly denial banner. Both\n // permissions stay tracked because handleTriggerNow's per-source\n // pre-check uses them defensively in case a future mixed-source\n // render lands here.\n const canExecuteCalendarTask = hasPermission(VIBE_PERMISSIONS.CALENDAR_AGENT_TASK_EXECUTE);\n const canExecuteScheduledJob = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n const canExecute = canExecuteScheduledJob;\n\n // URL overrides reach Apollo via per-request headers so tile-▶\n // mutations stay in the same tenant the support deep-link list\n // loaded against.\n const tenantContext = useTenantContext();\n\n const [currentDate, setCurrentDate] = useState(new Date());\n const [viewMode, setViewMode] = useState<CalendarViewMode>(\n (viewType as CalendarViewMode) || 'month'\n );\n const [showCreateModal, setShowCreateModal] = useState(false);\n\n // Auto-open the modal when arriving with an actionId query param.\n useEffect(() => {\n if (prefillActionId) {\n setShowCreateModal(true);\n }\n }, [prefillActionId]);\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedTask, setSelectedTask] = useState<VibeCalendarTask | null>(null);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n const [triggerError, setTriggerError] = useState<string | null>(null);\n\n // Memoized so downstream useCallback deps stay referentially\n // stable across renders.\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n // Calculate date range for current view\n const dateRange = useMemo(() => {\n const start = new Date(currentDate);\n const end = new Date(currentDate);\n\n switch (viewMode) {\n case 'month':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'week':\n start.setDate(start.getDate() - start.getDay());\n start.setHours(0, 0, 0, 0);\n end.setDate(start.getDate() + 6);\n end.setHours(23, 59, 59, 999);\n break;\n case 'day':\n start.setHours(0, 0, 0, 0);\n end.setHours(23, 59, 59, 999);\n break;\n case 'agenda':\n start.setDate(1);\n start.setHours(0, 0, 0, 0);\n end.setMonth(end.getMonth() + 1, 0);\n end.setHours(23, 59, 59, 999);\n break;\n }\n\n return { startDate: start.toISOString(), endDate: end.toISOString() };\n }, [currentDate, viewMode]);\n\n // Unified calendar view: fetch every ScheduledJob the workspace owns\n // (where /scheduler writes). The calendar renders jobs regardless of how\n // they were created, so all schedule-to-Action tasks show up here as a\n // single source of truth.\n //\n // `scheduledJobs` is published by `wspace-scheduler-svc` and is workspace-\n // scoped — `workspaceId` is required as a query variable. `graphqlFetch`\n // auto-resolves the workspace/org/tenant context from the shell storage\n // and sets the matching headers; URL params (`?org=`, `?tenant=`)\n // override that resolution for the support-tooling deep-link flow.\n const [jobs, setJobs] = useState<SchedulerJobLike[]>([]);\n const [rangeError, setRangeError] = useState<Error | undefined>(undefined);\n const [jobsLoaded, setJobsLoaded] = useState(false);\n const [jobsLoading, setJobsLoading] = useState(false);\n const refetch = useCallback(async () => {\n // Guard: scheduledJobs requires workspaceId — if it isn't resolved yet,\n // skip rather than firing a request that's guaranteed to fail validation.\n // The effect below re-invokes refetch once workspaceId becomes available.\n // (The state-reset on workspace transitions lives in the effect itself,\n // not here, because the effect early-returns when workspaceId is falsy\n // and refetch never gets called in that path.)\n if (!workspaceId) return;\n setJobsLoading(true);\n try {\n // graphqlFetch reads workspace/org/tenant context from storage and sets\n // the proper x-workspace-id / x-org-id / x-tenant-id headers itself —\n // passing `true` opts in to that auto-resolution. URL params override\n // storage for the org/tenant context (used by the support tooling\n // when impersonating a different tenant via deep-link).\n const urlParams = new URLSearchParams(\n typeof window === 'undefined' ? '' : window.location.search\n );\n const orgFromUrl = urlParams.get('org') ?? undefined;\n const tenantFromUrl = urlParams.get('tenant') ?? undefined;\n\n const res = await graphqlFetch<WorkspaceScheduledJobsResponse>({\n gateway: 'workspace',\n query: WORKSPACE_SCHEDULED_JOBS_QUERY,\n operationName: 'GetWorkspaceScheduledJobsForCalendar',\n variables: { workspaceId },\n // Pass the resolved workspaceId STRING (not `true`) so the\n // `x-workspace-id` header matches the GraphQL variable even when\n // ActiveContextStorage is stale / not hydrated yet. Otherwise the\n // gateway can authorize/federate against the previous workspace's\n // context while the resolver receives the new one.\n workspaceId,\n organizationId: orgFromUrl ?? true,\n tenantId: tenantFromUrl ?? true,\n });\n\n if (res.errors && res.errors.length) {\n // Wrap GraphQL/network errors in a generic, UI-safe Error so we\n // never leak raw resolver / federation messages into the banner.\n throw new Error(res.errors[0]?.message ?? 'Unable to load scheduled tasks');\n }\n setJobs(res.data?.scheduledJobs?.jobs ?? []);\n setRangeError(undefined);\n setJobsLoaded(true);\n } catch (err) {\n console.error('[VibeCalendarPage] Failed to load scheduled jobs:', err);\n setRangeError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setJobsLoading(false);\n }\n }, [workspaceId]);\n\n useEffect(() => {\n if (!workspaceId) {\n // Reset on workspace transitions so the empty-state CTA doesn't\n // flash stale data from the previous workspace while the new one\n // is still resolving. Lives here, not in refetch, because refetch\n // is only called from this effect — when workspaceId is falsy,\n // refetch never runs.\n setJobs([]);\n setRangeError(undefined);\n setJobsLoaded(false);\n return;\n }\n void refetch();\n }, [workspaceId, refetch]);\n\n // `sourceById` built in the same pass so handleTriggerNow does\n // an O(1) Map lookup per click — month/agenda views can mint\n // hundreds of occurrences from a single recurring job.\n const { occurrences, sourceById } = useMemo(() => {\n if (!jobs.length) {\n return {\n occurrences: [],\n sourceById: new Map<string, VibeCalendarTask['source']>(),\n };\n }\n const occs = expandJobsToOccurrences(\n jobs,\n new Date(dateRange.startDate),\n new Date(dateRange.endDate)\n );\n const map = new Map<string, VibeCalendarTask['source']>();\n for (const o of occs) {\n // Always populate with an explicit discriminator. Future\n // shapes (e.g. an externalTrigger source) would otherwise\n // collapse into the default branch silently — `undefined`\n // here means \"id no longer in the visible window\", nothing\n // more.\n map.set(o.task.id, o.task.source ?? 'vibeCalendarTask');\n }\n return { occurrences: occs, sourceById: map };\n }, [jobs, dateRange.startDate, dateRange.endDate]);\n\n // Agents are no longer collected in the modal — the scheduler resolves\n // them from the referenced Action at execution time.\n\n // Mutations\n const [createTaskMutation, { loading: createLoading }] = useCreateVibeCalendarTaskMutation();\n const [triggerNowMutation] = useTriggerVibeCalendarTaskNowMutation();\n // Scheduled-job tiles route to the scheduler subgraph rather than\n // the calendar one (different id space, different RBAC resource).\n const [executeScheduledJobNowMutation] = useExecuteScheduledJobNowMutation();\n\n const handleViewModeChange = useCallback((mode: CalendarViewMode) => {\n setViewMode(mode);\n }, []);\n\n // `undefined` return signals \"no longer in the visible window\" —\n // callers should treat that as the safe default `'vibeCalendarTask'`.\n const sourceForTask = useCallback(\n (taskId: string): VibeCalendarTask['source'] => {\n return sourceById.get(taskId);\n },\n [sourceById]\n );\n\n const handleTriggerNow = useCallback(\n async (taskId: string) => {\n // Clear any prior failure banner before re-attempting — same\n // pattern as ScheduledJobDetailPage.handleExecuteNow.\n setTriggerError(null);\n // Branch on the discriminator set by `jobToVibeCalendarTask`.\n // The view passes `onTriggerNow` to every tile when EITHER\n // permission is held (combined `canExecute` gate above), so we\n // pre-check the per-source permission here and surface a\n // friendly inline banner if missing rather than firing the\n // mutation just to bounce off a server-side RBAC denial.\n const source = sourceForTask(taskId);\n if (source === 'scheduledJob' && !canExecuteScheduledJob) {\n setTriggerError(\n tr(\n 'calendar.scheduleExecuteDenied',\n 'You do not have permission to run scheduled jobs (schedule:execute).'\n )\n );\n return;\n }\n if (source !== 'scheduledJob' && !canExecuteCalendarTask) {\n setTriggerError(\n tr(\n 'calendar.calendarTaskExecuteDenied',\n 'You do not have permission to run calendar tasks (calendar-agent-task:execute).'\n )\n );\n return;\n }\n // VibeControlsProvider sets `errorPolicy: 'all'` so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths consistently —\n // denials would otherwise be silently dropped. Same pattern\n // ActionsPage already uses for quota-exhaust handling.\n const fallbackMsg = tr(\n 'calendar.triggerFailedFallback',\n 'Failed to trigger task. Try again or contact support.'\n );\n try {\n const result =\n source === 'scheduledJob'\n ? await executeScheduledJobNowMutation({\n variables: { id: taskId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n })\n : await triggerNowMutation({\n variables: { id: taskId },\n context: tenantContext,\n });\n if (result.error) {\n console.error('Trigger mutation returned an Apollo error:', result.error);\n setTriggerError(result.error.message || fallbackMsg);\n }\n } catch (err) {\n console.error('Failed to trigger task:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setTriggerError(msg);\n }\n },\n [\n triggerNowMutation,\n executeScheduledJobNowMutation,\n sourceForTask,\n canReadExecutionLogs,\n canExecuteCalendarTask,\n canExecuteScheduledJob,\n tenantContext,\n tr,\n ]\n );\n\n const handleViewLogs = useCallback((task: VibeCalendarTask) => {\n setSelectedTask(task);\n setSelectedExecution(null);\n setShowLogsDrawer(true);\n }, []);\n\n const handleViewDetail = useCallback(\n (taskId: string) => {\n // A tile that originated from a ScheduledJob has no row in\n // wspace-calendar-svc, so VibeTaskDetailPage would 404 on it.\n // Route to the matching in-product detail page instead.\n //\n // Carry the existing query string so the support deep-link\n // flow (`?org=…&tenant=…`) keeps its tenant context across\n // the navigation — without it the detail query runs against\n // the default/stored tenant and returns a wrong-context view.\n // Encode the id for defense-in-depth in case the scheduler\n // ever emits ids that contain URL-reserved characters.\n const encoded = encodeURIComponent(taskId);\n const search = searchParams.toString();\n const suffix = search ? `?${search}` : '';\n const path =\n sourceForTask(taskId) === 'scheduledJob'\n ? `/calendar/scheduled-job/${encoded}`\n : `/calendar/task/${encoded}`;\n navigate(`${path}${suffix}`);\n },\n [navigate, sourceForTask, searchParams]\n );\n\n const handleCreateTask = useCallback(\n async (input: ScheduleTaskInput) => {\n try {\n // Calendar entries always delegate execution to a VibeControls\n // Action. The scheduler resolves agent/command/webhook details\n // from the Action definition at run time — we only send\n // actionId + calendar/schedule config from this modal.\n await createTaskMutation({\n variables: {\n input: {\n calendarId: input.calendarId || 'default',\n title: input.title,\n description: input.description,\n actionId: input.actionId,\n scheduleType: input.scheduleType,\n scheduledAt: input.scheduledAt,\n cronExpression: input.cronExpression,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n timezone: input.timezone,\n },\n },\n });\n await refetch();\n setShowCreateModal(false);\n // Drop the actionId query param after successful creation\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n } catch (err) {\n console.error('Failed to create task:', err);\n }\n },\n [createTaskMutation, refetch, searchParams, setSearchParams]\n );\n\n if (permissionsLoading)\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <LoadingState message={tr('calendar.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canList) {\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n <AccessDenied\n title={tr('common.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'calendar.accessDeniedDescription',\n \"You don't have permission to view the Calendar.\"\n )}\n />\n </MotionPage>\n );\n }\n\n // Show the empty-state CTA only after the first successful load returned\n // an empty list — never while loading, and never when an error blocks the\n // distinction between \"no data\" and \"couldn't fetch\".\n const showEmptyState = jobsLoaded && !rangeError && jobs.length === 0;\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-4\">\n {/* Error banner (non-blocking) — friendly message, never raw GraphQL */}\n {rangeError && jobs.length === 0 && (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2\">\n <AlertCircle size={16} className=\"text-status-error-text flex-shrink-0\" />\n <p className=\"flex-1 text-sm text-status-error-text\">\n {tr(\n 'calendar.couldNotLoadEvents',\n \"Could not load events. We'll keep trying — you can retry below.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={jobsLoading}\n className=\"rounded px-3 py-1 text-xs text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {tr('calendar.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {triggerError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{triggerError}</p>\n <button\n type=\"button\"\n onClick={() => setTriggerError(null)}\n className=\"p-0.5 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-2\">\n <CalendarClock size={24} className=\"text-text-primary\" />\n <div>\n <h1 className=\"text-xl font-bold text-text-primary\">\n {tr('nav.vibeCalendar', 'Calendar')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('calendar.description', 'Schedule and manage tasks across your agents')}\n </p>\n </div>\n </div>\n\n <PagePurpose icon={<CalendarClock className=\"size-4\" />}>\n {tr(\n 'calendar.purpose',\n 'The calendar is where you schedule work to run on its own — recurring or one-off tasks that fire actions on your agents at set times (nightly builds, periodic checks, cleanups). Plan it here once and VibeControls runs it for you, so routine jobs happen on time without anyone remembering to kick them off.'\n )}\n </PagePurpose>\n\n {/* Empty-state CTA shown when the user has no scheduled tasks yet. */}\n {showEmptyState && (\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-3 rounded-lg border border-border-default bg-bg-surface px-4 py-3\">\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('calendar.empty.title', 'No scheduled tasks yet')}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n 'calendar.empty.description',\n 'Schedule your first task to start automating actions across your agents.'\n )}\n </p>\n </div>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => setShowCreateModal(true)}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('calendar.empty.cta', 'Schedule your first task')}\n </button>\n )}\n </div>\n )}\n\n {/* Calendar Header with nav + view switcher */}\n <div data-tour=\"calendar-header\">\n <VibeCalendarHeader\n currentDate={currentDate}\n viewMode={viewMode}\n onDateChange={setCurrentDate}\n onViewModeChange={handleViewModeChange}\n onCreateTask={canCreate ? () => setShowCreateModal(true) : undefined}\n />\n </div>\n\n {/* Calendar Body */}\n <div data-tour=\"calendar-view-container\" className=\"flex-1 overflow-hidden\">\n {viewMode === 'month' && (\n <VibeCalMonthView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'week' && (\n <VibeCalWeekView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'day' && (\n <VibeCalDayView\n currentDate={currentDate}\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n {viewMode === 'agenda' && (\n <VibeCalAgendaView\n occurrences={occurrences}\n onTriggerNow={canExecute ? handleTriggerNow : undefined}\n onViewLogs={handleViewLogs}\n onViewDetail={handleViewDetail}\n />\n )}\n </div>\n\n {/* Schedule Task Modal */}\n <ScheduleTaskModal\n isOpen={showCreateModal}\n onClose={() => {\n setShowCreateModal(false);\n // Strip the actionId query param on dismiss so reopening the\n // modal without a fresh navigation doesn't silently resume\n // Action-linked mode.\n if (searchParams.has('actionId')) {\n const next = new URLSearchParams(searchParams);\n next.delete('actionId');\n setSearchParams(next, { replace: true });\n }\n }}\n onSubmit={handleCreateTask}\n loading={createLoading}\n prefillActionId={prefillActionId}\n />\n\n {/* Logs Drawer */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={selectedTask?.title || ''}\n agentId={selectedTask?.agentId || ''}\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|