@burdenoff/microfe-timecampus 2026.825.2 → 2026.828.1

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.
@@ -2,311 +2,339 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
2
  import { formatDuration as t, formatTime as n } from "../../utils/helpers.js";
3
3
  import { StatCard as r } from "../../components/StatCard.js";
4
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, GripVertical as g, MessageSquare as _, Users as v, Zap as y } from "lucide-react";
10
- import { jsx as b, jsxs as x } from "react/jsx-runtime";
11
- import { GlassCard as S } from "@burdenoff/fe-libs/ui";
12
- import { DndContext as C, DragOverlay as w, KeyboardSensor as T, PointerSensor as E, useDraggable as D, useDroppable as O, useSensor as k, useSensors as A } from "@dnd-kit/core";
5
+ import { useGetTimeCampusTimelineQuery as a, useListProjectsQuery as o, useRecategorizeTimeCampusCaptureEventMutation as s } from "../../../generated/wspace-operations.js";
6
+ import { PageLayout as c } from "../../components/PageLayout.js";
7
+ import { EmptyState as l } from "../../components/EmptyState.js";
8
+ import { useMemo as u, useState as d } from "react";
9
+ import { Activity as f, Brain as p, Calendar as m, Coffee as h, FolderOpen as g, Github as _, GripVertical as v, MessageSquare as y, Users as b, Zap as x } from "lucide-react";
10
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
11
+ import { GlassCard as w } from "@burdenoff/fe-libs/ui";
12
+ import { DndContext as T, DragOverlay as E, KeyboardSensor as D, PointerSensor as O, useDraggable as k, useDroppable as A, useSensor as j, useSensors as M } from "@dnd-kit/core";
13
13
  //#region src/timecampus/pages/timeline/TimelinePage.tsx
