@burdenoff/microfe-timecampus 2026.731.2 → 2026.820.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/generated/wspace-operations.d.ts +484 -861
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.js +1377 -1377
  4. package/dist/generated/wspace-operations.js.map +1 -1
  5. package/dist/generated/wspace-types.js.map +1 -1
  6. package/dist/index.js +5 -3
  7. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  8. package/dist/stories/EmptyState.stories.d.ts +0 -1
  9. package/dist/stories/LoadingSkeleton.stories.d.ts +0 -1
  10. package/dist/stories/StatCard.stories.d.ts +0 -1
  11. package/dist/stories/StatusBadge.stories.d.ts +0 -1
  12. package/dist/timecampus/AdminRoot.d.ts +8 -0
  13. package/dist/timecampus/AdminRoot.d.ts.map +1 -0
  14. package/dist/timecampus/AdminRoot.js +12 -0
  15. package/dist/timecampus/AdminRoot.js.map +1 -0
  16. package/dist/timecampus/AdminRoutes.d.ts +3 -0
  17. package/dist/timecampus/AdminRoutes.d.ts.map +1 -0
  18. package/dist/timecampus/AdminRoutes.js +8 -0
  19. package/dist/timecampus/AdminRoutes.js.map +1 -0
  20. package/dist/timecampus/TimeCampusRoot.d.ts +0 -1
  21. package/dist/timecampus/TimeCampusRoot.js +4 -2
  22. package/dist/timecampus/TimeCampusRoot.js.map +1 -1
  23. package/dist/timecampus/TimeCampusRoutes.js +1 -1
  24. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  25. package/dist/timecampus/components/ChartCard.d.ts +0 -1
  26. package/dist/timecampus/components/ChartCard.js.map +1 -1
  27. package/dist/timecampus/components/DataTable.d.ts +0 -1
  28. package/dist/timecampus/components/EmptyState.d.ts +0 -1
  29. package/dist/timecampus/components/EmptyState.js.map +1 -1
  30. package/dist/timecampus/components/ErrorBoundary.d.ts +0 -1
  31. package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
  32. package/dist/timecampus/components/FilterBar.d.ts +0 -1
  33. package/dist/timecampus/components/LoadingSkeleton.d.ts +0 -1
  34. package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
  35. package/dist/timecampus/components/Modal.d.ts +0 -1
  36. package/dist/timecampus/components/Modal.js +5 -3
  37. package/dist/timecampus/components/Modal.js.map +1 -1
  38. package/dist/timecampus/components/PageLayout.d.ts +0 -1
  39. package/dist/timecampus/components/PageLayout.js.map +1 -1
  40. package/dist/timecampus/components/StatCard.d.ts +0 -1
  41. package/dist/timecampus/components/StatCard.js.map +1 -1
  42. package/dist/timecampus/components/StatusBadge.d.ts +0 -1
  43. package/dist/timecampus/components/StatusBadge.js.map +1 -1
  44. package/dist/timecampus/components/Toast.d.ts +0 -1
  45. package/dist/timecampus/components/Toast.js.map +1 -1
  46. package/dist/timecampus/components/charts/BalanceWheel.js.map +1 -1
  47. package/dist/timecampus/components/charts/BarsChart.d.ts +0 -1
  48. package/dist/timecampus/components/charts/BarsChart.js.map +1 -1
  49. package/dist/timecampus/components/charts/ChartParts.d.ts +0 -1
  50. package/dist/timecampus/components/charts/ChartParts.js.map +1 -1
  51. package/dist/timecampus/components/charts/DonutChart.d.ts +0 -1
  52. package/dist/timecampus/components/charts/DonutChart.js.map +1 -1
  53. package/dist/timecampus/components/charts/Heatmap.d.ts +0 -1
  54. package/dist/timecampus/components/charts/Heatmap.js +29 -29
  55. package/dist/timecampus/components/charts/Heatmap.js.map +1 -1
  56. package/dist/timecampus/components/charts/ProgressMeter.d.ts +0 -1
  57. package/dist/timecampus/components/charts/ProgressMeter.js +2 -2
  58. package/dist/timecampus/components/charts/ProgressMeter.js.map +1 -1
  59. package/dist/timecampus/components/charts/RadialGauge.d.ts +0 -1
  60. package/dist/timecampus/components/charts/RadialGauge.js +2 -2
  61. package/dist/timecampus/components/charts/RadialGauge.js.map +1 -1
  62. package/dist/timecampus/components/charts/TrendChart.d.ts +0 -1
  63. package/dist/timecampus/components/charts/TrendChart.js.map +1 -1
  64. package/dist/timecampus/components/charts/WeeksGrid.js +2 -2
  65. package/dist/timecampus/components/charts/WeeksGrid.js.map +1 -1
  66. package/dist/timecampus/components/charts/chartTheme.js.map +1 -1
  67. package/dist/timecampus/components/charts/index.js +10 -9
  68. package/dist/timecampus/components/index.js +18 -17
  69. package/dist/timecampus/index.d.ts +3 -1
  70. package/dist/timecampus/index.d.ts.map +1 -1
  71. package/dist/timecampus/index.js +8 -5
  72. package/dist/timecampus/pages/HomePage.d.ts +0 -1
  73. package/dist/timecampus/pages/HomePage.js.map +1 -1
  74. package/dist/timecampus/pages/admin/AdminPage.d.ts +3 -0
  75. package/dist/timecampus/pages/admin/AdminPage.d.ts.map +1 -0
  76. package/dist/timecampus/pages/admin/AdminPage.js +235 -0
  77. package/dist/timecampus/pages/admin/AdminPage.js.map +1 -0
  78. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
  79. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  80. package/dist/timecampus/pages/client-portal/ClientPortalPage.d.ts.map +1 -1
  81. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  82. package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
  83. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  84. package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
  85. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.d.ts.map +1 -1
  86. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  87. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.d.ts.map +1 -1
  88. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  89. package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
  90. package/dist/timecampus/pages/geofencing/GeofencingPage.d.ts.map +1 -1
  91. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  92. package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -1
  93. package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
  94. package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js.map +1 -1
  95. package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
  96. package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
  97. package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
  98. package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
  99. package/dist/timecampus/pages/lifetime/RecommendationsPage.js +1 -1
  100. package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
  101. package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js.map +1 -1
  102. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +9 -6
  103. package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js.map +1 -1
  104. package/dist/timecampus/pages/lifetime/domains.d.ts +0 -1
  105. package/dist/timecampus/pages/lifetime/domains.js +1 -1
  106. package/dist/timecampus/pages/lifetime/domains.js.map +1 -1
  107. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.d.ts.map +1 -1
  108. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  109. package/dist/timecampus/pages/meetings/ActionItemFormModal.d.ts +0 -1
  110. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -1
  111. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +1 -1
  112. package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
  113. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  114. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +1 -1
  115. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  116. package/dist/timecampus/pages/meetings/MeetingFormModal.d.ts +0 -1
  117. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
  118. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  119. package/dist/timecampus/pages/meetings/ParticipantFormModal.d.ts +0 -1
  120. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -1
  121. package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
  122. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  123. package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
  124. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  125. package/dist/timecampus/pages/projects/ProjectDetailPage.js +1 -1
  126. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  127. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  128. package/dist/timecampus/pages/projects/TasksPage.js +1 -1
  129. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  130. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.d.ts.map +1 -1
  131. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  132. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  133. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  134. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  135. package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
  136. package/dist/timecampus/pages/time/TimeEntriesPage.d.ts.map +1 -1
  137. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  138. package/dist/timecampus/pages/time/TimeTrackingPage.js +1 -1
  139. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  140. package/dist/timecampus/pages/time/TimerPage.js +4 -4
  141. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  142. package/dist/timecampus/pages/time-approval/TimeApprovalPage.d.ts.map +1 -1
  143. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  144. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
  145. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +1 -1
  146. package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
  147. package/dist/timecampus/providers/TimeCampusProvider.d.ts +0 -1
  148. package/dist/timecampus/providers/TimeCampusProvider.d.ts.map +1 -1
  149. package/dist/timecampus/providers/TimeCampusProvider.js +39 -36
  150. package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
  151. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  152. package/dist/timecampus/types/index.d.ts +4 -1
  153. package/dist/timecampus/types/index.d.ts.map +1 -1
  154. package/dist/timecampus/utils/helpers.d.ts +0 -1
  155. package/dist/timecampus/utils/helpers.js.map +1 -1
  156. package/dist/types/assistant.d.ts +0 -1
  157. package/dist/utils/pageContext.js +1 -1
  158. package/dist/utils/pageContext.js.map +1 -1
  159. package/package.json +61 -59
