@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":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Plus,\n CheckSquare,\n Pencil,\n Trash2,\n ArrowUp,\n ArrowDown,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n useCreateTaskMutation,\n useUpdateTaskMutation,\n useUpdateTaskStatusMutation,\n useDeleteTaskMutation,\n useReorderTasksMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst TASK_STATUSES = [\"todo\", \"in-progress\", \"review\", \"done\"] as const;\nconst TASK_PRIORITIES = [\"low\", \"medium\", \"high\", \"urgent\"] as const;\n\ninterface TaskFormState {\n title: string;\n description: string;\n status: string;\n priority: string;\n assigneeId: string;\n estimatedHours: string;\n dueDate: string;\n}\n\nconst emptyForm: TaskFormState = {\n title: \"\",\n description: \"\",\n status: \"todo\",\n priority: \"medium\",\n assigneeId: \"\",\n estimatedHours: \"\",\n dueDate: \"\",\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\";\n\nexport function TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListTasksQuery({\n variables: { projectId: projectId!, pagination: { limit, offset: 0 } },\n skip: !projectId,\n notifyOnNetworkStatusChange: true,\n });\n\n const tasks = data?.tasks?.items ?? [];\n const hasMore = data?.tasks?.hasMore ?? false;\n\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<TaskFieldsFragment | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<TaskFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<TaskFormState>(emptyForm);\n\n const [createTask, { loading: creating, error: createError }] =\n useCreateTaskMutation();\n const [updateTask, { loading: updating, error: updateError }] =\n useUpdateTaskMutation();\n const [updateTaskStatus] = useUpdateTaskStatusMutation();\n const [deleteTask, { loading: deleting, error: deleteError }] =\n useDeleteTaskMutation();\n const [reorderTasks, { loading: reordering }] = useReorderTasksMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (task: TaskFieldsFragment) => {\n setForm({\n title: task.title,\n description: task.description ?? \"\",\n status: task.status,\n priority: task.priority,\n assigneeId: task.assigneeId ?? \"\",\n estimatedHours:\n task.estimatedHours != null ? String(task.estimatedHours) : \"\",\n dueDate: task.dueDate ? task.dueDate.slice(0, 10) : \"\",\n });\n setEditTarget(task);\n };\n\n const handleCreate = async () => {\n if (!projectId || !form.title.trim()) return;\n await createTask({\n variables: {\n input: {\n projectId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.title.trim()) return;\n await updateTask({\n variables: {\n id: editTarget.id,\n input: {\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleStatusChange = async (\n task: TaskFieldsFragment,\n status: string,\n ) => {\n await updateTaskStatus({ variables: { id: task.id, status } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteTask({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const handleMove = async (index: number, direction: -1 | 1) => {\n if (!projectId) return;\n const target = index + direction;\n if (target < 0 || target >= tasks.length) return;\n const ordered = tasks.map((task: TaskFieldsFragment) => task.id);\n const [moved] = ordered.splice(index, 1);\n ordered.splice(target, 0, moved);\n await reorderTasks({ variables: { projectId, taskIds: ordered } });\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"Task title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n placeholder=\"Task description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: 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}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Priority</label>\n <select\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n className={inputClass}\n >\n {TASK_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p}\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}>Assignee ID</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={form.assigneeId}\n onChange={(e) => setForm({ ...form, assigneeId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Estimated Hours</label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Due Date</label>\n <input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\n actions={\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 Task\n </button>\n }\n >\n {loading && tasks.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\n action={\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 Task\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment, index: number) => (\n <div\n key={task.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 items-center gap-3\">\n <CheckSquare\n className={`h-5 w-5 shrink-0 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} &middot;{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <select\n value={task.status}\n onChange={(e) =>\n void handleStatusChange(task, e.target.value)\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary\"\n aria-label=\"Task status\"\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => void handleMove(index, -1)}\n disabled={index === 0 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move up\"\n >\n <ArrowUp className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => void handleMove(index, 1)}\n disabled={index === tasks.length - 1 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move down\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => openEdit(task)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit task\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(task)}\n className=\"rounded-md p-1 text-status-error-text hover:bg-status-error-bg\"\n aria-label=\"Delete task\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\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 {loading ? \"Loading…\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"Add Task\"\n description=\"Break this project into a trackable piece of work.\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(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={handleCreate}\n disabled={creating || !form.title.trim()}\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 ? \"Creating…\" : \"Create Task\"}\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 create task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title=\"Edit Task\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(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={handleUpdate}\n disabled={updating || !form.title.trim()}\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 {updating ? \"Saving…\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\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 update task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete Task\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(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/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\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 delete task. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.title}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n Plus,\n CheckSquare,\n Pencil,\n Trash2,\n ArrowUp,\n ArrowDown,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n useCreateTaskMutation,\n useUpdateTaskMutation,\n useUpdateTaskStatusMutation,\n useDeleteTaskMutation,\n useReorderTasksMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst TASK_STATUSES = [\"todo\", \"in-progress\", \"review\", \"done\"] as const;\nconst TASK_PRIORITIES = [\"low\", \"medium\", \"high\", \"urgent\"] as const;\n\ninterface TaskFormState {\n title: string;\n description: string;\n status: string;\n priority: string;\n assigneeId: string;\n estimatedHours: string;\n dueDate: string;\n}\n\nconst emptyForm: TaskFormState = {\n title: \"\",\n description: \"\",\n status: \"todo\",\n priority: \"medium\",\n assigneeId: \"\",\n estimatedHours: \"\",\n dueDate: \"\",\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\";\n\nexport function TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListTasksQuery({\n variables: { projectId: projectId!, pagination: { limit, offset: 0 } },\n skip: !projectId,\n notifyOnNetworkStatusChange: true,\n });\n\n const tasks = data?.tasks?.items ?? [];\n const hasMore = data?.tasks?.hasMore ?? false;\n\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<TaskFieldsFragment | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<TaskFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<TaskFormState>(emptyForm);\n\n const [createTask, { loading: creating, error: createError }] =\n useCreateTaskMutation();\n const [updateTask, { loading: updating, error: updateError }] =\n useUpdateTaskMutation();\n const [updateTaskStatus] = useUpdateTaskStatusMutation();\n const [deleteTask, { loading: deleting, error: deleteError }] =\n useDeleteTaskMutation();\n const [reorderTasks, { loading: reordering }] = useReorderTasksMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (task: TaskFieldsFragment) => {\n setForm({\n title: task.title,\n description: task.description ?? \"\",\n status: task.status,\n priority: task.priority,\n assigneeId: task.assigneeId ?? \"\",\n estimatedHours:\n task.estimatedHours != null ? String(task.estimatedHours) : \"\",\n dueDate: task.dueDate ? task.dueDate.slice(0, 10) : \"\",\n });\n setEditTarget(task);\n };\n\n const handleCreate = async () => {\n if (!projectId || !form.title.trim()) return;\n await createTask({\n variables: {\n input: {\n projectId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.title.trim()) return;\n await updateTask({\n variables: {\n id: editTarget.id,\n input: {\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleStatusChange = async (\n task: TaskFieldsFragment,\n status: string,\n ) => {\n await updateTaskStatus({ variables: { id: task.id, status } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteTask({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const handleMove = async (index: number, direction: -1 | 1) => {\n if (!projectId) return;\n const target = index + direction;\n if (target < 0 || target >= tasks.length) return;\n const ordered = tasks.map((task: TaskFieldsFragment) => task.id);\n const [moved] = ordered.splice(index, 1);\n ordered.splice(target, 0, moved);\n await reorderTasks({ variables: { projectId, taskIds: ordered } });\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"Task title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n placeholder=\"Task description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: 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}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Priority</label>\n <select\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n className={inputClass}\n >\n {TASK_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p}\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}>Assignee ID</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={form.assigneeId}\n onChange={(e) => setForm({ ...form, assigneeId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Estimated Hours</label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Due Date</label>\n <input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\n actions={\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 Task\n </button>\n }\n >\n {loading && tasks.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\n action={\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 Task\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment, index: number) => (\n <div\n key={task.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 items-center gap-3\">\n <CheckSquare\n className={`h-5 w-5 shrink-0 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} &middot;{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <select\n value={task.status}\n onChange={(e) =>\n void handleStatusChange(task, e.target.value)\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary\"\n aria-label=\"Task status\"\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => void handleMove(index, -1)}\n disabled={index === 0 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move up\"\n >\n <ArrowUp className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => void handleMove(index, 1)}\n disabled={index === tasks.length - 1 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move down\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => openEdit(task)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit task\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(task)}\n className=\"rounded-md p-1 text-status-error-text hover:bg-status-error-bg\"\n aria-label=\"Delete task\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\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 {loading ? \"Loading…\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"Add Task\"\n description=\"Break this project into a trackable piece of work.\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(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={handleCreate}\n disabled={creating || !form.title.trim()}\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 ? \"Creating…\" : \"Create Task\"}\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 create task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title=\"Edit Task\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(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={handleUpdate}\n disabled={updating || !form.title.trim()}\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 {updating ? \"Saving…\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\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 update task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete Task\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(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/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\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 delete task. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.title}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ResourcePlanningPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"names":[],"mappings":"AAgGA,wBAAgB,oBAAoB,gCAoiCnC"}
1
+ {"version":3,"file":"ResourcePlanningPage.d.ts","sourceRoot":"","sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"names":[],"mappings":"AAiGA,wBAAgB,oBAAoB,gCAuiCnC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ResourcePlanningPage.js","names":[],"sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CalendarRange,\n Users,\n TrendingUp,\n Plus,\n Pencil,\n Trash2,\n UserPlus,\n AlertTriangle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n RadialGauge,\n ProgressMeter,\n StatusBadge,\n type MeterTone,\n} from \"../../components\";\nimport { getInitials, formatHours } from \"../../utils/helpers\";\nimport {\n useListResourcePlansQuery,\n useGetCapacityOverviewQuery,\n useCreateResourcePlanMutation,\n useUpdateResourcePlanMutation,\n useDeleteResourcePlanMutation,\n useCreateAllocationMutation,\n useUpdateAllocationMutation,\n useDeleteAllocationMutation,\n useBulkCreateAllocationsMutation,\n type ResourcePlanFieldsFragment,\n type ResourceAllocationFieldsFragment,\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\nconst toDateInput = (iso: string) => iso.slice(0, 10);\nconst toIso = (dateStr: string) => new Date(dateStr).toISOString();\n\ninterface PlanFormState {\n name: string;\n description: string;\n startDate: string;\n endDate: string;\n status: string;\n}\n\ninterface AllocationRow {\n userId: string;\n allocatedHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\ninterface AllocationEditState {\n userId: string;\n allocatedHours: string;\n actualHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\nconst emptyPlanForm = (): PlanFormState => {\n const now = new Date();\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7);\n return {\n name: \"\",\n description: \"\",\n startDate: toDateInput(start.toISOString()),\n endDate: toDateInput(end.toISOString()),\n status: \"active\",\n };\n};\n\nconst emptyAllocationRow = (\n plan?: ResourcePlanFieldsFragment,\n): AllocationRow => ({\n userId: \"\",\n allocatedHours: \"\",\n startDate: plan ? toDateInput(plan.startDate) : \"\",\n endDate: plan ? toDateInput(plan.endDate) : \"\",\n notes: \"\",\n});\n\nexport function ResourcePlanningPage() {\n const now = new Date();\n const startDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n ).toISOString();\n const endDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + 7,\n ).toISOString();\n\n const {\n data: plansData,\n loading: plansLoading,\n error: plansError,\n refetch: refetchPlans,\n fetchMore,\n } = useListResourcePlansQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const { data: capacityData, refetch: refetchCapacity } =\n useGetCapacityOverviewQuery({ variables: { startDate, endDate } });\n\n const [createPlan, { loading: creatingPlan, error: createPlanError }] =\n useCreateResourcePlanMutation();\n const [updatePlan, { loading: updatingPlan, error: updatePlanError }] =\n useUpdateResourcePlanMutation();\n const [deletePlan, { loading: deletingPlan }] =\n useDeleteResourcePlanMutation();\n const [createAllocation, { loading: creatingAlloc }] =\n useCreateAllocationMutation();\n const [bulkCreateAllocations, { loading: bulkCreating }] =\n useBulkCreateAllocationsMutation();\n const [\n updateAllocation,\n { loading: updatingAlloc, error: updateAllocError },\n ] = useUpdateAllocationMutation();\n const [deleteAllocation, { loading: deletingAlloc }] =\n useDeleteAllocationMutation();\n\n const [planModalOpen, setPlanModalOpen] = useState(false);\n const [editingPlan, setEditingPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [planForm, setPlanForm] = useState<PlanFormState>(emptyPlanForm());\n const [confirmDeletePlan, setConfirmDeletePlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n\n const [allocModalPlan, setAllocModalPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [allocRows, setAllocRows] = useState<AllocationRow[]>([]);\n const [allocError, setAllocError] = useState(false);\n\n const [editingAlloc, setEditingAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n const [allocEditForm, setAllocEditForm] =\n useState<AllocationEditState | null>(null);\n const [confirmDeleteAlloc, setConfirmDeleteAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const plans = plansData?.resourcePlans?.items ?? [];\n const hasMore = plansData?.resourcePlans?.hasMore ?? false;\n const capacity = capacityData?.capacityOverview;\n\n const refetchAll = async () => {\n await Promise.all([refetchPlans(), refetchCapacity()]);\n };\n\n // -- Plan handlers --------------------------------------------------------\n const openCreatePlan = () => {\n setEditingPlan(null);\n setPlanForm(emptyPlanForm());\n setPlanModalOpen(true);\n };\n\n const openEditPlan = (plan: ResourcePlanFieldsFragment) => {\n setEditingPlan(plan);\n setPlanForm({\n name: plan.name,\n description: plan.description ?? \"\",\n startDate: toDateInput(plan.startDate),\n endDate: toDateInput(plan.endDate),\n status: plan.status,\n });\n setPlanModalOpen(true);\n };\n\n const handleSubmitPlan = async () => {\n if (!planForm.name.trim()) return;\n if (editingPlan) {\n await updatePlan({\n variables: {\n id: editingPlan.id,\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n status: planForm.status,\n },\n },\n });\n } else {\n await createPlan({\n variables: {\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n },\n },\n });\n }\n setPlanModalOpen(false);\n await refetchAll();\n };\n\n const handleDeletePlan = async () => {\n if (!confirmDeletePlan) return;\n await deletePlan({ variables: { id: confirmDeletePlan.id } });\n setConfirmDeletePlan(null);\n await refetchAll();\n };\n\n // -- Allocation handlers --------------------------------------------------\n const openAddAllocation = (plan: ResourcePlanFieldsFragment) => {\n setAllocModalPlan(plan);\n setAllocRows([emptyAllocationRow(plan)]);\n setAllocError(false);\n };\n\n const updateAllocRow = (index: number, patch: Partial<AllocationRow>) => {\n setAllocRows((rows) =>\n rows.map((r, i) => (i === index ? { ...r, ...patch } : r)),\n );\n };\n\n const addAllocRow = () => {\n setAllocRows((rows) => [\n ...rows,\n emptyAllocationRow(allocModalPlan ?? undefined),\n ]);\n };\n\n const removeAllocRow = (index: number) => {\n setAllocRows((rows) => rows.filter((_, i) => i !== index));\n };\n\n const allocRowsValid = allocRows.every(\n (r) =>\n r.userId.trim() !== \"\" &&\n r.allocatedHours.trim() !== \"\" &&\n r.startDate !== \"\" &&\n r.endDate !== \"\",\n );\n\n const handleSubmitAllocations = async () => {\n if (!allocModalPlan || !allocRowsValid || allocRows.length === 0) return;\n setAllocError(false);\n try {\n if (allocRows.length === 1) {\n const r = allocRows[0];\n await createAllocation({\n variables: {\n input: {\n resourcePlanId: allocModalPlan.id,\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n },\n },\n });\n } else {\n await bulkCreateAllocations({\n variables: {\n planId: allocModalPlan.id,\n allocations: allocRows.map((r) => ({\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n })),\n },\n });\n }\n setAllocModalPlan(null);\n await refetchAll();\n } catch {\n setAllocError(true);\n }\n };\n\n const openEditAllocation = (alloc: ResourceAllocationFieldsFragment) => {\n setEditingAlloc(alloc);\n setAllocEditForm({\n userId: alloc.userId,\n allocatedHours: String(alloc.allocatedHours),\n actualHours: String(alloc.actualHours),\n startDate: toDateInput(alloc.startDate),\n endDate: toDateInput(alloc.endDate),\n notes: alloc.notes ?? \"\",\n });\n };\n\n const handleSubmitAllocEdit = async () => {\n if (!editingAlloc || !allocEditForm) return;\n await updateAllocation({\n variables: {\n id: editingAlloc.id,\n input: {\n userId: allocEditForm.userId.trim(),\n allocatedHours: Number(allocEditForm.allocatedHours),\n actualHours: Number(allocEditForm.actualHours),\n startDate: toIso(allocEditForm.startDate),\n endDate: toIso(allocEditForm.endDate),\n notes: allocEditForm.notes.trim() || undefined,\n },\n },\n });\n setEditingAlloc(null);\n setAllocEditForm(null);\n await refetchAll();\n };\n\n const handleDeleteAllocation = async () => {\n if (!confirmDeleteAlloc) return;\n await deleteAllocation({ variables: { id: confirmDeleteAlloc.id } });\n setConfirmDeleteAlloc(null);\n await refetchAll();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: plans.length } },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n resourcePlans: {\n ...fetchMoreResult.resourcePlans,\n items: [\n ...prev.resourcePlans.items,\n ...fetchMoreResult.resourcePlans.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const newPlanButton = (\n <button\n onClick={openCreatePlan}\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 New Resource Plan\n </button>\n );\n\n if (plansLoading) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (plansError) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\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 resource planning data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const utilizationRate = capacity?.utilizationRate ?? 0;\n const overallocatedUsers = capacity?.overallocatedUsers ?? [];\n const overallocatedCount = overallocatedUsers.length;\n const availableHours = capacity?.availableCapacity ?? 0;\n const allocatedHours = capacity?.allocatedCapacity ?? 0;\n const totalHours = capacity?.totalCapacity ?? 0;\n const hasCapacity =\n !!capacity &&\n (totalHours > 0 || allocatedHours > 0 || overallocatedCount > 0);\n\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n purpose=\"Match the work ahead to the people you have — see who's over- or under-booked and how much capacity is free this week — so you can commit to deadlines you can actually hit without burning anyone out.\"\n actions={newPlanButton}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Team Utilization\"\n value={`${Math.round(utilizationRate)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Over-allocated\"\n value={String(overallocatedCount)}\n icon={<Users className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Active Plans\"\n value={String(plans.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Available Capacity\"\n value={`${Math.round(availableHours)}h`}\n icon={<CalendarRange className=\"h-5 w-5\" />}\n description=\"This week\"\n />\n </div>\n\n {hasCapacity && (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Capacity split\"\n description=\"Allocated vs available hours this week\"\n >\n <DonutChart\n data={[\n { name: \"Allocated\", value: allocatedHours },\n { name: \"Available\", value: availableHours },\n ]}\n centerLabel=\"Total\"\n centerValue={`${Math.round(totalHours)}h`}\n valueFormat={(v) => `${Math.round(v)}h`}\n emptyLabel=\"No capacity data yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Utilization & over-allocation\"\n description=\"Team utilization and who is over-booked this week\"\n >\n <RadialGauge\n value={Math.round(utilizationRate)}\n max={100}\n label=\"Utilization\"\n sublabel=\"this week\"\n format={(v) => `${Math.round(v)}%`}\n />\n <div className=\"mt-4\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n Over-allocated ({overallocatedCount})\n </p>\n {overallocatedCount === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No one is over-allocated this week.\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {overallocatedUsers.map((userId: string) => (\n <span\n key={userId}\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {userId}\n </span>\n ))}\n </div>\n )}\n </div>\n </ChartCard>\n </div>\n )}\n\n {plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No resource plans yet\"\n description=\"Create a resource plan to start allocating team members and balancing workload across the week.\"\n action={newPlanButton}\n />\n ) : (\n <div className=\"space-y-6\">\n {plans.map((plan: ResourcePlanFieldsFragment) => (\n <GlassCard\n key={plan.id}\n treatment=\"glass\"\n className=\"overflow-hidden\"\n >\n <div className=\"flex flex-col gap-3 border-b border-border-seam p-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {plan.name}\n </p>\n <StatusBadge label={plan.status} status={plan.status} />\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {plan.startDate.slice(0, 10)} to{\" \"}\n {plan.endDate.slice(0, 10)} &middot;{\" \"}\n {plan.allocations.length}{\" \"}\n {plan.allocations.length === 1 ? \"person\" : \"people\"}\n </p>\n <div className=\"mt-3 max-w-sm\">\n <ProgressMeter\n size=\"sm\"\n label=\"Logged vs allocated\"\n value={plan.totalActualHours}\n max={\n plan.totalAllocatedHours > 0\n ? plan.totalAllocatedHours\n : Math.max(plan.totalActualHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={() => openAddAllocation(plan)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <UserPlus className=\"h-3.5 w-3.5\" />\n Add Allocation\n </button>\n <button\n onClick={() => openEditPlan(plan)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n Edit\n </button>\n <button\n onClick={() => setConfirmDeletePlan(plan)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n Delete\n </button>\n </div>\n </div>\n\n {plan.allocations.length === 0 ? (\n <div className=\"p-5 text-sm text-text-secondary\">\n No allocations yet. Use &ldquo;Add Allocation&rdquo; to\n assign team members.\n </div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"whitespace-nowrap px-5 py-2.5 font-medium\">\n Member\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Window\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Allocated\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Actual\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Utilization\n </th>\n <th className=\"whitespace-nowrap px-5 py-2.5 text-right font-medium\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {plan.allocations.map(\n (alloc: ResourceAllocationFieldsFragment) => {\n const utilization =\n alloc.utilizationRate ??\n (alloc.allocatedHours > 0\n ? Math.round(\n (alloc.actualHours / alloc.allocatedHours) *\n 100,\n )\n : 0);\n const tone: MeterTone =\n utilization > 100\n ? \"danger\"\n : utilization < 70\n ? \"warning\"\n : \"success\";\n const toneText =\n tone === \"danger\"\n ? \"text-status-error-text\"\n : tone === \"warning\"\n ? \"text-status-warning-text\"\n : \"text-status-success-text\";\n return (\n <tr\n key={alloc.id}\n className=\"transition-colors hover:bg-bg-sunken/50\"\n >\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(alloc.userId)}\n </div>\n <span className=\"font-medium text-text-primary\">\n {alloc.userId}\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-text-secondary\">\n {alloc.startDate.slice(0, 10)} to{\" \"}\n {alloc.endDate.slice(0, 10)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right font-medium text-text-primary\">\n {formatHours(alloc.allocatedHours)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right text-text-secondary\">\n {formatHours(alloc.actualHours)}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex w-40 items-center gap-2\">\n <ProgressMeter\n size=\"sm\"\n value={utilization}\n max={100}\n tone={tone}\n showValue={false}\n className=\"flex-1\"\n />\n <span\n className={`shrink-0 text-xs font-medium tabular-nums ${toneText}`}\n >\n {utilization}%\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEditAllocation(alloc)}\n aria-label=\"Edit allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() =>\n setConfirmDeleteAlloc(alloc)\n }\n aria-label=\"Delete allocation\"\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 </td>\n </tr>\n );\n },\n )}\n </tbody>\n </table>\n </div>\n )}\n </GlassCard>\n ))}\n {hasMore && (\n <div className=\"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 </div>\n )}\n </div>\n\n {/* Plan create/edit modal */}\n <Modal\n open={planModalOpen}\n onClose={() => setPlanModalOpen(false)}\n title={editingPlan ? \"Edit Resource Plan\" : \"New Resource Plan\"}\n description=\"Set the window this plan covers, then allocate people to it.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setPlanModalOpen(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={handleSubmitPlan}\n disabled={creatingPlan || updatingPlan || !planForm.name.trim()}\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 {creatingPlan || updatingPlan\n ? \"Saving...\"\n : editingPlan\n ? \"Save Changes\"\n : \"Create Plan\"}\n </button>\n </>\n }\n >\n {(createPlanError || updatePlanError) && (\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 resource plan. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Plan Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Q3 Delivery\"\n value={planForm.name}\n onChange={(e) =>\n setPlanForm({ ...planForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description (optional)\n </label>\n <textarea\n rows={2}\n placeholder=\"What this plan covers\"\n value={planForm.description}\n onChange={(e) =>\n setPlanForm({ ...planForm, description: 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=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={planForm.startDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, startDate: 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 Date\n </label>\n <input\n type=\"date\"\n value={planForm.endDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n {editingPlan && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n value={planForm.status}\n onChange={(e) =>\n setPlanForm({ ...planForm, status: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"draft\">Draft</option>\n <option value=\"completed\">Completed</option>\n <option value=\"archived\">Archived</option>\n </select>\n </div>\n )}\n </div>\n </Modal>\n\n {/* Add allocation(s) modal */}\n <Modal\n open={allocModalPlan !== null}\n onClose={() => setAllocModalPlan(null)}\n title=\"Add Allocation\"\n description={\n allocModalPlan\n ? `Assign team members to \"${allocModalPlan.name}\".`\n : undefined\n }\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setAllocModalPlan(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={handleSubmitAllocations}\n disabled={creatingAlloc || bulkCreating || !allocRowsValid}\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 {creatingAlloc || bulkCreating\n ? \"Saving...\"\n : allocRows.length > 1\n ? `Add ${allocRows.length} Allocations`\n : \"Add Allocation\"}\n </button>\n </>\n }\n >\n {allocError && (\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 allocations. Please check the values and try again.\n </div>\n )}\n <div className=\"space-y-4\">\n {allocRows.map((row, index) => (\n <div\n key={index}\n className=\"rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Allocation {index + 1}\n </span>\n {allocRows.length > 1 && (\n <button\n onClick={() => removeAllocRow(index)}\n className=\"text-xs font-medium text-status-error-text hover:underline\"\n >\n Remove\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n placeholder=\"User identifier\"\n value={row.userId}\n onChange={(e) =>\n updateAllocRow(index, { userId: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={row.allocatedHours}\n onChange={(e) =>\n updateAllocRow(index, {\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={row.startDate}\n onChange={(e) =>\n updateAllocRow(index, { startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={row.endDate}\n onChange={(e) =>\n updateAllocRow(index, { endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-xs font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"Context for this allocation\"\n value={row.notes}\n onChange={(e) =>\n updateAllocRow(index, { notes: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n ))}\n <button\n onClick={addAllocRow}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Plus className=\"h-4 w-4\" />\n Add another person\n </button>\n </div>\n </Modal>\n\n {/* Edit allocation modal */}\n <Modal\n open={editingAlloc !== null && allocEditForm !== null}\n onClose={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\n title=\"Edit Allocation\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\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={handleSubmitAllocEdit}\n disabled={updatingAlloc}\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 {updatingAlloc ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateAllocError && (\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 update allocation. Please try again.\n </div>\n )}\n {allocEditForm && (\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 User ID\n </label>\n <input\n type=\"text\"\n value={allocEditForm.userId}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n userId: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.allocatedHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Actual Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.actualHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n actualHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div />\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.startDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n startDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.endDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n endDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n value={allocEditForm.notes}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n notes: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n </div>\n )}\n </Modal>\n\n {/* Delete plan confirm */}\n <Modal\n open={confirmDeletePlan !== null}\n onClose={() => setConfirmDeletePlan(null)}\n title=\"Delete Resource Plan\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeletePlan(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={handleDeletePlan}\n disabled={deletingPlan}\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 {deletingPlan ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeletePlan?.name}\n </span>\n ? All its allocations will be removed.\n </p>\n </Modal>\n\n {/* Delete allocation confirm */}\n <Modal\n open={confirmDeleteAlloc !== null}\n onClose={() => setConfirmDeleteAlloc(null)}\n title=\"Delete Allocation\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeleteAlloc(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={handleDeleteAllocation}\n disabled={deletingAlloc}\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 {deletingAlloc ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove the allocation for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeleteAlloc?.userId}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ResourcePlanningPage.js","names":[],"sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CalendarRange,\n Users,\n TrendingUp,\n Plus,\n Pencil,\n Trash2,\n UserPlus,\n AlertTriangle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n RadialGauge,\n ProgressMeter,\n StatusBadge,\n type MeterTone,\n} from \"../../components\";\nimport { getInitials, formatHours } from \"../../utils/helpers\";\nimport {\n useListResourcePlansQuery,\n useGetCapacityOverviewQuery,\n useCreateResourcePlanMutation,\n useUpdateResourcePlanMutation,\n useDeleteResourcePlanMutation,\n useCreateAllocationMutation,\n useUpdateAllocationMutation,\n useDeleteAllocationMutation,\n useBulkCreateAllocationsMutation,\n type ResourcePlanFieldsFragment,\n type ResourceAllocationFieldsFragment,\n type ListResourcePlansQuery,\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\nconst toDateInput = (iso: string) => iso.slice(0, 10);\nconst toIso = (dateStr: string) => new Date(dateStr).toISOString();\n\ninterface PlanFormState {\n name: string;\n description: string;\n startDate: string;\n endDate: string;\n status: string;\n}\n\ninterface AllocationRow {\n userId: string;\n allocatedHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\ninterface AllocationEditState {\n userId: string;\n allocatedHours: string;\n actualHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\nconst emptyPlanForm = (): PlanFormState => {\n const now = new Date();\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7);\n return {\n name: \"\",\n description: \"\",\n startDate: toDateInput(start.toISOString()),\n endDate: toDateInput(end.toISOString()),\n status: \"active\",\n };\n};\n\nconst emptyAllocationRow = (\n plan?: ResourcePlanFieldsFragment,\n): AllocationRow => ({\n userId: \"\",\n allocatedHours: \"\",\n startDate: plan ? toDateInput(plan.startDate) : \"\",\n endDate: plan ? toDateInput(plan.endDate) : \"\",\n notes: \"\",\n});\n\nexport function ResourcePlanningPage() {\n const now = new Date();\n const startDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n ).toISOString();\n const endDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + 7,\n ).toISOString();\n\n const {\n data: plansData,\n loading: plansLoading,\n error: plansError,\n refetch: refetchPlans,\n fetchMore,\n } = useListResourcePlansQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const { data: capacityData, refetch: refetchCapacity } =\n useGetCapacityOverviewQuery({ variables: { startDate, endDate } });\n\n const [createPlan, { loading: creatingPlan, error: createPlanError }] =\n useCreateResourcePlanMutation();\n const [updatePlan, { loading: updatingPlan, error: updatePlanError }] =\n useUpdateResourcePlanMutation();\n const [deletePlan, { loading: deletingPlan }] =\n useDeleteResourcePlanMutation();\n const [createAllocation, { loading: creatingAlloc }] =\n useCreateAllocationMutation();\n const [bulkCreateAllocations, { loading: bulkCreating }] =\n useBulkCreateAllocationsMutation();\n const [\n updateAllocation,\n { loading: updatingAlloc, error: updateAllocError },\n ] = useUpdateAllocationMutation();\n const [deleteAllocation, { loading: deletingAlloc }] =\n useDeleteAllocationMutation();\n\n const [planModalOpen, setPlanModalOpen] = useState(false);\n const [editingPlan, setEditingPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [planForm, setPlanForm] = useState<PlanFormState>(emptyPlanForm());\n const [confirmDeletePlan, setConfirmDeletePlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n\n const [allocModalPlan, setAllocModalPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [allocRows, setAllocRows] = useState<AllocationRow[]>([]);\n const [allocError, setAllocError] = useState(false);\n\n const [editingAlloc, setEditingAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n const [allocEditForm, setAllocEditForm] =\n useState<AllocationEditState | null>(null);\n const [confirmDeleteAlloc, setConfirmDeleteAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const plans = plansData?.resourcePlans?.items ?? [];\n const hasMore = plansData?.resourcePlans?.hasMore ?? false;\n const capacity = capacityData?.capacityOverview;\n\n const refetchAll = async () => {\n await Promise.all([refetchPlans(), refetchCapacity()]);\n };\n\n // -- Plan handlers --------------------------------------------------------\n const openCreatePlan = () => {\n setEditingPlan(null);\n setPlanForm(emptyPlanForm());\n setPlanModalOpen(true);\n };\n\n const openEditPlan = (plan: ResourcePlanFieldsFragment) => {\n setEditingPlan(plan);\n setPlanForm({\n name: plan.name,\n description: plan.description ?? \"\",\n startDate: toDateInput(plan.startDate),\n endDate: toDateInput(plan.endDate),\n status: plan.status,\n });\n setPlanModalOpen(true);\n };\n\n const handleSubmitPlan = async () => {\n if (!planForm.name.trim()) return;\n if (editingPlan) {\n await updatePlan({\n variables: {\n id: editingPlan.id,\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n status: planForm.status,\n },\n },\n });\n } else {\n await createPlan({\n variables: {\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n },\n },\n });\n }\n setPlanModalOpen(false);\n await refetchAll();\n };\n\n const handleDeletePlan = async () => {\n if (!confirmDeletePlan) return;\n await deletePlan({ variables: { id: confirmDeletePlan.id } });\n setConfirmDeletePlan(null);\n await refetchAll();\n };\n\n // -- Allocation handlers --------------------------------------------------\n const openAddAllocation = (plan: ResourcePlanFieldsFragment) => {\n setAllocModalPlan(plan);\n setAllocRows([emptyAllocationRow(plan)]);\n setAllocError(false);\n };\n\n const updateAllocRow = (index: number, patch: Partial<AllocationRow>) => {\n setAllocRows((rows) =>\n rows.map((r, i) => (i === index ? { ...r, ...patch } : r)),\n );\n };\n\n const addAllocRow = () => {\n setAllocRows((rows) => [\n ...rows,\n emptyAllocationRow(allocModalPlan ?? undefined),\n ]);\n };\n\n const removeAllocRow = (index: number) => {\n setAllocRows((rows) => rows.filter((_, i) => i !== index));\n };\n\n const allocRowsValid = allocRows.every(\n (r) =>\n r.userId.trim() !== \"\" &&\n r.allocatedHours.trim() !== \"\" &&\n r.startDate !== \"\" &&\n r.endDate !== \"\",\n );\n\n const handleSubmitAllocations = async () => {\n if (!allocModalPlan || !allocRowsValid || allocRows.length === 0) return;\n setAllocError(false);\n try {\n if (allocRows.length === 1) {\n const r = allocRows[0];\n await createAllocation({\n variables: {\n input: {\n resourcePlanId: allocModalPlan.id,\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n },\n },\n });\n } else {\n await bulkCreateAllocations({\n variables: {\n planId: allocModalPlan.id,\n allocations: allocRows.map((r) => ({\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n })),\n },\n });\n }\n setAllocModalPlan(null);\n await refetchAll();\n } catch {\n setAllocError(true);\n }\n };\n\n const openEditAllocation = (alloc: ResourceAllocationFieldsFragment) => {\n setEditingAlloc(alloc);\n setAllocEditForm({\n userId: alloc.userId,\n allocatedHours: String(alloc.allocatedHours),\n actualHours: String(alloc.actualHours),\n startDate: toDateInput(alloc.startDate),\n endDate: toDateInput(alloc.endDate),\n notes: alloc.notes ?? \"\",\n });\n };\n\n const handleSubmitAllocEdit = async () => {\n if (!editingAlloc || !allocEditForm) return;\n await updateAllocation({\n variables: {\n id: editingAlloc.id,\n input: {\n userId: allocEditForm.userId.trim(),\n allocatedHours: Number(allocEditForm.allocatedHours),\n actualHours: Number(allocEditForm.actualHours),\n startDate: toIso(allocEditForm.startDate),\n endDate: toIso(allocEditForm.endDate),\n notes: allocEditForm.notes.trim() || undefined,\n },\n },\n });\n setEditingAlloc(null);\n setAllocEditForm(null);\n await refetchAll();\n };\n\n const handleDeleteAllocation = async () => {\n if (!confirmDeleteAlloc) return;\n await deleteAllocation({ variables: { id: confirmDeleteAlloc.id } });\n setConfirmDeleteAlloc(null);\n await refetchAll();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: plans.length } },\n updateQuery: (\n prev: ListResourcePlansQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListResourcePlansQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n resourcePlans: {\n ...fetchMoreResult.resourcePlans,\n items: [\n ...prev.resourcePlans.items,\n ...fetchMoreResult.resourcePlans.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const newPlanButton = (\n <button\n onClick={openCreatePlan}\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 New Resource Plan\n </button>\n );\n\n if (plansLoading) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (plansError) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\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 resource planning data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const utilizationRate = capacity?.utilizationRate ?? 0;\n const overallocatedUsers = capacity?.overallocatedUsers ?? [];\n const overallocatedCount = overallocatedUsers.length;\n const availableHours = capacity?.availableCapacity ?? 0;\n const allocatedHours = capacity?.allocatedCapacity ?? 0;\n const totalHours = capacity?.totalCapacity ?? 0;\n const hasCapacity =\n !!capacity &&\n (totalHours > 0 || allocatedHours > 0 || overallocatedCount > 0);\n\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n purpose=\"Match the work ahead to the people you have — see who's over- or under-booked and how much capacity is free this week — so you can commit to deadlines you can actually hit without burning anyone out.\"\n actions={newPlanButton}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Team Utilization\"\n value={`${Math.round(utilizationRate)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Over-allocated\"\n value={String(overallocatedCount)}\n icon={<Users className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Active Plans\"\n value={String(plans.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Available Capacity\"\n value={`${Math.round(availableHours)}h`}\n icon={<CalendarRange className=\"h-5 w-5\" />}\n description=\"This week\"\n />\n </div>\n\n {hasCapacity && (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Capacity split\"\n description=\"Allocated vs available hours this week\"\n >\n <DonutChart\n data={[\n { name: \"Allocated\", value: allocatedHours },\n { name: \"Available\", value: availableHours },\n ]}\n centerLabel=\"Total\"\n centerValue={`${Math.round(totalHours)}h`}\n valueFormat={(v) => `${Math.round(v)}h`}\n emptyLabel=\"No capacity data yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Utilization & over-allocation\"\n description=\"Team utilization and who is over-booked this week\"\n >\n <RadialGauge\n value={Math.round(utilizationRate)}\n max={100}\n label=\"Utilization\"\n sublabel=\"this week\"\n format={(v) => `${Math.round(v)}%`}\n />\n <div className=\"mt-4\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n Over-allocated ({overallocatedCount})\n </p>\n {overallocatedCount === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No one is over-allocated this week.\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {overallocatedUsers.map((userId: string) => (\n <span\n key={userId}\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {userId}\n </span>\n ))}\n </div>\n )}\n </div>\n </ChartCard>\n </div>\n )}\n\n {plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No resource plans yet\"\n description=\"Create a resource plan to start allocating team members and balancing workload across the week.\"\n action={newPlanButton}\n />\n ) : (\n <div className=\"space-y-6\">\n {plans.map((plan: ResourcePlanFieldsFragment) => (\n <GlassCard\n key={plan.id}\n treatment=\"glass\"\n className=\"overflow-hidden\"\n >\n <div className=\"flex flex-col gap-3 border-b border-border-seam p-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {plan.name}\n </p>\n <StatusBadge label={plan.status} status={plan.status} />\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {plan.startDate.slice(0, 10)} to{\" \"}\n {plan.endDate.slice(0, 10)} &middot;{\" \"}\n {plan.allocations.length}{\" \"}\n {plan.allocations.length === 1 ? \"person\" : \"people\"}\n </p>\n <div className=\"mt-3 max-w-sm\">\n <ProgressMeter\n size=\"sm\"\n label=\"Logged vs allocated\"\n value={plan.totalActualHours}\n max={\n plan.totalAllocatedHours > 0\n ? plan.totalAllocatedHours\n : Math.max(plan.totalActualHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={() => openAddAllocation(plan)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <UserPlus className=\"h-3.5 w-3.5\" />\n Add Allocation\n </button>\n <button\n onClick={() => openEditPlan(plan)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n Edit\n </button>\n <button\n onClick={() => setConfirmDeletePlan(plan)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n Delete\n </button>\n </div>\n </div>\n\n {plan.allocations.length === 0 ? (\n <div className=\"p-5 text-sm text-text-secondary\">\n No allocations yet. Use &ldquo;Add Allocation&rdquo; to\n assign team members.\n </div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"whitespace-nowrap px-5 py-2.5 font-medium\">\n Member\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Window\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Allocated\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Actual\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Utilization\n </th>\n <th className=\"whitespace-nowrap px-5 py-2.5 text-right font-medium\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {plan.allocations.map(\n (alloc: ResourceAllocationFieldsFragment) => {\n const utilization =\n alloc.utilizationRate ??\n (alloc.allocatedHours > 0\n ? Math.round(\n (alloc.actualHours / alloc.allocatedHours) *\n 100,\n )\n : 0);\n const tone: MeterTone =\n utilization > 100\n ? \"danger\"\n : utilization < 70\n ? \"warning\"\n : \"success\";\n const toneText =\n tone === \"danger\"\n ? \"text-status-error-text\"\n : tone === \"warning\"\n ? \"text-status-warning-text\"\n : \"text-status-success-text\";\n return (\n <tr\n key={alloc.id}\n className=\"transition-colors hover:bg-bg-sunken/50\"\n >\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(alloc.userId)}\n </div>\n <span className=\"font-medium text-text-primary\">\n {alloc.userId}\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-text-secondary\">\n {alloc.startDate.slice(0, 10)} to{\" \"}\n {alloc.endDate.slice(0, 10)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right font-medium text-text-primary\">\n {formatHours(alloc.allocatedHours)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right text-text-secondary\">\n {formatHours(alloc.actualHours)}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex w-40 items-center gap-2\">\n <ProgressMeter\n size=\"sm\"\n value={utilization}\n max={100}\n tone={tone}\n showValue={false}\n className=\"flex-1\"\n />\n <span\n className={`shrink-0 text-xs font-medium tabular-nums ${toneText}`}\n >\n {utilization}%\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEditAllocation(alloc)}\n aria-label=\"Edit allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() =>\n setConfirmDeleteAlloc(alloc)\n }\n aria-label=\"Delete allocation\"\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 </td>\n </tr>\n );\n },\n )}\n </tbody>\n </table>\n </div>\n )}\n </GlassCard>\n ))}\n {hasMore && (\n <div className=\"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 </div>\n )}\n </div>\n\n {/* Plan create/edit modal */}\n <Modal\n open={planModalOpen}\n onClose={() => setPlanModalOpen(false)}\n title={editingPlan ? \"Edit Resource Plan\" : \"New Resource Plan\"}\n description=\"Set the window this plan covers, then allocate people to it.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setPlanModalOpen(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={handleSubmitPlan}\n disabled={creatingPlan || updatingPlan || !planForm.name.trim()}\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 {creatingPlan || updatingPlan\n ? \"Saving...\"\n : editingPlan\n ? \"Save Changes\"\n : \"Create Plan\"}\n </button>\n </>\n }\n >\n {(createPlanError || updatePlanError) && (\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 resource plan. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Plan Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Q3 Delivery\"\n value={planForm.name}\n onChange={(e) =>\n setPlanForm({ ...planForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description (optional)\n </label>\n <textarea\n rows={2}\n placeholder=\"What this plan covers\"\n value={planForm.description}\n onChange={(e) =>\n setPlanForm({ ...planForm, description: 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=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={planForm.startDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, startDate: 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 Date\n </label>\n <input\n type=\"date\"\n value={planForm.endDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n {editingPlan && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n value={planForm.status}\n onChange={(e) =>\n setPlanForm({ ...planForm, status: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"draft\">Draft</option>\n <option value=\"completed\">Completed</option>\n <option value=\"archived\">Archived</option>\n </select>\n </div>\n )}\n </div>\n </Modal>\n\n {/* Add allocation(s) modal */}\n <Modal\n open={allocModalPlan !== null}\n onClose={() => setAllocModalPlan(null)}\n title=\"Add Allocation\"\n description={\n allocModalPlan\n ? `Assign team members to \"${allocModalPlan.name}\".`\n : undefined\n }\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setAllocModalPlan(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={handleSubmitAllocations}\n disabled={creatingAlloc || bulkCreating || !allocRowsValid}\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 {creatingAlloc || bulkCreating\n ? \"Saving...\"\n : allocRows.length > 1\n ? `Add ${allocRows.length} Allocations`\n : \"Add Allocation\"}\n </button>\n </>\n }\n >\n {allocError && (\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 allocations. Please check the values and try again.\n </div>\n )}\n <div className=\"space-y-4\">\n {allocRows.map((row, index) => (\n <div\n key={index}\n className=\"rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Allocation {index + 1}\n </span>\n {allocRows.length > 1 && (\n <button\n onClick={() => removeAllocRow(index)}\n className=\"text-xs font-medium text-status-error-text hover:underline\"\n >\n Remove\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n placeholder=\"User identifier\"\n value={row.userId}\n onChange={(e) =>\n updateAllocRow(index, { userId: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={row.allocatedHours}\n onChange={(e) =>\n updateAllocRow(index, {\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={row.startDate}\n onChange={(e) =>\n updateAllocRow(index, { startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={row.endDate}\n onChange={(e) =>\n updateAllocRow(index, { endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-xs font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"Context for this allocation\"\n value={row.notes}\n onChange={(e) =>\n updateAllocRow(index, { notes: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n ))}\n <button\n onClick={addAllocRow}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Plus className=\"h-4 w-4\" />\n Add another person\n </button>\n </div>\n </Modal>\n\n {/* Edit allocation modal */}\n <Modal\n open={editingAlloc !== null && allocEditForm !== null}\n onClose={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\n title=\"Edit Allocation\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\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={handleSubmitAllocEdit}\n disabled={updatingAlloc}\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 {updatingAlloc ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateAllocError && (\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 update allocation. Please try again.\n </div>\n )}\n {allocEditForm && (\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 User ID\n </label>\n <input\n type=\"text\"\n value={allocEditForm.userId}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n userId: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.allocatedHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Actual Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.actualHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n actualHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div />\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.startDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n startDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.endDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n endDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n value={allocEditForm.notes}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n notes: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n </div>\n )}\n </Modal>\n\n {/* Delete plan confirm */}\n <Modal\n open={confirmDeletePlan !== null}\n onClose={() => setConfirmDeletePlan(null)}\n title=\"Delete Resource Plan\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeletePlan(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={handleDeletePlan}\n disabled={deletingPlan}\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 {deletingPlan ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeletePlan?.name}\n </span>\n ? All its allocations will be removed.\n </p>\n </Modal>\n\n {/* Delete allocation confirm */}\n <Modal\n open={confirmDeleteAlloc !== null}\n onClose={() => setConfirmDeleteAlloc(null)}\n title=\"Delete Allocation\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeleteAlloc(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={handleDeleteAllocation}\n disabled={deletingAlloc}\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 {deletingAlloc ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove the allocation for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeleteAlloc?.userId}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyCA,IAAM,KAAY,IAEZ,IACJ,wIAEI,KAAe,MAAgB,EAAI,MAAM,GAAG,EAAE,GAC9C,KAAS,MAAoB,IAAI,KAAK,CAAO,CAAC,CAAC,YAAY,GA2B3D,UAAqC;CACzC,IAAM,oBAAM,IAAI,KAAK,GACf,IAAQ,IAAI,KAAK,EAAI,YAAY,GAAG,EAAI,SAAS,GAAG,EAAI,QAAQ,CAAC,GACjE,IAAM,IAAI,KAAK,EAAI,YAAY,GAAG,EAAI,SAAS,GAAG,EAAI,QAAQ,IAAI,CAAC;CACzE,OAAO;EACL,MAAM;EACN,aAAa;EACb,WAAW,EAAY,EAAM,YAAY,CAAC;EAC1C,SAAS,EAAY,EAAI,YAAY,CAAC;EACtC,QAAQ;CACV;AACF,GAEM,KACJ,OACmB;CACnB,QAAQ;CACR,gBAAgB;CAChB,WAAW,IAAO,EAAY,EAAK,SAAS,IAAI;CAChD,SAAS,IAAO,EAAY,EAAK,OAAO,IAAI;CAC5C,OAAO;AACT;AAEA,SAAgB,IAAuB;CACrC,IAAM,oBAAM,IAAI,KAAK,GACf,KAAY,IAAI,KACpB,EAAI,YAAY,GAChB,EAAI,SAAS,GACb,EAAI,QAAQ,CACd,CAAC,CAAC,YAAY,GACR,KAAU,IAAI,KAClB,EAAI,YAAY,GAChB,EAAI,SAAS,GACb,EAAI,QAAQ,IAAI,CAClB,CAAC,CAAC,YAAY,GAER,EACJ,MAAM,GACN,SAAS,IACT,OAAO,IACP,SAAS,IACT,kBACE,GAA0B,EAC5B,WAAW,EAAE,YAAY;EAAE,OAAO;EAAW,QAAQ;CAAE,EAAE,EAC3D,CAAC,GACK,EAAE,MAAM,IAAc,SAAS,OACnC,GAA4B,EAAE,WAAW;EAAE;EAAW;CAAQ,EAAE,CAAC,GAE7D,CAAC,IAAY,EAAE,SAAS,GAAc,OAAO,QACjD,GAA8B,GAC1B,CAAC,IAAY,EAAE,SAAS,GAAc,OAAO,QACjD,GAA8B,GAC1B,CAAC,IAAY,EAAE,SAAS,OAC5B,GAA8B,GAC1B,CAAC,IAAkB,EAAE,SAAS,OAClC,GAA4B,GACxB,CAAC,IAAuB,EAAE,SAAS,OACvC,EAAiC,GAC7B,CACJ,IACA,EAAE,SAAS,GAAe,OAAO,QAC/B,GAA4B,GAC1B,CAAC,IAAkB,EAAE,SAAS,OAClC,GAA4B,GAExB,CAAC,IAAe,KAAoB,EAAS,EAAK,GAClD,CAAC,GAAa,MAClB,EAA4C,IAAI,GAC5C,CAAC,GAAU,KAAe,EAAwB,EAAc,CAAC,GACjE,CAAC,GAAmB,KACxB,EAA4C,IAAI,GAE5C,CAAC,GAAgB,KACrB,EAA4C,IAAI,GAC5C,CAAC,GAAW,KAAgB,EAA0B,CAAC,CAAC,GACxD,CAAC,IAAY,KAAiB,EAAS,EAAK,GAE5C,CAAC,GAAc,KACnB,EAAkD,IAAI,GAClD,CAAC,GAAe,KACpB,EAAqC,IAAI,GACrC,CAAC,GAAoB,KACzB,EAAkD,IAAI,GAElD,CAAC,GAAa,KAAkB,EAAS,EAAK,GAE9C,IAAQ,GAAW,eAAe,SAAS,CAAC,GAC5C,KAAU,GAAW,eAAe,WAAW,IAC/C,IAAW,IAAc,kBAEzB,IAAa,YAAY;EAC7B,MAAM,QAAQ,IAAI,CAAC,GAAa,GAAG,GAAgB,CAAC,CAAC;CACvD,GAGM,WAAuB;EAG3B,AAFA,GAAe,IAAI,GACnB,EAAY,EAAc,CAAC,GAC3B,EAAiB,EAAI;CACvB,GAEM,MAAgB,MAAqC;EASzD,AARA,GAAe,CAAI,GACnB,EAAY;GACV,MAAM,EAAK;GACX,aAAa,EAAK,eAAe;GACjC,WAAW,EAAY,EAAK,SAAS;GACrC,SAAS,EAAY,EAAK,OAAO;GACjC,QAAQ,EAAK;EACf,CAAC,GACD,EAAiB,EAAI;CACvB,GAEM,KAAmB,YAAY;EAC9B,EAAS,KAAK,KAAK,MACpB,IACF,MAAM,GAAW,EACf,WAAW;GACT,IAAI,EAAY;GAChB,OAAO;IACL,MAAM,EAAS,KAAK,KAAK;IACzB,aAAa,EAAS,YAAY,KAAK,KAAK,KAAA;IAC5C,WAAW,EAAM,EAAS,SAAS;IACnC,SAAS,EAAM,EAAS,OAAO;IAC/B,QAAQ,EAAS;GACnB;EACF,EACF,CAAC,IAED,MAAM,GAAW,EACf,WAAW,EACT,OAAO;GACL,MAAM,EAAS,KAAK,KAAK;GACzB,aAAa,EAAS,YAAY,KAAK,KAAK,KAAA;GAC5C,WAAW,EAAM,EAAS,SAAS;GACnC,SAAS,EAAM,EAAS,OAAO;EACjC,EACF,EACF,CAAC,GAEH,EAAiB,EAAK,GACtB,MAAM,EAAW;CACnB,GAEM,KAAmB,YAAY;EAC9B,MACL,MAAM,GAAW,EAAE,WAAW,EAAE,IAAI,EAAkB,GAAG,EAAE,CAAC,GAC5D,EAAqB,IAAI,GACzB,MAAM,EAAW;CACnB,GAGM,MAAqB,MAAqC;EAG9D,AAFA,EAAkB,CAAI,GACtB,EAAa,CAAC,EAAmB,CAAI,CAAC,CAAC,GACvC,EAAc,EAAK;CACrB,GAEM,KAAkB,GAAe,MAAkC;EACvE,GAAc,MACZ,EAAK,KAAK,GAAG,MAAO,MAAM,IAAQ;GAAE,GAAG;GAAG,GAAG;EAAM,IAAI,CAAE,CAC3D;CACF,GAEM,WAAoB;EACxB,GAAc,MAAS,CACrB,GAAG,GACH,EAAmB,KAAkB,KAAA,CAAS,CAChD,CAAC;CACH,GAEM,MAAkB,MAAkB;EACxC,GAAc,MAAS,EAAK,QAAQ,GAAG,MAAM,MAAM,CAAK,CAAC;CAC3D,GAEM,KAAiB,EAAU,OAC9B,MACC,EAAE,OAAO,KAAK,MAAM,MACpB,EAAE,eAAe,KAAK,MAAM,MAC5B,EAAE,cAAc,MAChB,EAAE,YAAY,EAClB,GAEM,KAA0B,YAAY;EACtC,OAAC,KAAkB,CAAC,MAAkB,EAAU,WAAW,IAC/D;KAAc,EAAK;GACnB,IAAI;IACF,IAAI,EAAU,WAAW,GAAG;KAC1B,IAAM,IAAI,EAAU;KACpB,MAAM,GAAiB,EACrB,WAAW,EACT,OAAO;MACL,gBAAgB,EAAe;MAC/B,QAAQ,EAAE,OAAO,KAAK;MACtB,gBAAgB,OAAO,EAAE,cAAc;MACvC,WAAW,EAAM,EAAE,SAAS;MAC5B,SAAS,EAAM,EAAE,OAAO;MACxB,OAAO,EAAE,MAAM,KAAK,KAAK,KAAA;KAC3B,EACF,EACF,CAAC;IACH,OACE,MAAM,GAAsB,EAC1B,WAAW;KACT,QAAQ,EAAe;KACvB,aAAa,EAAU,KAAK,OAAO;MACjC,QAAQ,EAAE,OAAO,KAAK;MACtB,gBAAgB,OAAO,EAAE,cAAc;MACvC,WAAW,EAAM,EAAE,SAAS;MAC5B,SAAS,EAAM,EAAE,OAAO;MACxB,OAAO,EAAE,MAAM,KAAK,KAAK,KAAA;KAC3B,EAAE;IACJ,EACF,CAAC;IAGH,AADA,EAAkB,IAAI,GACtB,MAAM,EAAW;GACnB,QAAQ;IACN,EAAc,EAAI;GACpB;EAlCmB;CAmCrB,GAEM,MAAsB,MAA4C;EAEtE,AADA,EAAgB,CAAK,GACrB,EAAiB;GACf,QAAQ,EAAM;GACd,gBAAgB,OAAO,EAAM,cAAc;GAC3C,aAAa,OAAO,EAAM,WAAW;GACrC,WAAW,EAAY,EAAM,SAAS;GACtC,SAAS,EAAY,EAAM,OAAO;GAClC,OAAO,EAAM,SAAS;EACxB,CAAC;CACH,GAEM,KAAwB,YAAY;EACpC,CAAC,KAAgB,CAAC,MACtB,MAAM,GAAiB,EACrB,WAAW;GACT,IAAI,EAAa;GACjB,OAAO;IACL,QAAQ,EAAc,OAAO,KAAK;IAClC,gBAAgB,OAAO,EAAc,cAAc;IACnD,aAAa,OAAO,EAAc,WAAW;IAC7C,WAAW,EAAM,EAAc,SAAS;IACxC,SAAS,EAAM,EAAc,OAAO;IACpC,OAAO,EAAc,MAAM,KAAK,KAAK,KAAA;GACvC;EACF,EACF,CAAC,GACD,EAAgB,IAAI,GACpB,EAAiB,IAAI,GACrB,MAAM,EAAW;CACnB,GAEM,KAAyB,YAAY;EACpC,MACL,MAAM,GAAiB,EAAE,WAAW,EAAE,IAAI,EAAmB,GAAG,EAAE,CAAC,GACnE,EAAsB,IAAI,GAC1B,MAAM,EAAW;CACnB,GAEM,KAAiB,YAAY;EACjC,EAAe,EAAI;EACnB,IAAI;GACF,MAAM,GAAU;IACd,WAAW,EAAE,YAAY;KAAE,OAAO;KAAW,QAAQ,EAAM;IAAO,EAAE;IACpE,cACE,GACA,EAAE,yBAEG,IACE,EACL,eAAe;KACb,GAAG,EAAgB;KACnB,OAAO,CACL,GAAG,EAAK,cAAc,OACtB,GAAG,EAAgB,cAAc,KACnC;IACF,EACF,IAT6B;GAWjC,CAAC;EACH,UAAU;GACR,EAAe,EAAK;EACtB;CACF,GAEM,KACJ,kBAAC,UAAD;EACE,SAAS;EACT,WAAU;EAFZ,UAAA,CAIE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,GAAC,mBAEtB;;CAGV,IAAI,IACF,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EAEZ,UAAA,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;EAAI,CAAA;CACjC,CAAA;CAIhB,IAAI,IACF,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EAEZ,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAoG,UAAA;EAE9G,CAAA;CACK,CAAA;CAIhB,IAAM,KAAkB,GAAU,mBAAmB,GAC/C,IAAqB,GAAU,sBAAsB,CAAC,GACtD,IAAqB,EAAmB,QACxC,KAAiB,GAAU,qBAAqB,GAChD,KAAiB,GAAU,qBAAqB,GAChD,KAAa,GAAU,iBAAiB,GACxC,KACJ,CAAC,CAAC,MACD,KAAa,KAAK,KAAiB,KAAK,IAAqB;CAEhE,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SAAS;EAJX,UAAA;GAME,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,kBAAC,GAAD;QACE,UAAA;QACA,OAAM;QACN,OAAO,GAAG,KAAK,MAAM,EAAe,EAAE;QACtC,MAAM,kBAAC,IAAD,EAAY,WAAU,UAAW,CAAA;OACxC,CAAA;OACD,kBAAC,GAAD;QACE,OAAM;QACN,OAAO,OAAO,CAAkB;QAChC,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;QAClC,aAAY;OACb,CAAA;OACD,kBAAC,GAAD;QACE,OAAM;QACN,OAAO,OAAO,EAAM,MAAM;QAC1B,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;OACnC,CAAA;OACD,kBAAC,GAAD;QACE,OAAM;QACN,OAAO,GAAG,KAAK,MAAM,EAAc,EAAE;QACrC,MAAM,kBAAC,IAAD,EAAe,WAAU,UAAW,CAAA;QAC1C,aAAY;OACb,CAAA;MACE;;KAEJ,MACC,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OACE,OAAM;OACN,aAAY;OAEZ,UAAA,kBAAC,IAAD;QACE,MAAM,CACJ;SAAE,MAAM;SAAa,OAAO;QAAe,GAC3C;SAAE,MAAM;SAAa,OAAO;QAAe,CAC7C;QACA,aAAY;QACZ,aAAa,GAAG,KAAK,MAAM,EAAU,EAAE;QACvC,cAAc,MAAM,GAAG,KAAK,MAAM,CAAC,EAAE;QACrC,YAAW;OACZ,CAAA;MACQ,CAAA,GACX,kBAAC,GAAD;OACE,OAAM;OACN,aAAY;OAFd,UAAA,CAIE,kBAAC,IAAD;QACE,OAAO,KAAK,MAAM,EAAe;QACjC,KAAK;QACL,OAAM;QACN,UAAS;QACT,SAAS,MAAM,GAAG,KAAK,MAAM,CAAC,EAAE;OACjC,CAAA,GACD,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;SAAb,UAAA;UAA4D;UACzC;UAAmB;SACnC;QACF,CAAA,GAAA,MAAuB,IACtB,kBAAC,KAAD;SAAG,WAAU;SAA8B,UAAA;QAExC,CAAA,IAEH,kBAAC,OAAD;SAAK,WAAU;SACZ,UAAA,EAAmB,KAAK,MACvB,kBAAC,QAAD;UAEE,WAAU;UAFZ,UAAA,CAIE,kBAAC,IAAD,EAAe,WAAU,cAAe,CAAA,GACvC,CACG;SALC,GAAA,CAKD,CACP;QACE,CAAA,CAEJ;OACI,CAAA,CAAA;MACR,CAAA,CAAA;;KAGN,EAAM,WAAW,IAChB,kBAAC,IAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,QAAQ;KACT,CAAA,IAED,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACG,EAAM,KAAK,MACV,kBAAC,IAAD;OAEE,WAAU;OACV,WAAU;OAHZ,UAAA,CAKE,kBAAC,OAAD;QAAK,WAAU;QAAf,UAAA,CACE,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA;UACE,kBAAC,OAAD;WAAK,WAAU;WAAf,UAAA,CACE,kBAAC,KAAD;YAAG,WAAU;YACV,UAAA,EAAK;WACL,CAAA,GACH,kBAAC,IAAD;YAAa,OAAO,EAAK;YAAQ,QAAQ,EAAK;WAAS,CAAA,CACpD;;UACL,kBAAC,KAAD;WAAG,WAAU;WAAb,UAAA;YACG,EAAK,UAAU,MAAM,GAAG,EAAE;YAAE;YAAI;YAChC,EAAK,QAAQ,MAAM,GAAG,EAAE;YAAE;YAAU;YACpC,EAAK,YAAY;YAAQ;YACzB,EAAK,YAAY,WAAW,IAAI,WAAW;WAC3C;;UACH,kBAAC,OAAD;WAAK,WAAU;WACb,UAAA,kBAAC,IAAD;YACE,MAAK;YACL,OAAM;YACN,OAAO,EAAK;YACZ,KACE,EAAK,sBAAsB,IACvB,EAAK,sBACL,KAAK,IAAI,EAAK,kBAAkB,CAAC;YAEvC,SAAS,MAAM,EAAY,CAAC;WAC7B,CAAA;UACE,CAAA;SACF;QACL,CAAA,GAAA,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA;UACE,kBAAC,UAAD;WACE,eAAe,GAAkB,CAAI;WACrC,WAAU;WAFZ,UAAA,CAIE,kBAAC,IAAD,EAAU,WAAU,cAAe,CAAA,GAAC,gBAE9B;;UACR,kBAAC,UAAD;WACE,eAAe,GAAa,CAAI;WAChC,WAAU;WAFZ,UAAA,CAIE,kBAAC,IAAD,EAAQ,WAAU,cAAe,CAAA,GAAC,MAE5B;;UACR,kBAAC,UAAD;WACE,eAAe,EAAqB,CAAI;WACxC,WAAU;WAFZ,UAAA,CAIE,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA,GAAC,QAE5B;;SACL;QACF,CAAA,CAAA;OAEJ,CAAA,GAAA,EAAK,YAAY,WAAW,IAC3B,kBAAC,OAAD;QAAK,WAAU;QAAkC,UAAA;OAG5C,CAAA,IAEL,kBAAC,OAAD;QAAK,WAAU;QACb,UAAA,kBAAC,SAAD;SAAO,WAAU;SAAjB,UAAA,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;UAAI,WAAU;UAAd,UAAA;WACE,kBAAC,MAAD;YAAI,WAAU;YAA4C,UAAA;WAEtD,CAAA;WACJ,kBAAC,MAAD;YAAI,WAAU;YAA4C,UAAA;WAEtD,CAAA;WACJ,kBAAC,MAAD;YAAI,WAAU;YAAuD,UAAA;WAEjE,CAAA;WACJ,kBAAC,MAAD;YAAI,WAAU;YAAuD,UAAA;WAEjE,CAAA;WACJ,kBAAC,MAAD;YAAI,WAAU;YAA4C,UAAA;WAEtD,CAAA;WACJ,kBAAC,MAAD;YAAI,WAAU;YAAuD,UAAA;WAEjE,CAAA;UACF;SACC,CAAA,EAAA,CAAA,GACP,kBAAC,SAAD;UAAO,WAAU;UACd,UAAA,EAAK,YAAY,KACf,MAA4C;WAC3C,IAAM,IACJ,EAAM,oBACL,EAAM,iBAAiB,IACpB,KAAK,MACF,EAAM,cAAc,EAAM,iBACzB,GACJ,IACA,IACA,IACJ,IAAc,MACV,WACA,IAAc,KACZ,YACA,WACF,KACJ,MAAS,WACL,2BACA,MAAS,YACP,6BACA;WACR,OACE,kBAAC,MAAD;YAEE,WAAU;YAFZ,UAAA;aAIE,kBAAC,MAAD;cAAI,WAAU;cACZ,UAAA,kBAAC,OAAD;eAAK,WAAU;eAAf,UAAA,CACE,kBAAC,OAAD;gBAAK,WAAU;gBACZ,UAAA,EAAY,EAAM,MAAM;eACtB,CAAA,GACL,kBAAC,QAAD;gBAAM,WAAU;gBACb,UAAA,EAAM;eACH,CAAA,CACH;;aACH,CAAA;aACJ,kBAAC,MAAD;cAAI,WAAU;cAAd,UAAA;eACG,EAAM,UAAU,MAAM,GAAG,EAAE;eAAE;eAAI;eACjC,EAAM,QAAQ,MAAM,GAAG,EAAE;cACxB;;aACJ,kBAAC,MAAD;cAAI,WAAU;cACX,UAAA,EAAY,EAAM,cAAc;aAC/B,CAAA;aACJ,kBAAC,MAAD;cAAI,WAAU;cACX,UAAA,EAAY,EAAM,WAAW;aAC5B,CAAA;aACJ,kBAAC,MAAD;cAAI,WAAU;cACZ,UAAA,kBAAC,OAAD;eAAK,WAAU;eAAf,UAAA,CACE,kBAAC,IAAD;gBACE,MAAK;gBACL,OAAO;gBACP,KAAK;gBACC;gBACN,WAAW;gBACX,WAAU;eACX,CAAA,GACD,kBAAC,QAAD;gBACE,WAAW,6CAA6C;gBAD1D,UAAA,CAGG,GAAY,GACT;eACH,CAAA,CAAA;;aACH,CAAA;aACJ,kBAAC,MAAD;cAAI,WAAU;cACZ,UAAA,kBAAC,OAAD;eAAK,WAAU;eAAf,UAAA,CACE,kBAAC,UAAD;gBACE,eAAe,GAAmB,CAAK;gBACvC,cAAW;gBACX,WAAU;gBAEV,UAAA,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;eACvB,CAAA,GACR,kBAAC,UAAD;gBACE,eACE,EAAsB,CAAK;gBAE7B,cAAW;gBACX,WAAU;gBAEV,UAAA,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;eACvB,CAAA,CACL;;aACH,CAAA;YACF;WA5DG,GAAA,EAAM,EA4DT;UAER,CACF;SACK,CAAA,CACF;;OACJ,CAAA,CAEE;MApLJ,GAAA,EAAK,EAoLD,CACZ,GACA,MACC,kBAAC,OAAD;OAAK,WAAU;OACb,UAAA,kBAAC,UAAD;QACE,SAAS;QACT,UAAU;QACV,WAAU;QAET,UAAA,IAAc,eAAe;OACxB,CAAA;MACL,CAAA,CAEJ;;IAEJ;;GAGL,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAiB,EAAK;IACrC,OAAO,IAAc,uBAAuB;IAC5C,aAAY;IACZ,MAAK;IACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAiB,EAAK;KACrC,WAAU;KACX,UAAA;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,SAAS;KACT,UAAU,KAAgB,KAAgB,CAAC,EAAS,KAAK,KAAK;KAC9D,WAAU;KAET,UAAA,KAAgB,IACb,cACA,IACE,iBACA;IACA,CAAA,CACR,EAAA,CAAA;IAzBN,UAAA,EA4BI,MAAmB,OACnB,kBAAC,OAAD;KAAK,WAAU;KAAyG,UAAA;IAEnH,CAAA,GAEP,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,aAAY;OACZ,OAAO,EAAS;OAChB,WAAW,MACT,EAAY;QAAE,GAAG;QAAU,MAAM,EAAE,OAAO;OAAM,CAAC;OAEnD,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,YAAD;OACE,MAAM;OACN,aAAY;OACZ,OAAO,EAAS;OAChB,WAAW,MACT,EAAY;QAAE,GAAG;QAAU,aAAa,EAAE,OAAO;OAAM,CAAC;OAE1D,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;QAA8C,UAAA;OAExD,CAAA,GACP,kBAAC,SAAD;QACE,MAAK;QACL,OAAO,EAAS;QAChB,WAAW,MACT,EAAY;SAAE,GAAG;SAAU,WAAW,EAAE,OAAO;QAAM,CAAC;QAExD,WAAW;OACZ,CAAA,CACE,EAAA,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;QAA8C,UAAA;OAExD,CAAA,GACP,kBAAC,SAAD;QACE,MAAK;QACL,OAAO,EAAS;QAChB,WAAW,MACT,EAAY;SAAE,GAAG;SAAU,SAAS,EAAE,OAAO;QAAM,CAAC;QAEtD,WAAW;OACZ,CAAA,CACE,EAAA,CAAA,CACF;;MACJ,KACC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,UAAD;OACE,OAAO,EAAS;OAChB,WAAW,MACT,EAAY;QAAE,GAAG;QAAU,QAAQ,EAAE,OAAO;OAAM,CAAC;OAErD,WAAW;OALb,UAAA;QAOE,kBAAC,UAAD;SAAQ,OAAM;SAAS,UAAA;QAAc,CAAA;QACrC,kBAAC,UAAD;SAAQ,OAAM;SAAQ,UAAA;QAAa,CAAA;QACnC,kBAAC,UAAD;SAAQ,OAAM;SAAY,UAAA;QAAiB,CAAA;QAC3C,kBAAC,UAAD;SAAQ,OAAM;SAAW,UAAA;QAAgB,CAAA;OACnC;MACL,CAAA,CAAA,EAAA,CAAA;KAEJ;IACA,CAAA,CAAA;;GAGP,kBAAC,GAAD;IACE,MAAM,MAAmB;IACzB,eAAe,EAAkB,IAAI;IACrC,OAAM;IACN,aACE,IACI,2BAA2B,EAAe,KAAK,MAC/C,KAAA;IAEN,MAAK;IACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAkB,IAAI;KACrC,WAAU;KACX,UAAA;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,SAAS;KACT,UAAU,KAAiB,KAAgB,CAAC;KAC5C,WAAU;KAET,UAAA,KAAiB,IACd,cACA,EAAU,SAAS,IACjB,OAAO,EAAU,OAAO,gBACxB;IACA,CAAA,CACR,EAAA,CAAA;IA7BN,UAAA,CAgCG,MACC,kBAAC,OAAD;KAAK,WAAU;KAAyG,UAAA;IAEnH,CAAA,GAEP,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,EAAU,KAAK,GAAK,MACnB,kBAAC,OAAD;MAEE,WAAU;MAFZ,UAAA,CAIE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CAA0D,eAC5C,IAAQ,CAChB;OACL,CAAA,GAAA,EAAU,SAAS,KAClB,kBAAC,UAAD;QACE,eAAe,GAAe,CAAK;QACnC,WAAU;QACX,UAAA;OAEO,CAAA,CAEP;MACL,CAAA,GAAA,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;SAA8C,UAAA;QAExD,CAAA,GACP,kBAAC,SAAD;SACE,MAAK;SACL,aAAY;SACZ,OAAO,EAAI;SACX,WAAW,MACT,EAAe,GAAO,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC;SAElD,WAAW;QACZ,CAAA,CACE,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;SAA8C,UAAA;QAExD,CAAA,GACP,kBAAC,SAAD;SACE,MAAK;SACL,aAAY;SACZ,OAAO,EAAI;SACX,WAAW,MACT,EAAe,GAAO,EACpB,gBAAgB,EAAE,OAAO,MAC3B,CAAC;SAEH,WAAW;QACZ,CAAA,CACE,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;SAA8C,UAAA;QAExD,CAAA,GACP,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAI;SACX,WAAW,MACT,EAAe,GAAO,EAAE,WAAW,EAAE,OAAO,MAAM,CAAC;SAErD,WAAW;QACZ,CAAA,CACE,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;SAA8C,UAAA;QAExD,CAAA,GACP,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAI;SACX,WAAW,MACT,EAAe,GAAO,EAAE,SAAS,EAAE,OAAO,MAAM,CAAC;SAEnD,WAAW;QACZ,CAAA,CACE,EAAA,CAAA;QACL,kBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,kBAAC,SAAD;UAAO,WAAU;UAA8C,UAAA;SAExD,CAAA,GACP,kBAAC,SAAD;UACE,MAAK;UACL,aAAY;UACZ,OAAO,EAAI;UACX,WAAW,MACT,EAAe,GAAO,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC;UAEjD,WAAW;SACZ,CAAA,CACE;;OACF;MACF,CAAA,CAAA;KAxFE,GAAA,CAwFF,CACN,GACD,kBAAC,UAAD;MACE,SAAS;MACT,WAAU;MAFZ,UAAA,CAIE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,GAAC,oBAEtB;KACL,CAAA,CAAA;IACA,CAAA,CAAA;;GAGP,kBAAC,GAAD;IACE,MAAM,MAAiB,QAAQ,MAAkB;IACjD,eAAe;KAEb,AADA,EAAgB,IAAI,GACpB,EAAiB,IAAI;IACvB;IACA,OAAM;IACN,MAAK;IACL,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe;MAEb,AADA,EAAgB,IAAI,GACpB,EAAiB,IAAI;KACvB;KACA,WAAU;KACX,UAAA;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;KAET,UAAA,IAAgB,cAAc;IACzB,CAAA,CACR,EAAA,CAAA;IA1BN,UAAA,CA6BG,MACC,kBAAC,OAAD;KAAK,WAAU;KAAyG,UAAA;IAEnH,CAAA,GAEN,KACC,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAO,EAAc;OACrB,WAAW,MACT,EAAiB;QACf,GAAG;QACH,QAAQ,EAAE,OAAO;OACnB,CAAC;OAEH,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAO,EAAc;OACrB,WAAW,MACT,EAAiB;QACf,GAAG;QACH,gBAAgB,EAAE,OAAO;OAC3B,CAAC;OAEH,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAO,EAAc;OACrB,WAAW,MACT,EAAiB;QACf,GAAG;QACH,aAAa,EAAE,OAAO;OACxB,CAAC;OAEH,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD,CAAM,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAO,EAAc;OACrB,WAAW,MACT,EAAiB;QACf,GAAG;QACH,WAAW,EAAE,OAAO;OACtB,CAAC;OAEH,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;OAA8C,UAAA;MAExD,CAAA,GACP,kBAAC,SAAD;OACE,MAAK;OACL,OAAO,EAAc;OACrB,WAAW,MACT,EAAiB;QACf,GAAG;QACH,SAAS,EAAE,OAAO;OACpB,CAAC;OAEH,WAAW;MACZ,CAAA,CACE,EAAA,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;QAA8C,UAAA;OAExD,CAAA,GACP,kBAAC,SAAD;QACE,MAAK;QACL,OAAO,EAAc;QACrB,WAAW,MACT,EAAiB;SACf,GAAG;SACH,OAAO,EAAE,OAAO;QAClB,CAAC;QAEH,WAAW;OACZ,CAAA,CACE;;KACF;IAEF,CAAA,CAAA;;GAGP,kBAAC,GAAD;IACE,MAAM,MAAsB;IAC5B,eAAe,EAAqB,IAAI;IACxC,OAAM;IACN,aAAY;IACZ,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAqB,IAAI;KACxC,WAAU;KACX,UAAA;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;KAET,UAAA,IAAe,gBAAgB;IAC1B,CAAA,CACR,EAAA,CAAA;IAGJ,UAAA,kBAAC,KAAD;KAAG,WAAU;KAAb,UAAA;MAA2C;MACT;MAChC,kBAAC,QAAD;OAAM,WAAU;OACb,UAAA,GAAmB;MAChB,CAAA;MAAC;KAEN;;GACE,CAAA;GAGP,kBAAC,GAAD;IACE,MAAM,MAAuB;IAC7B,eAAe,EAAsB,IAAI;IACzC,OAAM;IACN,aAAY;IACZ,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;KACE,eAAe,EAAsB,IAAI;KACzC,WAAU;KACX,UAAA;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,SAAS;KACT,UAAU;KACV,WAAU;KAET,UAAA,IAAgB,gBAAgB;IAC3B,CAAA,CACR,EAAA,CAAA;IAGJ,UAAA,kBAAC,KAAD;KAAG,WAAU;KAAb,UAAA;MAA2C;MACf;MAC1B,kBAAC,QAAD;OAAM,WAAU;OACb,UAAA,GAAoB;MACjB,CAAA;MAAC;KAEN;;GACE,CAAA;EACG;;AAEhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamAnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamAnalyticsPage.tsx"],"sourcesContent":["import { BarChart3, Users, Clock, TrendingUp, Lightbulb } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n StatusBadge,\n LoadingSkeleton,\n BarsChart,\n DonutChart,\n RadialGauge,\n} from \"../../components\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetTeamWorkPatternSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nconst impactVariant = (\n impact?: string | null,\n): \"success\" | \"warning\" | \"info\" | \"muted\" => {\n const value = (impact ?? \"\").toLowerCase();\n if (value.includes(\"high\") || value.includes(\"critical\")) return \"warning\";\n if (value.includes(\"low\")) return \"muted\";\n if (value.includes(\"positive\") || value.includes(\"good\")) return \"success\";\n return \"info\";\n};\n\nexport function TeamAnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const analyticsActions = (\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => navigate(\"/team\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Team Overview\n </button>\n <button\n onClick={() => navigate(\"/team/members\")}\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 <Users className=\"h-4 w-4\" />\n View Members\n </button>\n </div>\n );\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n // Team-wide (BOFF-4037): aggregates every active workspace member, never\n // just the calling actor — see workPatternSummary for the self-scoped\n // equivalent used by the personal dashboard.\n const { data: patternData, loading: patternLoading } =\n useGetTeamWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const pattern = patternData?.teamWorkPatternSummary;\n\n if (statsLoading || patternLoading) {\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\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 team analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = stats?.totalHours ?? 0;\n const billableHours = stats?.billableHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const billablePercent =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const avgFocus = pattern?.averageFocusHours ?? 0;\n const avgMeeting = pattern?.averageMeetingHours ?? 0;\n const avgProductivity = pattern?.averageProductivity ?? 0;\n const insights = pattern?.insights ?? [];\n\n // Aggregate team composition — never individual — from the fetched stats.\n const distributionData = [\n {\n label: \"Logged\",\n billable: Math.round(billableHours * 10) / 10,\n nonBillable: Math.round(nonBillableHours * 10) / 10,\n },\n ];\n\n // Focus vs meeting balance from the work-pattern summary (avg per person).\n const balanceData = [\n { name: \"Deep Work / Focus\", value: Math.round(avgFocus * 10) / 10 },\n { name: \"Meetings\", value: Math.round(avgMeeting * 10) / 10 },\n ];\n\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\n purpose=\"Compare how your team works — hours by member, productive days, focus versus meeting time — so you can rebalance workload, recognise top contributors, and fix the patterns that slow everyone down.\"\n actions={analyticsActions}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Team Hours\"\n value={formatHrs(Math.round(totalHours))}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Billable Hours\"\n value={formatHrs(Math.round(billableHours))}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={{ value: billablePercent, label: \"billable rate\" }}\n />\n <StatCard\n title=\"Avg Productivity\"\n value={`${Math.round(avgProductivity)}%`}\n icon={<BarChart3 className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Meeting Load\"\n value={formatHrs(avgMeeting)}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"Avg / person — the true cost of meetings\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Team Time Distribution\"\n description=\"All data aggregate, not individual.\"\n >\n <BarsChart\n data={distributionData}\n xKey=\"label\"\n orientation=\"horizontal\"\n stacked\n height={150}\n categoryWidth={72}\n valueFormat={formatHrs}\n series={[\n { key: \"billable\", label: \"Billable\" },\n { key: \"nonBillable\", label: \"Non-billable\" },\n ]}\n emptyLabel=\"No logged hours yet\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {billablePercent}% of {formatHrs(Math.round(totalHours))} logged\n is billable.\n </p>\n </ChartCard>\n\n <ChartCard\n title=\"Focus vs Meeting Balance\"\n description=\"Average deep-work versus meeting time per person.\"\n >\n <DonutChart\n data={balanceData}\n centerValue={Math.round((avgFocus + avgMeeting) * 10) / 10}\n centerLabel=\"Avg / person\"\n valueFormat={formatHrs}\n height={220}\n emptyLabel=\"No work-pattern data yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Team Productivity\"\n description=\"Aggregate productivity score across the team.\"\n >\n <RadialGauge\n value={Math.round(avgProductivity)}\n min={0}\n max={100}\n label=\"Productivity\"\n sublabel={\n pattern?.mostProductiveDay\n ? `Peak day: ${pattern.mostProductiveDay}`\n : undefined\n }\n format={(v) => `${Math.round(v)}%`}\n height={220}\n />\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {pattern?.mostProductiveDay && (\n <StatusBadge\n variant=\"success\"\n label={`Most productive: ${pattern.mostProductiveDay}`}\n />\n )}\n {pattern?.leastProductiveDay && (\n <StatusBadge\n variant=\"muted\"\n label={`Quietest: ${pattern.leastProductiveDay}`}\n />\n )}\n </div>\n </ChartCard>\n\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <Lightbulb className=\"h-5 w-5 text-primary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Team Insights\n </h3>\n </div>\n {insights.length === 0 ? (\n <div className=\"flex h-40 items-center justify-center rounded-lg border border-border-subtle bg-bg-sunken/30 text-center\">\n <p className=\"max-w-xs text-sm text-text-secondary\">\n Insights appear here as your team logs more work patterns.\n </p>\n </div>\n ) : (\n <ul className=\"space-y-3\">\n {insights.map(\n (insight: (typeof insights)[number], index: number) => (\n <li\n key={`${insight.type}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n {insight.impact && (\n <StatusBadge\n variant={impactVariant(insight.impact)}\n label={insight.impact}\n />\n )}\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-2 text-xs text-primary\">\n {insight.recommendation}\n </p>\n )}\n </li>\n ),\n )}\n </ul>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamAnalyticsPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamAnalyticsPage.tsx"],"sourcesContent":["import { BarChart3, Users, Clock, TrendingUp, Lightbulb } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n StatusBadge,\n LoadingSkeleton,\n BarsChart,\n DonutChart,\n RadialGauge,\n} from \"../../components\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetTimeEntryStatsQuery,\n useGetTeamWorkPatternSummaryQuery,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nconst impactVariant = (\n impact?: string | null,\n): \"success\" | \"warning\" | \"info\" | \"muted\" => {\n const value = (impact ?? \"\").toLowerCase();\n if (value.includes(\"high\") || value.includes(\"critical\")) return \"warning\";\n if (value.includes(\"low\")) return \"muted\";\n if (value.includes(\"positive\") || value.includes(\"good\")) return \"success\";\n return \"info\";\n};\n\nexport function TeamAnalyticsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const analyticsActions = (\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => navigate(\"/team\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Team Overview\n </button>\n <button\n onClick={() => navigate(\"/team/members\")}\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 <Users className=\"h-4 w-4\" />\n View Members\n </button>\n </div>\n );\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetTimeEntryStatsQuery({\n skip: !workspaceId,\n });\n\n // Team-wide (BOFF-4037): aggregates every active workspace member, never\n // just the calling actor — see workPatternSummary for the self-scoped\n // equivalent used by the personal dashboard.\n const { data: patternData, loading: patternLoading } =\n useGetTeamWorkPatternSummaryQuery({\n skip: !workspaceId,\n });\n\n const stats = statsData?.timeEntryStats;\n const pattern = patternData?.teamWorkPatternSummary;\n\n if (statsLoading || patternLoading) {\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (statsError) {\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\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 team analytics. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalHours = stats?.totalHours ?? 0;\n const billableHours = stats?.billableHours ?? 0;\n const nonBillableHours = Math.max(0, totalHours - billableHours);\n const billablePercent =\n totalHours > 0 ? Math.round((billableHours / totalHours) * 100) : 0;\n\n const avgFocus = pattern?.averageFocusHours ?? 0;\n const avgMeeting = pattern?.averageMeetingHours ?? 0;\n const avgProductivity = pattern?.averageProductivity ?? 0;\n const insights = pattern?.insights ?? [];\n\n // Aggregate team composition — never individual — from the fetched stats.\n const distributionData = [\n {\n label: \"Logged\",\n billable: Math.round(billableHours * 10) / 10,\n nonBillable: Math.round(nonBillableHours * 10) / 10,\n },\n ];\n\n // Focus vs meeting balance from the work-pattern summary (avg per person).\n const balanceData = [\n { name: \"Deep Work / Focus\", value: Math.round(avgFocus * 10) / 10 },\n { name: \"Meetings\", value: Math.round(avgMeeting * 10) / 10 },\n ];\n\n return (\n <PageLayout\n title=\"Team Analytics\"\n description=\"Team performance insights and metrics\"\n purpose=\"Compare how your team works — hours by member, productive days, focus versus meeting time — so you can rebalance workload, recognise top contributors, and fix the patterns that slow everyone down.\"\n actions={analyticsActions}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Team Hours\"\n value={formatHrs(Math.round(totalHours))}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Billable Hours\"\n value={formatHrs(Math.round(billableHours))}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n trend={{ value: billablePercent, label: \"billable rate\" }}\n />\n <StatCard\n title=\"Avg Productivity\"\n value={`${Math.round(avgProductivity)}%`}\n icon={<BarChart3 className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n <StatCard\n title=\"Meeting Load\"\n value={formatHrs(avgMeeting)}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"Avg / person — the true cost of meetings\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Team Time Distribution\"\n description=\"All data aggregate, not individual.\"\n >\n <BarsChart\n data={distributionData}\n xKey=\"label\"\n orientation=\"horizontal\"\n stacked\n height={150}\n categoryWidth={72}\n valueFormat={formatHrs}\n series={[\n { key: \"billable\", label: \"Billable\" },\n { key: \"nonBillable\", label: \"Non-billable\" },\n ]}\n emptyLabel=\"No logged hours yet\"\n />\n <p className=\"mt-2 text-xs text-text-secondary\">\n {billablePercent}% of {formatHrs(Math.round(totalHours))} logged\n is billable.\n </p>\n </ChartCard>\n\n <ChartCard\n title=\"Focus vs Meeting Balance\"\n description=\"Average deep-work versus meeting time per person.\"\n >\n <DonutChart\n data={balanceData}\n centerValue={Math.round((avgFocus + avgMeeting) * 10) / 10}\n centerLabel=\"Avg / person\"\n valueFormat={formatHrs}\n height={220}\n emptyLabel=\"No work-pattern data yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Team Productivity\"\n description=\"Aggregate productivity score across the team.\"\n >\n <RadialGauge\n value={Math.round(avgProductivity)}\n min={0}\n max={100}\n label=\"Productivity\"\n sublabel={\n pattern?.mostProductiveDay\n ? `Peak day: ${pattern.mostProductiveDay}`\n : undefined\n }\n format={(v) => `${Math.round(v)}%`}\n height={220}\n />\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n {pattern?.mostProductiveDay && (\n <StatusBadge\n variant=\"success\"\n label={`Most productive: ${pattern.mostProductiveDay}`}\n />\n )}\n {pattern?.leastProductiveDay && (\n <StatusBadge\n variant=\"muted\"\n label={`Quietest: ${pattern.leastProductiveDay}`}\n />\n )}\n </div>\n </ChartCard>\n\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <Lightbulb className=\"h-5 w-5 text-primary\" />\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Team Insights\n </h3>\n </div>\n {insights.length === 0 ? (\n <div className=\"flex h-40 items-center justify-center rounded-lg border border-border-subtle bg-bg-sunken/30 text-center\">\n <p className=\"max-w-xs text-sm text-text-secondary\">\n Insights appear here as your team logs more work patterns.\n </p>\n </div>\n ) : (\n <ul className=\"space-y-3\">\n {insights.map(\n (insight: (typeof insights)[number], index: number) => (\n <li\n key={`${insight.type}-${index}`}\n className=\"rounded-lg border border-border-subtle bg-bg-surface/40 p-3\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {insight.title}\n </p>\n {insight.impact && (\n <StatusBadge\n variant={impactVariant(insight.impact)}\n label={insight.impact}\n />\n )}\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {insight.description}\n </p>\n {insight.recommendation && (\n <p className=\"mt-2 text-xs text-primary\">\n {insight.recommendation}\n </p>\n )}\n </li>\n ),\n )}\n </ul>\n )}\n </GlassCard>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMembersPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamMembersPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Activity, FolderKanban, Clock } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n EmptyState,\n LoadingSkeleton,\n Modal,\n BarsChart,\n} from \"../../components\";\nimport { formatHours, getInitials } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useListTimeEntriesQuery,\n type TimeEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nexport function TeamMembersPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const [inviteInfoOpen, setInviteInfoOpen] = useState(false);\n\n const inviteButton = (\n <button\n onClick={() => setInviteInfoOpen(true)}\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 Invite Member\n </button>\n );\n\n const { data, loading, error } = useListTimeEntriesQuery({\n variables: { pagination: { limit: 100 } },\n skip: !workspaceId,\n });\n\n const entries = data?.timeEntries?.items ?? [];\n\n // Group time entries by project to show team activity (member data comes from groups module)\n const projectSummary = entries.reduce<\n Record<string, { name: string; totalHours: number; entries: number }>\n >(\n (\n acc: Record<\n string,\n { name: string; totalHours: number; entries: number }\n >,\n entry: TimeEntryFieldsFragment,\n ) => {\n const projectName = entry.project?.name ?? \"Unassigned\";\n const projectId = entry.projectId ?? \"unassigned\";\n if (!acc[projectId]) {\n acc[projectId] = { name: projectName, totalHours: 0, entries: 0 };\n }\n acc[projectId].totalHours += (entry.duration ?? 0) / 3600;\n acc[projectId].entries += 1;\n return acc;\n },\n {},\n );\n\n const summaryList = Object.entries(projectSummary)\n .map(([id, info]) => ({\n id,\n ...(info as { name: string; totalHours: number; entries: number }),\n }))\n .sort((a, b) => b.totalHours - a.totalHours);\n\n const grandTotalHours = summaryList.reduce(\n (sum, project) => sum + project.totalHours,\n 0,\n );\n\n const hoursByProject = summaryList.slice(0, 8).map((project) => ({\n name: project.name,\n hours: Math.round(project.totalHours * 10) / 10,\n }));\n\n if (loading) {\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\n actions={inviteButton}\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\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 team data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\n purpose=\"Understand how effort is distributed across your team and projects — use it to spot who's overloaded, where time is concentrated, and when to invite or reassign people.\"\n actions={inviteButton}\n >\n <div className=\"space-y-6\">\n <div className=\"rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-xs text-status-warning-text\">\n Team member data is managed by the groups module. Showing time entry\n activity by project.\n </div>\n\n {summaryList.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No time entry data available\"\n description=\"Once your team starts tracking time, their activity by project shows up here.\"\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Active Projects\"\n value={String(summaryList.length)}\n icon={<FolderKanban className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tracked Hours\"\n value={formatHrs(Math.round(grandTotalHours))}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Time Entries\"\n value={String(entries.length)}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n </div>\n\n <ChartCard\n title=\"Hours by Project\"\n description=\"Where the team's tracked time is concentrated.\"\n >\n <BarsChart\n data={hoursByProject}\n xKey=\"name\"\n orientation=\"horizontal\"\n categoryWidth={132}\n height={Math.max(180, hoursByProject.length * 44)}\n valueFormat={formatHrs}\n series={[{ key: \"hours\", label: \"Hours\" }]}\n emptyLabel=\"No tracked hours yet\"\n />\n </ChartCard>\n\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {summaryList.map((project, index) => {\n const sharePct =\n grandTotalHours > 0\n ? Math.round((project.totalHours / grandTotalHours) * 100)\n : 0;\n return (\n <div\n key={project.id}\n className=\"p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-center justify-between gap-4\">\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-full bg-action-primary-bg/10 text-xs font-semibold uppercase text-primary\">\n {getInitials(project.name)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.entries} time entries\n {index === 0 && grandTotalHours > 0\n ? \" · top project\"\n : \"\"}\n </p>\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-medium tabular-nums text-text-primary\">\n {formatHours(project.totalHours)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {sharePct}% of tracked\n </p>\n </div>\n </div>\n <div className=\"mt-3 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg motion-safe:transition-[width] motion-safe:duration-500\"\n style={{ width: `${sharePct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n </GlassCard>\n </>\n )}\n </div>\n\n <Modal\n open={inviteInfoOpen}\n onClose={() => setInviteInfoOpen(false)}\n title=\"Managing Team Members\"\n description=\"How people are added to this workspace.\"\n footer={\n <>\n <button\n onClick={() => setInviteInfoOpen(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 Close\n </button>\n <button\n onClick={() => {\n setInviteInfoOpen(false);\n navigate(\"/team-activity\");\n }}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Activity className=\"h-4 w-4\" />\n View Team Activity\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Team membership and roles are managed centrally by your workspace\n People &amp; Groups administration, so invitations are sent from\n there. Once members join and start tracking time, their activity by\n project appears on this page and in the live team activity view.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamMembersPage.js","names":[],"sources":["../../../../src/timecampus/pages/team/TeamMembersPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Activity, FolderKanban, Clock } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport {\n PageLayout,\n StatCard,\n ChartCard,\n EmptyState,\n LoadingSkeleton,\n Modal,\n BarsChart,\n} from \"../../components\";\nimport { formatHours, getInitials } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useListTimeEntriesQuery,\n type TimeEntryFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst formatHrs = (v: number): string =>\n `${Number.isInteger(v) ? v : v.toFixed(1)}h`;\n\nexport function TeamMembersPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const [inviteInfoOpen, setInviteInfoOpen] = useState(false);\n\n const inviteButton = (\n <button\n onClick={() => setInviteInfoOpen(true)}\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 Invite Member\n </button>\n );\n\n const { data, loading, error } = useListTimeEntriesQuery({\n variables: { pagination: { limit: 100 } },\n skip: !workspaceId,\n });\n\n const entries = data?.timeEntries?.items ?? [];\n\n // Group time entries by project to show team activity (member data comes from groups module)\n const projectSummary = entries.reduce<\n Record<string, { name: string; totalHours: number; entries: number }>\n >(\n (\n acc: Record<\n string,\n { name: string; totalHours: number; entries: number }\n >,\n entry: TimeEntryFieldsFragment,\n ) => {\n const projectName = entry.project?.name ?? \"Unassigned\";\n const projectId = entry.projectId ?? \"unassigned\";\n if (!acc[projectId]) {\n acc[projectId] = { name: projectName, totalHours: 0, entries: 0 };\n }\n acc[projectId].totalHours += (entry.duration ?? 0) / 3600;\n acc[projectId].entries += 1;\n return acc;\n },\n {},\n );\n\n const summaryList = Object.entries(projectSummary)\n .map(([id, info]) => ({\n id,\n ...(info as { name: string; totalHours: number; entries: number }),\n }))\n .sort((a, b) => b.totalHours - a.totalHours);\n\n const grandTotalHours = summaryList.reduce(\n (sum, project) => sum + project.totalHours,\n 0,\n );\n\n const hoursByProject = summaryList.slice(0, 8).map((project) => ({\n name: project.name,\n hours: Math.round(project.totalHours * 10) / 10,\n }));\n\n if (loading) {\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\n actions={inviteButton}\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\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 team data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Team Members\"\n description=\"Manage team members and roles\"\n purpose=\"Understand how effort is distributed across your team and projects — use it to spot who's overloaded, where time is concentrated, and when to invite or reassign people.\"\n actions={inviteButton}\n >\n <div className=\"space-y-6\">\n <div className=\"rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-xs text-status-warning-text\">\n Team member data is managed by the groups module. Showing time entry\n activity by project.\n </div>\n\n {summaryList.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No time entry data available\"\n description=\"Once your team starts tracking time, their activity by project shows up here.\"\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n featured\n title=\"Active Projects\"\n value={String(summaryList.length)}\n icon={<FolderKanban className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tracked Hours\"\n value={formatHrs(Math.round(grandTotalHours))}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Time Entries\"\n value={String(entries.length)}\n icon={<Activity className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-success-bg text-status-success-text\"\n />\n </div>\n\n <ChartCard\n title=\"Hours by Project\"\n description=\"Where the team's tracked time is concentrated.\"\n >\n <BarsChart\n data={hoursByProject}\n xKey=\"name\"\n orientation=\"horizontal\"\n categoryWidth={132}\n height={Math.max(180, hoursByProject.length * 44)}\n valueFormat={formatHrs}\n series={[{ key: \"hours\", label: \"Hours\" }]}\n emptyLabel=\"No tracked hours yet\"\n />\n </ChartCard>\n\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {summaryList.map((project, index) => {\n const sharePct =\n grandTotalHours > 0\n ? Math.round((project.totalHours / grandTotalHours) * 100)\n : 0;\n return (\n <div\n key={project.id}\n className=\"p-4 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-center justify-between gap-4\">\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-full bg-action-primary-bg/10 text-xs font-semibold uppercase text-primary\">\n {getInitials(project.name)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.entries} time entries\n {index === 0 && grandTotalHours > 0\n ? \" · top project\"\n : \"\"}\n </p>\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-medium tabular-nums text-text-primary\">\n {formatHours(project.totalHours)}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {sharePct}% of tracked\n </p>\n </div>\n </div>\n <div className=\"mt-3 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg motion-safe:transition-[width] motion-safe:duration-500\"\n style={{ width: `${sharePct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n </GlassCard>\n </>\n )}\n </div>\n\n <Modal\n open={inviteInfoOpen}\n onClose={() => setInviteInfoOpen(false)}\n title=\"Managing Team Members\"\n description=\"How people are added to this workspace.\"\n footer={\n <>\n <button\n onClick={() => setInviteInfoOpen(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 Close\n </button>\n <button\n onClick={() => {\n setInviteInfoOpen(false);\n navigate(\"/team-activity\");\n }}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Activity className=\"h-4 w-4\" />\n View Team Activity\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Team membership and roles are managed centrally by your workspace\n People &amp; Groups administration, so invitations are sent from\n there. Once members join and start tracking time, their activity by\n project appears on this page and in the live team activity view.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}