@burdenoff/microfe-timecampus 2026.830.2 → 2026.904.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 (54) hide show
  1. package/dist/timecampus/components/index.js +4 -4
  2. package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
  3. package/dist/timecampus/pages/HomePage.js +149 -133
  4. package/dist/timecampus/pages/HomePage.js.map +1 -1
  5. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
  6. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  7. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
  8. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  9. package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
  10. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
  11. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
  12. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  13. package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
  14. package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
  15. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  16. package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
  17. package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
  18. package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
  19. package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
  20. package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
  21. package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
  22. package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
  23. package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
  24. package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
  25. package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
  26. package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
  27. package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
  28. package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
  29. package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
  30. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
  31. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
  32. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
  34. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  35. package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
  36. package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
  37. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  38. package/dist/timecampus/pages/projects/TasksPage.js +8 -8
  39. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
  40. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  41. package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
  42. package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
  43. package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
  44. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  45. package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
  46. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  47. package/dist/timecampus/pages/time/TimerPage.js +14 -14
  48. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  49. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
  50. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  51. package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
  52. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
  53. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
  54. package/package.json +1 -1
@@ -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":";;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,IAA+B,CACnC;CAAE,OAAO;CAAU,MAAM;AAAE,GAC3B;CAAE,OAAO;CAAW,MAAM;AAAG,CAC/B;AAGA,SAAS,EAAS,GAAuB;CACvC,OAAO,KAAK,MAAM,IAAQ,GAAG;AAC/B;AAGA,SAAS,GAAW,GAAyB;CAC3C,OAAO,EAAe,CAAO,CAAC,CAAC,QAAQ,CAAC;AAC1C;AAeA,IAAM,IAAgB;AAEtB,SAAS,EAAgB,EACvB,SACA,YACA,mBACA,YACA,aACuB;CACvB,IAAM,CAAC,GAAkB,EAAE,SAAS,OAAY,EAA4B,GACtE,CAAC,GAAO,KAAY,EAAiC,CAAC,CAAC;CAG7D,QAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,IAA+B,CAAC;EACtC,KAAK,IAAM,KAAU,GAAkB;GACrC,IAAM,IAAQ,EAAe;GAC7B,EAAK,KAAU,OAAO,KAAU,WAAW,OAAO,CAAK,IAAI;EAC7D;EACA,EAAS,CAAI;CACf,GAAG,CAAC,GAAM,CAAc,CAAC;CAEzB,IAAM,IAAY,QAAc;EAC9B,IAAI,IAAM;EACV,KAAK,IAAM,KAAU,GAAkB;GACrC,IAAM,IAAM,EAAM,IACZ,IAAQ,IAAM,OAAO,CAAG,IAAI;GAClC,AAAI,OAAO,SAAS,CAAK,KAAK,IAAQ,MAAG,KAAO;EAClD;EACA,OAAO,KAAK,MAAM,IAAM,EAAE,IAAI;CAChC,GAAG,CAAC,CAAK,CAAC,GAEJ,IAAgB,IAAY;CAyClC,OACE,kBAAC,GAAD;EACQ;EACG;EACT,OAAM;EACN,aAAY;EACZ,MAAK;EACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;GACE,SAAS;GACT,UAAU;GACV,WAAU;GACX,UAAA;EAEO,CAAA,GACR,kBAAC,UAAD;GACE,SAAS,YAxDY;IAC7B,IAAM,IAA8B,CAAC;IACrC,KAAK,IAAM,KAAU,GAAkB;KACrC,IAAM,IAAM,EAAM;KAClB,IAAI,MAAQ,KAAA,KAAa,EAAI,KAAK,MAAM,IAAI;KAC5C,IAAM,IAAQ,OAAO,CAAG;KACxB,IAAI,CAAC,OAAO,SAAS,CAAK,KAAK,IAAQ,GAAG;KAC1C,IAAM,IAAW,EAAe;KAC5B,MAAa,KAAA,KAAa,KAAK,IAAI,IAAW,CAAK,IAAI,QAC3D,EAAQ,KACN,EAAiB,EACf,WAAW;MAAE;MAAQ,qBAAqB;KAAM,EAClD,CAAC,CACH;IACF;IAEA,IAAI,EAAQ,WAAW,GAAG;KACxB,EAAQ;KACR;IACF;IAEA,IAAI;KAQF,AAPA,MAAM,QAAQ,IAAI,CAAO,GACzB,EACE,WACA,iBACA,WAAW,EAAQ,OAAO,GAAG,EAAQ,WAAW,IAAI,WAAW,UAAU,EAC3E,GACA,EAAQ,GACR,EAAQ;IACV,QAAQ;KACN,EACE,SACA,yBACA,yCACF;IACF;GACF;GAoBU,UAAU;GACV,WAAU;GAHZ,UAAA,CAKE,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,GAC3B,IAAS,YAAY,cAChB;EACR,CAAA,CAAA,EAAA,CAAA;EAvBN,UAAA,CA0BE,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EAAiB,KAAK,MACrB,kBAAC,OAAD;IAEE,WAAU;IAFZ,UAAA,CAIE,kBAAC,SAAD;KACE,SAAS,UAAU;KACnB,WAAU;KAET,UAAA,EAAgB,CAAM;IAClB,CAAA,GACP,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,SAAD;MACE,IAAI,UAAU;MACd,MAAK;MACL,KAAK;MACL,MAAM;MACN,WAAU;MACV,OAAO,EAAM,MAAW;MACxB,WAAW,MACT,GAAU,OAAU;OAAE,GAAG;QAAO,IAAS,EAAE,OAAO;MAAM,EAAE;MAE5D,aAAY;MACZ,WAAU;KACX,CAAA,GACD,kBAAC,QAAD;MAAM,WAAU;MAAmC,UAAA;KAAY,CAAA,CAC5D;IACF,CAAA,CAAA;GAzBE,GAAA,CAyBF,CACN;EACE,CAAA,GAEL,kBAAC,KAAD;GACE,WAAW,EACT,6BACA,IAAgB,6BAA6B,qBAC/C;GAJF,UAAA;IAMG,EAAU,QAAQ,CAAC;IAAE;IAAK;IAAc;IACxC,IACG,yDACA;GACH;EACE,CAAA,CAAA;;AAEX;AAKA,SAAS,EAAS,EAAE,UAAwB;CAC1C,IAAM,IAAK,KAAK,MAAM,KAAK,IAAI,CAAG,IAAI,GAAG;CAOzC,OANI,IAAK,IACA,kBAAC,GAAD;EAAa,SAAQ;EAAU,OAAM;CAAa,CAAA,IAEvD,IAAM,IACD,kBAAC,GAAD;EAAa,SAAQ;EAAU,OAAO,IAAI,EAAG;CAAW,CAAA,IAE1D,kBAAC,GAAD;EAAa,SAAQ;EAAO,OAAO,GAAG,EAAG;CAAY,CAAA;AAC9D;AAKA,SAAgB,IAAkB;CAChC,IAAM,EAAE,iBAAc,OAAO,EAAc,GACrC,EAAE,WAAQ,aAAU,oBAAiB,EAAS,GAC9C,KAAU,GAAuB,GAAe,MACpD,EAAS;EAAE;EAAS;EAAO;CAAY,CAAC,GAEpC,CAAC,GAAW,KAAgB,EAAiB,EAAE,GAC/C,CAAC,GAAa,KAAkB,EAAS,EAAK,GAE9C,IAAQ,SACL;EAAE,MAAM,GAAW,CAAS;EAAG,IAAI,EAAO;CAAE,IACnD,CAAC,CAAS,CACZ,GAEM,EACJ,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,MACP,EAAsB;EACxB,WAAW;GAAE,MAAM,EAAM;GAAM,IAAI,EAAM;EAAG;EAC5C,MAAM,CAAC;CACT,CAAC,GAEK,EAAE,MAAM,GAAa,SAAS,OAClC,EAAyB,EAAE,MAAM,CAAC,EAAY,CAAC,GAE3C,IAAU,GAAa,eACvB,IAAW,QACT,GAAS,YAAY,CAAC,GAC5B,CAAC,CAAO,CACV,GACM,IAAa,GAAS,cAAc,IAGpC,IAAsB,QAEpB;EACN,IAAM,IAAqD,CAAC,GACtD,IACJ,GAAa,oBAAoB,CAAC;EACpC,KAAK,IAAM,KAAU,GACnB,EAAI,EAAO,UAAU,EAAO;EAE9B,OAAO;CACT,GAAG,CAAC,CAAW,CAAC,GACV,KAAe,GAAa,iBAAiB,UAAU,GAGvD,IAAa,QAAc;EAC/B,IAAM,oBAAM,IAAI,IAAkC;EAElD,OADA,EAAiB,SAAS,GAAQ,MAAM,EAAI,IAAI,GAAQ,CAAC,CAAC,GACnD;CACT,GAAG,CAAC,CAAC,GAEC,KAAY,QACT,CAAC,GAAG,CAAQ,CAAC,CACjB,MACE,GAAG,OACD,EAAW,IAAI,EAAE,MAAM,KAAK,OAAO,EAAW,IAAI,EAAE,MAAM,KAAK,GACpE,CAAC,CACA,KAAK,OAAO;EACX,QAAQ,EAAE;EACV,OAAO,EAAgB,EAAE,MAAM;EAC/B,aAAa,EAAE;EACf,cAAc,IAAa,EAAE,eAAe,KAAA;CAC9C,EAAE,GACH;EAAC;EAAU;EAAY;CAAU,CAAC,GAG/B,KAAW,QACR,CAAC,GAAG,CAAQ,CAAC,CACjB,QACE,MACC,EAAE,gBAAgB,KACjB,KAAc,EAAE,eAAe,KAChC,EAAoB,EAAE,YAAY,KAAA,CACtC,CAAC,CACA,MAAM,GAAG,MAAM,EAAE,gBAAgB,EAAE,aAAa,GAClD;EAAC;EAAU;EAAY;CAAmB,CAAC,GAExC,IAAiB,GAAS,kBAAkB,GAC5C,IAAqB,GAAS,sBAAsB,GACpD,IAAe,IAAiB,GAChC,IACJ,IAAe,IAAI,KAAK,MAAO,IAAiB,IAAgB,GAAG,IAAI,GACnE,KAAgB,EAAS,QAAQ,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,QAC5D,KAAU,IAAiB,KAAK,EAAS,SAAS,GAElD,KACJ,EAAc,MAAM,MAAM,EAAE,SAAS,CAAS,CAAC,EAAE,SACjD,GAAG,EAAU,QAET,UAAmB;EAEvB,AADA,EAAoB,GACpB,GAAoB;CACtB,GA+BM,IAAY;EAChB,OAAO;EACP,aAAa;EACb,SACE;EACF,SAhCA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EAAc,KAAK,MAClB,kBAAC,UAAD;IAEE,eAAe,EAAa,EAAO,IAAI;IACvC,WAAW,EACT,gEACA,MAAc,EAAO,OACjB,8CACA,6CACN;IAEC,UAAA,EAAO;GACF,GAVD,EAAO,IAUN,CACT;EACE,CAAA,GACL,kBAAC,UAAD;GACE,eAAe,EAAe,EAAI;GAClC,WAAU;GAFZ,UAAA,CAIE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,GAAC,aAExB;EACR,CAAA,CAAA,EAAA,CAQF;CACF;CA0DA,OAvDI,KAAkB,CAAC,IAEnB,kBAAC,GAAD;EAAY,GAAI;EAAhB,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,GAAD;IAAiB,SAAQ;IAAO,OAAO;GAAI,CAAA,GAC3C,kBAAC,GAAD;IAAiB,SAAQ;IAAO,OAAO;GAAI,CAAA,CACxC;EACL,CAAA,GAAA,kBAAC,GAAD;GAAwB;GAAQ,WAAW;EAAe,CAAA,CAChD;MAKZ,IAEA,kBAAC,GAAD;EAAY,GAAI;EAAhB,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAoG,UAAA;EAG9G,CAAA,GACL,kBAAC,GAAD;GAAwB;GAAQ,WAAW;EAAe,CAAA,CAChD;MAKX,KA8BH,kBAAC,GAAD;EAAY,GAAI;EAAhB,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CAEE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,kBAAC,GAAD;OACE,UAAA;OACA,OAAM;OACN,OAAO,EAAmB,CAAc;OACxC,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OAClC,aAAa,QAAQ;MACtB,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,EAAmB,CAAkB;OAC5C,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OAClC,aAAa,GAAG,EAAY;MAC7B,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,OAAO,EAAa;OAC3B,MAAM,kBAAC,IAAD,EAAO,WAAU,UAAW,CAAA;OAClC,aAAa,MAAM,EAAiB,OAAO;MAC5C,CAAA;MACD,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,GAAa,KAAK,EAAiB;OAC7C,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;OACnC,aACE,IAAa,6BAA6B;MAE7C,CAAA;KACE;IAEL,CAAA,GAAA,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CAEE,kBAAC,GAAD;MACE,WAAU;MACV,OAAM;MACN,aACE,IACI,0EACA;MANR,UAAA,CASE,kBAAC,GAAD;OACE,MAAM;OACN,QAAQ;OACR,YAAW;OACX,WAAU;MACX,CAAA,GACD,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,QAAD,EAAA,UAAM,eAAkB,CAAA,GACxB,kBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA;UACG,EAAmB,CAAc;UAAE;UAAI;UACvC,EAAmB,CAAY;UAAE;UAAG;UAAY;SAC7C;QACH,CAAA,CAAA;OACL,CAAA,GAAA,kBAAC,IAAD;QACE,WAAU;QACV,OAAO;QACP,KAAK;QACL,eAAe;QACf,WAAW;QACX,OAAM;OACP,CAAA,CACE;MACI,CAAA,CAAA;KAGX,CAAA,GAAA,kBAAC,IAAD;MAAW,WAAU;MAAQ,WAAU;MAAvC,UAAA;OACE,kBAAC,MAAD;QAAI,WAAU;QAAoD,UAAA;OAE9D,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;QAAmC,UAAA;OAE7C,CAAA;OAEF,CAAC,KACA,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,IAAD,EAAU,WAAU,uCAAwC,CAAA,GAC5D,kBAAC,QAAD,EAAA,UAAA;SAAM;SAEc;SAClB,kBAAC,UAAD;UACE,eAAe,EAAe,EAAI;UAClC,WAAU;UACX,UAAA;SAEO,CAAA;SAAE;SAAI;QAEV,EAAA,CAAA,CACH;;OAGP,kBAAC,MAAD;QAAI,WAAU;QACX,UAAA,GAAS,KAAK,MAAM;SACnB,IAAM,IAAS,EAAoB,EAAE;SACrC,OACE,kBAAC,MAAD;UAEE,WAAU;UAFZ,UAAA;WAIE,kBAAC,OAAD;YAAK,WAAU;YAAf,UAAA,CACE,kBAAC,KAAD;aAAG,WAAU;aACV,UAAA,EAAgB,EAAE,MAAM;YACxB,CAAA,GACH,kBAAC,KAAD;aAAG,WAAU;aAAb,UAAA;cACG,EAAS,EAAE,WAAW;cAAE;cACxB,IACG,MAAM,EAAS,EAAE,YAAY,EAAE,aAC/B;cACH,OAAO,KAAW,WACf,MAAM,EAAO,eACb;aACH;YACA,CAAA,CAAA;;WACL,kBAAC,OAAD;YAAK,WAAU;YACb,UAAA,kBAAC,KAAD;aAAG,WAAU;aAAb,UAAA,CACG,GAAW,EAAE,aAAa,GAAE,GAC5B;;WACA,CAAA;WACJ,KACC,kBAAC,OAAD;YAAK,WAAU;YACb,UAAA,kBAAC,GAAD,EAAU,KAAK,EAAE,IAAM,CAAA;WACpB,CAAA;UAEL;SA3BG,GAAA,EAAE,MA2BL;QAER,CAAC;OACC,CAAA;MACK;KACR,CAAA,CAAA;IACF,CAAA,CAAA;;GAEL,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAe,EAAK;IACnC,gBAAgB;IAChB,SAAS;IACD;GACT,CAAA;GACD,kBAAC,GAAD;IAAwB;IAAQ,WAAW;GAAe,CAAA;EAChD;MA3KV,kBAAC,GAAD;EAAY,GAAI;EAAhB,UAAA;GACE,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,eAAe,EAAe,EAAI;KAClC,WAAU;KAFZ,UAAA,CAIE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,GAAC,wBAExB;;GAEX,CAAA;GACD,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAe,EAAK;IACnC,gBAAgB;IAChB,SAAS;IACD;GACT,CAAA;GACD,kBAAC,GAAD;IAAwB;IAAQ,WAAW;GAAe,CAAA;EAChD;;AAuJlB"}
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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"}
@@ -2,10 +2,10 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
2
  import { cn as t, formatTime as n } from "../../utils/helpers.js";