@@ -1 +1 @@
1
- {"version":3,"file":"ValuesGoalsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/ValuesGoalsPage.tsx"],"sourcesContent":["import { useState, type ComponentType } from \"react\";\nimport {\n Plus,\n Pencil,\n Trash2,\n Compass,\n Flag,\n Layers,\n Target,\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 Sparkles,\n Circle,\n} from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n EmptyState,\n LoadingSkeleton,\n StatusBadge,\n Modal,\n ChartCard,\n ProgressMeter,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { lifeDomainLabel, ALL_LIFE_DOMAINS } from \"./domains\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyLifeValuesQuery,\n useMyLifeGoalsQuery,\n useDeclareLifeValueMutation,\n useUpdateLifeValueMutation,\n useDeleteLifeValueMutation,\n useCreateLifeGoalMutation,\n useUpdateLifeGoalMutation,\n useDeleteLifeGoalMutation,\n type LifeValueFieldsFragment,\n type LifeGoalFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\n// ---------------------------------------------------------------------------\n// Static option sets (free-text scalars on the backend — we present a curated\n// list but always send/read a plain string).\n// ---------------------------------------------------------------------------\nconst CADENCE_OPTIONS = [\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"quarterly\",\n \"yearly\",\n] as const;\n\nconst GOAL_STATUS_OPTIONS = [\n \"active\",\n \"paused\",\n \"completed\",\n \"archived\",\n] as const;\n\nconst UNIT_SUGGESTIONS = [\"hours\", \"sessions\", \"days\", \"times\"];\n\n/** Icon per life domain, so a value/goal reads at a glance. */\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\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\nconst primaryBtnClass =\n \"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 disabled:opacity-50\";\nconst secondaryBtnClass =\n \"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\";\nconst dangerBtnClass =\n \"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\";\nconst iconBtnClass =\n \"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\";\n\nfunction titleCase(value: string): string {\n if (!value) return value;\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction domainIcon(\n domain: TimeCampusLifeDomain,\n): ComponentType<{ className?: string }> {\n return DOMAIN_ICON[domain] ?? Circle;\n}\n\n// ---------------------------------------------------------------------------\n// Value form\n// ---------------------------------------------------------------------------\ninterface ValueFormState {\n name: string;\n description: string;\n weight: string;\n domains: TimeCampusLifeDomain[];\n isActive: boolean;\n}\n\nconst emptyValueForm: ValueFormState = {\n name: \"\",\n description: \"\",\n weight: \"1\",\n domains: [],\n isActive: true,\n};\n\n// ---------------------------------------------------------------------------\n// Goal form\n// ---------------------------------------------------------------------------\ninterface GoalFormState {\n title: string;\n domain: TimeCampusLifeDomain;\n targetValue: string;\n unit: string;\n cadence: string;\n status: string;\n valueId: string;\n}\n\nconst emptyGoalForm: GoalFormState = {\n title: \"\",\n domain: TimeCampusLifeDomain.Health,\n targetValue: \"\",\n unit: \"hours\",\n cadence: \"weekly\",\n status: \"active\",\n valueId: \"\",\n};\n\nexport function ValuesGoalsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: valuesData,\n loading: valuesLoading,\n error: valuesError,\n refetch: refetchValues,\n } = useMyLifeValuesQuery({ skip: !workspaceId });\n\n const {\n data: goalsData,\n loading: goalsLoading,\n error: goalsError,\n refetch: refetchGoals,\n } = useMyLifeGoalsQuery({ skip: !workspaceId });\n\n const [declareValue, { loading: declaringValue }] =\n useDeclareLifeValueMutation();\n const [updateValue, { loading: updatingValue }] =\n useUpdateLifeValueMutation();\n const [deleteValue, { loading: deletingValue }] =\n useDeleteLifeValueMutation();\n const [createGoal, { loading: creatingGoal }] = useCreateLifeGoalMutation();\n const [updateGoal, { loading: updatingGoal }] = useUpdateLifeGoalMutation();\n const [deleteGoal, { loading: deletingGoal }] = useDeleteLifeGoalMutation();\n\n const values: LifeValueFieldsFragment[] = valuesData?.myLifeValues ?? [];\n const goals: LifeGoalFieldsFragment[] = goalsData?.myLifeGoals ?? [];\n\n // Derived, honest summary metrics from the fetched data only.\n const activeValues = values.filter((v) => v.isActive).length;\n const domainsCovered = new Set(values.flatMap((v) => v.domains)).size;\n const maxWeight = Math.max(1, ...values.map((v) => v.weight));\n const valueNameById = new Map(values.map((v) => [v.id, v.name] as const));\n\n // --- Value modal state --------------------------------------------------\n const [valueFormOpen, setValueFormOpen] = useState(false);\n const [editingValue, setEditingValue] =\n useState<LifeValueFieldsFragment | null>(null);\n const [valueForm, setValueForm] = useState<ValueFormState>(emptyValueForm);\n const [deleteValueTarget, setDeleteValueTarget] =\n useState<LifeValueFieldsFragment | null>(null);\n\n // --- Goal modal state ---------------------------------------------------\n const [goalFormOpen, setGoalFormOpen] = useState(false);\n const [editingGoal, setEditingGoal] = useState<LifeGoalFieldsFragment | null>(\n null,\n );\n const [goalForm, setGoalForm] = useState<GoalFormState>(emptyGoalForm);\n const [deleteGoalTarget, setDeleteGoalTarget] =\n useState<LifeGoalFieldsFragment | null>(null);\n\n // --- Value handlers -----------------------------------------------------\n const openCreateValue = () => {\n setEditingValue(null);\n setValueForm(emptyValueForm);\n setValueFormOpen(true);\n };\n\n const openEditValue = (value: LifeValueFieldsFragment) => {\n setEditingValue(value);\n setValueForm({\n name: value.name,\n description: value.description ?? \"\",\n weight: String(value.weight),\n domains: [...value.domains],\n isActive: value.isActive,\n });\n setValueFormOpen(true);\n };\n\n const closeValueForm = () => {\n setValueFormOpen(false);\n setEditingValue(null);\n };\n\n const toggleValueDomain = (domain: TimeCampusLifeDomain) => {\n setValueForm((f) => ({\n ...f,\n domains: f.domains.includes(domain)\n ? f.domains.filter((d) => d !== domain)\n : [...f.domains, domain],\n }));\n };\n\n const valueFormValid =\n valueForm.name.trim().length > 0 &&\n (valueForm.weight === \"\" || !Number.isNaN(Number(valueForm.weight)));\n\n const handleSubmitValue = async () => {\n if (!valueFormValid) return;\n const weight =\n valueForm.weight.trim() === \"\" ? undefined : Number(valueForm.weight);\n const input = {\n name: valueForm.name.trim(),\n description: valueForm.description.trim() || undefined,\n weight,\n domains: valueForm.domains,\n isActive: valueForm.isActive,\n };\n try {\n if (editingValue) {\n await updateValue({ variables: { id: editingValue.id, input } });\n addToast({ title: \"Value updated\", variant: \"success\" });\n } else {\n await declareValue({ variables: { input } });\n addToast({ title: \"Value declared\", variant: \"success\" });\n }\n closeValueForm();\n // Goals reference value names, so refresh both after a value change.\n await Promise.all([refetchValues(), refetchGoals()]);\n } catch {\n addToast({\n title: editingValue\n ? \"Failed to update value\"\n : \"Failed to declare value\",\n variant: \"error\",\n });\n }\n };\n\n const handleDeleteValue = async () => {\n if (!deleteValueTarget) return;\n try {\n await deleteValue({ variables: { id: deleteValueTarget.id } });\n addToast({ title: \"Value deleted\", variant: \"success\" });\n setDeleteValueTarget(null);\n await Promise.all([refetchValues(), refetchGoals()]);\n } catch {\n addToast({ title: \"Failed to delete value\", variant: \"error\" });\n }\n };\n\n // --- Goal handlers ------------------------------------------------------\n const openCreateGoal = () => {\n setEditingGoal(null);\n setGoalForm(emptyGoalForm);\n setGoalFormOpen(true);\n };\n\n const openEditGoal = (goal: LifeGoalFieldsFragment) => {\n setEditingGoal(goal);\n setGoalForm({\n title: goal.title,\n domain: goal.domain,\n targetValue: String(goal.targetValue),\n unit: goal.unit,\n cadence: goal.cadence,\n status: goal.status,\n valueId: goal.valueId ?? \"\",\n });\n setGoalFormOpen(true);\n };\n\n const closeGoalForm = () => {\n setGoalFormOpen(false);\n setEditingGoal(null);\n };\n\n const goalFormValid =\n goalForm.title.trim().length > 0 &&\n goalForm.targetValue.trim() !== \"\" &&\n !Number.isNaN(Number(goalForm.targetValue));\n\n const handleSubmitGoal = async () => {\n if (!goalFormValid) return;\n const input = {\n title: goalForm.title.trim(),\n domain: goalForm.domain,\n targetValue: Number(goalForm.targetValue),\n unit: goalForm.unit.trim() || undefined,\n cadence: goalForm.cadence || undefined,\n status: goalForm.status || undefined,\n valueId: goalForm.valueId || undefined,\n };\n try {\n if (editingGoal) {\n await updateGoal({ variables: { id: editingGoal.id, input } });\n addToast({ title: \"Goal updated\", variant: \"success\" });\n } else {\n await createGoal({ variables: { input } });\n addToast({ title: \"Goal created\", variant: \"success\" });\n }\n closeGoalForm();\n await refetchGoals();\n } catch {\n addToast({\n title: editingGoal ? \"Failed to update goal\" : \"Failed to create goal\",\n variant: \"error\",\n });\n }\n };\n\n const handleDeleteGoal = async () => {\n if (!deleteGoalTarget) return;\n try {\n await deleteGoal({ variables: { id: deleteGoalTarget.id } });\n addToast({ title: \"Goal deleted\", variant: \"success\" });\n setDeleteGoalTarget(null);\n await refetchGoals();\n } catch {\n addToast({ title: \"Failed to delete goal\", variant: \"error\" });\n }\n };\n\n // --- Render helpers -----------------------------------------------------\n const declareValueButton = (\n <button onClick={openCreateValue} className={primaryBtnClass}>\n <Plus className=\"h-4 w-4\" />\n Declare value\n </button>\n );\n\n const addGoalButton = (\n <button onClick={openCreateGoal} className={primaryBtnClass}>\n <Plus className=\"h-4 w-4\" />\n Add goal\n </button>\n );\n\n const hasAnyData = values.length > 0 || goals.length > 0;\n const initialLoading =\n valuesLoading && goalsLoading && !valuesData && !goalsData;\n\n if (initialLoading) {\n return (\n <PageLayout\n title=\"Values & Goals\"\n description=\"Declare what matters and set the life goals that shape it\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Values & Goals\"\n description=\"Declare what matters and set the life goals that shape it\"\n 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.\"\n actions={declareValueButton}\n >\n {hasAnyData && (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Active values\"\n value={String(activeValues)}\n icon={<Compass className=\"h-5 w-5\" />}\n description={`of ${values.length} declared`}\n />\n <StatCard\n title=\"Life goals\"\n value={String(goals.length)}\n icon={<Flag className=\"h-5 w-5\" />}\n description=\"targets you're working toward\"\n />\n <StatCard\n title=\"Domains covered\"\n value={String(domainsCovered)}\n icon={<Layers className=\"h-5 w-5\" />}\n description=\"life areas your values touch\"\n />\n </div>\n )}\n\n {/* ------------------------------------------------------------------ */}\n {/* VALUES */}\n {/* ------------------------------------------------------------------ */}\n <section className=\"mt-6\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n What you value\n </h2>\n <p className=\"text-sm text-text-secondary\">\n The priorities that weight your Balance Wheel.\n </p>\n </div>\n </div>\n\n {valuesError ? (\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 values. Please try again later.\n </div>\n ) : valuesLoading && values.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : values.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Declare what you value\"\n 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.\"\n action={declareValueButton}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {values.map((value) => {\n const Icon = domainIcon(\n value.domains[0] ?? TimeCampusLifeDomain.Other,\n );\n return (\n <GlassCard\n key={value.id}\n treatment=\"glass\"\n glow={value.isActive}\n className=\"flex flex-col gap-3 p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div 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 </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => openEditValue(value)}\n title=\"Edit value\"\n className={iconBtnClass}\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteValueTarget(value)}\n title=\"Delete value\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"min-w-0\">\n <p className=\"truncate text-base font-semibold text-text-primary\">\n {value.name}\n </p>\n {value.description && (\n <p className=\"mt-0.5 line-clamp-2 text-sm text-text-secondary\">\n {value.description}\n </p>\n )}\n </div>\n\n <ProgressMeter\n value={value.weight}\n max={maxWeight}\n label=\"Importance\"\n higherIsWorse={false}\n size=\"sm\"\n format={(v) => v.toFixed(1)}\n />\n\n {value.domains.length > 0 ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {value.domains.map((domain) => (\n <span\n key={domain}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {lifeDomainLabel(domain)}\n </span>\n ))}\n </div>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n No domains linked yet.\n </p>\n )}\n\n <div className=\"mt-auto pt-1\">\n <StatusBadge\n variant={value.isActive ? \"success\" : \"muted\"}\n label={value.isActive ? \"Active\" : \"Inactive\"}\n />\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </section>\n\n {/* ------------------------------------------------------------------ */}\n {/* GOALS */}\n {/* ------------------------------------------------------------------ */}\n <section className=\"mt-8\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n Key goals\n </h2>\n <p className=\"text-sm text-text-secondary\">\n Concrete targets that put your values into practice.\n </p>\n </div>\n {goals.length > 0 && addGoalButton}\n </div>\n\n {goalsError ? (\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 goals. Please try again later.\n </div>\n ) : goalsLoading && goals.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : goals.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Set your first life goal\"\n 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.\"\n action={addGoalButton}\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {goals.map((goal) => {\n const Icon = domainIcon(goal.domain);\n const linkedValueName = goal.valueId\n ? valueNameById.get(goal.valueId)\n : undefined;\n return (\n <div\n key={goal.id}\n className=\"flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {goal.title}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {lifeDomainLabel(goal.domain)}\n {linkedValueName\n ? ` · Supports ${linkedValueName}`\n : \"\"}\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2 sm:justify-end\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-1 text-xs font-medium text-text-secondary\">\n <Target className=\"h-3.5 w-3.5\" />\n {goal.targetValue} {goal.unit} ·{\" \"}\n {titleCase(goal.cadence)}\n </span>\n <StatusBadge\n status={goal.status}\n label={titleCase(goal.status)}\n />\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => openEditGoal(goal)}\n title=\"Edit goal\"\n className={iconBtnClass}\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteGoalTarget(goal)}\n title=\"Delete goal\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </section>\n\n {values.length > 0 && goals.length > 0 && (\n <ChartCard\n title=\"How your goals map to your values\"\n description=\"Every goal counts toward the value it supports — the fuel behind your Balance Wheel.\"\n className=\"mt-6\"\n >\n <p className=\"text-sm text-text-secondary\">\n {goals.filter((g) => g.valueId).length} of {goals.length} goals are\n linked to a declared value. Linking a goal to a value tells\n TimeCampus which priority the time you invest is really serving.\n </p>\n </ChartCard>\n )}\n\n {/* ------------------------------------------------------------------ */}\n {/* Value create / edit modal */}\n {/* ------------------------------------------------------------------ */}\n <Modal\n open={valueFormOpen}\n onClose={closeValueForm}\n title={editingValue ? \"Edit value\" : \"Declare a value\"}\n description={\n editingValue\n ? \"Update what this value stands for and how much it weighs.\"\n : \"Name a life area that matters and how the domains map to it.\"\n }\n footer={\n <>\n <button onClick={closeValueForm} className={secondaryBtnClass}>\n Cancel\n </button>\n <button\n onClick={handleSubmitValue}\n disabled={!valueFormValid || declaringValue || updatingValue}\n className={primaryBtnClass}\n >\n {declaringValue || updatingValue\n ? \"Saving...\"\n : editingValue\n ? \"Save changes\"\n : \"Declare value\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Name</label>\n <input\n type=\"text\"\n placeholder=\"e.g. Family, Health, Craft\"\n value={valueForm.name}\n onChange={(e) =>\n setValueForm({ ...valueForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n rows={2}\n placeholder=\"What does this value mean to you?\"\n value={valueForm.description}\n onChange={(e) =>\n setValueForm({ ...valueForm, description: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Weight (relative importance)</label>\n <input\n type=\"number\"\n min={0}\n step=\"0.1\"\n placeholder=\"1\"\n value={valueForm.weight}\n onChange={(e) =>\n setValueForm({ ...valueForm, weight: e.target.value })\n }\n className={inputClass}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Higher weights pull your Balance Wheel harder toward this value.\n </p>\n </div>\n <div>\n <label className={labelClass}>Domains</label>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Which life domains does this value cover?\n </p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {ALL_LIFE_DOMAINS.map((domain) => {\n const selected = valueForm.domains.includes(domain);\n return (\n <button\n key={domain}\n type=\"button\"\n onClick={() => toggleValueDomain(domain)}\n className={\n selected\n ? \"rounded-full bg-action-primary-bg px-3 py-1 text-xs font-medium text-action-primary-text\"\n : \"rounded-full border border-border-subtle px-3 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50\"\n }\n >\n {lifeDomainLabel(domain)}\n </button>\n );\n })}\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={valueForm.isActive}\n onChange={(e) =>\n setValueForm({ ...valueForm, isActive: e.target.checked })\n }\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n Active — count this value in my Balance Wheel\n </label>\n </div>\n </Modal>\n\n {/* Value delete confirm */}\n <Modal\n open={deleteValueTarget !== null}\n onClose={() => setDeleteValueTarget(null)}\n title=\"Delete value?\"\n description={\n deleteValueTarget\n ? `\"${deleteValueTarget.name}\" will be removed from your Balance Wheel.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteValueTarget(null)}\n className={secondaryBtnClass}\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteValue}\n disabled={deletingValue}\n className={dangerBtnClass}\n >\n {deletingValue ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n This permanently removes the value. Goals linked to it are kept but\n will no longer point to a value. This cannot be undone.\n </p>\n </Modal>\n\n {/* ------------------------------------------------------------------ */}\n {/* Goal create / edit modal */}\n {/* ------------------------------------------------------------------ */}\n <Modal\n open={goalFormOpen}\n onClose={closeGoalForm}\n title={editingGoal ? \"Edit goal\" : \"Add a goal\"}\n description={\n editingGoal\n ? \"Update this goal's target and status.\"\n : \"Set a concrete target that puts one of your values into practice.\"\n }\n footer={\n <>\n <button onClick={closeGoalForm} className={secondaryBtnClass}>\n Cancel\n </button>\n <button\n onClick={handleSubmitGoal}\n disabled={!goalFormValid || creatingGoal || updatingGoal}\n className={primaryBtnClass}\n >\n {creatingGoal || updatingGoal\n ? \"Saving...\"\n : editingGoal\n ? \"Save changes\"\n : \"Add goal\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"e.g. Run a marathon, 12h family time\"\n value={goalForm.title}\n onChange={(e) =>\n setGoalForm({ ...goalForm, title: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Domain</label>\n <select\n value={goalForm.domain}\n onChange={(e) =>\n setGoalForm({\n ...goalForm,\n domain: e.target.value as TimeCampusLifeDomain,\n })\n }\n className={inputClass}\n >\n {ALL_LIFE_DOMAINS.map((domain) => (\n <option key={domain} value={domain}>\n {lifeDomainLabel(domain)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Supports value</label>\n <select\n value={goalForm.valueId}\n onChange={(e) =>\n setGoalForm({ ...goalForm, valueId: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"\">None</option>\n {values.map((v) => (\n <option key={v.id} value={v.id}>\n {v.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label className={labelClass}>Target</label>\n <input\n type=\"number\"\n min={0}\n step=\"0.5\"\n placeholder=\"12\"\n value={goalForm.targetValue}\n onChange={(e) =>\n setGoalForm({ ...goalForm, targetValue: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Unit</label>\n <input\n type=\"text\"\n list=\"timecampus-goal-units\"\n placeholder=\"hours\"\n value={goalForm.unit}\n onChange={(e) =>\n setGoalForm({ ...goalForm, unit: e.target.value })\n }\n className={inputClass}\n />\n <datalist id=\"timecampus-goal-units\">\n {UNIT_SUGGESTIONS.map((u) => (\n <option key={u} value={u} />\n ))}\n </datalist>\n </div>\n <div>\n <label className={labelClass}>Cadence</label>\n <select\n value={goalForm.cadence}\n onChange={(e) =>\n setGoalForm({ ...goalForm, cadence: e.target.value })\n }\n className={inputClass}\n >\n {CADENCE_OPTIONS.map((c) => (\n <option key={c} value={c}>\n {titleCase(c)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={goalForm.status}\n onChange={(e) =>\n setGoalForm({ ...goalForm, status: e.target.value })\n }\n className={inputClass}\n >\n {GOAL_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {titleCase(s)}\n </option>\n ))}\n </select>\n </div>\n </div>\n </Modal>\n\n {/* Goal delete confirm */}\n <Modal\n open={deleteGoalTarget !== null}\n onClose={() => setDeleteGoalTarget(null)}\n title=\"Delete goal?\"\n description={\n deleteGoalTarget\n ? `\"${deleteGoalTarget.title}\" will be removed.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteGoalTarget(null)}\n className={secondaryBtnClass}\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteGoal}\n disabled={deletingGoal}\n className={dangerBtnClass}\n >\n {deletingGoal ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n This permanently removes the goal. This action cannot be undone.\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":"ValuesGoalsPage.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/ValuesGoalsPage.tsx"],"sourcesContent":["import { useState, type ComponentType } from \"react\";\nimport {\n Plus,\n Pencil,\n Trash2,\n Compass,\n Flag,\n Layers,\n Target,\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 Sparkles,\n Circle,\n} from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n EmptyState,\n LoadingSkeleton,\n StatusBadge,\n Modal,\n ChartCard,\n ProgressMeter,\n ToastContainer,\n useToast,\n} from \"../../components\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport { lifeDomainLabel, ALL_LIFE_DOMAINS } from \"./domains\";\nimport { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\nimport {\n useMyLifeValuesQuery,\n useMyLifeGoalsQuery,\n useDeclareLifeValueMutation,\n useUpdateLifeValueMutation,\n useDeleteLifeValueMutation,\n useCreateLifeGoalMutation,\n useUpdateLifeGoalMutation,\n useDeleteLifeGoalMutation,\n type LifeValueFieldsFragment,\n type LifeGoalFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\n// ---------------------------------------------------------------------------\n// Static option sets (free-text scalars on the backend — we present a curated\n// list but always send/read a plain string).\n// ---------------------------------------------------------------------------\nconst CADENCE_OPTIONS = [\n \"daily\",\n \"weekly\",\n \"monthly\",\n \"quarterly\",\n \"yearly\",\n] as const;\n\nconst GOAL_STATUS_OPTIONS = [\n \"active\",\n \"paused\",\n \"completed\",\n \"archived\",\n] as const;\n\nconst UNIT_SUGGESTIONS = [\"hours\", \"sessions\", \"days\", \"times\"];\n\n/** Icon per life domain, so a value/goal reads at a glance. */\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\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\nconst primaryBtnClass =\n \"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 disabled:opacity-50\";\nconst secondaryBtnClass =\n \"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\";\nconst dangerBtnClass =\n \"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\";\nconst iconBtnClass =\n \"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\";\n\nfunction titleCase(value: string): string {\n if (!value) return value;\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction domainIcon(\n domain: TimeCampusLifeDomain,\n): ComponentType<{ className?: string }> {\n return DOMAIN_ICON[domain] ?? Circle;\n}\n\n// ---------------------------------------------------------------------------\n// Value form\n// ---------------------------------------------------------------------------\ninterface ValueFormState {\n name: string;\n description: string;\n weight: string;\n domains: TimeCampusLifeDomain[];\n isActive: boolean;\n}\n\nconst emptyValueForm: ValueFormState = {\n name: \"\",\n description: \"\",\n weight: \"1\",\n domains: [],\n isActive: true,\n};\n\n// ---------------------------------------------------------------------------\n// Goal form\n// ---------------------------------------------------------------------------\ninterface GoalFormState {\n title: string;\n domain: TimeCampusLifeDomain;\n targetValue: string;\n unit: string;\n cadence: string;\n status: string;\n valueId: string;\n}\n\nconst emptyGoalForm: GoalFormState = {\n title: \"\",\n domain: TimeCampusLifeDomain.Health,\n targetValue: \"\",\n unit: \"hours\",\n cadence: \"weekly\",\n status: \"active\",\n valueId: \"\",\n};\n\nexport function ValuesGoalsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: valuesData,\n loading: valuesLoading,\n error: valuesError,\n refetch: refetchValues,\n } = useMyLifeValuesQuery({ skip: !workspaceId });\n\n const {\n data: goalsData,\n loading: goalsLoading,\n error: goalsError,\n refetch: refetchGoals,\n } = useMyLifeGoalsQuery({ skip: !workspaceId });\n\n const [declareValue, { loading: declaringValue }] =\n useDeclareLifeValueMutation();\n const [updateValue, { loading: updatingValue }] =\n useUpdateLifeValueMutation();\n const [deleteValue, { loading: deletingValue }] =\n useDeleteLifeValueMutation();\n const [createGoal, { loading: creatingGoal }] = useCreateLifeGoalMutation();\n const [updateGoal, { loading: updatingGoal }] = useUpdateLifeGoalMutation();\n const [deleteGoal, { loading: deletingGoal }] = useDeleteLifeGoalMutation();\n\n const values: LifeValueFieldsFragment[] = valuesData?.myLifeValues ?? [];\n const goals: LifeGoalFieldsFragment[] = goalsData?.myLifeGoals ?? [];\n\n // Derived, honest summary metrics from the fetched data only.\n const activeValues = values.filter((v) => v.isActive).length;\n const domainsCovered = new Set(values.flatMap((v) => v.domains)).size;\n const maxWeight = Math.max(1, ...values.map((v) => v.weight));\n const valueNameById = new Map(values.map((v) => [v.id, v.name] as const));\n\n // --- Value modal state --------------------------------------------------\n const [valueFormOpen, setValueFormOpen] = useState(false);\n const [editingValue, setEditingValue] =\n useState<LifeValueFieldsFragment | null>(null);\n const [valueForm, setValueForm] = useState<ValueFormState>(emptyValueForm);\n const [deleteValueTarget, setDeleteValueTarget] =\n useState<LifeValueFieldsFragment | null>(null);\n\n // --- Goal modal state ---------------------------------------------------\n const [goalFormOpen, setGoalFormOpen] = useState(false);\n const [editingGoal, setEditingGoal] = useState<LifeGoalFieldsFragment | null>(\n null,\n );\n const [goalForm, setGoalForm] = useState<GoalFormState>(emptyGoalForm);\n const [deleteGoalTarget, setDeleteGoalTarget] =\n useState<LifeGoalFieldsFragment | null>(null);\n\n // --- Value handlers -----------------------------------------------------\n const openCreateValue = () => {\n setEditingValue(null);\n setValueForm(emptyValueForm);\n setValueFormOpen(true);\n };\n\n const openEditValue = (value: LifeValueFieldsFragment) => {\n setEditingValue(value);\n setValueForm({\n name: value.name,\n description: value.description ?? \"\",\n weight: String(value.weight),\n domains: [...value.domains],\n isActive: value.isActive,\n });\n setValueFormOpen(true);\n };\n\n const closeValueForm = () => {\n setValueFormOpen(false);\n setEditingValue(null);\n };\n\n const toggleValueDomain = (domain: TimeCampusLifeDomain) => {\n setValueForm((f) => ({\n ...f,\n domains: f.domains.includes(domain)\n ? f.domains.filter((d) => d !== domain)\n : [...f.domains, domain],\n }));\n };\n\n const valueFormValid =\n valueForm.name.trim().length > 0 &&\n (valueForm.weight === \"\" || !Number.isNaN(Number(valueForm.weight)));\n\n const handleSubmitValue = async () => {\n if (!valueFormValid) return;\n const weight =\n valueForm.weight.trim() === \"\" ? undefined : Number(valueForm.weight);\n const input = {\n name: valueForm.name.trim(),\n description: valueForm.description.trim() || undefined,\n weight,\n domains: valueForm.domains,\n isActive: valueForm.isActive,\n };\n try {\n if (editingValue) {\n await updateValue({ variables: { id: editingValue.id, input } });\n addToast({ title: \"Value updated\", variant: \"success\" });\n } else {\n await declareValue({ variables: { input } });\n addToast({ title: \"Value declared\", variant: \"success\" });\n }\n closeValueForm();\n // Goals reference value names, so refresh both after a value change.\n await Promise.all([refetchValues(), refetchGoals()]);\n } catch {\n addToast({\n title: editingValue\n ? \"Failed to update value\"\n : \"Failed to declare value\",\n variant: \"error\",\n });\n }\n };\n\n const handleDeleteValue = async () => {\n if (!deleteValueTarget) return;\n try {\n await deleteValue({ variables: { id: deleteValueTarget.id } });\n addToast({ title: \"Value deleted\", variant: \"success\" });\n setDeleteValueTarget(null);\n await Promise.all([refetchValues(), refetchGoals()]);\n } catch {\n addToast({ title: \"Failed to delete value\", variant: \"error\" });\n }\n };\n\n // --- Goal handlers ------------------------------------------------------\n const openCreateGoal = () => {\n setEditingGoal(null);\n setGoalForm(emptyGoalForm);\n setGoalFormOpen(true);\n };\n\n const openEditGoal = (goal: LifeGoalFieldsFragment) => {\n setEditingGoal(goal);\n setGoalForm({\n title: goal.title,\n domain: goal.domain,\n targetValue: String(goal.targetValue),\n unit: goal.unit,\n cadence: goal.cadence,\n status: goal.status,\n valueId: goal.valueId ?? \"\",\n });\n setGoalFormOpen(true);\n };\n\n const closeGoalForm = () => {\n setGoalFormOpen(false);\n setEditingGoal(null);\n };\n\n const goalFormValid =\n goalForm.title.trim().length > 0 &&\n goalForm.targetValue.trim() !== \"\" &&\n !Number.isNaN(Number(goalForm.targetValue));\n\n const handleSubmitGoal = async () => {\n if (!goalFormValid) return;\n const input = {\n title: goalForm.title.trim(),\n domain: goalForm.domain,\n targetValue: Number(goalForm.targetValue),\n unit: goalForm.unit.trim() || undefined,\n cadence: goalForm.cadence || undefined,\n status: goalForm.status || undefined,\n valueId: goalForm.valueId || undefined,\n };\n try {\n if (editingGoal) {\n await updateGoal({ variables: { id: editingGoal.id, input } });\n addToast({ title: \"Goal updated\", variant: \"success\" });\n } else {\n await createGoal({ variables: { input } });\n addToast({ title: \"Goal created\", variant: \"success\" });\n }\n closeGoalForm();\n await refetchGoals();\n } catch {\n addToast({\n title: editingGoal ? \"Failed to update goal\" : \"Failed to create goal\",\n variant: \"error\",\n });\n }\n };\n\n const handleDeleteGoal = async () => {\n if (!deleteGoalTarget) return;\n try {\n await deleteGoal({ variables: { id: deleteGoalTarget.id } });\n addToast({ title: \"Goal deleted\", variant: \"success\" });\n setDeleteGoalTarget(null);\n await refetchGoals();\n } catch {\n addToast({ title: \"Failed to delete goal\", variant: \"error\" });\n }\n };\n\n // --- Render helpers -----------------------------------------------------\n const declareValueButton = (\n <button onClick={openCreateValue} className={primaryBtnClass}>\n <Plus className=\"h-4 w-4\" />\n Declare value\n </button>\n );\n\n const addGoalButton = (\n <button onClick={openCreateGoal} className={primaryBtnClass}>\n <Plus className=\"h-4 w-4\" />\n Add goal\n </button>\n );\n\n const hasAnyData = values.length > 0 || goals.length > 0;\n const initialLoading =\n valuesLoading && goalsLoading && !valuesData && !goalsData;\n\n if (initialLoading) {\n return (\n <PageLayout\n title=\"Values & Goals\"\n description=\"Declare what matters and set the life goals that shape it\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Values & Goals\"\n description=\"Declare what matters and set the life goals that shape it\"\n 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.\"\n actions={declareValueButton}\n >\n {hasAnyData && (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Active values\"\n value={String(activeValues)}\n icon={<Compass className=\"h-5 w-5\" />}\n description={`of ${values.length} declared`}\n />\n <StatCard\n title=\"Life goals\"\n value={String(goals.length)}\n icon={<Flag className=\"h-5 w-5\" />}\n description=\"targets you're working toward\"\n />\n <StatCard\n title=\"Domains covered\"\n value={String(domainsCovered)}\n icon={<Layers className=\"h-5 w-5\" />}\n description=\"life areas your values touch\"\n />\n </div>\n )}\n\n {/* ------------------------------------------------------------------ */}\n {/* VALUES */}\n {/* ------------------------------------------------------------------ */}\n <section className=\"mt-6\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n What you value\n </h2>\n <p className=\"text-sm text-text-secondary\">\n The priorities that weight your Balance Wheel.\n </p>\n </div>\n </div>\n\n {valuesError ? (\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 values. Please try again later.\n </div>\n ) : valuesLoading && values.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : values.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Declare what you value\"\n 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.\"\n action={declareValueButton}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {values.map((value) => {\n const Icon = domainIcon(\n value.domains[0] ?? TimeCampusLifeDomain.Other,\n );\n return (\n <GlassCard\n key={value.id}\n treatment=\"glass\"\n glow={value.isActive}\n className=\"flex flex-col gap-3 p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div 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 </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => openEditValue(value)}\n title=\"Edit value\"\n className={iconBtnClass}\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteValueTarget(value)}\n title=\"Delete value\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"min-w-0\">\n <p className=\"truncate text-base font-semibold text-text-primary\">\n {value.name}\n </p>\n {value.description && (\n <p className=\"mt-0.5 line-clamp-2 text-sm text-text-secondary\">\n {value.description}\n </p>\n )}\n </div>\n\n <ProgressMeter\n value={value.weight}\n max={maxWeight}\n label=\"Importance\"\n higherIsWorse={false}\n size=\"sm\"\n format={(v) => v.toFixed(1)}\n />\n\n {value.domains.length > 0 ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {value.domains.map((domain) => (\n <span\n key={domain}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {lifeDomainLabel(domain)}\n </span>\n ))}\n </div>\n ) : (\n <p className=\"text-xs text-text-secondary\">\n No domains linked yet.\n </p>\n )}\n\n <div className=\"mt-auto pt-1\">\n <StatusBadge\n variant={value.isActive ? \"success\" : \"muted\"}\n label={value.isActive ? \"Active\" : \"Inactive\"}\n />\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </section>\n\n {/* ------------------------------------------------------------------ */}\n {/* GOALS */}\n {/* ------------------------------------------------------------------ */}\n <section className=\"mt-8\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n Key goals\n </h2>\n <p className=\"text-sm text-text-secondary\">\n Concrete targets that put your values into practice.\n </p>\n </div>\n {goals.length > 0 && addGoalButton}\n </div>\n\n {goalsError ? (\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 goals. Please try again later.\n </div>\n ) : goalsLoading && goals.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={3} />\n ) : goals.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"Set your first life goal\"\n 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.\"\n action={addGoalButton}\n />\n ) : (\n <div className=\"overflow-hidden rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {goals.map((goal) => {\n const Icon = domainIcon(goal.domain);\n const linkedValueName = goal.valueId\n ? valueNameById.get(goal.valueId)\n : undefined;\n return (\n <div\n key={goal.id}\n className=\"flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Icon className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {goal.title}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {lifeDomainLabel(goal.domain)}\n {linkedValueName\n ? ` · Supports ${linkedValueName}`\n : \"\"}\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2 sm:justify-end\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-1 text-xs font-medium text-text-secondary\">\n <Target className=\"h-3.5 w-3.5\" />\n {goal.targetValue} {goal.unit} ·{\" \"}\n {titleCase(goal.cadence)}\n </span>\n <StatusBadge\n status={goal.status}\n label={titleCase(goal.status)}\n />\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => openEditGoal(goal)}\n title=\"Edit goal\"\n className={iconBtnClass}\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteGoalTarget(goal)}\n title=\"Delete goal\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </section>\n\n {values.length > 0 && goals.length > 0 && (\n <ChartCard\n title=\"How your goals map to your values\"\n description=\"Every goal counts toward the value it supports — the fuel behind your Balance Wheel.\"\n className=\"mt-6\"\n >\n <p className=\"text-sm text-text-secondary\">\n {goals.filter((g) => g.valueId).length} of {goals.length} goals are\n linked to a declared value. Linking a goal to a value tells\n TimeCampus which priority the time you invest is really serving.\n </p>\n </ChartCard>\n )}\n\n {/* ------------------------------------------------------------------ */}\n {/* Value create / edit modal */}\n {/* ------------------------------------------------------------------ */}\n <Modal\n open={valueFormOpen}\n onClose={closeValueForm}\n title={editingValue ? \"Edit value\" : \"Declare a value\"}\n description={\n editingValue\n ? \"Update what this value stands for and how much it weighs.\"\n : \"Name a life area that matters and how the domains map to it.\"\n }\n footer={\n <>\n <button onClick={closeValueForm} className={secondaryBtnClass}>\n Cancel\n </button>\n <button\n onClick={handleSubmitValue}\n disabled={!valueFormValid || declaringValue || updatingValue}\n className={primaryBtnClass}\n >\n {declaringValue || updatingValue\n ? \"Saving...\"\n : editingValue\n ? \"Save changes\"\n : \"Declare value\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Name</label>\n <input\n type=\"text\"\n placeholder=\"e.g. Family, Health, Craft\"\n value={valueForm.name}\n onChange={(e) =>\n setValueForm({ ...valueForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n rows={2}\n placeholder=\"What does this value mean to you?\"\n value={valueForm.description}\n onChange={(e) =>\n setValueForm({ ...valueForm, description: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Weight (relative importance)</label>\n <input\n type=\"number\"\n min={0}\n step=\"0.1\"\n placeholder=\"1\"\n value={valueForm.weight}\n onChange={(e) =>\n setValueForm({ ...valueForm, weight: e.target.value })\n }\n className={inputClass}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Higher weights pull your Balance Wheel harder toward this value.\n </p>\n </div>\n <div>\n <label className={labelClass}>Domains</label>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Which life domains does this value cover?\n </p>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n {ALL_LIFE_DOMAINS.map((domain) => {\n const selected = valueForm.domains.includes(domain);\n return (\n <button\n key={domain}\n type=\"button\"\n onClick={() => toggleValueDomain(domain)}\n className={\n selected\n ? \"rounded-full bg-action-primary-bg px-3 py-1 text-xs font-medium text-action-primary-text\"\n : \"rounded-full border border-border-subtle px-3 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50\"\n }\n >\n {lifeDomainLabel(domain)}\n </button>\n );\n })}\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={valueForm.isActive}\n onChange={(e) =>\n setValueForm({ ...valueForm, isActive: e.target.checked })\n }\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n Active — count this value in my Balance Wheel\n </label>\n </div>\n </Modal>\n\n {/* Value delete confirm */}\n <Modal\n open={deleteValueTarget !== null}\n onClose={() => setDeleteValueTarget(null)}\n title=\"Delete value?\"\n description={\n deleteValueTarget\n ? `\"${deleteValueTarget.name}\" will be removed from your Balance Wheel.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteValueTarget(null)}\n className={secondaryBtnClass}\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteValue}\n disabled={deletingValue}\n className={dangerBtnClass}\n >\n {deletingValue ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n This permanently removes the value. Goals linked to it are kept but\n will no longer point to a value. This cannot be undone.\n </p>\n </Modal>\n\n {/* ------------------------------------------------------------------ */}\n {/* Goal create / edit modal */}\n {/* ------------------------------------------------------------------ */}\n <Modal\n open={goalFormOpen}\n onClose={closeGoalForm}\n title={editingGoal ? \"Edit goal\" : \"Add a goal\"}\n description={\n editingGoal\n ? \"Update this goal's target and status.\"\n : \"Set a concrete target that puts one of your values into practice.\"\n }\n footer={\n <>\n <button onClick={closeGoalForm} className={secondaryBtnClass}>\n Cancel\n </button>\n <button\n onClick={handleSubmitGoal}\n disabled={!goalFormValid || creatingGoal || updatingGoal}\n className={primaryBtnClass}\n >\n {creatingGoal || updatingGoal\n ? \"Saving...\"\n : editingGoal\n ? \"Save changes\"\n : \"Add goal\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"e.g. Run a marathon, 12h family time\"\n value={goalForm.title}\n onChange={(e) =>\n setGoalForm({ ...goalForm, title: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Domain</label>\n <select\n value={goalForm.domain}\n onChange={(e) =>\n setGoalForm({\n ...goalForm,\n domain: e.target.value as TimeCampusLifeDomain,\n })\n }\n className={inputClass}\n >\n {ALL_LIFE_DOMAINS.map((domain) => (\n <option key={domain} value={domain}>\n {lifeDomainLabel(domain)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Supports value</label>\n <select\n value={goalForm.valueId}\n onChange={(e) =>\n setGoalForm({ ...goalForm, valueId: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"\">None</option>\n {values.map((v) => (\n <option key={v.id} value={v.id}>\n {v.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label className={labelClass}>Target</label>\n <input\n type=\"number\"\n min={0}\n step=\"0.5\"\n placeholder=\"12\"\n value={goalForm.targetValue}\n onChange={(e) =>\n setGoalForm({ ...goalForm, targetValue: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Unit</label>\n <input\n type=\"text\"\n list=\"timecampus-goal-units\"\n placeholder=\"hours\"\n value={goalForm.unit}\n onChange={(e) =>\n setGoalForm({ ...goalForm, unit: e.target.value })\n }\n className={inputClass}\n />\n <datalist id=\"timecampus-goal-units\">\n {UNIT_SUGGESTIONS.map((u) => (\n <option key={u} value={u} />\n ))}\n </datalist>\n </div>\n <div>\n <label className={labelClass}>Cadence</label>\n <select\n value={goalForm.cadence}\n onChange={(e) =>\n setGoalForm({ ...goalForm, cadence: e.target.value })\n }\n className={inputClass}\n >\n {CADENCE_OPTIONS.map((c) => (\n <option key={c} value={c}>\n {titleCase(c)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={goalForm.status}\n onChange={(e) =>\n setGoalForm({ ...goalForm, status: e.target.value })\n }\n className={inputClass}\n >\n {GOAL_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {titleCase(s)}\n </option>\n ))}\n </select>\n </div>\n </div>\n </Modal>\n\n {/* Goal delete confirm */}\n <Modal\n open={deleteGoalTarget !== null}\n onClose={() => setDeleteGoalTarget(null)}\n title=\"Delete goal?\"\n description={\n deleteGoalTarget\n ? `\"${deleteGoalTarget.title}\" will be removed.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteGoalTarget(null)}\n className={secondaryBtnClass}\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteGoal}\n disabled={deletingGoal}\n className={dangerBtnClass}\n >\n {deletingGoal ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n This permanently removes the goal. This action cannot be undone.\n </p>\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { TimeCampusLifeDomain } from '../../../generated/wspace-types';
2
-
3
2
  /**
4
3
  * Human labels for the life-domain enum, shared by every life-time page so the
5
4
  * vocabulary stays consistent. The enum values themselves are the source of
@@ -51,6 +51,6 @@ function c() {
51
51
  return (/* @__PURE__ */ new Date()).toISOString();
