@burdenoff/microfe-timecampus 2026.731.2 → 2026.820.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.
Files changed (159) hide show
  1. package/dist/generated/wspace-operations.d.ts +484 -861
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.js +1377 -1377
  4. package/dist/generated/wspace-operations.js.map +1 -1
  5. package/dist/generated/wspace-types.js.map +1 -1
  6. package/dist/index.js +5 -3
  7. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  8. package/dist/stories/EmptyState.stories.d.ts +0 -1
  9. package/dist/stories/LoadingSkeleton.stories.d.ts +0 -1
  10. package/dist/stories/StatCard.stories.d.ts +0 -1
  11. package/dist/stories/StatusBadge.stories.d.ts +0 -1
  12. package/dist/timecampus/AdminRoot.d.ts +8 -0
  13. package/dist/timecampus/AdminRoot.d.ts.map +1 -0
  14. package/dist/timecampus/AdminRoot.js +12 -0
  15. package/dist/timecampus/AdminRoot.js.map +1 -0
  16. package/dist/timecampus/AdminRoutes.d.ts +3 -0
  17. package/dist/timecampus/AdminRoutes.d.ts.map +1 -0
  18. package/dist/timecampus/AdminRoutes.js +8 -0
  19. package/dist/timecampus/AdminRoutes.js.map +1 -0
  20. package/dist/timecampus/TimeCampusRoot.d.ts +0 -1
  21. package/dist/timecampus/TimeCampusRoot.js +4 -2
  22. package/dist/timecampus/TimeCampusRoot.js.map +1 -1
  23. package/dist/timecampus/TimeCampusRoutes.js +1 -1
  24. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  25. package/dist/timecampus/components/ChartCard.d.ts +0 -1
  26. package/dist/timecampus/components/ChartCard.js.map +1 -1
  27. package/dist/timecampus/components/DataTable.d.ts +0 -1
  28. package/dist/timecampus/components/EmptyState.d.ts +0 -1
  29. package/dist/timecampus/components/EmptyState.js.map +1 -1
  30. package/dist/timecampus/components/ErrorBoundary.d.ts +0 -1
  31. package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
  32. package/dist/timecampus/components/FilterBar.d.ts +0 -1
  33. package/dist/timecampus/components/LoadingSkeleton.d.ts +0 -1
  34. package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
  35. package/dist/timecampus/components/Modal.d.ts +0 -1
  36. package/dist/timecampus/components/Modal.js +5 -3
  37. package/dist/timecampus/components/Modal.js.map +1 -1
  38. package/dist/timecampus/components/PageLayout.d.ts +0 -1
  39. package/dist/timecampus/components/PageLayout.js.map +1 -1
  40. package/dist/timecampus/components/StatCard.d.ts +0 -1
  41. package/dist/timecampus/components/StatCard.js.map +1 -1
  42. package/dist/timecampus/components/StatusBadge.d.ts +0 -1
  43. package/dist/timecampus/components/StatusBadge.js.map +1 -1
  44. package/dist/timecampus/components/Toast.d.ts +0 -1
  45. package/dist/timecampus/components/Toast.js.map +1 -1
  46. package/dist/timecampus/components/charts/BalanceWheel.js.map +1 -1
  47. package/dist/timecampus/components/charts/BarsChart.d.ts +0 -1
  48. package/dist/timecampus/components/charts/BarsChart.js.map +1 -1
  49. package/dist/timecampus/components/charts/ChartParts.d.ts +0 -1
  50. package/dist/timecampus/components/charts/ChartParts.js.map +1 -1
  51. package/dist/timecampus/components/charts/DonutChart.d.ts +0 -1
  52. package/dist/timecampus/components/charts/DonutChart.js.map +1 -1
  53. package/dist/timecampus/components/charts/Heatmap.d.ts +0 -1
  54. package/dist/timecampus/components/charts/Heatmap.js +29 -29
  55. package/dist/timecampus/components/charts/Heatmap.js.map +1 -1
  56. package/dist/timecampus/components/charts/ProgressMeter.d.ts +0 -1
  57. package/dist/timecampus/components/charts/ProgressMeter.js +2 -2
  58. package/dist/timecampus/components/charts/ProgressMeter.js.map +1 -1
  59. package/dist/timecampus/components/charts/RadialGauge.d.ts +0 -1
  60. package/dist/timecampus/components/charts/RadialGauge.js +2 -2
  61. package/dist/timecampus/components/charts/RadialGauge.js.map +1 -1
  62. package/dist/timecampus/components/charts/TrendChart.d.ts +0 -1
  63. package/dist/timecampus/components/charts/TrendChart.js.map +1 -1
  64. package/dist/timecampus/components/charts/WeeksGrid.js +2 -2
  65. package/dist/timecampus/components/charts/WeeksGrid.js.map +1 -1
  66. package/dist/timecampus/components/charts/chartTheme.js.map +1 -1
  67. package/dist/timecampus/components/charts/index.js +10 -9
  68. package/dist/timecampus/components/index.js +18 -17
  69. package/dist/timecampus/index.d.ts +3 -1
  70. package/dist/timecampus/index.d.ts.map +1 -1
  71. package/dist/timecampus/index.js +8 -5
  72. package/dist/timecampus/pages/HomePage.d.ts +0 -1
  73. package/dist/timecampus/pages/HomePage.js.map +1 -1
  74. package/dist/timecampus/pages/admin/AdminPage.d.ts +3 -0
  75. package/dist/timecampus/pages/admin/AdminPage.d.ts.map +1 -0
  76. package/dist/timecampus/pages/admin/AdminPage.js +235 -0
  77. package/dist/timecampus/pages/admin/AdminPage.js.map +1 -0
  78. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
  79. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  80. package/dist/timecampus/pages/client-portal/ClientPortalPage.d.ts.map +1 -1
  81. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  82. package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
  83. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  84. package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
  85. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.d.ts.map +1 -1
  86. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  87. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.d.ts.map +1 -1
  88. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  89. package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
  90. package/dist/timecampus/pages/geofencing/GeofencingPage.d.ts.map +1 -1
  91. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  92. package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -1
  93. package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
  94. package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js.map +1 -1
  95. package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
  96. package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
  97. package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
  98. package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
  99. package/dist/timecampus/pages/lifetime/RecommendationsPage.js +1 -1
  100. package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
  101. package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js.map +1 -1
  102. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +9 -6
  103. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js.map +1 -1
  104. package/dist/timecampus/pages/lifetime/domains.d.ts +0 -1
  105. package/dist/timecampus/pages/lifetime/domains.js +1 -1
  106. package/dist/timecampus/pages/lifetime/domains.js.map +1 -1
  107. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.d.ts.map +1 -1
  108. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  109. package/dist/timecampus/pages/meetings/ActionItemFormModal.d.ts +0 -1
  110. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -1
  111. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +1 -1
  112. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
  113. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  114. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +1 -1
  115. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  116. package/dist/timecampus/pages/meetings/MeetingFormModal.d.ts +0 -1
  117. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
  118. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  119. package/dist/timecampus/pages/meetings/ParticipantFormModal.d.ts +0 -1
  120. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -1
  121. package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
  122. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  123. package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
  124. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  125. package/dist/timecampus/pages/projects/ProjectDetailPage.js +1 -1
  126. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  127. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  128. package/dist/timecampus/pages/projects/TasksPage.js +1 -1
  129. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  130. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.d.ts.map +1 -1
  131. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  132. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  133. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  134. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  135. package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
  136. package/dist/timecampus/pages/time/TimeEntriesPage.d.ts.map +1 -1
  137. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  138. package/dist/timecampus/pages/time/TimeTrackingPage.js +1 -1
  139. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  140. package/dist/timecampus/pages/time/TimerPage.js +4 -4
  141. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  142. package/dist/timecampus/pages/time-approval/TimeApprovalPage.d.ts.map +1 -1
  143. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  144. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
  145. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +1 -1
  146. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
  147. package/dist/timecampus/providers/TimeCampusProvider.d.ts +0 -1
  148. package/dist/timecampus/providers/TimeCampusProvider.d.ts.map +1 -1
  149. package/dist/timecampus/providers/TimeCampusProvider.js +39 -36
  150. package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
  151. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  152. package/dist/timecampus/types/index.d.ts +4 -1
  153. package/dist/timecampus/types/index.d.ts.map +1 -1
  154. package/dist/timecampus/utils/helpers.d.ts +0 -1
  155. package/dist/timecampus/utils/helpers.js.map +1 -1
  156. package/dist/types/assistant.d.ts +0 -1
  157. package/dist/utils/pageContext.js +1 -1
  158. package/dist/utils/pageContext.js.map +1 -1
  159. package/package.json +61 -59