14
- var j = {
14
+ var N = {
15
15
  DEEP_WORK: {
16
16
  label: "Deep work",
17
- icon: f,
17
+ icon: p,
18
18
  dot: "bg-status-success-bg",
19
19
  chip: "bg-status-success-bg text-status-success-text"
20
20
  },
21
21
  MEETING: {
22
22
  label: "Meeting",
23
- icon: v,
23
+ icon: b,
24
24
  dot: "bg-status-info-bg",
25
25
  chip: "bg-status-info-bg text-status-info-text"
26
26
  },
27
27
  COMMUNICATION: {
28
28
  label: "Communication",
29
- icon: _,
29
+ icon: y,
30
30
  dot: "bg-status-warning-bg",
31
31
  chip: "bg-status-warning-bg text-status-warning-text"
32
32
  },
33
33
  BREAK: {
34
34
  label: "Break",
35
- icon: m,
35
+ icon: h,
36
36
  dot: "bg-bg-sunken",
37
37
  chip: "bg-bg-sunken text-text-secondary"
38
38
  },
39
39
  OTHER: {
40
40
  label: "Other",
41
- icon: d,
41
+ icon: f,
42
42
  dot: "bg-bg-sunken",
43
43
  chip: "bg-bg-sunken text-text-secondary"
44
44
  }
45
- }, M = {
46
- TIME_ENTRY: y,
47
- MEETING: v,
48
- POMODORO: f,
49
- EMAIL: _,
50
- CAPTURE: h
51
- }, N = [
45
+ }, P = {
46
+ TIME_ENTRY: x,
47
+ MEETING: b,
48
+ POMODORO: p,
49
+ EMAIL: y,
50
+ CAPTURE: _
51
+ }, F = [
52
52
  "DEEP_WORK",
53
53
  "MEETING",
54
54
  "COMMUNICATION",
55
55
  "BREAK",
56
56
  "OTHER"
57
- ];
58
- function P({ category: e, label: n, dotClassName: r, totalSeconds: i }) {
59
- let { isOver: a, setNodeRef: o } = O({ id: `category:${e}` });
60
- return /* @__PURE__ */ x("div", {
57
+ ], I = {
58
+ auto: "Auto-categorized",
59
+ user: "You set this",
60
+ channel: "From channel rules"
61
+ };
62
+ function L(e) {
63
+ return e ? I[e.toLowerCase()] ?? e : null;
64
+ }
65
+ function R({ category: e, label: n, dotClassName: r, totalSeconds: i }) {
66
+ let { isOver: a, setNodeRef: o } = A({ id: `category:${e}` });
67
+ return /* @__PURE__ */ C("div", {
61
68
  ref: o,
62
69
  className: `flex items-center gap-2 rounded-lg border px-3 py-2 transition-colors ${a ? "border-action-primary-bg bg-action-primary-bg/10" : "border-border-seam bg-bg-surface"}`,
63
70
  children: [
64
- /* @__PURE__ */ b("span", {
71
+ /* @__PURE__ */ S("span", {
65
72
  className: `inline-block h-2.5 w-2.5 rounded-full ${r}`,
66
73
  "aria-hidden": !0
67
74
  }),
68
- /* @__PURE__ */ b("span", {
75
+ /* @__PURE__ */ S("span", {
69
76
  className: "text-sm font-medium text-text-primary",
70
77
  children: n
71
78
  }),
72
- /* @__PURE__ */ b("span", {
79
+ /* @__PURE__ */ S("span", {
73
80
  className: "text-sm tabular-nums text-text-secondary",
74
81
  children: t(i)
75
82
  }),
76
- a && /* @__PURE__ */ b("span", {
83
+ a && /* @__PURE__ */ S("span", {
77
84
  className: "text-xs font-medium text-primary",
78
85
  children: "Drop here"
79
86
  })
80
87
  ]
81
88
  });
82
89
  }
83
- function F({ segment: e, category: r, recategorizing: i, onRecategorize: a }) {
84
- let o = j[r] ?? j.OTHER, s = M[e.source] ?? d, { attributes: c, listeners: l, setNodeRef: u, isDragging: f } = D({
90
+ function z({ segment: e, category: r, projectName: i, recategorizing: a, onRecategorize: o }) {
91
+ let s = N[r] ?? N.OTHER, c = P[e.source] ?? f, l = L(e.categorySource), { attributes: u, listeners: d, setNodeRef: p, isDragging: m } = k({
85
92
  id: e.id,
86
93
  data: { segment: e },
87
94
  disabled: !e.isRecategorizable
88
95
  });
89
- return /* @__PURE__ */ x("li", {
90
- ref: u,
91
- className: `flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50 ${f ? "opacity-40" : ""}`,
96
+ return /* @__PURE__ */ C("li", {
97
+ ref: p,
98
+ className: `flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50 ${m ? "opacity-40" : ""}`,
92
99
  children: [
93
- /* @__PURE__ */ x("div", {
100
+ /* @__PURE__ */ C("div", {
94
101
  className: "flex w-16 shrink-0 flex-col items-end pt-0.5 text-xs tabular-nums text-text-secondary",
95
- children: [/* @__PURE__ */ b("span", { children: n(e.startTime) }), /* @__PURE__ */ b("span", {
102
+ children: [/* @__PURE__ */ S("span", { children: n(e.startTime) }), /* @__PURE__ */ S("span", {
96
103
  className: "text-text-tertiary",
97
104
  children: t(e.durationSeconds)
98
105
  })]
99
106
  }),
100
- e.isRecategorizable && /* @__PURE__ */ b("button", {
107
+ e.isRecategorizable && /* @__PURE__ */ S("button", {
101
108
  type: "button",
102
- "aria-label": `Drag to recategorize ${e.title || o.label}`,
109
+ "aria-label": `Drag to recategorize ${e.title || s.label}`,
103
110
  className: "mt-1.5 shrink-0 cursor-grab touch-none text-text-tertiary hover:text-text-secondary active:cursor-grabbing",
104
- ...c,
105
- ...l,
106
- children: /* @__PURE__ */ b(g, { className: "h-4 w-4" })
111
+ ...u,
112
+ ...d,
113
+ children: /* @__PURE__ */ S(v, { className: "h-4 w-4" })
107
114
  }),
108
- /* @__PURE__ */ b("div", {
109
- className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${o.chip}`,
110
- children: /* @__PURE__ */ b(s, { className: "h-4 w-4" })
115
+ /* @__PURE__ */ S("div", {
116
+ className: `mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${s.chip}`,
117
+ children: /* @__PURE__ */ S(c, { className: "h-4 w-4" })
111
118
  }),
112
- /* @__PURE__ */ x("div", {
119
+ /* @__PURE__ */ C("div", {
113
120
  className: "min-w-0 flex-1",
114
- children: [/* @__PURE__ */ b("p", {
121
+ children: [/* @__PURE__ */ S("p", {
115
122
  className: "truncate text-sm font-medium text-text-primary",
116
- children: e.title || o.label
117
- }), /* @__PURE__ */ x("div", {
123
+ children: e.title || s.label
124
+ }), /* @__PURE__ */ C("div", {
118
125
  className: "mt-1 flex flex-wrap items-center gap-2",
119
126
  children: [
120
- /* @__PURE__ */ b("span", {
121
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${o.chip}`,
122
- children: o.label
127
+ /* @__PURE__ */ S("span", {
128
+ className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${s.chip}`,
129
+ children: s.label
130
+ }),
131
+ i !== void 0 && /* @__PURE__ */ C("span", {
132
+ className: "inline-flex max-w-[12rem] items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
133
+ children: [/* @__PURE__ */ S(g, { className: "h-3 w-3 shrink-0" }), /* @__PURE__ */ S("span", {
134
+ className: "truncate",
135
+ children: i
136
+ })]
123
137
  }),
124
- e.app && /* @__PURE__ */ b("span", {
138
+ e.app && /* @__PURE__ */ S("span", {
125
139
  className: "text-xs text-text-secondary",
126
140
  children: e.app
127
141
  }),
128
- e.isRecategorizable && /* @__PURE__ */ b("span", {
142
+ l && /* @__PURE__ */ S("span", {
129
143
  className: "text-xs text-text-tertiary",
130
- children: "via capture"
144
+ children: l
131
145
  })
132
146
  ]
133
147
  })]
134
148
  }),
135
- e.isRecategorizable && /* @__PURE__ */ b("select", {
149
+ e.isRecategorizable && /* @__PURE__ */ S("select", {
136
150
  "aria-label": "Recategorize activity",
137
- disabled: i,
151
+ disabled: a,
138
152
  value: r,
139
- onChange: (t) => a(e, t.target.value),
153
+ onChange: (t) => o(e, t.target.value),
140
154
  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",
141
- children: N.map((e) => /* @__PURE__ */ b("option", {
155
+ children: F.map((e) => /* @__PURE__ */ S("option", {
142
156
  value: e,
143
- children: j[e].label
157
+ children: N[e].label
144
158
  }, e))
145
159
  })
146
160
  ]
147
161
  });
148
162
  }
149
- function I(e) {
163
+ function B(e) {
150
164
  let t = e.getTimezoneOffset();
151
165
  return (/* @__PURE__ */ new Date(e.getTime() - t * 60 * 1e3)).toISOString().slice(0, 10);
152
166
  }
153
- function L(e) {
167
+ function V(e) {
154
168
  let t = /* @__PURE__ */ new Date(`${e}T00:00:00`), n = /* @__PURE__ */ new Date(`${e}T23:59:59.999`);
155
169
  return {
156
170
  from: t.toISOString(),
157
171
  to: n.toISOString()
158
172
  };
159
173
  }
160
- function R() {
161
- let { workspaceId: n = "" } = e(), [m, h] = u(() => I(/* @__PURE__ */ new Date())), [_, v] = u(null), { from: D, to: O } = l(() => L(m), [m]), { data: M, loading: R, error: z, refetch: B } = a({
174
+ function H() {
175
+ let { workspaceId: h = "" } = e(), [g, _] = d(() => B(/* @__PURE__ */ new Date())), [y, b] = d(null), { from: k, to: A } = u(() => V(g), [g]), { data: P, loading: I, error: L, refetch: H } = a({
162
176
  variables: {
163
- from: D,
164
- to: O
177
+ from: k,
178
+ to: A
165
179
  },
166
- skip: !n,
180
+ skip: !h,
167
181
  fetchPolicy: "cache-and-network"
168
- }), [V, { loading: H }] = o(), U = A(k(E, { activationConstraint: { distance: 6 } }), k(T)), W = M?.timeCampusTimeline, G = W?.segments ?? [], K = W?.summary, q = l(() => {
182
+ }), { data: U } = o({
183
+ variables: { pagination: { limit: 200 } },
184
+ skip: !h
185
+ }), [W, { loading: G }] = s(), K = M(j(O, { activationConstraint: { distance: 6 } }), j(D)), q = P?.timeCampusTimeline, J = q?.segments ?? [], Y = q?.summary, X = u(() => {
186
+ let e = /* @__PURE__ */ new Map();
187
+ for (let t of U?.projects?.items ?? []) e.set(t.id, t.name);
188
+ return e;
189
+ }, [U?.projects?.items]), Z = u(() => {
169
190
  let e = /* @__PURE__ */ new Map();
170
- for (let t of K?.byCategory ?? []) e.set(t.category, {
191
+ for (let t of Y?.byCategory ?? []) e.set(t.category, {
171
192
  totalSeconds: t.totalSeconds,
172
193
  segmentCount: t.segmentCount
173
194
  });
174
195
  return e;
175
- }, [K]), J = async (e, t) => {
176
- !e.isRecategorizable || e.category === t || (await V({ variables: {
196
+ }, [Y]), Q = async (e, t) => {
197
+ !e.isRecategorizable || e.category === t || (await W({ variables: {
177
198
  id: e.sourceId,
178
199
  category: t
179
- } }), await B());
180
- }, Y = (e) => {
200
+ } }), await H());
201
+ }, ee = (e) => {
181
202
  let t = e.active.data.current?.segment;
182
- v(t ?? null);
183
- }, X = (e) => {
203
+ b(t ?? null);
204
+ }, te = (e) => {
184
205
  let t = e.active.data.current?.segment;
185
- v(null);
206
+ b(null);
186
207
  let n = e.over?.id;
187
208
  if (!t || typeof n != "string") return;
188
209
  let r = n.replace(/^category:/, "");
189
- N.includes(r) && J(t, r);
190
- }, Z = K && K.focusRatio != null ? Math.round(K.focusRatio * 100) : 0;
191
- return /* @__PURE__ */ b(s, {
210
+ F.includes(r) && Q(t, r);
211
+ }, ne = Y && Y.focusRatio != null ? Math.round(Y.focusRatio * 100) : 0, $ = Y?.firstActivityAt && Y?.lastActivityAt ? `${n(Y.firstActivityAt)} – ${n(Y.lastActivityAt)}` : null;
212
+ return /* @__PURE__ */ S(c, {
192
213
  title: "Timeline",
193
214
  description: "Your day, assembled automatically",
194
215
  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.",
195
- actions: /* @__PURE__ */ x("label", {
196
- className: "flex items-center gap-2 text-sm text-text-secondary",
197
- children: [/* @__PURE__ */ b(p, { className: "h-4 w-4" }), /* @__PURE__ */ b("input", {
198
- type: "date",
199
- value: m,
200
- max: I(/* @__PURE__ */ new Date()),
201
- onChange: (e) => h(e.target.value),
202
- 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"
216
+ actions: /* @__PURE__ */ C("div", {
217
+ className: "flex flex-wrap items-center gap-3",
218
+ children: [$ && /* @__PURE__ */ C("span", {
219
+ className: "inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-1 text-xs font-medium tabular-nums text-text-secondary",
220
+ children: [/* @__PURE__ */ S(f, { className: "h-3.5 w-3.5" }), $]
221
+ }), /* @__PURE__ */ C("label", {
222
+ className: "flex items-center gap-2 text-sm text-text-secondary",
223
+ children: [/* @__PURE__ */ S(m, { className: "h-4 w-4" }), /* @__PURE__ */ S("input", {
224
+ type: "date",
225
+ value: g,
226
+ max: B(/* @__PURE__ */ new Date()),
227
+ onChange: (e) => _(e.target.value),
228
+ 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"
229
+ })]
203
230
  })]
204
231
  }),
205
- children: R && !W ? /* @__PURE__ */ x("div", {
232
+ children: I && !q ? /* @__PURE__ */ C("div", {
206
233
  className: "space-y-6",
207
- children: [/* @__PURE__ */ b(i, {
234
+ children: [/* @__PURE__ */ S(i, {
208
235
  variant: "card",
209
236
  count: 4
210
- }), /* @__PURE__ */ b(i, {
237
+ }), /* @__PURE__ */ S(i, {
211
238
  variant: "list",
212
239
  count: 6
213
240
  })]
214
- }) : z ? /* @__PURE__ */ b("div", {
241
+ }) : L ? /* @__PURE__ */ S("div", {
215
242
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
216
243
  children: "Failed to load your timeline. Please try again later."
217
- }) : /* @__PURE__ */ x(C, {
218
- sensors: U,
219
- onDragStart: Y,
220
- onDragEnd: X,
221
- children: [/* @__PURE__ */ x("div", {
244
+ }) : /* @__PURE__ */ C(T, {
245
+ sensors: K,
246
+ onDragStart: ee,
247
+ onDragEnd: te,
248
+ children: [/* @__PURE__ */ C("div", {
222
249
  className: "space-y-6",
223
250
  children: [
224
- /* @__PURE__ */ x("div", {
251
+ /* @__PURE__ */ C("div", {
225
252
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
226
253
  children: [
227
- /* @__PURE__ */ b(r, {
254
+ /* @__PURE__ */ S(r, {
228
255
  featured: !0,
229
256
  title: "Tracked time",
230
- value: t(K?.totalTrackedSeconds ?? 0),
231
- icon: /* @__PURE__ */ b(d, { className: "h-5 w-5" }),
257
+ value: t(Y?.totalTrackedSeconds ?? 0),
258
+ icon: /* @__PURE__ */ S(f, { className: "h-5 w-5" }),
232
259
  description: "Total activity across the day"
233
260
  }),
234
- /* @__PURE__ */ b(r, {
261
+ /* @__PURE__ */ S(r, {
235
262
  title: "Focus ratio",
236
- value: `${Z}%`,
237
- icon: /* @__PURE__ */ b(f, { className: "h-5 w-5" }),
263
+ value: `${ne}%`,
264
+ icon: /* @__PURE__ */ S(p, { className: "h-5 w-5" }),
238
265
  description: "Share of time in deep work"
239
266
  }),
240
- /* @__PURE__ */ b(r, {
267
+ /* @__PURE__ */ S(r, {
241
268
  title: "Context switches",
242
- value: K?.contextSwitches ?? 0,
243
- icon: /* @__PURE__ */ b(y, { className: "h-5 w-5" }),
269
+ value: Y?.contextSwitches ?? 0,
270
+ icon: /* @__PURE__ */ S(x, { className: "h-5 w-5" }),
244
271
  description: "Category changes through the day"
245
272
  }),
246
- /* @__PURE__ */ b(r, {
273
+ /* @__PURE__ */ S(r, {
247
274
  title: "Longest focus block",
248
- value: t(K?.longestFocusBlockSeconds ?? 0),
249
- icon: /* @__PURE__ */ b(f, { className: "h-5 w-5" }),
275
+ value: t(Y?.longestFocusBlockSeconds ?? 0),
276
+ icon: /* @__PURE__ */ S(p, { className: "h-5 w-5" }),
250
277
  description: "Your deepest uninterrupted stretch"
251
278
  })
252
279
  ]
253
280
  }),
254
- G.length > 0 && /* @__PURE__ */ x(S, {
281
+ J.length > 0 && /* @__PURE__ */ C(w, {
255
282
  treatment: "glass",
256
283
  className: "p-4 sm:p-6",
257
284
  children: [
258
- /* @__PURE__ */ b("h2", {
285
+ /* @__PURE__ */ S("h2", {
259
286
  className: "mb-1 text-sm font-semibold text-text-primary",
260
287
  children: "Where the time went"
261
288
  }),
262
- /* @__PURE__ */ b("p", {
289
+ /* @__PURE__ */ S("p", {
263
290
  className: "mb-4 text-xs text-text-secondary",
264
291
  children: "Drag a segment from the timeline below onto a category to recategorize it — or use its dropdown."
265
292
  }),
266
- /* @__PURE__ */ b("div", {
293
+ /* @__PURE__ */ S("div", {
267
294
  className: "flex flex-wrap gap-4",
268
- children: N.filter((e) => q.has(e)).map((e) => {
269
- let t = j[e], n = q.get(e);
270
- return /* @__PURE__ */ b(P, {
295
+ children: F.map((e) => {
296
+ let t = N[e], n = Z.get(e);
297
+ return /* @__PURE__ */ S(R, {
271
298
  category: e,
272
299
  label: t.label,
273
300
  dotClassName: t.dot,
274
- totalSeconds: n.totalSeconds
301
+ totalSeconds: n?.totalSeconds ?? 0
275
302
  }, e);
276
303
  })
277
304
  })
278
305
  ]
279
306
  }),
280
- G.length === 0 ? /* @__PURE__ */ b(c, {
307
+ J.length === 0 ? /* @__PURE__ */ S(l, {
281
308
  illustration: "empty-generic",
282
309
  title: "No activity captured for this day",
283
310
  description: "As you track time, hold meetings, run focus sessions, and connect shared integrations, your day assembles here automatically — categorized and in order."
284
- }) : /* @__PURE__ */ b(S, {
311
+ }) : /* @__PURE__ */ S(w, {
285
312
  treatment: "glass",
286
313
  glow: !0,
287
314
  className: "overflow-hidden",
288
- children: /* @__PURE__ */ b("ol", {
315
+ children: /* @__PURE__ */ S("ol", {
289
316
  className: "divide-y divide-border-seam",
290
- children: G.map((e) => /* @__PURE__ */ b(F, {
317
+ children: J.map((e) => /* @__PURE__ */ S(z, {
291
318
  segment: e,
292
319
  category: e.category ?? "OTHER",
293
- recategorizing: H,
294
- onRecategorize: J
320
+ projectName: e.projectId ? X.get(e.projectId) : void 0,
321
+ recategorizing: G,
322
+ onRecategorize: Q
295
323
  }, e.id))
296
324
  })
297
325
  })
298
326
  ]
299
- }), /* @__PURE__ */ b(w, { children: _ && /* @__PURE__ */ x("div", {
327
+ }), /* @__PURE__ */ S(E, { children: y && /* @__PURE__ */ C("div", {
300
328
  className: "flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 shadow-pop",
301
- children: [/* @__PURE__ */ b(g, { className: "h-4 w-4 text-text-tertiary" }), /* @__PURE__ */ b("span", {
329
+ children: [/* @__PURE__ */ S(v, { className: "h-4 w-4 text-text-tertiary" }), /* @__PURE__ */ S("span", {
302
330
  className: "text-sm font-medium text-text-primary",
303
- children: _.title || j[_.category ?? "OTHER"].label
331
+ children: y.title || N[y.category ?? "OTHER"].label
304
332
  })]
305
333
  }) })]
306
334
  })
307
335
  });
308
336
  }
309
337
  //#endregion
310
- export { R as TimelinePage };
338
+ export { H as TimelinePage };
311
339
 
312
340
  //# sourceMappingURL=TimelinePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TimelinePage.js","names":[],"sources":["../../../../src/timecampus/pages/timeline/TimelinePage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n DndContext,\n DragOverlay,\n KeyboardSensor,\n PointerSensor,\n useDraggable,\n useDroppable,\n useSensor,\n useSensors,\n type DragEndEvent,\n type DragStartEvent,\n} from \"@dnd-kit/core\";\nimport {\n Activity,\n Brain,\n Calendar,\n Coffee,\n Github,\n GripVertical,\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\n/** Droppable target: a category chip in the \"Where the time went\" summary. */\nfunction DroppableCategoryChip({\n category,\n label,\n dotClassName,\n totalSeconds,\n}: {\n category: Category;\n label: string;\n dotClassName: string;\n totalSeconds: number;\n}) {\n const { isOver, setNodeRef } = useDroppable({ id: `category:${category}` });\n return (\n <div\n ref={setNodeRef}\n className={`flex items-center gap-2 rounded-lg border px-3 py-2 transition-colors ${\n isOver\n ? \"border-action-primary-bg bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface\"\n }`}\n >\n <span\n className={`inline-block h-2.5 w-2.5 rounded-full ${dotClassName}`}\n aria-hidden\n />\n <span className=\"text-sm font-medium text-text-primary\">{label}</span>\n <span className=\"text-sm tabular-nums text-text-secondary\">\n {formatDuration(totalSeconds)}\n </span>\n {isOver && (\n <span className=\"text-xs font-medium text-primary\">Drop here</span>\n )}\n </div>\n );\n}\n\n/** Draggable row for a single timeline segment, with the existing dropdown kept as an accessible fallback. */\nfunction DraggableSegmentRow({\n segment,\n category,\n recategorizing,\n onRecategorize,\n}: {\n segment: TimelineSegmentFieldsFragment;\n category: Category;\n recategorizing: boolean;\n onRecategorize: (\n segment: TimelineSegmentFieldsFragment,\n category: Category,\n ) => void;\n}) {\n const meta = CATEGORY_META[category] ?? CATEGORY_META.OTHER;\n const SourceIcon = SOURCE_ICON[segment.source] ?? Activity;\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({\n id: segment.id,\n data: { segment },\n disabled: !segment.isRecategorizable,\n });\n\n return (\n <li\n ref={setNodeRef}\n className={`flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50 ${\n isDragging ? \"opacity-40\" : \"\"\n }`}\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 {segment.isRecategorizable && (\n <button\n type=\"button\"\n aria-label={`Drag to recategorize ${segment.title || meta.label}`}\n className=\"mt-1.5 shrink-0 cursor-grab touch-none text-text-tertiary hover:text-text-secondary active:cursor-grabbing\"\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"h-4 w-4\" />\n </button>\n )}\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\">{segment.app}</span>\n )}\n {segment.isRecategorizable && (\n <span className=\"text-xs text-text-tertiary\">via capture</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) => onRecategorize(segment, e.target.value as Category)}\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\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 const [draggingSegment, setDraggingSegment] =\n useState<TimelineSegmentFieldsFragment | null>(null);\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 sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),\n useSensor(KeyboardSensor),\n );\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<\n string,\n { totalSeconds: number; segmentCount: number }\n >();\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 handleDragStart = (event: DragStartEvent) => {\n const segment = event.active.data.current?.segment as\n TimelineSegmentFieldsFragment | undefined;\n setDraggingSegment(segment ?? null);\n };\n\n const handleDragEnd = (event: DragEndEvent) => {\n const segment = event.active.data.current?.segment as\n TimelineSegmentFieldsFragment | undefined;\n setDraggingSegment(null);\n const overId = event.over?.id;\n if (!segment || typeof overId !== \"string\") return;\n const category = overId.replace(/^category:/, \"\") as Category;\n if (!CATEGORY_ORDER.includes(category)) return;\n void handleRecategorize(segment, category);\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 <DndContext\n sensors={sensors}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\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 — also the drop targets for drag-to-recategorize */}\n {segments.length > 0 && (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-6\">\n <h2 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Where the time went\n </h2>\n <p className=\"mb-4 text-xs text-text-secondary\">\n Drag a segment from the timeline below onto a category to\n recategorize it — or use its dropdown.\n </p>\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 <DroppableCategoryChip\n key={category}\n category={category}\n label={meta.label}\n dotClassName={meta.dot}\n totalSeconds={total.totalSeconds}\n />\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 shared integrations, 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 <DraggableSegmentRow\n key={segment.id}\n segment={segment}\n category={(segment.category as Category) ?? \"OTHER\"}\n recategorizing={recategorizing}\n onRecategorize={handleRecategorize}\n />\n ))}\n </ol>\n </GlassCard>\n )}\n </div>\n\n <DragOverlay>\n {draggingSegment && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 shadow-pop\">\n <GripVertical className=\"h-4 w-4 text-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {draggingSegment.title ||\n CATEGORY_META[\n (draggingSegment.category as Category) ?? \"OTHER\"\n ].label}\n </span>\n </div>\n )}\n </DragOverlay>\n </DndContext>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TimelinePage.js","names":[],"sources":["../../../../src/timecampus/pages/timeline/TimelinePage.tsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n DndContext,\n DragOverlay,\n KeyboardSensor,\n PointerSensor,\n useDraggable,\n useDroppable,\n useSensor,\n useSensors,\n type DragEndEvent,\n type DragStartEvent,\n} from \"@dnd-kit/core\";\nimport {\n Activity,\n Brain,\n Calendar,\n Coffee,\n FolderOpen,\n Github,\n GripVertical,\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 useListProjectsQuery,\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\n/**\n * How a segment came by its category. The service writes these lowercase\n * (`timeline.service.ts` → 'auto' | 'user' | 'channel'), so normalise before\n * matching rather than trusting the string's casing.\n */\nconst CATEGORY_SOURCE_LABEL: Record<string, string> = {\n auto: \"Auto-categorized\",\n user: \"You set this\",\n channel: \"From channel rules\",\n};\n\nfunction categorySourceLabel(source: string | null | undefined): string | null {\n if (!source) return null;\n return CATEGORY_SOURCE_LABEL[source.toLowerCase()] ?? source;\n}\n\n/** Droppable target: a category chip in the \"Where the time went\" summary. */\nfunction DroppableCategoryChip({\n category,\n label,\n dotClassName,\n totalSeconds,\n}: {\n category: Category;\n label: string;\n dotClassName: string;\n totalSeconds: number;\n}) {\n const { isOver, setNodeRef } = useDroppable({ id: `category:${category}` });\n return (\n <div\n ref={setNodeRef}\n className={`flex items-center gap-2 rounded-lg border px-3 py-2 transition-colors ${\n isOver\n ? \"border-action-primary-bg bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface\"\n }`}\n >\n <span\n className={`inline-block h-2.5 w-2.5 rounded-full ${dotClassName}`}\n aria-hidden\n />\n <span className=\"text-sm font-medium text-text-primary\">{label}</span>\n <span className=\"text-sm tabular-nums text-text-secondary\">\n {formatDuration(totalSeconds)}\n </span>\n {isOver && (\n <span className=\"text-xs font-medium text-primary\">Drop here</span>\n )}\n </div>\n );\n}\n\n/** Draggable row for a single timeline segment, with the existing dropdown kept as an accessible fallback. */\nfunction DraggableSegmentRow({\n segment,\n category,\n projectName,\n recategorizing,\n onRecategorize,\n}: {\n segment: TimelineSegmentFieldsFragment;\n category: Category;\n /** Resolved name for `segment.projectId`; undefined when it carries none. */\n projectName: string | undefined;\n recategorizing: boolean;\n onRecategorize: (\n segment: TimelineSegmentFieldsFragment,\n category: Category,\n ) => void;\n}) {\n const meta = CATEGORY_META[category] ?? CATEGORY_META.OTHER;\n const SourceIcon = SOURCE_ICON[segment.source] ?? Activity;\n const provenance = categorySourceLabel(segment.categorySource);\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({\n id: segment.id,\n data: { segment },\n disabled: !segment.isRecategorizable,\n });\n\n return (\n <li\n ref={setNodeRef}\n className={`flex items-start gap-3 p-4 transition-colors hover:bg-bg-sunken/50 ${\n isDragging ? \"opacity-40\" : \"\"\n }`}\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 {segment.isRecategorizable && (\n <button\n type=\"button\"\n aria-label={`Drag to recategorize ${segment.title || meta.label}`}\n className=\"mt-1.5 shrink-0 cursor-grab touch-none text-text-tertiary hover:text-text-secondary active:cursor-grabbing\"\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"h-4 w-4\" />\n </button>\n )}\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 {projectName !== undefined && (\n <span className=\"inline-flex max-w-[12rem] items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n <FolderOpen className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{projectName}</span>\n </span>\n )}\n {segment.app && (\n <span className=\"text-xs text-text-secondary\">{segment.app}</span>\n )}\n {provenance && (\n <span className=\"text-xs text-text-tertiary\">{provenance}</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) => onRecategorize(segment, e.target.value as Category)}\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\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 const [draggingSegment, setDraggingSegment] =\n useState<TimelineSegmentFieldsFragment | null>(null);\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 // Segments carry a `projectId` only; the workspace's projects supply the\n // name to show next to them. A segment whose id isn't in this list simply\n // keeps no project chip — it is never labelled from a guess.\n const { data: projectsData } = useListProjectsQuery({\n variables: { pagination: { limit: 200 } },\n skip: !workspaceId,\n });\n\n const [recategorize, { loading: recategorizing }] =\n useRecategorizeTimeCampusCaptureEventMutation();\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),\n useSensor(KeyboardSensor),\n );\n\n const timeline = data?.timeCampusTimeline;\n const segments = timeline?.segments ?? [];\n const summary = timeline?.summary;\n\n const projectNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const project of projectsData?.projects?.items ?? []) {\n map.set(project.id, project.name);\n }\n return map;\n }, [projectsData?.projects?.items]);\n\n const totalsByCategory = useMemo(() => {\n const map = new Map<\n string,\n { totalSeconds: number; segmentCount: number }\n >();\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 handleDragStart = (event: DragStartEvent) => {\n const segment = event.active.data.current?.segment as\n TimelineSegmentFieldsFragment | undefined;\n setDraggingSegment(segment ?? null);\n };\n\n const handleDragEnd = (event: DragEndEvent) => {\n const segment = event.active.data.current?.segment as\n TimelineSegmentFieldsFragment | undefined;\n setDraggingSegment(null);\n const overId = event.over?.id;\n if (!segment || typeof overId !== \"string\") return;\n const category = overId.replace(/^category:/, \"\") as Category;\n if (!CATEGORY_ORDER.includes(category)) return;\n void handleRecategorize(segment, category);\n };\n\n const focusPct =\n summary && summary.focusRatio != null\n ? Math.round(summary.focusRatio * 100)\n : 0;\n\n // The real span of the day, straight off the summary — not inferred from the\n // segment list, which may be paginated or filtered differently.\n const daySpan =\n summary?.firstActivityAt && summary?.lastActivityAt\n ? `${formatTime(summary.firstActivityAt)} – ${formatTime(summary.lastActivityAt)}`\n : null;\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 <div className=\"flex flex-wrap items-center gap-3\">\n {daySpan && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-1 text-xs font-medium tabular-nums text-text-secondary\">\n <Activity className=\"h-3.5 w-3.5\" />\n {daySpan}\n </span>\n )}\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 </div>\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 <DndContext\n sensors={sensors}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\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 — also the drop targets for drag-to-recategorize */}\n {segments.length > 0 && (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-6\">\n <h2 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Where the time went\n </h2>\n <p className=\"mb-4 text-xs text-text-secondary\">\n Drag a segment from the timeline below onto a category to\n recategorize it — or use its dropdown.\n </p>\n <div className=\"flex flex-wrap gap-4\">\n {CATEGORY_ORDER.map((category) => {\n const meta = CATEGORY_META[category];\n const total = totalsByCategory.get(category);\n return (\n <DroppableCategoryChip\n key={category}\n category={category}\n label={meta.label}\n dotClassName={meta.dot}\n totalSeconds={total?.totalSeconds ?? 0}\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 shared integrations, 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 <DraggableSegmentRow\n key={segment.id}\n segment={segment}\n category={(segment.category as Category) ?? \"OTHER\"}\n projectName={\n segment.projectId\n ? projectNameById.get(segment.projectId)\n : undefined\n }\n recategorizing={recategorizing}\n onRecategorize={handleRecategorize}\n />\n ))}\n </ol>\n </GlassCard>\n )}\n </div>\n\n <DragOverlay>\n {draggingSegment && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2 shadow-pop\">\n <GripVertical className=\"h-4 w-4 text-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {draggingSegment.title ||\n CATEGORY_META[\n (draggingSegment.category as Category) ?? \"OTHER\"\n ].label}\n </span>\n </div>\n )}\n </DragOverlay>\n </DndContext>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-timecampus",
3
- "version": "2026.825.2",
3
+ "version": "2026.828.1",
4
4
  "description": "Burdenoff TimeCampus Microfrontend - AI-powered time management and productivity platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -125,7 +125,7 @@
125
125
  "@hono/node-server": ">=1.19.15",
126
126
  "ip-address": ">=10.3.1",
127
127
  "dompurify": ">=3.4.13",
128
- "react-router": ">=7.18.2 <9.0.0",
128
+ "react-router": ">=8.3.0 <9.0.0",
129
129
  "uuid": ">=11.1.1",
130
130
  "xlsx": ">=0.19.3",
131
131
  "vitest": ">=3.2.6 <4.0.0",