@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,78 +2,84 @@ import { useTimeCampus as e, useTimeCampusNavigate as t } from "../../providers/
2
2
  import { formatHours 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 { useGetFocusScoreSummaryQuery as a, useGetTimeEntryStatsQuery as o, useGetWorkPatternSummaryQuery as s } from "../../../generated/wspace-operations.js";
6
- import { PageLayout as c } from "../../components/PageLayout.js";
7
- import { StatusBadge as l } from "../../components/StatusBadge.js";
8
- import { ChartCard as u } from "../../components/ChartCard.js";
9
- import { BarsChart as d } from "../../components/charts/BarsChart.js";
10
- import { DonutChart as f } from "../../components/charts/DonutChart.js";
11
- import { RadialGauge as p } from "../../components/charts/RadialGauge.js";
5
+ import { useGetFocusScoreSummaryQuery as a, useGetTimeEntryStatsQuery as o, useGetWorkPatternSummaryQuery as s, useListProjectsQuery as c } from "../../../generated/wspace-operations.js";
6
+ import { PageLayout as l } from "../../components/PageLayout.js";
7
+ import { StatusBadge as u } from "../../components/StatusBadge.js";
8
+ import { ChartCard as d } from "../../components/ChartCard.js";
9
+ import { BarsChart as f } from "../../components/charts/BarsChart.js";
10
+ import { DonutChart as p } from "../../components/charts/DonutChart.js";
11
+ import { RadialGauge as m } from "../../components/charts/RadialGauge.js";
12
12
  import "../../components/index.js";
13
- import { WorkPatternInsightType as m } from "../../../generated/wspace-types.js";
14
- import { Brain as h, CalendarRange as g, Clock as _, DollarSign as v, Gauge as y, Lightbulb as b, PieChart as x, TrendingUp as S } from "lucide-react";
15
- import { jsx as C, jsxs as w } from "react/jsx-runtime";
16
- import { NextSteps as T } from "@burdenoff/fe-libs/ui";
13
+ import { WorkPatternInsightType as h } from "../../../generated/wspace-types.js";
14
+ import { Brain as g, CalendarRange as _, Clock as v, DollarSign as y, FolderOpen as b, Gauge as x, Lightbulb as S, PieChart as C, TrendingUp as w } from "lucide-react";
15
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
16
+ import { NextSteps as D } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/timecampus/pages/dashboard/AnalyticsPage.tsx
18
- var E = {
19
- [m.Positive]: {
18
+ var O = {
19
+ [h.Positive]: {
20
20
  variant: "success",
21
21
  label: "Positive"
22
22
  },
23
- [m.Negative]: {
23
+ [h.Negative]: {
24
24
  variant: "error",
25
25
  label: "Needs work"
26
26
  },
27
- [m.Warning]: {
27
+ [h.Warning]: {
28
28
  variant: "warning",
29
29
  label: "Warning"
30
30
  },
31
- [m.Recommendation]: {
31
+ [h.Recommendation]: {
32
32
  variant: "info",
33
33
  label: "Tip"
34
34
  },
35
- [m.Neutral]: {
35
+ [h.Neutral]: {
36
36
  variant: "muted",
37
37
  label: "Insight"
38
38
  }
39
39
  };
40
- function D() {
41
- let { workspaceId: m = "" } = e(), D = t(), { data: O, loading: k, error: A } = o({ skip: !m }), { data: j, loading: M } = a({ skip: !m }), { data: N, loading: P } = s({ skip: !m }), F = O?.timeEntryStats, I = j?.focusScoreSummary, L = N?.workPatternSummary;
42
- if (k || M || P) return /* @__PURE__ */ C(c, {
40
+ function k() {
41
+ let { workspaceId: h = "" } = e(), k = t(), { data: A, loading: j, error: M } = o({ skip: !h }), { data: N, loading: P } = a({ skip: !h }), { data: F, loading: I } = s({ skip: !h }), { data: L, loading: R } = c({
42
+ variables: { pagination: { limit: 200 } },
43
+ skip: !h
44
+ }), z = A?.timeEntryStats, B = N?.focusScoreSummary, V = F?.workPatternSummary;
45
+ if (j || P || I || R) return /* @__PURE__ */ T(l, {
43
46
  title: "Analytics",
44
47
  description: "Insights into your time tracking and productivity",
45
- children: /* @__PURE__ */ C(i, {
48
+ children: /* @__PURE__ */ T(i, {
46
49
  variant: "card",
47
50
  count: 4
48
51
  })
49
52
  });
50
- if (A) return /* @__PURE__ */ C(c, {
53
+ if (M) return /* @__PURE__ */ T(l, {
51
54
  title: "Analytics",
52
55
  description: "Insights into your time tracking and productivity",
53
- children: /* @__PURE__ */ C("div", {
56
+ children: /* @__PURE__ */ T("div", {
54
57
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
55
58
  children: "Failed to load analytics. Please try again later."
56
59
  })
57
60
  });
58
- let R = F ? Math.max(0, Math.round(F.billableHours)) : 0, z = F ? Math.max(0, Math.round(F.totalHours - F.billableHours)) : 0, B = F && F.totalHours > 0 ? Math.round(F.billableHours / F.totalHours * 100) : 0, V = [{
61
+ let H = z ? Math.max(0, Math.round(z.billableHours)) : 0, U = z ? Math.max(0, Math.round(z.totalHours - z.billableHours)) : 0, W = z && z.totalHours > 0 ? Math.round(z.billableHours / z.totalHours * 100) : 0, G = [{
59
62
  name: "Billable",
60
- value: R
63
+ value: H
61
64
  }, {
62
65
  name: "Non-billable",
63
- value: z
64
- }], H = L ? [{
66
+ value: U
67
+ }], K = V ? [{
65
68
  label: "Focus",
66
- hours: Math.max(0, L.averageFocusHours)
69
+ hours: Math.max(0, V.averageFocusHours)
67
70
  }, {
68
71
  label: "Meetings",
69
- hours: Math.max(0, L.averageMeetingHours)
70
- }] : [], U = I?.trend ? I.trend.charAt(0).toUpperCase() + I.trend.slice(1) : void 0;
71
- return /* @__PURE__ */ w(c, {
72
+ hours: Math.max(0, V.averageMeetingHours)
73
+ }] : [], q = B?.trend ? B.trend.charAt(0).toUpperCase() + B.trend.slice(1) : void 0, J = (L?.projects?.items ?? []).filter((e) => (e.trackedHours ?? 0) > 0), Y = [...J].sort((e, t) => (t.trackedHours ?? 0) - (e.trackedHours ?? 0)).slice(0, 10).map((e) => ({
74
+ project: e.name,
75
+ hours: Math.round((e.trackedHours ?? 0) * 100) / 100
76
+ })), X = J.length;
77
+ return /* @__PURE__ */ E(l, {
72
78
  title: "Analytics",
73
79
  description: "Insights into your time tracking and productivity",
74
80
  purpose: "Turn the raw hours you track into answers — spot whether your focus is improving, where billable time is going, and which habits to change to get more done with less burnout.",
75
81
  children: [
76
- /* @__PURE__ */ C(T, {
82
+ /* @__PURE__ */ T(D, {
77
83
  storageKey: "timecampus-analytics",
78
84
  className: "mb-6",
79
85
  steps: [
@@ -81,149 +87,169 @@ function D() {
81
87
  id: "reports",
82
88
  label: "Build a detailed report",
83
89
  description: "Slice time by project, client or person.",
84
- onClick: () => D("/reports")
90
+ onClick: () => k("/reports")
85
91
  },
86
92
  {
87
93
  id: "focus",
88
94
  label: "Dig into your focus score",
89
95
  description: "Understand what's helping or hurting deep work.",
90
- onClick: () => D("/focus-score")
96
+ onClick: () => k("/focus-score")
91
97
  },
92
98
  {
93
99
  id: "patterns",
94
100
  label: "Review your work patterns",
95
101
  description: "See how your week actually breaks down.",
96
- onClick: () => D("/work-patterns")
102
+ onClick: () => k("/work-patterns")
97
103
  }
98
104
  ]
99
105
  }),
100
- /* @__PURE__ */ w("div", {
106
+ /* @__PURE__ */ E("div", {
101
107
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
102
108
  children: [
103
- /* @__PURE__ */ C(r, {
109
+ /* @__PURE__ */ T(r, {
104
110
  title: "Total Hours",
105
- value: F ? n(F.totalHours) : "--",
106
- icon: /* @__PURE__ */ C(_, { className: "h-5 w-5" }),
107
- description: F ? `${F.totalEntries} entries` : void 0
111
+ value: z ? n(z.totalHours) : "--",
112
+ icon: /* @__PURE__ */ T(v, { className: "h-5 w-5" }),
113
+ description: z ? `${z.totalEntries} entries` : void 0
108
114
  }),
109
- /* @__PURE__ */ C(r, {
115
+ /* @__PURE__ */ T(r, {
110
116
  title: "Billable Hours",
111
- value: F ? n(F.billableHours) : "--",
112
- icon: /* @__PURE__ */ C(v, { className: "h-5 w-5" }),
113
- description: F ? `${B}% of tracked time` : void 0,
117
+ value: z ? n(z.billableHours) : "--",
118
+ icon: /* @__PURE__ */ T(y, { className: "h-5 w-5" }),
119
+ description: z ? `${W}% of tracked time` : void 0,
114
120
  iconBgColor: "bg-status-success-bg text-status-success-text"
115
121
  }),
116
- /* @__PURE__ */ C(r, {
122
+ /* @__PURE__ */ T(r, {
117
123
  featured: !0,
118
124
  title: "Avg Focus Score",
119
- value: I ? `${Math.round(I.averageScore)}` : "--",
120
- icon: /* @__PURE__ */ C(h, { className: "h-5 w-5" }),
121
- description: U ? `${U} trend` : void 0,
125
+ value: B ? `${Math.round(B.averageScore)}` : "--",
126
+ icon: /* @__PURE__ */ T(g, { className: "h-5 w-5" }),
127
+ description: q ? `${q} trend` : void 0,
122
128
  iconBgColor: "bg-status-info-bg text-status-info-text"
123
129
  }),
124
- /* @__PURE__ */ C(r, {
130
+ /* @__PURE__ */ T(r, {
125
131
  title: "Avg Productivity",
126
- value: L ? `${Math.round(L.averageProductivity)}%` : "--",
127
- icon: /* @__PURE__ */ C(S, { className: "h-5 w-5" }),
128
- description: F && F.activeTimers > 0 ? `${F.activeTimers} timer(s) running` : void 0
132
+ value: V ? `${Math.round(V.averageProductivity)}%` : "--",
133
+ icon: /* @__PURE__ */ T(w, { className: "h-5 w-5" }),
134
+ description: z && z.activeTimers > 0 ? `${z.activeTimers} timer(s) running` : void 0
129
135
  })
130
136
  ]
131
137
  }),
132
- /* @__PURE__ */ w("div", {
138
+ /* @__PURE__ */ E("div", {
133
139
  className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2",
134
- children: [/* @__PURE__ */ C(u, {
140
+ children: [/* @__PURE__ */ T(d, {
135
141
  title: "Focus Score",
136
142
  description: "Your average deep-work score across the tracked window",
137
- actions: /* @__PURE__ */ C(y, { className: "h-4 w-4 text-text-secondary" }),
138
- children: I ? /* @__PURE__ */ w("div", {
143
+ actions: /* @__PURE__ */ T(x, { className: "h-4 w-4 text-text-secondary" }),
144
+ children: B ? /* @__PURE__ */ E("div", {
139
145
  className: "space-y-4",
140
146
  children: [
141
- /* @__PURE__ */ C(p, {
142
- value: I.averageScore,
147
+ /* @__PURE__ */ T(m, {
148
+ value: B.averageScore,
143
149
  min: 0,
144
150
  max: 100,
145
151
  label: "Avg score",
146
- sublabel: U ? `${U} trend` : void 0,
152
+ sublabel: q ? `${q} trend` : void 0,
147
153
  thresholds: {
148
154
  warning: 75,
149
155
  danger: 50
150
156
  },
151
157
  height: 196
152
158
  }),
153
- /* @__PURE__ */ w("div", {
159
+ /* @__PURE__ */ E("div", {
154
160
  className: "grid grid-cols-2 gap-3",
155
- children: [/* @__PURE__ */ w("div", {
161
+ children: [/* @__PURE__ */ E("div", {
156
162
  className: "rounded-lg border border-border-seam bg-bg-sunken/40 p-3",
157
- children: [/* @__PURE__ */ C("p", {
163
+ children: [/* @__PURE__ */ T("p", {
158
164
  className: "text-xs text-text-secondary",
159
165
  children: "Lowest"
160
- }), /* @__PURE__ */ C("p", {
166
+ }), /* @__PURE__ */ T("p", {
161
167
  className: "mt-0.5 text-lg font-semibold tabular-nums text-text-primary",
162
- children: Math.round(I.lowestScore)
168
+ children: Math.round(B.lowestScore)
163
169
  })]
164
- }), /* @__PURE__ */ w("div", {
170
+ }), /* @__PURE__ */ E("div", {
165
171
  className: "rounded-lg border border-border-seam bg-bg-sunken/40 p-3",
166
- children: [/* @__PURE__ */ C("p", {
172
+ children: [/* @__PURE__ */ T("p", {
167
173
  className: "text-xs text-text-secondary",
168
174
  children: "Highest"
169
- }), /* @__PURE__ */ C("p", {
175
+ }), /* @__PURE__ */ T("p", {
170
176
  className: "mt-0.5 text-lg font-semibold tabular-nums text-text-primary",
171
- children: Math.round(I.highestScore)
177
+ children: Math.round(B.highestScore)
172
178
  })]
173
179
  })]
174
180
  }),
175
- (I.topFactor || I.improvementArea) && /* @__PURE__ */ w("div", {
181
+ (B.topFactor || B.improvementArea) && /* @__PURE__ */ E("div", {
176
182
  className: "flex flex-wrap items-center gap-2",
177
- children: [I.topFactor && /* @__PURE__ */ C(l, {
183
+ children: [B.topFactor && /* @__PURE__ */ T(u, {
178
184
  variant: "success",
179
- label: `Strength: ${I.topFactor}`
180
- }), I.improvementArea && /* @__PURE__ */ C(l, {
185
+ label: `Strength: ${B.topFactor}`
186
+ }), B.improvementArea && /* @__PURE__ */ T(u, {
181
187
  variant: "warning",
182
- label: `Improve: ${I.improvementArea}`
188
+ label: `Improve: ${B.improvementArea}`
183
189
  })]
184
190
  })
185
191
  ]
186
- }) : /* @__PURE__ */ C("p", {
192
+ }) : /* @__PURE__ */ T("p", {
187
193
  className: "py-8 text-center text-sm text-text-secondary",
188
194
  children: "No focus score data yet."
189
195
  })
190
- }), /* @__PURE__ */ C(u, {
196
+ }), /* @__PURE__ */ T(d, {
191
197
  title: "Time Distribution",
192
198
  description: "Billable vs non-billable across everything you tracked",
193
- actions: /* @__PURE__ */ C(x, { className: "h-4 w-4 text-text-secondary" }),
194
- children: F && F.totalHours > 0 ? /* @__PURE__ */ C(f, {
195
- data: V,
199
+ actions: /* @__PURE__ */ T(C, { className: "h-4 w-4 text-text-secondary" }),
200
+ children: z && z.totalHours > 0 ? /* @__PURE__ */ T(p, {
201
+ data: G,
196
202
  centerLabel: "Tracked",
197
- centerValue: `${Math.round(F.totalHours)}h`,
203
+ centerValue: `${Math.round(z.totalHours)}h`,
198
204
  valueFormat: n,
199
205
  height: 240,
200
206
  ariaLabel: "Billable versus non-billable hours"
201
- }) : /* @__PURE__ */ C("p", {
207
+ }) : /* @__PURE__ */ T("p", {
202
208
  className: "py-8 text-center text-sm text-text-secondary",
203
209
  children: "No time entries tracked yet."
204
210
  })
205
211
  })]
206
212
  }),
207
- L && /* @__PURE__ */ C(u, {
213
+ /* @__PURE__ */ T(d, {
214
+ title: "Time by project",
215
+ description: X > 0 ? `Tracked hours across ${X} project${X === 1 ? "" : "s"}${X > Y.length ? ` — top ${Y.length} shown` : ""}` : "Tracked hours per project",
216
+ actions: /* @__PURE__ */ T(b, { className: "h-4 w-4 text-text-secondary" }),
217
+ className: "mt-6",
218
+ children: /* @__PURE__ */ T(f, {
219
+ data: Y,
220
+ xKey: "project",
221
+ series: [{
222
+ key: "hours",
223
+ label: "Tracked hours"
224
+ }],
225
+ orientation: "horizontal",
226
+ valueFormat: n,
227
+ categoryWidth: 140,
228
+ height: Math.max(160, Y.length * 34),
229
+ ariaLabel: "Tracked hours by project",
230
+ emptyLabel: "No project has tracked hours yet — log time against a project and it will appear here."
231
+ })
232
+ }),
233
+ V && /* @__PURE__ */ T(d, {
208
234
  title: "Work Patterns",
209
235
  description: "How your average day splits and what to change",
210
- actions: /* @__PURE__ */ C(g, { className: "h-4 w-4 text-text-secondary" }),
236
+ actions: /* @__PURE__ */ T(_, { className: "h-4 w-4 text-text-secondary" }),
211
237
  className: "mt-6",
212
- children: /* @__PURE__ */ w("div", {
238
+ children: /* @__PURE__ */ E("div", {
213
239
  className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
214
- children: [/* @__PURE__ */ w("div", {
240
+ children: [/* @__PURE__ */ E("div", {
215
241
  className: "space-y-4",
216
- children: [/* @__PURE__ */ w("div", {
242
+ children: [/* @__PURE__ */ E("div", {
217
243
  className: "flex flex-wrap gap-2",
218
- children: [L.mostProductiveDay && /* @__PURE__ */ C(l, {
244
+ children: [V.mostProductiveDay && /* @__PURE__ */ T(u, {
219
245
  variant: "success",
220
- label: `Peak: ${L.mostProductiveDay}`
221
- }), L.leastProductiveDay && /* @__PURE__ */ C(l, {
246
+ label: `Peak: ${V.mostProductiveDay}`
247
+ }), V.leastProductiveDay && /* @__PURE__ */ T(u, {
222
248
  variant: "muted",
223
- label: `Quietest: ${L.leastProductiveDay}`
249
+ label: `Quietest: ${V.leastProductiveDay}`
224
250
  })]
225
- }), H.some((e) => e.hours > 0) ? /* @__PURE__ */ C(d, {
226
- data: H,
251
+ }), K.some((e) => e.hours > 0) ? /* @__PURE__ */ T(f, {
252
+ data: K,
227
253
  xKey: "label",
228
254
  series: [{
229
255
  key: "hours",
@@ -234,37 +260,37 @@ function D() {
234
260
  categoryWidth: 80,
235
261
  height: 140,
236
262
  ariaLabel: "Average focus versus meeting hours per day"
237
- }) : /* @__PURE__ */ C("p", {
263
+ }) : /* @__PURE__ */ T("p", {
238
264
  className: "text-sm text-text-secondary",
239
265
  children: "Not enough tracked days to break down your average day yet."
240
266
  })]
241
- }), /* @__PURE__ */ C("div", {
267
+ }), /* @__PURE__ */ T("div", {
242
268
  className: "space-y-3",
243
- children: L.insights.length === 0 ? /* @__PURE__ */ C("p", {
269
+ children: V.insights.length === 0 ? /* @__PURE__ */ T("p", {
244
270
  className: "text-sm text-text-secondary",
245
271
  children: "No pattern insights surfaced yet — keep tracking and they'll appear here."
246
- }) : L.insights.map((e, t) => {
247
- let n = E[e.type];
248
- return /* @__PURE__ */ w("div", {
272
+ }) : V.insights.map((e, t) => {
273
+ let n = O[e.type];
274
+ return /* @__PURE__ */ E("div", {
249
275
  className: "rounded-lg border border-border-subtle bg-bg-sunken/40 p-3",
250
276
  children: [
251
- /* @__PURE__ */ w("div", {
277
+ /* @__PURE__ */ E("div", {
252
278
  className: "flex items-start justify-between gap-3",
253
- children: [/* @__PURE__ */ C("p", {
279
+ children: [/* @__PURE__ */ T("p", {
254
280
  className: "text-sm font-medium text-text-primary",
255
281
  children: e.title
256
- }), /* @__PURE__ */ C(l, {
282
+ }), /* @__PURE__ */ T(u, {
257
283
  variant: n.variant,
258
284
  label: n.label
259
285
  })]
260
286
  }),
261
- /* @__PURE__ */ C("p", {
287
+ /* @__PURE__ */ T("p", {
262
288
  className: "mt-1 text-xs text-text-secondary",
263
289
  children: e.description
264
290
  }),
265
- e.recommendation && /* @__PURE__ */ w("p", {
291
+ e.recommendation && /* @__PURE__ */ E("p", {
266
292
  className: "mt-1.5 flex items-start gap-1.5 text-xs text-text-secondary",
267
- children: [/* @__PURE__ */ C(b, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-status-info-text" }), /* @__PURE__ */ C("span", { children: e.recommendation })]
293
+ children: [/* @__PURE__ */ T(S, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-status-info-text" }), /* @__PURE__ */ T("span", { children: e.recommendation })]
268
294
  })
269
295
  ]
270
296
  }, `${e.title}-${t}`);
@@ -276,6 +302,6 @@ function D() {
276
302
  });
277
303
  }
278
304
  //#endregion
279
- export { D as AnalyticsPage };
305
+ export { k as AnalyticsPage };
280
306
 
281
307
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/dashboard/AnalyticsPage.tsx"],"sourcesContent":["import {\n Clock,\n DollarSign,\n Brain,\n TrendingUp,\n Gauge,\n PieChart as PieIcon,\n CalendarRange,\n Lightbulb,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n StatCard,\n StatusBadge,\n ChartCard,\n RadialGauge,\n DonutChart,\n BarsChart,\n type DonutSlice,\n} from \"../../components\";\nimport { formatHours } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n WorkPatternInsightType,\n type WorkPatternInsight,\n} from \"@/generated/wspace-types\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetFocusScoreSummaryQuery,\n useGetWorkPatternSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\ntype BadgeVariant = \"success\" | \"warning\" | \"error\" | \"info\" | \"muted\";\n\nconst INSIGHT_META: Record<\n WorkPatternInsightType,\n { variant: BadgeVariant; label: string }\n> = {\n [WorkPatternInsightType.Positive]: { variant: \"success\", label: \"Positive\" },\n [WorkPatternInsightType.Negative]: { variant: \"error\", label: \"Needs work\" },\n [WorkPatternInsightType.Warning]: { variant: \"warning\", label: \"Warning\" },\n [WorkPatternInsightType.Recommendation]: { variant: \"info\", label: \"Tip\" },\n [WorkPatternInsightType.Neutral]: { variant: \"muted\", label: \"Insight\" },\n};\n\nexport function AnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const { data: focusData, loading: focusLoading } =\n useGetFocusScoreSummaryQuery({\n skip: !workspaceId,\n });\n\n const { data: patternData, loading: patternLoading } =\n useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const focus = focusData?.focusScoreSummary;\n const pattern = patternData?.workPatternSummary;\n\n if (statsLoading || focusLoading || patternLoading) {\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Billable vs non-billable split from the real time-entry aggregate.\n const billableHours = stats\n ? Math.max(0, Math.round(stats.billableHours))\n : 0;\n const nonBillableHours = stats\n ? Math.max(0, Math.round(stats.totalHours - stats.billableHours))\n : 0;\n const billablePct =\n stats && stats.totalHours > 0\n ? Math.round((stats.billableHours / stats.totalHours) * 100)\n : 0;\n const distributionSlices: DonutSlice[] = [\n { name: \"Billable\", value: billableHours },\n { name: \"Non-billable\", value: nonBillableHours },\n ];\n\n // Average-day split from the real work-pattern aggregate.\n const dayBreakdown = pattern\n ? [\n { label: \"Focus\", hours: Math.max(0, pattern.averageFocusHours) },\n { label: \"Meetings\", hours: Math.max(0, pattern.averageMeetingHours) },\n ]\n : [];\n\n const trendText = focus?.trend\n ? focus.trend.charAt(0).toUpperCase() + focus.trend.slice(1)\n : undefined;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n purpose=\"Turn the raw hours you track into answers — spot whether your focus is improving, where billable time is going, and which habits to change to get more done with less burnout.\"\n >\n <NextSteps\n storageKey=\"timecampus-analytics\"\n className=\"mb-6\"\n steps={[\n {\n id: \"reports\",\n label: \"Build a detailed report\",\n description: \"Slice time by project, client or person.\",\n onClick: () => navigate(\"/reports\"),\n },\n {\n id: \"focus\",\n label: \"Dig into your focus score\",\n description: \"Understand what's helping or hurting deep work.\",\n onClick: () => navigate(\"/focus-score\"),\n },\n {\n id: \"patterns\",\n label: \"Review your work patterns\",\n description: \"See how your week actually breaks down.\",\n onClick: () => navigate(\"/work-patterns\"),\n },\n ]}\n />\n\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n title=\"Total Hours\"\n value={stats ? formatHours(stats.totalHours) : \"--\"}\n icon={<Clock className=\"h-5 w-5\" />}\n description={stats ? `${stats.totalEntries} entries` : undefined}\n />\n <StatCard\n title=\"Billable Hours\"\n value={stats ? formatHours(stats.billableHours) : \"--\"}\n icon={<DollarSign className=\"h-5 w-5\" />}\n description={stats ? `${billablePct}% of tracked time` : undefined}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n featured\n title=\"Avg Focus Score\"\n value={focus ? `${Math.round(focus.averageScore)}` : \"--\"}\n icon={<Brain className=\"h-5 w-5\" />}\n description={trendText ? `${trendText} trend` : undefined}\n iconBgColor=\"bg-status-info-bg text-status-info-text\"\n />\n <StatCard\n title=\"Avg Productivity\"\n value={pattern ? `${Math.round(pattern.averageProductivity)}%` : \"--\"}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n description={\n stats && stats.activeTimers > 0\n ? `${stats.activeTimers} timer(s) running`\n : undefined\n }\n />\n </div>\n\n {/* Focus gauge + time distribution */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Focus Score\"\n description=\"Your average deep-work score across the tracked window\"\n actions={<Gauge className=\"h-4 w-4 text-text-secondary\" />}\n >\n {focus ? (\n <div className=\"space-y-4\">\n <RadialGauge\n value={focus.averageScore}\n min={0}\n max={100}\n label=\"Avg score\"\n sublabel={trendText ? `${trendText} trend` : undefined}\n thresholds={{ warning: 75, danger: 50 }}\n height={196}\n />\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Lowest</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">\n {Math.round(focus.lowestScore)}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Highest</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">\n {Math.round(focus.highestScore)}\n </p>\n </div>\n </div>\n {(focus.topFactor || focus.improvementArea) && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {focus.topFactor && (\n <StatusBadge\n variant=\"success\"\n label={`Strength: ${focus.topFactor}`}\n />\n )}\n {focus.improvementArea && (\n <StatusBadge\n variant=\"warning\"\n label={`Improve: ${focus.improvementArea}`}\n />\n )}\n </div>\n )}\n </div>\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No focus score data yet.\n </p>\n )}\n </ChartCard>\n\n <ChartCard\n title=\"Time Distribution\"\n description=\"Billable vs non-billable across everything you tracked\"\n actions={<PieIcon className=\"h-4 w-4 text-text-secondary\" />}\n >\n {stats && stats.totalHours > 0 ? (\n <DonutChart\n data={distributionSlices}\n centerLabel=\"Tracked\"\n centerValue={`${Math.round(stats.totalHours)}h`}\n valueFormat={formatHours}\n height={240}\n ariaLabel=\"Billable versus non-billable hours\"\n />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No time entries tracked yet.\n </p>\n )}\n </ChartCard>\n </div>\n\n {/* Work pattern insights */}\n {pattern && (\n <ChartCard\n title=\"Work Patterns\"\n description=\"How your average day splits and what to change\"\n actions={<CalendarRange className=\"h-4 w-4 text-text-secondary\" />}\n className=\"mt-6\"\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap gap-2\">\n {pattern.mostProductiveDay && (\n <StatusBadge\n variant=\"success\"\n label={`Peak: ${pattern.mostProductiveDay}`}\n />\n )}\n {pattern.leastProductiveDay && (\n <StatusBadge\n variant=\"muted\"\n label={`Quietest: ${pattern.leastProductiveDay}`}\n />\n )}\n </div>\n {dayBreakdown.some((d) => d.hours > 0) ? (\n <BarsChart\n data={dayBreakdown}\n xKey=\"label\"\n series={[{ key: \"hours\", label: \"Avg hours / day\" }]}\n orientation=\"horizontal\"\n valueFormat={formatHours}\n categoryWidth={80}\n height={140}\n ariaLabel=\"Average focus versus meeting hours per day\"\n />\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Not enough tracked days to break down your average day yet.\n </p>\n )}\n </div>\n\n <div className=\"space-y-3\">\n {pattern.insights.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No pattern insights surfaced yet — keep tracking and they'll\n appear here.\n </p>\n ) : (\n pattern.insights.map(\n (insight: WorkPatternInsight, index: number) => {\n const meta = INSIGHT_META[insight.type];\n return (\n <div\n key={`${insight.title}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n <StatusBadge\n variant={meta.variant}\n label={meta.label}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-1.5 flex items-start gap-1.5 text-xs text-text-secondary\">\n <Lightbulb className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-info-text\" />\n <span>{insight.recommendation}</span>\n </p>\n )}\n </div>\n );\n },\n )\n )}\n </div>\n </div>\n </ChartCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/dashboard/AnalyticsPage.tsx"],"sourcesContent":["import {\n Clock,\n DollarSign,\n Brain,\n TrendingUp,\n Gauge,\n FolderOpen,\n PieChart as PieIcon,\n CalendarRange,\n Lightbulb,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n StatCard,\n StatusBadge,\n ChartCard,\n RadialGauge,\n DonutChart,\n BarsChart,\n type ChartDatum,\n type DonutSlice,\n} from \"../../components\";\nimport { formatHours } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n WorkPatternInsightType,\n type WorkPatternInsight,\n} from \"@/generated/wspace-types\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetFocusScoreSummaryQuery,\n useGetWorkPatternSummaryQuery,\n useListProjectsQuery,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\n\ntype BadgeVariant = \"success\" | \"warning\" | \"error\" | \"info\" | \"muted\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst INSIGHT_META: Record<\n WorkPatternInsightType,\n { variant: BadgeVariant; label: string }\n> = {\n [WorkPatternInsightType.Positive]: { variant: \"success\", label: \"Positive\" },\n [WorkPatternInsightType.Negative]: { variant: \"error\", label: \"Needs work\" },\n [WorkPatternInsightType.Warning]: { variant: \"warning\", label: \"Warning\" },\n [WorkPatternInsightType.Recommendation]: { variant: \"info\", label: \"Tip\" },\n [WorkPatternInsightType.Neutral]: { variant: \"muted\", label: \"Insight\" },\n};\n\nexport function AnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n const { data: focusData, loading: focusLoading } =\n useGetFocusScoreSummaryQuery({\n skip: !workspaceId,\n });\n\n const { data: patternData, loading: patternLoading } =\n useGetWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n // Where the tracked hours landed, per project. `trackedHours` is the running\n // total the time-entry service keeps on each project row — no client maths.\n const { data: projectsData, loading: projectsLoading } = useListProjectsQuery(\n {\n variables: { pagination: { limit: 200 } },\n skip: !workspaceId,\n },\n );\n\n const stats = statsData?.timeEntryStats;\n const focus = focusData?.focusScoreSummary;\n const pattern = patternData?.workPatternSummary;\n\n if (statsLoading || focusLoading || patternLoading || projectsLoading) {\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n // Billable vs non-billable split from the real time-entry aggregate.\n const billableHours = stats\n ? Math.max(0, Math.round(stats.billableHours))\n : 0;\n const nonBillableHours = stats\n ? Math.max(0, Math.round(stats.totalHours - stats.billableHours))\n : 0;\n const billablePct =\n stats && stats.totalHours > 0\n ? Math.round((stats.billableHours / stats.totalHours) * 100)\n : 0;\n const distributionSlices: DonutSlice[] = [\n { name: \"Billable\", value: billableHours },\n { name: \"Non-billable\", value: nonBillableHours },\n ];\n\n // Average-day split from the real work-pattern aggregate.\n const dayBreakdown = pattern\n ? [\n { label: \"Focus\", hours: Math.max(0, pattern.averageFocusHours) },\n { label: \"Meetings\", hours: Math.max(0, pattern.averageMeetingHours) },\n ]\n : [];\n\n const trendText = focus?.trend\n ? focus.trend.charAt(0).toUpperCase() + focus.trend.slice(1)\n : undefined;\n\n // Time by project — the ten projects carrying the most tracked hours.\n const allProjects: ProjectItem[] = projectsData?.projects?.items ?? [];\n const trackedProjects = allProjects.filter(\n (project: ProjectItem) => (project.trackedHours ?? 0) > 0,\n );\n const projectHours: ChartDatum[] = [...trackedProjects]\n .sort(\n (a: ProjectItem, b: ProjectItem) =>\n (b.trackedHours ?? 0) - (a.trackedHours ?? 0),\n )\n .slice(0, 10)\n .map((project: ProjectItem) => ({\n project: project.name,\n hours: Math.round((project.trackedHours ?? 0) * 100) / 100,\n }));\n const projectsWithTime = trackedProjects.length;\n\n return (\n <PageLayout\n title=\"Analytics\"\n description=\"Insights into your time tracking and productivity\"\n purpose=\"Turn the raw hours you track into answers — spot whether your focus is improving, where billable time is going, and which habits to change to get more done with less burnout.\"\n >\n <NextSteps\n storageKey=\"timecampus-analytics\"\n className=\"mb-6\"\n steps={[\n {\n id: \"reports\",\n label: \"Build a detailed report\",\n description: \"Slice time by project, client or person.\",\n onClick: () => navigate(\"/reports\"),\n },\n {\n id: \"focus\",\n label: \"Dig into your focus score\",\n description: \"Understand what's helping or hurting deep work.\",\n onClick: () => navigate(\"/focus-score\"),\n },\n {\n id: \"patterns\",\n label: \"Review your work patterns\",\n description: \"See how your week actually breaks down.\",\n onClick: () => navigate(\"/work-patterns\"),\n },\n ]}\n />\n\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n title=\"Total Hours\"\n value={stats ? formatHours(stats.totalHours) : \"--\"}\n icon={<Clock className=\"h-5 w-5\" />}\n description={stats ? `${stats.totalEntries} entries` : undefined}\n />\n <StatCard\n title=\"Billable Hours\"\n value={stats ? formatHours(stats.billableHours) : \"--\"}\n icon={<DollarSign className=\"h-5 w-5\" />}\n description={stats ? `${billablePct}% of tracked time` : undefined}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n featured\n title=\"Avg Focus Score\"\n value={focus ? `${Math.round(focus.averageScore)}` : \"--\"}\n icon={<Brain className=\"h-5 w-5\" />}\n description={trendText ? `${trendText} trend` : undefined}\n iconBgColor=\"bg-status-info-bg text-status-info-text\"\n />\n <StatCard\n title=\"Avg Productivity\"\n value={pattern ? `${Math.round(pattern.averageProductivity)}%` : \"--\"}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n description={\n stats && stats.activeTimers > 0\n ? `${stats.activeTimers} timer(s) running`\n : undefined\n }\n />\n </div>\n\n {/* Focus gauge + time distribution */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Focus Score\"\n description=\"Your average deep-work score across the tracked window\"\n actions={<Gauge className=\"h-4 w-4 text-text-secondary\" />}\n >\n {focus ? (\n <div className=\"space-y-4\">\n <RadialGauge\n value={focus.averageScore}\n min={0}\n max={100}\n label=\"Avg score\"\n sublabel={trendText ? `${trendText} trend` : undefined}\n thresholds={{ warning: 75, danger: 50 }}\n height={196}\n />\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Lowest</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">\n {Math.round(focus.lowestScore)}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs text-text-secondary\">Highest</p>\n <p className=\"mt-0.5 text-lg font-semibold tabular-nums text-text-primary\">\n {Math.round(focus.highestScore)}\n </p>\n </div>\n </div>\n {(focus.topFactor || focus.improvementArea) && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {focus.topFactor && (\n <StatusBadge\n variant=\"success\"\n label={`Strength: ${focus.topFactor}`}\n />\n )}\n {focus.improvementArea && (\n <StatusBadge\n variant=\"warning\"\n label={`Improve: ${focus.improvementArea}`}\n />\n )}\n </div>\n )}\n </div>\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No focus score data yet.\n </p>\n )}\n </ChartCard>\n\n <ChartCard\n title=\"Time Distribution\"\n description=\"Billable vs non-billable across everything you tracked\"\n actions={<PieIcon className=\"h-4 w-4 text-text-secondary\" />}\n >\n {stats && stats.totalHours > 0 ? (\n <DonutChart\n data={distributionSlices}\n centerLabel=\"Tracked\"\n centerValue={`${Math.round(stats.totalHours)}h`}\n valueFormat={formatHours}\n height={240}\n ariaLabel=\"Billable versus non-billable hours\"\n />\n ) : (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No time entries tracked yet.\n </p>\n )}\n </ChartCard>\n </div>\n\n {/* Where the tracked hours actually landed */}\n <ChartCard\n title=\"Time by project\"\n description={\n projectsWithTime > 0\n ? `Tracked hours across ${projectsWithTime} project${projectsWithTime === 1 ? \"\" : \"s\"}${projectsWithTime > projectHours.length ? ` — top ${projectHours.length} shown` : \"\"}`\n : \"Tracked hours per project\"\n }\n actions={<FolderOpen className=\"h-4 w-4 text-text-secondary\" />}\n className=\"mt-6\"\n >\n <BarsChart\n data={projectHours}\n xKey=\"project\"\n series={[{ key: \"hours\", label: \"Tracked hours\" }]}\n orientation=\"horizontal\"\n valueFormat={formatHours}\n categoryWidth={140}\n height={Math.max(160, projectHours.length * 34)}\n ariaLabel=\"Tracked hours by project\"\n emptyLabel=\"No project has tracked hours yet — log time against a project and it will appear here.\"\n />\n </ChartCard>\n\n {/* Work pattern insights */}\n {pattern && (\n <ChartCard\n title=\"Work Patterns\"\n description=\"How your average day splits and what to change\"\n actions={<CalendarRange className=\"h-4 w-4 text-text-secondary\" />}\n className=\"mt-6\"\n >\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap gap-2\">\n {pattern.mostProductiveDay && (\n <StatusBadge\n variant=\"success\"\n label={`Peak: ${pattern.mostProductiveDay}`}\n />\n )}\n {pattern.leastProductiveDay && (\n <StatusBadge\n variant=\"muted\"\n label={`Quietest: ${pattern.leastProductiveDay}`}\n />\n )}\n </div>\n {dayBreakdown.some((d) => d.hours > 0) ? (\n <BarsChart\n data={dayBreakdown}\n xKey=\"label\"\n series={[{ key: \"hours\", label: \"Avg hours / day\" }]}\n orientation=\"horizontal\"\n valueFormat={formatHours}\n categoryWidth={80}\n height={140}\n ariaLabel=\"Average focus versus meeting hours per day\"\n />\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Not enough tracked days to break down your average day yet.\n </p>\n )}\n </div>\n\n <div className=\"space-y-3\">\n {pattern.insights.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No pattern insights surfaced yet — keep tracking and they'll\n appear here.\n </p>\n ) : (\n pattern.insights.map(\n (insight: WorkPatternInsight, index: number) => {\n const meta = INSIGHT_META[insight.type];\n return (\n <div\n key={`${insight.title}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-sunken/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n <StatusBadge\n variant={meta.variant}\n label={meta.label}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-1.5 flex items-start gap-1.5 text-xs text-text-secondary\">\n <Lightbulb className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-info-text\" />\n <span>{insight.recommendation}</span>\n </p>\n )}\n </div>\n );\n },\n )\n )}\n </div>\n </div>\n </ChartCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RecommendationsPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"names":[],"mappings":"AAuSA,wBAAgB,mBAAmB,gCAiSlC"}
1
+ {"version":3,"file":"RecommendationsPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"names":[],"mappings":"AA6oBA,wBAAgB,mBAAmB,gCA4blC"}