52
52
  }
53
53
  //#endregion
54
- export { r as ALL_LIFE_DOMAINS, n as PERSONAL_LIFE_DOMAINS, o as formatSecondsHuman, s as isoDaysAgo, c as isoNow, i as lifeDomainLabel, a as secondsToHours };
54
+ export { r as ALL_LIFE_DOMAINS, t as LIFE_DOMAIN_LABEL, n as PERSONAL_LIFE_DOMAINS, o as formatSecondsHuman, s as isoDaysAgo, c as isoNow, i as lifeDomainLabel, a as secondsToHours };
55
55
 
56
56
  //# sourceMappingURL=domains.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"domains.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/domains.ts"],"sourcesContent":["import { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\n\n/**\n * Human labels for the life-domain enum, shared by every life-time page so the\n * vocabulary stays consistent. The enum values themselves are the source of\n * truth (stored as category strings on the backend); this only maps them to\n * display text.\n */\nexport const LIFE_DOMAIN_LABEL: Record<TimeCampusLifeDomain, string> = {\n [TimeCampusLifeDomain.Work]: \"Work\",\n [TimeCampusLifeDomain.Sleep]: \"Sleep\",\n [TimeCampusLifeDomain.Health]: \"Health\",\n [TimeCampusLifeDomain.Family]: \"Family & relationships\",\n [TimeCampusLifeDomain.Learning]: \"Learning\",\n [TimeCampusLifeDomain.Leisure]: \"Leisure\",\n [TimeCampusLifeDomain.Commute]: \"Commute\",\n [TimeCampusLifeDomain.Waiting]: \"Waiting\",\n [TimeCampusLifeDomain.Chores]: \"Chores\",\n [TimeCampusLifeDomain.SelfCare]: \"Self-care\",\n [TimeCampusLifeDomain.Social]: \"Social\",\n [TimeCampusLifeDomain.FinanceAdmin]: \"Finance & admin\",\n [TimeCampusLifeDomain.Reflection]: \"Reflection\",\n [TimeCampusLifeDomain.Other]: \"Other\",\n};\n\n/** Domains the user typically logs by hand (excludes WORK, which is captured). */\nexport const PERSONAL_LIFE_DOMAINS: TimeCampusLifeDomain[] = [\n TimeCampusLifeDomain.Sleep,\n TimeCampusLifeDomain.Health,\n TimeCampusLifeDomain.Family,\n TimeCampusLifeDomain.Learning,\n TimeCampusLifeDomain.Leisure,\n TimeCampusLifeDomain.Commute,\n TimeCampusLifeDomain.Waiting,\n TimeCampusLifeDomain.Chores,\n TimeCampusLifeDomain.SelfCare,\n TimeCampusLifeDomain.Social,\n TimeCampusLifeDomain.FinanceAdmin,\n TimeCampusLifeDomain.Reflection,\n];\n\n/** All life domains in a sensible display order. */\nexport const ALL_LIFE_DOMAINS: TimeCampusLifeDomain[] = [\n TimeCampusLifeDomain.Work,\n ...PERSONAL_LIFE_DOMAINS,\n TimeCampusLifeDomain.Other,\n];\n\nexport function lifeDomainLabel(domain: TimeCampusLifeDomain | string): string {\n return LIFE_DOMAIN_LABEL[domain as TimeCampusLifeDomain] ?? String(domain);\n}\n\n/** Whole hours from seconds, one decimal (e.g. 6.5). */\nexport function secondsToHours(seconds: number): number {\n return Math.round((seconds / 3600) * 10) / 10;\n}\n\n/** A compact \"6h 30m\" from seconds. */\nexport function formatSecondsHuman(seconds: number): string {\n const s = Math.max(0, Math.round(seconds));\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n if (h === 0 && m === 0) return \"0m\";\n if (h === 0) return `${m}m`;\n if (m === 0) return `${h}h`;\n return `${h}h ${m}m`;\n}\n\n/** ISO string for a date `days` ago at the start of the day. */\nexport function isoDaysAgo(days: number): string {\n const d = new Date(Date.now() - days * 86_400_000);\n d.setHours(0, 0, 0, 0);\n return d.toISOString();\n}\n\n/** ISO string for the end of today. */\nexport function isoNow(): string {\n return new Date().toISOString();\n}\n"],"mappings":";;AAQA,IAAa,IAA0D;EACpE,EAAqB,OAAO;EAC5B,EAAqB,QAAQ;EAC7B,EAAqB,SAAS;EAC9B,EAAqB,SAAS;EAC9B,EAAqB,WAAW;EAChC,EAAqB,UAAU;EAC/B,EAAqB,UAAU;EAC/B,EAAqB,UAAU;EAC/B,EAAqB,SAAS;EAC9B,EAAqB,WAAW;EAChC,EAAqB,SAAS;EAC9B,EAAqB,eAAe;EACpC,EAAqB,aAAa;EAClC,EAAqB,QAAQ;CAC/B,EAGY,IAAgD;CAC3D,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACtB,EAGY,IAA2C;CACtD,EAAqB;CACrB,GAAG;CACH,EAAqB;CACtB;AAED,SAAgB,EAAgB,GAA+C;AAC7E,QAAO,EAAkB,MAAmC,OAAO,EAAO;;AAI5E,SAAgB,EAAe,GAAyB;AACtD,QAAO,KAAK,MAAO,IAAU,OAAQ,GAAG,GAAG;;AAI7C,SAAgB,EAAmB,GAAyB;CAC1D,IAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAQ,CAAC,EACpC,IAAI,KAAK,MAAM,IAAI,KAAK,EACxB,IAAI,KAAK,MAAO,IAAI,OAAQ,GAAG;AAIrC,QAHI,MAAM,KAAK,MAAM,IAAU,OAC3B,MAAM,IAAU,GAAG,EAAE,KACrB,MAAM,IAAU,GAAG,EAAE,KAClB,GAAG,EAAE,IAAI,EAAE;;AAIpB,SAAgB,EAAW,GAAsB;CAC/C,IAAM,oBAAI,IAAI,KAAK,KAAK,KAAK,GAAG,IAAO,MAAW;AAElD,QADA,EAAE,SAAS,GAAG,GAAG,GAAG,EAAE,EACf,EAAE,aAAa;;AAIxB,SAAgB,IAAiB;AAC/B,yBAAO,IAAI,MAAM,EAAC,aAAa"}
