@burdenoff/microfe-planmagnet 2026.928.4 → 2026.928.6

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 (226) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +130 -97
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +256 -156
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
  8. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
  9. package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
  10. package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
  11. package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
  12. package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
  13. package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
  14. package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
  15. package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
  16. package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
  17. package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
  18. package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
  19. package/dist/planmagnet/board/BulkActions.d.ts +11 -0
  20. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
  21. package/dist/planmagnet/board/BulkActions.js +182 -0
  22. package/dist/planmagnet/board/BulkActions.js.map +1 -0
  23. package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
  24. package/dist/planmagnet/board/ProjectBoard.js +289 -254
  25. package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
  26. package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
  27. package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
  28. package/dist/planmagnet/budgets/BudgetControls.js +470 -0
  29. package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
  30. package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
  31. package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
  32. package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
  33. package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
  34. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
  35. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
  36. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
  37. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
  38. package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
  39. package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
  40. package/dist/planmagnet/budgets/budgetMath.js +49 -0
  41. package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
  42. package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
  43. package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
  44. package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
  45. package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
  46. package/dist/planmagnet/controls/CompetitorControls.js +15 -15
  47. package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
  48. package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
  49. package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
  50. package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
  51. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
  52. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
  53. package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
  54. package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
  55. package/dist/planmagnet/controls/FeatureControls.js +54 -54
  56. package/dist/planmagnet/controls/ObjectiveControls.js +15 -15
  57. package/dist/planmagnet/controls/Pickers.d.ts +7 -0
  58. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  59. package/dist/planmagnet/controls/Pickers.js +58 -20
  60. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  61. package/dist/planmagnet/controls/PortfolioControls.js +7 -7
  62. package/dist/planmagnet/controls/ProgramControls.js +19 -19
  63. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  64. package/dist/planmagnet/controls/ProjectControls.js +54 -47
  65. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  66. package/dist/planmagnet/controls/ReleaseControls.js +7 -7
  67. package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
  68. package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
  69. package/dist/planmagnet/controls/RiskControls.js +346 -0
  70. package/dist/planmagnet/controls/RiskControls.js.map +1 -0
  71. package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
  72. package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
  73. package/dist/planmagnet/controls/TemplateControls.js +55 -0
  74. package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
  75. package/dist/planmagnet/controls/WorkItemControls.d.ts +6 -2
  76. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  77. package/dist/planmagnet/controls/WorkItemControls.js +9 -9
  78. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  79. package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
  80. package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
  81. package/dist/planmagnet/create/CreateDialogs.js +455 -116
  82. package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
  83. package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
  84. package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
  85. package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
  86. package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
  87. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
  88. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
  89. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
  90. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
  91. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
  92. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
  93. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
  94. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
  95. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
  96. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
  97. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
  98. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
  99. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  100. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -3
  101. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  102. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +5 -5
  103. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +9 -9
  104. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +6 -6
  105. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +4 -4
  106. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +6 -6
  107. package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +5 -5
  108. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
  109. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
  110. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
  111. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
  112. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
  113. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
  114. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
  115. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
  116. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
  117. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
  118. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
  119. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
  120. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
  121. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
  122. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
  123. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
  124. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +10 -10
  125. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
  126. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  127. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +20 -10
  128. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  129. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
  130. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +13 -8
  131. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
  132. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  133. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +76 -56
  134. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  135. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  136. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +372 -203
  137. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  138. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  139. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
  140. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  141. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
  142. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
  143. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
  144. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
  145. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
  146. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
  147. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
  148. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
  149. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  150. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +168 -122
  151. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  152. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
  153. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
  154. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  155. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
  156. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  157. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  158. package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
  159. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  160. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
  161. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
  162. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
  163. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
  164. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
  165. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
  166. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
  167. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
  168. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
  169. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
  170. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
  171. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
  172. package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
  173. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +288 -91
  174. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
  175. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts +2 -0
  176. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -0
  177. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +217 -0
  178. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -0
  179. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts +2 -0
  180. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts.map +1 -0
  181. package/dist/planmagnet/pages/TeamTeamEditPagePage.js +211 -0
  182. package/dist/planmagnet/pages/TeamTeamEditPagePage.js.map +1 -0
  183. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts +2 -0
  184. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts.map +1 -0
  185. package/dist/planmagnet/pages/TeamTeamsListPagePage.js +118 -0
  186. package/dist/planmagnet/pages/TeamTeamsListPagePage.js.map +1 -0
  187. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
  188. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
  189. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
  190. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
  191. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
  192. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
  193. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
  194. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
  195. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
  196. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  197. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
  198. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  199. package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
  200. package/dist/planmagnet/sprints/SprintControls.d.ts +15 -3
  201. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  202. package/dist/planmagnet/sprints/SprintControls.js +293 -190
  203. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  204. package/dist/planmagnet/team/TeamControls.d.ts +31 -0
  205. package/dist/planmagnet/team/TeamControls.d.ts.map +1 -0
  206. package/dist/planmagnet/team/TeamControls.js +407 -0
  207. package/dist/planmagnet/team/TeamControls.js.map +1 -0
  208. package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
  209. package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
  210. package/dist/planmagnet/time/TaskTimePanel.js +104 -0
  211. package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
  212. package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
  213. package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
  214. package/dist/planmagnet/time/TimeEntryControls.js +338 -0
  215. package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
  216. package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
  217. package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
  218. package/dist/planmagnet/time/TimesheetControls.js +244 -0
  219. package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
  220. package/dist/planmagnet/time/timeShared.d.ts +29 -0
  221. package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
  222. package/dist/planmagnet/time/timeShared.js +34 -0
  223. package/dist/planmagnet/time/timeShared.js.map +1 -0
  224. package/dist/planmagnet/utils/rowNavigation.js +5 -2
  225. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  226. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SprintControls.js","names":[],"sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Checkbox,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Play, Plus, X, XCircle } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\n\n/**\n * Sprint lifecycle + membership controls.\n *\n * The buttons mirror the service's transition table exactly\n * (wspace-planmagnet-svc sprint.service.ts `TRANSITIONS`):\n * start PLANNING -> ACTIVE\n * complete ACTIVE | REVIEW -> COMPLETED\n * cancel PLANNING|ACTIVE|REVIEW -> CANCELLED\n * A COMPLETED or CANCELLED sprint is finished: no transitions, and its work\n * items can no longer be added or removed. The server enforces all of this;\n * the UI only hides what would be refused, and still surfaces a refusal (a\n * concurrent change can make a shown button stale).\n */\n\nexport type SprintStatus = 'PLANNING' | 'ACTIVE' | 'REVIEW' | 'COMPLETED' | 'CANCELLED';\nexport type SprintAction = 'start' | 'complete' | 'cancel';\n\nconst ALLOWED: Record<SprintAction, readonly SprintStatus[]> = {\n start: ['PLANNING'],\n complete: ['ACTIVE', 'REVIEW'],\n cancel: ['PLANNING', 'ACTIVE', 'REVIEW'],\n};\n\nexport function sprintActionsFor(status: string): SprintAction[] {\n return (Object.keys(ALLOWED) as SprintAction[]).filter((action) =>\n (ALLOWED[action] as readonly string[]).includes(status)\n );\n}\n\nexport function isSprintFinished(status: string): boolean {\n return status === 'COMPLETED' || status === 'CANCELLED';\n}\n\n/** Every query that renders sprint status or membership. */\nconst SPRINT_QUERIES = ['GetSprints', 'GetSprintDetail', 'GetSprintForEdit', 'GetDashboardData'];\n\nconst START_SPRINT = gql`\n mutation SprintControlsStart($id: ID!) {\n planmagnetStartSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst COMPLETE_SPRINT = gql`\n mutation SprintControlsComplete($id: ID!) {\n planmagnetCompleteSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst CANCEL_SPRINT = gql`\n mutation SprintControlsCancel($id: ID!) {\n planmagnetCancelSprint(id: $id) {\n id\n status\n }\n }\n`;\n\nexport function SprintLifecycleActions({\n sprintId,\n sprintName,\n status,\n}: {\n sprintId: string;\n sprintName: string;\n status: string;\n}) {\n const options = { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true };\n const [start, startState] = useMutation(START_SPRINT, options);\n const [complete, completeState] = useMutation(COMPLETE_SPRINT, options);\n const [cancel, cancelState] = useMutation(CANCEL_SPRINT, options);\n const [error, setError] = useState<string | null>(null);\n\n const busy = startState.loading || completeState.loading || cancelState.loading;\n const actions = sprintActionsFor(status);\n // A refused transition still refetches; if the sprint turned out to be\n // finished there are no buttons left, but the reason must stay visible.\n if (actions.length === 0) return error ? <ErrorLine message={error} /> : null;\n\n const run = async (mutate: typeof start) => {\n setError(null);\n try {\n await throwOnError(mutate({ variables: { id: sprintId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be updated.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\" data-testid=\"sprint-lifecycle-actions\">\n <div className=\"flex gap-2\">\n {actions.includes('start') && (\n <Button size=\"sm\" onClick={() => run(start)} disabled={busy}>\n <Play className=\"mr-1 h-4 w-4\" />\n Start sprint\n </Button>\n )}\n {actions.includes('complete') && (\n <ConfirmButton\n label=\"Complete sprint\"\n icon={<CheckCircle2 className=\"mr-1 h-4 w-4\" />}\n title={`Complete ${sprintName}?`}\n description=\"The sprint is closed and its work items can no longer be changed from it. This cannot be undone.\"\n confirmLabel=\"Complete sprint\"\n disabled={busy}\n onConfirm={() => run(complete)}\n />\n )}\n {actions.includes('cancel') && (\n <ConfirmButton\n label=\"Cancel sprint\"\n variant=\"outline\"\n icon={<XCircle className=\"mr-1 h-4 w-4\" />}\n title={`Cancel ${sprintName}?`}\n description=\"The sprint is abandoned and cannot be restarted. Its work items stay in it for the record. This cannot be undone.\"\n confirmLabel=\"Cancel sprint\"\n destructive\n disabled={busy}\n onConfirm={() => run(cancel)}\n />\n )}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n// ─── Work items in the sprint ──────────────────────────────────────────────\n\nconst ADD_ITEMS = gql`\n mutation SprintControlsAddItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetAddWorkItemsToSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst REMOVE_ITEMS = gql`\n mutation SprintControlsRemoveItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetRemoveWorkItemsFromSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst PROJECT_WORK_ITEMS = gql`\n query SprintControlsCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n totalCount\n items {\n id\n key\n title\n status\n storyPoints\n sprintId\n }\n }\n }\n`;\n\nexport interface SprintWorkItem {\n id: string;\n key: string;\n title?: string | null;\n status: string;\n storyPoints: number | null;\n}\n\ninterface CandidateItem extends SprintWorkItem {\n sprintId: string | null;\n}\n\nconst statusLabel = (status: string) => status.toLowerCase().replace(/_/g, ' ');\n\nexport function SprintWorkItemsPanel({\n sprintId,\n projectId,\n status,\n items,\n}: {\n sprintId: string;\n projectId: string;\n status: string;\n items: SprintWorkItem[];\n}) {\n const finished = isSprintFinished(status);\n const [remove, removeState] = useMutation(REMOVE_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (workItemId: string) => {\n setError(null);\n setRemovingId(workItemId);\n try {\n await throwOnError(\n remove({ variables: { id: sprintId, input: { workItemIds: [workItemId] } } })\n );\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work item could not be removed.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"sprint-work-items\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {items.length} work item{items.length === 1 ? '' : 's'}\n </p>\n {!finished && (\n <AddWorkItemsDialog\n sprintId={sprintId}\n projectId={projectId}\n memberIds={items.map((i) => i.id)}\n />\n )}\n </div>\n {finished && (\n <p className=\"text-xs text-muted-foreground\">\n This sprint is {statusLabel(status)}; its work items can no longer be changed.\n </p>\n )}\n {error && <ErrorLine message={error} />}\n {items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No work items in this sprint yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {items.map((item) => (\n <li\n key={item.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <span className=\"min-w-0 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n <span className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"secondary\" className=\"text-xs capitalize\">\n {statusLabel(item.status)}\n </Badge>\n <span className=\"text-xs text-muted-foreground\">{item.storyPoints ?? 0} pts</span>\n {!finished && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Remove ${item.key} from sprint`}\n disabled={removeState.loading}\n onClick={() => handleRemove(item.id)}\n >\n {removingId === item.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n )}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddWorkItemsDialog({\n sprintId,\n projectId,\n memberIds,\n}: {\n sprintId: string;\n projectId: string;\n memberIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search.trim());\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetWorkItems: { totalCount: number; items: CandidateItem[] };\n }>(PROJECT_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n // Members are excluded on the SERVER (BOFF-7984): taking 100 and dropping members afterwards could say\n // \"every work item is already in this sprint\" while others existed, and the \"first 100 of N\" counted members.\n filter: {\n projectId,\n excludeIds: memberIds,\n ...(deferredSearch ? { search: deferredSearch } : {}),\n },\n },\n skip: !open || !workspaceId,\n fetchPolicy: 'network-only',\n });\n const [add, addState] = useMutation(ADD_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const candidates = (data?.planmagnetWorkItems.items ?? []).filter(\n (item) => item.sprintId !== sprintId\n );\n const totalCount = data?.planmagnetWorkItems.totalCount ?? 0;\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setSearch('');\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await throwOnError(\n add({ variables: { id: sprintId, input: { workItemIds: [...selected] } } })\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work items could not be added.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add work items\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add work items</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Work items from this sprint&apos;s project. An item already in another sprint moves\n here.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by title\"\n aria-label=\"Search work items\"\n />\n <div className=\"max-h-80 overflow-y-auto space-y-1\" data-testid=\"sprint-add-candidates\">\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {deferredSearch\n ? 'No matching work items.'\n : 'Every work item in this project is already in this sprint.'}\n </p>\n )}\n {candidates.map((item) => {\n const inputId = `sprint-add-${item.id}`;\n return (\n <label\n key={item.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(item.id)}\n onCheckedChange={(checked) => toggle(item.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n {item.sprintId && (\n <span className=\"text-xs text-muted-foreground shrink-0\">in another sprint</span>\n )}\n <Badge variant=\"secondary\" className=\"text-xs capitalize shrink-0\">\n {statusLabel(item.status)}\n </Badge>\n </label>\n );\n })}\n {totalCount > 100 && (\n <p className=\"text-xs text-muted-foreground pt-2\">\n Showing the first 100 of {totalCount}. Search to narrow the list.\n </p>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={handleAdd}\n disabled={selected.size === 0 || addState.loading}\n data-testid=\"sprint-add-confirm\"\n >\n {addState.loading\n ? 'Adding…'\n : `Add ${selected.size} work item${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── New sprint ─────────────────────────────────────────────────────────────\n\nconst CREATE_SPRINT = gql`\n mutation SprintControlsCreate($input: PlanmagnetCreateSprintInput!) {\n planmagnetCreateSprint(input: $input) {\n id\n }\n }\n`;\nconst SPRINT_PROJECTS = gql`\n query SprintControlsProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n }\n }\n`;\n\nconst EMPTY_FORM = { name: '', projectId: '', startDate: '', endDate: '', goal: '', capacity: '' };\n\nexport function NewSprintDialog({ onCreated }: { onCreated: (sprintId: string) => void }) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n const {\n data: projectsData,\n loading: projectsLoading,\n error: projectsError,\n refetch: refetchProjects,\n } = useQuery<{\n planmagnetProjects: { items: Array<{ id: string; name: string }> };\n }>(SPRINT_PROJECTS, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const projects = projectsData?.planmagnetProjects.items ?? [];\n const projectId = form.projectId || projects[0]?.id || '';\n\n const [create, { loading }] = useMutation<{ planmagnetCreateSprint: { id: string } }>(\n CREATE_SPRINT,\n { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true }\n );\n\n const capacity = form.capacity.trim() === '' ? undefined : Number(form.capacity);\n const invalidCapacity = capacity !== undefined && (!Number.isInteger(capacity) || capacity < 0);\n const invalidDates = Boolean(form.startDate && form.endDate && form.endDate < form.startDate);\n const canSubmit =\n form.name.trim() !== '' && projectId !== '' && !invalidCapacity && !invalidDates && !loading;\n\n const handleCreate = async () => {\n setError(null);\n try {\n const result = await throwOnError(\n create({\n variables: {\n input: {\n name: form.name.trim(),\n projectId,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n goal: form.goal.trim() || undefined,\n capacity,\n },\n },\n })\n );\n const id = result.data?.planmagnetCreateSprint.id;\n setForm(EMPTY_FORM);\n setOpen(false);\n if (id) onCreated(id);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be created.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\">\n <Plus className=\"mr-1 h-4 w-4\" />\n New sprint\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New sprint</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n The sprint starts in Planning. Start it when it is ready.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"new-sprint-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"new-sprint-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"new-sprint-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <select\n id=\"new-sprint-project\"\n className={field}\n value={projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n {projects.length === 0 && (\n <option value=\"\">\n {projectsLoading && !projectsData\n ? 'Loading projects…'\n : projectsError\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n {/* C2 (BOFF-7833): loading, failure and \"no projects\" are told apart. */}\n {projectsError && (\n <p role=\"alert\" className=\"mt-1 flex items-center gap-2 text-xs text-destructive\">\n {projectsError.message}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-2 text-xs\"\n onClick={() => void refetchProjects()}\n >\n Retry\n </Button>\n </p>\n )}\n {!projectsLoading && !projectsError && projectsData && projects.length === 0 && (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Create a project first (Projects → New project).\n </p>\n )}\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"new-sprint-start\" className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id=\"new-sprint-start\"\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"new-sprint-end\" className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id=\"new-sprint-end\"\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor=\"new-sprint-capacity\" className=\"text-sm font-medium\">\n Capacity (story points)\n </label>\n <Input\n id=\"new-sprint-capacity\"\n type=\"number\"\n min={0}\n className=\"mt-1\"\n value={form.capacity}\n onChange={(e) => setForm({ ...form, capacity: e.target.value })}\n />\n </div>\n {invalidCapacity && <ErrorLine message=\"Capacity must be a whole number, 0 or more.\" />}\n <div>\n <label htmlFor=\"new-sprint-goal\" className=\"text-sm font-medium\">\n Goal\n </label>\n <Input\n id=\"new-sprint-goal\"\n className=\"mt-1\"\n value={form.goal}\n onChange={(e) => setForm({ ...form, goal: e.target.value })}\n />\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button onClick={handleCreate} disabled={!canSubmit} data-testid=\"new-sprint-submit\">\n {loading ? 'Creating…' : 'Create sprint'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SprintControls.js","names":[],"sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"sourcesContent":["import { useDeferredValue, useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Checkbox,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Play, Plus, X, XCircle } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { TeamSelect } from '../controls/Pickers';\n\n/**\n * Sprint lifecycle + membership controls.\n *\n * The buttons mirror the service's transition table exactly\n * (wspace-planmagnet-svc sprint.service.ts `TRANSITIONS`):\n * start PLANNING -> ACTIVE\n * complete ACTIVE | REVIEW -> COMPLETED\n * cancel PLANNING|ACTIVE|REVIEW -> CANCELLED\n * A COMPLETED or CANCELLED sprint is finished: no transitions, and its work\n * items can no longer be added or removed. The server enforces all of this;\n * the UI only hides what would be refused, and still surfaces a refusal (a\n * concurrent change can make a shown button stale).\n */\n\nexport type SprintStatus = 'PLANNING' | 'ACTIVE' | 'REVIEW' | 'COMPLETED' | 'CANCELLED';\nexport type SprintAction = 'start' | 'complete' | 'cancel';\n\nconst ALLOWED: Record<SprintAction, readonly SprintStatus[]> = {\n start: ['PLANNING'],\n complete: ['ACTIVE', 'REVIEW'],\n cancel: ['PLANNING', 'ACTIVE', 'REVIEW'],\n};\n\nexport function sprintActionsFor(status: string): SprintAction[] {\n return (Object.keys(ALLOWED) as SprintAction[]).filter((action) =>\n (ALLOWED[action] as readonly string[]).includes(status)\n );\n}\n\nexport function isSprintFinished(status: string): boolean {\n return status === 'COMPLETED' || status === 'CANCELLED';\n}\n\n/** Every query that renders sprint status or membership. */\nconst SPRINT_QUERIES = ['GetSprints', 'GetSprintDetail', 'GetSprintForEdit', 'GetDashboardData'];\n\nconst START_SPRINT = gql`\n mutation SprintControlsStart($id: ID!) {\n planmagnetStartSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst COMPLETE_SPRINT = gql`\n mutation SprintControlsComplete($id: ID!) {\n planmagnetCompleteSprint(id: $id) {\n id\n status\n }\n }\n`;\nconst CANCEL_SPRINT = gql`\n mutation SprintControlsCancel($id: ID!) {\n planmagnetCancelSprint(id: $id) {\n id\n status\n }\n }\n`;\n\nexport function SprintLifecycleActions({\n sprintId,\n sprintName,\n status,\n}: {\n sprintId: string;\n sprintName: string;\n status: string;\n}) {\n const options = { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true };\n const [start, startState] = useMutation(START_SPRINT, options);\n const [complete, completeState] = useMutation(COMPLETE_SPRINT, options);\n const [cancel, cancelState] = useMutation(CANCEL_SPRINT, options);\n const [error, setError] = useState<string | null>(null);\n\n const busy = startState.loading || completeState.loading || cancelState.loading;\n const actions = sprintActionsFor(status);\n // A refused transition still refetches; if the sprint turned out to be\n // finished there are no buttons left, but the reason must stay visible.\n if (actions.length === 0) return error ? <ErrorLine message={error} /> : null;\n\n const run = async (mutate: typeof start) => {\n setError(null);\n try {\n await throwOnError(mutate({ variables: { id: sprintId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be updated.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\" data-testid=\"sprint-lifecycle-actions\">\n <div className=\"flex gap-2\">\n {actions.includes('start') && (\n <Button size=\"sm\" onClick={() => run(start)} disabled={busy}>\n <Play className=\"mr-1 h-4 w-4\" />\n Start sprint\n </Button>\n )}\n {actions.includes('complete') && (\n <ConfirmButton\n label=\"Complete sprint\"\n icon={<CheckCircle2 className=\"mr-1 h-4 w-4\" />}\n title={`Complete ${sprintName}?`}\n description=\"The sprint is closed and its work items can no longer be changed from it. This cannot be undone.\"\n confirmLabel=\"Complete sprint\"\n disabled={busy}\n onConfirm={() => run(complete)}\n />\n )}\n {actions.includes('cancel') && (\n <ConfirmButton\n label=\"Cancel sprint\"\n variant=\"outline\"\n icon={<XCircle className=\"mr-1 h-4 w-4\" />}\n title={`Cancel ${sprintName}?`}\n description=\"The sprint is abandoned and cannot be restarted. Its work items stay in it for the record. This cannot be undone.\"\n confirmLabel=\"Cancel sprint\"\n destructive\n disabled={busy}\n onConfirm={() => run(cancel)}\n />\n )}\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n// ─── Work items in the sprint ──────────────────────────────────────────────\n\nconst ADD_ITEMS = gql`\n mutation SprintControlsAddItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetAddWorkItemsToSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst REMOVE_ITEMS = gql`\n mutation SprintControlsRemoveItems($id: ID!, $input: PlanmagnetSprintWorkItemsInput!) {\n planmagnetRemoveWorkItemsFromSprint(id: $id, input: $input) {\n id\n }\n }\n`;\nconst PROJECT_WORK_ITEMS = gql`\n query SprintControlsCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n totalCount\n items {\n id\n key\n title\n status\n storyPoints\n sprintId\n projectId\n }\n }\n }\n`;\n\nexport interface SprintWorkItem {\n id: string;\n key: string;\n title?: string | null;\n status: string;\n storyPoints: number | null;\n}\n\ninterface CandidateItem extends SprintWorkItem {\n sprintId: string | null;\n projectId: string;\n}\n\nconst statusLabel = (status: string) => status.toLowerCase().replace(/_/g, ' ');\n\nexport function SprintWorkItemsPanel({\n sprintId,\n eligibleProjectIds,\n projectNames,\n status,\n items,\n}: {\n sprintId: string;\n /**\n * The project(s) this sprint may draw work items from (BOFF-8044). A project-scoped sprint\n * always has exactly one; a team-scoped sprint has the team's own live member projects (which\n * can be zero if the team currently has none, or more than one for a real cross-project team) —\n * never the whole workspace. See AddWorkItemsDialog for how a length > 1 is queried.\n */\n eligibleProjectIds: string[];\n /** Project id -> name, for labelling candidates when more than one project is eligible. */\n projectNames?: Record<string, string>;\n status: string;\n items: SprintWorkItem[];\n}) {\n const finished = isSprintFinished(status);\n const [remove, removeState] = useMutation(REMOVE_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (workItemId: string) => {\n setError(null);\n setRemovingId(workItemId);\n try {\n await throwOnError(\n remove({ variables: { id: sprintId, input: { workItemIds: [workItemId] } } })\n );\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work item could not be removed.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"sprint-work-items\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {items.length} work item{items.length === 1 ? '' : 's'}\n </p>\n {!finished && eligibleProjectIds.length > 0 && (\n <AddWorkItemsDialog\n sprintId={sprintId}\n eligibleProjectIds={eligibleProjectIds}\n projectNames={projectNames}\n memberIds={items.map((i) => i.id)}\n />\n )}\n </div>\n {finished && (\n <p className=\"text-xs text-muted-foreground\">\n This sprint is {statusLabel(status)}; its work items can no longer be changed.\n </p>\n )}\n {!finished && eligibleProjectIds.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n This team has no projects yet — add a project to the team before adding work items.\n </p>\n )}\n {error && <ErrorLine message={error} />}\n {items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No work items in this sprint yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {items.map((item) => (\n <li\n key={item.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <span className=\"min-w-0 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n <span className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"secondary\" className=\"text-xs capitalize\">\n {statusLabel(item.status)}\n </Badge>\n <span className=\"text-xs text-muted-foreground\">{item.storyPoints ?? 0} pts</span>\n {!finished && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Remove ${item.key} from sprint`}\n disabled={removeState.loading}\n onClick={() => handleRemove(item.id)}\n >\n {removingId === item.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n )}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddWorkItemsDialog({\n sprintId,\n eligibleProjectIds,\n projectNames,\n memberIds,\n}: {\n sprintId: string;\n eligibleProjectIds: string[];\n projectNames?: Record<string, string>;\n memberIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search.trim());\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n // `key` identifies the inputs a result was fetched for; `loading` below is derived by comparing\n // it against the CURRENT inputs, rather than a separate flag flipped synchronously at effect\n // start (react-hooks/set-state-in-effect) — the only setState call this effect makes happens\n // after the `await`, once the real result (or error) is in hand.\n const requestKey = `${eligibleProjectIds.join(',')}|${memberIds.join(',')}|${deferredSearch}`;\n const [fetchState, setFetchState] = useState<{\n key: string | null;\n items: CandidateItem[];\n fetchedCount: number;\n totalCount: number;\n error: string | null;\n }>({ key: null, items: [], fetchedCount: 0, totalCount: 0, error: null });\n\n // A project-scoped sprint always has exactly one eligible project; a team-scoped sprint has the\n // team's own live member projects (BOFF-8044), which can be more than one for a real\n // cross-project team. PlanmagnetWorkItemFilter only accepts a single projectId, so a\n // multi-project team runs one query per eligible project and merges the results — never the\n // whole workspace, and never a single arbitrary project's worth of candidates for a team sprint.\n useEffect(() => {\n if (!open || !workspaceId || eligibleProjectIds.length === 0) return;\n let cancelled = false;\n (async () => {\n try {\n const pages = await Promise.all(\n eligibleProjectIds.map((eligibleProjectId) =>\n client.query<{\n planmagnetWorkItems: { totalCount: number; items: CandidateItem[] };\n }>({\n query: PROJECT_WORK_ITEMS,\n variables: {\n workspaceId,\n // Members are excluded on the SERVER (BOFF-7984): taking 100 and dropping members\n // afterwards could say \"every work item is already in this sprint\" while others\n // existed, and the \"first 100 of N\" counted members.\n filter: {\n projectId: eligibleProjectId,\n excludeIds: memberIds,\n ...(deferredSearch ? { search: deferredSearch } : {}),\n },\n },\n fetchPolicy: 'network-only',\n })\n )\n );\n if (cancelled) return;\n setFetchState({\n key: requestKey,\n items: pages.flatMap((page) => page.data?.planmagnetWorkItems.items ?? []),\n fetchedCount: pages.reduce(\n (sum, page) => sum + (page.data?.planmagnetWorkItems.items.length ?? 0),\n 0\n ),\n totalCount: pages.reduce(\n (sum, page) => sum + (page.data?.planmagnetWorkItems.totalCount ?? 0),\n 0\n ),\n error: null,\n });\n } catch (e) {\n if (cancelled) return;\n setFetchState({\n key: requestKey,\n items: [],\n fetchedCount: 0,\n totalCount: 0,\n error: e instanceof Error ? e.message : 'The work items could not be loaded.',\n });\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [open, workspaceId, eligibleProjectIds, memberIds, deferredSearch, client, requestKey]);\n\n const [add, addState] = useMutation(ADD_ITEMS, {\n refetchQueries: SPRINT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const candidates = fetchState.items.filter((item) => item.sprintId !== sprintId);\n const { totalCount, fetchedCount } = fetchState;\n const loading = open && fetchState.key !== requestKey;\n const showProjectLabel = eligibleProjectIds.length > 1 && !!projectNames;\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setSearch('');\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await throwOnError(\n add({ variables: { id: sprintId, input: { workItemIds: [...selected] } } })\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The work items could not be added.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add work items\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add work items</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {eligibleProjectIds.length > 1\n ? \"Work items from this team's projects. An item already in another sprint moves here.\"\n : \"Work items from this sprint's project. An item already in another sprint moves here.\"}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <Input\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by title\"\n aria-label=\"Search work items\"\n />\n <div className=\"max-h-80 overflow-y-auto space-y-1\" data-testid=\"sprint-add-candidates\">\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && fetchState.error && (\n <p className=\"text-sm text-destructive py-4 text-center\">{fetchState.error}</p>\n )}\n {!loading && !fetchState.error && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {deferredSearch\n ? 'No matching work items.'\n : eligibleProjectIds.length > 1\n ? \"Every work item in this team's projects is already in this sprint.\"\n : 'Every work item in this project is already in this sprint.'}\n </p>\n )}\n {candidates.map((item) => {\n const inputId = `sprint-add-${item.id}`;\n return (\n <label\n key={item.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(item.id)}\n onCheckedChange={(checked) => toggle(item.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">\n <span className=\"text-muted-foreground mr-2\">{item.key}</span>\n {item.title}\n </span>\n {showProjectLabel && (\n <span className=\"text-xs text-muted-foreground shrink-0\">\n {projectNames?.[item.projectId] ?? item.projectId}\n </span>\n )}\n {item.sprintId && (\n <span className=\"text-xs text-muted-foreground shrink-0\">in another sprint</span>\n )}\n <Badge variant=\"secondary\" className=\"text-xs capitalize shrink-0\">\n {statusLabel(item.status)}\n </Badge>\n </label>\n );\n })}\n {fetchedCount < totalCount && (\n <p className=\"text-xs text-muted-foreground pt-2\">\n Showing the first {fetchedCount} of {totalCount}. Search to narrow the list.\n </p>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={handleAdd}\n disabled={selected.size === 0 || addState.loading}\n data-testid=\"sprint-add-confirm\"\n >\n {addState.loading\n ? 'Adding…'\n : `Add ${selected.size} work item${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── New sprint ─────────────────────────────────────────────────────────────\n\nconst CREATE_SPRINT = gql`\n mutation SprintControlsCreate($input: PlanmagnetCreateSprintInput!) {\n planmagnetCreateSprint(input: $input) {\n id\n }\n }\n`;\nconst SPRINT_PROJECTS = gql`\n query SprintControlsProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n }\n }\n`;\n\nconst EMPTY_FORM = { name: '', projectId: '', startDate: '', endDate: '', goal: '', capacity: '' };\n\nexport type SprintScope = 'project' | 'team';\n\nexport function NewSprintDialog({\n onCreated,\n initialScope,\n initialTeamId,\n}: {\n onCreated: (sprintId: string) => void;\n /** BOFF-8044: preset for a caller that already knows the scope, e.g. the Team detail page. */\n initialScope?: SprintScope;\n initialTeamId?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(EMPTY_FORM);\n // A sprint's scope is PROJECT xor TEAM (BOFF-8044) — never both, never neither.\n const [scope, setScope] = useState<SprintScope>(initialScope ?? 'project');\n const [teamId, setTeamId] = useState(initialTeamId ?? '');\n const [error, setError] = useState<string | null>(null);\n\n const {\n data: projectsData,\n loading: projectsLoading,\n error: projectsError,\n refetch: refetchProjects,\n } = useQuery<{\n planmagnetProjects: { items: Array<{ id: string; name: string }> };\n }>(SPRINT_PROJECTS, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId || scope !== 'project',\n });\n const projects = projectsData?.planmagnetProjects.items ?? [];\n const projectId = form.projectId || projects[0]?.id || '';\n\n const [create, { loading }] = useMutation<{ planmagnetCreateSprint: { id: string } }>(\n CREATE_SPRINT,\n { refetchQueries: SPRINT_QUERIES, awaitRefetchQueries: true }\n );\n\n const capacity = form.capacity.trim() === '' ? undefined : Number(form.capacity);\n const invalidCapacity = capacity !== undefined && (!Number.isInteger(capacity) || capacity < 0);\n const invalidDates = Boolean(form.startDate && form.endDate && form.endDate < form.startDate);\n const scopeChosen = scope === 'project' ? projectId !== '' : teamId !== '';\n const canSubmit =\n form.name.trim() !== '' && scopeChosen && !invalidCapacity && !invalidDates && !loading;\n\n const handleCreate = async () => {\n setError(null);\n try {\n const result = await throwOnError(\n create({\n variables: {\n input: {\n name: form.name.trim(),\n ...(scope === 'team' ? { teamId } : { projectId }),\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n goal: form.goal.trim() || undefined,\n capacity,\n },\n },\n })\n );\n const id = result.data?.planmagnetCreateSprint.id;\n setForm(EMPTY_FORM);\n setOpen(false);\n if (id) onCreated(id);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The sprint could not be created.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\">\n <Plus className=\"mr-1 h-4 w-4\" />\n New sprint\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New sprint</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n The sprint starts in Planning. Start it when it is ready.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"new-sprint-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"new-sprint-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Belongs to</span>\n {/* BOFF-8044: a sprint is either a project's own sprint, or a cross-project team sprint —\n never both. Switching clears the other side's selection so a stale value never ships. */}\n <div className=\"mt-1 flex gap-2\" role=\"radiogroup\" aria-label=\"Sprint scope\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant={scope === 'project' ? 'default' : 'outline'}\n aria-pressed={scope === 'project'}\n data-testid=\"new-sprint-scope-project\"\n onClick={() => setScope('project')}\n >\n A project\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={scope === 'team' ? 'default' : 'outline'}\n aria-pressed={scope === 'team'}\n data-testid=\"new-sprint-scope-team\"\n onClick={() => setScope('team')}\n >\n A team\n </Button>\n </div>\n </div>\n {scope === 'project' ? (\n <div>\n <label htmlFor=\"new-sprint-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <select\n id=\"new-sprint-project\"\n className={field}\n value={projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n {projects.length === 0 && (\n <option value=\"\">\n {projectsLoading && !projectsData\n ? 'Loading projects…'\n : projectsError\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n {/* C2 (BOFF-7833): loading, failure and \"no projects\" are told apart. */}\n {projectsError && (\n <p role=\"alert\" className=\"mt-1 flex items-center gap-2 text-xs text-destructive\">\n {projectsError.message}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-6 px-2 text-xs\"\n onClick={() => void refetchProjects()}\n >\n Retry\n </Button>\n </p>\n )}\n {!projectsLoading && !projectsError && projectsData && projects.length === 0 && (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Create a project first (Projects → New project).\n </p>\n )}\n </div>\n ) : (\n <div>\n <label htmlFor=\"new-sprint-team\" className=\"text-sm font-medium\">\n Team\n </label>\n <div className=\"mt-1\">\n <TeamSelect id=\"new-sprint-team\" value={teamId} onChange={setTeamId} />\n </div>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Only work items from the team&apos;s own projects can join this sprint.\n </p>\n </div>\n )}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"new-sprint-start\" className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id=\"new-sprint-start\"\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"new-sprint-end\" className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id=\"new-sprint-end\"\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor=\"new-sprint-capacity\" className=\"text-sm font-medium\">\n Capacity (story points)\n </label>\n <Input\n id=\"new-sprint-capacity\"\n type=\"number\"\n min={0}\n className=\"mt-1\"\n value={form.capacity}\n onChange={(e) => setForm({ ...form, capacity: e.target.value })}\n />\n </div>\n {invalidCapacity && <ErrorLine message=\"Capacity must be a whole number, 0 or more.\" />}\n <div>\n <label htmlFor=\"new-sprint-goal\" className=\"text-sm font-medium\">\n Goal\n </label>\n <Input\n id=\"new-sprint-goal\"\n className=\"mt-1\"\n value={form.goal}\n onChange={(e) => setForm({ ...form, goal: e.target.value })}\n />\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button onClick={handleCreate} disabled={!canSubmit} data-testid=\"new-sprint-submit\">\n {loading ? 'Creating…' : 'Create sprint'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Team controls (BOFF-8044). A Team is a first-class, cross-project entity: members, a lead, capacity, and the
3
+ * projects it can draw team-sprint work from. Adding a member or a project here writes ONLY `team_members` /
4
+ * `team_projects` rows — it never touches RBAC, never assigns a platform role, and grants no project access
5
+ * (the owner decision this ticket is built to). Access to a project's own data stays governed purely by each
6
+ * actor's own PlanMagnet workspace-role permissions, exactly as before a team existed.
7
+ */
8
+ export interface TeamMemberRef {
9
+ id: string;
10
+ actorId: string;
11
+ }
12
+ export interface TeamProjectRef {
13
+ id: string;
14
+ name: string;
15
+ key: string;
16
+ }
17
+ /** Every query that lists teams or shows a team's membership/projects. */
18
+ export declare const TEAM_QUERIES: string[];
19
+ export declare function TeamHeaderActions({ teamId, name }: {
20
+ teamId: string;
21
+ name: string;
22
+ }): import("react/jsx-runtime").JSX.Element;
23
+ export declare function TeamMembersPanel({ teamId, members, }: {
24
+ teamId: string;
25
+ members: TeamMemberRef[];
26
+ }): import("react/jsx-runtime").JSX.Element;
27
+ export declare function TeamProjectsPanel({ teamId, projects, }: {
28
+ teamId: string;
29
+ projects: TeamProjectRef[];
30
+ }): import("react/jsx-runtime").JSX.Element;
31
+ //# sourceMappingURL=TeamControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/team/TeamControls.tsx"],"names":[],"mappings":"AAuBA;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,0EAA0E;AAC1E,eAAO,MAAM,YAAY,UAAgC,CAAC;AAU1D,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,2CA6CnF;AAmBD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,GACR,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B,2CA8DA;AA+KD,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B,2CAkEA"}
@@ -0,0 +1,407 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { PersonAvatar as n, PersonName as r } from "../people/People.js";
3
+ import { LEAVES_PAGE_CONTEXT as i, throwOnError as a } from "../utils/mutationResult.js";
4
+ import { ConfirmButton as o, ErrorLine as s } from "../controls/ConfirmButton.js";
5
+ import { detailPath as c } from "../utils/rowNavigation.js";
6
+ import { useDeferredValue as l, useEffect as u, useState as d } from "react";
7
+ import { Pencil as f, Plus as p, Trash2 as m, X as h } from "lucide-react";
8
+ import { gql as g } from "@apollo/client";
9
+ import { useMutation as _, useQuery as v } from "@apollo/client/react";
10
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
11
+ import { Button as x, Checkbox as S, Input as C, ResponsiveDialog as w, ResponsiveDialogContent as T, ResponsiveDialogDescription as E, ResponsiveDialogFooter as D, ResponsiveDialogHeader as O, ResponsiveDialogTitle as k, ResponsiveDialogTrigger as A } from "@burdenoff/fe-libs/ui";
12
+ import { useWorkspaceDirectory as j } from "@burdenoff/fe-libs/sharing";
13
+ //#region src/planmagnet/team/TeamControls.tsx
14
+ var M = ["GetTeams", "GetTeamDetail"], N = g`
15
+ mutation TeamControlsDelete($id: ID!) {
16
+ planmagnetDeleteTeam(id: $id)
17
+ }
18
+ `;
19
+ function P({ teamId: e, name: n }) {
20
+ let r = t(), [l, u] = _(N, {
21
+ refetchQueries: M,
22
+ context: i
23
+ }), [p, h] = d(null), g = async () => {
24
+ h(null);
25
+ try {
26
+ await a(l({ variables: { id: e } })), r("/team/teams-page");
27
+ } catch (e) {
28
+ h(e instanceof Error ? e.message : "The team could not be deleted.");
29
+ }
30
+ };
31
+ return /* @__PURE__ */ b("div", {
32
+ className: "flex flex-col items-end gap-2",
33
+ children: [/* @__PURE__ */ b("div", {
34
+ className: "flex flex-wrap items-center justify-end gap-2",
35
+ children: [/* @__PURE__ */ b(x, {
36
+ size: "sm",
37
+ variant: "outline",
38
+ "data-testid": "team-edit",
39
+ onClick: () => r(c("/team/team-edit-page", e)),
40
+ children: [/* @__PURE__ */ y(f, { className: "mr-1 h-4 w-4" }), "Edit"]
41
+ }), /* @__PURE__ */ y(o, {
42
+ label: "Delete",
43
+ variant: "outline",
44
+ icon: /* @__PURE__ */ y(m, { className: "mr-1 h-4 w-4" }),
45
+ title: `Delete ${n}?`,
46
+ description: "The team is deleted. Its team-scoped sprints disappear from every list — the same way a deleted project's sprints do. Its projects and members are unaffected; nothing about them is deleted. This cannot be undone here.",
47
+ confirmLabel: "Delete team",
48
+ destructive: !0,
49
+ disabled: u.loading,
50
+ onConfirm: () => void g()
51
+ })]
52
+ }), p && /* @__PURE__ */ y(s, { message: p })]
53
+ });
54
+ }
55
+ var F = g`
56
+ mutation TeamControlsAddMembers($id: ID!, $input: PlanmagnetTeamMembersInput!) {
57
+ planmagnetAddTeamMembers(id: $id, input: $input) {
58
+ id
59
+ }
60
+ }
61
+ `, I = g`
62
+ mutation TeamControlsRemoveMembers($id: ID!, $input: PlanmagnetTeamMembersInput!) {
63
+ planmagnetRemoveTeamMembers(id: $id, input: $input) {
64
+ id
65
+ }
66
+ }
67
+ `;
68
+ function L({ teamId: e, members: t }) {
69
+ let [i, o] = _(I, {
70
+ refetchQueries: M,
71
+ awaitRefetchQueries: !0
72
+ }), [c, l] = d(null), [u, f] = d(null), p = async (t) => {
73
+ l(null), f(t);
74
+ try {
75
+ await a(i({ variables: {
76
+ id: e,
77
+ input: { actorIds: [t] }
78
+ } }));
79
+ } catch (e) {
80
+ l(e instanceof Error ? e.message : "The member could not be removed.");
81
+ } finally {
82
+ f(null);
83
+ }
84
+ };
85
+ return /* @__PURE__ */ b("div", {
86
+ className: "space-y-3",
87
+ "data-testid": "team-members",
88
+ children: [
89
+ /* @__PURE__ */ b("div", {
90
+ className: "flex items-center justify-between",
91
+ children: [/* @__PURE__ */ b("p", {
92
+ className: "text-sm text-muted-foreground",
93
+ children: [
94
+ t.length,
95
+ " member",
96
+ t.length === 1 ? "" : "s"
97
+ ]
98
+ }), /* @__PURE__ */ y(R, {
99
+ teamId: e,
100
+ existingActorIds: t.map((e) => e.actorId)
101
+ })]
102
+ }),
103
+ c && /* @__PURE__ */ y(s, { message: c }),
104
+ t.length === 0 ? /* @__PURE__ */ y("p", {
105
+ className: "text-sm text-muted-foreground text-center py-4",
106
+ children: "No members on this team yet."
107
+ }) : /* @__PURE__ */ y("ul", {
108
+ className: "space-y-1",
109
+ children: t.map((e) => /* @__PURE__ */ b("li", {
110
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
111
+ children: [/* @__PURE__ */ b("span", {
112
+ className: "flex items-center gap-2 min-w-0",
113
+ children: [/* @__PURE__ */ y(n, {
114
+ id: e.actorId,
115
+ className: "h-7 w-7 shrink-0 rounded-full bg-primary/10 flex items-center justify-center text-[10px] font-medium text-primary"
116
+ }), /* @__PURE__ */ y(r, {
117
+ id: e.actorId,
118
+ className: "truncate"
119
+ })]
120
+ }), /* @__PURE__ */ y(x, {
121
+ size: "sm",
122
+ variant: "ghost",
123
+ "aria-label": "Remove member",
124
+ disabled: o.loading,
125
+ onClick: () => p(e.actorId),
126
+ children: u === e.actorId ? "…" : /* @__PURE__ */ y(h, { className: "h-4 w-4" })
127
+ })]
128
+ }, e.id))
129
+ })
130
+ ]
131
+ });
132
+ }
133
+ function R({ teamId: t, existingActorIds: n }) {
134
+ let { workspaceId: r, authToken: i, apiGatewayUrl: o } = e(), [c, f] = d(!1), [m, h] = d(""), g = l(m.trim()), [v, N] = d(/* @__PURE__ */ new Set()), [P, I] = d(null), L = j({
135
+ workspaceId: r ?? "",
136
+ authToken: i ?? void 0,
137
+ apiGatewayUrl: o ?? void 0
138
+ }), { search: R } = L;
139
+ u(() => {
140
+ R(g);
141
+ }, [g, R]);
142
+ let z = L.members.filter((e) => !n.includes(e.id)), [B, V] = _(F, {
143
+ refetchQueries: M,
144
+ awaitRefetchQueries: !0
145
+ }), H = (e, t) => N((n) => {
146
+ let r = new Set(n);
147
+ return t ? r.add(e) : r.delete(e), r;
148
+ }), U = () => {
149
+ N(/* @__PURE__ */ new Set()), h(""), I(null);
150
+ };
151
+ return /* @__PURE__ */ b(w, {
152
+ open: c,
153
+ onOpenChange: (e) => {
154
+ f(e), e || U();
155
+ },
156
+ children: [/* @__PURE__ */ y(A, {
157
+ asChild: !0,
158
+ children: /* @__PURE__ */ b(x, {
159
+ size: "sm",
160
+ variant: "outline",
161
+ "data-testid": "team-add-members",
162
+ children: [/* @__PURE__ */ y(p, { className: "mr-1 h-4 w-4" }), "Add members"]
163
+ })
164
+ }), /* @__PURE__ */ b(T, {
165
+ className: "max-w-lg",
166
+ children: [
167
+ /* @__PURE__ */ b(O, { children: [/* @__PURE__ */ y(k, { children: "Add members" }), /* @__PURE__ */ y(E, { children: "Adding someone to the team does not grant them access to the team's projects — project access is still controlled by each person's own workspace role." })] }),
168
+ /* @__PURE__ */ y(C, {
169
+ value: m,
170
+ onChange: (e) => h(e.target.value),
171
+ placeholder: "Search workspace members",
172
+ "aria-label": "Search workspace members"
173
+ }),
174
+ /* @__PURE__ */ b("div", {
175
+ className: "max-h-80 overflow-y-auto space-y-1",
176
+ "data-testid": "team-add-candidates",
177
+ children: [
178
+ L.loading && z.length === 0 && /* @__PURE__ */ y("p", {
179
+ className: "text-sm text-muted-foreground py-4 text-center",
180
+ children: "Loading…"
181
+ }),
182
+ !L.loading && z.length === 0 && /* @__PURE__ */ y("p", {
183
+ className: "text-sm text-muted-foreground py-4 text-center",
184
+ children: g ? "No matching members." : "Everyone in the workspace is already on this team."
185
+ }),
186
+ z.map((e) => {
187
+ let t = `team-add-member-${e.id}`;
188
+ return /* @__PURE__ */ b("label", {
189
+ htmlFor: t,
190
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
191
+ children: [/* @__PURE__ */ y(S, {
192
+ id: t,
193
+ checked: v.has(e.id),
194
+ onCheckedChange: (t) => H(e.id, t === !0)
195
+ }), /* @__PURE__ */ b("span", {
196
+ className: "min-w-0 flex-1 truncate",
197
+ children: [e.name, e.email && /* @__PURE__ */ b("span", {
198
+ className: "text-muted-foreground",
199
+ children: [" · ", e.email]
200
+ })]
201
+ })]
202
+ }, e.id);
203
+ }),
204
+ L.hasMore && /* @__PURE__ */ y("button", {
205
+ type: "button",
206
+ className: "w-full py-1 text-xs text-primary hover:underline",
207
+ onClick: L.fetchMore,
208
+ children: "Show more members"
209
+ })
210
+ ]
211
+ }),
212
+ P && /* @__PURE__ */ y(s, { message: P }),
213
+ /* @__PURE__ */ y(D, { children: /* @__PURE__ */ y(x, {
214
+ onClick: async () => {
215
+ I(null);
216
+ try {
217
+ await a(B({ variables: {
218
+ id: t,
219
+ input: { actorIds: [...v] }
220
+ } })), U(), f(!1);
221
+ } catch (e) {
222
+ I(e instanceof Error ? e.message : "The members could not be added.");
223
+ }
224
+ },
225
+ disabled: v.size === 0 || V.loading,
226
+ "data-testid": "team-add-members-confirm",
227
+ children: V.loading ? "Adding…" : `Add ${v.size} member${v.size === 1 ? "" : "s"}`
228
+ }) })
229
+ ]
230
+ })]
231
+ });
232
+ }
233
+ var z = g`
234
+ mutation TeamControlsAddProjects($id: ID!, $input: PlanmagnetTeamProjectsInput!) {
235
+ planmagnetAddTeamProjects(id: $id, input: $input) {
236
+ id
237
+ }
238
+ }
239
+ `, B = g`
240
+ mutation TeamControlsRemoveProjects($id: ID!, $input: PlanmagnetTeamProjectsInput!) {
241
+ planmagnetRemoveTeamProjects(id: $id, input: $input) {
242
+ id
243
+ }
244
+ }
245
+ `, V = g`
246
+ query TeamControlsCandidateProjects($workspaceId: ID!) {
247
+ planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {
248
+ items {
249
+ id
250
+ name
251
+ key
252
+ }
253
+ totalCount
254
+ }
255
+ }
256
+ `;
257
+ function H({ teamId: e, projects: n }) {
258
+ let r = t(), [i, o] = _(B, {
259
+ refetchQueries: M,
260
+ awaitRefetchQueries: !0
261
+ }), [l, u] = d(null), [f, p] = d(null), m = async (t) => {
262
+ u(null), p(t);
263
+ try {
264
+ await a(i({ variables: {
265
+ id: e,
266
+ input: { projectIds: [t] }
267
+ } }));
268
+ } catch (e) {
269
+ u(e instanceof Error ? e.message : "The project could not be removed.");
270
+ } finally {
271
+ p(null);
272
+ }
273
+ };
274
+ return /* @__PURE__ */ b("div", {
275
+ className: "space-y-3",
276
+ "data-testid": "team-projects",
277
+ children: [
278
+ /* @__PURE__ */ b("div", {
279
+ className: "flex items-center justify-between",
280
+ children: [/* @__PURE__ */ b("p", {
281
+ className: "text-sm text-muted-foreground",
282
+ children: [
283
+ n.length,
284
+ " project",
285
+ n.length === 1 ? "" : "s"
286
+ ]
287
+ }), /* @__PURE__ */ y(U, {
288
+ teamId: e,
289
+ existingProjectIds: n.map((e) => e.id)
290
+ })]
291
+ }),
292
+ l && /* @__PURE__ */ y(s, { message: l }),
293
+ n.length === 0 ? /* @__PURE__ */ y("p", {
294
+ className: "text-sm text-muted-foreground text-center py-4",
295
+ children: "No projects on this team yet. Team sprints can only draw work items from these."
296
+ }) : /* @__PURE__ */ y("ul", {
297
+ className: "space-y-1",
298
+ children: n.map((e) => /* @__PURE__ */ b("li", {
299
+ className: "flex items-center justify-between gap-2 border rounded-md p-2 text-sm",
300
+ children: [/* @__PURE__ */ b("button", {
301
+ type: "button",
302
+ className: "min-w-0 truncate text-left hover:underline",
303
+ onClick: () => r(c("/projects/project-detail-page", e.id)),
304
+ children: [/* @__PURE__ */ y("span", {
305
+ className: "text-muted-foreground mr-2",
306
+ children: e.key
307
+ }), e.name]
308
+ }), /* @__PURE__ */ y(x, {
309
+ size: "sm",
310
+ variant: "ghost",
311
+ "aria-label": `Remove ${e.name} from team`,
312
+ disabled: o.loading,
313
+ onClick: () => m(e.id),
314
+ children: f === e.id ? "…" : /* @__PURE__ */ y(h, { className: "h-4 w-4" })
315
+ })]
316
+ }, e.id))
317
+ })
318
+ ]
319
+ });
320
+ }
321
+ function U({ teamId: t, existingProjectIds: n }) {
322
+ let { workspaceId: r } = e(), [i, o] = d(!1), [c, l] = d(/* @__PURE__ */ new Set()), [u, f] = d(null), { data: m, loading: h } = v(V, {
323
+ variables: { workspaceId: r ?? "" },
324
+ skip: !i || !r
325
+ }), g = (m?.planmagnetProjects.items ?? []).filter((e) => !n.includes(e.id)), [C, j] = _(z, {
326
+ refetchQueries: M,
327
+ awaitRefetchQueries: !0
328
+ }), N = (e, t) => l((n) => {
329
+ let r = new Set(n);
330
+ return t ? r.add(e) : r.delete(e), r;
331
+ }), P = () => {
332
+ l(/* @__PURE__ */ new Set()), f(null);
333
+ };
334
+ return /* @__PURE__ */ b(w, {
335
+ open: i,
336
+ onOpenChange: (e) => {
337
+ o(e), e || P();
338
+ },
339
+ children: [/* @__PURE__ */ y(A, {
340
+ asChild: !0,
341
+ children: /* @__PURE__ */ b(x, {
342
+ size: "sm",
343
+ variant: "outline",
344
+ "data-testid": "team-add-projects",
345
+ children: [/* @__PURE__ */ y(p, { className: "mr-1 h-4 w-4" }), "Add projects"]
346
+ })
347
+ }), /* @__PURE__ */ b(T, {
348
+ className: "max-w-lg",
349
+ children: [
350
+ /* @__PURE__ */ b(O, { children: [/* @__PURE__ */ y(k, { children: "Add projects" }), /* @__PURE__ */ y(E, { children: "A team sprint can only hold work items from a project on this list." })] }),
351
+ /* @__PURE__ */ b("div", {
352
+ className: "max-h-80 overflow-y-auto space-y-1",
353
+ "data-testid": "team-add-project-candidates",
354
+ children: [
355
+ h && /* @__PURE__ */ y("p", {
356
+ className: "text-sm text-muted-foreground py-4 text-center",
357
+ children: "Loading…"
358
+ }),
359
+ !h && g.length === 0 && /* @__PURE__ */ y("p", {
360
+ className: "text-sm text-muted-foreground py-4 text-center",
361
+ children: "Every project in this workspace is already on this team."
362
+ }),
363
+ g.map((e) => {
364
+ let t = `team-add-project-${e.id}`;
365
+ return /* @__PURE__ */ b("label", {
366
+ htmlFor: t,
367
+ className: "flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted",
368
+ children: [/* @__PURE__ */ y(S, {
369
+ id: t,
370
+ checked: c.has(e.id),
371
+ onCheckedChange: (t) => N(e.id, t === !0)
372
+ }), /* @__PURE__ */ b("span", {
373
+ className: "min-w-0 flex-1 truncate",
374
+ children: [/* @__PURE__ */ y("span", {
375
+ className: "text-muted-foreground mr-2",
376
+ children: e.key
377
+ }), e.name]
378
+ })]
379
+ }, e.id);
380
+ })
381
+ ]
382
+ }),
383
+ u && /* @__PURE__ */ y(s, { message: u }),
384
+ /* @__PURE__ */ y(D, { children: /* @__PURE__ */ y(x, {
385
+ onClick: async () => {
386
+ f(null);
387
+ try {
388
+ await a(C({ variables: {
389
+ id: t,
390
+ input: { projectIds: [...c] }
391
+ } })), P(), o(!1);
392
+ } catch (e) {
393
+ f(e instanceof Error ? e.message : "The projects could not be added.");
394
+ }
395
+ },
396
+ disabled: c.size === 0 || j.loading,
397
+ "data-testid": "team-add-projects-confirm",
398
+ children: j.loading ? "Adding…" : `Add ${c.size} project${c.size === 1 ? "" : "s"}`
399
+ }) })
400
+ ]
401
+ })]
402
+ });
403
+ }
404
+ //#endregion
405
+ export { M as TEAM_QUERIES, P as TeamHeaderActions, L as TeamMembersPanel, H as TeamProjectsPanel };
406
+
407
+ //# sourceMappingURL=TeamControls.js.map