3
3
  import { StatCard as r } from "../../components/StatCard.js";
4
4
  import { LoadingSkeleton as i } from "../../components/LoadingSkeleton.js";
5
- import { useLifeTimelineQuery as a, useLogLifeTimeEntryMutation as ee } from "../../../generated/wspace-operations.js";
6
- import { PageLayout as te } from "../../components/PageLayout.js";
5
+ import { EmptyState as a } from "../../components/EmptyState.js";
6
+ import { useLifeTimelineQuery as ee, useLogLifeTimeEntryMutation as te } from "../../../generated/wspace-operations.js";
7
+ import { PageLayout as ne } from "../../components/PageLayout.js";
7
8
  import { StatusBadge as o } from "../../components/StatusBadge.js";
8
- import { EmptyState as ne } from "../../components/EmptyState.js";
9
9
  import { Modal as re } from "../../components/Modal.js";
10
10
  import { ChartCard as ie } from "../../components/ChartCard.js";
11
11
  import { ToastContainer as ae, useToast as oe } from "../../components/Toast.js";
@@ -132,14 +132,14 @@ function F() {
132
132
  } : {
133
133
  from: u(30),
134
134
  to: d()
135
- }, [S, w]), { data: O, loading: M, error: F, refetch: ve } = a({
135
+ }, [S, w]), { data: O, loading: M, error: F, refetch: ve } = ee({
136
136
  variables: {
137
137
  from: E,
138
138
  to: D
139
139
  },
140
140
  skip: !h,
141
141
  fetchPolicy: "cache-and-network"
142
- }), [ye, { loading: I }] = ee(), L = O?.lifeTimeline, R = L?.summary, z = p(() => [...L?.segments ?? []].sort((e, t) => new Date(e.startTime).getTime() - new Date(t.startTime).getTime()), [L]), B = p(() => R?.byDomain ?? [], [R]), V = R?.totalCoveredSeconds ?? 0, H = R?.unaccountedSeconds ?? 0, be = B.filter((e) => e.domain === s.Work).reduce((e, t) => e + t.totalSeconds, 0), xe = B.filter((e) => e.domain !== s.Work).reduce((e, t) => e + t.totalSeconds, 0), U = p(() => B.filter((e) => e.totalSeconds > 0).map((e) => ({
142
+ }), [ye, { loading: I }] = te(), L = O?.lifeTimeline, R = L?.summary, z = p(() => [...L?.segments ?? []].sort((e, t) => new Date(e.startTime).getTime() - new Date(t.startTime).getTime()), [L]), B = p(() => R?.byDomain ?? [], [R]), V = R?.totalCoveredSeconds ?? 0, H = R?.unaccountedSeconds ?? 0, be = B.filter((e) => e.domain === s.Work).reduce((e, t) => e + t.totalSeconds, 0), xe = B.filter((e) => e.domain !== s.Work).reduce((e, t) => e + t.totalSeconds, 0), U = p(() => B.filter((e) => e.totalSeconds > 0).map((e) => ({
143
143
  name: f(e.domain),
144
144
  value: e.totalSeconds
145
145
  })), [B]), [Se, W] = m(!1), [G, K] = m(c[0]), [q, J] = m(""), [Y, X] = m("30"), [Z, Q] = m(""), Ce = () => {
@@ -180,7 +180,7 @@ function F() {
180
180
  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",
181
181
  children: [/* @__PURE__ */ A(ue, { className: "h-4 w-4" }), "Log life time"]
182
182
  });
183
- return /* @__PURE__ */ j(te, {
183
+ return /* @__PURE__ */ j(ne, {
184
184
  title: "Life timeline",
185
185
  description: "Your whole day — work and life — in one honest thread",
186
186
  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.",
@@ -272,7 +272,7 @@ function F() {
272
272
  "of this range isn’t accounted for yet. Log life time to fill the gaps and see the true shape of your day."
273
273
  ] })]
274
274
  }),
275
- z.length === 0 ? /* @__PURE__ */ A(ne, {
275
+ z.length === 0 ? /* @__PURE__ */ A(a, {
276
276
  illustration: "empty-generic",
277
277
  title: "No life yet in this range",
278
278
  description: "Work sessions land here automatically. Add the rest of your day — sleep, family, health, commute, downtime — and your real timeline assembles in order.",
@@ -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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"}
@@ -2,15 +2,15 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
2
  import { cn as t } from "../../utils/helpers.js";
3
3
  import { StatCard as n } from "../../components/StatCard.js";
4
4
  import { LoadingSkeleton as r } from "../../components/LoadingSkeleton.js";
5
- import { useGrantSourceConsentMutation as i, useMyContextSharingPolicyQuery as a, useMyPersonalDataSummaryQuery as ee, useMySourceConsentsQuery as o, useRevokeSourceConsentMutation as s, useUpdateContextSharingPolicyMutation as c } from "../../../generated/wspace-operations.js";
6
- import { PageLayout as l } from "../../components/PageLayout.js";
7
- import { StatusBadge as u } from "../../components/StatusBadge.js";
8
- import { EmptyState as d } from "../../components/EmptyState.js";
9
- import { ToastContainer as te, useToast as ne } from "../../components/Toast.js";
5
+ import { EmptyState as i } from "../../components/EmptyState.js";
6
+ import { useGrantSourceConsentMutation as a, useMyContextSharingPolicyQuery as o, useMyPersonalDataSummaryQuery as s, useMySourceConsentsQuery as ee, useRevokeSourceConsentMutation as te, useUpdateContextSharingPolicyMutation as ne } from "../../../generated/wspace-operations.js";
7
+ import { PageLayout as c } from "../../components/PageLayout.js";
8
+ import { StatusBadge as l } from "../../components/StatusBadge.js";
9
+ import { ToastContainer as re, useToast as u } from "../../components/Toast.js";
10
10
  import "../../components/index.js";
11
- import { formatSecondsHuman as re, isoDaysAgo as ie, isoNow as ae, lifeDomainLabel as oe } from "./domains.js";
12
- import { useMemo as f, useState as p } from "react";
13
- import { ArrowUpRight as m, Building2 as h, Info as g, Lock as _, Plug as v, Share2 as se, ShieldCheck as ce } from "lucide-react";
11
+ import { formatSecondsHuman as ie, isoDaysAgo as ae, isoNow as d, lifeDomainLabel as f } from "./domains.js";
12
+ import { useMemo as p, useState as m } from "react";
13
+ import { ArrowUpRight as oe, Building2 as se, Info as h, Lock as g, Plug as ce, Share2 as _, ShieldCheck as v } from "lucide-react";
14
14
  import { jsx as y, jsxs as b } from "react/jsx-runtime";
15
15
  import { GlassCard as x } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/timecampus/pages/lifetime/PersonalCorporatePage.tsx
@@ -48,21 +48,21 @@ function w({ checked: e, onChange: n, disabled: r = !1, ariaLabel: i }) {
48
48
  });
49
49
  }
50
50
  function T() {
51
- let { workspaceId: T = "" } = e(), { toasts: le, addToast: E, dismissToast: ue } = ne(), D = f(() => ({
52
- from: ie(30),
53
- to: ae()
54
- }), []), { data: O, loading: k, error: A } = a({ skip: !T }), { data: j, loading: M, error: N, refetch: P } = ee({
51
+ let { workspaceId: T = "" } = e(), { toasts: le, addToast: E, dismissToast: ue } = u(), D = p(() => ({
52
+ from: ae(30),
53
+ to: d()
54
+ }), []), { data: O, loading: k, error: A } = o({ skip: !T }), { data: j, loading: M, error: N, refetch: P } = s({
55
55
  variables: {
56
56
  from: D.from,
57
57
  to: D.to
58
58
  },
59
59
  skip: !T
60
- }), { data: F, loading: I, error: L, refetch: R } = o({ skip: !T }), [z] = c(), [B] = i(), [V] = s(), [H, U] = p(null), [de, W] = p(null), [G, K] = p(null), q = O?.myContextSharingPolicy, J = j?.myPersonalDataSummary, Y = F?.mySourceConsents ?? [], X = f(() => [...J?.byDomain ?? []].sort((e, t) => t.totalSeconds - e.totalSeconds), [J]), Z = X[0]?.totalSeconds ?? 0, Q = J?.sharedAggregates ?? [], $ = {
60
+ }), { data: F, loading: I, error: L, refetch: R } = ee({ skip: !T }), [z] = ne(), [B] = a(), [V] = te(), [H, U] = m(null), [de, W] = m(null), [G, K] = m(null), q = O?.myContextSharingPolicy, J = j?.myPersonalDataSummary, Y = F?.mySourceConsents ?? [], X = p(() => [...J?.byDomain ?? []].sort((e, t) => t.totalSeconds - e.totalSeconds), [J]), Z = X[0]?.totalSeconds ?? 0, Q = J?.sharedAggregates ?? [], $ = {
61
61
  title: "Personal & Corporate",
62
62
  description: "Two contexts, one boundary you control",
63
63
  purpose: "TimeCampus keeps two worlds apart: your private life record, which stays yours, and the workspace you work in. This is where you decide exactly which aggregate work signals — never your personal context — travel up to your employer."
64
64
  };
65
- if (!T || k && !O || M && !j) return /* @__PURE__ */ y(l, {
65
+ if (!T || k && !O || M && !j) return /* @__PURE__ */ y(c, {
66
66
  ...$,
67
67
  children: /* @__PURE__ */ b("div", {
68
68
  className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
@@ -82,7 +82,7 @@ function T() {
82
82
  ]
83
83
  })
84
84
  });
85
- if (A && N && !q && !J) return /* @__PURE__ */ y(l, {
85
+ if (A && N && !q && !J) return /* @__PURE__ */ y(c, {
86
86
  ...$,
87
87
  children: /* @__PURE__ */ y("div", {
88
88
  className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
@@ -164,7 +164,7 @@ function T() {
164
164
  W(null);
165
165
  }
166
166
  };
167
- return /* @__PURE__ */ b(l, {
167
+ return /* @__PURE__ */ b(c, {
168
168
  ...$,
169
169
  children: [
170
170
  /* @__PURE__ */ b("div", {
@@ -177,7 +177,7 @@ function T() {
177
177
  className: "mb-4 flex items-start gap-3",
178
178
  children: [/* @__PURE__ */ y("div", {
179
179
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
180
- children: /* @__PURE__ */ y(_, { className: "h-5 w-5" })
180
+ children: /* @__PURE__ */ y(g, { className: "h-5 w-5" })
181
181
  }), /* @__PURE__ */ b("div", {
182
182
  className: "min-w-0",
183
183
  children: [/* @__PURE__ */ b("h2", {
@@ -198,7 +198,7 @@ function T() {
198
198
  }), N && !J ? /* @__PURE__ */ y("div", {
199
199
  className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-3 text-xs text-status-error-text",
200
200
  children: "Couldn't load your private data summary right now."
201
- }) : !J || J.totalPersonalEvents === 0 ? /* @__PURE__ */ y(d, {
201
+ }) : !J || J.totalPersonalEvents === 0 ? /* @__PURE__ */ y(i, {
202
202
  illustration: "empty-data",
203
203
  title: "Nothing private captured yet",
204
204
  description: "Log your first life entry and it will live here — always private to you."
@@ -207,7 +207,7 @@ function T() {
207
207
  children: [/* @__PURE__ */ y(n, {
208
208
  title: "Private life entries",
209
209
  value: J.totalPersonalEvents.toLocaleString(),
210
- icon: /* @__PURE__ */ y(_, { className: "h-5 w-5" }),
210
+ icon: /* @__PURE__ */ y(g, { className: "h-5 w-5" }),
211
211
  description: "captured in the last 30 days"
212
212
  }), X.length === 0 ? /* @__PURE__ */ y("p", {
213
213
  className: "text-xs text-text-secondary",
@@ -224,11 +224,11 @@ function T() {
224
224
  className: "min-w-0",
225
225
  children: [/* @__PURE__ */ y("p", {
226
226
  className: "truncate text-sm font-medium text-text-primary",
227
- children: oe(e.domain)
227
+ children: f(e.domain)
228
228
  }), /* @__PURE__ */ b("p", {
229
229
  className: "mt-0.5 text-xs text-text-secondary",
230
230
  children: [
231
- re(e.totalSeconds),
231
+ ie(e.totalSeconds),
232
232
  " ·",
233
233
  " ",
234
234
  e.eventCount.toLocaleString(),
@@ -236,7 +236,7 @@ function T() {
236
236
  e.eventCount === 1 ? "entry" : "entries"
237
237
  ]
238
238
  })]
239
- }), /* @__PURE__ */ y(_, {
239
+ }), /* @__PURE__ */ y(g, {
240
240
  className: "h-3.5 w-3.5 shrink-0 text-text-muted",
241
241
  "aria-label": "Private to you"
242
242
  })]
@@ -262,7 +262,7 @@ function T() {
262
262
  className: "mb-4 flex items-start gap-3",
263
263
  children: [/* @__PURE__ */ y("div", {
264
264
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg/40 text-status-info-text",
265
- children: /* @__PURE__ */ y(se, { className: "h-5 w-5" })
265
+ children: /* @__PURE__ */ y(_, { className: "h-5 w-5" })
266
266
  }), /* @__PURE__ */ b("div", {
267
267
  className: "min-w-0",
268
268
  children: [/* @__PURE__ */ y("h2", {
@@ -316,7 +316,7 @@ function T() {
316
316
  className: "flex items-center gap-3",
317
317
  children: [/* @__PURE__ */ y("div", {
318
318
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-success-bg/40 text-status-success-text",
319
- children: /* @__PURE__ */ y(ce, { className: "h-5 w-5" })
319
+ children: /* @__PURE__ */ y(v, { className: "h-5 w-5" })
320
320
  }), /* @__PURE__ */ y("p", {
321
321
  className: "text-sm font-semibold text-text-primary",
322
322
  children: "Your employer never sees your personal context."
@@ -331,7 +331,7 @@ function T() {
331
331
  className: "mb-4 flex items-start gap-3",
332
332
  children: [/* @__PURE__ */ y("div", {
333
333
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
334
- children: /* @__PURE__ */ y(h, { className: "h-5 w-5" })
334
+ children: /* @__PURE__ */ y(se, { className: "h-5 w-5" })
335
335
  }), /* @__PURE__ */ b("div", {
336
336
  className: "min-w-0",
337
337
  children: [/* @__PURE__ */ b("h2", {
@@ -354,7 +354,7 @@ function T() {
354
354
  children: [
355
355
  /* @__PURE__ */ b("div", {
356
356
  className: "flex items-start gap-2 rounded-lg border border-status-info-border/40 bg-status-info-bg/10 px-3 py-2.5 text-xs text-status-info-text",
357
- children: [/* @__PURE__ */ y(g, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ y("span", { children: "Your workspace only ever receives aggregate work signals — coarse yes/no bands, never entries, locations, health or timestamps." })]
357
+ children: [/* @__PURE__ */ y(h, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ y("span", { children: "Your workspace only ever receives aggregate work signals — coarse yes/no bands, never entries, locations, health or timestamps." })]
358
358
  }),
359
359
  /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("p", {
360
360
  className: "mb-2 text-xs font-medium uppercase tracking-wide text-text-muted",
@@ -374,11 +374,11 @@ function T() {
374
374
  className: "flex items-center justify-between gap-3 rounded-lg border border-border-subtle bg-bg-sunken/40 px-3 py-2.5",
375
375
  children: [/* @__PURE__ */ b("span", {
376
376
  className: "flex min-w-0 items-center gap-2 text-sm text-text-primary",
377
- children: [/* @__PURE__ */ y(m, { className: "h-4 w-4 shrink-0 text-status-success-text" }), /* @__PURE__ */ y("span", {
377
+ children: [/* @__PURE__ */ y(oe, { className: "h-4 w-4 shrink-0 text-status-success-text" }), /* @__PURE__ */ y("span", {
378
378
  className: "truncate",
379
379
  children: C(e)
380
380
  })]
381
- }), /* @__PURE__ */ y(u, {
381
+ }), /* @__PURE__ */ y(l, {
382
382
  variant: "success",
383
383
  label: "Shared",
384
384
  dot: !0
@@ -401,7 +401,7 @@ function T() {
401
401
  className: "mb-4 flex items-start gap-3",
402
402
  children: [/* @__PURE__ */ y("div", {
403
403
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
404
- children: /* @__PURE__ */ y(v, { className: "h-5 w-5" })
404
+ children: /* @__PURE__ */ y(ce, { className: "h-5 w-5" })
405
405
  }), /* @__PURE__ */ b("div", {
406
406
  className: "min-w-0",
407
407
  children: [/* @__PURE__ */ y("h3", {
@@ -418,7 +418,7 @@ function T() {
418
418
  }) : I && Y.length === 0 ? /* @__PURE__ */ y(r, {
419
419
  variant: "list",
420
420
  count: 3
421
- }) : Y.length === 0 ? /* @__PURE__ */ y(d, {
421
+ }) : Y.length === 0 ? /* @__PURE__ */ y(i, {
422
422
  illustration: "empty-data",
423
423
  title: "No sources connected yet",
424
424
  description: "When you connect a capture source — a calendar, location or health signal — you'll grant and revoke exactly what it may feed here."
@@ -439,7 +439,7 @@ function T() {
439
439
  }), /* @__PURE__ */ b("div", {
440
440
  className: "mt-1.5 flex flex-wrap items-center gap-1.5",
441
441
  children: [
442
- /* @__PURE__ */ y(u, {
442
+ /* @__PURE__ */ y(l, {
443
443
  variant: "muted",
444
444
  label: C(e.mode)
445
445
  }),
@@ -452,7 +452,7 @@ function T() {
452
452
  className: t("rounded-full border px-2 py-0.5 text-[0.65rem] font-medium uppercase tracking-wide transition-colors", "focus:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50", e.onDeviceOnly ? "border-status-info-border/60 bg-status-info-bg/40 text-status-info-text" : "border-border-subtle bg-bg-sunken/40 text-text-muted hover:text-text-secondary", G === e.source && "cursor-not-allowed opacity-50"),
453
453
  children: G === e.source ? "Saving…" : e.onDeviceOnly ? "On-device only" : "Turn on on-device only"
454
454
  }),
455
- e.retentionDays != null && /* @__PURE__ */ y(u, {
455
+ e.retentionDays != null && /* @__PURE__ */ y(l, {
456
456
  variant: "muted",
457
457
  label: `Kept ${e.retentionDays}d`
458
458
  })
@@ -475,7 +475,7 @@ function T() {
475
475
  })
476
476
  })]
477
477
  }),
478
- /* @__PURE__ */ y(te, {
478
+ /* @__PURE__ */ y(re, {
479
479
  toasts: le,
480
480
  onDismiss: ue
481
481
  })