@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
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
|
+
import { formatDuration as t, formatTime as n } from "../../utils/helpers.js";
|
|
3
|
+
import { StatCard as r } from "../../components/StatCard.js";
|
|
4
|
+
import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
|
|
5
|
+
import { useGetTimeCampusTimelineQuery as a, useRecategorizeTimeCampusCaptureEventMutation as o } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as s } from "../../components/PageLayout.js";
|
|
7
|
+
import { EmptyState as c } from "../../components/EmptyState.js";
|
|
8
|
+
import { useMemo as l, useState as u } from "react";
|
|
9
|
+
import { Activity as d, Brain as f, Calendar as p, Coffee as m, Github as h, MessageSquare as g, Users as _, Zap as v } from "lucide-react";
|
|
10
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
11
|
+
import { GlassCard as x } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/timecampus/pages/timeline/TimelinePage.tsx
|
|
13
|
+
var S = {
|
|
14
|
+
DEEP_WORK: {
|
|
15
|
+
label: "Deep work",
|
|
16
|
+
icon: f,
|
|
17
|
+
dot: "bg-status-success-bg",
|
|
18
|
+
chip: "bg-status-success-bg text-status-success-text"
|
|
19
|
+
},
|
|
20
|
+
MEETING: {
|
|
21
|
+
label: "Meeting",
|
|
22
|
+
icon: _,
|
|
23
|
+
dot: "bg-status-info-bg",
|
|
24
|
+
chip: "bg-status-info-bg text-status-info-text"
|
|
25
|
+
},
|
|
26
|
+
COMMUNICATION: {
|
|
27
|
+
label: "Communication",
|
|
28
|
+
icon: g,
|
|
29
|
+
dot: "bg-status-warning-bg",
|
|
30
|
+
chip: "bg-status-warning-bg text-status-warning-text"
|
|
31
|
+
},
|
|
32
|
+
BREAK: {
|
|
33
|
+
label: "Break",
|
|
34
|
+
icon: m,
|
|
35
|
+
dot: "bg-bg-sunken",
|
|
36
|
+
chip: "bg-bg-sunken text-text-secondary"
|
|
37
|
+
},
|
|
38
|
+
OTHER: {
|
|
39
|
+
label: "Other",
|
|
40
|
+
icon: d,
|
|
41
|
+
dot: "bg-bg-sunken",
|
|
42
|
+
chip: "bg-bg-sunken text-text-secondary"
|
|
43
|
+
}
|
|
44
|
+
}, C = {
|
|
45
|
+
TIME_ENTRY: v,
|
|
46
|
+
MEETING: _,
|
|
47
|
+
POMODORO: f,
|
|
48
|
+
EMAIL: g,
|
|
49
|
+
CAPTURE: h
|
|
50
|
+
}, w = [
|
|
51
|
+
"DEEP_WORK",
|
|
52
|
+
"MEETING",
|
|
53
|
+
"COMMUNICATION",
|
|
54
|
+
"BREAK",
|
|
55
|
+
"OTHER"
|
|
56
|
+
];
|
|
57
|
+
function T(e) {
|
|
58
|
+
let t = e.getTimezoneOffset();
|
|
59
|
+
return (/* @__PURE__ */ new Date(e.getTime() - t * 60 * 1e3)).toISOString().slice(0, 10);
|
|
60
|
+
}
|
|
61
|
+
function E(e) {
|
|
62
|
+
let t = /* @__PURE__ */ new Date(`${e}T00:00:00`), n = /* @__PURE__ */ new Date(`${e}T23:59:59.999`);
|
|
63
|
+
return {
|
|
64
|
+
from: t.toISOString(),
|
|
65
|
+
to: n.toISOString()
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function D() {
|
|
69
|
+
let { workspaceId: m = "" } = e(), [h, g] = u(() => T(/* @__PURE__ */ new Date())), { from: _, to: D } = l(() => E(h), [h]), { data: O, loading: k, error: A, refetch: j } = a({
|
|
70
|
+
variables: {
|
|
71
|
+
from: _,
|
|
72
|
+
to: D
|
|
73
|
+
},
|
|
74
|
+
skip: !m,
|
|
75
|
+
fetchPolicy: "cache-and-network"
|
|
76
|
+
}), [M, { loading: N }] = o(), P = O?.timeCampusTimeline, F = P?.segments ?? [], I = P?.summary, L = l(() => {
|
|
77
|
+
let e = /* @__PURE__ */ new Map();
|
|
78
|
+
for (let t of I?.byCategory ?? []) e.set(t.category, {
|
|
79
|
+
totalSeconds: t.totalSeconds,
|
|
80
|
+
segmentCount: t.segmentCount
|
|
81
|
+
});
|
|
82
|
+
return e;
|
|
83
|
+
}, [I]), R = async (e, t) => {
|
|
84
|
+
!e.isRecategorizable || e.category === t || (await M({ variables: {
|
|
85
|
+
id: e.sourceId,
|
|
86
|
+
category: t
|
|
87
|
+
} }), await j());
|
|
88
|
+
}, z = I && I.focusRatio != null ? Math.round(I.focusRatio * 100) : 0;
|
|
89
|
+
return /* @__PURE__ */ y(s, {
|
|
90
|
+
title: "Timeline",
|
|
91
|
+
description: "Your day, assembled automatically",
|
|
92
|
+
purpose: "See where your time actually went — meetings, deep work, communication and breaks stitched into one honest, chronological view from everything TimeCampus captures, so you can spot the patterns worth changing.",
|
|
93
|
+
actions: /* @__PURE__ */ b("label", {
|
|
94
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
95
|
+
children: [/* @__PURE__ */ y(p, { className: "h-4 w-4" }), /* @__PURE__ */ y("input", {
|
|
96
|
+
type: "date",
|
|
97
|
+
value: h,
|
|
98
|
+
max: T(/* @__PURE__ */ new Date()),
|
|
99
|
+
onChange: (e) => g(e.target.value),
|
|
100
|
+
className: "rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40"
|
|
101
|
+
})]
|
|
102
|
+
}),
|
|
103
|
+
children: k && !P ? /* @__PURE__ */ b("div", {
|
|
104
|
+
className: "space-y-6",
|
|
105
|
+
children: [/* @__PURE__ */ y(i, {
|
|
106
|
+
variant: "card",
|
|
107
|
+
count: 4
|
|
108
|
+
}), /* @__PURE__ */ y(i, {
|
|
109
|
+
variant: "list",
|
|
110
|
+
count: 6
|
|
111
|
+
})]
|
|
112
|
+
}) : A ? /* @__PURE__ */ y("div", {
|
|
113
|
+
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
114
|
+
children: "Failed to load your timeline. Please try again later."
|
|
115
|
+
}) : /* @__PURE__ */ b("div", {
|
|
116
|
+
className: "space-y-6",
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ b("div", {
|
|
119
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ y(r, {
|
|
122
|
+
featured: !0,
|
|
123
|
+
title: "Tracked time",
|
|
124
|
+
value: t(I?.totalTrackedSeconds ?? 0),
|
|
125
|
+
icon: /* @__PURE__ */ y(d, { className: "h-5 w-5" }),
|
|
126
|
+
description: "Total activity across the day"
|
|
127
|
+
}),
|
|
128
|
+
/* @__PURE__ */ y(r, {
|
|
129
|
+
title: "Focus ratio",
|
|
130
|
+
value: `${z}%`,
|
|
131
|
+
icon: /* @__PURE__ */ y(f, { className: "h-5 w-5" }),
|
|
132
|
+
description: "Share of time in deep work"
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ y(r, {
|
|
135
|
+
title: "Context switches",
|
|
136
|
+
value: I?.contextSwitches ?? 0,
|
|
137
|
+
icon: /* @__PURE__ */ y(v, { className: "h-5 w-5" }),
|
|
138
|
+
description: "Category changes through the day"
|
|
139
|
+
}),
|
|
140
|
+
/* @__PURE__ */ y(r, {
|
|
141
|
+
title: "Longest focus block",
|
|
142
|
+
value: t(I?.longestFocusBlockSeconds ?? 0),
|
|
143
|
+
icon: /* @__PURE__ */ y(f, { className: "h-5 w-5" }),
|
|
144
|
+
description: "Your deepest uninterrupted stretch"
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
}),
|
|
148
|
+
F.length > 0 && /* @__PURE__ */ b(x, {
|
|
149
|
+
treatment: "glass",
|
|
150
|
+
className: "p-4 sm:p-6",
|
|
151
|
+
children: [/* @__PURE__ */ y("h2", {
|
|
152
|
+
className: "mb-4 text-sm font-semibold text-text-primary",
|
|
153
|
+
children: "Where the time went"
|
|
154
|
+
}), /* @__PURE__ */ y("div", {
|
|
155
|
+
className: "flex flex-wrap gap-4",
|
|
156
|
+
children: w.filter((e) => L.has(e)).map((e) => {
|
|
157
|
+
let n = S[e], r = L.get(e);
|
|
158
|
+
return /* @__PURE__ */ b("div", {
|
|
159
|
+
className: "flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2",
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ y("span", {
|
|
162
|
+
className: `inline-block h-2.5 w-2.5 rounded-full ${n.dot}`,
|
|
163
|
+
"aria-hidden": !0
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ y("span", {
|
|
166
|
+
className: "text-sm font-medium text-text-primary",
|
|
167
|
+
children: n.label
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ y("span", {
|
|
170
|
+
className: "text-sm tabular-nums text-text-secondary",
|
|
171
|
+
children: t(r.totalSeconds)
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
}, e);
|
|
175
|
+
})
|
|
176
|
+
})]
|
|
177
|
+
}),
|
|
178
|
+
F.length === 0 ? /* @__PURE__ */ y(c, {
|
|
179
|
+
illustration: "empty-generic",
|
|
180
|
+
title: "No activity captured for this day",
|
|
181
|
+
description: "As you track time, hold meetings, run focus sessions, and connect capture channels, your day assembles here automatically — categorized and in order."
|
|
182
|
+
}) : /* @__PURE__ */ y(x, {
|
|
183
|
+
treatment: "glass",
|
|
184
|
+
glow: !0,
|
|
185
|
+
className: "overflow-hidden",
|
|
186
|
+
children: /* @__PURE__ */ y("ol", {
|
|
187
|
+
className: "divide-y divide-border-seam",
|
|
188
|
+
children: F.map((e) => {
|
|
189
|
+
let r = e.category ?? "OTHER", i = S[r] ?? S.OTHER, a = C[e.source] ?? d;
|
|
190
|
+
return /* @__PURE__ */ b("li", {
|
|
191
|
+
className: "flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50",
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ b("div", {
|
|
194
|
+
className: "flex w-16 shrink-0 flex-col items-end pt-0.5 text-xs tabular-nums text-text-secondary",
|
|
195
|
+
children: [/* @__PURE__ */ y("span", { children: n(e.startTime) }), /* @__PURE__ */ y("span", {
|
|
196
|
+
className: "text-text-tertiary",
|
|
197
|
+
children: t(e.durationSeconds)
|
|
198
|
+
})]
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ y("div", {
|
|
201
|
+
className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${i.chip}`,
|
|
202
|
+
children: /* @__PURE__ */ y(a, { className: "h-4 w-4" })
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ b("div", {
|
|
205
|
+
className: "min-w-0 flex-1",
|
|
206
|
+
children: [/* @__PURE__ */ y("p", {
|
|
207
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
208
|
+
children: e.title || i.label
|
|
209
|
+
}), /* @__PURE__ */ b("div", {
|
|
210
|
+
className: "mt-1 flex flex-wrap items-center gap-2",
|
|
211
|
+
children: [
|
|
212
|
+
/* @__PURE__ */ y("span", {
|
|
213
|
+
className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${i.chip}`,
|
|
214
|
+
children: i.label
|
|
215
|
+
}),
|
|
216
|
+
e.app && /* @__PURE__ */ y("span", {
|
|
217
|
+
className: "text-xs text-text-secondary",
|
|
218
|
+
children: e.app
|
|
219
|
+
}),
|
|
220
|
+
e.isRecategorizable && /* @__PURE__ */ y("span", {
|
|
221
|
+
className: "text-xs text-text-tertiary",
|
|
222
|
+
children: "via capture"
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
})]
|
|
226
|
+
}),
|
|
227
|
+
e.isRecategorizable && /* @__PURE__ */ y("select", {
|
|
228
|
+
"aria-label": "Recategorize activity",
|
|
229
|
+
disabled: N,
|
|
230
|
+
value: r,
|
|
231
|
+
onChange: (t) => R(e, t.target.value),
|
|
232
|
+
className: "shrink-0 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40",
|
|
233
|
+
children: w.map((e) => /* @__PURE__ */ y("option", {
|
|
234
|
+
value: e,
|
|
235
|
+
children: S[e].label
|
|
236
|
+
}, e))
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
}, e.id);
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
//#endregion
|
|
248
|
+
export { D as TimelinePage };
|
|
249
|
+
|
|
250
|
+
//# sourceMappingURL=TimelinePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimelinePage.js","names":[],"sources":["../../../../src/timecampus/pages/timeline/TimelinePage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n Activity,\n Brain,\n Calendar,\n Coffee,\n Github,\n MessageSquare,\n Users,\n Zap,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { formatDuration, formatTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeCampusTimelineQuery,\n useRecategorizeTimeCampusCaptureEventMutation,\n type TimelineSegmentFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\ntype Category = \"MEETING\" | \"DEEP_WORK\" | \"COMMUNICATION\" | \"BREAK\" | \"OTHER\";\n\nconst CATEGORY_META: Record<\n Category,\n { label: string; icon: typeof Brain; dot: string; chip: string }\n> = {\n DEEP_WORK: {\n label: \"Deep work\",\n icon: Brain,\n dot: \"bg-status-success-bg\",\n chip: \"bg-status-success-bg text-status-success-text\",\n },\n MEETING: {\n label: \"Meeting\",\n icon: Users,\n dot: \"bg-status-info-bg\",\n chip: \"bg-status-info-bg text-status-info-text\",\n },\n COMMUNICATION: {\n label: \"Communication\",\n icon: MessageSquare,\n dot: \"bg-status-warning-bg\",\n chip: \"bg-status-warning-bg text-status-warning-text\",\n },\n BREAK: {\n label: \"Break\",\n icon: Coffee,\n dot: \"bg-bg-sunken\",\n chip: \"bg-bg-sunken text-text-secondary\",\n },\n OTHER: {\n label: \"Other\",\n icon: Activity,\n dot: \"bg-bg-sunken\",\n chip: \"bg-bg-sunken text-text-secondary\",\n },\n};\n\nconst SOURCE_ICON: Record<string, typeof Brain> = {\n TIME_ENTRY: Zap,\n MEETING: Users,\n POMODORO: Brain,\n EMAIL: MessageSquare,\n CAPTURE: Github,\n};\n\nconst CATEGORY_ORDER: Category[] = [\n \"DEEP_WORK\",\n \"MEETING\",\n \"COMMUNICATION\",\n \"BREAK\",\n \"OTHER\",\n];\n\nfunction toLocalDateInput(date: Date): string {\n const offset = date.getTimezoneOffset();\n const local = new Date(date.getTime() - offset * 60 * 1000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction dayBounds(dateStr: string): { from: string; to: string } {\n const start = new Date(`${dateStr}T00:00:00`);\n const end = new Date(`${dateStr}T23:59:59.999`);\n return { from: start.toISOString(), to: end.toISOString() };\n}\n\nexport function TimelinePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const [dateStr, setDateStr] = useState(() => toLocalDateInput(new Date()));\n\n const { from, to } = useMemo(() => dayBounds(dateStr), [dateStr]);\n\n const { data, loading, error, refetch } = useGetTimeCampusTimelineQuery({\n variables: { from, to },\n skip: !workspaceId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const [recategorize, { loading: recategorizing }] =\n useRecategorizeTimeCampusCaptureEventMutation();\n\n const timeline = data?.timeCampusTimeline;\n const segments = timeline?.segments ?? [];\n const summary = timeline?.summary;\n\n const totalsByCategory = useMemo(() => {\n const map = new Map<string, { totalSeconds: number; segmentCount: number }>();\n for (const t of summary?.byCategory ?? []) {\n map.set(t.category, {\n totalSeconds: t.totalSeconds,\n segmentCount: t.segmentCount,\n });\n }\n return map;\n }, [summary]);\n\n const handleRecategorize = async (\n segment: TimelineSegmentFieldsFragment,\n category: Category,\n ) => {\n if (!segment.isRecategorizable || segment.category === category) return;\n await recategorize({\n variables: { id: segment.sourceId, category },\n });\n await refetch();\n };\n\n const focusPct =\n summary && summary.focusRatio != null\n ? Math.round(summary.focusRatio * 100)\n : 0;\n\n return (\n <PageLayout\n title=\"Timeline\"\n description=\"Your day, assembled automatically\"\n purpose=\"See where your time actually went — meetings, deep work, communication and breaks stitched into one honest, chronological view from everything TimeCampus captures, so you can spot the patterns worth changing.\"\n actions={\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Calendar className=\"h-4 w-4\" />\n <input\n type=\"date\"\n value={dateStr}\n max={toLocalDateInput(new Date())}\n onChange={(e) => setDateStr(e.target.value)}\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n }\n >\n {loading && !timeline ? (\n <div className=\"space-y-6\">\n <LoadingSkeleton variant=\"card\" count={4} />\n <LoadingSkeleton variant=\"list\" count={6} />\n </div>\n ) : error ? (\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 your timeline. Please try again later.\n </div>\n ) : (\n <div className=\"space-y-6\">\n {/* Summary */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Tracked time\"\n value={formatDuration(summary?.totalTrackedSeconds ?? 0)}\n icon={<Activity className=\"h-5 w-5\" />}\n description=\"Total activity across the day\"\n />\n <StatCard\n title=\"Focus ratio\"\n value={`${focusPct}%`}\n icon={<Brain className=\"h-5 w-5\" />}\n description=\"Share of time in deep work\"\n />\n <StatCard\n title=\"Context switches\"\n value={summary?.contextSwitches ?? 0}\n icon={<Zap className=\"h-5 w-5\" />}\n description=\"Category changes through the day\"\n />\n <StatCard\n title=\"Longest focus block\"\n value={formatDuration(summary?.longestFocusBlockSeconds ?? 0)}\n icon={<Brain className=\"h-5 w-5\" />}\n description=\"Your deepest uninterrupted stretch\"\n />\n </div>\n\n {/* Category breakdown */}\n {segments.length > 0 && (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-6\">\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Where the time went\n </h2>\n <div className=\"flex flex-wrap gap-4\">\n {CATEGORY_ORDER.filter((c) => totalsByCategory.has(c)).map(\n (category) => {\n const meta = CATEGORY_META[category];\n const total = totalsByCategory.get(category)!;\n return (\n <div\n key={category}\n className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <span\n className={`inline-block h-2.5 w-2.5 rounded-full ${meta.dot}`}\n aria-hidden\n />\n <span className=\"text-sm font-medium text-text-primary\">\n {meta.label}\n </span>\n <span className=\"text-sm tabular-nums text-text-secondary\">\n {formatDuration(total.totalSeconds)}\n </span>\n </div>\n );\n },\n )}\n </div>\n </GlassCard>\n )}\n\n {/* Chronological timeline */}\n {segments.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No activity captured for this day\"\n description=\"As you track time, hold meetings, run focus sessions, and connect capture channels, your day assembles here automatically — categorized and in order.\"\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <ol className=\"divide-y divide-border-seam\">\n {segments.map((segment: TimelineSegmentFieldsFragment) => {\n const category = (segment.category as Category) ?? \"OTHER\";\n const meta = CATEGORY_META[category] ?? CATEGORY_META.OTHER;\n const SourceIcon = SOURCE_ICON[segment.source] ?? Activity;\n return (\n <li\n key={segment.id}\n className=\"flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex w-16 shrink-0 flex-col items-end pt-0.5 text-xs tabular-nums text-text-secondary\">\n <span>{formatTime(segment.startTime)}</span>\n <span className=\"text-text-tertiary\">\n {formatDuration(segment.durationSeconds)}\n </span>\n </div>\n <div\n className={`mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${meta.chip}`}\n >\n <SourceIcon className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {segment.title || meta.label}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${meta.chip}`}\n >\n {meta.label}\n </span>\n {segment.app && (\n <span className=\"text-xs text-text-secondary\">\n {segment.app}\n </span>\n )}\n {segment.isRecategorizable && (\n <span className=\"text-xs text-text-tertiary\">\n via capture\n </span>\n )}\n </div>\n </div>\n {segment.isRecategorizable && (\n <select\n aria-label=\"Recategorize activity\"\n disabled={recategorizing}\n value={category}\n onChange={(e) =>\n handleRecategorize(\n segment,\n e.target.value as Category,\n )\n }\n className=\"shrink-0 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n {CATEGORY_ORDER.map((c) => (\n <option key={c} value={c}>\n {CATEGORY_META[c].label}\n </option>\n ))}\n </select>\n )}\n </li>\n );\n })}\n </ol>\n </GlassCard>\n )}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,107 +1,189 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LoadingSkeleton as
|
|
3
|
-
import { useGetConfigurationQuery as r } from "../../../generated/wspace-operations.js";
|
|
4
|
-
import { PageLayout as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.js";
|
|
2
|
+
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
+
import { useDeleteConfigurationMutation as n, useGetConfigurationQuery as r, useUpdateConfigurationMutation as i } from "../../../generated/wspace-operations.js";
|
|
4
|
+
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
5
|
+
import { useEffect as o, useState as s } from "react";
|
|
6
|
+
import { ArrowLeft as c, LayoutGrid as l, Save as u, Trash2 as d } from "lucide-react";
|
|
7
|
+
import { useParams as f } from "react-router-dom";
|
|
8
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
9
|
+
import { GlassCard as h, NextSteps as g } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/timecampus/pages/widgets/WidgetDetailPage.tsx
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
11
|
+
var _ = [
|
|
12
|
+
{
|
|
13
|
+
value: "timer",
|
|
14
|
+
label: "Active timer"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: "project-progress",
|
|
18
|
+
label: "Project progress"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "focus-score",
|
|
22
|
+
label: "Focus score"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "team-stats",
|
|
26
|
+
label: "Team stats"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "billable-hours",
|
|
30
|
+
label: "Billable hours"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "recent-entries",
|
|
34
|
+
label: "Recent time entries"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
function v() {
|
|
38
|
+
let { id: v } = f(), y = e(), { data: b, loading: x, error: S } = r({
|
|
39
|
+
variables: { id: v ?? "" },
|
|
40
|
+
skip: !v
|
|
41
|
+
}), [C, { loading: w, error: T }] = i(), [E, { loading: D }] = n(), O = b?.configuration, [k, A] = s(""), [j, M] = s(_[0].value), [N, P] = s("");
|
|
42
|
+
o(() => {
|
|
43
|
+
if (O) {
|
|
44
|
+
let e = O.value ?? {};
|
|
45
|
+
A(e.name ?? O.key.replace("widget.", "")), M(e.type ?? _[0].value), P(e.description ?? O.description ?? "");
|
|
46
|
+
}
|
|
47
|
+
}, [O]);
|
|
48
|
+
let F = async () => {
|
|
49
|
+
if (!O || !k.trim()) return;
|
|
50
|
+
let e = O.value ?? {}, t = N.trim();
|
|
51
|
+
await C({ variables: {
|
|
52
|
+
id: O.id,
|
|
53
|
+
input: {
|
|
54
|
+
value: {
|
|
55
|
+
...e,
|
|
56
|
+
name: k.trim(),
|
|
57
|
+
type: j,
|
|
58
|
+
description: t || void 0
|
|
59
|
+
},
|
|
60
|
+
description: t || void 0
|
|
61
|
+
}
|
|
62
|
+
} }), y("/widgets");
|
|
63
|
+
};
|
|
64
|
+
return x ? /* @__PURE__ */ p(a, {
|
|
16
65
|
title: "Widget Details",
|
|
17
66
|
description: "Loading...",
|
|
18
|
-
children: /* @__PURE__ */
|
|
19
|
-
}) :
|
|
67
|
+
children: /* @__PURE__ */ p(t, { variant: "detail" })
|
|
68
|
+
}) : S || !O ? /* @__PURE__ */ p(a, {
|
|
20
69
|
title: "Widget Details",
|
|
21
|
-
description: `Widget ID: ${
|
|
22
|
-
actions: /* @__PURE__ */
|
|
70
|
+
description: `Widget ID: ${v}`,
|
|
71
|
+
actions: /* @__PURE__ */ m("button", {
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: () => y("/widgets"),
|
|
23
74
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
24
|
-
children: [/* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ p(c, { className: "h-4 w-4" }), "Back to Widgets"]
|
|
25
76
|
}),
|
|
26
|
-
children: /* @__PURE__ */
|
|
77
|
+
children: /* @__PURE__ */ p("div", {
|
|
27
78
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
28
|
-
children:
|
|
79
|
+
children: S ? "Failed to load widget details. Please try again later." : "Widget configuration not found."
|
|
29
80
|
})
|
|
30
|
-
}) : /* @__PURE__ */
|
|
81
|
+
}) : /* @__PURE__ */ p(a, {
|
|
31
82
|
title: "Widget Details",
|
|
32
|
-
description: `Widget ID: ${
|
|
33
|
-
purpose: "
|
|
34
|
-
actions: /* @__PURE__ */
|
|
83
|
+
description: `Widget ID: ${v}`,
|
|
84
|
+
purpose: "Edit what this widget shows and how it's labelled, then save it back to your workspace — or remove it if you no longer need it on your dashboard.",
|
|
85
|
+
actions: /* @__PURE__ */ m("button", {
|
|
86
|
+
type: "button",
|
|
87
|
+
onClick: () => y("/widgets"),
|
|
35
88
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
|
|
36
|
-
children: [/* @__PURE__ */
|
|
89
|
+
children: [/* @__PURE__ */ p(c, { className: "h-4 w-4" }), "Back to Widgets"]
|
|
37
90
|
}),
|
|
38
|
-
children: /* @__PURE__ */
|
|
91
|
+
children: /* @__PURE__ */ m("div", {
|
|
39
92
|
className: "mx-auto max-w-2xl",
|
|
40
|
-
children: [/* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ p(g, {
|
|
41
94
|
storageKey: "timecampus-widget-detail",
|
|
42
95
|
className: "mb-6",
|
|
43
96
|
steps: [{
|
|
44
|
-
id: "
|
|
45
|
-
label: "
|
|
46
|
-
description: "
|
|
47
|
-
onClick: () =>
|
|
97
|
+
id: "save",
|
|
98
|
+
label: "Save your changes",
|
|
99
|
+
description: "Update this widget's name, metric or description.",
|
|
100
|
+
onClick: () => void F()
|
|
48
101
|
}, {
|
|
49
102
|
id: "browse",
|
|
50
103
|
label: "Browse other widgets",
|
|
51
|
-
description: "
|
|
52
|
-
onClick: () =>
|
|
104
|
+
description: "Manage the rest of your dashboard widgets.",
|
|
105
|
+
onClick: () => y("/widgets")
|
|
53
106
|
}]
|
|
54
|
-
}), /* @__PURE__ */
|
|
107
|
+
}), /* @__PURE__ */ m(h, {
|
|
55
108
|
treatment: "glass",
|
|
56
109
|
glow: !0,
|
|
57
110
|
className: "p-6",
|
|
58
111
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ m("div", {
|
|
60
113
|
className: "flex items-start gap-4",
|
|
61
|
-
children: [/* @__PURE__ */
|
|
114
|
+
children: [/* @__PURE__ */ p("div", {
|
|
62
115
|
className: "flex h-16 w-16 items-center justify-center rounded-xl bg-action-primary-bg/10",
|
|
63
|
-
children: /* @__PURE__ */
|
|
64
|
-
}), /* @__PURE__ */
|
|
116
|
+
children: /* @__PURE__ */ p(l, { className: "h-8 w-8 text-primary" })
|
|
117
|
+
}), /* @__PURE__ */ m("div", {
|
|
65
118
|
className: "flex-1",
|
|
66
|
-
children: [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
children: C
|
|
74
|
-
}),
|
|
75
|
-
/* @__PURE__ */ d("div", {
|
|
76
|
-
className: "mt-2 flex items-center gap-3 text-sm text-text-secondary",
|
|
77
|
-
children: [
|
|
78
|
-
/* @__PURE__ */ d("div", {
|
|
79
|
-
className: "flex items-center gap-1",
|
|
80
|
-
children: [/* @__PURE__ */ u(c, { className: "h-4 w-4 fill-amber-400 text-status-warning-text" }), /* @__PURE__ */ u("span", { children: w })]
|
|
81
|
-
}),
|
|
82
|
-
/* @__PURE__ */ u("span", { children: "·" }),
|
|
83
|
-
/* @__PURE__ */ d("div", {
|
|
84
|
-
className: "flex items-center gap-1",
|
|
85
|
-
children: [/* @__PURE__ */ u(o, { className: "h-4 w-4" }), /* @__PURE__ */ d("span", { children: [T.toLocaleString(), " installs"] })]
|
|
86
|
-
})
|
|
87
|
-
]
|
|
88
|
-
})
|
|
89
|
-
]
|
|
119
|
+
children: [/* @__PURE__ */ p("h2", {
|
|
120
|
+
className: "text-lg font-semibold text-text-primary",
|
|
121
|
+
children: k || "Untitled widget"
|
|
122
|
+
}), /* @__PURE__ */ p("p", {
|
|
123
|
+
className: "text-sm text-text-secondary",
|
|
124
|
+
children: "This widget is saved to your workspace and can be shown on your dashboard."
|
|
125
|
+
})]
|
|
90
126
|
})]
|
|
91
127
|
}),
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
className: "mt-
|
|
94
|
-
children:
|
|
95
|
-
className: "text-sm text-text-secondary",
|
|
96
|
-
children: "Widget preview will render here"
|
|
97
|
-
})
|
|
128
|
+
T && /* @__PURE__ */ p("div", {
|
|
129
|
+
className: "mt-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
|
|
130
|
+
children: "Failed to save widget. Please try again."
|
|
98
131
|
}),
|
|
99
|
-
/* @__PURE__ */
|
|
100
|
-
className: "mt-
|
|
101
|
-
children:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
/* @__PURE__ */ m("div", {
|
|
133
|
+
className: "mt-6 space-y-4",
|
|
134
|
+
children: [
|
|
135
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
136
|
+
className: "block text-sm font-medium text-text-primary",
|
|
137
|
+
children: "Widget Name"
|
|
138
|
+
}), /* @__PURE__ */ p("input", {
|
|
139
|
+
type: "text",
|
|
140
|
+
value: k,
|
|
141
|
+
onChange: (e) => A(e.target.value),
|
|
142
|
+
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"
|
|
143
|
+
})] }),
|
|
144
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
145
|
+
className: "block text-sm font-medium text-text-primary",
|
|
146
|
+
children: "Metric"
|
|
147
|
+
}), /* @__PURE__ */ p("select", {
|
|
148
|
+
value: j,
|
|
149
|
+
onChange: (e) => M(e.target.value),
|
|
150
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
151
|
+
children: _.map((e) => /* @__PURE__ */ p("option", {
|
|
152
|
+
value: e.value,
|
|
153
|
+
children: e.label
|
|
154
|
+
}, e.value))
|
|
155
|
+
})] }),
|
|
156
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ m("label", {
|
|
157
|
+
className: "block text-sm font-medium text-text-primary",
|
|
158
|
+
children: ["Description", /* @__PURE__ */ p("span", {
|
|
159
|
+
className: "ml-1 font-normal text-text-secondary",
|
|
160
|
+
children: "(optional)"
|
|
161
|
+
})]
|
|
162
|
+
}), /* @__PURE__ */ p("textarea", {
|
|
163
|
+
rows: 3,
|
|
164
|
+
value: N,
|
|
165
|
+
onChange: (e) => P(e.target.value),
|
|
166
|
+
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"
|
|
167
|
+
})] })
|
|
168
|
+
]
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ m("div", {
|
|
171
|
+
className: "mt-6 flex items-center justify-between",
|
|
172
|
+
children: [/* @__PURE__ */ m("button", {
|
|
173
|
+
type: "button",
|
|
174
|
+
onClick: async () => {
|
|
175
|
+
O && (await E({ variables: { id: O.id } }), y("/widgets"));
|
|
176
|
+
},
|
|
177
|
+
disabled: D,
|
|
178
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50",
|
|
179
|
+
children: [/* @__PURE__ */ p(d, { className: "h-4 w-4" }), D ? "Deleting..." : "Delete"]
|
|
180
|
+
}), /* @__PURE__ */ m("button", {
|
|
181
|
+
type: "button",
|
|
182
|
+
onClick: F,
|
|
183
|
+
disabled: w || !k.trim(),
|
|
184
|
+
className: "inline-flex items-center gap-1.5 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",
|
|
185
|
+
children: [/* @__PURE__ */ p(u, { className: "h-4 w-4" }), w ? "Saving..." : "Save Changes"]
|
|
186
|
+
})]
|
|
105
187
|
})
|
|
106
188
|
]
|
|
107
189
|
})]
|
|
@@ -109,6 +191,6 @@ function m() {
|
|
|
109
191
|
});
|
|
110
192
|
}
|
|
111
193
|
//#endregion
|
|
112
|
-
export {
|
|
194
|
+
export { v as WidgetDetailPage };
|
|
113
195
|
|
|
114
196
|
//# sourceMappingURL=WidgetDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetDetailPage.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { LayoutGrid, ArrowLeft, Star, Download } from \"lucide-react\";\nimport { GlassCard, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport { useGetConfigurationQuery } from \"@/generated/wspace-operations\";\n\nexport function WidgetDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const { data, loading, error } = useGetConfigurationQuery({\n variables: { id: id ?? \"\" },\n skip: !id,\n });\n\n const config = data?.configuration;\n const val = (config?.value as Record<string, unknown>) ?? {};\n const name =\n (val.name as string) ?? config?.key?.replace(\"widget.\", \"\") ?? \"Widget\";\n const description =\n config?.description ??\n (val.description as string) ??\n \"A configurable dashboard widget for time tracking.\";\n const rating = (val.rating as number) ?? 0;\n const installs = (val.downloads as number) ?? 0;\n\n if (loading) {\n return (\n <PageLayout title=\"Widget Details\" description=\"Loading...\">\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !config) {\n return (\n <PageLayout\n title=\"Widget Details\"\n description={`Widget ID: ${id}`}\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\">\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Widgets\n </button>\n }\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 {error\n ? \"Failed to load widget details. Please try again later.\"\n : \"Widget configuration not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widget Details\"\n description={`Widget ID: ${id}`}\n purpose=\"Preview what a widget shows and how it behaves before adding it to your dashboard, so you only install the panels that genuinely help you keep an eye on your time.\"\n actions={\n <button className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\">\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Widgets\n </button>\n }\n >\n <div className=\"mx-auto max-w-2xl\">\n <NextSteps\n storageKey=\"timecampus-widget-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"install\",\n label: \"Install this widget\",\n description: \"Add it to your dashboard.\",\n onClick: () => {},\n },\n {\n id: \"browse\",\n label: \"Browse other widgets\",\n description: \"Find more panels for your dashboard.\",\n onClick: () => navigate(\"/widgets\"),\n },\n ]}\n />\n <GlassCard treatment=\"glass\" glow className=\"p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex h-16 w-16 items-center justify-center rounded-xl bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-8 w-8 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {name}\n </h2>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n <div className=\"mt-2 flex items-center gap-3 text-sm text-text-secondary\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-4 w-4 fill-amber-400 text-status-warning-text\" />\n <span>{rating}</span>\n </div>\n <span>·</span>\n <div className=\"flex items-center gap-1\">\n <Download className=\"h-4 w-4\" />\n <span>{installs.toLocaleString()} installs</span>\n </div>\n </div>\n </div>\n </div>\n <div className=\"mt-6 flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Widget preview will render here\n </p>\n </div>\n <div className=\"mt-4 flex justify-end\">\n <button 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\">\n Install Widget\n </button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,IAAmB;CACjC,IAAM,EAAE,UAAO,GAA2B,EACpC,EAAE,iBAAc,OAAO,GAAe,EACtC,IAAW,GAAuB,EAElC,EAAE,SAAM,YAAS,aAAU,EAAyB;EACxD,WAAW,EAAE,IAAI,KAAM,IAAI;EAC3B,MAAM,CAAC;EACR,CAAC,EAEI,IAAS,GAAM,eACf,IAAO,GAAQ,SAAqC,EAAE,EACtD,IACH,EAAI,QAAmB,GAAQ,KAAK,QAAQ,WAAW,GAAG,IAAI,UAC3D,IACJ,GAAQ,eACP,EAAI,eACL,sDACI,IAAU,EAAI,UAAqB,GACnC,IAAY,EAAI,aAAwB;AA+B9C,QA7BI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAiB,aAAY;YAC7C,kBAAC,GAAD,EAAiB,SAAQ,UAAW,CAAA;EACzB,CAAA,GAIb,KAAS,CAAC,IAEV,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,cAAc;EAC3B,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,kBAE1B;;YAGX,kBAAC,OAAD;GAAK,WAAU;aACZ,IACG,2DACA;GACA,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,cAAc;EAC3B,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,kBAE1B;;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,YAAW;IACX,WAAU;IACV,OAAO,CACL;KACE,IAAI;KACJ,OAAO;KACP,aAAa;KACb,eAAe;KAChB,EACD;KACE,IAAI;KACJ,OAAO;KACP,aAAa;KACb,eAAe,EAAS,WAAW;KACpC,CACF;IACD,CAAA,EACF,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAA5C;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAY,WAAU,wBAAyB,CAAA;OAC3C,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,MAAD;SAAI,WAAU;mBACX;SACE,CAAA;QACL,kBAAC,KAAD;SAAG,WAAU;mBAA+B;SAAgB,CAAA;QAC5D,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,mDAAoD,CAAA,EACpE,kBAAC,QAAD,EAAA,UAAO,GAAc,CAAA,CACjB;;UACN,kBAAC,QAAD,EAAA,UAAM,KAAe,CAAA;UACrB,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAChC,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAS,gBAAgB,EAAC,YAAgB,EAAA,CAAA,CAC7C;;UACF;;QACF;SACF;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACA,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,UAAD;OAAQ,WAAU;iBAAuH;OAEhI,CAAA;MACL,CAAA;KACI;MACR;;EACK,CAAA"}
|
|
1
|
+
{"version":3,"file":"WidgetDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/widgets/WidgetDetailPage.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { LayoutGrid, ArrowLeft, Save, Trash2 } from \"lucide-react\";\nimport { GlassCard, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetConfigurationQuery,\n useUpdateConfigurationMutation,\n useDeleteConfigurationMutation,\n} from \"@/generated/wspace-operations\";\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\nexport function WidgetDetailPage() {\n const { id } = useParams<{ id: string }>();\n const navigate = useTimeCampusNavigate();\n\n const { data, loading, error } = useGetConfigurationQuery({\n variables: { id: id ?? \"\" },\n skip: !id,\n });\n\n const [updateConfiguration, { loading: saving, error: saveError }] =\n useUpdateConfigurationMutation();\n const [deleteConfiguration, { loading: deleting }] =\n useDeleteConfigurationMutation();\n\n const config = data?.configuration;\n\n const [name, setName] = useState(\"\");\n const [type, setType] = useState(WIDGET_TYPES[0].value);\n const [description, setDescription] = useState(\"\");\n\n useEffect(() => {\n if (config) {\n const val = (config.value as Record<string, unknown>) ?? {};\n setName((val.name as string) ?? config.key.replace(\"widget.\", \"\"));\n setType((val.type as string) ?? WIDGET_TYPES[0].value);\n setDescription((val.description as string) ?? config.description ?? \"\");\n }\n }, [config]);\n\n const handleSave = async () => {\n if (!config || !name.trim()) return;\n const val = (config.value as Record<string, unknown>) ?? {};\n const trimmedDescription = description.trim();\n await updateConfiguration({\n variables: {\n id: config.id,\n input: {\n value: {\n ...val,\n name: name.trim(),\n type,\n description: trimmedDescription || undefined,\n },\n description: trimmedDescription || undefined,\n },\n },\n });\n navigate(\"/widgets\");\n };\n\n const handleDelete = async () => {\n if (!config) return;\n await deleteConfiguration({ variables: { id: config.id } });\n navigate(\"/widgets\");\n };\n\n if (loading) {\n return (\n <PageLayout title=\"Widget Details\" description=\"Loading...\">\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !config) {\n return (\n <PageLayout\n title=\"Widget Details\"\n description={`Widget ID: ${id}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(\"/widgets\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Widgets\n </button>\n }\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 {error\n ? \"Failed to load widget details. Please try again later.\"\n : \"Widget configuration not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Widget Details\"\n description={`Widget ID: ${id}`}\n purpose=\"Edit what this widget shows and how it's labelled, then save it back to your workspace — or remove it if you no longer need it on your dashboard.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigate(\"/widgets\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Widgets\n </button>\n }\n >\n <div className=\"mx-auto max-w-2xl\">\n <NextSteps\n storageKey=\"timecampus-widget-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"save\",\n label: \"Save your changes\",\n description: \"Update this widget's name, metric or description.\",\n onClick: () => void handleSave(),\n },\n {\n id: \"browse\",\n label: \"Browse other widgets\",\n description: \"Manage the rest of your dashboard widgets.\",\n onClick: () => navigate(\"/widgets\"),\n },\n ]}\n />\n <GlassCard treatment=\"glass\" glow className=\"p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex h-16 w-16 items-center justify-center rounded-xl bg-action-primary-bg/10\">\n <LayoutGrid className=\"h-8 w-8 text-primary\" />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {name || \"Untitled widget\"}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n This widget is saved to your workspace and can be shown on your\n dashboard.\n </p>\n </div>\n </div>\n\n {saveError && (\n <div className=\"mt-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save widget. Please try again.\n </div>\n )}\n\n <div className=\"mt-6 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 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 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\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={deleting}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={saving || !name.trim()}\n className=\"inline-flex items-center gap-1.5 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 <Save className=\"h-4 w-4\" />\n {saving ? \"Saving...\" : \"Save Changes\"}\n </button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|