@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":"ProjectBoard.js","names":[],"sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"sourcesContent":["import { createContext, useContext, useState, type ReactNode } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight, LayoutGrid, Pencil, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\nimport {\n type ProjectWorkflow,\n allowedTargets,\n useProjectWorkflow,\n workflowLabel,\n} from '../hooks/useProjectWorkflow';\n\n/**\n * A project's board (P1.2). The server owns placement: each column's `cards`\n * follow the rule in wspace-planmagnet-svc board.service.ts `columnCardsWhere`\n * (placed column, else the first column mapped to the item's status), and\n * `unplacedCount` says how many items are on no column. Moving a card into a\n * column mapped to a status changes that status on the server, and a column's\n * WIP limit is enforced there too. This component only draws and asks.\n */\n\nconst STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\n/** The project's workflow for everything drawn on its board (BOFF-8043): status names and allowed moves. */\nconst BoardWorkflow = createContext<ProjectWorkflow | null>(null);\nconst useStatusName = () => {\n const workflow = useContext(BoardWorkflow);\n return (status: string) => workflowLabel(workflow, status);\n};\n\nconst statusLabel = (status: string) =>\n status.charAt(0) + status.slice(1).toLowerCase().replace(/_/g, ' ');\n\nconst CARDS_PER_COLUMN = 100;\n\nconst BOARDS = gql`\n query ProjectBoards($workspaceId: ID!, $projectId: String) {\n planmagnetBoards(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 20 }\n ) {\n items {\n id\n name\n isDefault\n }\n }\n }\n`;\n\n/** The next page of one column (P1.2: a column shows 100 cards and used to say only \"+N more\"). */\nconst COLUMN_PAGE = gql`\n query ProjectBoardColumnPage($boardId: ID!, $columnId: ID!, $after: String) {\n planmagnetBoardColumnCards(\n boardId: $boardId\n columnId: $columnId\n pagination: { first: 100, after: $after }\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n`;\n\nconst BOARD = gql`\n query ProjectBoard($id: ID!) {\n planmagnetBoard(id: $id) {\n id\n name\n unplacedCount\n columns {\n id\n name\n mappedStatus\n wipLimit\n sortOrder\n cards(pagination: { first: 100 }) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n }\n }\n`;\n\nconst MOVE_CARD = gql`\n mutation ProjectBoardMoveCard($boardId: ID!, $input: PlanmagnetMoveCardInput!) {\n planmagnetMoveCard(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\nconst CREATE_BOARD = gql`\n mutation ProjectBoardCreate($input: PlanmagnetCreateBoardInput!) {\n planmagnetCreateBoard(input: $input) {\n id\n }\n }\n`;\nconst CREATE_COLUMN = gql`\n mutation ProjectBoardCreateColumn($input: PlanmagnetCreateBoardColumnInput!) {\n planmagnetCreateBoardColumn(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_COLUMN = gql`\n mutation ProjectBoardUpdateColumn($id: ID!, $input: PlanmagnetUpdateBoardColumnInput!) {\n planmagnetUpdateBoardColumn(id: $id, input: $input) {\n id\n }\n }\n`;\nconst DELETE_COLUMN = gql`\n mutation ProjectBoardDeleteColumn($id: ID!) {\n planmagnetDeleteBoardColumn(id: $id)\n }\n`;\nconst REORDER_COLUMNS = gql`\n mutation ProjectBoardReorder($boardId: ID!, $input: PlanmagnetReorderColumnsInput!) {\n planmagnetReorderColumns(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\n\n/** Every query whose numbers a board change can move. */\nconst REFETCH = [\n 'ProjectBoard',\n 'ProjectBoards',\n 'GetWorkItems',\n 'GetWorkItemCounts',\n 'GetDashboardData',\n];\n\ninterface BoardCard {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n /** When it entered its current status; null when unknown (changed by a path that records no history). */\n statusSince: string | null;\n}\n\ninterface PageInfo {\n hasNextPage: boolean;\n endCursor: string | null;\n}\n\ninterface BoardColumn {\n id: string;\n name: string;\n mappedStatus: string | null;\n wipLimit: number | null;\n sortOrder: number;\n cards: { totalCount: number; pageInfo: PageInfo; items: BoardCard[] };\n}\n\ntype LaneBy = 'none' | 'assignee' | 'priority';\nconst PRIORITY_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NONE'];\nconst DAY_MS = 86_400_000;\n\n/** Whole days in the current status, or null when unknown. */\nexport function daysInStatus(statusSince: string | null, now: number): number | null {\n if (!statusSince) return null;\n return Math.max(0, Math.floor((now - new Date(statusSince).getTime()) / DAY_MS));\n}\n\n/** Card aging: quiet under a week, a warning from 7 days, stronger from 14. Semantic tokens only. */\nfunction ageClass(days: number): string {\n if (days >= 14) return 'text-destructive font-medium';\n if (days >= 7) return 'text-status-warning-text';\n return 'text-muted-foreground';\n}\n\nfunction laneOf(card: BoardCard, by: LaneBy): string {\n if (by === 'assignee') return card.assigneeId ?? '';\n if (by === 'priority') return card.priority || 'NONE';\n return '';\n}\n\ninterface BoardData {\n id: string;\n name: string;\n unplacedCount: number;\n columns: BoardColumn[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\n\nfunction ErrorLine({ message }: { message: string }) {\n return (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {message}\n </p>\n );\n}\n\nexport function ProjectBoard({\n projectId,\n projectName,\n}: {\n projectId: string;\n projectName: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n const [chosenBoardId, setChosenBoardId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n // Card age is read against the moment the board opened (Date.now() may not run during render).\n const [now] = useState(() => Date.now());\n const [laneBy, setLaneBy] = useState<LaneBy>('none');\n // Pages loaded past each column's first 100, keyed by the board data they extend: a refetch starts over.\n const [more, setMore] = useState<{\n key: unknown;\n byColumn: Record<string, { items: BoardCard[]; pageInfo: PageInfo }>;\n }>({\n key: null,\n byColumn: {},\n });\n const [loadingMore, setLoadingMore] = useState<string | null>(null);\n const workflow = useProjectWorkflow(projectId);\n\n const { data: boardsData, loading: boardsLoading } = useQuery<{\n planmagnetBoards: { items: Array<{ id: string; name: string; isDefault: boolean }> };\n }>(BOARDS, { variables: { workspaceId, projectId }, skip: !workspaceId });\n const boards = boardsData?.planmagnetBoards.items ?? [];\n const boardId =\n (chosenBoardId && boards.some((b) => b.id === chosenBoardId) ? chosenBoardId : null) ??\n boards.find((b) => b.isDefault)?.id ??\n boards[0]?.id ??\n null;\n\n const { data, loading } = useQuery<{ planmagnetBoard: BoardData | null }>(BOARD, {\n variables: { id: boardId },\n skip: !boardId,\n });\n const board = data?.planmagnetBoard ?? null;\n const extra = more.key === board ? more.byColumn : {};\n /** A column with every page loaded so far. */\n const cardsOf = (column: BoardColumn) => {\n const loaded = extra[column.id];\n return {\n items: loaded ? [...column.cards.items, ...loaded.items] : column.cards.items,\n pageInfo: loaded ? loaded.pageInfo : column.cards.pageInfo,\n totalCount: column.cards.totalCount,\n };\n };\n const loadMore = async (column: BoardColumn) => {\n const cursor = cardsOf(column).pageInfo.endCursor;\n if (!board || !cursor) return;\n setError(null);\n setLoadingMore(column.id);\n try {\n const res = await client.query<{\n planmagnetBoardColumnCards: { pageInfo: PageInfo; items: BoardCard[] };\n }>({\n query: COLUMN_PAGE,\n variables: { boardId: board.id, columnId: column.id, after: cursor },\n fetchPolicy: 'network-only',\n });\n const page = res.data?.planmagnetBoardColumnCards;\n if (!page) throw new Error(res.error?.message ?? 'The next cards could not be loaded.');\n const prior = extra[column.id]?.items ?? [];\n setMore({\n key: board,\n byColumn: {\n ...extra,\n [column.id]: { items: [...prior, ...page.items], pageInfo: page.pageInfo },\n },\n });\n } catch (e) {\n setError(messageOf(e, 'The next cards could not be loaded.'));\n } finally {\n setLoadingMore(null);\n }\n };\n\n const [createBoard, createBoardState] = useMutation<{ planmagnetCreateBoard: { id: string } }>(\n CREATE_BOARD,\n { refetchQueries: REFETCH, awaitRefetchQueries: true }\n );\n const [moveCard, moveState] = useMutation(MOVE_CARD, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n const [reorder] = useMutation(REORDER_COLUMNS, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(messageOf(e, fallback));\n }\n };\n\n const move = (workItemId: string, targetColumnId: string) =>\n run(\n () => moveCard({ variables: { boardId, input: { workItemId, targetColumnId } } }),\n 'The card could not be moved.'\n );\n\n const shiftColumn = (index: number, delta: -1 | 1) => {\n if (!board) return;\n const order = board.columns.map((c) => c.id);\n const target = index + delta;\n if (target < 0 || target >= order.length) return;\n [order[index], order[target]] = [order[target], order[index]];\n return run(\n () => reorder({ variables: { boardId, input: { columnIds: order } } }),\n 'The columns could not be reordered.'\n );\n };\n\n if (boardsLoading && !boardsData) {\n return <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>;\n }\n\n if (!boardId) {\n return (\n <Card>\n <CardContent className=\"p-8 text-center space-y-3\" data-testid=\"board-empty\">\n <LayoutGrid className=\"h-8 w-8 mx-auto text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">{projectName} has no board yet.</p>\n <Button\n size=\"sm\"\n disabled={createBoardState.loading}\n onClick={() =>\n run(\n () =>\n createBoard({\n variables: {\n input: {\n projectId,\n name: `${projectName} board`,\n type: 'KANBAN',\n isDefault: true,\n withDefaultColumns: true,\n },\n },\n }),\n 'The board could not be created.'\n )\n }\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n {createBoardState.loading ? 'Creating…' : 'Create board'}\n </Button>\n <p className=\"text-xs text-muted-foreground\">\n Starts with one column per status: Backlog, To Do, In Progress, In Review, Testing,\n Done.\n </p>\n {error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n }\n\n return (\n <BoardWorkflow.Provider value={workflow}>\n <div className=\"space-y-3\" data-testid=\"project-board\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {boards.length > 1 && (\n <select\n aria-label=\"Board\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={boardId}\n onChange={(e) => setChosenBoardId(e.target.value)}\n >\n {boards.map((b) => (\n <option key={b.id} value={b.id}>\n {b.name}\n </option>\n ))}\n </select>\n )}\n {board && (\n <label className=\"flex items-center gap-1 text-sm\">\n <span className=\"text-muted-foreground\">Swimlanes</span>\n <select\n aria-label=\"Swimlanes\"\n data-testid=\"board-lanes\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={laneBy}\n onChange={(e) => setLaneBy(e.target.value as LaneBy)}\n >\n <option value=\"none\">None</option>\n <option value=\"assignee\">By assignee</option>\n <option value=\"priority\">By priority</option>\n </select>\n </label>\n )}\n {board && board.unplacedCount > 0 && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"board-unplaced\">\n {board.unplacedCount} work item{board.unplacedCount === 1 ? ' is' : 's are'} not on\n this board: {board.unplacedCount === 1 ? 'its status has' : 'their status has'} no\n column.\n </p>\n )}\n {board && (\n <div className=\"ml-auto\">\n <ColumnDialog boardId={board.id} />\n </div>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n {loading && !board ? (\n <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>\n ) : board && laneBy !== 'none' ? (\n <SwimlaneBoard\n board={board}\n laneBy={laneBy}\n cardsOf={cardsOf}\n now={now}\n busy={moveState.loading}\n loadingMore={loadingMore}\n onLoadMore={(column) => void loadMore(column)}\n onMove={move}\n />\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {board?.columns.map((column, index) => (\n <BoardColumnView\n key={column.id}\n boardId={board.id}\n column={column}\n columns={board.columns}\n cards={cardsOf(column)}\n isFirst={index === 0}\n isLast={index === board.columns.length - 1}\n busy={moveState.loading}\n now={now}\n loadingMore={loadingMore === column.id}\n onLoadMore={() => void loadMore(column)}\n onMove={move}\n onShift={(delta) => shiftColumn(index, delta)}\n />\n ))}\n {board && board.columns.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">\n This board has no columns. Add one to start placing work.\n </p>\n )}\n </div>\n )}\n </div>\n </BoardWorkflow.Provider>\n );\n}\n\ntype ColumnCards = { items: BoardCard[]; pageInfo: PageInfo; totalCount: number };\n\n/** One card: title, key, points, assignee, age in status, and a keyboard \"Move…\" equal to drag-and-drop. */\nfunction CardView({\n card,\n column,\n columns,\n busy,\n now,\n onMove,\n}: {\n card: BoardCard;\n column: BoardColumn;\n columns: BoardColumn[];\n busy: boolean;\n now: number;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const navigate = usePlanMagnetNavigate();\n const workflow = useContext(BoardWorkflow);\n const days = daysInStatus(card.statusSince, now);\n // Only columns this card may move into: unmapped ones (no status change) and those whose status the workflow allows.\n const reachable = allowedTargets(workflow, card.status, STATUSES);\n const targets = columns.filter(\n (c) => c.id !== column.id && (!c.mappedStatus || reachable.includes(c.mappedStatus))\n );\n return (\n <li\n draggable\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className=\"rounded-md border bg-background p-2 space-y-1 cursor-grab\"\n data-testid=\"board-card\"\n >\n <button\n type=\"button\"\n className=\"text-left text-sm font-medium leading-tight hover:underline\"\n onClick={() => navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(card.id)}`)}\n >\n {card.title}\n </button>\n <div className=\"flex items-center justify-between text-[11px] text-muted-foreground\">\n <span>{card.key}</span>\n {/* Card aging (P1.2): how long it has had this status. Unknown when its status changed off the record. */}\n {days !== null && (\n <span\n data-testid=\"card-age\"\n className={ageClass(days)}\n title={`In ${workflowLabel(workflow, card.status)} since ${new Date(card.statusSince ?? '').toLocaleDateString()}`}\n >\n {days === 0 ? 'today' : `${days}d in status`}\n </span>\n )}\n <span>{card.storyPoints ?? 0} pts</span>\n </div>\n <div className=\"flex items-center justify-between gap-1 text-[11px] text-muted-foreground\">\n <span className=\"truncate\">\n {card.assigneeId ? <PersonName id={card.assigneeId} /> : 'Unassigned'}\n </span>\n {/* The same move as drag-and-drop, for keyboard and assistive tech. */}\n <select\n aria-label={`Move ${card.key} to column`}\n className=\"rounded border border-input bg-background px-1 py-0.5 text-[11px]\"\n value=\"\"\n disabled={busy}\n onChange={(e) => e.target.value && onMove(card.id, e.target.value)}\n >\n <option value=\"\">Move…</option>\n {targets.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n </div>\n </li>\n );\n}\n\n/** A drop target: dropping a card here moves it into `column`. */\nfunction DropZone({\n column,\n onMove,\n className,\n children,\n label,\n}: {\n column: BoardColumn;\n onMove: (workItemId: string, targetColumnId: string) => void;\n className: string;\n children: ReactNode;\n label: string;\n}) {\n const [dragOver, setDragOver] = useState(false);\n return (\n <section\n aria-label={label}\n className={`${className} ${dragOver ? 'ring-2 ring-primary' : ''}`}\n onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n const workItemId = e.dataTransfer.getData('text/plain');\n if (workItemId) onMove(workItemId, column.id);\n }}\n >\n {children}\n </section>\n );\n}\n\n/** \"Showing N of M — Show more\" for a column with cards past the ones loaded. */\nfunction MoreCards({\n cards,\n loading,\n onLoadMore,\n}: {\n cards: ColumnCards;\n loading: boolean;\n onLoadMore: () => void;\n}) {\n const hidden = cards.totalCount - cards.items.length;\n if (hidden <= 0 || !cards.pageInfo.hasNextPage) return null;\n return (\n <div className=\"px-2 pb-2 space-y-1\">\n <p className=\"text-xs text-muted-foreground\">\n Showing {cards.items.length} of {cards.totalCount}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"w-full\"\n data-testid=\"board-column-more\"\n disabled={loading}\n onClick={onLoadMore}\n >\n {loading ? 'Loading…' : `Show ${Math.min(hidden, CARDS_PER_COLUMN)} more`}\n </Button>\n </div>\n );\n}\n\n/**\n * Swimlanes (P1.2): rows by assignee or priority across the same columns. Moving a card changes its column (and the\n * status that column sets); its lane follows its assignee or priority, which are changed on the card itself.\n */\nfunction SwimlaneBoard({\n board,\n laneBy,\n cardsOf,\n now,\n busy,\n loadingMore,\n onLoadMore,\n onMove,\n}: {\n board: BoardData;\n laneBy: LaneBy;\n cardsOf: (column: BoardColumn) => ColumnCards;\n now: number;\n busy: boolean;\n loadingMore: string | null;\n onLoadMore: (column: BoardColumn) => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const perColumn = board.columns.map((column) => ({ column, cards: cardsOf(column) }));\n const lanes = [\n ...new Set(perColumn.flatMap(({ cards }) => cards.items.map((c) => laneOf(c, laneBy)))),\n ].sort((a, b) =>\n laneBy === 'priority'\n ? PRIORITY_ORDER.indexOf(a) - PRIORITY_ORDER.indexOf(b)\n : a === ''\n ? 1\n : b === ''\n ? -1\n : a.localeCompare(b)\n );\n const laneName = (lane: string) =>\n laneBy === 'priority' ? statusLabel(lane) : lane ? <PersonName id={lane} /> : 'Unassigned';\n return (\n <div className=\"overflow-x-auto pb-2 space-y-3\" data-testid=\"board-swimlanes\">\n <p className=\"text-xs text-muted-foreground\">\n Dragging moves a card between columns. Its lane follows its{' '}\n {laneBy === 'priority' ? 'priority' : 'assignee'}.\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0 px-2\">\n <h3 className=\"text-sm font-medium truncate\">\n {column.name}{' '}\n <span className=\"text-xs text-muted-foreground\">\n {column.wipLimit ? `${cards.totalCount} / ${column.wipLimit}` : cards.totalCount}\n </span>\n </h3>\n </div>\n ))}\n </div>\n {lanes.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">No cards on this board yet.</p>\n )}\n {lanes.map((lane) => (\n <div key={lane || 'none'} data-testid=\"board-lane\" className=\"space-y-1\">\n <p className=\"text-xs font-medium\">\n {laneName(lane)}{' '}\n <span className=\"text-muted-foreground\">\n (\n {perColumn.reduce(\n (n, { cards }) => n + cards.items.filter((c) => laneOf(c, laneBy) === lane).length,\n 0\n )}\n )\n </span>\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <DropZone\n key={column.id}\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <ul className=\"p-2 space-y-2 min-h-[48px]\">\n {cards.items\n .filter((c) => laneOf(c, laneBy) === lane)\n .map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={board.columns}\n busy={busy}\n now={now}\n onMove={onMove}\n />\n ))}\n </ul>\n </DropZone>\n ))}\n </div>\n </div>\n ))}\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0\">\n <MoreCards\n cards={cards}\n loading={loadingMore === column.id}\n onLoadMore={() => onLoadMore(column)}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nfunction BoardColumnView({\n boardId,\n column,\n columns,\n cards,\n isFirst,\n isLast,\n busy,\n now,\n loadingMore,\n onLoadMore,\n onMove,\n onShift,\n}: {\n boardId: string;\n column: BoardColumn;\n columns: BoardColumn[];\n cards: ColumnCards;\n isFirst: boolean;\n isLast: boolean;\n busy: boolean;\n now: number;\n loadingMore: boolean;\n onLoadMore: () => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n onShift: (delta: -1 | 1) => void;\n}) {\n const count = cards.totalCount;\n const atLimit = column.wipLimit != null && column.wipLimit > 0 && count >= column.wipLimit;\n const statusName = useStatusName();\n\n return (\n <DropZone\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <div data-testid={`board-column-${column.name}`}>\n <header className=\"p-2 border-b space-y-1\">\n <div className=\"flex items-center justify-between gap-1\">\n <h3 className=\"text-sm font-medium truncate\">{column.name}</h3>\n <span\n data-testid={`board-column-count-${column.name}`}\n className={`text-xs rounded-full px-1.5 ${atLimit ? 'bg-status-warning-subtle text-status-warning' : 'bg-muted'}`}\n title={column.wipLimit ? `WIP limit ${column.wipLimit}` : undefined}\n >\n {column.wipLimit ? `${count} / ${column.wipLimit}` : count}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-1\">\n <span className=\"text-[11px] text-muted-foreground\">\n {column.mappedStatus\n ? `Sets status: ${statusName(column.mappedStatus)}`\n : 'No status'}\n </span>\n <span className=\"flex\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column left`}\n disabled={isFirst}\n onClick={() => onShift(-1)}\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column right`}\n disabled={isLast}\n onClick={() => onShift(1)}\n >\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </Button>\n <ColumnDialog boardId={boardId} column={column} />\n </span>\n </div>\n </header>\n <ul className=\"p-2 space-y-2 min-h-[80px]\">\n {cards.items.map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={columns}\n busy={busy}\n now={now}\n onMove={onMove}\n />\n ))}\n {count === 0 && <li className=\"text-xs text-muted-foreground text-center py-4\">Empty</li>}\n </ul>\n <MoreCards cards={cards} loading={loadingMore} onLoadMore={onLoadMore} />\n </div>\n </DropZone>\n );\n}\n\nfunction ColumnDialog({ boardId, column }: { boardId: string; column?: BoardColumn }) {\n const editing = column !== undefined;\n const statusName = useStatusName();\n const [open, setOpen] = useState(false);\n const [name, setName] = useState(column?.name ?? '');\n const [mappedStatus, setMappedStatus] = useState(column?.mappedStatus ?? '');\n const [wip, setWip] = useState(column?.wipLimit ? String(column.wipLimit) : '');\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: REFETCH, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_COLUMN, options);\n const [update, updateState] = useMutation(UPDATE_COLUMN, options);\n const [remove, removeState] = useMutation(DELETE_COLUMN, options);\n const busy = createState.loading || updateState.loading || removeState.loading;\n\n const wipValue = wip.trim() === '' ? null : Number(wip);\n const invalidWip = wipValue !== null && (!Number.isInteger(wipValue) || wipValue < 1);\n const canSave = name.trim() !== '' && !invalidWip && !busy;\n\n const reset = () => {\n setName(column?.name ?? '');\n setMappedStatus(column?.mappedStatus ?? '');\n setWip(column?.wipLimit ? String(column.wipLimit) : '');\n setError(null);\n };\n\n const save = async () => {\n setError(null);\n const fields = {\n name: name.trim(),\n mappedStatus: mappedStatus || null,\n wipLimit: wipValue,\n };\n try {\n if (editing) {\n await throwOnError(update({ variables: { id: column.id, input: fields } }));\n } else {\n await throwOnError(\n create({ variables: { input: { boardId, type: 'CUSTOM', ...fields } } })\n );\n }\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be saved.'));\n }\n };\n\n const idPrefix = editing ? `column-${column.id}` : 'new-column';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {editing ? (\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Edit ${column.name} column`}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add column\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? `Edit ${column.name}` : 'Add column'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Moving a card into a column that sets a status changes the work item&apos;s status.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-name`} className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id={`${idPrefix}-name`}\n className=\"mt-1\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Sets status\n </label>\n <select\n id={`${idPrefix}-status`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={mappedStatus}\n onChange={(e) => setMappedStatus(e.target.value)}\n >\n <option value=\"\">No status (cards keep theirs)</option>\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {statusName(s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-wip`} className=\"text-sm font-medium\">\n WIP limit (blank for none)\n </label>\n <Input\n id={`${idPrefix}-wip`}\n type=\"number\"\n min={1}\n className=\"mt-1\"\n value={wip}\n onChange={(e) => setWip(e.target.value)}\n />\n {invalidWip && <ErrorLine message=\"The WIP limit must be a whole number, 1 or more.\" />}\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter className=\"gap-2\">\n {editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\" disabled={busy}>\n Delete column\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete {column.name}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its cards are not deleted: each goes back to the column for its status, or off\n the board if no column sets that status.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n try {\n await throwOnError(remove({ variables: { id: column.id } }));\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be deleted.'));\n }\n }}\n >\n Delete column\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n <Button onClick={save} disabled={!canSave} data-testid={`${idPrefix}-save`}>\n {busy ? 'Saving…' : editing ? 'Save' : 'Add column'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectBoard.js","names":[],"sources":["../../../src/planmagnet/board/ProjectBoard.tsx"],"sourcesContent":["import { createContext, useContext, useState, type ReactNode } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight, LayoutGrid, Pencil, Plus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\nimport {\n type ProjectWorkflow,\n allowedTargets,\n useProjectWorkflow,\n workflowLabel,\n} from '../hooks/useProjectWorkflow';\nimport { BulkActionsBar } from './BulkActions';\n\n/**\n * A project's board (P1.2). The server owns placement: each column's `cards`\n * follow the rule in wspace-planmagnet-svc board.service.ts `columnCardsWhere`\n * (placed column, else the first column mapped to the item's status), and\n * `unplacedCount` says how many items are on no column. Moving a card into a\n * column mapped to a status changes that status on the server, and a column's\n * WIP limit is enforced there too. This component only draws and asks.\n */\n\nconst STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\n/** The project's workflow for everything drawn on its board (BOFF-8043): status names and allowed moves. */\nconst BoardWorkflow = createContext<ProjectWorkflow | null>(null);\nconst useStatusName = () => {\n const workflow = useContext(BoardWorkflow);\n return (status: string) => workflowLabel(workflow, status);\n};\n\nconst statusLabel = (status: string) =>\n status.charAt(0) + status.slice(1).toLowerCase().replace(/_/g, ' ');\n\nconst CARDS_PER_COLUMN = 100;\n\nconst BOARDS = gql`\n query ProjectBoards($workspaceId: ID!, $projectId: String) {\n planmagnetBoards(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 20 }\n ) {\n items {\n id\n name\n isDefault\n }\n }\n }\n`;\n\n/** The next page of one column (P1.2: a column shows 100 cards and used to say only \"+N more\"). */\nconst COLUMN_PAGE = gql`\n query ProjectBoardColumnPage($boardId: ID!, $columnId: ID!, $after: String) {\n planmagnetBoardColumnCards(\n boardId: $boardId\n columnId: $columnId\n pagination: { first: 100, after: $after }\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n`;\n\nconst BOARD = gql`\n query ProjectBoard($id: ID!) {\n planmagnetBoard(id: $id) {\n id\n name\n unplacedCount\n columns {\n id\n name\n mappedStatus\n wipLimit\n sortOrder\n cards(pagination: { first: 100 }) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n items {\n id\n key\n title\n status\n priority\n storyPoints\n assigneeId\n statusSince\n }\n }\n }\n }\n }\n`;\n\nconst MOVE_CARD = gql`\n mutation ProjectBoardMoveCard($boardId: ID!, $input: PlanmagnetMoveCardInput!) {\n planmagnetMoveCard(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\nconst CREATE_BOARD = gql`\n mutation ProjectBoardCreate($input: PlanmagnetCreateBoardInput!) {\n planmagnetCreateBoard(input: $input) {\n id\n }\n }\n`;\nconst CREATE_COLUMN = gql`\n mutation ProjectBoardCreateColumn($input: PlanmagnetCreateBoardColumnInput!) {\n planmagnetCreateBoardColumn(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_COLUMN = gql`\n mutation ProjectBoardUpdateColumn($id: ID!, $input: PlanmagnetUpdateBoardColumnInput!) {\n planmagnetUpdateBoardColumn(id: $id, input: $input) {\n id\n }\n }\n`;\nconst DELETE_COLUMN = gql`\n mutation ProjectBoardDeleteColumn($id: ID!) {\n planmagnetDeleteBoardColumn(id: $id)\n }\n`;\nconst REORDER_COLUMNS = gql`\n mutation ProjectBoardReorder($boardId: ID!, $input: PlanmagnetReorderColumnsInput!) {\n planmagnetReorderColumns(boardId: $boardId, input: $input) {\n id\n }\n }\n`;\n\n/** Every query whose numbers a board change can move. */\nconst REFETCH = [\n 'ProjectBoard',\n 'ProjectBoards',\n 'GetWorkItems',\n 'GetWorkItemCounts',\n 'GetDashboardData',\n];\n\ninterface BoardCard {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n assigneeId: string | null;\n /** When it entered its current status; null when unknown (changed by a path that records no history). */\n statusSince: string | null;\n}\n\ninterface PageInfo {\n hasNextPage: boolean;\n endCursor: string | null;\n}\n\ninterface BoardColumn {\n id: string;\n name: string;\n mappedStatus: string | null;\n wipLimit: number | null;\n sortOrder: number;\n cards: { totalCount: number; pageInfo: PageInfo; items: BoardCard[] };\n}\n\ntype LaneBy = 'none' | 'assignee' | 'priority';\nconst PRIORITY_ORDER = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NONE'];\nconst DAY_MS = 86_400_000;\n\n/** Whole days in the current status, or null when unknown. */\nexport function daysInStatus(statusSince: string | null, now: number): number | null {\n if (!statusSince) return null;\n return Math.max(0, Math.floor((now - new Date(statusSince).getTime()) / DAY_MS));\n}\n\n/** Card aging: quiet under a week, a warning from 7 days, stronger from 14. Semantic tokens only. */\nfunction ageClass(days: number): string {\n if (days >= 14) return 'text-destructive font-medium';\n if (days >= 7) return 'text-status-warning-text';\n return 'text-muted-foreground';\n}\n\nfunction laneOf(card: BoardCard, by: LaneBy): string {\n if (by === 'assignee') return card.assigneeId ?? '';\n if (by === 'priority') return card.priority || 'NONE';\n return '';\n}\n\ninterface BoardData {\n id: string;\n name: string;\n unplacedCount: number;\n columns: BoardColumn[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\n\nfunction ErrorLine({ message }: { message: string }) {\n return (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {message}\n </p>\n );\n}\n\nexport function ProjectBoard({\n projectId,\n projectName,\n}: {\n projectId: string;\n projectName: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const client = useApolloClient();\n const [chosenBoardId, setChosenBoardId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n // Card age is read against the moment the board opened (Date.now() may not run during render).\n const [now] = useState(() => Date.now());\n const [laneBy, setLaneBy] = useState<LaneBy>('none');\n // Pages loaded past each column's first 100, keyed by the board data they extend: a refetch starts over.\n const [more, setMore] = useState<{\n key: unknown;\n byColumn: Record<string, { items: BoardCard[]; pageInfo: PageInfo }>;\n }>({\n key: null,\n byColumn: {},\n });\n const [loadingMore, setLoadingMore] = useState<string | null>(null);\n // Bulk update (BOFF-8047): ids of the cards currently checked, across every column/lane.\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const toggleSelect = (workItemId: string) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (next.has(workItemId)) next.delete(workItemId);\n else next.add(workItemId);\n return next;\n });\n const clearSelection = () => setSelected(new Set());\n const workflow = useProjectWorkflow(projectId);\n\n const { data: boardsData, loading: boardsLoading } = useQuery<{\n planmagnetBoards: { items: Array<{ id: string; name: string; isDefault: boolean }> };\n }>(BOARDS, { variables: { workspaceId, projectId }, skip: !workspaceId });\n const boards = boardsData?.planmagnetBoards.items ?? [];\n const boardId =\n (chosenBoardId && boards.some((b) => b.id === chosenBoardId) ? chosenBoardId : null) ??\n boards.find((b) => b.isDefault)?.id ??\n boards[0]?.id ??\n null;\n // A selection only makes sense on the board it was made on: switching boards (or projects, which changes\n // `boardId` too) drops it, the same \"derive during render\" pattern `more` below uses for its own reset.\n const [selectionBoardId, setSelectionBoardId] = useState(boardId);\n if (selectionBoardId !== boardId) {\n setSelectionBoardId(boardId);\n setSelected(new Set());\n }\n\n const { data, loading } = useQuery<{ planmagnetBoard: BoardData | null }>(BOARD, {\n variables: { id: boardId },\n skip: !boardId,\n });\n const board = data?.planmagnetBoard ?? null;\n const extra = more.key === board ? more.byColumn : {};\n /** A column with every page loaded so far. */\n const cardsOf = (column: BoardColumn) => {\n const loaded = extra[column.id];\n return {\n items: loaded ? [...column.cards.items, ...loaded.items] : column.cards.items,\n pageInfo: loaded ? loaded.pageInfo : column.cards.pageInfo,\n totalCount: column.cards.totalCount,\n };\n };\n const loadMore = async (column: BoardColumn) => {\n const cursor = cardsOf(column).pageInfo.endCursor;\n if (!board || !cursor) return;\n setError(null);\n setLoadingMore(column.id);\n try {\n const res = await client.query<{\n planmagnetBoardColumnCards: { pageInfo: PageInfo; items: BoardCard[] };\n }>({\n query: COLUMN_PAGE,\n variables: { boardId: board.id, columnId: column.id, after: cursor },\n fetchPolicy: 'network-only',\n });\n const page = res.data?.planmagnetBoardColumnCards;\n if (!page) throw new Error(res.error?.message ?? 'The next cards could not be loaded.');\n const prior = extra[column.id]?.items ?? [];\n setMore({\n key: board,\n byColumn: {\n ...extra,\n [column.id]: { items: [...prior, ...page.items], pageInfo: page.pageInfo },\n },\n });\n } catch (e) {\n setError(messageOf(e, 'The next cards could not be loaded.'));\n } finally {\n setLoadingMore(null);\n }\n };\n\n const [createBoard, createBoardState] = useMutation<{ planmagnetCreateBoard: { id: string } }>(\n CREATE_BOARD,\n { refetchQueries: REFETCH, awaitRefetchQueries: true }\n );\n const [moveCard, moveState] = useMutation(MOVE_CARD, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n const [reorder] = useMutation(REORDER_COLUMNS, {\n refetchQueries: REFETCH,\n awaitRefetchQueries: true,\n });\n\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(messageOf(e, fallback));\n }\n };\n\n const move = (workItemId: string, targetColumnId: string) =>\n run(\n () => moveCard({ variables: { boardId, input: { workItemId, targetColumnId } } }),\n 'The card could not be moved.'\n );\n\n const shiftColumn = (index: number, delta: -1 | 1) => {\n if (!board) return;\n const order = board.columns.map((c) => c.id);\n const target = index + delta;\n if (target < 0 || target >= order.length) return;\n [order[index], order[target]] = [order[target], order[index]];\n return run(\n () => reorder({ variables: { boardId, input: { columnIds: order } } }),\n 'The columns could not be reordered.'\n );\n };\n\n if (boardsLoading && !boardsData) {\n return <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>;\n }\n\n if (!boardId) {\n return (\n <Card>\n <CardContent className=\"p-8 text-center space-y-3\" data-testid=\"board-empty\">\n <LayoutGrid className=\"h-8 w-8 mx-auto text-muted-foreground\" />\n <p className=\"text-sm text-muted-foreground\">{projectName} has no board yet.</p>\n <Button\n size=\"sm\"\n disabled={createBoardState.loading}\n onClick={() =>\n run(\n () =>\n createBoard({\n variables: {\n input: {\n projectId,\n name: `${projectName} board`,\n type: 'KANBAN',\n isDefault: true,\n withDefaultColumns: true,\n },\n },\n }),\n 'The board could not be created.'\n )\n }\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n {createBoardState.loading ? 'Creating…' : 'Create board'}\n </Button>\n <p className=\"text-xs text-muted-foreground\">\n Starts with one column per status: Backlog, To Do, In Progress, In Review, Testing,\n Done.\n </p>\n {error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n }\n\n return (\n <BoardWorkflow.Provider value={workflow}>\n <div className=\"space-y-3\" data-testid=\"project-board\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {boards.length > 1 && (\n <select\n aria-label=\"Board\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={boardId}\n onChange={(e) => setChosenBoardId(e.target.value)}\n >\n {boards.map((b) => (\n <option key={b.id} value={b.id}>\n {b.name}\n </option>\n ))}\n </select>\n )}\n {board && (\n <label className=\"flex items-center gap-1 text-sm\">\n <span className=\"text-muted-foreground\">Swimlanes</span>\n <select\n aria-label=\"Swimlanes\"\n data-testid=\"board-lanes\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={laneBy}\n onChange={(e) => setLaneBy(e.target.value as LaneBy)}\n >\n <option value=\"none\">None</option>\n <option value=\"assignee\">By assignee</option>\n <option value=\"priority\">By priority</option>\n </select>\n </label>\n )}\n {board && board.unplacedCount > 0 && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"board-unplaced\">\n {board.unplacedCount} work item{board.unplacedCount === 1 ? ' is' : 's are'} not on\n this board: {board.unplacedCount === 1 ? 'its status has' : 'their status has'} no\n column.\n </p>\n )}\n {board && (\n <div className=\"ml-auto\">\n <ColumnDialog boardId={board.id} />\n </div>\n )}\n </div>\n {error && <ErrorLine message={error} />}\n {/* Bulk update (BOFF-8047): appears once at least one card is checked, disappears once the selection\n is cleared — it never blanks mid-action since a successful apply leaves the selection in place. */}\n {board && selected.size > 0 && (\n <BulkActionsBar\n projectId={projectId}\n selectedIds={[...selected]}\n workflow={workflow}\n onDone={clearSelection}\n />\n )}\n {loading && !board ? (\n <p className=\"text-sm text-muted-foreground p-4\">Loading board…</p>\n ) : board && laneBy !== 'none' ? (\n <SwimlaneBoard\n board={board}\n laneBy={laneBy}\n cardsOf={cardsOf}\n now={now}\n busy={moveState.loading}\n loadingMore={loadingMore}\n selected={selected}\n onToggleSelect={toggleSelect}\n onLoadMore={(column) => void loadMore(column)}\n onMove={move}\n />\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-2\">\n {board?.columns.map((column, index) => (\n <BoardColumnView\n key={column.id}\n boardId={board.id}\n column={column}\n columns={board.columns}\n cards={cardsOf(column)}\n isFirst={index === 0}\n isLast={index === board.columns.length - 1}\n busy={moveState.loading}\n now={now}\n loadingMore={loadingMore === column.id}\n selected={selected}\n onToggleSelect={toggleSelect}\n onLoadMore={() => void loadMore(column)}\n onMove={move}\n onShift={(delta) => shiftColumn(index, delta)}\n />\n ))}\n {board && board.columns.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">\n This board has no columns. Add one to start placing work.\n </p>\n )}\n </div>\n )}\n </div>\n </BoardWorkflow.Provider>\n );\n}\n\ntype ColumnCards = { items: BoardCard[]; pageInfo: PageInfo; totalCount: number };\n\n/** One card: title, key, points, assignee, age in status, and a keyboard \"Move…\" equal to drag-and-drop. */\nfunction CardView({\n card,\n column,\n columns,\n busy,\n now,\n selected,\n onToggleSelect,\n onMove,\n}: {\n card: BoardCard;\n column: BoardColumn;\n columns: BoardColumn[];\n busy: boolean;\n now: number;\n selected: boolean;\n onToggleSelect: (workItemId: string) => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const navigate = usePlanMagnetNavigate();\n const workflow = useContext(BoardWorkflow);\n const days = daysInStatus(card.statusSince, now);\n // Only columns this card may move into: unmapped ones (no status change) and those whose status the workflow allows.\n const reachable = allowedTargets(workflow, card.status, STATUSES);\n const targets = columns.filter(\n (c) => c.id !== column.id && (!c.mappedStatus || reachable.includes(c.mappedStatus))\n );\n return (\n <li\n draggable\n onDragStart={(e) => {\n e.dataTransfer.setData('text/plain', card.id);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className={`rounded-md border bg-background p-2 space-y-1 cursor-grab ${selected ? 'ring-2 ring-primary' : ''}`}\n data-testid=\"board-card\"\n >\n <div className=\"flex items-start gap-1.5\">\n {/* Bulk update (BOFF-8047): checking a card adds it to the selection the bulk action bar acts on.\n The `<li>` above is `draggable` for card drag-and-drop; a native drag source is resolved by\n walking UP from wherever the mouse/pointer went down, so without this the checkbox's own\n mousedown would still be picked up as \"drag the card\" instead of toggling the box. Stop that\n gesture (and any stray dragstart/click bubbling) at the checkbox itself; every other pointer\n in the card is untouched, so dragging the card from anywhere else still works. */}\n <input\n type=\"checkbox\"\n className=\"mt-0.5 shrink-0\"\n aria-label={`Select ${card.key}`}\n data-testid=\"board-card-select\"\n checked={selected}\n draggable={false}\n onMouseDown={(e) => e.stopPropagation()}\n onPointerDown={(e) => e.stopPropagation()}\n onDragStart={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n onChange={() => onToggleSelect(card.id)}\n />\n <button\n type=\"button\"\n className=\"text-left text-sm font-medium leading-tight hover:underline\"\n onClick={() => navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(card.id)}`)}\n >\n {card.title}\n </button>\n </div>\n <div className=\"flex items-center justify-between text-[11px] text-muted-foreground\">\n <span>{card.key}</span>\n {/* Card aging (P1.2): how long it has had this status. Unknown when its status changed off the record. */}\n {days !== null && (\n <span\n data-testid=\"card-age\"\n className={ageClass(days)}\n title={`In ${workflowLabel(workflow, card.status)} since ${new Date(card.statusSince ?? '').toLocaleDateString()}`}\n >\n {days === 0 ? 'today' : `${days}d in status`}\n </span>\n )}\n <span>{card.storyPoints ?? 0} pts</span>\n </div>\n <div className=\"flex items-center justify-between gap-1 text-[11px] text-muted-foreground\">\n <span className=\"truncate\">\n {card.assigneeId ? <PersonName id={card.assigneeId} /> : 'Unassigned'}\n </span>\n {/* The same move as drag-and-drop, for keyboard and assistive tech. */}\n <select\n aria-label={`Move ${card.key} to column`}\n className=\"rounded border border-input bg-background px-1 py-0.5 text-[11px]\"\n value=\"\"\n disabled={busy}\n onChange={(e) => e.target.value && onMove(card.id, e.target.value)}\n >\n <option value=\"\">Move…</option>\n {targets.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n </div>\n </li>\n );\n}\n\n/** A drop target: dropping a card here moves it into `column`. */\nfunction DropZone({\n column,\n onMove,\n className,\n children,\n label,\n}: {\n column: BoardColumn;\n onMove: (workItemId: string, targetColumnId: string) => void;\n className: string;\n children: ReactNode;\n label: string;\n}) {\n const [dragOver, setDragOver] = useState(false);\n return (\n <section\n aria-label={label}\n className={`${className} ${dragOver ? 'ring-2 ring-primary' : ''}`}\n onDragOver={(e) => {\n e.preventDefault();\n setDragOver(true);\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragOver(false);\n const workItemId = e.dataTransfer.getData('text/plain');\n if (workItemId) onMove(workItemId, column.id);\n }}\n >\n {children}\n </section>\n );\n}\n\n/** \"Showing N of M — Show more\" for a column with cards past the ones loaded. */\nfunction MoreCards({\n cards,\n loading,\n onLoadMore,\n}: {\n cards: ColumnCards;\n loading: boolean;\n onLoadMore: () => void;\n}) {\n const hidden = cards.totalCount - cards.items.length;\n if (hidden <= 0 || !cards.pageInfo.hasNextPage) return null;\n return (\n <div className=\"px-2 pb-2 space-y-1\">\n <p className=\"text-xs text-muted-foreground\">\n Showing {cards.items.length} of {cards.totalCount}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"w-full\"\n data-testid=\"board-column-more\"\n disabled={loading}\n onClick={onLoadMore}\n >\n {loading ? 'Loading…' : `Show ${Math.min(hidden, CARDS_PER_COLUMN)} more`}\n </Button>\n </div>\n );\n}\n\n/**\n * Swimlanes (P1.2): rows by assignee or priority across the same columns. Moving a card changes its column (and the\n * status that column sets); its lane follows its assignee or priority, which are changed on the card itself.\n */\nfunction SwimlaneBoard({\n board,\n laneBy,\n cardsOf,\n now,\n busy,\n loadingMore,\n selected,\n onToggleSelect,\n onLoadMore,\n onMove,\n}: {\n board: BoardData;\n laneBy: LaneBy;\n cardsOf: (column: BoardColumn) => ColumnCards;\n now: number;\n busy: boolean;\n loadingMore: string | null;\n selected: Set<string>;\n onToggleSelect: (workItemId: string) => void;\n onLoadMore: (column: BoardColumn) => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n}) {\n const perColumn = board.columns.map((column) => ({ column, cards: cardsOf(column) }));\n const lanes = [\n ...new Set(perColumn.flatMap(({ cards }) => cards.items.map((c) => laneOf(c, laneBy)))),\n ].sort((a, b) =>\n laneBy === 'priority'\n ? PRIORITY_ORDER.indexOf(a) - PRIORITY_ORDER.indexOf(b)\n : a === ''\n ? 1\n : b === ''\n ? -1\n : a.localeCompare(b)\n );\n const laneName = (lane: string) =>\n laneBy === 'priority' ? statusLabel(lane) : lane ? <PersonName id={lane} /> : 'Unassigned';\n return (\n <div className=\"overflow-x-auto pb-2 space-y-3\" data-testid=\"board-swimlanes\">\n <p className=\"text-xs text-muted-foreground\">\n Dragging moves a card between columns. Its lane follows its{' '}\n {laneBy === 'priority' ? 'priority' : 'assignee'}.\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0 px-2\">\n <h3 className=\"text-sm font-medium truncate\">\n {column.name}{' '}\n <span className=\"text-xs text-muted-foreground\">\n {column.wipLimit ? `${cards.totalCount} / ${column.wipLimit}` : cards.totalCount}\n </span>\n </h3>\n </div>\n ))}\n </div>\n {lanes.length === 0 && (\n <p className=\"text-sm text-muted-foreground p-4\">No cards on this board yet.</p>\n )}\n {lanes.map((lane) => (\n <div key={lane || 'none'} data-testid=\"board-lane\" className=\"space-y-1\">\n <p className=\"text-xs font-medium\">\n {laneName(lane)}{' '}\n <span className=\"text-muted-foreground\">\n (\n {perColumn.reduce(\n (n, { cards }) => n + cards.items.filter((c) => laneOf(c, laneBy) === lane).length,\n 0\n )}\n )\n </span>\n </p>\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <DropZone\n key={column.id}\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <ul className=\"p-2 space-y-2 min-h-[48px]\">\n {cards.items\n .filter((c) => laneOf(c, laneBy) === lane)\n .map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={board.columns}\n busy={busy}\n now={now}\n selected={selected.has(card.id)}\n onToggleSelect={onToggleSelect}\n onMove={onMove}\n />\n ))}\n </ul>\n </DropZone>\n ))}\n </div>\n </div>\n ))}\n <div className=\"flex gap-3\">\n {perColumn.map(({ column, cards }) => (\n <div key={column.id} className=\"min-w-[240px] w-[240px] shrink-0\">\n <MoreCards\n cards={cards}\n loading={loadingMore === column.id}\n onLoadMore={() => onLoadMore(column)}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n\nfunction BoardColumnView({\n boardId,\n column,\n columns,\n cards,\n isFirst,\n isLast,\n busy,\n now,\n loadingMore,\n selected,\n onToggleSelect,\n onLoadMore,\n onMove,\n onShift,\n}: {\n boardId: string;\n column: BoardColumn;\n columns: BoardColumn[];\n cards: ColumnCards;\n isFirst: boolean;\n isLast: boolean;\n busy: boolean;\n now: number;\n loadingMore: boolean;\n selected: Set<string>;\n onToggleSelect: (workItemId: string) => void;\n onLoadMore: () => void;\n onMove: (workItemId: string, targetColumnId: string) => void;\n onShift: (delta: -1 | 1) => void;\n}) {\n const count = cards.totalCount;\n const atLimit = column.wipLimit != null && column.wipLimit > 0 && count >= column.wipLimit;\n const statusName = useStatusName();\n\n return (\n <DropZone\n column={column}\n onMove={onMove}\n label={`${column.name} column`}\n className=\"min-w-[240px] w-[240px] shrink-0 rounded-md border bg-muted/30\"\n >\n <div data-testid={`board-column-${column.name}`}>\n <header className=\"p-2 border-b space-y-1\">\n <div className=\"flex items-center justify-between gap-1\">\n <h3 className=\"text-sm font-medium truncate\">{column.name}</h3>\n <span\n data-testid={`board-column-count-${column.name}`}\n className={`text-xs rounded-full px-1.5 ${atLimit ? 'bg-status-warning-subtle text-status-warning' : 'bg-muted'}`}\n title={column.wipLimit ? `WIP limit ${column.wipLimit}` : undefined}\n >\n {column.wipLimit ? `${count} / ${column.wipLimit}` : count}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-1\">\n <span className=\"text-[11px] text-muted-foreground\">\n {column.mappedStatus\n ? `Sets status: ${statusName(column.mappedStatus)}`\n : 'No status'}\n </span>\n <span className=\"flex\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column left`}\n disabled={isFirst}\n onClick={() => onShift(-1)}\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Move ${column.name} column right`}\n disabled={isLast}\n onClick={() => onShift(1)}\n >\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </Button>\n <ColumnDialog boardId={boardId} column={column} />\n </span>\n </div>\n </header>\n <ul className=\"p-2 space-y-2 min-h-[80px]\">\n {cards.items.map((card) => (\n <CardView\n key={card.id}\n card={card}\n column={column}\n columns={columns}\n busy={busy}\n now={now}\n selected={selected.has(card.id)}\n onToggleSelect={onToggleSelect}\n onMove={onMove}\n />\n ))}\n {count === 0 && <li className=\"text-xs text-muted-foreground text-center py-4\">Empty</li>}\n </ul>\n <MoreCards cards={cards} loading={loadingMore} onLoadMore={onLoadMore} />\n </div>\n </DropZone>\n );\n}\n\nfunction ColumnDialog({ boardId, column }: { boardId: string; column?: BoardColumn }) {\n const editing = column !== undefined;\n const statusName = useStatusName();\n const [open, setOpen] = useState(false);\n const [name, setName] = useState(column?.name ?? '');\n const [mappedStatus, setMappedStatus] = useState(column?.mappedStatus ?? '');\n const [wip, setWip] = useState(column?.wipLimit ? String(column.wipLimit) : '');\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: REFETCH, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_COLUMN, options);\n const [update, updateState] = useMutation(UPDATE_COLUMN, options);\n const [remove, removeState] = useMutation(DELETE_COLUMN, options);\n const busy = createState.loading || updateState.loading || removeState.loading;\n\n const wipValue = wip.trim() === '' ? null : Number(wip);\n const invalidWip = wipValue !== null && (!Number.isInteger(wipValue) || wipValue < 1);\n const canSave = name.trim() !== '' && !invalidWip && !busy;\n\n const reset = () => {\n setName(column?.name ?? '');\n setMappedStatus(column?.mappedStatus ?? '');\n setWip(column?.wipLimit ? String(column.wipLimit) : '');\n setError(null);\n };\n\n const save = async () => {\n setError(null);\n const fields = {\n name: name.trim(),\n mappedStatus: mappedStatus || null,\n wipLimit: wipValue,\n };\n try {\n if (editing) {\n await throwOnError(update({ variables: { id: column.id, input: fields } }));\n } else {\n await throwOnError(\n create({ variables: { input: { boardId, type: 'CUSTOM', ...fields } } })\n );\n }\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be saved.'));\n }\n };\n\n const idPrefix = editing ? `column-${column.id}` : 'new-column';\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {editing ? (\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-6 w-6\"\n aria-label={`Edit ${column.name} column`}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"outline\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add column\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {editing ? `Edit ${column.name}` : 'Add column'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Moving a card into a column that sets a status changes the work item&apos;s status.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-name`} className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id={`${idPrefix}-name`}\n className=\"mt-1\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Sets status\n </label>\n <select\n id={`${idPrefix}-status`}\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={mappedStatus}\n onChange={(e) => setMappedStatus(e.target.value)}\n >\n <option value=\"\">No status (cards keep theirs)</option>\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {statusName(s)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-wip`} className=\"text-sm font-medium\">\n WIP limit (blank for none)\n </label>\n <Input\n id={`${idPrefix}-wip`}\n type=\"number\"\n min={1}\n className=\"mt-1\"\n value={wip}\n onChange={(e) => setWip(e.target.value)}\n />\n {invalidWip && <ErrorLine message=\"The WIP limit must be a whole number, 1 or more.\" />}\n </div>\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter className=\"gap-2\">\n {editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"outline\" disabled={busy}>\n Delete column\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete {column.name}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its cards are not deleted: each goes back to the column for its status, or off\n the board if no column sets that status.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n try {\n await throwOnError(remove({ variables: { id: column.id } }));\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The column could not be deleted.'));\n }\n }}\n >\n Delete column\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n <Button onClick={save} disabled={!canSave} data-testid={`${idPrefix}-save`}>\n {busy ? 'Saving…' : editing ? 'Save' : 'Add column'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Budget controls (BOFF-8047): create a budget, edit it, approve it, delete it.
3
+ *
4
+ * Approval is its own mutation (`planmagnetApproveBudget`), not a plain status change: it is the only write
5
+ * that records who approved a budget and when (`approverId`/`approvedAt` — the service's `approve()`, unlike
6
+ * `update()`, sets both). The status select below therefore never offers APPROVED as a target; moving to it
7
+ * goes through the Approve button so that audit trail is never silently skipped.
8
+ */
9
+ export declare const BUDGET_STATUSES: readonly ["DRAFT", "ACTIVE", "APPROVED", "EXCEEDED", "CLOSED"];
10
+ /** Create a budget. With `presetProjectId` (the Project Detail entry point) the project picker is skipped. */
11
+ export declare function CreateBudgetDialog({ presetProjectId, onCreated, triggerSize, }: {
12
+ presetProjectId?: string;
13
+ onCreated?: (id: string) => void;
14
+ triggerSize?: 'sm' | 'default';
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ export interface BudgetValues {
17
+ id: string;
18
+ name: string;
19
+ description: string | null;
20
+ status: string;
21
+ plannedAmount: number;
22
+ currency: string;
23
+ periodStart: string | null;
24
+ periodEnd: string | null;
25
+ }
26
+ /** Status select, Edit, Approve and Delete for the budget detail header. */
27
+ export declare function BudgetHeaderActions({ budget }: {
28
+ budget: BudgetValues;
29
+ }): import("react/jsx-runtime").JSX.Element;
30
+ //# sourceMappingURL=BudgetControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BudgetControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/budgets/BudgetControls.tsx"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AAEH,eAAO,MAAM,eAAe,gEAAiE,CAAC;AA+D9F,8GAA8G;AAC9G,wBAAgB,kBAAkB,CAAC,EACjC,eAAe,EACf,SAAS,EACT,WAAkB,GACnB,EAAE;IACD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAChC,2CA6LA;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,2CAyFvE"}