@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.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/generated/wspace-operations.js +996 -816
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js +28 -0
- package/dist/generated/wspace-types.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/timecampus/TimeCampusRoutes.js +16 -12
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/Modal.js +65 -0
- package/dist/timecampus/components/Modal.js.map +1 -0
- package/dist/timecampus/components/StatusBadge.js +28 -0
- package/dist/timecampus/components/StatusBadge.js.map +1 -0
- package/dist/timecampus/components/Toast.js +87 -0
- package/dist/timecampus/components/Toast.js.map +1 -0
- package/dist/timecampus/index.js +1 -1
- package/dist/timecampus/pages/HomePage.js +143 -135
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
- package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
- package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
- package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
- package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
- package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +367 -65
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js +4 -3
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
- package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
- package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
- package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +229 -97
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
- package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
- package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
- package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
- package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
- package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js +13 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/utils/helpers.js +20 -10
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,84 +1,213 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.js";
|
|
2
2
|
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
-
import {
|
|
4
|
-
import { PageLayout as
|
|
5
|
-
import { EmptyState as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { useDeleteConfigurationMutation as n, useListConfigurationsQuery as r, useSetConfigurationMutation as i } from "../../../generated/wspace-operations.js";
|
|
4
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
5
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
|
+
import { TimeCampusConfigScope as s } from "../../../generated/wspace-types.js";
|
|
7
|
+
import { Modal as c } from "../../components/Modal.js";
|
|
8
|
+
import { useState as l } from "react";
|
|
9
|
+
import { LayoutGrid as u, Plus as d, Trash2 as f } from "lucide-react";
|
|
10
|
+
import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
8
11
|
//#region src/timecampus/pages/widgets/WidgetsPage.tsx
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
var g = [
|
|
13
|
+
{
|
|
14
|
+
value: "timer",
|
|
15
|
+
label: "Active timer"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
value: "project-progress",
|
|
19
|
+
label: "Project progress"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
value: "focus-score",
|
|
23
|
+
label: "Focus score"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "team-stats",
|
|
27
|
+
label: "Team stats"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: "billable-hours",
|
|
31
|
+
label: "Billable hours"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: "recent-entries",
|
|
35
|
+
label: "Recent time entries"
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
function _(e) {
|
|
39
|
+
return g.find((t) => t.value === e)?.label ?? e;
|
|
40
|
+
}
|
|
41
|
+
function v() {
|
|
42
|
+
let v = e(), { data: y, loading: b, error: x, refetch: S } = r({ variables: { scope: s.Workspace } }), [C, { loading: w, error: T }] = i(), [E] = n(), [D, O] = l(!1), [k, A] = l(""), [j, M] = l(g[0].value), [N, P] = l(""), [F, I] = l(null), L = (y?.configurations ?? []).filter((e) => e.key.startsWith("widget.")), R = () => {
|
|
43
|
+
A(""), M(g[0].value), P("");
|
|
44
|
+
}, z = () => {
|
|
45
|
+
R(), O(!0);
|
|
46
|
+
}, B = async () => {
|
|
47
|
+
if (!k.trim()) return;
|
|
48
|
+
let e = typeof crypto < "u" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}`, t = N.trim();
|
|
49
|
+
await C({ variables: { input: {
|
|
50
|
+
key: `widget.${e}`,
|
|
51
|
+
scope: s.Workspace,
|
|
52
|
+
value: {
|
|
53
|
+
name: k.trim(),
|
|
54
|
+
type: j,
|
|
55
|
+
description: t || void 0,
|
|
56
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
57
|
+
},
|
|
58
|
+
description: t || void 0
|
|
59
|
+
} } }), await S(), O(!1), R();
|
|
60
|
+
}, V = async (e) => {
|
|
61
|
+
I(e);
|
|
62
|
+
try {
|
|
63
|
+
await E({ variables: { id: e } }), await S();
|
|
64
|
+
} finally {
|
|
65
|
+
I(null);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return b ? /* @__PURE__ */ m(a, {
|
|
12
69
|
title: "Widgets",
|
|
13
70
|
description: "Customize your dashboard with widgets",
|
|
14
|
-
children: /* @__PURE__ */
|
|
71
|
+
children: /* @__PURE__ */ m(t, {
|
|
15
72
|
variant: "card",
|
|
16
73
|
count: 6
|
|
17
74
|
})
|
|
18
|
-
}) :
|
|
75
|
+
}) : x ? /* @__PURE__ */ m(a, {
|
|
19
76
|
title: "Widgets",
|
|
20
77
|
description: "Customize your dashboard with widgets",
|
|
21
|
-
children: /* @__PURE__ */
|
|
78
|
+
children: /* @__PURE__ */ m("div", {
|
|
22
79
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
23
80
|
children: "Failed to load widget configurations. Please try again later."
|
|
24
81
|
})
|
|
25
|
-
}) : /* @__PURE__ */
|
|
82
|
+
}) : /* @__PURE__ */ h(a, {
|
|
26
83
|
title: "Widgets",
|
|
27
84
|
description: "Customize your dashboard with widgets",
|
|
28
|
-
purpose: "Build a dashboard that shows what you care about —
|
|
29
|
-
actions: /* @__PURE__ */
|
|
85
|
+
purpose: "Build a dashboard that shows what you care about — define widgets for timers, projects, focus and team stats so your most important numbers are the first thing you see. Each widget you create is saved to your workspace configuration.",
|
|
86
|
+
actions: /* @__PURE__ */ h("button", {
|
|
87
|
+
type: "button",
|
|
88
|
+
onClick: z,
|
|
30
89
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
31
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ m(d, { className: "h-4 w-4" }), "Create Widget"]
|
|
32
91
|
}),
|
|
33
|
-
children:
|
|
92
|
+
children: [L.length === 0 ? /* @__PURE__ */ m(o, {
|
|
34
93
|
illustration: "empty-generic",
|
|
35
94
|
title: "No widgets configured yet",
|
|
36
95
|
description: "Add your first widget to start customising your dashboard — widgets are stored as workspace configurations and appear here.",
|
|
37
|
-
action: /* @__PURE__ */
|
|
96
|
+
action: /* @__PURE__ */ h("button", {
|
|
97
|
+
type: "button",
|
|
98
|
+
onClick: z,
|
|
38
99
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
100
|
+
children: [/* @__PURE__ */ m(d, { className: "h-4 w-4" }), "Create Widget"]
|
|
40
101
|
})
|
|
41
|
-
}) : /* @__PURE__ */
|
|
102
|
+
}) : /* @__PURE__ */ m("div", {
|
|
42
103
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
43
|
-
children:
|
|
44
|
-
let t = e.value, n = t.name ?? e.key.replace("widget.", ""), r = t.
|
|
45
|
-
return /* @__PURE__ */
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
104
|
+
children: L.map((e) => {
|
|
105
|
+
let t = e.value, n = t.name ?? e.key.replace("widget.", ""), r = t.type ?? "timer", i = t.description ?? e.description ?? "";
|
|
106
|
+
return /* @__PURE__ */ h("div", {
|
|
107
|
+
role: "button",
|
|
108
|
+
tabIndex: 0,
|
|
109
|
+
onClick: () => v(`/widgets/${e.id}`),
|
|
110
|
+
onKeyDown: (t) => {
|
|
111
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), v(`/widgets/${e.id}`));
|
|
112
|
+
},
|
|
113
|
+
className: "cursor-pointer rounded-xl border border-border-seam bg-gloss-card p-5 shadow-pop transition-colors hover:bg-bg-sunken/50",
|
|
114
|
+
children: [/* @__PURE__ */ h("div", {
|
|
115
|
+
className: "flex items-start justify-between gap-3",
|
|
116
|
+
children: [/* @__PURE__ */ h("div", {
|
|
117
|
+
className: "flex min-w-0 items-center gap-3",
|
|
118
|
+
children: [/* @__PURE__ */ m("div", {
|
|
52
119
|
className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10",
|
|
53
|
-
children: /* @__PURE__ */
|
|
54
|
-
}), /* @__PURE__ */
|
|
55
|
-
className: "
|
|
56
|
-
children:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
120
|
+
children: /* @__PURE__ */ m(u, { className: "h-5 w-5 text-primary" })
|
|
121
|
+
}), /* @__PURE__ */ h("div", {
|
|
122
|
+
className: "min-w-0",
|
|
123
|
+
children: [/* @__PURE__ */ m("p", {
|
|
124
|
+
className: "truncate text-sm font-semibold text-text-primary",
|
|
125
|
+
children: n
|
|
126
|
+
}), /* @__PURE__ */ m("p", {
|
|
127
|
+
className: "text-xs text-text-secondary",
|
|
128
|
+
children: _(r)
|
|
129
|
+
})]
|
|
130
|
+
})]
|
|
131
|
+
}), /* @__PURE__ */ m("button", {
|
|
132
|
+
type: "button",
|
|
133
|
+
"aria-label": `Delete ${n}`,
|
|
134
|
+
disabled: F === e.id,
|
|
135
|
+
onClick: (t) => {
|
|
136
|
+
t.stopPropagation(), V(e.id);
|
|
137
|
+
},
|
|
138
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50",
|
|
139
|
+
children: /* @__PURE__ */ m(f, { className: "h-4 w-4" })
|
|
68
140
|
})]
|
|
69
|
-
}), /* @__PURE__ */
|
|
70
|
-
className: "mt-3
|
|
71
|
-
children:
|
|
72
|
-
className: "flex items-center gap-1",
|
|
73
|
-
children: [/* @__PURE__ */ c(s, { className: "h-3 w-3 fill-amber-400 text-status-warning-text" }), /* @__PURE__ */ c("span", { children: u })]
|
|
74
|
-
}), /* @__PURE__ */ l("span", { children: [d, " downloads"] })]
|
|
141
|
+
}), i && /* @__PURE__ */ m("p", {
|
|
142
|
+
className: "mt-3 line-clamp-2 text-xs text-text-secondary",
|
|
143
|
+
children: i
|
|
75
144
|
})]
|
|
76
145
|
}, e.id);
|
|
77
146
|
})
|
|
78
|
-
})
|
|
147
|
+
}), /* @__PURE__ */ h(c, {
|
|
148
|
+
open: D,
|
|
149
|
+
onClose: () => O(!1),
|
|
150
|
+
title: "Create Widget",
|
|
151
|
+
description: "Define a dashboard widget for your workspace.",
|
|
152
|
+
footer: /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m("button", {
|
|
153
|
+
type: "button",
|
|
154
|
+
onClick: () => O(!1),
|
|
155
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
156
|
+
children: "Cancel"
|
|
157
|
+
}), /* @__PURE__ */ m("button", {
|
|
158
|
+
type: "button",
|
|
159
|
+
onClick: B,
|
|
160
|
+
disabled: w || !k.trim(),
|
|
161
|
+
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
162
|
+
children: w ? "Creating..." : "Create Widget"
|
|
163
|
+
})] }),
|
|
164
|
+
children: [T && /* @__PURE__ */ m("div", {
|
|
165
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
166
|
+
children: "Failed to create widget. Please try again."
|
|
167
|
+
}), /* @__PURE__ */ h("div", {
|
|
168
|
+
className: "space-y-4",
|
|
169
|
+
children: [
|
|
170
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
|
|
171
|
+
className: "block text-sm font-medium text-text-primary",
|
|
172
|
+
children: "Widget Name"
|
|
173
|
+
}), /* @__PURE__ */ m("input", {
|
|
174
|
+
type: "text",
|
|
175
|
+
placeholder: "e.g. This week's billable hours",
|
|
176
|
+
value: k,
|
|
177
|
+
onChange: (e) => A(e.target.value),
|
|
178
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary"
|
|
179
|
+
})] }),
|
|
180
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("label", {
|
|
181
|
+
className: "block text-sm font-medium text-text-primary",
|
|
182
|
+
children: "Metric"
|
|
183
|
+
}), /* @__PURE__ */ m("select", {
|
|
184
|
+
value: j,
|
|
185
|
+
onChange: (e) => M(e.target.value),
|
|
186
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
187
|
+
children: g.map((e) => /* @__PURE__ */ m("option", {
|
|
188
|
+
value: e.value,
|
|
189
|
+
children: e.label
|
|
190
|
+
}, e.value))
|
|
191
|
+
})] }),
|
|
192
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("label", {
|
|
193
|
+
className: "block text-sm font-medium text-text-primary",
|
|
194
|
+
children: ["Description", /* @__PURE__ */ m("span", {
|
|
195
|
+
className: "ml-1 font-normal text-text-secondary",
|
|
196
|
+
children: "(optional)"
|
|
197
|
+
})]
|
|
198
|
+
}), /* @__PURE__ */ m("textarea", {
|
|
199
|
+
placeholder: "What this widget shows",
|
|
200
|
+
rows: 3,
|
|
201
|
+
value: N,
|
|
202
|
+
onChange: (e) => P(e.target.value),
|
|
203
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary"
|
|
204
|
+
})] })
|
|
205
|
+
]
|
|
206
|
+
})]
|
|
207
|
+
})]
|
|
79
208
|
});
|
|
80
209
|
}
|
|
81
210
|
//#endregion
|
|
82
|
-
export {
|
|
211
|
+
export { v as WidgetsPage };
|
|
83
212
|
|
|
84
213
|
//# sourceMappingURL=WidgetsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetsPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetsPage.tsx"],"sourcesContent":["import { LayoutGrid, Plus, Star } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListConfigurationsQuery,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function WidgetsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useListConfigurationsQuery({\n variables: { scope: \"WORKSPACE\" as any },\n });\n\n const configurations = data?.configurations ?? [];\n\n // Filter configurations that represent widgets (key starts with 'widget.')\n const widgets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"widget.\"),\n );\n\n if (loading) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load widget configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n purpose=\"Build a dashboard that shows what you care about — pick and arrange widgets for timers, projects, focus and team stats so your most important numbers are the first thing you see.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Create Widget\n </button>\n }\n >\n {widgets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No widgets configured yet\"\n description=\"Add your first widget to start customising your dashboard — widgets are stored as workspace configurations and appear here.\"\n action={\n <button className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\">\n <Plus className=\"h-4 w-4\" />\n Create Widget\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {widgets.map((widget: ConfigurationFieldsFragment) => {\n const val = widget.value as Record<string, unknown>;\n const name =\n (val.name as string) ?? widget.key.replace(\"widget.\", \"\");\n const category = (val.category as string) ?? \"General\";\n const size = (val.size as string) ?? \"medium\";\n const isInstalled = (val.isInstalled as boolean) ?? false;\n const rating = (val.rating as number) ?? 0;\n const downloads = (val.downloads as number) ?? 0;\n\n return (\n <div\n key={widget.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-5 w-5 text-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {category} · {size}\n </p>\n </div>\n </div>\n {isInstalled && (\n <span className=\"inline-flex rounded-full bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\">\n Installed\n </span>\n )}\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-amber-400 text-status-warning-text\" />\n <span>{rating}</span>\n </div>\n <span>{downloads} downloads</span>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,IAAc;CAC5B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EAAE,SAAM,YAAS,aAAU,EAA2B,EAC1D,WAAW,EAAE,OAAO,aAAoB,EACzC,CAAC,EAKI,KAHiB,GAAM,kBAAkB,EAAE,EAGlB,QAAQ,MACrC,EAAE,IAAI,WAAW,UAAU,CAC5B;AA0BD,QAxBI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,gBAErB;;YAGV,EAAQ,WAAW,IAClB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,gBAErB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAQ,KAAK,MAAwC;IACpD,IAAM,IAAM,EAAO,OACb,IACH,EAAI,QAAmB,EAAO,IAAI,QAAQ,WAAW,GAAG,EACrD,IAAY,EAAI,YAAuB,WACvC,IAAQ,EAAI,QAAmB,UAC/B,IAAe,EAAI,eAA2B,IAC9C,IAAU,EAAI,UAAqB,GACnC,IAAa,EAAI,aAAwB;AAE/C,WACE,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAY,WAAU,wBAAyB,CAAA;QAC3C,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV;QACC,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG;SAAS;SAAW;SACnB;UACA,EAAA,CAAA,CACF;UACL,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAyG;OAElH,CAAA,CAEL;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,mDAAoD,CAAA,EACpE,kBAAC,QAAD,EAAA,UAAO,GAAc,CAAA,CACjB;UACN,kBAAC,QAAD,EAAA,UAAA,CAAO,GAAU,aAAiB,EAAA,CAAA,CAC9B;QACF;OA9BC,EAAO,GA8BR;KAER;GACE,CAAA;EAEG,CAAA"}
|
|
1
|
+
{"version":3,"file":"WidgetsPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { LayoutGrid, Plus, Trash2 } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport {\n useListConfigurationsQuery,\n useSetConfigurationMutation,\n useDeleteConfigurationMutation,\n type ConfigurationFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusConfigScope } from \"@/generated/wspace-types\";\n\nconst WIDGET_TYPES = [\n { value: \"timer\", label: \"Active timer\" },\n { value: \"project-progress\", label: \"Project progress\" },\n { value: \"focus-score\", label: \"Focus score\" },\n { value: \"team-stats\", label: \"Team stats\" },\n { value: \"billable-hours\", label: \"Billable hours\" },\n { value: \"recent-entries\", label: \"Recent time entries\" },\n];\n\nfunction widgetTypeLabel(value: string): string {\n return WIDGET_TYPES.find((t) => t.value === value)?.label ?? value;\n}\n\nexport function WidgetsPage() {\n const navigate = useTimeCampusNavigate();\n\n const { data, loading, error, refetch } = useListConfigurationsQuery({\n variables: { scope: TimeCampusConfigScope.Workspace },\n });\n\n const [setConfiguration, { loading: creating, error: createError }] =\n useSetConfigurationMutation();\n const [deleteConfiguration] = useDeleteConfigurationMutation();\n\n const [modalOpen, setModalOpen] = useState(false);\n const [name, setName] = useState(\"\");\n const [type, setType] = useState(WIDGET_TYPES[0].value);\n const [description, setDescription] = useState(\"\");\n const [deletingId, setDeletingId] = useState<string | null>(null);\n\n const configurations = data?.configurations ?? [];\n const widgets = configurations.filter((c: ConfigurationFieldsFragment) =>\n c.key.startsWith(\"widget.\"),\n );\n\n const resetForm = () => {\n setName(\"\");\n setType(WIDGET_TYPES[0].value);\n setDescription(\"\");\n };\n\n const openModal = () => {\n resetForm();\n setModalOpen(true);\n };\n\n const handleCreate = async () => {\n if (!name.trim()) return;\n const id =\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}`;\n const trimmedDescription = description.trim();\n await setConfiguration({\n variables: {\n input: {\n key: `widget.${id}`,\n scope: TimeCampusConfigScope.Workspace,\n value: {\n name: name.trim(),\n type,\n description: trimmedDescription || undefined,\n createdAt: new Date().toISOString(),\n },\n description: trimmedDescription || undefined,\n },\n },\n });\n await refetch();\n setModalOpen(false);\n resetForm();\n };\n\n const handleDelete = async (id: string) => {\n setDeletingId(id);\n try {\n await deleteConfiguration({ variables: { id } });\n await refetch();\n } finally {\n setDeletingId(null);\n }\n };\n\n if (loading) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <LoadingSkeleton variant=\"card\" count={6} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load widget configurations. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widgets\"\n description=\"Customize your dashboard with widgets\"\n purpose=\"Build a dashboard that shows what you care about — define widgets for timers, projects, focus and team stats so your most important numbers are the first thing you see. Each widget you create is saved to your workspace configuration.\"\n actions={\n <button\n type=\"button\"\n onClick={openModal}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Create Widget\n </button>\n }\n >\n {widgets.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No widgets configured yet\"\n description=\"Add your first widget to start customising your dashboard — widgets are stored as workspace configurations and appear here.\"\n action={\n <button\n type=\"button\"\n onClick={openModal}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Create Widget\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {widgets.map((widget: ConfigurationFieldsFragment) => {\n const val = widget.value as Record<string, unknown>;\n const widgetName =\n (val.name as string) ?? widget.key.replace(\"widget.\", \"\");\n const widgetType = (val.type as string) ?? \"timer\";\n const widgetDescription =\n (val.description as string) ?? widget.description ?? \"\";\n\n return (\n <div\n key={widget.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/widgets/${widget.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/widgets/${widget.id}`);\n }\n }}\n className=\"cursor-pointer rounded-xl border border-border-seam bg-gloss-card p-5 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-5 w-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {widgetName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {widgetTypeLabel(widgetType)}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label={`Delete ${widgetName}`}\n disabled={deletingId === widget.id}\n onClick={(e) => {\n e.stopPropagation();\n void handleDelete(widget.id);\n }}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n {widgetDescription && (\n <p className=\"mt-3 line-clamp-2 text-xs text-text-secondary\">\n {widgetDescription}\n </p>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n <Modal\n open={modalOpen}\n onClose={() => setModalOpen(false)}\n title=\"Create Widget\"\n description=\"Define a dashboard widget for your workspace.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setModalOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleCreate}\n disabled={creating || !name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Creating...\" : \"Create Widget\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create widget. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Widget Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. This week's billable hours\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Metric\n </label>\n <select\n value={type}\n onChange={(e) => setType(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {WIDGET_TYPES.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n <span className=\"ml-1 font-normal text-text-secondary\">\n (optional)\n </span>\n </label>\n <textarea\n placeholder=\"What this widget shows\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n </div>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,90 +1,241 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { getStatusColor as e } from "../../utils/helpers.js";
|
|
2
|
+
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
+
import { useDeleteConfigurationMutation as n, useListConfigurationsQuery as r, useSetConfigurationMutation as i } from "../../../generated/wspace-operations.js";
|
|
4
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
5
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
|
+
import { TimeCampusConfigScope as s } from "../../../generated/wspace-types.js";
|
|
7
|
+
import { Modal as c } from "../../components/Modal.js";
|
|
8
|
+
import { useState as l } from "react";
|
|
9
|
+
import { GitBranch as u, Plus as d, Power as f, Trash2 as p, Zap as m } from "lucide-react";
|
|
10
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
10
12
|
//#region src/timecampus/pages/workflows/WorkflowsPage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
var y = [
|
|
14
|
+
{
|
|
15
|
+
value: "timer.started",
|
|
16
|
+
label: "When a timer starts"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "timer.stopped",
|
|
20
|
+
label: "When a timer stops"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "entry.created",
|
|
24
|
+
label: "When a time entry is created"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: "timesheet.due",
|
|
28
|
+
label: "When a timesheet is due"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: "budget.overrun",
|
|
32
|
+
label: "When a project runs over budget"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: "manual",
|
|
36
|
+
label: "Manually"
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
function b(e) {
|
|
40
|
+
return y.find((t) => t.value === e)?.label ?? e;
|
|
41
|
+
}
|
|
42
|
+
function x() {
|
|
43
|
+
let { data: x, loading: S, error: C, refetch: w } = r({ variables: { scope: s.Workspace } }), [T, { loading: E, error: D }] = i(), [O] = n(), [k, A] = l(!1), [j, M] = l(""), [N, P] = l(y[0].value), [F, I] = l(""), [L, R] = l(null), z = (x?.configurations ?? []).filter((e) => e.key.startsWith("workflow.")), B = () => {
|
|
44
|
+
M(""), P(y[0].value), I("");
|
|
45
|
+
}, V = () => {
|
|
46
|
+
B(), A(!0);
|
|
47
|
+
}, H = async () => {
|
|
48
|
+
if (!j.trim()) return;
|
|
49
|
+
let e = typeof crypto < "u" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}`, t = F.trim();
|
|
50
|
+
await T({ variables: { input: {
|
|
51
|
+
key: `workflow.${e}`,
|
|
52
|
+
scope: s.Workspace,
|
|
53
|
+
value: {
|
|
54
|
+
name: j.trim(),
|
|
55
|
+
trigger: N,
|
|
56
|
+
status: "active",
|
|
57
|
+
description: t || void 0,
|
|
58
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
59
|
+
},
|
|
60
|
+
description: t || void 0
|
|
61
|
+
} } }), await w(), A(!1), B();
|
|
62
|
+
}, U = async (e) => {
|
|
63
|
+
R(e.id);
|
|
64
|
+
try {
|
|
65
|
+
let t = e.value ?? {}, n = t.status === "active" ? "inactive" : "active";
|
|
66
|
+
await T({ variables: { input: {
|
|
67
|
+
key: e.key,
|
|
68
|
+
scope: s.Workspace,
|
|
69
|
+
value: {
|
|
70
|
+
...t,
|
|
71
|
+
status: n
|
|
72
|
+
},
|
|
73
|
+
description: e.description ?? void 0
|
|
74
|
+
} } }), await w();
|
|
75
|
+
} finally {
|
|
76
|
+
R(null);
|
|
77
|
+
}
|
|
78
|
+
}, W = async (e) => {
|
|
79
|
+
R(e);
|
|
80
|
+
try {
|
|
81
|
+
await O({ variables: { id: e } }), await w();
|
|
82
|
+
} finally {
|
|
83
|
+
R(null);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
if (S) return /* @__PURE__ */ g(a, {
|
|
14
87
|
title: "Workflows",
|
|
15
88
|
description: "Automate repetitive tasks",
|
|
16
|
-
children: /* @__PURE__ */
|
|
89
|
+
children: /* @__PURE__ */ g(t, {
|
|
17
90
|
variant: "list",
|
|
18
91
|
count: 5
|
|
19
92
|
})
|
|
20
|
-
})
|
|
93
|
+
});
|
|
94
|
+
if (C) return /* @__PURE__ */ g(a, {
|
|
21
95
|
title: "Workflows",
|
|
22
96
|
description: "Automate repetitive tasks",
|
|
23
|
-
children: /* @__PURE__ */
|
|
97
|
+
children: /* @__PURE__ */ g("div", {
|
|
24
98
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
25
99
|
children: "Failed to load workflow configurations. Please try again later."
|
|
26
100
|
})
|
|
27
|
-
})
|
|
101
|
+
});
|
|
102
|
+
let G = /* @__PURE__ */ _("button", {
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: V,
|
|
105
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
106
|
+
children: [/* @__PURE__ */ g(d, { className: "h-4 w-4" }), "Create Workflow"]
|
|
107
|
+
});
|
|
108
|
+
return /* @__PURE__ */ _(a, {
|
|
28
109
|
title: "Workflows",
|
|
29
110
|
description: "Automate repetitive tasks",
|
|
30
|
-
purpose: "Put the busywork on autopilot — auto-start timers, submit timesheets,
|
|
31
|
-
actions:
|
|
32
|
-
|
|
33
|
-
children: [/* @__PURE__ */ l(s, { className: "h-4 w-4" }), "Create Workflow"]
|
|
34
|
-
}),
|
|
35
|
-
children: g.length === 0 ? /* @__PURE__ */ l(a, {
|
|
111
|
+
purpose: "Put the busywork on autopilot — auto-start timers, submit timesheets, flag overruns — by wiring up rules that react to what happens in TimeCampus. Each workflow you define is saved to your workspace; the advanced trigger/condition builder is on the roadmap.",
|
|
112
|
+
actions: G,
|
|
113
|
+
children: [z.length === 0 ? /* @__PURE__ */ g(o, {
|
|
36
114
|
illustration: "empty-generic",
|
|
37
115
|
title: "No workflows configured yet",
|
|
38
116
|
description: "Create your first automation to handle repetitive time-tracking tasks — workflows are stored as workspace configurations and appear here.",
|
|
39
|
-
action:
|
|
40
|
-
|
|
41
|
-
children: [/* @__PURE__ */ l(s, { className: "h-4 w-4" }), "Create Workflow"]
|
|
42
|
-
})
|
|
43
|
-
}) : /* @__PURE__ */ l(d, {
|
|
117
|
+
action: G
|
|
118
|
+
}) : /* @__PURE__ */ g(v, {
|
|
44
119
|
treatment: "glass",
|
|
45
120
|
glow: !0,
|
|
46
121
|
className: "overflow-hidden",
|
|
47
|
-
children: /* @__PURE__ */
|
|
48
|
-
className: "divide-y divide-border",
|
|
49
|
-
children:
|
|
50
|
-
let n =
|
|
51
|
-
return /* @__PURE__ */
|
|
52
|
-
className: "flex items-center justify-between p-4 hover:bg-bg-sunken/50
|
|
53
|
-
children: [/* @__PURE__ */
|
|
54
|
-
className: "flex items-center gap-3",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: /* @__PURE__ */ g("div", {
|
|
123
|
+
className: "divide-y divide-border-seam",
|
|
124
|
+
children: z.map((t) => {
|
|
125
|
+
let n = t.value ?? {}, r = n.name ?? t.key.replace("workflow.", ""), i = n.status ?? "inactive", a = n.trigger ?? "manual";
|
|
126
|
+
return /* @__PURE__ */ _("div", {
|
|
127
|
+
className: "flex items-center justify-between p-4 transition-colors hover:bg-bg-sunken/50",
|
|
128
|
+
children: [/* @__PURE__ */ _("div", {
|
|
129
|
+
className: "flex min-w-0 items-center gap-3",
|
|
130
|
+
children: [/* @__PURE__ */ g("div", {
|
|
56
131
|
className: "flex h-9 w-9 items-center justify-center rounded-lg bg-action-primary-bg/10",
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
-
}), /* @__PURE__ */
|
|
59
|
-
className: "
|
|
60
|
-
children:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}), /* @__PURE__ */ u("div", {
|
|
72
|
-
className: "flex items-center gap-4",
|
|
73
|
-
children: [/* @__PURE__ */ u("div", {
|
|
74
|
-
className: "text-right text-xs text-text-secondary",
|
|
75
|
-
children: [/* @__PURE__ */ u("p", { children: [d, " runs"] }), /* @__PURE__ */ u("p", { children: [f, "% success"] })]
|
|
76
|
-
}), /* @__PURE__ */ l("span", {
|
|
77
|
-
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(i)}`,
|
|
78
|
-
children: i
|
|
132
|
+
children: /* @__PURE__ */ g(u, { className: "h-4 w-4 text-primary" })
|
|
133
|
+
}), /* @__PURE__ */ _("div", {
|
|
134
|
+
className: "min-w-0",
|
|
135
|
+
children: [/* @__PURE__ */ g("p", {
|
|
136
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
137
|
+
children: r
|
|
138
|
+
}), /* @__PURE__ */ _("p", {
|
|
139
|
+
className: "text-xs text-text-secondary",
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ g(m, { className: "inline h-3 w-3" }),
|
|
142
|
+
" ",
|
|
143
|
+
b(a)
|
|
144
|
+
]
|
|
145
|
+
})]
|
|
79
146
|
})]
|
|
147
|
+
}), /* @__PURE__ */ _("div", {
|
|
148
|
+
className: "flex items-center gap-2",
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ g("span", {
|
|
151
|
+
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${e(i)}`,
|
|
152
|
+
children: i
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ g("button", {
|
|
155
|
+
type: "button",
|
|
156
|
+
"aria-label": i === "active" ? `Deactivate ${r}` : `Activate ${r}`,
|
|
157
|
+
disabled: L === t.id,
|
|
158
|
+
onClick: () => void U(t),
|
|
159
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50",
|
|
160
|
+
children: /* @__PURE__ */ g(f, { className: "h-4 w-4" })
|
|
161
|
+
}),
|
|
162
|
+
/* @__PURE__ */ g("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
"aria-label": `Delete ${r}`,
|
|
165
|
+
disabled: L === t.id,
|
|
166
|
+
onClick: () => void W(t.id),
|
|
167
|
+
className: "rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-status-error-text disabled:opacity-50",
|
|
168
|
+
children: /* @__PURE__ */ g(p, { className: "h-4 w-4" })
|
|
169
|
+
})
|
|
170
|
+
]
|
|
80
171
|
})]
|
|
81
|
-
},
|
|
172
|
+
}, t.id);
|
|
82
173
|
})
|
|
83
174
|
})
|
|
84
|
-
})
|
|
175
|
+
}), /* @__PURE__ */ _(c, {
|
|
176
|
+
open: k,
|
|
177
|
+
onClose: () => A(!1),
|
|
178
|
+
title: "Create Workflow",
|
|
179
|
+
description: "Define an automation rule for your workspace.",
|
|
180
|
+
footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
|
|
181
|
+
type: "button",
|
|
182
|
+
onClick: () => A(!1),
|
|
183
|
+
className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
184
|
+
children: "Cancel"
|
|
185
|
+
}), /* @__PURE__ */ g("button", {
|
|
186
|
+
type: "button",
|
|
187
|
+
onClick: H,
|
|
188
|
+
disabled: E || !j.trim(),
|
|
189
|
+
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
|
|
190
|
+
children: E ? "Creating..." : "Create Workflow"
|
|
191
|
+
})] }),
|
|
192
|
+
children: [D && /* @__PURE__ */ g("div", {
|
|
193
|
+
className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
194
|
+
children: "Failed to create workflow. Please try again."
|
|
195
|
+
}), /* @__PURE__ */ _("div", {
|
|
196
|
+
className: "space-y-4",
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
199
|
+
className: "block text-sm font-medium text-text-primary",
|
|
200
|
+
children: "Workflow Name"
|
|
201
|
+
}), /* @__PURE__ */ g("input", {
|
|
202
|
+
type: "text",
|
|
203
|
+
placeholder: "e.g. Auto-submit my weekly timesheet",
|
|
204
|
+
value: j,
|
|
205
|
+
onChange: (e) => M(e.target.value),
|
|
206
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary"
|
|
207
|
+
})] }),
|
|
208
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
209
|
+
className: "block text-sm font-medium text-text-primary",
|
|
210
|
+
children: "Trigger"
|
|
211
|
+
}), /* @__PURE__ */ g("select", {
|
|
212
|
+
value: N,
|
|
213
|
+
onChange: (e) => P(e.target.value),
|
|
214
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
215
|
+
children: y.map((e) => /* @__PURE__ */ g("option", {
|
|
216
|
+
value: e.value,
|
|
217
|
+
children: e.label
|
|
218
|
+
}, e.value))
|
|
219
|
+
})] }),
|
|
220
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
|
|
221
|
+
className: "block text-sm font-medium text-text-primary",
|
|
222
|
+
children: ["Description", /* @__PURE__ */ g("span", {
|
|
223
|
+
className: "ml-1 font-normal text-text-secondary",
|
|
224
|
+
children: "(optional)"
|
|
225
|
+
})]
|
|
226
|
+
}), /* @__PURE__ */ g("textarea", {
|
|
227
|
+
placeholder: "What this workflow should do",
|
|
228
|
+
rows: 3,
|
|
229
|
+
value: F,
|
|
230
|
+
onChange: (e) => I(e.target.value),
|
|
231
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary"
|
|
232
|
+
})] })
|
|
233
|
+
]
|
|
234
|
+
})]
|
|
235
|
+
})]
|
|
85
236
|
});
|
|
86
237
|
}
|
|
87
238
|
//#endregion
|
|
88
|
-
export {
|
|
239
|
+
export { x as WorkflowsPage };
|
|
89
240
|
|
|
90
241
|
//# sourceMappingURL=WorkflowsPage.js.map
|