@@ -1 +1 @@
1
- {"version":3,"file":"FiniteLifePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/FiniteLifePage.tsx"],"sourcesContent":["import { useMemo, useState, type FormEvent } from \"react\";\nimport {\n Hourglass,\n Sparkles,\n SlidersHorizontal,\n CalendarDays,\n Lock,\n ArrowRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n ChartCard,\n EmptyState,\n Modal,\n DonutChart,\n WeeksGrid,\n LoadingSkeleton,\n ToastContainer,\n useToast,\n type DonutSlice,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useMyLifeInWeeksQuery,\n useMyLifeProfileQuery,\n useUpdateMyLifeProfileMutation,\n type MyLifeInWeeksQuery,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel, formatSecondsHuman } from \"./domains\";\n\n/** One life-domain roll-up row, typed from the generated query result. */\ntype LifeDomainRollup =\n MyLifeInWeeksQuery[\"myLifeInWeeks\"][\"domainRollups\"][number];\n\n/** Grouped integer (e.g. 1,742). */\nfunction nf(n: number): string {\n return Math.max(0, Math.round(n)).toLocaleString();\n}\n\n/** Whole-week count → years with one decimal (e.g. 33.5). */\nfunction weeksToYears(weeks: number): string {\n return (Math.round((weeks / 52) * 10) / 10).toFixed(1);\n}\n\n/** ISO datetime → the `YYYY-MM-DD` a native date input expects. */\nfunction isoToDateInput(iso: string | null | undefined): string {\n if (!iso) return \"\";\n return iso.slice(0, 10);\n}\n\nconst TODAY_DATE_INPUT = new Date().toISOString().slice(0, 10);\n\n/**\n * FiniteLifePage — the finite-life \"your life in weeks\" memento.\n *\n * A calm, opt-in, entirely private reflection: each cell is one week of a life,\n * anchored to the gentle north star \"What if today was your last?\". Everything\n * it draws comes from the viewer's own (optional) birth date and life\n * expectancy — never a guessed age or an invented mortality date. It is\n * self-scoped (identity travels in the gateway headers, so the queries take no\n * user argument) and honest by construction: with no birth date it still\n * renders an open horizon and simply invites you to add one. It respects the\n * `hideRemainingWeeks` choice so no one is forced to stare down a countdown.\n */\nexport function FiniteLifePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: weeksData,\n loading: weeksLoading,\n error: weeksError,\n refetch: refetchWeeks,\n } = useMyLifeInWeeksQuery({ skip: !workspaceId });\n\n const {\n data: profileData,\n loading: profileLoading,\n error: profileError,\n refetch: refetchProfile,\n } = useMyLifeProfileQuery({ skip: !workspaceId });\n\n const [updateProfile, { loading: saving }] = useUpdateMyLifeProfileMutation();\n\n const weeks = weeksData?.myLifeInWeeks;\n const profile = profileData?.myLifeProfile;\n\n // ---- Setup modal (opt-in, dismissible) -----------------------------------\n const [setupOpen, setSetupOpen] = useState(false);\n const [birthDateInput, setBirthDateInput] = useState(\"\");\n const [expectancyInput, setExpectancyInput] = useState(\"\");\n const [hideRemainingInput, setHideRemainingInput] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const openSetup = () => {\n setBirthDateInput(isoToDateInput(profile?.birthDate ?? weeks?.birthDate));\n setExpectancyInput(\n profile?.lifeExpectancyYears != null\n ? String(profile.lifeExpectancyYears)\n : weeks?.lifeExpectancyYears != null\n ? String(weeks.lifeExpectancyYears)\n : \"\",\n );\n setHideRemainingInput(\n profile?.hideRemainingWeeks ?? weeks?.hideRemainingWeeks ?? false,\n );\n setFormError(null);\n setSetupOpen(true);\n };\n\n const handleSave = async (e?: FormEvent) => {\n e?.preventDefault();\n setFormError(null);\n\n if (!birthDateInput) {\n setFormError(\"Please add your birth date to continue.\");\n return;\n }\n if (birthDateInput > TODAY_DATE_INPUT) {\n setFormError(\"Your birth date can't be in the future.\");\n return;\n }\n\n let expectancy: number | null = null;\n if (expectancyInput.trim() !== \"\") {\n const parsed = Number(expectancyInput);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 130) {\n setFormError(\n \"Life expectancy should be a whole number of years (1–130).\",\n );\n return;\n }\n expectancy = parsed;\n }\n\n try {\n await updateProfile({\n variables: {\n input: {\n birthDate: new Date(\n `${birthDateInput}T00:00:00.000Z`,\n ).toISOString(),\n lifeExpectancyYears: expectancy,\n hideRemainingWeeks: hideRemainingInput,\n finiteLifeOptIn: true,\n },\n },\n });\n await Promise.all([refetchWeeks(), refetchProfile()]);\n setSetupOpen(false);\n addToast({\n variant: \"success\",\n title: \"Saved — this is just for you\",\n description: \"You can change or turn this off anytime.\",\n });\n } catch {\n addToast({\n variant: \"error\",\n title: \"Couldn't save your settings\",\n description: \"Please try again in a moment.\",\n });\n }\n };\n\n // ---- Derived, honest values ----------------------------------------------\n const hasBirthDate = !!weeks?.birthDate;\n const hideRemaining =\n weeks?.hideRemainingWeeks ?? profile?.hideRemainingWeeks ?? false;\n\n const weeksLived = weeks?.weeksLived ?? null;\n const expectancyYears = weeks?.lifeExpectancyYears ?? null;\n // Only reveal the full mortal horizon when the user hasn't asked to hide it.\n const gridTotalWeeks = hideRemaining\n ? null\n : (weeks?.expectedTotalWeeks ?? null);\n const weeksRemaining =\n hideRemaining || weeks?.weeksRemaining == null\n ? null\n : weeks.weeksRemaining;\n\n // Reflection: where the recent (backend-defined) window actually went.\n const domainSlices: DonutSlice[] = useMemo(() => {\n const rollups: LifeDomainRollup[] = weeks?.domainRollups ?? [];\n return rollups\n .filter((r) => r.totalSeconds > 0)\n .map((r) => ({\n name: lifeDomainLabel(r.domain),\n value: r.totalSeconds,\n }));\n }, [weeks?.domainRollups]);\n const rollupTotalSeconds = domainSlices.reduce((sum, s) => sum + s.value, 0);\n\n const loading = weeksLoading || profileLoading;\n const hardError = (weeksError || profileError) && !weeks && !profile;\n\n const setupAction = (\n <button\n type=\"button\"\n onClick={openSetup}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n Life settings\n </button>\n );\n\n if (loading && !weeks && !profile) {\n return (\n <PageLayout\n title=\"Your Life in Weeks\"\n description=\"A calm, private view of the weeks you've lived — and the ones still ahead.\"\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Your Life in Weeks\"\n description=\"A calm, private view of the weeks you've lived — and the ones still ahead.\"\n purpose=\"Each square is a single week of your life. It's a gentle memento — not a countdown — meant only for you, to help this week feel a little more intentional.\"\n actions={setupAction}\n >\n {hardError ? (\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n We couldn't load your life view right now. Please try again in a\n moment.\n </div>\n ) : (\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* The grid — the memento itself */}\n <div className=\"lg:col-span-2\">\n <ChartCard\n title=\"Your life in weeks\"\n description={\n hasBirthDate\n ? \"Each row is a year; each square, a week you have lived or have yet to live.\"\n : \"Add your birth date to fill in the weeks you've already lived.\"\n }\n >\n <WeeksGrid\n weeksLived={weeksLived}\n totalWeeks={gridTotalWeeks}\n ariaLabel={\n hasBirthDate\n ? `Your life in weeks: ${nf(weeksLived ?? 0)} weeks lived so far`\n : \"Your life in weeks — birth date not set yet\"\n }\n />\n <div className=\"mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] bg-action-primary-bg\" />\n Weeks lived\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] border-2 border-action-primary-bg bg-bg-surface\" />\n This week\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] border border-border-seam\" />\n Ahead\n </span>\n </div>\n </ChartCard>\n </div>\n\n {/* The calm side card */}\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"flex flex-col p-5 sm:p-6\"\n >\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary\">\n <Hourglass className=\"h-5 w-5\" />\n </div>\n\n {hasBirthDate ? (\n <div className=\"mt-4 space-y-5\">\n <div>\n <p className=\"text-sm text-text-secondary\">\n You have lived\n </p>\n <p className=\"mt-0.5 text-3xl font-bold tabular-nums text-text-primary\">\n {nf(weeksLived ?? 0)}{\" \"}\n <span className=\"text-xl font-semibold text-text-secondary\">\n weeks\n </span>\n </p>\n </div>\n\n {expectancyYears != null && weeksRemaining != null ? (\n <div>\n <p className=\"text-sm text-text-secondary\">\n If you live to {expectancyYears}, about\n </p>\n <p className=\"mt-0.5 text-2xl font-bold tabular-nums text-text-primary\">\n {nf(weeksRemaining)}{\" \"}\n <span className=\"text-lg font-semibold text-text-secondary\">\n remain\n </span>\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {weeksToYears(weeksLived ?? 0)} years lived ·{\" \"}\n {weeksToYears(weeksRemaining)} years ahead\n </p>\n </div>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n {hideRemaining\n ? \"You've chosen to see only the weeks you've lived.\"\n : \"Add a life expectancy in settings to gently sketch the weeks ahead.\"}\n </p>\n )}\n\n <div className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3\">\n <p className=\"flex items-start gap-2 text-sm text-text-primary\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>\n What if today was your last? Make this week count.\n </span>\n </p>\n </div>\n </div>\n ) : (\n <div className=\"mt-4 space-y-4\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n See your life in weeks\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Add your birth date and we'll gently fill in the weeks\n you've lived so far — a quiet reminder that time is\n finite, and this week is yours to spend.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={openSetup}\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\"\n >\n Add your birth date\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n\n <p className=\"mt-6 flex items-start gap-1.5 text-xs text-text-secondary\">\n <Lock className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" />\n <span>\n Only you can see this. Your birth date is never shared, and\n you can turn this off anytime.\n </span>\n </p>\n </GlassCard>\n </div>\n\n {/* Gentle reflection — only when there's real recent data to show */}\n {domainSlices.length > 0 && (\n <ChartCard\n title=\"Where your recent weeks went\"\n description=\"How your logged time settled across life's domains lately — a mirror, not a scorecard.\"\n >\n <DonutChart\n data={domainSlices}\n centerLabel=\"logged lately\"\n centerValue={formatSecondsHuman(rollupTotalSeconds)}\n valueFormat={(v) => formatSecondsHuman(v)}\n emptyLabel=\"Nothing logged in this window yet.\"\n ariaLabel=\"Recent time by life domain\"\n />\n </ChartCard>\n )}\n\n {/* Honest first-run nudge when nothing has been logged either */}\n {hasBirthDate && domainSlices.length === 0 && (\n <EmptyState\n title=\"Your weeks are yours to shape\"\n description=\"As you capture how you spend your time, a quiet reflection of your recent weeks will appear here — only for you.\"\n />\n )}\n </div>\n )}\n\n {/* Opt-in setup */}\n <Modal\n open={setupOpen}\n onClose={() => setSetupOpen(false)}\n title=\"Your life in weeks\"\n description=\"This is private and entirely optional. Add only what feels comfortable.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setSetupOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n form=\"finite-life-setup\"\n disabled={saving}\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 {saving ? \"Saving…\" : \"Save\"}\n </button>\n </>\n }\n >\n <form\n id=\"finite-life-setup\"\n onSubmit={handleSave}\n className=\"space-y-5\"\n >\n <div>\n <label\n htmlFor=\"finite-life-birthdate\"\n className=\"flex items-center gap-1.5 text-sm font-medium text-text-primary\"\n >\n <CalendarDays className=\"h-4 w-4 text-text-secondary\" />\n Your birth date\n </label>\n <input\n id=\"finite-life-birthdate\"\n type=\"date\"\n value={birthDateInput}\n max={TODAY_DATE_INPUT}\n onChange={(e) => setBirthDateInput(e.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-subtle bg-bg-sunken px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n Used only to count the weeks you've lived. It stays with you.\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"finite-life-expectancy\"\n className=\"text-sm font-medium text-text-primary\"\n >\n Life expectancy{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (optional)\n </span>\n </label>\n <input\n id=\"finite-life-expectancy\"\n type=\"number\"\n inputMode=\"numeric\"\n min={1}\n max={130}\n step={1}\n placeholder=\"e.g. 90\"\n value={expectancyInput}\n onChange={(e) => setExpectancyInput(e.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-subtle bg-bg-sunken px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n A gentle horizon for the weeks ahead — never a deadline. Leave it\n blank to simply see the weeks you've lived.\n </p>\n </div>\n\n <label className=\"flex items-start gap-2.5 rounded-lg border border-border-subtle bg-bg-surface/40 p-3\">\n <input\n type=\"checkbox\"\n checked={hideRemainingInput}\n onChange={(e) => setHideRemainingInput(e.target.checked)}\n className=\"mt-0.5 h-4 w-4 shrink-0 rounded border-border-subtle\"\n />\n <span className=\"text-sm text-text-primary\">\n Hide the weeks ahead\n <span className=\"mt-0.5 block text-xs text-text-secondary\">\n Show only the weeks you've lived — no horizon, no countdown.\n </span>\n </span>\n </label>\n\n {formError && (\n <p className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </p>\n )}\n </form>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoCA,SAAS,EAAG,GAAmB;AAC7B,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC,CAAC,gBAAgB;;AAIpD,SAAS,EAAa,GAAuB;AAC3C,SAAQ,KAAK,MAAO,IAAQ,KAAM,GAAG,GAAG,IAAI,QAAQ,EAAE;;AAIxD,SAAS,GAAe,GAAwC;AAE9D,QADK,IACE,EAAI,MAAM,GAAG,GAAG,GADN;;AAInB,IAAM,qBAAmB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;AAc9D,SAAgB,IAAiB;CAC/B,IAAM,EAAE,iBAAc,OAAO,GAAe,EACtC,EAAE,YAAQ,aAAU,oBAAiB,IAAU,EAE/C,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,MACP,EAAsB,EAAE,MAAM,CAAC,GAAa,CAAC,EAE3C,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,MACP,GAAsB,EAAE,MAAM,CAAC,GAAa,CAAC,EAE3C,CAAC,GAAe,EAAE,SAAS,OAAY,GAAgC,EAEvE,IAAQ,GAAW,eACnB,IAAU,GAAa,eAGvB,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAgB,KAAqB,EAAS,GAAG,EAClD,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,UAAkB;AAatB,EAZA,EAAkB,GAAe,GAAS,aAAa,GAAO,UAAU,CAAC,EACzE,EACE,GAAS,uBAAuB,OAE5B,GAAO,uBAAuB,OAE5B,KADA,OAAO,EAAM,oBAAoB,GAFnC,OAAO,EAAQ,oBAAoB,CAIxC,EACD,EACE,GAAS,sBAAsB,GAAO,sBAAsB,GAC7D,EACD,EAAa,KAAK,EAClB,EAAa,GAAK;IAGd,KAAa,OAAO,MAAkB;AAI1C,MAHA,GAAG,gBAAgB,EACnB,EAAa,KAAK,EAEd,CAAC,GAAgB;AACnB,KAAa,0CAA0C;AACvD;;AAEF,MAAI,IAAiB,GAAkB;AACrC,KAAa,0CAA0C;AACvD;;EAGF,IAAI,IAA4B;AAChC,MAAI,EAAgB,MAAM,KAAK,IAAI;GACjC,IAAM,IAAS,OAAO,EAAgB;AACtC,OAAI,CAAC,OAAO,UAAU,EAAO,IAAI,IAAS,KAAK,IAAS,KAAK;AAC3D,MACE,6DACD;AACD;;AAEF,OAAa;;AAGf,MAAI;AAeF,GAdA,MAAM,EAAc,EAClB,WAAW,EACT,OAAO;IACL,4BAAW,IAAI,KACb,GAAG,EAAe,gBACnB,EAAC,aAAa;IACf,qBAAqB;IACrB,oBAAoB;IACpB,iBAAiB;IAClB,EACF,EACF,CAAC,EACF,MAAM,QAAQ,IAAI,CAAC,GAAc,EAAE,GAAgB,CAAC,CAAC,EACrD,EAAa,GAAM,EACnB,EAAS;IACP,SAAS;IACT,OAAO;IACP,aAAa;IACd,CAAC;UACI;AACN,KAAS;IACP,SAAS;IACT,OAAO;IACP,aAAa;IACd,CAAC;;IAKA,IAAe,CAAC,CAAC,GAAO,WACxB,IACJ,GAAO,sBAAsB,GAAS,sBAAsB,IAExD,IAAa,GAAO,cAAc,MAClC,IAAkB,GAAO,uBAAuB,MAEhD,KAAiB,IACnB,OACC,GAAO,sBAAsB,MAC5B,IACJ,KAAiB,GAAO,kBAAkB,OACtC,OACA,EAAM,gBAGN,IAA6B,SACG,GAAO,iBAAiB,EAAE,EAE3D,QAAQ,MAAM,EAAE,eAAe,EAAE,CACjC,KAAK,OAAO;EACX,MAAM,EAAgB,EAAE,OAAO;EAC/B,OAAO,EAAE;EACV,EAAE,EACJ,CAAC,GAAO,cAAc,CAAC,EACpB,KAAqB,EAAa,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE;AA2B5E,SAzBgB,KAAgB,MAcjB,CAAC,KAAS,CAAC,IAEtB,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD,EAAiB,SAAQ,UAAW,CAAA;EACzB,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SA1BF,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAmB,WAAU,WAAY,CAAA,EAAA,gBAElC;;YAeT;IAzBiB,KAAc,MAAiB,CAAC,KAAS,CAAC,IAgCvD,kBAAC,OAAD;IAAK,WAAU;cAA2G;IAGpH,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,OAAM;QACN,aACE,IACI,gFACA;kBALR,CAQE,kBAAC,GAAD;SACc;SACZ,YAAY;SACZ,WACE,IACI,uBAAuB,EAAG,KAAc,EAAE,CAAC,uBAC3C;SAEN,CAAA,EACF,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,8CAA+C,CAAA,EAAA,cAE1D;;UACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,yEAA0E,CAAA,EAAA,YAErF;;UACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,mDAAoD,CAAA,EAAA,QAE/D;;UACH;WACI;;OACR,CAAA,EAGN,kBAAC,IAAD;OACE,WAAU;OACV,MAAA;OACA,WAAU;iBAHZ;QAKE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;SAC7B,CAAA;QAEL,IACC,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;WAAG,WAAU;qBAA8B;WAEvC,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAG,KAAc,EAAE;YAAE;YACtB,kBAAC,QAAD;aAAM,WAAU;uBAA4C;aAErD,CAAA;YACL;aACA,EAAA,CAAA;UAEL,KAAmB,QAAQ,KAAkB,OAC5C,kBAAC,OAAD,EAAA,UAAA;WACE,kBAAC,KAAD;YAAG,WAAU;sBAAb;aAA2C;aACzB;aAAgB;aAC9B;;WACJ,kBAAC,KAAD;YAAG,WAAU;sBAAb;aACG,EAAG,EAAe;aAAE;aACrB,kBAAC,QAAD;cAAM,WAAU;wBAA4C;cAErD,CAAA;aACL;;WACJ,kBAAC,KAAD;YAAG,WAAU;sBAAb;aACG,EAAa,KAAc,EAAE;aAAC;aAAe;aAC7C,EAAa,EAAe;aAAC;aAC5B;;WACA,EAAA,CAAA,GAEN,kBAAC,KAAD;WAAG,WAAU;qBACV,IACG,sDACA;WACF,CAAA;UAGN,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,KAAD;YAAG,WAAU;sBAAb,CACE,kBAAC,GAAD,EAAU,WAAU,wCAAyC,CAAA,EAC7D,kBAAC,QAAD,EAAA,UAAM,sDAEC,CAAA,CACL;;WACA,CAAA;UACF;aAEN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBAA4C;UAErD,CAAA,EACL,kBAAC,KAAD;UAAG,WAAU;oBAAmC;UAI5C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,WAAU;oBAHZ,CAIC,uBAEC,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CAC3B;YACL;;QAGR,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACE,kBAAC,GAAD,EAAM,WAAU,+BAAgC,CAAA,EAChD,kBAAC,QAAD,EAAA,UAAM,8FAGC,CAAA,CACL;;QACM;SACR;;KAGL,EAAa,SAAS,KACrB,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;gBAEZ,kBAAC,IAAD;OACE,MAAM;OACN,aAAY;OACZ,aAAa,EAAmB,GAAmB;OACnD,cAAc,MAAM,EAAmB,EAAE;OACzC,YAAW;OACX,WAAU;OACV,CAAA;MACQ,CAAA;KAIb,KAAgB,EAAa,WAAW,KACvC,kBAAC,IAAD;MACE,OAAM;MACN,aAAY;MACZ,CAAA;KAEA;;GAIR,kBAAC,IAAD;IACE,MAAM;IACN,eAAe,EAAa,GAAM;IAClC,OAAM;IACN,aAAY;IACZ,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAa,GAAM;KAClC,WAAU;eACX;KAEQ,CAAA,EACT,kBAAC,UAAD;KACE,MAAK;KACL,MAAK;KACL,UAAU;KACV,WAAU;eAET,IAAS,YAAY;KACf,CAAA,CACR,EAAA,CAAA;cAGL,kBAAC,QAAD;KACE,IAAG;KACH,UAAU;KACV,WAAU;eAHZ;MAKE,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;kBAFZ,CAIE,kBAAC,IAAD,EAAc,WAAU,+BAAgC,CAAA,EAAA,kBAElD;;OACR,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,OAAO;QACP,KAAK;QACL,WAAW,MAAM,EAAkB,EAAE,OAAO,MAAM;QAClD,WAAU;QACV,CAAA;OACF,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAE9C,CAAA;OACA,EAAA,CAAA;MAEN,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;kBAFZ;SAGC;SACiB;SAChB,kBAAC,QAAD;UAAM,WAAU;oBAAkC;UAE3C,CAAA;SACD;;OACR,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,WAAU;QACV,KAAK;QACL,KAAK;QACL,MAAM;QACN,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAmB,EAAE,OAAO,MAAM;QACnD,WAAU;QACV,CAAA;OACF,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAG9C,CAAA;OACA,EAAA,CAAA;MAEN,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,SAAD;QACE,MAAK;QACL,SAAS;QACT,WAAW,MAAM,EAAsB,EAAE,OAAO,QAAQ;QACxD,WAAU;QACV,CAAA,EACF,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA4C,wBAE1C,kBAAC,QAAD;SAAM,WAAU;mBAA2C;SAEpD,CAAA,CACF;UACD;;MAEP,KACC,kBAAC,KAAD;OAAG,WAAU;iBACV;OACC,CAAA;MAED;;IACD,CAAA;GAER,kBAAC,IAAD;IAAwB;IAAQ,WAAW;IAAgB,CAAA;GAChD"}
1
+ {"version":3,"file":"FiniteLifePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/FiniteLifePage.tsx"],"sourcesContent":["import { useMemo, useState, type FormEvent } from \"react\";\nimport {\n Hourglass,\n Sparkles,\n SlidersHorizontal,\n CalendarDays,\n Lock,\n ArrowRight,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n ChartCard,\n EmptyState,\n Modal,\n DonutChart,\n WeeksGrid,\n LoadingSkeleton,\n ToastContainer,\n useToast,\n type DonutSlice,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useMyLifeInWeeksQuery,\n useMyLifeProfileQuery,\n useUpdateMyLifeProfileMutation,\n type MyLifeInWeeksQuery,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel, formatSecondsHuman } from \"./domains\";\n\n/** One life-domain roll-up row, typed from the generated query result. */\ntype LifeDomainRollup =\n MyLifeInWeeksQuery[\"myLifeInWeeks\"][\"domainRollups\"][number];\n\n/** Grouped integer (e.g. 1,742). */\nfunction nf(n: number): string {\n return Math.max(0, Math.round(n)).toLocaleString();\n}\n\n/** Whole-week count → years with one decimal (e.g. 33.5). */\nfunction weeksToYears(weeks: number): string {\n return (Math.round((weeks / 52) * 10) / 10).toFixed(1);\n}\n\n/** ISO datetime → the `YYYY-MM-DD` a native date input expects. */\nfunction isoToDateInput(iso: string | null | undefined): string {\n if (!iso) return \"\";\n return iso.slice(0, 10);\n}\n\nconst TODAY_DATE_INPUT = new Date().toISOString().slice(0, 10);\n\n/**\n * FiniteLifePage — the finite-life \"your life in weeks\" memento.\n *\n * A calm, opt-in, entirely private reflection: each cell is one week of a life,\n * anchored to the gentle north star \"What if today was your last?\". Everything\n * it draws comes from the viewer's own (optional) birth date and life\n * expectancy — never a guessed age or an invented mortality date. It is\n * self-scoped (identity travels in the gateway headers, so the queries take no\n * user argument) and honest by construction: with no birth date it still\n * renders an open horizon and simply invites you to add one. It respects the\n * `hideRemainingWeeks` choice so no one is forced to stare down a countdown.\n */\nexport function FiniteLifePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: weeksData,\n loading: weeksLoading,\n error: weeksError,\n refetch: refetchWeeks,\n } = useMyLifeInWeeksQuery({ skip: !workspaceId });\n\n const {\n data: profileData,\n loading: profileLoading,\n error: profileError,\n refetch: refetchProfile,\n } = useMyLifeProfileQuery({ skip: !workspaceId });\n\n const [updateProfile, { loading: saving }] = useUpdateMyLifeProfileMutation();\n\n const weeks = weeksData?.myLifeInWeeks;\n const profile = profileData?.myLifeProfile;\n\n // ---- Setup modal (opt-in, dismissible) -----------------------------------\n const [setupOpen, setSetupOpen] = useState(false);\n const [birthDateInput, setBirthDateInput] = useState(\"\");\n const [expectancyInput, setExpectancyInput] = useState(\"\");\n const [hideRemainingInput, setHideRemainingInput] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const openSetup = () => {\n setBirthDateInput(isoToDateInput(profile?.birthDate ?? weeks?.birthDate));\n setExpectancyInput(\n profile?.lifeExpectancyYears != null\n ? String(profile.lifeExpectancyYears)\n : weeks?.lifeExpectancyYears != null\n ? String(weeks.lifeExpectancyYears)\n : \"\",\n );\n setHideRemainingInput(\n profile?.hideRemainingWeeks ?? weeks?.hideRemainingWeeks ?? false,\n );\n setFormError(null);\n setSetupOpen(true);\n };\n\n const handleSave = async (e?: FormEvent) => {\n e?.preventDefault();\n setFormError(null);\n\n if (!birthDateInput) {\n setFormError(\"Please add your birth date to continue.\");\n return;\n }\n if (birthDateInput > TODAY_DATE_INPUT) {\n setFormError(\"Your birth date can't be in the future.\");\n return;\n }\n\n let expectancy: number | null = null;\n if (expectancyInput.trim() !== \"\") {\n const parsed = Number(expectancyInput);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 130) {\n setFormError(\n \"Life expectancy should be a whole number of years (1–130).\",\n );\n return;\n }\n expectancy = parsed;\n }\n\n try {\n await updateProfile({\n variables: {\n input: {\n birthDate: new Date(\n `${birthDateInput}T00:00:00.000Z`,\n ).toISOString(),\n lifeExpectancyYears: expectancy,\n hideRemainingWeeks: hideRemainingInput,\n finiteLifeOptIn: true,\n },\n },\n });\n await Promise.all([refetchWeeks(), refetchProfile()]);\n setSetupOpen(false);\n addToast({\n variant: \"success\",\n title: \"Saved — this is just for you\",\n description: \"You can change or turn this off anytime.\",\n });\n } catch {\n addToast({\n variant: \"error\",\n title: \"Couldn't save your settings\",\n description: \"Please try again in a moment.\",\n });\n }\n };\n\n // ---- Derived, honest values ----------------------------------------------\n const hasBirthDate = !!weeks?.birthDate;\n const hideRemaining =\n weeks?.hideRemainingWeeks ?? profile?.hideRemainingWeeks ?? false;\n\n const weeksLived = weeks?.weeksLived ?? null;\n const expectancyYears = weeks?.lifeExpectancyYears ?? null;\n // Only reveal the full mortal horizon when the user hasn't asked to hide it.\n const gridTotalWeeks = hideRemaining\n ? null\n : (weeks?.expectedTotalWeeks ?? null);\n const weeksRemaining =\n hideRemaining || weeks?.weeksRemaining == null\n ? null\n : weeks.weeksRemaining;\n\n // Reflection: where the recent (backend-defined) window actually went.\n const domainSlices: DonutSlice[] = useMemo(() => {\n const rollups: LifeDomainRollup[] = weeks?.domainRollups ?? [];\n return rollups\n .filter((r) => r.totalSeconds > 0)\n .map((r) => ({\n name: lifeDomainLabel(r.domain),\n value: r.totalSeconds,\n }));\n }, [weeks?.domainRollups]);\n const rollupTotalSeconds = domainSlices.reduce((sum, s) => sum + s.value, 0);\n\n const loading = weeksLoading || profileLoading;\n const hardError = (weeksError || profileError) && !weeks && !profile;\n\n const setupAction = (\n <button\n type=\"button\"\n onClick={openSetup}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle bg-bg-surface px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <SlidersHorizontal className=\"h-4 w-4\" />\n Life settings\n </button>\n );\n\n if (loading && !weeks && !profile) {\n return (\n <PageLayout\n title=\"Your Life in Weeks\"\n description=\"A calm, private view of the weeks you've lived — and the ones still ahead.\"\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Your Life in Weeks\"\n description=\"A calm, private view of the weeks you've lived — and the ones still ahead.\"\n purpose=\"Each square is a single week of your life. It's a gentle memento — not a countdown — meant only for you, to help this week feel a little more intentional.\"\n actions={setupAction}\n >\n {hardError ? (\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n We couldn't load your life view right now. Please try again in a\n moment.\n </div>\n ) : (\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* The grid — the memento itself */}\n <div className=\"lg:col-span-2\">\n <ChartCard\n title=\"Your life in weeks\"\n description={\n hasBirthDate\n ? \"Each row is a year; each square, a week you have lived or have yet to live.\"\n : \"Add your birth date to fill in the weeks you've already lived.\"\n }\n >\n <WeeksGrid\n weeksLived={weeksLived}\n totalWeeks={gridTotalWeeks}\n ariaLabel={\n hasBirthDate\n ? `Your life in weeks: ${nf(weeksLived ?? 0)} weeks lived so far`\n : \"Your life in weeks — birth date not set yet\"\n }\n />\n <div className=\"mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] bg-action-primary-bg\" />\n Weeks lived\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] border-2 border-action-primary-bg bg-bg-surface\" />\n This week\n </span>\n <span className=\"flex items-center gap-1.5\">\n <span className=\"h-3 w-3 rounded-[3px] border border-border-seam\" />\n Ahead\n </span>\n </div>\n </ChartCard>\n </div>\n\n {/* The calm side card */}\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"flex flex-col p-5 sm:p-6\"\n >\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/10 text-primary\">\n <Hourglass className=\"h-5 w-5\" />\n </div>\n\n {hasBirthDate ? (\n <div className=\"mt-4 space-y-5\">\n <div>\n <p className=\"text-sm text-text-secondary\">\n You have lived\n </p>\n <p className=\"mt-0.5 text-3xl font-bold tabular-nums text-text-primary\">\n {nf(weeksLived ?? 0)}{\" \"}\n <span className=\"text-xl font-semibold text-text-secondary\">\n weeks\n </span>\n </p>\n </div>\n\n {expectancyYears != null && weeksRemaining != null ? (\n <div>\n <p className=\"text-sm text-text-secondary\">\n If you live to {expectancyYears}, about\n </p>\n <p className=\"mt-0.5 text-2xl font-bold tabular-nums text-text-primary\">\n {nf(weeksRemaining)}{\" \"}\n <span className=\"text-lg font-semibold text-text-secondary\">\n remain\n </span>\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {weeksToYears(weeksLived ?? 0)} years lived ·{\" \"}\n {weeksToYears(weeksRemaining)} years ahead\n </p>\n </div>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n {hideRemaining\n ? \"You've chosen to see only the weeks you've lived.\"\n : \"Add a life expectancy in settings to gently sketch the weeks ahead.\"}\n </p>\n )}\n\n <div className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3\">\n <p className=\"flex items-start gap-2 text-sm text-text-primary\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>\n What if today was your last? Make this week count.\n </span>\n </p>\n </div>\n </div>\n ) : (\n <div className=\"mt-4 space-y-4\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n See your life in weeks\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Add your birth date and we'll gently fill in the weeks\n you've lived so far — a quiet reminder that time is\n finite, and this week is yours to spend.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={openSetup}\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\"\n >\n Add your birth date\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n\n <p className=\"mt-6 flex items-start gap-1.5 text-xs text-text-secondary\">\n <Lock className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" />\n <span>\n Only you can see this. Your birth date is never shared, and\n you can turn this off anytime.\n </span>\n </p>\n </GlassCard>\n </div>\n\n {/* Gentle reflection — only when there's real recent data to show */}\n {domainSlices.length > 0 && (\n <ChartCard\n title=\"Where your recent weeks went\"\n description=\"How your logged time settled across life's domains lately — a mirror, not a scorecard.\"\n >\n <DonutChart\n data={domainSlices}\n centerLabel=\"logged lately\"\n centerValue={formatSecondsHuman(rollupTotalSeconds)}\n valueFormat={(v) => formatSecondsHuman(v)}\n emptyLabel=\"Nothing logged in this window yet.\"\n ariaLabel=\"Recent time by life domain\"\n />\n </ChartCard>\n )}\n\n {/* Honest first-run nudge when nothing has been logged either */}\n {hasBirthDate && domainSlices.length === 0 && (\n <EmptyState\n title=\"Your weeks are yours to shape\"\n description=\"As you capture how you spend your time, a quiet reflection of your recent weeks will appear here — only for you.\"\n />\n )}\n </div>\n )}\n\n {/* Opt-in setup */}\n <Modal\n open={setupOpen}\n onClose={() => setSetupOpen(false)}\n title=\"Your life in weeks\"\n description=\"This is private and entirely optional. Add only what feels comfortable.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setSetupOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n form=\"finite-life-setup\"\n disabled={saving}\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 {saving ? \"Saving…\" : \"Save\"}\n </button>\n </>\n }\n >\n <form\n id=\"finite-life-setup\"\n onSubmit={handleSave}\n className=\"space-y-5\"\n >\n <div>\n <label\n htmlFor=\"finite-life-birthdate\"\n className=\"flex items-center gap-1.5 text-sm font-medium text-text-primary\"\n >\n <CalendarDays className=\"h-4 w-4 text-text-secondary\" />\n Your birth date\n </label>\n <input\n id=\"finite-life-birthdate\"\n type=\"date\"\n value={birthDateInput}\n max={TODAY_DATE_INPUT}\n onChange={(e) => setBirthDateInput(e.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-subtle bg-bg-sunken px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n Used only to count the weeks you've lived. It stays with you.\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"finite-life-expectancy\"\n className=\"text-sm font-medium text-text-primary\"\n >\n Life expectancy{\" \"}\n <span className=\"font-normal text-text-secondary\">\n (optional)\n </span>\n </label>\n <input\n id=\"finite-life-expectancy\"\n type=\"number\"\n inputMode=\"numeric\"\n min={1}\n max={130}\n step={1}\n placeholder=\"e.g. 90\"\n value={expectancyInput}\n onChange={(e) => setExpectancyInput(e.target.value)}\n className=\"mt-2 w-full rounded-lg border border-border-subtle bg-bg-sunken px-3 py-2 text-sm text-text-primary\"\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n A gentle horizon for the weeks ahead — never a deadline. Leave it\n blank to simply see the weeks you've lived.\n </p>\n </div>\n\n <label className=\"flex items-start gap-2.5 rounded-lg border border-border-subtle bg-bg-surface/40 p-3\">\n <input\n type=\"checkbox\"\n checked={hideRemainingInput}\n onChange={(e) => setHideRemainingInput(e.target.checked)}\n className=\"mt-0.5 h-4 w-4 shrink-0 rounded border-border-subtle\"\n />\n <span className=\"text-sm text-text-primary\">\n Hide the weeks ahead\n <span className=\"mt-0.5 block text-xs text-text-secondary\">\n Show only the weeks you've lived — no horizon, no countdown.\n </span>\n </span>\n </label>\n\n {formError && (\n <p className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </p>\n )}\n </form>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LifeBalancePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/LifeBalancePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { Scale, Target, Clock, Gauge, Sparkles, Check } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n EmptyState,\n StatusBadge,\n Modal,\n LoadingSkeleton,\n BalanceWheel,\n ProgressMeter,\n ToastContainer,\n useToast,\n type BalanceWheelDatum,\n type ToastVariant,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n useMyLifeBalanceQuery,\n useMyBalanceTargetsQuery,\n useSetBalanceTargetMutation,\n type MyLifeBalanceQuery,\n type BalanceTargetFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n ALL_LIFE_DOMAINS,\n lifeDomainLabel,\n secondsToHours,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n} from \"./domains\";\n\ntype DomainBalance = MyLifeBalanceQuery[\"myLifeBalance\"][\"byDomain\"][number];\n\ninterface RangeOption {\n label: string;\n days: number;\n}\n\nconst RANGE_OPTIONS: RangeOption[] = [\n { label: \"7 days\", days: 7 },\n { label: \"30 days\", days: 30 },\n];\n\n/** Percentage points of a 0..1 share, rounded to a whole number. */\nfunction sharePct(share: number): number {\n return Math.round(share * 100);\n}\n\n/** Fixed one-decimal hours from seconds, e.g. \"40.0\". */\nfunction hoursLabel(seconds: number): string {\n return secondsToHours(seconds).toFixed(1);\n}\n\n// ---------------------------------------------------------------------------\n// Set-targets modal — declares desired hours/week per life domain.\n// ---------------------------------------------------------------------------\ninterface SetTargetsModalProps {\n open: boolean;\n onClose: () => void;\n /** Current desired hours/week keyed by domain (from myBalanceTargets). */\n currentTargets: Partial<Record<TimeCampusLifeDomain, number>>;\n /** Refetch the balance + targets queries after a successful save. */\n onSaved: () => void;\n notify: (variant: ToastVariant, title: string, description?: string) => void;\n}\n\nconst HOURS_IN_WEEK = 168;\n\nfunction SetTargetsModal({\n open,\n onClose,\n currentTargets,\n onSaved,\n notify,\n}: SetTargetsModalProps) {\n const [setBalanceTarget, { loading: saving }] = useSetBalanceTargetMutation();\n const [draft, setDraft] = useState<Record<string, string>>({});\n\n // Seed the draft from the live targets each time the modal opens.\n useEffect(() => {\n if (!open) return;\n const next: Record<string, string> = {};\n for (const domain of ALL_LIFE_DOMAINS) {\n const value = currentTargets[domain];\n next[domain] = typeof value === \"number\" ? String(value) : \"\";\n }\n setDraft(next);\n }, [open, currentTargets]);\n\n const allocated = useMemo(() => {\n let sum = 0;\n for (const domain of ALL_LIFE_DOMAINS) {\n const raw = draft[domain];\n const value = raw ? Number(raw) : NaN;\n if (Number.isFinite(value) && value > 0) sum += value;\n }\n return Math.round(sum * 10) / 10;\n }, [draft]);\n\n const overAllocated = allocated > HOURS_IN_WEEK;\n\n const handleSave = async () => {\n const pending: Promise<unknown>[] = [];\n for (const domain of ALL_LIFE_DOMAINS) {\n const raw = draft[domain];\n if (raw === undefined || raw.trim() === \"\") continue;\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0) continue;\n const original = currentTargets[domain];\n if (original !== undefined && Math.abs(original - value) < 1e-9) continue;\n pending.push(\n setBalanceTarget({\n variables: { domain, desiredHoursPerWeek: value },\n }),\n );\n }\n\n if (pending.length === 0) {\n onClose();\n return;\n }\n\n try {\n await Promise.all(pending);\n notify(\n \"success\",\n \"Targets saved\",\n `Updated ${pending.length} ${pending.length === 1 ? \"domain\" : \"domains\"}.`,\n );\n onSaved();\n onClose();\n } catch {\n notify(\n \"error\",\n \"Couldn't save targets\",\n \"Something went wrong. Please try again.\",\n );\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title=\"Set your desired balance\"\n description=\"Declare the hours per week you want to spend in each life domain. The wheel compares this to where your time actually goes.\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={onClose}\n disabled={saving}\n className=\"rounded-lg border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary disabled:opacity-60\"\n >\n Cancel\n </button>\n <button\n onClick={handleSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-60\"\n >\n <Check className=\"h-4 w-4\" />\n {saving ? \"Saving…\" : \"Save targets\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-2\">\n {ALL_LIFE_DOMAINS.map((domain) => (\n <div\n key={domain}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2\"\n >\n <label\n htmlFor={`target-${domain}`}\n className=\"min-w-0 flex-1 truncate text-sm text-text-primary\"\n >\n {lifeDomainLabel(domain)}\n </label>\n <div className=\"flex shrink-0 items-center gap-2\">\n <input\n id={`target-${domain}`}\n type=\"number\"\n min={0}\n step={0.5}\n inputMode=\"decimal\"\n value={draft[domain] ?? \"\"}\n onChange={(e) =>\n setDraft((prev) => ({ ...prev, [domain]: e.target.value }))\n }\n placeholder=\"0\"\n className=\"w-24 rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-right text-sm tabular-nums text-text-primary outline-none transition-colors focus:border-action-primary-bg\"\n />\n <span className=\"w-12 text-xs text-text-secondary\">h / wk</span>\n </div>\n </div>\n ))}\n </div>\n\n <p\n className={cn(\n \"mt-4 text-xs tabular-nums\",\n overAllocated ? \"text-status-warning-text\" : \"text-text-secondary\",\n )}\n >\n {allocated.toFixed(1)} of {HOURS_IN_WEEK} hours a week allocated\n {overAllocated\n ? \" — that's more than a week holds; consider trimming.\"\n : \".\"}\n </p>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Per-domain gap badge (over / under / on-target vs desired share).\n// ---------------------------------------------------------------------------\nfunction GapBadge({ gap }: { gap: number }) {\n const pp = Math.round(Math.abs(gap) * 100);\n if (pp < 2) {\n return <StatusBadge variant=\"success\" label=\"On target\" />;\n }\n if (gap > 0) {\n return <StatusBadge variant=\"warning\" label={`+${pp}pp over`} />;\n }\n return <StatusBadge variant=\"info\" label={`${pp}pp under`} />;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nexport function LifeBalancePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const notify = (variant: ToastVariant, title: string, description?: string) =>\n addToast({ variant, title, description });\n\n const [rangeDays, setRangeDays] = useState<number>(30);\n const [targetsOpen, setTargetsOpen] = useState(false);\n\n const range = useMemo(\n () => ({ from: isoDaysAgo(rangeDays), to: isoNow() }),\n [rangeDays],\n );\n\n const {\n data: balanceData,\n loading: balanceLoading,\n error: balanceError,\n refetch: refetchBalance,\n } = useMyLifeBalanceQuery({\n variables: { from: range.from, to: range.to },\n skip: !workspaceId,\n });\n\n const { data: targetsData, refetch: refetchTargets } =\n useMyBalanceTargetsQuery({ skip: !workspaceId });\n\n const balance = balanceData?.myLifeBalance;\n const byDomain = useMemo<DomainBalance[]>(\n () => balance?.byDomain ?? [],\n [balance],\n );\n const hasTargets = balance?.hasTargets ?? false;\n\n // Map current targets (desired hours/week) by domain for the modal + list.\n const targetHoursByDomain = useMemo<\n Partial<Record<TimeCampusLifeDomain, number>>\n >(() => {\n const map: Partial<Record<TimeCampusLifeDomain, number>> = {};\n const targets: BalanceTargetFieldsFragment[] =\n targetsData?.myBalanceTargets ?? [];\n for (const target of targets) {\n map[target.domain] = target.desiredHoursPerWeek;\n }\n return map;\n }, [targetsData]);\n const targetsCount = targetsData?.myBalanceTargets.length ?? 0;\n\n // Canonical display order for the wheel.\n const orderIndex = useMemo(() => {\n const map = new Map<TimeCampusLifeDomain, number>();\n ALL_LIFE_DOMAINS.forEach((domain, i) => map.set(domain, i));\n return map;\n }, []);\n\n const wheelData = useMemo<BalanceWheelDatum[]>(() => {\n return [...byDomain]\n .sort(\n (a, b) =>\n (orderIndex.get(a.domain) ?? 99) - (orderIndex.get(b.domain) ?? 99),\n )\n .map((d) => ({\n domain: d.domain,\n label: lifeDomainLabel(d.domain),\n actualShare: d.actualShare,\n desiredShare: hasTargets ? d.desiredShare : undefined,\n }));\n }, [byDomain, hasTargets, orderIndex]);\n\n // Ranked list (most-spent domains first) — only domains with real signal.\n const listRows = useMemo<DomainBalance[]>(() => {\n return [...byDomain]\n .filter(\n (d) =>\n d.actualSeconds > 0 ||\n (hasTargets && d.desiredShare > 0) ||\n targetHoursByDomain[d.domain] !== undefined,\n )\n .sort((a, b) => b.actualSeconds - a.actualSeconds);\n }, [byDomain, hasTargets, targetHoursByDomain]);\n\n const coveredSeconds = balance?.coveredSeconds ?? 0;\n const unaccountedSeconds = balance?.unaccountedSeconds ?? 0;\n const totalSeconds = coveredSeconds + unaccountedSeconds;\n const coveragePct =\n totalSeconds > 0 ? Math.round((coveredSeconds / totalSeconds) * 100) : 0;\n const activeDomains = byDomain.filter((d) => d.actualSeconds > 0).length;\n const hasData = coveredSeconds > 0 && byDomain.length > 0;\n\n const rangeLabel =\n RANGE_OPTIONS.find((o) => o.days === rangeDays)?.label ??\n `${rangeDays} days`;\n\n const refetchAll = () => {\n void refetchBalance();\n void refetchTargets();\n };\n\n // -- Header actions (range selector + set-targets) -------------------------\n const actions = (\n <>\n <div className=\"flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\">\n {RANGE_OPTIONS.map((option) => (\n <button\n key={option.days}\n onClick={() => setRangeDays(option.days)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n rangeDays === option.days\n ? \"bg-bg-surface text-text-primary shadow-sm\"\n : \"text-text-secondary hover:text-text-primary\",\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Target className=\"h-4 w-4\" />\n Set targets\n </button>\n </>\n );\n\n const pageProps = {\n title: \"Life Balance\",\n description: \"How your finite time actually splits across life\",\n purpose:\n \"Your week is a fixed budget of hours. This wheel shows how that budget actually splits across the domains of your life, overlaid with the balance you said you want — so the gap between the two is impossible to ignore.\",\n actions,\n } as const;\n\n // -- Loading --------------------------------------------------------------\n if (balanceLoading && !balance) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"space-y-6\">\n <LoadingSkeleton variant=\"card\" count={4} />\n <LoadingSkeleton variant=\"card\" count={2} />\n </div>\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Error ----------------------------------------------------------------\n if (balanceError) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n We couldn't load your life balance right now. Please try again in a\n moment.\n </div>\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Empty (no tracked time yet) ------------------------------------------\n if (!hasData) {\n return (\n <PageLayout {...pageProps}>\n <EmptyState\n title=\"No life data yet\"\n description=\"Once you start tracking time across your day, your balance wheel will show how your hours split across each life domain. In the meantime, you can declare the balance you're aiming for.\"\n action={\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Target className=\"h-4 w-4\" />\n Declare what you value\n </button>\n }\n />\n <SetTargetsModal\n open={targetsOpen}\n onClose={() => setTargetsOpen(false)}\n currentTargets={targetHoursByDomain}\n onSaved={refetchAll}\n notify={notify}\n />\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Main -----------------------------------------------------------------\n return (\n <PageLayout {...pageProps}>\n <div className=\"space-y-6\">\n {/* Stat row */}\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={formatSecondsHuman(coveredSeconds)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`Last ${rangeLabel}`}\n />\n <StatCard\n title=\"Unaccounted\"\n value={formatSecondsHuman(unaccountedSeconds)}\n icon={<Gauge className=\"h-5 w-5\" />}\n description={`${coveragePct}% of time is tracked`}\n />\n <StatCard\n title=\"Active domains\"\n value={String(activeDomains)}\n icon={<Scale className=\"h-5 w-5\" />}\n description={`of ${ALL_LIFE_DOMAINS.length} life domains`}\n />\n <StatCard\n title=\"Targets set\"\n value={`${targetsCount} / ${ALL_LIFE_DOMAINS.length}`}\n icon={<Target className=\"h-5 w-5\" />}\n description={\n hasTargets ? \"Desired balance declared\" : \"Not set yet\"\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* The wheel */}\n <ChartCard\n className=\"lg:col-span-2\"\n title=\"Life Balance Wheel\"\n description={\n hasTargets\n ? \"Solid = where your time actually goes · dashed = the balance you want\"\n : \"Where your time actually goes across life domains\"\n }\n >\n <BalanceWheel\n data={wheelData}\n height={360}\n emptyLabel=\"No tracked time in this range yet.\"\n ariaLabel=\"Life balance wheel: actual versus desired share of time by domain\"\n />\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Time tracked</span>\n <span className=\"tabular-nums text-text-primary\">\n {formatSecondsHuman(coveredSeconds)} of{\" \"}\n {formatSecondsHuman(totalSeconds)} ({coveragePct}%)\n </span>\n </div>\n <ProgressMeter\n className=\"mt-2\"\n value={coveragePct}\n max={100}\n higherIsWorse={false}\n showValue={false}\n label=\"Coverage\"\n />\n </div>\n </ChartCard>\n\n {/* Headline + per-domain list */}\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-xl font-bold leading-tight text-text-primary\">\n Your week vs the life you want\n </h2>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Per-domain — hours tracked vs your desired share\n </p>\n\n {!hasTargets && (\n <div className=\"mt-3 flex items-start gap-2 rounded-lg border border-border-subtle bg-action-primary-bg/10 p-3 text-xs text-text-secondary\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>\n You haven't declared a desired balance yet, so the wheel is\n descriptive only.{\" \"}\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"font-medium text-primary underline-offset-2 hover:underline\"\n >\n Set your targets\n </button>{\" \"}\n to see the gap.\n </span>\n </div>\n )}\n\n <ul className=\"mt-4 max-h-[30rem] space-y-2 overflow-y-auto pr-1\">\n {listRows.map((d) => {\n const target = targetHoursByDomain[d.domain];\n return (\n <li\n key={d.domain}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface/40 px-3 py-2.5\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {lifeDomainLabel(d.domain)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {sharePct(d.actualShare)}% of tracked\n {hasTargets\n ? ` · ${sharePct(d.desiredShare)}% desired`\n : \"\"}\n {typeof target === \"number\"\n ? ` · ${target}h/wk target`\n : \"\"}\n </p>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {hoursLabel(d.actualSeconds)}h\n </p>\n </div>\n {hasTargets && (\n <div className=\"shrink-0\">\n <GapBadge gap={d.gap} />\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </GlassCard>\n </div>\n </div>\n\n <SetTargetsModal\n open={targetsOpen}\n onClose={() => setTargetsOpen(false)}\n currentTargets={targetHoursByDomain}\n onSaved={refetchAll}\n notify={notify}\n />\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LifeBalancePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/LifeBalancePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { Scale, Target, Clock, Gauge, Sparkles, Check } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n EmptyState,\n StatusBadge,\n Modal,\n LoadingSkeleton,\n BalanceWheel,\n ProgressMeter,\n ToastContainer,\n useToast,\n type BalanceWheelDatum,\n type ToastVariant,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n useMyLifeBalanceQuery,\n useMyBalanceTargetsQuery,\n useSetBalanceTargetMutation,\n type MyLifeBalanceQuery,\n type BalanceTargetFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n ALL_LIFE_DOMAINS,\n lifeDomainLabel,\n secondsToHours,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n} from \"./domains\";\n\ntype DomainBalance = MyLifeBalanceQuery[\"myLifeBalance\"][\"byDomain\"][number];\n\ninterface RangeOption {\n label: string;\n days: number;\n}\n\nconst RANGE_OPTIONS: RangeOption[] = [\n { label: \"7 days\", days: 7 },\n { label: \"30 days\", days: 30 },\n];\n\n/** Percentage points of a 0..1 share, rounded to a whole number. */\nfunction sharePct(share: number): number {\n return Math.round(share * 100);\n}\n\n/** Fixed one-decimal hours from seconds, e.g. \"40.0\". */\nfunction hoursLabel(seconds: number): string {\n return secondsToHours(seconds).toFixed(1);\n}\n\n// ---------------------------------------------------------------------------\n// Set-targets modal — declares desired hours/week per life domain.\n// ---------------------------------------------------------------------------\ninterface SetTargetsModalProps {\n open: boolean;\n onClose: () => void;\n /** Current desired hours/week keyed by domain (from myBalanceTargets). */\n currentTargets: Partial<Record<TimeCampusLifeDomain, number>>;\n /** Refetch the balance + targets queries after a successful save. */\n onSaved: () => void;\n notify: (variant: ToastVariant, title: string, description?: string) => void;\n}\n\nconst HOURS_IN_WEEK = 168;\n\nfunction SetTargetsModal({\n open,\n onClose,\n currentTargets,\n onSaved,\n notify,\n}: SetTargetsModalProps) {\n const [setBalanceTarget, { loading: saving }] = useSetBalanceTargetMutation();\n const [draft, setDraft] = useState<Record<string, string>>({});\n\n // Seed the draft from the live targets each time the modal opens.\n useEffect(() => {\n if (!open) return;\n const next: Record<string, string> = {};\n for (const domain of ALL_LIFE_DOMAINS) {\n const value = currentTargets[domain];\n next[domain] = typeof value === \"number\" ? String(value) : \"\";\n }\n setDraft(next);\n }, [open, currentTargets]);\n\n const allocated = useMemo(() => {\n let sum = 0;\n for (const domain of ALL_LIFE_DOMAINS) {\n const raw = draft[domain];\n const value = raw ? Number(raw) : NaN;\n if (Number.isFinite(value) && value > 0) sum += value;\n }\n return Math.round(sum * 10) / 10;\n }, [draft]);\n\n const overAllocated = allocated > HOURS_IN_WEEK;\n\n const handleSave = async () => {\n const pending: Promise<unknown>[] = [];\n for (const domain of ALL_LIFE_DOMAINS) {\n const raw = draft[domain];\n if (raw === undefined || raw.trim() === \"\") continue;\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0) continue;\n const original = currentTargets[domain];\n if (original !== undefined && Math.abs(original - value) < 1e-9) continue;\n pending.push(\n setBalanceTarget({\n variables: { domain, desiredHoursPerWeek: value },\n }),\n );\n }\n\n if (pending.length === 0) {\n onClose();\n return;\n }\n\n try {\n await Promise.all(pending);\n notify(\n \"success\",\n \"Targets saved\",\n `Updated ${pending.length} ${pending.length === 1 ? \"domain\" : \"domains\"}.`,\n );\n onSaved();\n onClose();\n } catch {\n notify(\n \"error\",\n \"Couldn't save targets\",\n \"Something went wrong. Please try again.\",\n );\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title=\"Set your desired balance\"\n description=\"Declare the hours per week you want to spend in each life domain. The wheel compares this to where your time actually goes.\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={onClose}\n disabled={saving}\n className=\"rounded-lg border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary disabled:opacity-60\"\n >\n Cancel\n </button>\n <button\n onClick={handleSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-60\"\n >\n <Check className=\"h-4 w-4\" />\n {saving ? \"Saving…\" : \"Save targets\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-2\">\n {ALL_LIFE_DOMAINS.map((domain) => (\n <div\n key={domain}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2\"\n >\n <label\n htmlFor={`target-${domain}`}\n className=\"min-w-0 flex-1 truncate text-sm text-text-primary\"\n >\n {lifeDomainLabel(domain)}\n </label>\n <div className=\"flex shrink-0 items-center gap-2\">\n <input\n id={`target-${domain}`}\n type=\"number\"\n min={0}\n step={0.5}\n inputMode=\"decimal\"\n value={draft[domain] ?? \"\"}\n onChange={(e) =>\n setDraft((prev) => ({ ...prev, [domain]: e.target.value }))\n }\n placeholder=\"0\"\n className=\"w-24 rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-right text-sm tabular-nums text-text-primary outline-none transition-colors focus:border-action-primary-bg\"\n />\n <span className=\"w-12 text-xs text-text-secondary\">h / wk</span>\n </div>\n </div>\n ))}\n </div>\n\n <p\n className={cn(\n \"mt-4 text-xs tabular-nums\",\n overAllocated ? \"text-status-warning-text\" : \"text-text-secondary\",\n )}\n >\n {allocated.toFixed(1)} of {HOURS_IN_WEEK} hours a week allocated\n {overAllocated\n ? \" — that's more than a week holds; consider trimming.\"\n : \".\"}\n </p>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Per-domain gap badge (over / under / on-target vs desired share).\n// ---------------------------------------------------------------------------\nfunction GapBadge({ gap }: { gap: number }) {\n const pp = Math.round(Math.abs(gap) * 100);\n if (pp < 2) {\n return <StatusBadge variant=\"success\" label=\"On target\" />;\n }\n if (gap > 0) {\n return <StatusBadge variant=\"warning\" label={`+${pp}pp over`} />;\n }\n return <StatusBadge variant=\"info\" label={`${pp}pp under`} />;\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nexport function LifeBalancePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const notify = (variant: ToastVariant, title: string, description?: string) =>\n addToast({ variant, title, description });\n\n const [rangeDays, setRangeDays] = useState<number>(30);\n const [targetsOpen, setTargetsOpen] = useState(false);\n\n const range = useMemo(\n () => ({ from: isoDaysAgo(rangeDays), to: isoNow() }),\n [rangeDays],\n );\n\n const {\n data: balanceData,\n loading: balanceLoading,\n error: balanceError,\n refetch: refetchBalance,\n } = useMyLifeBalanceQuery({\n variables: { from: range.from, to: range.to },\n skip: !workspaceId,\n });\n\n const { data: targetsData, refetch: refetchTargets } =\n useMyBalanceTargetsQuery({ skip: !workspaceId });\n\n const balance = balanceData?.myLifeBalance;\n const byDomain = useMemo<DomainBalance[]>(\n () => balance?.byDomain ?? [],\n [balance],\n );\n const hasTargets = balance?.hasTargets ?? false;\n\n // Map current targets (desired hours/week) by domain for the modal + list.\n const targetHoursByDomain = useMemo<\n Partial<Record<TimeCampusLifeDomain, number>>\n >(() => {\n const map: Partial<Record<TimeCampusLifeDomain, number>> = {};\n const targets: BalanceTargetFieldsFragment[] =\n targetsData?.myBalanceTargets ?? [];\n for (const target of targets) {\n map[target.domain] = target.desiredHoursPerWeek;\n }\n return map;\n }, [targetsData]);\n const targetsCount = targetsData?.myBalanceTargets.length ?? 0;\n\n // Canonical display order for the wheel.\n const orderIndex = useMemo(() => {\n const map = new Map<TimeCampusLifeDomain, number>();\n ALL_LIFE_DOMAINS.forEach((domain, i) => map.set(domain, i));\n return map;\n }, []);\n\n const wheelData = useMemo<BalanceWheelDatum[]>(() => {\n return [...byDomain]\n .sort(\n (a, b) =>\n (orderIndex.get(a.domain) ?? 99) - (orderIndex.get(b.domain) ?? 99),\n )\n .map((d) => ({\n domain: d.domain,\n label: lifeDomainLabel(d.domain),\n actualShare: d.actualShare,\n desiredShare: hasTargets ? d.desiredShare : undefined,\n }));\n }, [byDomain, hasTargets, orderIndex]);\n\n // Ranked list (most-spent domains first) — only domains with real signal.\n const listRows = useMemo<DomainBalance[]>(() => {\n return [...byDomain]\n .filter(\n (d) =>\n d.actualSeconds > 0 ||\n (hasTargets && d.desiredShare > 0) ||\n targetHoursByDomain[d.domain] !== undefined,\n )\n .sort((a, b) => b.actualSeconds - a.actualSeconds);\n }, [byDomain, hasTargets, targetHoursByDomain]);\n\n const coveredSeconds = balance?.coveredSeconds ?? 0;\n const unaccountedSeconds = balance?.unaccountedSeconds ?? 0;\n const totalSeconds = coveredSeconds + unaccountedSeconds;\n const coveragePct =\n totalSeconds > 0 ? Math.round((coveredSeconds / totalSeconds) * 100) : 0;\n const activeDomains = byDomain.filter((d) => d.actualSeconds > 0).length;\n const hasData = coveredSeconds > 0 && byDomain.length > 0;\n\n const rangeLabel =\n RANGE_OPTIONS.find((o) => o.days === rangeDays)?.label ??\n `${rangeDays} days`;\n\n const refetchAll = () => {\n void refetchBalance();\n void refetchTargets();\n };\n\n // -- Header actions (range selector + set-targets) -------------------------\n const actions = (\n <>\n <div className=\"flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\">\n {RANGE_OPTIONS.map((option) => (\n <button\n key={option.days}\n onClick={() => setRangeDays(option.days)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n rangeDays === option.days\n ? \"bg-bg-surface text-text-primary shadow-sm\"\n : \"text-text-secondary hover:text-text-primary\",\n )}\n >\n {option.label}\n </button>\n ))}\n </div>\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Target className=\"h-4 w-4\" />\n Set targets\n </button>\n </>\n );\n\n const pageProps = {\n title: \"Life Balance\",\n description: \"How your finite time actually splits across life\",\n purpose:\n \"Your week is a fixed budget of hours. This wheel shows how that budget actually splits across the domains of your life, overlaid with the balance you said you want — so the gap between the two is impossible to ignore.\",\n actions,\n } as const;\n\n // -- Loading --------------------------------------------------------------\n if (balanceLoading && !balance) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"space-y-6\">\n <LoadingSkeleton variant=\"card\" count={4} />\n <LoadingSkeleton variant=\"card\" count={2} />\n </div>\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Error ----------------------------------------------------------------\n if (balanceError) {\n return (\n <PageLayout {...pageProps}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n We couldn't load your life balance right now. Please try again in a\n moment.\n </div>\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Empty (no tracked time yet) ------------------------------------------\n if (!hasData) {\n return (\n <PageLayout {...pageProps}>\n <EmptyState\n title=\"No life data yet\"\n description=\"Once you start tracking time across your day, your balance wheel will show how your hours split across each life domain. In the meantime, you can declare the balance you're aiming for.\"\n action={\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n <Target className=\"h-4 w-4\" />\n Declare what you value\n </button>\n }\n />\n <SetTargetsModal\n open={targetsOpen}\n onClose={() => setTargetsOpen(false)}\n currentTargets={targetHoursByDomain}\n onSaved={refetchAll}\n notify={notify}\n />\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n }\n\n // -- Main -----------------------------------------------------------------\n return (\n <PageLayout {...pageProps}>\n <div className=\"space-y-6\">\n {/* Stat row */}\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={formatSecondsHuman(coveredSeconds)}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`Last ${rangeLabel}`}\n />\n <StatCard\n title=\"Unaccounted\"\n value={formatSecondsHuman(unaccountedSeconds)}\n icon={<Gauge className=\"h-5 w-5\" />}\n description={`${coveragePct}% of time is tracked`}\n />\n <StatCard\n title=\"Active domains\"\n value={String(activeDomains)}\n icon={<Scale className=\"h-5 w-5\" />}\n description={`of ${ALL_LIFE_DOMAINS.length} life domains`}\n />\n <StatCard\n title=\"Targets set\"\n value={`${targetsCount} / ${ALL_LIFE_DOMAINS.length}`}\n icon={<Target className=\"h-5 w-5\" />}\n description={\n hasTargets ? \"Desired balance declared\" : \"Not set yet\"\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* The wheel */}\n <ChartCard\n className=\"lg:col-span-2\"\n title=\"Life Balance Wheel\"\n description={\n hasTargets\n ? \"Solid = where your time actually goes · dashed = the balance you want\"\n : \"Where your time actually goes across life domains\"\n }\n >\n <BalanceWheel\n data={wheelData}\n height={360}\n emptyLabel=\"No tracked time in this range yet.\"\n ariaLabel=\"Life balance wheel: actual versus desired share of time by domain\"\n />\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Time tracked</span>\n <span className=\"tabular-nums text-text-primary\">\n {formatSecondsHuman(coveredSeconds)} of{\" \"}\n {formatSecondsHuman(totalSeconds)} ({coveragePct}%)\n </span>\n </div>\n <ProgressMeter\n className=\"mt-2\"\n value={coveragePct}\n max={100}\n higherIsWorse={false}\n showValue={false}\n label=\"Coverage\"\n />\n </div>\n </ChartCard>\n\n {/* Headline + per-domain list */}\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-xl font-bold leading-tight text-text-primary\">\n Your week vs the life you want\n </h2>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Per-domain — hours tracked vs your desired share\n </p>\n\n {!hasTargets && (\n <div className=\"mt-3 flex items-start gap-2 rounded-lg border border-border-subtle bg-action-primary-bg/10 p-3 text-xs text-text-secondary\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-primary\" />\n <span>\n You haven't declared a desired balance yet, so the wheel is\n descriptive only.{\" \"}\n <button\n onClick={() => setTargetsOpen(true)}\n className=\"font-medium text-primary underline-offset-2 hover:underline\"\n >\n Set your targets\n </button>{\" \"}\n to see the gap.\n </span>\n </div>\n )}\n\n <ul className=\"mt-4 max-h-[30rem] space-y-2 overflow-y-auto pr-1\">\n {listRows.map((d) => {\n const target = targetHoursByDomain[d.domain];\n return (\n <li\n key={d.domain}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface/40 px-3 py-2.5\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {lifeDomainLabel(d.domain)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {sharePct(d.actualShare)}% of tracked\n {hasTargets\n ? ` · ${sharePct(d.desiredShare)}% desired`\n : \"\"}\n {typeof target === \"number\"\n ? ` · ${target}h/wk target`\n : \"\"}\n </p>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {hoursLabel(d.actualSeconds)}h\n </p>\n </div>\n {hasTargets && (\n <div className=\"shrink-0\">\n <GapBadge gap={d.gap} />\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </GlassCard>\n </div>\n </div>\n\n <SetTargetsModal\n open={targetsOpen}\n onClose={() => setTargetsOpen(false)}\n currentTargets={targetHoursByDomain}\n onSaved={refetchAll}\n notify={notify}\n />\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LifeTimelinePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/LifeTimelinePage.tsx"],"sourcesContent":["import { useMemo, useState, type FormEvent } from \"react\";\nimport {\n Activity,\n BookOpen,\n Briefcase,\n Car,\n CircleDashed,\n Clapperboard,\n Dumbbell,\n Heart,\n Hourglass,\n House,\n MessageCircle,\n Moon,\n NotebookPen,\n Plus,\n Sparkles,\n Wallet,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n ChartCard,\n DonutChart,\n EmptyState,\n LoadingSkeleton,\n Modal,\n useToast,\n ToastContainer,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { cn, formatTime, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n PERSONAL_LIFE_DOMAINS,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n lifeDomainLabel,\n} from \"./domains\";\nimport {\n TimeCampusLifeDomain,\n TimeCampusOwnershipContext,\n} from \"@/generated/wspace-types\";\nimport {\n useLifeTimelineQuery,\n useLogLifeTimeEntryMutation,\n type LifeTimelineQuery,\n} from \"@/generated/wspace-operations\";\n\ntype LifeSegment = LifeTimelineQuery[\"lifeTimeline\"][\"segments\"][number];\ntype DomainTotal =\n LifeTimelineQuery[\"lifeTimeline\"][\"summary\"][\"byDomain\"][number];\n\n/** Per-domain presentation: an icon + a semantic colour family. Identity is\n * always carried by the label — colour is only a supporting cue, so reusing a\n * family across domains is honest. Every class here is a registered token. */\nconst DOMAIN_META: Record<\n TimeCampusLifeDomain,\n { icon: typeof Briefcase; variant: BadgeVariant }\n> = {\n [TimeCampusLifeDomain.Work]: { icon: Briefcase, variant: \"info\" },\n [TimeCampusLifeDomain.Sleep]: { icon: Moon, variant: \"muted\" },\n [TimeCampusLifeDomain.Health]: { icon: Dumbbell, variant: \"success\" },\n [TimeCampusLifeDomain.Family]: { icon: Heart, variant: \"error\" },\n [TimeCampusLifeDomain.Learning]: { icon: BookOpen, variant: \"info\" },\n [TimeCampusLifeDomain.Leisure]: { icon: Clapperboard, variant: \"default\" },\n [TimeCampusLifeDomain.Commute]: { icon: Car, variant: \"warning\" },\n [TimeCampusLifeDomain.Waiting]: { icon: Hourglass, variant: \"muted\" },\n [TimeCampusLifeDomain.Chores]: { icon: House, variant: \"warning\" },\n [TimeCampusLifeDomain.SelfCare]: { icon: Sparkles, variant: \"success\" },\n [TimeCampusLifeDomain.Social]: { icon: MessageCircle, variant: \"info\" },\n [TimeCampusLifeDomain.FinanceAdmin]: { icon: Wallet, variant: \"default\" },\n [TimeCampusLifeDomain.Reflection]: { icon: NotebookPen, variant: \"default\" },\n [TimeCampusLifeDomain.Other]: { icon: CircleDashed, variant: \"muted\" },\n};\n\n/** Colour-family → dot/edge background token (mirrors StatusBadge dotStyles). */\nconst DOT_BY_VARIANT: Record<BadgeVariant, string> = {\n success: \"bg-status-success-text\",\n warning: \"bg-status-warning-text\",\n error: \"bg-status-error-text\",\n info: \"bg-status-info-text\",\n default: \"bg-action-primary-bg\",\n muted: \"bg-muted-foreground\",\n};\n\n/** Colour-family → icon-chip background+text tokens (mirrors StatusBadge). */\nconst CHIP_BY_VARIANT: Record<BadgeVariant, string> = {\n success: \"bg-status-success-bg text-status-success-text\",\n warning: \"bg-status-warning-bg text-status-warning-text\",\n error: \"bg-status-error-bg text-status-error-text\",\n info: \"bg-status-info-bg text-status-info-text\",\n default: \"bg-action-primary-bg/10 text-primary\",\n muted: \"bg-bg-sunken text-text-secondary\",\n};\n\nfunction domainMeta(domain: TimeCampusLifeDomain) {\n return DOMAIN_META[domain] ?? DOMAIN_META[TimeCampusLifeDomain.Other];\n}\n\ntype RangeKey = \"day\" | \"7d\" | \"30d\";\n\nconst RANGE_OPTIONS: { key: RangeKey; label: string }[] = [\n { key: \"day\", label: \"Day\" },\n { key: \"7d\", label: \"7 days\" },\n { key: \"30d\", label: \"30 days\" },\n];\n\nfunction toLocalDateInput(date: Date): string {\n const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction toLocalDateTimeInput(date: Date): string {\n const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);\n return local.toISOString().slice(0, 16);\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\n/** End of a segment: explicit endTime, else start + duration. */\nfunction segmentEndTime(segment: LifeSegment): string {\n if (segment.endTime) return segment.endTime;\n const endMs =\n new Date(segment.startTime).getTime() + segment.durationSeconds * 1000;\n return new Date(endMs).toISOString();\n}\n\nexport function LifeTimelinePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const [range, setRange] = useState<RangeKey>(\"day\");\n const [dateStr, setDateStr] = useState(() => toLocalDateInput(new Date()));\n\n const { from, to } = useMemo(() => {\n if (range === \"day\") return dayBounds(dateStr);\n if (range === \"7d\") return { from: isoDaysAgo(7), to: isoNow() };\n return { from: isoDaysAgo(30), to: isoNow() };\n }, [range, dateStr]);\n\n const { data, loading, error, refetch } = useLifeTimelineQuery({\n variables: { from, to },\n skip: !workspaceId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const [logLifeTime, { loading: logging }] = useLogLifeTimeEntryMutation();\n\n const timeline = data?.lifeTimeline;\n const summary = timeline?.summary;\n\n const segments = useMemo(\n () =>\n [...(timeline?.segments ?? [])].sort(\n (a, b) =>\n new Date(a.startTime).getTime() - new Date(b.startTime).getTime(),\n ),\n [timeline],\n );\n\n const byDomain: DomainTotal[] = useMemo(\n () => summary?.byDomain ?? [],\n [summary],\n );\n const totalCovered = summary?.totalCoveredSeconds ?? 0;\n const unaccounted = summary?.unaccountedSeconds ?? 0;\n const workSeconds = byDomain\n .filter((d) => d.domain === TimeCampusLifeDomain.Work)\n .reduce((s, d) => s + d.totalSeconds, 0);\n const personalSeconds = byDomain\n .filter((d) => d.domain !== TimeCampusLifeDomain.Work)\n .reduce((s, d) => s + d.totalSeconds, 0);\n\n const donutData = useMemo(\n () =>\n byDomain\n .filter((d) => d.totalSeconds > 0)\n .map((d) => ({\n name: lifeDomainLabel(d.domain),\n value: d.totalSeconds,\n })),\n [byDomain],\n );\n\n // ---- Log life time modal ----\n const [logOpen, setLogOpen] = useState(false);\n const [logDomain, setLogDomain] = useState<TimeCampusLifeDomain>(\n PERSONAL_LIFE_DOMAINS[0],\n );\n const [logStart, setLogStart] = useState(\"\");\n const [logMinutes, setLogMinutes] = useState(\"30\");\n const [logNote, setLogNote] = useState(\"\");\n\n const openLog = () => {\n setLogDomain(PERSONAL_LIFE_DOMAINS[0]);\n setLogStart(toLocalDateTimeInput(new Date()));\n setLogMinutes(\"30\");\n setLogNote(\"\");\n setLogOpen(true);\n };\n\n const handleLog = async (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n const minutes = Number(logMinutes);\n if (!logStart || !Number.isFinite(minutes) || minutes <= 0) {\n addToast({\n variant: \"error\",\n title: \"Add a start time and a positive duration\",\n });\n return;\n }\n try {\n const result = await logLifeTime({\n variables: {\n domain: logDomain,\n startTime: new Date(logStart).toISOString(),\n duration: Math.round(minutes * 60),\n note: logNote.trim() ? logNote.trim() : undefined,\n },\n });\n if (result.error) {\n throw result.error;\n }\n addToast({\n variant: \"success\",\n title: \"Life time logged\",\n description: `${lifeDomainLabel(logDomain)} · ${formatSecondsHuman(\n Math.round(minutes * 60),\n )}`,\n });\n setLogOpen(false);\n await refetch();\n } catch (err) {\n addToast({\n variant: \"error\",\n title: \"Could not log life time\",\n description: err instanceof Error ? err.message : \"Please try again.\",\n });\n }\n };\n\n const logButton = (\n <button\n type=\"button\"\n onClick={openLog}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n <Plus className=\"h-4 w-4\" />\n Log life time\n </button>\n );\n\n return (\n <PageLayout\n title=\"Life timeline\"\n description=\"Your whole day — work and life — in one honest thread\"\n purpose=\"This is your life, not just your job. Work sessions captured by TimeCampus sit alongside the sleep, family, health, commute and downtime you log — assembled in order so you can see the real shape of a day and where the hours actually go.\"\n actions={\n <>\n <div className=\"flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.key}\n type=\"button\"\n onClick={() => setRange(opt.key)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n range === opt.key\n ? \"bg-bg-surface text-text-primary shadow-sm\"\n : \"text-text-secondary hover:text-text-primary\",\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n {range === \"day\" && (\n <input\n type=\"date\"\n aria-label=\"Pick a day\"\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 )}\n {logButton}\n </>\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 life timeline. Please try again later.\n </div>\n ) : (\n <div className=\"space-y-6\">\n {/* Work vs Personal vs total */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Time covered\"\n value={formatSecondsHuman(totalCovered)}\n icon={<Activity className=\"h-5 w-5\" />}\n description=\"Accounted-for time in this range\"\n />\n <StatCard\n title=\"Work\"\n value={formatSecondsHuman(workSeconds)}\n icon={<Briefcase className=\"h-5 w-5\" />}\n description=\"Captured work time\"\n />\n <StatCard\n title=\"Personal\"\n value={formatSecondsHuman(personalSeconds)}\n icon={<Heart className=\"h-5 w-5\" />}\n description=\"Everything beyond work\"\n />\n <StatCard\n title=\"Unaccounted\"\n value={formatSecondsHuman(unaccounted)}\n icon={<CircleDashed className=\"h-5 w-5\" />}\n description=\"Not logged yet\"\n />\n </div>\n\n {/* Where life divided (real byDomain distribution) */}\n {donutData.length > 0 && (\n <ChartCard\n title=\"How your life divided\"\n description=\"Share of accounted-for time across life domains\"\n >\n <DonutChart\n data={donutData}\n centerLabel=\"Time covered\"\n centerValue={formatSecondsHuman(totalCovered)}\n valueFormat={(v) => formatSecondsHuman(v)}\n ariaLabel=\"Time by life domain\"\n />\n </ChartCard>\n )}\n\n {/* Honest unaccounted note */}\n {unaccounted > 0 && segments.length > 0 && (\n <div className=\"flex items-start gap-2 rounded-lg border border-border-seam bg-bg-surface px-4 py-3 text-sm text-text-secondary\">\n <CircleDashed className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {formatSecondsHuman(unaccounted)}\n </span>{\" \"}\n of this range isn&rsquo;t accounted for yet. Log life time to\n fill the gaps and see the true shape of your day.\n </span>\n </div>\n )}\n\n {/* Chronological life timeline */}\n {segments.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No life yet in this range\"\n description=\"Work sessions land here automatically. Add the rest of your day — sleep, family, health, commute, downtime — and your real timeline assembles in order.\"\n action={logButton}\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <ol className=\"divide-y divide-border-seam\">\n {segments.map((segment) => {\n const meta = domainMeta(segment.lifeDomain);\n const Icon = meta.icon;\n const isPersonal =\n segment.ownershipContext ===\n TimeCampusOwnershipContext.Personal;\n return (\n <li\n key={segment.id}\n className=\"flex items-stretch gap-3 p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex w-24 shrink-0 flex-col items-end pt-0.5 text-xs tabular-nums text-text-secondary\">\n <span>\n {formatTime(segment.startTime)} –{\" \"}\n {formatTime(segmentEndTime(segment))}\n </span>\n <span className=\"text-text-tertiary\">\n {formatSecondsHuman(segment.durationSeconds)}\n </span>\n </div>\n <span\n aria-hidden\n className={cn(\n \"w-1 shrink-0 rounded-full\",\n DOT_BY_VARIANT[meta.variant],\n )}\n />\n <div\n className={cn(\n \"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg\",\n CHIP_BY_VARIANT[meta.variant],\n )}\n >\n <Icon 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 || lifeDomainLabel(segment.lifeDomain)}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={lifeDomainLabel(segment.lifeDomain)}\n variant={meta.variant}\n dot\n />\n <StatusBadge\n label={isPersonal ? \"Personal\" : \"Work\"}\n variant=\"muted\"\n />\n {segment.app && (\n <span className=\"text-xs text-text-secondary\">\n {segment.app}\n </span>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </GlassCard>\n )}\n </div>\n )}\n\n {/* Log life time modal */}\n <Modal\n open={logOpen}\n onClose={() => setLogOpen(false)}\n title=\"Log life time\"\n description=\"Add a slice of life — sleep, family, health, downtime — to your timeline.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setLogOpen(false)}\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n form=\"log-life-form\"\n disabled={logging}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-60\"\n >\n {logging ? \"Logging…\" : \"Log life time\"}\n </button>\n </>\n }\n >\n <form id=\"log-life-form\" onSubmit={handleLog} className=\"space-y-4\">\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Life domain\n </span>\n <select\n value={logDomain}\n onChange={(e) =>\n setLogDomain(e.target.value as TimeCampusLifeDomain)\n }\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n {PERSONAL_LIFE_DOMAINS.map((d) => (\n <option key={d} value={d}>\n {lifeDomainLabel(d)}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Starts\n </span>\n <input\n type=\"datetime-local\"\n value={logStart}\n max={toLocalDateTimeInput(new Date())}\n onChange={(e) => setLogStart(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Duration (minutes)\n </span>\n <input\n type=\"number\"\n min={1}\n step={1}\n value={logMinutes}\n onChange={(e) => setLogMinutes(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Note{\" \"}\n <span className=\"font-normal text-text-tertiary\">(optional)</span>\n </span>\n <textarea\n value={logNote}\n onChange={(e) => setLogNote(e.target.value)}\n rows={2}\n placeholder=\"What was this time about?\"\n className=\"w-full resize-none rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n </form>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LifeTimelinePage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/LifeTimelinePage.tsx"],"sourcesContent":["import { useMemo, useState, type FormEvent } from \"react\";\nimport {\n Activity,\n BookOpen,\n Briefcase,\n Car,\n CircleDashed,\n Clapperboard,\n Dumbbell,\n Heart,\n Hourglass,\n House,\n MessageCircle,\n Moon,\n NotebookPen,\n Plus,\n Sparkles,\n Wallet,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n ChartCard,\n DonutChart,\n EmptyState,\n LoadingSkeleton,\n Modal,\n useToast,\n ToastContainer,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { cn, formatTime, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n PERSONAL_LIFE_DOMAINS,\n formatSecondsHuman,\n isoDaysAgo,\n isoNow,\n lifeDomainLabel,\n} from \"./domains\";\nimport {\n TimeCampusLifeDomain,\n TimeCampusOwnershipContext,\n} from \"@/generated/wspace-types\";\nimport {\n useLifeTimelineQuery,\n useLogLifeTimeEntryMutation,\n type LifeTimelineQuery,\n} from \"@/generated/wspace-operations\";\n\ntype LifeSegment = LifeTimelineQuery[\"lifeTimeline\"][\"segments\"][number];\ntype DomainTotal =\n LifeTimelineQuery[\"lifeTimeline\"][\"summary\"][\"byDomain\"][number];\n\n/** Per-domain presentation: an icon + a semantic colour family. Identity is\n * always carried by the label — colour is only a supporting cue, so reusing a\n * family across domains is honest. Every class here is a registered token. */\nconst DOMAIN_META: Record<\n TimeCampusLifeDomain,\n { icon: typeof Briefcase; variant: BadgeVariant }\n> = {\n [TimeCampusLifeDomain.Work]: { icon: Briefcase, variant: \"info\" },\n [TimeCampusLifeDomain.Sleep]: { icon: Moon, variant: \"muted\" },\n [TimeCampusLifeDomain.Health]: { icon: Dumbbell, variant: \"success\" },\n [TimeCampusLifeDomain.Family]: { icon: Heart, variant: \"error\" },\n [TimeCampusLifeDomain.Learning]: { icon: BookOpen, variant: \"info\" },\n [TimeCampusLifeDomain.Leisure]: { icon: Clapperboard, variant: \"default\" },\n [TimeCampusLifeDomain.Commute]: { icon: Car, variant: \"warning\" },\n [TimeCampusLifeDomain.Waiting]: { icon: Hourglass, variant: \"muted\" },\n [TimeCampusLifeDomain.Chores]: { icon: House, variant: \"warning\" },\n [TimeCampusLifeDomain.SelfCare]: { icon: Sparkles, variant: \"success\" },\n [TimeCampusLifeDomain.Social]: { icon: MessageCircle, variant: \"info\" },\n [TimeCampusLifeDomain.FinanceAdmin]: { icon: Wallet, variant: \"default\" },\n [TimeCampusLifeDomain.Reflection]: { icon: NotebookPen, variant: \"default\" },\n [TimeCampusLifeDomain.Other]: { icon: CircleDashed, variant: \"muted\" },\n};\n\n/** Colour-family → dot/edge background token (mirrors StatusBadge dotStyles). */\nconst DOT_BY_VARIANT: Record<BadgeVariant, string> = {\n success: \"bg-status-success-text\",\n warning: \"bg-status-warning-text\",\n error: \"bg-status-error-text\",\n info: \"bg-status-info-text\",\n default: \"bg-action-primary-bg\",\n muted: \"bg-muted-foreground\",\n};\n\n/** Colour-family → icon-chip background+text tokens (mirrors StatusBadge). */\nconst CHIP_BY_VARIANT: Record<BadgeVariant, string> = {\n success: \"bg-status-success-bg text-status-success-text\",\n warning: \"bg-status-warning-bg text-status-warning-text\",\n error: \"bg-status-error-bg text-status-error-text\",\n info: \"bg-status-info-bg text-status-info-text\",\n default: \"bg-action-primary-bg/10 text-primary\",\n muted: \"bg-bg-sunken text-text-secondary\",\n};\n\nfunction domainMeta(domain: TimeCampusLifeDomain) {\n return DOMAIN_META[domain] ?? DOMAIN_META[TimeCampusLifeDomain.Other];\n}\n\ntype RangeKey = \"day\" | \"7d\" | \"30d\";\n\nconst RANGE_OPTIONS: { key: RangeKey; label: string }[] = [\n { key: \"day\", label: \"Day\" },\n { key: \"7d\", label: \"7 days\" },\n { key: \"30d\", label: \"30 days\" },\n];\n\nfunction toLocalDateInput(date: Date): string {\n const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction toLocalDateTimeInput(date: Date): string {\n const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);\n return local.toISOString().slice(0, 16);\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\n/** End of a segment: explicit endTime, else start + duration. */\nfunction segmentEndTime(segment: LifeSegment): string {\n if (segment.endTime) return segment.endTime;\n const endMs =\n new Date(segment.startTime).getTime() + segment.durationSeconds * 1000;\n return new Date(endMs).toISOString();\n}\n\nexport function LifeTimelinePage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const [range, setRange] = useState<RangeKey>(\"day\");\n const [dateStr, setDateStr] = useState(() => toLocalDateInput(new Date()));\n\n const { from, to } = useMemo(() => {\n if (range === \"day\") return dayBounds(dateStr);\n if (range === \"7d\") return { from: isoDaysAgo(7), to: isoNow() };\n return { from: isoDaysAgo(30), to: isoNow() };\n }, [range, dateStr]);\n\n const { data, loading, error, refetch } = useLifeTimelineQuery({\n variables: { from, to },\n skip: !workspaceId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const [logLifeTime, { loading: logging }] = useLogLifeTimeEntryMutation();\n\n const timeline = data?.lifeTimeline;\n const summary = timeline?.summary;\n\n const segments = useMemo(\n () =>\n [...(timeline?.segments ?? [])].sort(\n (a, b) =>\n new Date(a.startTime).getTime() - new Date(b.startTime).getTime(),\n ),\n [timeline],\n );\n\n const byDomain: DomainTotal[] = useMemo(\n () => summary?.byDomain ?? [],\n [summary],\n );\n const totalCovered = summary?.totalCoveredSeconds ?? 0;\n const unaccounted = summary?.unaccountedSeconds ?? 0;\n const workSeconds = byDomain\n .filter((d) => d.domain === TimeCampusLifeDomain.Work)\n .reduce((s, d) => s + d.totalSeconds, 0);\n const personalSeconds = byDomain\n .filter((d) => d.domain !== TimeCampusLifeDomain.Work)\n .reduce((s, d) => s + d.totalSeconds, 0);\n\n const donutData = useMemo(\n () =>\n byDomain\n .filter((d) => d.totalSeconds > 0)\n .map((d) => ({\n name: lifeDomainLabel(d.domain),\n value: d.totalSeconds,\n })),\n [byDomain],\n );\n\n // ---- Log life time modal ----\n const [logOpen, setLogOpen] = useState(false);\n const [logDomain, setLogDomain] = useState<TimeCampusLifeDomain>(\n PERSONAL_LIFE_DOMAINS[0],\n );\n const [logStart, setLogStart] = useState(\"\");\n const [logMinutes, setLogMinutes] = useState(\"30\");\n const [logNote, setLogNote] = useState(\"\");\n\n const openLog = () => {\n setLogDomain(PERSONAL_LIFE_DOMAINS[0]);\n setLogStart(toLocalDateTimeInput(new Date()));\n setLogMinutes(\"30\");\n setLogNote(\"\");\n setLogOpen(true);\n };\n\n const handleLog = async (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n const minutes = Number(logMinutes);\n if (!logStart || !Number.isFinite(minutes) || minutes <= 0) {\n addToast({\n variant: \"error\",\n title: \"Add a start time and a positive duration\",\n });\n return;\n }\n try {\n const result = await logLifeTime({\n variables: {\n domain: logDomain,\n startTime: new Date(logStart).toISOString(),\n duration: Math.round(minutes * 60),\n note: logNote.trim() ? logNote.trim() : undefined,\n },\n });\n if (result.error) {\n throw result.error;\n }\n addToast({\n variant: \"success\",\n title: \"Life time logged\",\n description: `${lifeDomainLabel(logDomain)} · ${formatSecondsHuman(\n Math.round(minutes * 60),\n )}`,\n });\n setLogOpen(false);\n await refetch();\n } catch (err) {\n addToast({\n variant: \"error\",\n title: \"Could not log life time\",\n description: err instanceof Error ? err.message : \"Please try again.\",\n });\n }\n };\n\n const logButton = (\n <button\n type=\"button\"\n onClick={openLog}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n <Plus className=\"h-4 w-4\" />\n Log life time\n </button>\n );\n\n return (\n <PageLayout\n title=\"Life timeline\"\n description=\"Your whole day — work and life — in one honest thread\"\n purpose=\"This is your life, not just your job. Work sessions captured by TimeCampus sit alongside the sleep, family, health, commute and downtime you log — assembled in order so you can see the real shape of a day and where the hours actually go.\"\n actions={\n <>\n <div className=\"flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.key}\n type=\"button\"\n onClick={() => setRange(opt.key)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n range === opt.key\n ? \"bg-bg-surface text-text-primary shadow-sm\"\n : \"text-text-secondary hover:text-text-primary\",\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n {range === \"day\" && (\n <input\n type=\"date\"\n aria-label=\"Pick a day\"\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 )}\n {logButton}\n </>\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 life timeline. Please try again later.\n </div>\n ) : (\n <div className=\"space-y-6\">\n {/* Work vs Personal vs total */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Time covered\"\n value={formatSecondsHuman(totalCovered)}\n icon={<Activity className=\"h-5 w-5\" />}\n description=\"Accounted-for time in this range\"\n />\n <StatCard\n title=\"Work\"\n value={formatSecondsHuman(workSeconds)}\n icon={<Briefcase className=\"h-5 w-5\" />}\n description=\"Captured work time\"\n />\n <StatCard\n title=\"Personal\"\n value={formatSecondsHuman(personalSeconds)}\n icon={<Heart className=\"h-5 w-5\" />}\n description=\"Everything beyond work\"\n />\n <StatCard\n title=\"Unaccounted\"\n value={formatSecondsHuman(unaccounted)}\n icon={<CircleDashed className=\"h-5 w-5\" />}\n description=\"Not logged yet\"\n />\n </div>\n\n {/* Where life divided (real byDomain distribution) */}\n {donutData.length > 0 && (\n <ChartCard\n title=\"How your life divided\"\n description=\"Share of accounted-for time across life domains\"\n >\n <DonutChart\n data={donutData}\n centerLabel=\"Time covered\"\n centerValue={formatSecondsHuman(totalCovered)}\n valueFormat={(v) => formatSecondsHuman(v)}\n ariaLabel=\"Time by life domain\"\n />\n </ChartCard>\n )}\n\n {/* Honest unaccounted note */}\n {unaccounted > 0 && segments.length > 0 && (\n <div className=\"flex items-start gap-2 rounded-lg border border-border-seam bg-bg-surface px-4 py-3 text-sm text-text-secondary\">\n <CircleDashed className=\"mt-0.5 h-4 w-4 shrink-0 text-text-tertiary\" />\n <span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {formatSecondsHuman(unaccounted)}\n </span>{\" \"}\n of this range isn&rsquo;t accounted for yet. Log life time to\n fill the gaps and see the true shape of your day.\n </span>\n </div>\n )}\n\n {/* Chronological life timeline */}\n {segments.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No life yet in this range\"\n description=\"Work sessions land here automatically. Add the rest of your day — sleep, family, health, commute, downtime — and your real timeline assembles in order.\"\n action={logButton}\n />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <ol className=\"divide-y divide-border-seam\">\n {segments.map((segment) => {\n const meta = domainMeta(segment.lifeDomain);\n const Icon = meta.icon;\n const isPersonal =\n segment.ownershipContext ===\n TimeCampusOwnershipContext.Personal;\n return (\n <li\n key={segment.id}\n className=\"flex items-stretch gap-3 p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex w-24 shrink-0 flex-col items-end pt-0.5 text-xs tabular-nums text-text-secondary\">\n <span>\n {formatTime(segment.startTime)} –{\" \"}\n {formatTime(segmentEndTime(segment))}\n </span>\n <span className=\"text-text-tertiary\">\n {formatSecondsHuman(segment.durationSeconds)}\n </span>\n </div>\n <span\n aria-hidden\n className={cn(\n \"w-1 shrink-0 rounded-full\",\n DOT_BY_VARIANT[meta.variant],\n )}\n />\n <div\n className={cn(\n \"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg\",\n CHIP_BY_VARIANT[meta.variant],\n )}\n >\n <Icon 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 || lifeDomainLabel(segment.lifeDomain)}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={lifeDomainLabel(segment.lifeDomain)}\n variant={meta.variant}\n dot\n />\n <StatusBadge\n label={isPersonal ? \"Personal\" : \"Work\"}\n variant=\"muted\"\n />\n {segment.app && (\n <span className=\"text-xs text-text-secondary\">\n {segment.app}\n </span>\n )}\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </GlassCard>\n )}\n </div>\n )}\n\n {/* Log life time modal */}\n <Modal\n open={logOpen}\n onClose={() => setLogOpen(false)}\n title=\"Log life time\"\n description=\"Add a slice of life — sleep, family, health, downtime — to your timeline.\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setLogOpen(false)}\n className=\"rounded-md border border-border-seam bg-bg-surface px-3 py-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n form=\"log-life-form\"\n disabled={logging}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-60\"\n >\n {logging ? \"Logging…\" : \"Log life time\"}\n </button>\n </>\n }\n >\n <form id=\"log-life-form\" onSubmit={handleLog} className=\"space-y-4\">\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Life domain\n </span>\n <select\n value={logDomain}\n onChange={(e) =>\n setLogDomain(e.target.value as TimeCampusLifeDomain)\n }\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n >\n {PERSONAL_LIFE_DOMAINS.map((d) => (\n <option key={d} value={d}>\n {lifeDomainLabel(d)}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Starts\n </span>\n <input\n type=\"datetime-local\"\n value={logStart}\n max={toLocalDateTimeInput(new Date())}\n onChange={(e) => setLogStart(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Duration (minutes)\n </span>\n <input\n type=\"number\"\n min={1}\n step={1}\n value={logMinutes}\n onChange={(e) => setLogMinutes(e.target.value)}\n className=\"w-full rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n\n <label className=\"block\">\n <span className=\"mb-1 block text-sm font-medium text-text-primary\">\n Note{\" \"}\n <span className=\"font-normal text-text-tertiary\">(optional)</span>\n </span>\n <textarea\n value={logNote}\n onChange={(e) => setLogNote(e.target.value)}\n rows={2}\n placeholder=\"What was this time about?\"\n className=\"w-full resize-none rounded-md border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40\"\n />\n </label>\n </form>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}