@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.
- package/dist/timecampus/components/index.js +4 -4
- package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
- package/dist/timecampus/pages/HomePage.js +149 -133
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
- package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
- package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +14 -14
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- 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 {
|
|
6
|
-
import {
|
|
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 } =
|
|
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 }] =
|
|
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(
|
|
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(
|
|
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’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’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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { ToastContainer as
|
|
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
|
|
12
|
-
import { useMemo as
|
|
13
|
-
import { ArrowUpRight as
|
|
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 } =
|
|
52
|
-
from:
|
|
53
|
-
to:
|
|
54
|
-
}), []), { data: O, loading: k, error: A } =
|
|
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 } =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
478
|
+
/* @__PURE__ */ y(re, {
|
|
479
479
|
toasts: le,
|
|
480
480
|
onDismiss: ue
|
|
481
481
|
})
|