1
+ {"version":3,"file":"domains.js","names":[],"sources":["../../../../src/timecampus/pages/lifetime/domains.ts"],"sourcesContent":["import { TimeCampusLifeDomain } from \"@/generated/wspace-types\";\n\n/**\n * Human labels for the life-domain enum, shared by every life-time page so the\n * vocabulary stays consistent. The enum values themselves are the source of\n * truth (stored as category strings on the backend); this only maps them to\n * display text.\n */\nexport const LIFE_DOMAIN_LABEL: Record<TimeCampusLifeDomain, string> = {\n [TimeCampusLifeDomain.Work]: \"Work\",\n [TimeCampusLifeDomain.Sleep]: \"Sleep\",\n [TimeCampusLifeDomain.Health]: \"Health\",\n [TimeCampusLifeDomain.Family]: \"Family & relationships\",\n [TimeCampusLifeDomain.Learning]: \"Learning\",\n [TimeCampusLifeDomain.Leisure]: \"Leisure\",\n [TimeCampusLifeDomain.Commute]: \"Commute\",\n [TimeCampusLifeDomain.Waiting]: \"Waiting\",\n [TimeCampusLifeDomain.Chores]: \"Chores\",\n [TimeCampusLifeDomain.SelfCare]: \"Self-care\",\n [TimeCampusLifeDomain.Social]: \"Social\",\n [TimeCampusLifeDomain.FinanceAdmin]: \"Finance & admin\",\n [TimeCampusLifeDomain.Reflection]: \"Reflection\",\n [TimeCampusLifeDomain.Other]: \"Other\",\n};\n\n/** Domains the user typically logs by hand (excludes WORK, which is captured). */\nexport const PERSONAL_LIFE_DOMAINS: TimeCampusLifeDomain[] = [\n TimeCampusLifeDomain.Sleep,\n TimeCampusLifeDomain.Health,\n TimeCampusLifeDomain.Family,\n TimeCampusLifeDomain.Learning,\n TimeCampusLifeDomain.Leisure,\n TimeCampusLifeDomain.Commute,\n TimeCampusLifeDomain.Waiting,\n TimeCampusLifeDomain.Chores,\n TimeCampusLifeDomain.SelfCare,\n TimeCampusLifeDomain.Social,\n TimeCampusLifeDomain.FinanceAdmin,\n TimeCampusLifeDomain.Reflection,\n];\n\n/** All life domains in a sensible display order. */\nexport const ALL_LIFE_DOMAINS: TimeCampusLifeDomain[] = [\n TimeCampusLifeDomain.Work,\n ...PERSONAL_LIFE_DOMAINS,\n TimeCampusLifeDomain.Other,\n];\n\nexport function lifeDomainLabel(domain: TimeCampusLifeDomain | string): string {\n return LIFE_DOMAIN_LABEL[domain as TimeCampusLifeDomain] ?? String(domain);\n}\n\n/** Whole hours from seconds, one decimal (e.g. 6.5). */\nexport function secondsToHours(seconds: number): number {\n return Math.round((seconds / 3600) * 10) / 10;\n}\n\n/** A compact \"6h 30m\" from seconds. */\nexport function formatSecondsHuman(seconds: number): string {\n const s = Math.max(0, Math.round(seconds));\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n if (h === 0 && m === 0) return \"0m\";\n if (h === 0) return `${m}m`;\n if (m === 0) return `${h}h`;\n return `${h}h ${m}m`;\n}\n\n/** ISO string for a date `days` ago at the start of the day. */\nexport function isoDaysAgo(days: number): string {\n const d = new Date(Date.now() - days * 86_400_000);\n d.setHours(0, 0, 0, 0);\n return d.toISOString();\n}\n\n/** ISO string for the end of today. */\nexport function isoNow(): string {\n return new Date().toISOString();\n}\n"],"mappings":";;AAQA,IAAa,IAA0D;EACpE,EAAqB,OAAO;EAC5B,EAAqB,QAAQ;EAC7B,EAAqB,SAAS;EAC9B,EAAqB,SAAS;EAC9B,EAAqB,WAAW;EAChC,EAAqB,UAAU;EAC/B,EAAqB,UAAU;EAC/B,EAAqB,UAAU;EAC/B,EAAqB,SAAS;EAC9B,EAAqB,WAAW;EAChC,EAAqB,SAAS;EAC9B,EAAqB,eAAe;EACpC,EAAqB,aAAa;EAClC,EAAqB,QAAQ;AAChC,GAGa,IAAgD;CAC3D,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;CACrB,EAAqB;AACvB,GAGa,IAA2C;CACtD,EAAqB;CACrB,GAAG;CACH,EAAqB;AACvB;AAEA,SAAgB,EAAgB,GAA+C;CAC7E,OAAO,EAAkB,MAAmC,OAAO,CAAM;AAC3E;AAGA,SAAgB,EAAe,GAAyB;CACtD,OAAO,KAAK,MAAO,IAAU,OAAQ,EAAE,IAAI;AAC7C;AAGA,SAAgB,EAAmB,GAAyB;CAC1D,IAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,CAAO,CAAC,GACnC,IAAI,KAAK,MAAM,IAAI,IAAI,GACvB,IAAI,KAAK,MAAO,IAAI,OAAQ,EAAE;CAIpC,OAHI,MAAM,KAAK,MAAM,IAAU,OAC3B,MAAM,IAAU,GAAG,EAAE,KACrB,MAAM,IAAU,GAAG,EAAE,KAClB,GAAG,EAAE,IAAI,EAAE;AACpB;AAGA,SAAgB,EAAW,GAAsB;CAC/C,IAAM,oBAAI,IAAI,KAAK,KAAK,IAAI,IAAI,IAAO,KAAU;CAEjD,OADA,EAAE,SAAS,GAAG,GAAG,GAAG,CAAC,GACd,EAAE,YAAY;AACvB;AAGA,SAAgB,IAAiB;CAC/B,wBAAO,IAAI,KAAK,EAAA,CAAE,YAAY;AAChC"}
@@ -1 +1 @@
1
- {"version":3,"file":"LocationTrackingPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"names":[],"mappings":"AAqDA,wBAAgB,oBAAoB,gCA+cnC"}
1
+ {"version":3,"file":"LocationTrackingPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"names":[],"mappings":"AAsDA,wBAAgB,oBAAoB,gCAkdnC"}
@@ -1 +1 @@
1
- {"version":3,"file":"LocationTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n MapPin,\n Clock,\n Plus,\n Trash2,\n Building2,\n Satellite,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n ProgressMeter,\n type ChartDatum,\n} from \"../../components\";\nimport { formatHours, formatDate, formatTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListLocationEntriesQuery,\n useGetLocationStatsQuery,\n useCreateLocationEntryMutation,\n useDeleteLocationEntryMutation,\n type LocationEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"mt-1 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\ninterface EntryFormState {\n address: string;\n latitude: string;\n longitude: string;\n startTime: string;\n endTime: string;\n}\n\nconst emptyForm: EntryFormState = {\n address: \"\",\n latitude: \"\",\n longitude: \"\",\n startTime: \"\",\n endTime: \"\",\n};\n\nexport function LocationTrackingPage() {\n const { currentUser } = useTimeCampus();\n\n const {\n data: entriesData,\n loading: entriesLoading,\n error: entriesError,\n refetch: refetchEntries,\n fetchMore,\n } = useListLocationEntriesQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetLocationStatsQuery();\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateLocationEntryMutation();\n const [deleteEntry, { loading: deleting }] = useDeleteLocationEntryMutation();\n\n const [formOpen, setFormOpen] = useState(false);\n const [form, setForm] = useState<EntryFormState>(emptyForm);\n const [confirmDelete, setConfirmDelete] =\n useState<LocationEntryFieldsFragment | null>(null);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const locations = entriesData?.locationEntries?.items ?? [];\n const hasMore = entriesData?.locationEntries?.hasMore ?? false;\n const stats = statsData?.locationStats;\n\n // ---- Derived, real-data views over the loaded entries -------------------\n const geofencedCount = locations.filter(\n (l: LocationEntryFieldsFragment) => l.geofenceId,\n ).length;\n const manualCount = locations.length - geofencedCount;\n const sourceSlices = [\n { name: \"Geofenced\", value: geofencedCount },\n { name: \"Manual\", value: manualCount },\n ];\n\n const maxDuration = locations.reduce(\n (max: number, l: LocationEntryFieldsFragment) =>\n Math.max(max, l.duration ?? 0),\n 0,\n );\n\n const durationByAddress = new Map<string, number>();\n for (const l of locations as LocationEntryFieldsFragment[]) {\n if (!l.duration) continue;\n const key = l.address?.trim() || \"Unknown location\";\n durationByAddress.set(key, (durationByAddress.get(key) ?? 0) + l.duration);\n }\n const topLocations: ChartDatum[] = Array.from(durationByAddress.entries())\n .map(([location, seconds]) => ({ location, hours: seconds / 3600 }))\n .sort((a, b) => (b.hours as number) - (a.hours as number))\n .slice(0, 6);\n\n const openCreate = () => {\n const now = new Date();\n const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, 16);\n setForm({ ...emptyForm, startTime: localIso });\n setFormOpen(true);\n };\n\n const canSubmit =\n !!currentUser?.id &&\n form.latitude.trim() !== \"\" &&\n form.longitude.trim() !== \"\" &&\n form.startTime.trim() !== \"\";\n\n const handleSubmit = async () => {\n if (!canSubmit || !currentUser?.id) return;\n const startTime = new Date(form.startTime).toISOString();\n const endTime = form.endTime\n ? new Date(form.endTime).toISOString()\n : undefined;\n const duration = endTime\n ? Math.max(\n 0,\n Math.round(\n (new Date(endTime).getTime() - new Date(startTime).getTime()) /\n 1000,\n ),\n )\n : undefined;\n await createEntry({\n variables: {\n input: {\n userId: currentUser.id,\n latitude: Number(form.latitude),\n longitude: Number(form.longitude),\n address: form.address.trim() || undefined,\n startTime,\n endTime,\n duration,\n },\n },\n });\n setFormOpen(false);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleDelete = async () => {\n if (!confirmDelete) return;\n await deleteEntry({ variables: { id: confirmDelete.id } });\n setConfirmDelete(null);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: locations.length },\n },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n locationEntries: {\n ...fetchMoreResult.locationEntries,\n items: [\n ...prev.locationEntries.items,\n ...fetchMoreResult.locationEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const addButton = (\n <button\n onClick={openCreate}\n 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\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Location Entry\n </button>\n );\n\n if (entriesLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (entriesError && statsError) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load location data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n 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.\"\n actions={addButton}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Locations\"\n value={String(stats?.totalLocations ?? 0)}\n icon={<MapPin className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Unique Addresses\"\n value={String(stats?.uniqueAddresses ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Most Frequent\"\n value={stats?.mostFrequentLocation ?? \"N/A\"}\n />\n <StatCard\n title=\"Total Duration\"\n value={formatHours((stats?.totalDuration ?? 0) / 3600)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Tracked time\"\n />\n </div>\n\n {locations.length === 0 && !entriesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No location entries found\"\n 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.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Tracking source\"\n description=\"How your loaded entries were captured\"\n >\n <DonutChart\n data={sourceSlices}\n centerLabel=\"Entries\"\n centerValue={locations.length}\n ariaLabel=\"Geofenced vs manual location entries\"\n emptyLabel=\"No entries to summarize yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Time by location\"\n description=\"Hours logged across your loaded entries\"\n >\n <BarsChart\n data={topLocations}\n xKey=\"location\"\n orientation=\"horizontal\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n categoryWidth={120}\n valueFormat={(v) => formatHours(v)}\n ariaLabel=\"Hours logged by location\"\n emptyLabel=\"No completed entries with a duration yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"mt-6 space-y-3\">\n {locations.map((location: LocationEntryFieldsFragment) => {\n const geofenced = Boolean(location.geofenceId);\n const durationSeconds = location.duration ?? 0;\n return (\n <div\n key={location.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card p-4 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start 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 {geofenced ? (\n <Satellite className=\"h-5 w-5\" />\n ) : (\n <MapPin className=\"h-5 w-5\" />\n )}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {location.address ?? \"Unknown Location\"}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <StatusBadge\n label={geofenced ? \"Geofenced\" : \"Manual\"}\n variant={geofenced ? \"info\" : \"muted\"}\n />\n <span>{formatDate(location.startTime)}</span>\n <span className=\"text-text-muted\">\n {formatTime(location.startTime)}\n {location.endTime\n ? ` – ${formatTime(location.endTime)}`\n : \"\"}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {location.duration\n ? formatHours(location.duration / 3600)\n : \"Active\"}\n </p>\n <p className=\"text-xs text-text-secondary\">duration</p>\n </div>\n <button\n onClick={() => setConfirmDelete(location)}\n aria-label=\"Delete entry\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {location.duration && maxDuration > 0 ? (\n <div className=\"mt-3\">\n <ProgressMeter\n value={durationSeconds}\n max={maxDuration}\n tone=\"success\"\n showValue={false}\n size=\"sm\"\n />\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n <Modal\n open={formOpen}\n onClose={() => setFormOpen(false)}\n title=\"Add Location Entry\"\n description=\"Manually log a work location. Only you and people you share with can see this.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setFormOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Add Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to add location entry. Please check the values and try again.\n </div>\n )}\n {!currentUser?.id && (\n <div className=\"mb-4 rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-sm text-status-warning-text\">\n You must be signed in to add a location entry.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Address (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Client site, Chennai\"\n value={form.address}\n onChange={(e) => setForm({ ...form, address: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.longitude}\n onChange={(e) =>\n setForm({ ...form, longitude: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Time\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) =>\n setForm({ ...form, startTime: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Time (optional)\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => setForm({ ...form, endTime: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={confirmDelete !== null}\n onClose={() => setConfirmDelete(null)}\n title=\"Delete Location Entry\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDelete(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this location entry\n {confirmDelete?.address ? ` at ${confirmDelete.address}` : \"\"}?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LocationTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n MapPin,\n Clock,\n Plus,\n Trash2,\n Building2,\n Satellite,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n ProgressMeter,\n type ChartDatum,\n} from \"../../components\";\nimport { formatHours, formatDate, formatTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListLocationEntriesQuery,\n useGetLocationStatsQuery,\n useCreateLocationEntryMutation,\n useDeleteLocationEntryMutation,\n type LocationEntryFieldsFragment,\n type ListLocationEntriesQuery,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"mt-1 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\ninterface EntryFormState {\n address: string;\n latitude: string;\n longitude: string;\n startTime: string;\n endTime: string;\n}\n\nconst emptyForm: EntryFormState = {\n address: \"\",\n latitude: \"\",\n longitude: \"\",\n startTime: \"\",\n endTime: \"\",\n};\n\nexport function LocationTrackingPage() {\n const { currentUser } = useTimeCampus();\n\n const {\n data: entriesData,\n loading: entriesLoading,\n error: entriesError,\n refetch: refetchEntries,\n fetchMore,\n } = useListLocationEntriesQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetLocationStatsQuery();\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateLocationEntryMutation();\n const [deleteEntry, { loading: deleting }] = useDeleteLocationEntryMutation();\n\n const [formOpen, setFormOpen] = useState(false);\n const [form, setForm] = useState<EntryFormState>(emptyForm);\n const [confirmDelete, setConfirmDelete] =\n useState<LocationEntryFieldsFragment | null>(null);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const locations = entriesData?.locationEntries?.items ?? [];\n const hasMore = entriesData?.locationEntries?.hasMore ?? false;\n const stats = statsData?.locationStats;\n\n // ---- Derived, real-data views over the loaded entries -------------------\n const geofencedCount = locations.filter(\n (l: LocationEntryFieldsFragment) => l.geofenceId,\n ).length;\n const manualCount = locations.length - geofencedCount;\n const sourceSlices = [\n { name: \"Geofenced\", value: geofencedCount },\n { name: \"Manual\", value: manualCount },\n ];\n\n const maxDuration = locations.reduce(\n (max: number, l: LocationEntryFieldsFragment) =>\n Math.max(max, l.duration ?? 0),\n 0,\n );\n\n const durationByAddress = new Map<string, number>();\n for (const l of locations as LocationEntryFieldsFragment[]) {\n if (!l.duration) continue;\n const key = l.address?.trim() || \"Unknown location\";\n durationByAddress.set(key, (durationByAddress.get(key) ?? 0) + l.duration);\n }\n const topLocations: ChartDatum[] = Array.from(durationByAddress.entries())\n .map(([location, seconds]) => ({ location, hours: seconds / 3600 }))\n .sort((a, b) => (b.hours as number) - (a.hours as number))\n .slice(0, 6);\n\n const openCreate = () => {\n const now = new Date();\n const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, 16);\n setForm({ ...emptyForm, startTime: localIso });\n setFormOpen(true);\n };\n\n const canSubmit =\n !!currentUser?.id &&\n form.latitude.trim() !== \"\" &&\n form.longitude.trim() !== \"\" &&\n form.startTime.trim() !== \"\";\n\n const handleSubmit = async () => {\n if (!canSubmit || !currentUser?.id) return;\n const startTime = new Date(form.startTime).toISOString();\n const endTime = form.endTime\n ? new Date(form.endTime).toISOString()\n : undefined;\n const duration = endTime\n ? Math.max(\n 0,\n Math.round(\n (new Date(endTime).getTime() - new Date(startTime).getTime()) /\n 1000,\n ),\n )\n : undefined;\n await createEntry({\n variables: {\n input: {\n userId: currentUser.id,\n latitude: Number(form.latitude),\n longitude: Number(form.longitude),\n address: form.address.trim() || undefined,\n startTime,\n endTime,\n duration,\n },\n },\n });\n setFormOpen(false);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleDelete = async () => {\n if (!confirmDelete) return;\n await deleteEntry({ variables: { id: confirmDelete.id } });\n setConfirmDelete(null);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: locations.length },\n },\n updateQuery: (\n prev: ListLocationEntriesQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListLocationEntriesQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n locationEntries: {\n ...fetchMoreResult.locationEntries,\n items: [\n ...prev.locationEntries.items,\n ...fetchMoreResult.locationEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const addButton = (\n <button\n onClick={openCreate}\n 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\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Location Entry\n </button>\n );\n\n if (entriesLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (entriesError && statsError) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load location data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n 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.\"\n actions={addButton}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Locations\"\n value={String(stats?.totalLocations ?? 0)}\n icon={<MapPin className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Unique Addresses\"\n value={String(stats?.uniqueAddresses ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Most Frequent\"\n value={stats?.mostFrequentLocation ?? \"N/A\"}\n />\n <StatCard\n title=\"Total Duration\"\n value={formatHours((stats?.totalDuration ?? 0) / 3600)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Tracked time\"\n />\n </div>\n\n {locations.length === 0 && !entriesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No location entries found\"\n 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.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Tracking source\"\n description=\"How your loaded entries were captured\"\n >\n <DonutChart\n data={sourceSlices}\n centerLabel=\"Entries\"\n centerValue={locations.length}\n ariaLabel=\"Geofenced vs manual location entries\"\n emptyLabel=\"No entries to summarize yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Time by location\"\n description=\"Hours logged across your loaded entries\"\n >\n <BarsChart\n data={topLocations}\n xKey=\"location\"\n orientation=\"horizontal\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n categoryWidth={120}\n valueFormat={(v) => formatHours(v)}\n ariaLabel=\"Hours logged by location\"\n emptyLabel=\"No completed entries with a duration yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"mt-6 space-y-3\">\n {locations.map((location: LocationEntryFieldsFragment) => {\n const geofenced = Boolean(location.geofenceId);\n const durationSeconds = location.duration ?? 0;\n return (\n <div\n key={location.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card p-4 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start 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 {geofenced ? (\n <Satellite className=\"h-5 w-5\" />\n ) : (\n <MapPin className=\"h-5 w-5\" />\n )}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {location.address ?? \"Unknown Location\"}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <StatusBadge\n label={geofenced ? \"Geofenced\" : \"Manual\"}\n variant={geofenced ? \"info\" : \"muted\"}\n />\n <span>{formatDate(location.startTime)}</span>\n <span className=\"text-text-muted\">\n {formatTime(location.startTime)}\n {location.endTime\n ? ` – ${formatTime(location.endTime)}`\n : \"\"}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {location.duration\n ? formatHours(location.duration / 3600)\n : \"Active\"}\n </p>\n <p className=\"text-xs text-text-secondary\">duration</p>\n </div>\n <button\n onClick={() => setConfirmDelete(location)}\n aria-label=\"Delete entry\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {location.duration && maxDuration > 0 ? (\n <div className=\"mt-3\">\n <ProgressMeter\n value={durationSeconds}\n max={maxDuration}\n tone=\"success\"\n showValue={false}\n size=\"sm\"\n />\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n <Modal\n open={formOpen}\n onClose={() => setFormOpen(false)}\n title=\"Add Location Entry\"\n description=\"Manually log a work location. Only you and people you share with can see this.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setFormOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Add Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to add location entry. Please check the values and try again.\n </div>\n )}\n {!currentUser?.id && (\n <div className=\"mb-4 rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-sm text-status-warning-text\">\n You must be signed in to add a location entry.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Address (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Client site, Chennai\"\n value={form.address}\n onChange={(e) => setForm({ ...form, address: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.longitude}\n onChange={(e) =>\n setForm({ ...form, longitude: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Time\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) =>\n setForm({ ...form, startTime: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Time (optional)\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => setForm({ ...form, endTime: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={confirmDelete !== null}\n onClose={() => setConfirmDelete(null)}\n title=\"Delete Location Entry\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDelete(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this location entry\n {confirmDelete?.address ? ` at ${confirmDelete.address}` : \"\"}?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import { FC } from 'react';
2
2
  import { CreateActionItemInput, UpdateActionItemInput } from '../../../generated/wspace-types';
3
3
  import { MeetingActionItemFieldsFragment } from '../../../generated/wspace-operations';
4
-
5
4
  interface ActionItemFormModalProps {
6
5
  open: boolean;
7
6
  onClose: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"ActionItemFormModal.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/ActionItemFormModal.tsx"],"sourcesContent":["import { useState, type FC } from \"react\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n MeetingActionItemStatus,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport type { MeetingActionItemFieldsFragment } from \"@/generated/wspace-operations\";\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nconst STATUSES: { value: MeetingActionItemStatus; label: string }[] = [\n { value: MeetingActionItemStatus.Pending, label: \"Pending\" },\n { value: MeetingActionItemStatus.InProgress, label: \"In progress\" },\n { value: MeetingActionItemStatus.Completed, label: \"Completed\" },\n { value: MeetingActionItemStatus.Cancelled, label: \"Cancelled\" },\n];\n\n/** Convert an ISO string to a value usable by a date input. */\nfunction toDateInput(iso?: string | null): string {\n if (!iso) return \"\";\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toISOString().slice(0, 10);\n}\n\ninterface ActionItemFormModalProps {\n open: boolean;\n onClose: () => void;\n item?: MeetingActionItemFieldsFragment;\n submitting: boolean;\n error?: boolean;\n onCreate?: (input: CreateActionItemInput) => Promise<void> | void;\n onUpdate?: (input: UpdateActionItemInput) => Promise<void> | void;\n}\n\nexport const ActionItemFormModal: FC<ActionItemFormModalProps> = ({\n open,\n onClose,\n item,\n submitting,\n error,\n onCreate,\n onUpdate,\n}) => {\n const isEdit = Boolean(item);\n const [title, setTitle] = useState(item?.title ?? \"\");\n const [assigneeId, setAssigneeId] = useState(item?.assigneeId ?? \"\");\n const [dueDate, setDueDate] = useState(toDateInput(item?.dueDate));\n const [notes, setNotes] = useState(item?.notes ?? \"\");\n const [status, setStatus] = useState<MeetingActionItemStatus>(\n (item?.status as MeetingActionItemStatus | undefined) ??\n MeetingActionItemStatus.Pending,\n );\n\n const canSubmit = Boolean(title.trim()) && !submitting;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n const dueIso = dueDate ? new Date(dueDate).toISOString() : undefined;\n if (isEdit && onUpdate) {\n await onUpdate({\n title: title.trim(),\n assigneeId: assigneeId.trim() || undefined,\n dueDate: dueIso,\n notes: notes.trim() || undefined,\n status,\n });\n } else if (onCreate) {\n await onCreate({\n title: title.trim(),\n assigneeId: assigneeId.trim() || undefined,\n dueDate: dueIso,\n notes: notes.trim() || undefined,\n });\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={isEdit ? \"Edit action item\" : \"Add action item\"}\n description=\"Capture a follow-up so commitments from the meeting don't get lost.\"\n footer={\n <>\n <button\n onClick={onClose}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {submitting\n ? \"Saving...\"\n : isEdit\n ? \"Save Changes\"\n : \"Add action item\"}\n </button>\n </>\n }\n >\n {error && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save action item. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"What needs to happen?\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Assignee (User ID)</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={assigneeId}\n onChange={(e) => setAssigneeId(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Due date</label>\n <input\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n {isEdit && (\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={status}\n onChange={(e) =>\n setStatus(e.target.value as MeetingActionItemStatus)\n }\n className={inputClass}\n >\n {STATUSES.map((s) => (\n <option key={s.value} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className={labelClass}>Notes</label>\n <textarea\n placeholder=\"Additional context...\"\n rows={3}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActionItemFormModal.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/ActionItemFormModal.tsx"],"sourcesContent":["import { useState, type FC } from \"react\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n MeetingActionItemStatus,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport type { MeetingActionItemFieldsFragment } from \"@/generated/wspace-operations\";\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nconst STATUSES: { value: MeetingActionItemStatus; label: string }[] = [\n { value: MeetingActionItemStatus.Pending, label: \"Pending\" },\n { value: MeetingActionItemStatus.InProgress, label: \"In progress\" },\n { value: MeetingActionItemStatus.Completed, label: \"Completed\" },\n { value: MeetingActionItemStatus.Cancelled, label: \"Cancelled\" },\n];\n\n/** Convert an ISO string to a value usable by a date input. */\nfunction toDateInput(iso?: string | null): string {\n if (!iso) return \"\";\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return d.toISOString().slice(0, 10);\n}\n\ninterface ActionItemFormModalProps {\n open: boolean;\n onClose: () => void;\n item?: MeetingActionItemFieldsFragment;\n submitting: boolean;\n error?: boolean;\n onCreate?: (input: CreateActionItemInput) => Promise<void> | void;\n onUpdate?: (input: UpdateActionItemInput) => Promise<void> | void;\n}\n\nexport const ActionItemFormModal: FC<ActionItemFormModalProps> = ({\n open,\n onClose,\n item,\n submitting,\n error,\n onCreate,\n onUpdate,\n}) => {\n const isEdit = Boolean(item);\n const [title, setTitle] = useState(item?.title ?? \"\");\n const [assigneeId, setAssigneeId] = useState(item?.assigneeId ?? \"\");\n const [dueDate, setDueDate] = useState(toDateInput(item?.dueDate));\n const [notes, setNotes] = useState(item?.notes ?? \"\");\n const [status, setStatus] = useState<MeetingActionItemStatus>(\n (item?.status as MeetingActionItemStatus | undefined) ??\n MeetingActionItemStatus.Pending,\n );\n\n const canSubmit = Boolean(title.trim()) && !submitting;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n const dueIso = dueDate ? new Date(dueDate).toISOString() : undefined;\n if (isEdit && onUpdate) {\n await onUpdate({\n title: title.trim(),\n assigneeId: assigneeId.trim() || undefined,\n dueDate: dueIso,\n notes: notes.trim() || undefined,\n status,\n });\n } else if (onCreate) {\n await onCreate({\n title: title.trim(),\n assigneeId: assigneeId.trim() || undefined,\n dueDate: dueIso,\n notes: notes.trim() || undefined,\n });\n }\n };\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={isEdit ? \"Edit action item\" : \"Add action item\"}\n description=\"Capture a follow-up so commitments from the meeting don't get lost.\"\n footer={\n <>\n <button\n onClick={onClose}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {submitting\n ? \"Saving...\"\n : isEdit\n ? \"Save Changes\"\n : \"Add action item\"}\n </button>\n </>\n }\n >\n {error && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save action item. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"What needs to happen?\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Assignee (User ID)</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={assigneeId}\n onChange={(e) => setAssigneeId(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Due date</label>\n <input\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n {isEdit && (\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={status}\n onChange={(e) =>\n setStatus(e.target.value as MeetingActionItemStatus)\n }\n className={inputClass}\n >\n {STATUSES.map((s) => (\n <option key={s.value} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n </div>\n )}\n <div>\n <label className={labelClass}>Notes</label>\n <textarea\n placeholder=\"Additional context...\"\n rows={3}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
@@ -74,7 +74,7 @@ function O() {
74
74
  return Number.isFinite(n) && n > e ? n : e;
75
75
  }, -Infinity), V = w(Number.isFinite(B) ? new Date(B) : /* @__PURE__ */ new Date()), H = Array.from({ length: x }, (e, t) => {
76
76
  let n = new Date(V);
77
- return n.setDate(n.getDate() - (x - 1 - t) * 7), {
77
+ return n.setDate(n.getDate() - (5 - t) * 7), {
78
78
  start: n.getTime(),
79
79
  label: T(n),
80
80
  minutes: 0
@@ -1 +1 @@
1
- {"version":3,"file":"MeetingAnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingAnalyticsPage.tsx"],"sourcesContent":["import { BarChart3, CalendarDays, Clock, DollarSign } from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n LoadingSkeleton,\n ChartCard,\n TrendChart,\n BarsChart,\n DonutChart,\n RadialGauge,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingStatsQuery,\n useListMeetingsQuery,\n} from \"@/generated/wspace-operations\";\nimport { MeetingType } from \"@/generated/wspace-types\";\nimport { formatCurrency, formatNumber } from \"../../utils/helpers\";\n\nconst MEETING_TYPE_LABELS: Record<MeetingType, string> = {\n [MeetingType.Standup]: \"Standups\",\n [MeetingType.OneOnOne]: \"1-on-1s\",\n [MeetingType.Team]: \"Team\",\n [MeetingType.Review]: \"Reviews\",\n [MeetingType.Planning]: \"Planning\",\n [MeetingType.Retrospective]: \"Retros\",\n [MeetingType.External]: \"External\",\n};\n\nconst WEEKS_IN_TREND = 6;\nconst MEETINGS_SAMPLE = 250;\n\nfunction meetingTypeLabel(type: MeetingType): string {\n return MEETING_TYPE_LABELS[type] ?? type;\n}\n\n/** Monday 00:00 of the week containing `date`. */\nfunction startOfWeek(date: Date): Date {\n const d = new Date(date);\n d.setHours(0, 0, 0, 0);\n const dayFromMonday = (d.getDay() + 6) % 7;\n d.setDate(d.getDate() - dayFromMonday);\n return d;\n}\n\nfunction weekLabel(date: Date): string {\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n}\n\nconst roundHours = (minutes: number): number =>\n Math.round((minutes / 60) * 10) / 10;\n\nconst formatHoursValue = (value: number): string =>\n `${Number.isInteger(value) ? value : value.toFixed(1)}h`;\n\nexport function MeetingAnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useGetMeetingStatsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const { data: meetingsData, loading: meetingsLoading } = useListMeetingsQuery(\n {\n variables: {\n workspaceId,\n pagination: { limit: MEETINGS_SAMPLE, offset: 0 },\n },\n skip: !workspaceId,\n },\n );\n\n const stats = data?.meetingStats;\n const meetings = meetingsData?.meetings ?? [];\n\n if (loading || (meetingsLoading && !meetingsData)) {\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load meeting analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = Math.round((stats?.totalDuration ?? 0) / 60);\n const avgDuration = Math.round(stats?.averageDuration ?? 0);\n const avgRating = stats?.averageRating ?? 0;\n const actionItemsTotal = stats?.actionItemsTotal ?? 0;\n const actionItemsCompleted = stats?.actionItemsCompleted ?? 0;\n const actionItemsOutstanding = Math.max(\n 0,\n actionItemsTotal - actionItemsCompleted,\n );\n\n // --- Meeting load trend: real per-meeting startTime bucketed into weeks ---\n const latestMs = meetings.reduce(\n (max: number, m: (typeof meetings)[number]) => {\n const t = new Date(m.startTime).getTime();\n return Number.isFinite(t) && t > max ? t : max;\n },\n Number.NEGATIVE_INFINITY,\n );\n const anchorWeek = startOfWeek(\n Number.isFinite(latestMs) ? new Date(latestMs) : new Date(),\n );\n\n const weekBuckets = Array.from({ length: WEEKS_IN_TREND }, (_, i) => {\n const start = new Date(anchorWeek);\n start.setDate(start.getDate() - (WEEKS_IN_TREND - 1 - i) * 7);\n return { start: start.getTime(), label: weekLabel(start), minutes: 0 };\n });\n const bucketByStart = new Map(weekBuckets.map((b) => [b.start, b]));\n for (const m of meetings) {\n const t = new Date(m.startTime).getTime();\n if (!Number.isFinite(t)) continue;\n const bucket = bucketByStart.get(startOfWeek(new Date(t)).getTime());\n if (bucket) bucket.minutes += m.duration ?? 0;\n }\n const trendData = weekBuckets.map((b) => ({\n week: b.label,\n hours: roundHours(b.minutes),\n }));\n const hasTrend = meetings.length > 0;\n\n // --- Meeting type breakdown: real hours grouped by type ---\n const typeMinutes = new Map<MeetingType, number>();\n for (const m of meetings) {\n typeMinutes.set(\n m.meetingType,\n (typeMinutes.get(m.meetingType) ?? 0) + (m.duration ?? 0),\n );\n }\n const byTypeData = Array.from(typeMinutes.entries())\n .map(([type, minutes]) => ({\n type: meetingTypeLabel(type),\n hours: roundHours(minutes),\n }))\n .filter((d) => d.hours > 0)\n .sort((a, b) => b.hours - a.hours);\n\n // --- Action-item follow-through (aggregate) ---\n const actionSlices = [\n { name: \"Completed\", value: actionItemsCompleted },\n { name: \"Outstanding\", value: actionItemsOutstanding },\n ];\n\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n purpose=\"Put a number on your meeting habit — total hours, dollar cost and follow-through on action items — so you can cut the meetings that don't pay for themselves and reclaim time for focused work.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Meetings\"\n value={String(stats?.totalMeetings ?? 0)}\n icon={<CalendarDays className=\"h-5 w-5\" />}\n description={`${formatNumber(actionItemsTotal)} action items tracked`}\n />\n <StatCard\n title=\"Meeting Hours\"\n value={`${totalHours}h`}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${formatNumber(stats?.totalDuration ?? 0)} total minutes`}\n />\n <StatCard\n title=\"True Cost of Meetings\"\n value={formatCurrency(stats?.totalCost ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n description=\"Estimated productivity cost\"\n />\n <StatCard\n title=\"Avg. Duration\"\n value={`${avgDuration} min`}\n icon={<BarChart3 className=\"h-5 w-5\" />}\n description={`${avgRating.toFixed(1)}★ average rating`}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Meeting Load Trend\"\n description=\"Meeting hours per week over the last 6 weeks\"\n >\n <TrendChart\n data={hasTrend ? trendData : []}\n xKey=\"week\"\n series={[{ key: \"hours\", label: \"Meeting hours\" }]}\n type=\"area\"\n height={240}\n valueFormat={formatHoursValue}\n emptyLabel=\"No meeting activity to chart yet.\"\n ariaLabel=\"Meeting hours per week\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Meeting Type Breakdown\"\n description=\"Hours spent by meeting type\"\n >\n <BarsChart\n data={byTypeData}\n xKey=\"type\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n orientation=\"horizontal\"\n height={240}\n valueFormat={formatHoursValue}\n categoryWidth={88}\n emptyLabel=\"No meetings recorded yet.\"\n ariaLabel=\"Meeting hours by type\"\n />\n </ChartCard>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Action Item Follow-through\"\n description=\"Completed vs. outstanding across all meetings\"\n >\n <DonutChart\n data={actionSlices}\n centerValue={actionItemsTotal}\n centerLabel=\"action items\"\n height={240}\n emptyLabel=\"No action items captured yet.\"\n ariaLabel=\"Action item completion\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Average Meeting Rating\"\n description=\"How worthwhile meetings have felt, out of 5\"\n >\n <div className=\"flex items-center justify-center\">\n <RadialGauge\n value={avgRating}\n min={0}\n max={5}\n label=\"out of 5\"\n sublabel={`${formatNumber(stats?.totalMeetings ?? 0)} meetings`}\n height={240}\n format={(v) => v.toFixed(1)}\n ariaLabel=\"Average meeting rating out of 5\"\n />\n </div>\n </ChartCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MeetingAnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingAnalyticsPage.tsx"],"sourcesContent":["import { BarChart3, CalendarDays, Clock, DollarSign } from \"lucide-react\";\nimport {\n PageLayout,\n StatCard,\n LoadingSkeleton,\n ChartCard,\n TrendChart,\n BarsChart,\n DonutChart,\n RadialGauge,\n} from \"../../components\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingStatsQuery,\n useListMeetingsQuery,\n} from \"@/generated/wspace-operations\";\nimport { MeetingType } from \"@/generated/wspace-types\";\nimport { formatCurrency, formatNumber } from \"../../utils/helpers\";\n\nconst MEETING_TYPE_LABELS: Record<MeetingType, string> = {\n [MeetingType.Standup]: \"Standups\",\n [MeetingType.OneOnOne]: \"1-on-1s\",\n [MeetingType.Team]: \"Team\",\n [MeetingType.Review]: \"Reviews\",\n [MeetingType.Planning]: \"Planning\",\n [MeetingType.Retrospective]: \"Retros\",\n [MeetingType.External]: \"External\",\n};\n\nconst WEEKS_IN_TREND = 6;\nconst MEETINGS_SAMPLE = 250;\n\nfunction meetingTypeLabel(type: MeetingType): string {\n return MEETING_TYPE_LABELS[type] ?? type;\n}\n\n/** Monday 00:00 of the week containing `date`. */\nfunction startOfWeek(date: Date): Date {\n const d = new Date(date);\n d.setHours(0, 0, 0, 0);\n const dayFromMonday = (d.getDay() + 6) % 7;\n d.setDate(d.getDate() - dayFromMonday);\n return d;\n}\n\nfunction weekLabel(date: Date): string {\n return date.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n}\n\nconst roundHours = (minutes: number): number =>\n Math.round((minutes / 60) * 10) / 10;\n\nconst formatHoursValue = (value: number): string =>\n `${Number.isInteger(value) ? value : value.toFixed(1)}h`;\n\nexport function MeetingAnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useGetMeetingStatsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const { data: meetingsData, loading: meetingsLoading } = useListMeetingsQuery(\n {\n variables: {\n workspaceId,\n pagination: { limit: MEETINGS_SAMPLE, offset: 0 },\n },\n skip: !workspaceId,\n },\n );\n\n const stats = data?.meetingStats;\n const meetings = meetingsData?.meetings ?? [];\n\n if (loading || (meetingsLoading && !meetingsData)) {\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load meeting analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = Math.round((stats?.totalDuration ?? 0) / 60);\n const avgDuration = Math.round(stats?.averageDuration ?? 0);\n const avgRating = stats?.averageRating ?? 0;\n const actionItemsTotal = stats?.actionItemsTotal ?? 0;\n const actionItemsCompleted = stats?.actionItemsCompleted ?? 0;\n const actionItemsOutstanding = Math.max(\n 0,\n actionItemsTotal - actionItemsCompleted,\n );\n\n // --- Meeting load trend: real per-meeting startTime bucketed into weeks ---\n const latestMs = meetings.reduce(\n (max: number, m: (typeof meetings)[number]) => {\n const t = new Date(m.startTime).getTime();\n return Number.isFinite(t) && t > max ? t : max;\n },\n Number.NEGATIVE_INFINITY,\n );\n const anchorWeek = startOfWeek(\n Number.isFinite(latestMs) ? new Date(latestMs) : new Date(),\n );\n\n const weekBuckets = Array.from({ length: WEEKS_IN_TREND }, (_, i) => {\n const start = new Date(anchorWeek);\n start.setDate(start.getDate() - (WEEKS_IN_TREND - 1 - i) * 7);\n return { start: start.getTime(), label: weekLabel(start), minutes: 0 };\n });\n const bucketByStart = new Map(weekBuckets.map((b) => [b.start, b]));\n for (const m of meetings) {\n const t = new Date(m.startTime).getTime();\n if (!Number.isFinite(t)) continue;\n const bucket = bucketByStart.get(startOfWeek(new Date(t)).getTime());\n if (bucket) bucket.minutes += m.duration ?? 0;\n }\n const trendData = weekBuckets.map((b) => ({\n week: b.label,\n hours: roundHours(b.minutes),\n }));\n const hasTrend = meetings.length > 0;\n\n // --- Meeting type breakdown: real hours grouped by type ---\n const typeMinutes = new Map<MeetingType, number>();\n for (const m of meetings) {\n typeMinutes.set(\n m.meetingType,\n (typeMinutes.get(m.meetingType) ?? 0) + (m.duration ?? 0),\n );\n }\n const byTypeData = Array.from(typeMinutes.entries())\n .map(([type, minutes]) => ({\n type: meetingTypeLabel(type),\n hours: roundHours(minutes),\n }))\n .filter((d) => d.hours > 0)\n .sort((a, b) => b.hours - a.hours);\n\n // --- Action-item follow-through (aggregate) ---\n const actionSlices = [\n { name: \"Completed\", value: actionItemsCompleted },\n { name: \"Outstanding\", value: actionItemsOutstanding },\n ];\n\n return (\n <PageLayout\n title=\"Meeting Analytics\"\n description=\"Insights into meeting time and cost\"\n purpose=\"Put a number on your meeting habit — total hours, dollar cost and follow-through on action items — so you can cut the meetings that don't pay for themselves and reclaim time for focused work.\"\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Meetings\"\n value={String(stats?.totalMeetings ?? 0)}\n icon={<CalendarDays className=\"h-5 w-5\" />}\n description={`${formatNumber(actionItemsTotal)} action items tracked`}\n />\n <StatCard\n title=\"Meeting Hours\"\n value={`${totalHours}h`}\n icon={<Clock className=\"h-5 w-5\" />}\n description={`${formatNumber(stats?.totalDuration ?? 0)} total minutes`}\n />\n <StatCard\n title=\"True Cost of Meetings\"\n value={formatCurrency(stats?.totalCost ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n description=\"Estimated productivity cost\"\n />\n <StatCard\n title=\"Avg. Duration\"\n value={`${avgDuration} min`}\n icon={<BarChart3 className=\"h-5 w-5\" />}\n description={`${avgRating.toFixed(1)}★ average rating`}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Meeting Load Trend\"\n description=\"Meeting hours per week over the last 6 weeks\"\n >\n <TrendChart\n data={hasTrend ? trendData : []}\n xKey=\"week\"\n series={[{ key: \"hours\", label: \"Meeting hours\" }]}\n type=\"area\"\n height={240}\n valueFormat={formatHoursValue}\n emptyLabel=\"No meeting activity to chart yet.\"\n ariaLabel=\"Meeting hours per week\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Meeting Type Breakdown\"\n description=\"Hours spent by meeting type\"\n >\n <BarsChart\n data={byTypeData}\n xKey=\"type\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n orientation=\"horizontal\"\n height={240}\n valueFormat={formatHoursValue}\n categoryWidth={88}\n emptyLabel=\"No meetings recorded yet.\"\n ariaLabel=\"Meeting hours by type\"\n />\n </ChartCard>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Action Item Follow-through\"\n description=\"Completed vs. outstanding across all meetings\"\n >\n <DonutChart\n data={actionSlices}\n centerValue={actionItemsTotal}\n centerLabel=\"action items\"\n height={240}\n emptyLabel=\"No action items captured yet.\"\n ariaLabel=\"Action item completion\"\n />\n </ChartCard>\n\n <ChartCard\n title=\"Average Meeting Rating\"\n description=\"How worthwhile meetings have felt, out of 5\"\n >\n <div className=\"flex items-center justify-center\">\n <RadialGauge\n value={avgRating}\n min={0}\n max={5}\n label=\"out of 5\"\n sublabel={`${formatNumber(stats?.totalMeetings ?? 0)} meetings`}\n height={240}\n format={(v) => v.toFixed(1)}\n ariaLabel=\"Average meeting rating out of 5\"\n />\n </div>\n </ChartCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}