@burdenoff/microfe-timecampus 2026.830.2 → 2026.903.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 +142 -131
- 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":"RecommendationsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from \"react\";\nimport {\n Sparkles,\n Check,\n CheckCircle2,\n ChevronDown,\n X,\n Clock,\n BellOff,\n CalendarClock,\n Microscope,\n ShieldCheck,\n Lightbulb,\n Briefcase,\n Moon,\n HeartPulse,\n Users,\n GraduationCap,\n Coffee,\n Bus,\n Hourglass,\n Home,\n Heart,\n Handshake,\n Wallet,\n Circle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n Modal,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { cn } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyRecommendationsQuery,\n useMyRecommendationPreferencesQuery,\n useGenerateMyRecommendationsMutation,\n useAcceptRecommendationMutation,\n useDismissRecommendationMutation,\n useSnoozeRecommendationMutation,\n useUpdateMyRecommendationPreferencesMutation,\n type RecommendationFieldsFragment,\n type RecommendationPreferenceFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel } from \"./domains\";\nimport {\n formatEvidenceRef,\n formatHourOfDay,\n humaniseToken,\n parseEvidenceRefs,\n parseSuggestedAction,\n} from \"./recommendationEvidence\";\n\ntype RecItem = RecommendationFieldsFragment;\ntype PrefItem = RecommendationPreferenceFieldsFragment;\n\n// ---------------------------------------------------------------------------\n// Recommendation states are stored as free strings on the backend\n// (NEW / ACCEPTED / DISMISSED / SNOOZED / EXPIRED — see the svc schema). We\n// segment the feed by them; unknown states simply don't match a tab.\n// ---------------------------------------------------------------------------\nconst REC_STATE = {\n New: \"NEW\",\n Snoozed: \"SNOOZED\",\n Accepted: \"ACCEPTED\",\n Dismissed: \"DISMISSED\",\n Expired: \"EXPIRED\",\n} as const;\n\nconst SEGMENTS = [\n { key: REC_STATE.New, label: \"New\" },\n { key: REC_STATE.Snoozed, label: \"Snoozed\" },\n { key: REC_STATE.Accepted, label: \"Accepted\" },\n { key: REC_STATE.Dismissed, label: \"Dismissed\" },\n] as const;\n\ntype SegmentKey = (typeof SEGMENTS)[number][\"key\"];\n\n/** A NEW or SNOOZED recommendation is still actionable (accept / snooze / dismiss). */\nfunction isActionable(state: string): boolean {\n return state === REC_STATE.New || state === REC_STATE.Snoozed;\n}\n\n/** Snooze a week out — long enough to be non-nagging, short enough to resurface. */\nfunction snoozeUntilIso(): string {\n return new Date(Date.now() + 7 * 86_400_000).toISOString();\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst asPercent = (n: number) => `${Math.round(clamp01(n) * 100)}%`;\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n}\n\n/** Hours of the day offered by the quiet-hours pickers. */\nconst HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => hour);\n\nconst DOMAIN_ICON: Record<\n TimeCampusLifeDomain,\n ComponentType<{ className?: string }>\n> = {\n [TimeCampusLifeDomain.Work]: Briefcase,\n [TimeCampusLifeDomain.Sleep]: Moon,\n [TimeCampusLifeDomain.Health]: HeartPulse,\n [TimeCampusLifeDomain.Family]: Users,\n [TimeCampusLifeDomain.Learning]: GraduationCap,\n [TimeCampusLifeDomain.Leisure]: Coffee,\n [TimeCampusLifeDomain.Commute]: Bus,\n [TimeCampusLifeDomain.Waiting]: Hourglass,\n [TimeCampusLifeDomain.Chores]: Home,\n [TimeCampusLifeDomain.SelfCare]: Heart,\n [TimeCampusLifeDomain.Social]: Handshake,\n [TimeCampusLifeDomain.FinanceAdmin]: Wallet,\n [TimeCampusLifeDomain.Reflection]: Sparkles,\n [TimeCampusLifeDomain.Other]: Circle,\n};\n\nfunction recIcon(\n domain?: TimeCampusLifeDomain | null,\n): ComponentType<{ className?: string }> {\n if (!domain) return Lightbulb;\n return DOMAIN_ICON[domain] ?? Lightbulb;\n}\n\ntype BadgeTone = \"success\" | \"warning\" | \"muted\" | \"info\";\n\nfunction stateBadge(state: string): { variant: BadgeTone; label: string } {\n switch (state) {\n case REC_STATE.Accepted:\n return { variant: \"success\", label: \"Accepted\" };\n case REC_STATE.Snoozed:\n return { variant: \"warning\", label: \"Snoozed\" };\n case REC_STATE.Dismissed:\n return { variant: \"muted\", label: \"Dismissed\" };\n case REC_STATE.Expired:\n return { variant: \"muted\", label: \"Expired\" };\n default:\n return { variant: \"info\", label: \"New\" };\n }\n}\n\nconst primaryBtn =\n \"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 hover:bg-action-primary-bg/90 disabled:opacity-50\";\nconst neutralBtn =\n \"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\";\nconst chipClass =\n \"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\";\n\n/**\n * Local, token-styled switch (the shared UI kit isn't imported here). The knob\n * translates only on the checked state — never on hover — per the repo's\n * no-hover-movement mandate.\n */\nfunction ToggleSwitch({\n checked,\n onChange,\n label,\n disabled,\n}: {\n checked: boolean;\n onChange: (next: boolean) => void;\n label: string;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={cn(\n \"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50\",\n checked\n ? \"border-transparent bg-action-primary-bg\"\n : \"border-border-subtle bg-bg-sunken\",\n )}\n >\n <span\n className={cn(\n \"inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform\",\n checked\n ? \"translate-x-4 bg-action-primary-text\"\n : \"translate-x-0.5 bg-text-secondary\",\n )}\n />\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Recommendation card\n// ---------------------------------------------------------------------------\ninterface RecommendationCardProps {\n rec: RecItem;\n busy: boolean;\n muted: boolean;\n onAccept: (id: string) => void;\n onSnooze: (id: string) => void;\n onDismiss: (rec: RecItem) => void;\n onMuteKind: (kind: string) => void;\n}\n\nfunction RecommendationCard({\n rec,\n busy,\n muted,\n onAccept,\n onSnooze,\n onDismiss,\n onMuteKind,\n}: RecommendationCardProps) {\n const Icon = recIcon(rec.domain);\n const actionable = isActionable(rec.state);\n const badge = stateBadge(rec.state);\n const [evidenceOpen, setEvidenceOpen] = useState(false);\n\n // Both come off this card's own query result — the JSON the generator wrote\n // when it raised the recommendation. Nothing is derived or estimated here.\n const evidence = parseEvidenceRefs(rec.evidenceRefs);\n const action = parseSuggestedAction(rec.suggestedAction);\n const evidenceId = `rec-evidence-${rec.id}`;\n\n return (\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {humaniseToken(rec.kind)}\n </p>\n <h3 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {rec.title}\n </h3>\n </div>\n </div>\n {rec.confidence != null && (\n <StatusBadge\n variant=\"info\"\n label={`${asPercent(rec.confidence)} confident`}\n className=\"shrink-0\"\n />\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary\">{rec.rationale}</p>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {rec.domain && (\n <span className={chipClass}>{lifeDomainLabel(rec.domain)}</span>\n )}\n {rec.valueAlignment != null && (\n <span className={chipClass}>\n <ShieldCheck className=\"h-3 w-3\" />\n Aligns with your values · {asPercent(rec.valueAlignment)}\n </span>\n )}\n {rec.state === REC_STATE.Snoozed && rec.snoozeUntil && (\n <span className={chipClass}>\n <Clock className=\"h-3 w-3\" />\n Snoozed until {formatDate(rec.snoozeUntil)}\n </span>\n )}\n {actionable && rec.expiresAt && (\n <span className={chipClass}>\n <CalendarClock className=\"h-3 w-3\" />\n Expires {formatDate(rec.expiresAt)}\n </span>\n )}\n </div>\n\n {/* The concrete next step the generator attached, if any. */}\n {action && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Suggested action\n </p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {humaniseToken(action.type)}\n </p>\n {action.details.length > 0 && (\n <dl className=\"mt-1.5 flex flex-wrap gap-x-4 gap-y-1\">\n {action.details.map((detail) => (\n <div key={detail.key} className=\"flex items-center gap-1.5\">\n <dt className=\"text-xs text-text-secondary\">{detail.key}</dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {detail.value}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {/* Why this was raised — the exact metrics the generator measured. */}\n {evidence.length > 0 && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40\">\n <button\n type=\"button\"\n onClick={() => setEvidenceOpen((open) => !open)}\n aria-expanded={evidenceOpen}\n aria-controls={evidenceId}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left\"\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n <Microscope className=\"h-3.5 w-3.5 shrink-0 text-text-secondary\" />\n <span className=\"truncate text-xs text-text-secondary\">\n Evidence: {formatEvidenceRef(evidence[0])}\n {evidence.length > 1 ? ` · +${evidence.length - 1} more` : \"\"}\n </span>\n </span>\n <ChevronDown\n className={cn(\n \"h-4 w-4 shrink-0 text-text-secondary transition-transform\",\n evidenceOpen && \"rotate-180\",\n )}\n />\n </button>\n {evidenceOpen && (\n <dl\n id={evidenceId}\n className=\"space-y-1.5 border-t border-border-seam px-3 py-2.5\"\n >\n {evidence.map((ref, index) => (\n <div\n key={`${ref.metric}-${ref.period ?? ref.domain ?? index}`}\n className=\"flex items-baseline justify-between gap-3\"\n >\n <dt className=\"text-xs text-text-secondary\">\n {formatEvidenceRef(ref).split(\": \")[0]}\n </dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {formatEvidenceRef(ref).split(\": \").slice(1).join(\": \")}\n </dd>\n </div>\n ))}\n <p className=\"pt-1 text-xs text-text-tertiary\">\n Measured from your own tracked week. Nothing is applied until\n you accept.\n </p>\n </dl>\n )}\n </div>\n )}\n\n {actionable ? (\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={() => onAccept(rec.id)}\n disabled={busy}\n className={primaryBtn}\n >\n <Check className=\"h-4 w-4\" />\n Accept\n </button>\n <button\n type=\"button\"\n onClick={() => onSnooze(rec.id)}\n disabled={busy}\n className={neutralBtn}\n >\n <Clock className=\"h-4 w-4\" />\n Snooze\n </button>\n <button\n type=\"button\"\n onClick={() => onDismiss(rec)}\n disabled={busy}\n className={neutralBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n <button\n type=\"button\"\n onClick={() => onMuteKind(rec.kind)}\n disabled={busy || muted}\n className={neutralBtn}\n title={\n muted\n ? `${humaniseToken(rec.kind)} suggestions are already muted`\n : `Stop generating ${humaniseToken(rec.kind)} suggestions`\n }\n >\n <BellOff className=\"h-4 w-4\" />\n {muted ? \"Muted\" : \"Mute this kind\"}\n </button>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2 pt-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <StatusBadge variant={badge.variant} label={badge.label} />\n <span className=\"text-xs text-text-secondary\">\n {formatDate(rec.createdAt)}\n </span>\n </div>\n {rec.state === REC_STATE.Dismissed && rec.dismissReason && (\n <p className=\"text-xs text-text-secondary\">\n Reason given: {rec.dismissReason}\n </p>\n )}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Guardrails — the preferences that decide what may be suggested at all.\n// Every value here is read from `myRecommendationPreferences`; the controls\n// write back through `updateMyRecommendationPreferences`.\n// ---------------------------------------------------------------------------\ninterface GuardrailsPanelProps {\n prefs: PrefItem | undefined;\n loading: boolean;\n saving: boolean;\n onToggleOutputGuard: (next: boolean) => void;\n onQuietHoursChange: (start: number | null, end: number | null) => void;\n onUnmuteKind: (kind: string) => void;\n}\n\nfunction GuardrailsPanel({\n prefs,\n loading,\n saving,\n onToggleOutputGuard,\n onQuietHoursChange,\n onUnmuteKind,\n}: GuardrailsPanelProps) {\n if (loading) {\n return <LoadingSkeleton variant=\"card\" count={1} />;\n }\n if (!prefs) return null;\n\n const quietStart = formatHourOfDay(prefs.quietHoursStart);\n const quietEnd = formatHourOfDay(prefs.quietHoursEnd);\n const quietSet = quietStart !== null && quietEnd !== null;\n\n const hourSelectClass =\n \"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 disabled:opacity-50\";\n\n return (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-5\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">Guardrails</h3>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n What TimeCampus is allowed to suggest, and when.\n </p>\n\n <div className=\"mt-4 space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n Never suggest more work\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Output-increasing suggestions are filtered out before they reach\n you.\n </p>\n </div>\n <ToggleSwitch\n checked={prefs.neverOptimiseForOutput}\n disabled={saving}\n label=\"Never suggest more work\"\n onChange={onToggleOutputGuard}\n />\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Quiet hours</p>\n <span className=\"text-xs text-text-secondary\">\n {quietSet ? `${quietStart}–${quietEnd}` : \"Not set\"}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n No nudges are sent inside this window.\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n From\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursStart ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n prefs.quietHoursEnd ?? null,\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n To\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursEnd ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n prefs.quietHoursStart ?? null,\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n {quietSet && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => onQuietHoursChange(null, null)}\n className=\"text-xs font-medium text-text-secondary underline-offset-2 hover:text-text-primary hover:underline disabled:opacity-50\"\n >\n Clear\n </button>\n )}\n </div>\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Muted kinds</p>\n <span className={chipClass}>{prefs.mutedKinds.length}</span>\n </div>\n {prefs.mutedKinds.length === 0 ? (\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Nothing muted — mute a kind from any suggestion to stop it coming\n back.\n </p>\n ) : (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {prefs.mutedKinds.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n disabled={saving}\n onClick={() => onUnmuteKind(kind)}\n className={cn(\n chipClass,\n \"hover:text-text-primary disabled:opacity-50\",\n )}\n title={`Unmute ${humaniseToken(kind)} suggestions`}\n >\n {humaniseToken(kind)}\n <X className=\"h-3 w-3\" />\n </button>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Cadence</p>\n <span className=\"text-xs text-text-secondary\">\n {humaniseToken(prefs.cadence)}\n </span>\n </div>\n {prefs.optedInDomains.length > 0 && (\n <>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n Opted-in domains\n </p>\n <div className=\"mt-1.5 flex flex-wrap gap-1.5\">\n {prefs.optedInDomains.map((domain) => (\n <span key={domain} className={chipClass}>\n {lifeDomainLabel(domain)}\n </span>\n ))}\n </div>\n </>\n )}\n </div>\n </div>\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nfunction segmentEmptyCopy(segment: SegmentKey): {\n title: string;\n description: string;\n} {\n switch (segment) {\n case REC_STATE.Snoozed:\n return {\n title: \"Nothing snoozed\",\n description:\n \"Suggestions you snooze for later will wait here quietly until they're worth another look.\",\n };\n case REC_STATE.Accepted:\n return {\n title: \"Nothing accepted yet\",\n description:\n \"When you accept a suggestion it moves here, so you can see the changes you chose to make.\",\n };\n case REC_STATE.Dismissed:\n return {\n title: \"Nothing dismissed\",\n description:\n \"Suggestions you dismiss are kept here — TimeCampus learns from them and won't keep nagging.\",\n };\n default:\n return {\n title: \"You're all caught up\",\n description:\n \"No new suggestions right now. Generate again after a few more days of tracked time.\",\n };\n }\n}\n\nexport function RecommendationsPage() {\n const { workspaceId } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const [segment, setSegment] = useState<SegmentKey>(REC_STATE.New);\n const [busyId, setBusyId] = useState<string | null>(null);\n const [dismissTarget, setDismissTarget] = useState<RecItem | null>(null);\n const [dismissReason, setDismissReason] = useState(\"\");\n\n const { data, loading, error, refetch } = useMyRecommendationsQuery({\n variables: { state: null },\n skip: !workspaceId,\n });\n\n const {\n data: prefsData,\n loading: prefsLoading,\n refetch: refetchPrefs,\n } = useMyRecommendationPreferencesQuery({ skip: !workspaceId });\n\n const [generate, { loading: generating }] =\n useGenerateMyRecommendationsMutation();\n const [acceptRec] = useAcceptRecommendationMutation();\n const [dismissRec] = useDismissRecommendationMutation();\n const [snoozeRec] = useSnoozeRecommendationMutation();\n const [updatePrefs, { loading: savingPrefs }] =\n useUpdateMyRecommendationPreferencesMutation();\n\n const prefs = prefsData?.myRecommendationPreferences;\n const mutedKinds = useMemo<Set<string>>(\n () => new Set<string>(prefs?.mutedKinds ?? []),\n [prefs?.mutedKinds],\n );\n\n const recommendations: RecItem[] = useMemo(\n () => data?.myRecommendations ?? [],\n [data?.myRecommendations],\n );\n\n const counts = useMemo(() => {\n const map: Record<string, number> = {};\n for (const rec of recommendations) {\n map[rec.state] = (map[rec.state] ?? 0) + 1;\n }\n return map;\n }, [recommendations]);\n\n const visible = useMemo(\n () => recommendations.filter((rec) => rec.state === segment),\n [recommendations, segment],\n );\n\n const newCount = counts[REC_STATE.New] ?? 0;\n const acceptedCount = counts[REC_STATE.Accepted] ?? 0;\n const snoozedCount = counts[REC_STATE.Snoozed] ?? 0;\n\n const handleGenerate = async () => {\n try {\n const res = await generate();\n const generated = res.data?.generateMyRecommendations ?? [];\n addToast({\n title:\n generated.length > 0\n ? `${generated.length} suggestion${generated.length === 1 ? \"\" : \"s\"} ready`\n : \"No new suggestions\",\n description:\n generated.length > 0\n ? \"Fresh, values-aware ideas are in your New feed.\"\n : \"You're all caught up — nothing new to suggest right now.\",\n variant: generated.length > 0 ? \"success\" : \"info\",\n });\n setSegment(REC_STATE.New);\n await refetch();\n } catch {\n addToast({\n title: \"Couldn't generate recommendations\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleAccept = async (id: string) => {\n setBusyId(id);\n try {\n await acceptRec({ variables: { id } });\n addToast({ title: \"Accepted — nice\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't accept that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleSnooze = async (id: string) => {\n setBusyId(id);\n try {\n await snoozeRec({ variables: { id, until: snoozeUntilIso() } });\n addToast({\n title: \"Snoozed for a week\",\n description: \"It'll resurface later — no nagging in between.\",\n variant: \"info\",\n });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't snooze that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const requestDismiss = (rec: RecItem) => {\n setDismissReason(\"\");\n setDismissTarget(rec);\n };\n\n /**\n * Dismissing asks for an optional reason first — `dismissRecommendation`\n * takes one and the card reads it back, so the note the user leaves is the\n * note they later see.\n */\n const handleConfirmDismiss = async () => {\n const target = dismissTarget;\n if (!target) return;\n const reason = dismissReason.trim();\n setBusyId(target.id);\n try {\n await dismissRec({\n variables: { id: target.id, reason: reason.length > 0 ? reason : null },\n });\n addToast({ title: \"Dismissed\", variant: \"success\" });\n setDismissTarget(null);\n setDismissReason(\"\");\n await refetch();\n } catch {\n addToast({ title: \"Couldn't dismiss that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n /** One write path for every guardrail control, so failures read the same. */\n const savePrefs = async (\n input: {\n neverOptimiseForOutput?: boolean;\n quietHoursStart?: number | null;\n quietHoursEnd?: number | null;\n mutedKinds?: string[];\n },\n successTitle: string,\n ) => {\n try {\n await updatePrefs({ variables: { input } });\n addToast({ title: successTitle, variant: \"success\" });\n await refetchPrefs();\n } catch {\n addToast({\n title: \"Couldn't save that guardrail\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleToggleOutputGuard = (next: boolean) =>\n void savePrefs(\n { neverOptimiseForOutput: next },\n next\n ? \"Output-increasing suggestions are off\"\n : \"Output-increasing suggestions allowed\",\n );\n\n const handleQuietHoursChange = (start: number | null, end: number | null) =>\n void savePrefs(\n { quietHoursStart: start, quietHoursEnd: end },\n start === null && end === null\n ? \"Quiet hours cleared\"\n : \"Quiet hours saved\",\n );\n\n const handleMuteKind = (kind: string) => {\n if (mutedKinds.has(kind)) return;\n void savePrefs(\n { mutedKinds: [...mutedKinds, kind] },\n `${humaniseToken(kind)} suggestions muted`,\n );\n };\n\n const handleUnmuteKind = (kind: string) =>\n void savePrefs(\n { mutedKinds: [...mutedKinds].filter((entry) => entry !== kind) },\n `${humaniseToken(kind)} suggestions unmuted`,\n );\n\n const guardrailsPanel = (\n <GuardrailsPanel\n prefs={prefs}\n loading={prefsLoading}\n saving={savingPrefs}\n onToggleOutputGuard={handleToggleOutputGuard}\n onQuietHoursChange={handleQuietHoursChange}\n onUnmuteKind={handleUnmuteKind}\n />\n );\n\n const generateButton = (\n <button\n type=\"button\"\n onClick={handleGenerate}\n disabled={generating || !workspaceId}\n className={primaryBtn}\n >\n <Sparkles className=\"h-4 w-4\" />\n {generating ? \"Generating…\" : \"Generate recommendations\"}\n </button>\n );\n\n const purpose =\n \"Gentle, values-aware nudges built from your real timeline — protect a focus block, \" +\n \"reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and \" +\n \"dismiss what doesn't. TimeCampus never optimises for more output — only for time that \" +\n \"matches what you value.\";\n\n const footerNote = (\n <GlassCard treatment=\"insight\" className=\"mt-6 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg text-status-info-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Never optimises for more output.\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Every suggestion protects something you value — rest, focus,\n relationships — not your throughput. Snooze or dismiss freely;\n TimeCampus takes the hint and won't keep nagging.\n </p>\n </div>\n </div>\n </GlassCard>\n );\n\n if (!workspaceId || loading) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Couldn't load your recommendations right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const hasAny = recommendations.length > 0;\n const emptyCopy = segmentEmptyCopy(segment);\n\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n {!hasAny ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div>\n <EmptyState\n illustration=\"empty-data\"\n title=\"No recommendations yet\"\n description=\"TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.\"\n action={generateButton}\n />\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div className=\"min-w-0 space-y-6\">\n {/* Summary of where each suggestion stands — from the query only. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"New for you\"\n value={String(newCount)}\n icon={<Sparkles className=\"h-5 w-5\" />}\n description=\"Awaiting your call\"\n />\n <StatCard\n title=\"Accepted\"\n value={String(acceptedCount)}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n description=\"Changes you chose to make\"\n />\n <StatCard\n title=\"Snoozed\"\n value={String(snoozedCount)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Waiting for a better moment\"\n />\n </div>\n\n {/* Segmented control by state. */}\n <div className=\"overflow-x-auto\">\n <div\n className=\"inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\"\n role=\"tablist\"\n aria-label=\"Filter recommendations by state\"\n >\n {SEGMENTS.map((seg) => {\n const active = seg.key === segment;\n const count = counts[seg.key] ?? 0;\n return (\n <button\n key={seg.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => setSegment(seg.key)}\n className={\n active\n ? \"flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm\"\n : \"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n }\n >\n {seg.label}\n <span\n className={\n active\n ? \"rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary\"\n : \"rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary\"\n }\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* The feed for the active segment. */}\n {visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyCopy.title}\n description={emptyCopy.description}\n action={segment === REC_STATE.New ? generateButton : undefined}\n />\n ) : (\n <div className=\"space-y-4\">\n {visible.map((rec) => (\n <RecommendationCard\n key={rec.id}\n rec={rec}\n busy={busyId === rec.id || savingPrefs}\n muted={mutedKinds.has(rec.kind)}\n onAccept={handleAccept}\n onSnooze={handleSnooze}\n onDismiss={requestDismiss}\n onMuteKind={handleMuteKind}\n />\n ))}\n </div>\n )}\n\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n )}\n\n {/* Dismissing captures the optional reason the mutation accepts, so the\n note is there when the card is read back under Dismissed. */}\n <Modal\n open={dismissTarget !== null}\n onClose={() => setDismissTarget(null)}\n title=\"Dismiss this suggestion\"\n description={dismissTarget?.title}\n size=\"sm\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setDismissTarget(null)}\n className={neutralBtn}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleConfirmDismiss()}\n disabled={busyId !== null}\n className={primaryBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n </>\n }\n >\n <label\n htmlFor=\"rec-dismiss-reason\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Why isn't this one for you?{\" \"}\n <span className=\"font-normal text-text-secondary\">(optional)</span>\n </label>\n <textarea\n id=\"rec-dismiss-reason\"\n rows={3}\n value={dismissReason}\n onChange={(event) => setDismissReason(event.target.value)}\n placeholder=\"e.g. already handled, not the right week\"\n className=\"mt-1.5 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n Saved with the suggestion so you can see why you passed on it later.\n To stop this kind of suggestion entirely, use Mute this kind.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecommendationsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/RecommendationsPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from \"react\";\nimport {\n Sparkles,\n Check,\n CheckCircle2,\n ChevronDown,\n X,\n Clock,\n BellOff,\n CalendarClock,\n Microscope,\n ShieldCheck,\n Lightbulb,\n Briefcase,\n Moon,\n HeartPulse,\n Users,\n GraduationCap,\n Coffee,\n Bus,\n Hourglass,\n Home,\n Heart,\n Handshake,\n Wallet,\n Circle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n StatusBadge,\n EmptyState,\n LoadingSkeleton,\n Modal,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { cn } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyRecommendationsQuery,\n useMyRecommendationPreferencesQuery,\n useGenerateMyRecommendationsMutation,\n useAcceptRecommendationMutation,\n useDismissRecommendationMutation,\n useSnoozeRecommendationMutation,\n useUpdateMyRecommendationPreferencesMutation,\n type RecommendationFieldsFragment,\n type RecommendationPreferenceFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { lifeDomainLabel } from \"./domains\";\nimport {\n formatEvidenceRef,\n formatHourOfDay,\n humaniseToken,\n parseEvidenceRefs,\n parseSuggestedAction,\n} from \"./recommendationEvidence\";\n\ntype RecItem = RecommendationFieldsFragment;\ntype PrefItem = RecommendationPreferenceFieldsFragment;\n\n// ---------------------------------------------------------------------------\n// Recommendation states are stored as free strings on the backend\n// (NEW / ACCEPTED / DISMISSED / SNOOZED / EXPIRED — see the svc schema). We\n// segment the feed by them; unknown states simply don't match a tab.\n// ---------------------------------------------------------------------------\nconst REC_STATE = {\n New: \"NEW\",\n Snoozed: \"SNOOZED\",\n Accepted: \"ACCEPTED\",\n Dismissed: \"DISMISSED\",\n Expired: \"EXPIRED\",\n} as const;\n\nconst SEGMENTS = [\n { key: REC_STATE.New, label: \"New\" },\n { key: REC_STATE.Snoozed, label: \"Snoozed\" },\n { key: REC_STATE.Accepted, label: \"Accepted\" },\n { key: REC_STATE.Dismissed, label: \"Dismissed\" },\n] as const;\n\ntype SegmentKey = (typeof SEGMENTS)[number][\"key\"];\n\n/** A NEW or SNOOZED recommendation is still actionable (accept / snooze / dismiss). */\nfunction isActionable(state: string): boolean {\n return state === REC_STATE.New || state === REC_STATE.Snoozed;\n}\n\n/** Snooze a week out — long enough to be non-nagging, short enough to resurface. */\nfunction snoozeUntilIso(): string {\n return new Date(Date.now() + 7 * 86_400_000).toISOString();\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst asPercent = (n: number) => `${Math.round(clamp01(n) * 100)}%`;\n\nfunction formatDate(iso: string): string {\n return new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n });\n}\n\n/** Hours of the day offered by the quiet-hours pickers. */\nconst HOUR_OPTIONS = Array.from({ length: 24 }, (_, hour) => hour);\n\nconst DOMAIN_ICON: Record<\n TimeCampusLifeDomain,\n ComponentType<{ className?: string }>\n> = {\n [TimeCampusLifeDomain.Work]: Briefcase,\n [TimeCampusLifeDomain.Sleep]: Moon,\n [TimeCampusLifeDomain.Health]: HeartPulse,\n [TimeCampusLifeDomain.Family]: Users,\n [TimeCampusLifeDomain.Learning]: GraduationCap,\n [TimeCampusLifeDomain.Leisure]: Coffee,\n [TimeCampusLifeDomain.Commute]: Bus,\n [TimeCampusLifeDomain.Waiting]: Hourglass,\n [TimeCampusLifeDomain.Chores]: Home,\n [TimeCampusLifeDomain.SelfCare]: Heart,\n [TimeCampusLifeDomain.Social]: Handshake,\n [TimeCampusLifeDomain.FinanceAdmin]: Wallet,\n [TimeCampusLifeDomain.Reflection]: Sparkles,\n [TimeCampusLifeDomain.Other]: Circle,\n};\n\nfunction recIcon(\n domain?: TimeCampusLifeDomain | null,\n): ComponentType<{ className?: string }> {\n if (!domain) return Lightbulb;\n return DOMAIN_ICON[domain] ?? Lightbulb;\n}\n\ntype BadgeTone = \"success\" | \"warning\" | \"muted\" | \"info\";\n\nfunction stateBadge(state: string): { variant: BadgeTone; label: string } {\n switch (state) {\n case REC_STATE.Accepted:\n return { variant: \"success\", label: \"Accepted\" };\n case REC_STATE.Snoozed:\n return { variant: \"warning\", label: \"Snoozed\" };\n case REC_STATE.Dismissed:\n return { variant: \"muted\", label: \"Dismissed\" };\n case REC_STATE.Expired:\n return { variant: \"muted\", label: \"Expired\" };\n default:\n return { variant: \"info\", label: \"New\" };\n }\n}\n\nconst primaryBtn =\n \"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 hover:bg-action-primary-bg/90 disabled:opacity-50\";\nconst neutralBtn =\n \"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\";\nconst chipClass =\n \"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\";\n\n/**\n * Local, token-styled switch (the shared UI kit isn't imported here). The knob\n * translates only on the checked state — never on hover — per the repo's\n * no-hover-movement mandate.\n */\nfunction ToggleSwitch({\n checked,\n onChange,\n label,\n disabled,\n}: {\n checked: boolean;\n onChange: (next: boolean) => void;\n label: string;\n disabled?: boolean;\n}) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n disabled={disabled}\n onClick={() => onChange(!checked)}\n className={cn(\n \"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50\",\n checked\n ? \"border-transparent bg-action-primary-bg\"\n : \"border-border-subtle bg-bg-sunken\",\n )}\n >\n <span\n className={cn(\n \"inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform\",\n checked\n ? \"translate-x-4 bg-action-primary-text\"\n : \"translate-x-0.5 bg-text-secondary\",\n )}\n />\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Recommendation card\n// ---------------------------------------------------------------------------\ninterface RecommendationCardProps {\n rec: RecItem;\n busy: boolean;\n muted: boolean;\n onAccept: (id: string) => void;\n onSnooze: (id: string) => void;\n onDismiss: (rec: RecItem) => void;\n onMuteKind: (kind: string) => void;\n}\n\nfunction RecommendationCard({\n rec,\n busy,\n muted,\n onAccept,\n onSnooze,\n onDismiss,\n onMuteKind,\n}: RecommendationCardProps) {\n const Icon = recIcon(rec.domain);\n const actionable = isActionable(rec.state);\n const badge = stateBadge(rec.state);\n const [evidenceOpen, setEvidenceOpen] = useState(false);\n\n // Both come off this card's own query result — the JSON the generator wrote\n // when it raised the recommendation. Nothing is derived or estimated here.\n const evidence = parseEvidenceRefs(rec.evidenceRefs);\n const action = parseSuggestedAction(rec.suggestedAction);\n const evidenceId = `rec-evidence-${rec.id}`;\n\n return (\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {humaniseToken(rec.kind)}\n </p>\n <h3 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {rec.title}\n </h3>\n </div>\n </div>\n {rec.confidence != null && (\n <StatusBadge\n variant=\"info\"\n label={`${asPercent(rec.confidence)} confident`}\n className=\"shrink-0\"\n />\n )}\n </div>\n\n <p className=\"text-sm text-text-secondary\">{rec.rationale}</p>\n\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {rec.domain && (\n <span className={chipClass}>{lifeDomainLabel(rec.domain)}</span>\n )}\n {rec.valueAlignment != null && (\n <span className={chipClass}>\n <ShieldCheck className=\"h-3 w-3\" />\n Aligns with your values · {asPercent(rec.valueAlignment)}\n </span>\n )}\n {rec.state === REC_STATE.Snoozed && rec.snoozeUntil && (\n <span className={chipClass}>\n <Clock className=\"h-3 w-3\" />\n Snoozed until {formatDate(rec.snoozeUntil)}\n </span>\n )}\n {actionable && rec.expiresAt && (\n <span className={chipClass}>\n <CalendarClock className=\"h-3 w-3\" />\n Expires {formatDate(rec.expiresAt)}\n </span>\n )}\n </div>\n\n {/* The concrete next step the generator attached, if any. */}\n {action && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-3\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n Suggested action\n </p>\n <p className=\"mt-1 text-sm font-medium text-text-primary\">\n {humaniseToken(action.type)}\n </p>\n {action.details.length > 0 && (\n <dl className=\"mt-1.5 flex flex-wrap gap-x-4 gap-y-1\">\n {action.details.map((detail) => (\n <div key={detail.key} className=\"flex items-center gap-1.5\">\n <dt className=\"text-xs text-text-secondary\">{detail.key}</dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {detail.value}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {/* Why this was raised — the exact metrics the generator measured. */}\n {evidence.length > 0 && (\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken/40\">\n <button\n type=\"button\"\n onClick={() => setEvidenceOpen((open) => !open)}\n aria-expanded={evidenceOpen}\n aria-controls={evidenceId}\n className=\"flex w-full items-center justify-between gap-2 px-3 py-2 text-left\"\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n <Microscope className=\"h-3.5 w-3.5 shrink-0 text-text-secondary\" />\n <span className=\"truncate text-xs text-text-secondary\">\n Evidence: {formatEvidenceRef(evidence[0])}\n {evidence.length > 1 ? ` · +${evidence.length - 1} more` : \"\"}\n </span>\n </span>\n <ChevronDown\n className={cn(\n \"h-4 w-4 shrink-0 text-text-secondary transition-transform\",\n evidenceOpen && \"rotate-180\",\n )}\n />\n </button>\n {evidenceOpen && (\n <dl\n id={evidenceId}\n className=\"space-y-1.5 border-t border-border-seam px-3 py-2.5\"\n >\n {evidence.map((ref, index) => (\n <div\n key={`${ref.metric}-${ref.period ?? ref.domain ?? index}`}\n className=\"flex items-baseline justify-between gap-3\"\n >\n <dt className=\"text-xs text-text-secondary\">\n {formatEvidenceRef(ref).split(\": \")[0]}\n </dt>\n <dd className=\"text-xs font-medium tabular-nums text-text-primary\">\n {formatEvidenceRef(ref).split(\": \").slice(1).join(\": \")}\n </dd>\n </div>\n ))}\n <p className=\"pt-1 text-xs text-text-tertiary\">\n Measured from your own tracked week. Nothing is applied until\n you accept.\n </p>\n </dl>\n )}\n </div>\n )}\n\n {actionable ? (\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n type=\"button\"\n onClick={() => onAccept(rec.id)}\n disabled={busy}\n className={primaryBtn}\n >\n <Check className=\"h-4 w-4\" />\n Accept\n </button>\n <button\n type=\"button\"\n onClick={() => onSnooze(rec.id)}\n disabled={busy}\n className={neutralBtn}\n >\n <Clock className=\"h-4 w-4\" />\n Snooze\n </button>\n <button\n type=\"button\"\n onClick={() => onDismiss(rec)}\n disabled={busy}\n className={neutralBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n <button\n type=\"button\"\n onClick={() => onMuteKind(rec.kind)}\n disabled={busy || muted}\n className={neutralBtn}\n title={\n muted\n ? `${humaniseToken(rec.kind)} suggestions are already muted`\n : `Stop generating ${humaniseToken(rec.kind)} suggestions`\n }\n >\n <BellOff className=\"h-4 w-4\" />\n {muted ? \"Muted\" : \"Mute this kind\"}\n </button>\n </div>\n ) : (\n <div className=\"flex flex-col gap-2 pt-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <StatusBadge variant={badge.variant} label={badge.label} />\n <span className=\"text-xs text-text-secondary\">\n {formatDate(rec.createdAt)}\n </span>\n </div>\n {rec.state === REC_STATE.Dismissed && rec.dismissReason && (\n <p className=\"text-xs text-text-secondary\">\n Reason given: {rec.dismissReason}\n </p>\n )}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Guardrails — the preferences that decide what may be suggested at all.\n// Every value here is read from `myRecommendationPreferences`; the controls\n// write back through `updateMyRecommendationPreferences`.\n// ---------------------------------------------------------------------------\ninterface GuardrailsPanelProps {\n prefs: PrefItem | undefined;\n loading: boolean;\n saving: boolean;\n onToggleOutputGuard: (next: boolean) => void;\n onQuietHoursChange: (start: number | null, end: number | null) => void;\n onUnmuteKind: (kind: string) => void;\n}\n\nfunction GuardrailsPanel({\n prefs,\n loading,\n saving,\n onToggleOutputGuard,\n onQuietHoursChange,\n onUnmuteKind,\n}: GuardrailsPanelProps) {\n if (loading) {\n return <LoadingSkeleton variant=\"card\" count={1} />;\n }\n if (!prefs) return null;\n\n const quietStart = formatHourOfDay(prefs.quietHoursStart);\n const quietEnd = formatHourOfDay(prefs.quietHoursEnd);\n const quietSet = quietStart !== null && quietEnd !== null;\n\n const hourSelectClass =\n \"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 disabled:opacity-50\";\n\n return (\n <GlassCard treatment=\"glass\" className=\"p-4 sm:p-5\">\n <div className=\"flex items-center gap-2\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">Guardrails</h3>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n What TimeCampus is allowed to suggest, and when.\n </p>\n\n <div className=\"mt-4 space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n Never suggest more work\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Output-increasing suggestions are filtered out before they reach\n you.\n </p>\n </div>\n <ToggleSwitch\n checked={prefs.neverOptimiseForOutput}\n disabled={saving}\n label=\"Never suggest more work\"\n onChange={onToggleOutputGuard}\n />\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Quiet hours</p>\n <span className=\"text-xs text-text-secondary\">\n {quietSet ? `${quietStart}–${quietEnd}` : \"Not set\"}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n No nudges are sent inside this window.\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n From\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursStart ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n prefs.quietHoursEnd ?? null,\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n To\n <select\n className={hourSelectClass}\n disabled={saving}\n value={prefs.quietHoursEnd ?? \"\"}\n onChange={(event) =>\n onQuietHoursChange(\n prefs.quietHoursStart ?? null,\n event.target.value === \"\"\n ? null\n : Number(event.target.value),\n )\n }\n >\n <option value=\"\">—</option>\n {HOUR_OPTIONS.map((hour) => (\n <option key={hour} value={hour}>\n {formatHourOfDay(hour)}\n </option>\n ))}\n </select>\n </label>\n {quietSet && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => onQuietHoursChange(null, null)}\n className=\"text-xs font-medium text-text-secondary underline-offset-2 hover:text-text-primary hover:underline disabled:opacity-50\"\n >\n Clear\n </button>\n )}\n </div>\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Muted kinds</p>\n <span className={chipClass}>{prefs.mutedKinds.length}</span>\n </div>\n {prefs.mutedKinds.length === 0 ? (\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Nothing muted — mute a kind from any suggestion to stop it coming\n back.\n </p>\n ) : (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {prefs.mutedKinds.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n disabled={saving}\n onClick={() => onUnmuteKind(kind)}\n className={cn(\n chipClass,\n \"hover:text-text-primary disabled:opacity-50\",\n )}\n title={`Unmute ${humaniseToken(kind)} suggestions`}\n >\n {humaniseToken(kind)}\n <X className=\"h-3 w-3\" />\n </button>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"border-t border-border-seam pt-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">Cadence</p>\n <span className=\"text-xs text-text-secondary\">\n {humaniseToken(prefs.cadence)}\n </span>\n </div>\n {prefs.optedInDomains.length > 0 && (\n <>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n Opted-in domains\n </p>\n <div className=\"mt-1.5 flex flex-wrap gap-1.5\">\n {prefs.optedInDomains.map((domain) => (\n <span key={domain} className={chipClass}>\n {lifeDomainLabel(domain)}\n </span>\n ))}\n </div>\n </>\n )}\n </div>\n </div>\n </GlassCard>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\nfunction segmentEmptyCopy(segment: SegmentKey): {\n title: string;\n description: string;\n} {\n switch (segment) {\n case REC_STATE.Snoozed:\n return {\n title: \"Nothing snoozed\",\n description:\n \"Suggestions you snooze for later will wait here quietly until they're worth another look.\",\n };\n case REC_STATE.Accepted:\n return {\n title: \"Nothing accepted yet\",\n description:\n \"When you accept a suggestion it moves here, so you can see the changes you chose to make.\",\n };\n case REC_STATE.Dismissed:\n return {\n title: \"Nothing dismissed\",\n description:\n \"Suggestions you dismiss are kept here — TimeCampus learns from them and won't keep nagging.\",\n };\n default:\n return {\n title: \"You're all caught up\",\n description:\n \"No new suggestions right now. Generate again after a few more days of tracked time.\",\n };\n }\n}\n\nexport function RecommendationsPage() {\n const { workspaceId } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n const [segment, setSegment] = useState<SegmentKey>(REC_STATE.New);\n const [busyId, setBusyId] = useState<string | null>(null);\n const [dismissTarget, setDismissTarget] = useState<RecItem | null>(null);\n const [dismissReason, setDismissReason] = useState(\"\");\n\n const { data, loading, error, refetch } = useMyRecommendationsQuery({\n variables: { state: null },\n skip: !workspaceId,\n });\n\n const {\n data: prefsData,\n loading: prefsLoading,\n refetch: refetchPrefs,\n } = useMyRecommendationPreferencesQuery({ skip: !workspaceId });\n\n const [generate, { loading: generating }] =\n useGenerateMyRecommendationsMutation();\n const [acceptRec] = useAcceptRecommendationMutation();\n const [dismissRec] = useDismissRecommendationMutation();\n const [snoozeRec] = useSnoozeRecommendationMutation();\n const [updatePrefs, { loading: savingPrefs }] =\n useUpdateMyRecommendationPreferencesMutation();\n\n const prefs = prefsData?.myRecommendationPreferences;\n const mutedKinds = useMemo<Set<string>>(\n () => new Set<string>(prefs?.mutedKinds ?? []),\n [prefs?.mutedKinds],\n );\n\n const recommendations: RecItem[] = useMemo(\n () => data?.myRecommendations ?? [],\n [data?.myRecommendations],\n );\n\n const counts = useMemo(() => {\n const map: Record<string, number> = {};\n for (const rec of recommendations) {\n map[rec.state] = (map[rec.state] ?? 0) + 1;\n }\n return map;\n }, [recommendations]);\n\n const visible = useMemo(\n () => recommendations.filter((rec) => rec.state === segment),\n [recommendations, segment],\n );\n\n const newCount = counts[REC_STATE.New] ?? 0;\n const acceptedCount = counts[REC_STATE.Accepted] ?? 0;\n const snoozedCount = counts[REC_STATE.Snoozed] ?? 0;\n\n const handleGenerate = async () => {\n try {\n const res = await generate();\n const generated = res.data?.generateMyRecommendations ?? [];\n addToast({\n title:\n generated.length > 0\n ? `${generated.length} suggestion${generated.length === 1 ? \"\" : \"s\"} ready`\n : \"No new suggestions\",\n description:\n generated.length > 0\n ? \"Fresh, values-aware ideas are in your New feed.\"\n : \"You're all caught up — nothing new to suggest right now.\",\n variant: generated.length > 0 ? \"success\" : \"info\",\n });\n setSegment(REC_STATE.New);\n await refetch();\n } catch {\n addToast({\n title: \"Couldn't generate recommendations\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleAccept = async (id: string) => {\n setBusyId(id);\n try {\n await acceptRec({ variables: { id } });\n addToast({ title: \"Accepted — nice\", variant: \"success\" });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't accept that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleSnooze = async (id: string) => {\n setBusyId(id);\n try {\n await snoozeRec({ variables: { id, until: snoozeUntilIso() } });\n addToast({\n title: \"Snoozed for a week\",\n description: \"It'll resurface later — no nagging in between.\",\n variant: \"info\",\n });\n await refetch();\n } catch {\n addToast({ title: \"Couldn't snooze that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const requestDismiss = (rec: RecItem) => {\n setDismissReason(\"\");\n setDismissTarget(rec);\n };\n\n /**\n * Dismissing asks for an optional reason first — `dismissRecommendation`\n * takes one and the card reads it back, so the note the user leaves is the\n * note they later see.\n */\n const handleConfirmDismiss = async () => {\n const target = dismissTarget;\n if (!target) return;\n const reason = dismissReason.trim();\n setBusyId(target.id);\n try {\n await dismissRec({\n variables: { id: target.id, reason: reason.length > 0 ? reason : null },\n });\n addToast({ title: \"Dismissed\", variant: \"success\" });\n setDismissTarget(null);\n setDismissReason(\"\");\n await refetch();\n } catch {\n addToast({ title: \"Couldn't dismiss that one\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n /** One write path for every guardrail control, so failures read the same. */\n const savePrefs = async (\n input: {\n neverOptimiseForOutput?: boolean;\n quietHoursStart?: number | null;\n quietHoursEnd?: number | null;\n mutedKinds?: string[];\n },\n successTitle: string,\n ) => {\n try {\n await updatePrefs({ variables: { input } });\n addToast({ title: successTitle, variant: \"success\" });\n await refetchPrefs();\n } catch {\n addToast({\n title: \"Couldn't save that guardrail\",\n description: \"Please try again in a moment.\",\n variant: \"error\",\n });\n }\n };\n\n const handleToggleOutputGuard = (next: boolean) =>\n void savePrefs(\n { neverOptimiseForOutput: next },\n next\n ? \"Output-increasing suggestions are off\"\n : \"Output-increasing suggestions allowed\",\n );\n\n const handleQuietHoursChange = (start: number | null, end: number | null) =>\n void savePrefs(\n { quietHoursStart: start, quietHoursEnd: end },\n start === null && end === null\n ? \"Quiet hours cleared\"\n : \"Quiet hours saved\",\n );\n\n const handleMuteKind = (kind: string) => {\n if (mutedKinds.has(kind)) return;\n void savePrefs(\n { mutedKinds: [...mutedKinds, kind] },\n `${humaniseToken(kind)} suggestions muted`,\n );\n };\n\n const handleUnmuteKind = (kind: string) =>\n void savePrefs(\n { mutedKinds: [...mutedKinds].filter((entry) => entry !== kind) },\n `${humaniseToken(kind)} suggestions unmuted`,\n );\n\n const guardrailsPanel = (\n <GuardrailsPanel\n prefs={prefs}\n loading={prefsLoading}\n saving={savingPrefs}\n onToggleOutputGuard={handleToggleOutputGuard}\n onQuietHoursChange={handleQuietHoursChange}\n onUnmuteKind={handleUnmuteKind}\n />\n );\n\n const generateButton = (\n <button\n type=\"button\"\n onClick={handleGenerate}\n disabled={generating || !workspaceId}\n className={primaryBtn}\n >\n <Sparkles className=\"h-4 w-4\" />\n {generating ? \"Generating…\" : \"Generate recommendations\"}\n </button>\n );\n\n const purpose =\n \"Gentle, values-aware nudges built from your real timeline — protect a focus block, \" +\n \"reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and \" +\n \"dismiss what doesn't. TimeCampus never optimises for more output — only for time that \" +\n \"matches what you value.\";\n\n const footerNote = (\n <GlassCard treatment=\"insight\" className=\"mt-6 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg text-status-info-text\">\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Never optimises for more output.\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Every suggestion protects something you value — rest, focus,\n relationships — not your throughput. Snooze or dismiss freely;\n TimeCampus takes the hint and won't keep nagging.\n </p>\n </div>\n </div>\n </GlassCard>\n );\n\n if (!workspaceId || loading) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n <div className=\"rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Couldn't load your recommendations right now. Please try again in\n a moment.\n </div>\n </PageLayout>\n );\n }\n\n const hasAny = recommendations.length > 0;\n const emptyCopy = segmentEmptyCopy(segment);\n\n return (\n <PageLayout\n title=\"Recommendations\"\n description=\"Values-aware suggestions from your real timeline\"\n purpose={purpose}\n actions={generateButton}\n >\n {!hasAny ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div>\n <EmptyState\n illustration=\"empty-data\"\n title=\"No recommendations yet\"\n description=\"TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.\"\n action={generateButton}\n />\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <div className=\"min-w-0 space-y-6\">\n {/* Summary of where each suggestion stands — from the query only. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"New for you\"\n value={String(newCount)}\n icon={<Sparkles className=\"h-5 w-5\" />}\n description=\"Awaiting your call\"\n />\n <StatCard\n title=\"Accepted\"\n value={String(acceptedCount)}\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n description=\"Changes you chose to make\"\n />\n <StatCard\n title=\"Snoozed\"\n value={String(snoozedCount)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Waiting for a better moment\"\n />\n </div>\n\n {/* Segmented control by state. */}\n <div className=\"overflow-x-auto\">\n <div\n className=\"inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5\"\n role=\"tablist\"\n aria-label=\"Filter recommendations by state\"\n >\n {SEGMENTS.map((seg) => {\n const active = seg.key === segment;\n const count = counts[seg.key] ?? 0;\n return (\n <button\n key={seg.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n onClick={() => setSegment(seg.key)}\n className={\n active\n ? \"flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm\"\n : \"flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n }\n >\n {seg.label}\n <span\n className={\n active\n ? \"rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary\"\n : \"rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary\"\n }\n >\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* The feed for the active segment. */}\n {visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyCopy.title}\n description={emptyCopy.description}\n action={segment === REC_STATE.New ? generateButton : undefined}\n />\n ) : (\n <div className=\"space-y-4\">\n {visible.map((rec) => (\n <RecommendationCard\n key={rec.id}\n rec={rec}\n busy={busyId === rec.id || savingPrefs}\n muted={mutedKinds.has(rec.kind)}\n onAccept={handleAccept}\n onSnooze={handleSnooze}\n onDismiss={requestDismiss}\n onMuteKind={handleMuteKind}\n />\n ))}\n </div>\n )}\n\n {footerNote}\n </div>\n <div>{guardrailsPanel}</div>\n </div>\n )}\n\n {/* Dismissing captures the optional reason the mutation accepts, so the\n note is there when the card is read back under Dismissed. */}\n <Modal\n open={dismissTarget !== null}\n onClose={() => setDismissTarget(null)}\n title=\"Dismiss this suggestion\"\n description={dismissTarget?.title}\n size=\"sm\"\n footer={\n <>\n <button\n type=\"button\"\n onClick={() => setDismissTarget(null)}\n className={neutralBtn}\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleConfirmDismiss()}\n disabled={busyId !== null}\n className={primaryBtn}\n >\n <X className=\"h-4 w-4\" />\n Dismiss\n </button>\n </>\n }\n >\n <label\n htmlFor=\"rec-dismiss-reason\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Why isn't this one for you?{\" \"}\n <span className=\"font-normal text-text-secondary\">(optional)</span>\n </label>\n <textarea\n id=\"rec-dismiss-reason\"\n rows={3}\n value={dismissReason}\n onChange={(event) => setDismissReason(event.target.value)}\n placeholder=\"e.g. already handled, not the right week\"\n className=\"mt-1.5 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n Saved with the suggestion so you can see why you passed on it later.\n To stop this kind of suggestion entirely, use Mute this kind.\n </p>\n </Modal>\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 } 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 {
|
|
5
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
6
|
+
import { useMyTimeWellSpentQuery as a } from "../../../generated/wspace-operations.js";
|
|
7
|
+
import { PageLayout as o } from "../../components/PageLayout.js";
|
|
8
|
+
import { StatusBadge as s } from "../../components/StatusBadge.js";
|
|
9
9
|
import { ChartCard as c } from "../../components/ChartCard.js";
|
|
10
10
|
import { RadialGauge as l } from "../../components/charts/RadialGauge.js";
|
|
11
11
|
import { ProgressMeter as u } from "../../components/charts/ProgressMeter.js";
|
|
@@ -45,7 +45,7 @@ function P() {
|
|
|
45
45
|
let { workspaceId: P = "" } = e(), [F, I] = _(30), L = g(() => ({
|
|
46
46
|
from: f(F),
|
|
47
47
|
to: p()
|
|
48
|
-
}), [F]), { data: R, loading: z, error: B } =
|
|
48
|
+
}), [F]), { data: R, loading: z, error: B } = a({
|
|
49
49
|
variables: {
|
|
50
50
|
from: L.from,
|
|
51
51
|
to: L.to
|
|
@@ -65,47 +65,47 @@ function P() {
|
|
|
65
65
|
}, e.value))
|
|
66
66
|
})
|
|
67
67
|
};
|
|
68
|
-
if (!P || z) return /* @__PURE__ */ T(
|
|
68
|
+
if (!P || z) return /* @__PURE__ */ T(o, {
|
|
69
69
|
...U,
|
|
70
70
|
children: /* @__PURE__ */ T(r, {
|
|
71
71
|
variant: "card",
|
|
72
72
|
count: 4
|
|
73
73
|
})
|
|
74
74
|
});
|
|
75
|
-
if (B) return /* @__PURE__ */ T(
|
|
75
|
+
if (B) return /* @__PURE__ */ T(o, {
|
|
76
76
|
...U,
|
|
77
77
|
children: /* @__PURE__ */ T("div", {
|
|
78
78
|
className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
79
79
|
children: "We couldn't load your Time-Well-Spent score right now. Please try again in a moment."
|
|
80
80
|
})
|
|
81
81
|
});
|
|
82
|
-
if (!V) return /* @__PURE__ */ T(
|
|
82
|
+
if (!V) return /* @__PURE__ */ T(o, {
|
|
83
83
|
...U,
|
|
84
|
-
children: /* @__PURE__ */ T(
|
|
84
|
+
children: /* @__PURE__ */ T(i, {
|
|
85
85
|
illustration: "empty-data",
|
|
86
86
|
title: "Nothing to score yet",
|
|
87
87
|
description: "Once you start capturing your time, your alignment score will appear here."
|
|
88
88
|
})
|
|
89
89
|
});
|
|
90
90
|
let { score: W, hasDeclaredValues: G, coverageConfidence: K, coveredSeconds: q } = V;
|
|
91
|
-
if (!G) return /* @__PURE__ */ T(
|
|
91
|
+
if (!G) return /* @__PURE__ */ T(o, {
|
|
92
92
|
...U,
|
|
93
|
-
children: /* @__PURE__ */ T(
|
|
93
|
+
children: /* @__PURE__ */ T(i, {
|
|
94
94
|
illustration: "empty-data",
|
|
95
95
|
title: "Declare what you value first",
|
|
96
96
|
description: "Time Well Spent needs to know what matters to you. Head to the Values & Goals page to declare your life values or balance targets — then this score will show how closely your real time matches them."
|
|
97
97
|
})
|
|
98
98
|
});
|
|
99
|
-
if (q <= 0) return /* @__PURE__ */ T(
|
|
99
|
+
if (q <= 0) return /* @__PURE__ */ T(o, {
|
|
100
100
|
...U,
|
|
101
|
-
children: /* @__PURE__ */ T(
|
|
101
|
+
children: /* @__PURE__ */ T(i, {
|
|
102
102
|
illustration: "empty-data",
|
|
103
103
|
title: "No time logged in this range",
|
|
104
104
|
description: "Log your first life entry — or connect a source that captures your time — and we'll score how it aligns with what you value."
|
|
105
105
|
})
|
|
106
106
|
});
|
|
107
107
|
let J = W == null ? 0 : Math.round(W * 100), Y = Math.round(K * 100), X = K < k;
|
|
108
|
-
return /* @__PURE__ */ E(
|
|
108
|
+
return /* @__PURE__ */ E(o, {
|
|
109
109
|
...U,
|
|
110
110
|
children: [/* @__PURE__ */ E("div", {
|
|
111
111
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
@@ -212,7 +212,7 @@ function P() {
|
|
|
212
212
|
children: "Your actual share of accounted time versus what you intended, by life domain."
|
|
213
213
|
})]
|
|
214
214
|
})]
|
|
215
|
-
}), H.length === 0 ? /* @__PURE__ */ T(
|
|
215
|
+
}), H.length === 0 ? /* @__PURE__ */ T(i, {
|
|
216
216
|
illustration: "empty-data",
|
|
217
217
|
title: "No domain breakdown yet",
|
|
218
218
|
description: "As your time is captured across life domains, each one's alignment with your intentions will appear here."
|
|
@@ -235,7 +235,7 @@ function P() {
|
|
|
235
235
|
className: "mt-0.5 text-xs text-text-secondary",
|
|
236
236
|
children: [i, " logged"]
|
|
237
237
|
})]
|
|
238
|
-
}), /* @__PURE__ */ T(
|
|
238
|
+
}), /* @__PURE__ */ T(s, {
|
|
239
239
|
variant: j[t],
|
|
240
240
|
label: N(e.gap, t)
|
|
241
241
|
})]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
2
2
|
import { StatCard as t } from "../../components/StatCard.js";
|
|
3
3
|
import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { EmptyState as ee } from "../../components/EmptyState.js";
|
|
5
|
+
import { useCreateLifeGoalMutation as te, useDeclareLifeValueMutation as ne, useDeleteLifeGoalMutation as re, useDeleteLifeValueMutation as ie, useMyLifeGoalsQuery as ae, useMyLifeValuesQuery as oe, useUpdateLifeGoalMutation as se, useUpdateLifeValueMutation as ce } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as le } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as ue } from "../../components/StatusBadge.js";
|
|
8
8
|
import { Modal as r } from "../../components/Modal.js";
|
|
9
9
|
import { ChartCard as de } from "../../components/ChartCard.js";
|
|
10
10
|
import { ToastContainer as fe, useToast as pe } from "../../components/Toast.js";
|
|
@@ -71,7 +71,7 @@ var A = {
|
|
|
71
71
|
valueId: ""
|
|
72
72
|
};
|
|
73
73
|
function M() {
|
|
74
|
-
let { workspaceId: c = "" } = e(), { toasts: l, addToast: u, dismissToast: he } = pe(), { data: d, loading: f, error: ve, refetch: p } =
|
|
74
|
+
let { workspaceId: c = "" } = e(), { toasts: l, addToast: u, dismissToast: he } = pe(), { data: d, loading: f, error: ve, refetch: p } = oe({ skip: !c }), { data: m, loading: h, error: be, refetch: g } = ae({ skip: !c }), [Te, { loading: _ }] = ne(), [x, { loading: M }] = ce(), [Ae, { loading: N }] = ie(), [je, { loading: P }] = te(), [Me, { loading: Ne }] = se(), [Pe, { loading: Fe }] = re(), F = d?.myLifeValues ?? [], I = m?.myLifeGoals ?? [], Ie = F.filter((e) => e.isActive).length, Le = new Set(F.flatMap((e) => e.domains)).size, Re = Math.max(1, ...F.map((e) => e.weight)), ze = new Map(F.map((e) => [e.id, e.name])), [Be, L] = s(!1), [R, z] = s(null), [B, V] = s(A), [H, U] = s(null), [Ve, W] = s(!1), [G, K] = s(null), [q, J] = s(j), [Y, X] = s(null), He = () => {
|
|
75
75
|
z(null), V(A), L(!0);
|
|
76
76
|
}, Ue = (e) => {
|
|
77
77
|
z(e), V({
|
|
@@ -189,14 +189,14 @@ function M() {
|
|
|
189
189
|
className: w,
|
|
190
190
|
children: [/* @__PURE__ */ y(Se, { className: "h-4 w-4" }), "Add goal"]
|
|
191
191
|
}), et = F.length > 0 || I.length > 0;
|
|
192
|
-
return f && h && !d && !m ? /* @__PURE__ */ y(
|
|
192
|
+
return f && h && !d && !m ? /* @__PURE__ */ y(le, {
|
|
193
193
|
title: "Values & Goals",
|
|
194
194
|
description: "Declare what matters and set the life goals that shape it",
|
|
195
195
|
children: /* @__PURE__ */ y(n, {
|
|
196
196
|
variant: "list",
|
|
197
197
|
count: 4
|
|
198
198
|
})
|
|
199
|
-
}) : /* @__PURE__ */ b(
|
|
199
|
+
}) : /* @__PURE__ */ b(le, {
|
|
200
200
|
title: "Values & Goals",
|
|
201
201
|
description: "Declare what matters and set the life goals that shape it",
|
|
202
202
|
purpose: "Name the things you value and the goals that serve them. These directly shape your Balance Wheel, your Time-Well-Spent score, and the recommendations TimeCampus surfaces — so your time can follow your intentions, not the other way round.",
|
|
@@ -243,7 +243,7 @@ function M() {
|
|
|
243
243
|
}) : f && F.length === 0 ? /* @__PURE__ */ y(n, {
|
|
244
244
|
variant: "list",
|
|
245
245
|
count: 3
|
|
246
|
-
}) : F.length === 0 ? /* @__PURE__ */ y(
|
|
246
|
+
}) : F.length === 0 ? /* @__PURE__ */ y(ee, {
|
|
247
247
|
illustration: "empty-data",
|
|
248
248
|
title: "Declare what you value",
|
|
249
249
|
description: "Name the life areas that matter to you — family, health, craft — and give each a weight. TimeCampus uses them to score how well your time is spent.",
|
|
@@ -309,7 +309,7 @@ function M() {
|
|
|
309
309
|
}),
|
|
310
310
|
/* @__PURE__ */ y("div", {
|
|
311
311
|
className: "mt-auto pt-1",
|
|
312
|
-
children: /* @__PURE__ */ y(
|
|
312
|
+
children: /* @__PURE__ */ y(ue, {
|
|
313
313
|
variant: e.isActive ? "success" : "muted",
|
|
314
314
|
label: e.isActive ? "Active" : "Inactive"
|
|
315
315
|
})
|
|
@@ -336,7 +336,7 @@ function M() {
|
|
|
336
336
|
}) : h && I.length === 0 ? /* @__PURE__ */ y(n, {
|
|
337
337
|
variant: "list",
|
|
338
338
|
count: 3
|
|
339
|
-
}) : I.length === 0 ? /* @__PURE__ */ y(
|
|
339
|
+
}) : I.length === 0 ? /* @__PURE__ */ y(ee, {
|
|
340
340
|
illustration: "empty-data",
|
|
341
341
|
title: "Set your first life goal",
|
|
342
342
|
description: "Turn a value into a target — like 'Run a marathon' or '12h of family time a week' — and track it alongside everything else you do.",
|
|
@@ -379,7 +379,7 @@ function M() {
|
|
|
379
379
|
O(e.cadence)
|
|
380
380
|
]
|
|
381
381
|
}),
|
|
382
|
-
/* @__PURE__ */ y(
|
|
382
|
+
/* @__PURE__ */ y(ue, {
|
|
383
383
|
status: e.status,
|
|
384
384
|
label: O(e.status)
|
|
385
385
|
}),
|
|
@@ -2,10 +2,10 @@ import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
|
|
|
2
2
|
import { formatDate as t, formatHours as n, formatTime as r } from "../../utils/helpers.js";
|
|
3
3
|
import { StatCard as i } from "../../components/StatCard.js";
|
|
4
4
|
import { LoadingSkeleton as a } from "../../components/LoadingSkeleton.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { EmptyState as o } from "../../components/EmptyState.js";
|
|
6
|
+
import { useCreateLocationEntryMutation as ee, useDeleteLocationEntryMutation as s, useGetLocationStatsQuery as c, useListLocationEntriesQuery as l } from "../../../generated/wspace-operations.js";
|
|
7
|
+
import { PageLayout as u } from "../../components/PageLayout.js";
|
|
8
|
+
import { StatusBadge as d } from "../../components/StatusBadge.js";
|
|
9
9
|
import { Modal as f } from "../../components/Modal.js";
|
|
10
10
|
import { ChartCard as p } from "../../components/ChartCard.js";
|
|
11
11
|
import { BarsChart as m } from "../../components/charts/BarsChart.js";
|
|
@@ -24,10 +24,10 @@ var w = 25, T = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surfac
|
|
|
24
24
|
endTime: ""
|
|
25
25
|
};
|
|
26
26
|
function D() {
|
|
27
|
-
let { currentUser: D } = e(), { data: O, loading: k, error: A, refetch: j, fetchMore: ie } =
|
|
27
|
+
let { currentUser: D } = e(), { data: O, loading: k, error: A, refetch: j, fetchMore: ie } = l({ variables: { pagination: {
|
|
28
28
|
limit: w,
|
|
29
29
|
offset: 0
|
|
30
|
-
} } }), { data: ae, loading: oe, error: M, refetch: N } =
|
|
30
|
+
} } }), { data: ae, loading: oe, error: M, refetch: N } = c(), [P, { loading: F, error: I }] = ee(), [L, { loading: R }] = s(), [z, B] = h(!1), [V, H] = h(E), [U, W] = h(null), [G, K] = h(!1), q = O?.locationEntries?.items ?? [], se = O?.locationEntries?.hasMore ?? !1, J = ae?.locationStats, Y = q.filter((e) => e.geofenceId).length, ce = q.length - Y, le = [{
|
|
31
31
|
name: "Geofenced",
|
|
32
32
|
value: Y
|
|
33
33
|
}, {
|
|
@@ -83,21 +83,21 @@ function D() {
|
|
|
83
83
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
84
84
|
children: [/* @__PURE__ */ S(v, { className: "h-4 w-4" }), "Add Location Entry"]
|
|
85
85
|
});
|
|
86
|
-
return k && oe ? /* @__PURE__ */ S(
|
|
86
|
+
return k && oe ? /* @__PURE__ */ S(u, {
|
|
87
87
|
title: "Location Tracking",
|
|
88
88
|
description: "Track work locations and productivity",
|
|
89
89
|
children: /* @__PURE__ */ S(a, {
|
|
90
90
|
variant: "list",
|
|
91
91
|
count: 4
|
|
92
92
|
})
|
|
93
|
-
}) : A && M ? /* @__PURE__ */ S(
|
|
93
|
+
}) : A && M ? /* @__PURE__ */ S(u, {
|
|
94
94
|
title: "Location Tracking",
|
|
95
95
|
description: "Track work locations and productivity",
|
|
96
96
|
children: /* @__PURE__ */ S("div", {
|
|
97
97
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
98
98
|
children: "Failed to load location data. Please try again later."
|
|
99
99
|
})
|
|
100
|
-
}) : /* @__PURE__ */ C(
|
|
100
|
+
}) : /* @__PURE__ */ C(u, {
|
|
101
101
|
title: "Location Tracking",
|
|
102
102
|
description: "Track work locations and productivity",
|
|
103
103
|
purpose: "See where work actually happens — office, home, client sites — and how long you spend at each, so you can verify on-site time, support field teams, and back up location-based billing.",
|
|
@@ -129,7 +129,7 @@ function D() {
|
|
|
129
129
|
})
|
|
130
130
|
]
|
|
131
131
|
}),
|
|
132
|
-
q.length === 0 && !k ? /* @__PURE__ */ S(
|
|
132
|
+
q.length === 0 && !k ? /* @__PURE__ */ S(o, {
|
|
133
133
|
illustration: "empty-data",
|
|
134
134
|
title: "No location entries found",
|
|
135
135
|
description: "With location tracking enabled, the places you work from will show up here with time spent at each. You can also add an entry manually.",
|
|
@@ -186,7 +186,7 @@ function D() {
|
|
|
186
186
|
}), /* @__PURE__ */ C("div", {
|
|
187
187
|
className: "mt-1 flex flex-wrap items-center gap-2 text-xs text-text-secondary",
|
|
188
188
|
children: [
|
|
189
|
-
/* @__PURE__ */ S(
|
|
189
|
+
/* @__PURE__ */ S(d, {
|
|
190
190
|
label: i ? "Geofenced" : "Manual",
|
|
191
191
|
variant: i ? "info" : "muted"
|
|
192
192
|
}),
|