@burdenoff/microfe-planmagnet 2026.1002.1 → 2026.1002.3

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 (218) hide show
  1. package/dist/generated/wspace-operations.d.ts +2533 -546
  2. package/dist/generated/wspace-operations.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +5654 -71
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/index.js +8 -8
  6. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  7. package/dist/planmagnet/PlanMagnetRoot.js +180 -162
  8. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  10. package/dist/planmagnet/PlanMagnetRoutes.js +100 -84
  11. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  12. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -1
  13. package/dist/planmagnet/board/BulkActions.js +106 -55
  14. package/dist/planmagnet/board/BulkActions.js.map +1 -1
  15. package/dist/planmagnet/controls/Pickers.d.ts +10 -5
  16. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  17. package/dist/planmagnet/controls/Pickers.js +10 -9
  18. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  19. package/dist/planmagnet/dashboards/DashboardUI.d.ts +0 -4
  20. package/dist/planmagnet/dashboards/DashboardUI.d.ts.map +1 -1
  21. package/dist/planmagnet/dashboards/DashboardUI.js +6 -13
  22. package/dist/planmagnet/dashboards/DashboardUI.js.map +1 -1
  23. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  24. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +70 -59
  25. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  26. package/dist/planmagnet/forecast/ResourceForecastCard.js +1 -1
  27. package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -1
  28. package/dist/planmagnet/index.d.ts +1 -1
  29. package/dist/planmagnet/index.d.ts.map +1 -1
  30. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts +3 -3
  31. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts.map +1 -1
  32. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +26 -19
  33. package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js.map +1 -1
  34. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts +2 -2
  35. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts.map +1 -1
  36. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +18 -12
  37. package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js.map +1 -1
  38. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts +2 -0
  39. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts.map +1 -0
  40. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js +251 -0
  41. package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js.map +1 -0
  42. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  43. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  44. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  45. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  46. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  47. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  48. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  49. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  50. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  51. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +107 -82
  54. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  56. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  57. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  58. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  59. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  60. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  61. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  62. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  63. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  64. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  65. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  66. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  67. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  69. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +116 -114
  72. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  73. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  74. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +174 -159
  75. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  76. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  77. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +159 -145
  78. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  79. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  80. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +161 -143
  81. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  82. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  83. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +160 -156
  84. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  85. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.d.ts.map +1 -1
  86. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +97 -99
  87. package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js.map +1 -1
  88. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  89. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  90. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  91. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  92. package/dist/planmagnet/product/ProductControls.js +1 -1
  93. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  94. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  95. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  96. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  97. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  98. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  99. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  100. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  101. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  102. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  103. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  104. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  105. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  106. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  107. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  108. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  109. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  110. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  111. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  112. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  113. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  114. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  115. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  116. package/dist/planmagnet/product/hierarchy.js +173 -0
  117. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  118. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  119. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  120. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  121. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  122. package/dist/planmagnet/product/refusal.d.ts +14 -0
  123. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  124. package/dist/planmagnet/product/refusal.js +10 -0
  125. package/dist/planmagnet/product/refusal.js.map +1 -0
  126. package/dist/planmagnet/product/routes.d.ts +10 -0
  127. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  128. package/dist/planmagnet/product/routes.js +2 -2
  129. package/dist/planmagnet/product/routes.js.map +1 -1
  130. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts +20 -0
  131. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts.map +1 -0
  132. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js +55 -0
  133. package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js.map +1 -0
  134. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts +30 -0
  135. package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts.map +1 -0
  136. package/dist/planmagnet/satisfaction/SatisfactionFigures.js +282 -0
  137. package/dist/planmagnet/satisfaction/SatisfactionFigures.js.map +1 -0
  138. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts +9 -0
  139. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts.map +1 -0
  140. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js +390 -0
  141. package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js.map +1 -0
  142. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts +17 -0
  143. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts.map +1 -0
  144. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js +77 -0
  145. package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js.map +1 -0
  146. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts +18 -0
  147. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts.map +1 -0
  148. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js +87 -0
  149. package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js.map +1 -0
  150. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts +13 -0
  151. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts.map +1 -0
  152. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js +326 -0
  153. package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js.map +1 -0
  154. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts +23 -0
  155. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts.map +1 -0
  156. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js +258 -0
  157. package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js.map +1 -0
  158. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts +6 -0
  159. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts.map +1 -0
  160. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js +56 -0
  161. package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js.map +1 -0
  162. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts +16 -0
  163. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts.map +1 -0
  164. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js +102 -0
  165. package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js.map +1 -0
  166. package/dist/planmagnet/satisfaction/satisfaction.d.ts +154 -0
  167. package/dist/planmagnet/satisfaction/satisfaction.d.ts.map +1 -0
  168. package/dist/planmagnet/satisfaction/satisfaction.js +210 -0
  169. package/dist/planmagnet/satisfaction/satisfaction.js.map +1 -0
  170. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts +108 -0
  171. package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts.map +1 -0
  172. package/dist/planmagnet/satisfaction/satisfactionCsv.js +211 -0
  173. package/dist/planmagnet/satisfaction/satisfactionCsv.js.map +1 -0
  174. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts +90 -0
  175. package/dist/planmagnet/satisfaction/satisfactionForm.d.ts.map +1 -0
  176. package/dist/planmagnet/satisfaction/satisfactionForm.js +90 -0
  177. package/dist/planmagnet/satisfaction/satisfactionForm.js.map +1 -0
  178. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts +116 -0
  179. package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts.map +1 -0
  180. package/dist/planmagnet/satisfaction/satisfactionOperations.js +86 -0
  181. package/dist/planmagnet/satisfaction/satisfactionOperations.js.map +1 -0
  182. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts +20 -0
  183. package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts.map +1 -0
  184. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js +31 -0
  185. package/dist/planmagnet/satisfaction/useSatisfactionAccess.js.map +1 -0
  186. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts +35 -0
  187. package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts.map +1 -0
  188. package/dist/planmagnet/skills/RequiredSkillsEditor.js +124 -0
  189. package/dist/planmagnet/skills/RequiredSkillsEditor.js.map +1 -0
  190. package/dist/planmagnet/skills/SkillChips.d.ts +30 -0
  191. package/dist/planmagnet/skills/SkillChips.d.ts.map +1 -0
  192. package/dist/planmagnet/skills/SkillChips.js +57 -0
  193. package/dist/planmagnet/skills/SkillChips.js.map +1 -0
  194. package/dist/planmagnet/skills/SkillCoverageCard.d.ts +24 -0
  195. package/dist/planmagnet/skills/SkillCoverageCard.d.ts.map +1 -0
  196. package/dist/planmagnet/skills/SkillCoverageCard.js +256 -0
  197. package/dist/planmagnet/skills/SkillCoverageCard.js.map +1 -0
  198. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts +10 -0
  199. package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts.map +1 -0
  200. package/dist/planmagnet/skills/WorkItemRequiredSkills.js +65 -0
  201. package/dist/planmagnet/skills/WorkItemRequiredSkills.js.map +1 -0
  202. package/dist/planmagnet/skills/skillOperations.d.ts +52 -0
  203. package/dist/planmagnet/skills/skillOperations.d.ts.map +1 -0
  204. package/dist/planmagnet/skills/skillOperations.js +44 -0
  205. package/dist/planmagnet/skills/skillOperations.js.map +1 -0
  206. package/dist/planmagnet/skills/skillTags.d.ts +103 -0
  207. package/dist/planmagnet/skills/skillTags.d.ts.map +1 -0
  208. package/dist/planmagnet/skills/skillTags.js +115 -0
  209. package/dist/planmagnet/skills/skillTags.js.map +1 -0
  210. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts +28 -0
  211. package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts.map +1 -0
  212. package/dist/planmagnet/skills/useRequiredSkillsDraft.js +30 -0
  213. package/dist/planmagnet/skills/useRequiredSkillsDraft.js.map +1 -0
  214. package/dist/shared/utils/i18n.d.ts +3 -0
  215. package/dist/shared/utils/i18n.d.ts.map +1 -1
  216. package/dist/shared/utils/i18n.js +1 -1
  217. package/dist/shared/utils/i18n.js.map +1 -1
  218. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { TaskTimePanel } from '../time/TaskTimePanel';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport { CustomFieldsPanel } from '../controls/CustomFieldValuesPanel';\nimport { DependencyPanel } from '../dependency/DependencyPanel';\nimport { IssueLinksPanel } from '../links/IssueLinksPanel';\nimport { WorkItemMilestoneSelect } from '../milestones/MilestoneControls';\nimport {\n WORK_ITEM_PRIORITIES,\n WorkItemStatusSelect,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { usePresence } from '../hooks/usePresence';\nimport { PresenceAvatars } from '../presence/PresenceAvatars';\nimport { WorkItemDiscussionPanel } from '../discussion/WorkItemDiscussionPanel';\nimport { CodeLinksPanel, type WorkItemCodeLink } from '../links/CodeLinksPanel';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n projectId\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n milestoneId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n customFieldValues {\n id\n definitionId\n value\n }\n discussionConversationId\n codeLinks {\n id\n kind\n title\n url\n state\n number\n authorLogin\n mergedAt\n ref\n }\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n milestoneId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n customFieldValues: { id: string; definitionId: string; value: unknown }[];\n discussionConversationId: string | null;\n codeLinks: WorkItemCodeLink[];\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // CWF-24: who else is looking at this task right now, and who's mid-comment.\n const presence = usePresence('WORK_ITEM', workItemId || null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} &middot; {task.type.toLowerCase()}\n </p>\n <PresenceAvatars others={presence.others} />\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <CustomFieldsPanel\n workItemId={task.id}\n projectId={task.projectId}\n values={task.customFieldValues ?? []}\n />\n <LinkedDocsPanel workItemId={task.id} />\n <TaskTimePanel\n workspaceId={workspaceId}\n workItemId={task.id}\n projectId={task.projectId}\n taskLabel={`${task.key} — ${task.title}`}\n />\n <DependencyPanel\n workspaceId={workspaceId ?? ''}\n workItemId={task.id}\n projectId={task.projectId}\n />\n {/* PDM-24: semantic links (blocks / duplicates / causes / relates to), not scheduling. */}\n <IssueLinksPanel workItemId={task.id} />\n {/* CWF-26: a real group conversation in Conversations, not a comment thread of its own. */}\n <WorkItemDiscussionPanel\n workItemId={task.id}\n workItemKey={task.key}\n workItemTitle={task.title}\n assigneeId={task.assigneeId}\n discussionConversationId={task.discussionConversationId}\n />\n {/* AUT-12: PRs/commits/releases GitHub sync discovered that mention this item's key. */}\n <CodeLinksPanel codeLinks={task.codeLinks ?? []} />\n <WorkItemComments\n workItemId={task.id}\n presence={{\n typingActorIds: presence.typingActorIds,\n notifyTyping: presence.notifyTyping,\n }}\n />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemStatusSelect\n workItemId={task.id}\n projectId={task.projectId}\n value={task.status}\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Milestone</span>\n {/* PDM-22: a milestone of this task's project, or none. */}\n <WorkItemMilestoneSelect\n workItemId={task.id}\n projectId={task.projectId}\n milestoneId={task.milestoneId}\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { TaskTimePanel } from '../time/TaskTimePanel';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport { CustomFieldsPanel } from '../controls/CustomFieldValuesPanel';\nimport { DependencyPanel } from '../dependency/DependencyPanel';\nimport { IssueLinksPanel } from '../links/IssueLinksPanel';\nimport { WorkItemMilestoneSelect } from '../milestones/MilestoneControls';\nimport {\n WORK_ITEM_PRIORITIES,\n WorkItemStatusSelect,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { usePresence } from '../hooks/usePresence';\nimport { PresenceAvatars } from '../presence/PresenceAvatars';\nimport { WorkItemDiscussionPanel } from '../discussion/WorkItemDiscussionPanel';\nimport { CodeLinksPanel, type WorkItemCodeLink } from '../links/CodeLinksPanel';\nimport { WorkItemProductFeatureChip } from '../product/WorkItemProductFeatureChip';\nimport { WorkItemRequiredSkills } from '../skills/WorkItemRequiredSkills';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n projectId\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n milestoneId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n customFieldValues {\n id\n definitionId\n value\n }\n discussionConversationId\n codeLinks {\n id\n kind\n title\n url\n state\n number\n authorLogin\n mergedAt\n ref\n }\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n milestoneId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n customFieldValues: { id: string; definitionId: string; value: unknown }[];\n discussionConversationId: string | null;\n codeLinks: WorkItemCodeLink[];\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // CWF-24: who else is looking at this task right now, and who's mid-comment.\n const presence = usePresence('WORK_ITEM', workItemId || null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} &middot; {task.type.toLowerCase()}\n </p>\n {/* PRM-01: read-only — an epic's product feature, or a story's through its epic. */}\n <WorkItemProductFeatureChip workItemId={task.id} />\n <PresenceAvatars others={presence.others} />\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <CustomFieldsPanel\n workItemId={task.id}\n projectId={task.projectId}\n values={task.customFieldValues ?? []}\n />\n <LinkedDocsPanel workItemId={task.id} />\n <TaskTimePanel\n workspaceId={workspaceId}\n workItemId={task.id}\n projectId={task.projectId}\n taskLabel={`${task.key} — ${task.title}`}\n />\n <DependencyPanel\n workspaceId={workspaceId ?? ''}\n workItemId={task.id}\n projectId={task.projectId}\n />\n {/* PDM-24: semantic links (blocks / duplicates / causes / relates to), not scheduling. */}\n <IssueLinksPanel workItemId={task.id} />\n {/* CWF-26: a real group conversation in Conversations, not a comment thread of its own. */}\n <WorkItemDiscussionPanel\n workItemId={task.id}\n workItemKey={task.key}\n workItemTitle={task.title}\n assigneeId={task.assigneeId}\n discussionConversationId={task.discussionConversationId}\n />\n {/* AUT-12: PRs/commits/releases GitHub sync discovered that mention this item's key. */}\n <CodeLinksPanel codeLinks={task.codeLinks ?? []} />\n <WorkItemComments\n workItemId={task.id}\n presence={{\n typingActorIds: presence.typingActorIds,\n notifyTyping: presence.notifyTyping,\n }}\n />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemStatusSelect\n workItemId={task.id}\n projectId={task.projectId}\n value={task.status}\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Milestone</span>\n {/* PDM-22: a milestone of this task's project, or none. */}\n <WorkItemMilestoneSelect\n workItemId={task.id}\n projectId={task.projectId}\n milestoneId={task.milestoneId}\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n {/* ANR-07/ANR-28: read-only here (edited on the edit page); an epic also shows its children's. */}\n <WorkItemRequiredSkills workItemId={task.id} />\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DeliveryKpisCard.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"names":[],"mappings":"AA+GA,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,2CA2IA"}
1
+ {"version":3,"file":"DeliveryKpisCard.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"names":[],"mappings":"AAgHA,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,2CA0IA"}
@@ -1,5 +1,6 @@
1
1
  import { StatTile as e } from "./DeliveryHealthCards.js";
2
- import { Info as t, Target as n } from "lucide-react";
2
+ import { DeliverySatisfactionSection as t } from "../../satisfaction/DeliverySatisfactionSection.js";
3
+ import { Target as n } from "lucide-react";
3
4
  import { gql as r } from "@apollo/client";
4
5
  import { useQuery as i } from "@apollo/client/react";
5
6
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
@@ -87,8 +88,8 @@ function m({ workspaceId: r, projectId: m, startDate: h, endDate: g }) {
87
88
  children: [/* @__PURE__ */ a(n, { className: "h-4 w-4" }), _("planmagnet.analytics.delivery.kpiTitle", "Delivery")]
88
89
  }), /* @__PURE__ */ a("p", {
89
90
  className: "text-xs text-muted-foreground",
90
- children: _("planmagnet.analytics.delivery.kpiSubtitle", "On-time delivery and scope adherence, for sprints/releases/milestones completed in the selected range.")
91
- })] }), /* @__PURE__ */ a(c, { children: y ? /* @__PURE__ */ a("p", {
91
+ children: _("planmagnet.analytics.delivery.kpiSubtitleWithSatisfaction", "On-time delivery and scope adherence for sprints/releases/milestones completed in the selected range, and customer satisfaction from the responses given in it.")
92
+ })] }), /* @__PURE__ */ o(c, { children: [y ? /* @__PURE__ */ a("p", {
92
93
  className: "text-sm text-muted-foreground",
93
94
  children: _("planmagnet.analytics.loading", "Loading…")
94
95
  }) : b ? /* @__PURE__ */ o("p", {
@@ -101,108 +102,105 @@ function m({ workspaceId: r, projectId: m, startDate: h, endDate: g }) {
101
102
  ]
102
103
  }) : x ? /* @__PURE__ */ o("div", {
103
104
  className: "space-y-4",
104
- children: [
105
- /* @__PURE__ */ o("div", { children: [
106
- /* @__PURE__ */ a("h3", {
107
- className: "text-sm font-medium mb-1",
108
- children: _("planmagnet.analytics.delivery.onTimeTitle", "On-time rate")
109
- }),
110
- /* @__PURE__ */ a(p, {
111
- label: _("planmagnet.analytics.delivery.sprints", "Sprints"),
112
- onTime: x.sprintsOnTime,
113
- total: x.sprintCount,
114
- withoutPlan: x.sprintsWithoutPlan,
115
- rate: x.sprintOnTimeRate,
116
- noPlanNoun: _("planmagnet.analytics.delivery.sprintPlanNoun", "planned end date")
117
- }),
118
- /* @__PURE__ */ a(p, {
119
- label: _("planmagnet.analytics.delivery.releases", "Releases"),
120
- onTime: x.releasesOnTime,
121
- total: x.releaseCount,
122
- withoutPlan: x.releasesWithoutPlannedDate,
123
- rate: x.releaseOnTimeRate,
124
- noPlanNoun: _("planmagnet.analytics.delivery.releasePlanNoun", "planned date")
125
- }),
126
- /* @__PURE__ */ a(p, {
127
- label: _("planmagnet.analytics.delivery.milestones", "Milestones"),
128
- onTime: x.milestonesOnTime,
129
- total: x.milestoneCount,
130
- withoutPlan: x.milestonesWithoutDueDate,
131
- rate: x.milestoneOnTimeRate,
132
- noPlanNoun: _("planmagnet.analytics.delivery.milestonePlanNoun", "due date")
133
- })
134
- ] }),
135
- /* @__PURE__ */ o("div", { children: [/* @__PURE__ */ a("h3", {
105
+ children: [/* @__PURE__ */ o("div", { children: [
106
+ /* @__PURE__ */ a("h3", {
136
107
  className: "text-sm font-medium mb-1",
137
- children: _("planmagnet.analytics.delivery.scopeAdherence", "Scope adherence")
138
- }), S && S.sprints.length === 0 ? /* @__PURE__ */ a("p", {
139
- className: "text-sm text-muted-foreground py-2",
140
- children: _("planmagnet.analytics.delivery.scopeAdherenceEmpty", "No completed sprint with a committed plan in this range.")
141
- }) : S && /* @__PURE__ */ o("div", {
142
- className: "space-y-2",
143
- children: [/* @__PURE__ */ o("div", {
144
- className: "grid grid-cols-3 gap-3",
108
+ children: _("planmagnet.analytics.delivery.onTimeTitle", "On-time rate")
109
+ }),
110
+ /* @__PURE__ */ a(p, {
111
+ label: _("planmagnet.analytics.delivery.sprints", "Sprints"),
112
+ onTime: x.sprintsOnTime,
113
+ total: x.sprintCount,
114
+ withoutPlan: x.sprintsWithoutPlan,
115
+ rate: x.sprintOnTimeRate,
116
+ noPlanNoun: _("planmagnet.analytics.delivery.sprintPlanNoun", "planned end date")
117
+ }),
118
+ /* @__PURE__ */ a(p, {
119
+ label: _("planmagnet.analytics.delivery.releases", "Releases"),
120
+ onTime: x.releasesOnTime,
121
+ total: x.releaseCount,
122
+ withoutPlan: x.releasesWithoutPlannedDate,
123
+ rate: x.releaseOnTimeRate,
124
+ noPlanNoun: _("planmagnet.analytics.delivery.releasePlanNoun", "planned date")
125
+ }),
126
+ /* @__PURE__ */ a(p, {
127
+ label: _("planmagnet.analytics.delivery.milestones", "Milestones"),
128
+ onTime: x.milestonesOnTime,
129
+ total: x.milestoneCount,
130
+ withoutPlan: x.milestonesWithoutDueDate,
131
+ rate: x.milestoneOnTimeRate,
132
+ noPlanNoun: _("planmagnet.analytics.delivery.milestonePlanNoun", "due date")
133
+ })
134
+ ] }), /* @__PURE__ */ o("div", { children: [/* @__PURE__ */ a("h3", {
135
+ className: "text-sm font-medium mb-1",
136
+ children: _("planmagnet.analytics.delivery.scopeAdherence", "Scope adherence")
137
+ }), S && S.sprints.length === 0 ? /* @__PURE__ */ a("p", {
138
+ className: "text-sm text-muted-foreground py-2",
139
+ children: _("planmagnet.analytics.delivery.scopeAdherenceEmpty", "No completed sprint with a committed plan in this range.")
140
+ }) : S && /* @__PURE__ */ o("div", {
141
+ className: "space-y-2",
142
+ children: [/* @__PURE__ */ o("div", {
143
+ className: "grid grid-cols-3 gap-3",
144
+ children: [
145
+ /* @__PURE__ */ a(e, {
146
+ label: _("planmagnet.analytics.delivery.committed", "Committed"),
147
+ value: `${S.committedPoints} pts`
148
+ }),
149
+ /* @__PURE__ */ a(e, {
150
+ label: _("planmagnet.analytics.delivery.delivered", "Delivered"),
151
+ value: `${S.deliveredPoints} pts`
152
+ }),
153
+ /* @__PURE__ */ a(e, {
154
+ label: _("planmagnet.analytics.delivery.adherenceRate", "Adherence"),
155
+ value: C === null ? "—" : `${C}%`
156
+ })
157
+ ]
158
+ }), /* @__PURE__ */ o("table", {
159
+ className: "w-full text-sm",
160
+ children: [/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ o("tr", {
161
+ className: "text-left text-xs text-muted-foreground",
145
162
  children: [
146
- /* @__PURE__ */ a(e, {
147
- label: _("planmagnet.analytics.delivery.committed", "Committed"),
148
- value: `${S.committedPoints} pts`
163
+ /* @__PURE__ */ a("th", {
164
+ className: "py-1 pr-2 font-normal",
165
+ children: _("planmagnet.analytics.delivery.sprint", "Sprint")
149
166
  }),
150
- /* @__PURE__ */ a(e, {
151
- label: _("planmagnet.analytics.delivery.delivered", "Delivered"),
152
- value: `${S.deliveredPoints} pts`
167
+ /* @__PURE__ */ a("th", {
168
+ className: "py-1 pr-2 text-right font-normal",
169
+ children: _("planmagnet.analytics.delivery.committed", "Committed")
153
170
  }),
154
- /* @__PURE__ */ a(e, {
155
- label: _("planmagnet.analytics.delivery.adherenceRate", "Adherence"),
156
- value: C === null ? "—" : `${C}%`
171
+ /* @__PURE__ */ a("th", {
172
+ className: "py-1 text-right font-normal",
173
+ children: _("planmagnet.analytics.delivery.delivered", "Delivered")
157
174
  })
158
175
  ]
159
- }), /* @__PURE__ */ o("table", {
160
- className: "w-full text-sm",
161
- children: [/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ o("tr", {
162
- className: "text-left text-xs text-muted-foreground",
163
- children: [
164
- /* @__PURE__ */ a("th", {
165
- className: "py-1 pr-2 font-normal",
166
- children: _("planmagnet.analytics.delivery.sprint", "Sprint")
167
- }),
168
- /* @__PURE__ */ a("th", {
169
- className: "py-1 pr-2 text-right font-normal",
170
- children: _("planmagnet.analytics.delivery.committed", "Committed")
171
- }),
172
- /* @__PURE__ */ a("th", {
173
- className: "py-1 text-right font-normal",
174
- children: _("planmagnet.analytics.delivery.delivered", "Delivered")
175
- })
176
- ]
177
- }) }), /* @__PURE__ */ a("tbody", { children: S.sprints.map((e) => /* @__PURE__ */ o("tr", {
178
- className: "border-t",
179
- children: [
180
- /* @__PURE__ */ a("td", {
181
- className: "py-1.5 pr-2",
182
- children: e.name
183
- }),
184
- /* @__PURE__ */ a("td", {
185
- className: "py-1.5 pr-2 text-right tabular-nums",
186
- children: e.committed
187
- }),
188
- /* @__PURE__ */ a("td", {
189
- className: "py-1.5 text-right tabular-nums",
190
- children: e.delivered
191
- })
192
- ]
193
- }, e.id)) })]
194
- })]
195
- })] }),
196
- /* @__PURE__ */ o("p", {
197
- className: "flex items-start gap-1.5 text-xs text-muted-foreground border-t pt-2",
198
- "data-testid": "delivery-no-csat-note",
199
- children: [/* @__PURE__ */ a(t, {
200
- className: "h-3.5 w-3.5 shrink-0 mt-0.5",
201
- "aria-hidden": !0
202
- }), _("planmagnet.analytics.delivery.noCsat", "Customer satisfaction is not shown here: PlanMagnet has no survey/NPS/CSAT data source to compute it from.")]
203
- })
204
- ]
205
- }) : null })]
176
+ }) }), /* @__PURE__ */ a("tbody", { children: S.sprints.map((e) => /* @__PURE__ */ o("tr", {
177
+ className: "border-t",
178
+ children: [
179
+ /* @__PURE__ */ a("td", {
180
+ className: "py-1.5 pr-2",
181
+ children: e.name
182
+ }),
183
+ /* @__PURE__ */ a("td", {
184
+ className: "py-1.5 pr-2 text-right tabular-nums",
185
+ children: e.committed
186
+ }),
187
+ /* @__PURE__ */ a("td", {
188
+ className: "py-1.5 text-right tabular-nums",
189
+ children: e.delivered
190
+ })
191
+ ]
192
+ }, e.id)) })]
193
+ })]
194
+ })] })]
195
+ }) : null, r && /* @__PURE__ */ a("div", {
196
+ className: "mt-4 border-t pt-3",
197
+ children: /* @__PURE__ */ a(t, {
198
+ workspaceId: r,
199
+ projectId: m,
200
+ startDate: h,
201
+ endDate: g
202
+ })
203
+ })] })]
206
204
  });
207
205
  }
208
206
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"DeliveryKpisCard.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Info, Target } from 'lucide-react';\nimport { StatTile } from './DeliveryHealthCards';\n\n/**\n * ANR-21: on-time delivery rate and scope adherence, for the workspace or one project, over the page's date\n * range. `planmagnetDeliveryKpis` deliberately has no `customerSatisfaction` field — PlanMagnet has no\n * survey/NPS/CSAT data source to back one (see the type's own doc comment in graphql/wspace.graphql) — so this\n * card says that explicitly (`delivery-no-csat-note`) instead of the gap just being silent.\n */\n\nconst GET_DELIVERY_KPIS = gql`\n query GetAnalyticsDeliveryKpis(\n $workspaceId: ID!\n $projectId: ID\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetDeliveryKpis(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n sprintCount\n sprintsOnTime\n sprintsWithoutPlan\n sprintOnTimeRate\n releaseCount\n releasesOnTime\n releasesWithoutPlannedDate\n releaseOnTimeRate\n milestoneCount\n milestonesOnTime\n milestonesWithoutDueDate\n milestoneOnTimeRate\n scopeAdherence {\n committedPoints\n deliveredPoints\n adherenceRate\n sprints {\n id\n name\n committed\n delivered\n }\n }\n }\n }\n`;\n\ninterface DeliveryKpisData {\n planmagnetDeliveryKpis: {\n sprintCount: number;\n sprintsOnTime: number;\n sprintsWithoutPlan: number;\n sprintOnTimeRate: number | null;\n releaseCount: number;\n releasesOnTime: number;\n releasesWithoutPlannedDate: number;\n releaseOnTimeRate: number | null;\n milestoneCount: number;\n milestonesOnTime: number;\n milestonesWithoutDueDate: number;\n milestoneOnTimeRate: number | null;\n scopeAdherence: {\n committedPoints: number;\n deliveredPoints: number;\n adherenceRate: number | null;\n sprints: Array<{ id: string; name: string; committed: number; delivered: number }>;\n };\n };\n}\n\n/** One on-time row: `x of y on time`, the count with no plan/due date to measure against, and the rate — or \"—\"\n * with the reason, never an invented 0%. */\nfunction OnTimeRow({\n label,\n onTime,\n total,\n withoutPlan,\n rate,\n noPlanNoun,\n}: {\n label: string;\n onTime: number;\n total: number;\n withoutPlan: number;\n rate: number | null;\n noPlanNoun: string;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-t py-2 first:border-t-0 first:pt-0\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium\">{label}</p>\n <p className=\"text-xs text-muted-foreground\">\n {onTime} / {total - withoutPlan} on time\n {withoutPlan > 0 ? ` · ${withoutPlan} with no ${noPlanNoun}` : ''}\n {total === 0 ? ' · none in range' : ''}\n </p>\n </div>\n <span className=\"text-lg font-semibold tabular-nums shrink-0\">\n {rate === null ? '—' : `${Math.round(rate * 100)}%`}\n </span>\n </div>\n );\n}\n\nexport function DeliveryKpisCard({\n workspaceId,\n projectId,\n startDate,\n endDate,\n}: {\n workspaceId?: string | null;\n /** Omitted = every project in the workspace (ANR-09 project scope set to \"All projects\"). */\n projectId?: string;\n startDate: string;\n endDate: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<DeliveryKpisData>(GET_DELIVERY_KPIS, {\n variables: { workspaceId, projectId, startDate, endDate },\n skip: !workspaceId,\n });\n const k = data?.planmagnetDeliveryKpis;\n const adherence = k?.scopeAdherence;\n const adherencePercent =\n adherence?.adherenceRate == null ? null : Math.round(adherence.adherenceRate * 100);\n\n return (\n <Card data-testid=\"delivery-card\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.delivery.kpiTitle', 'Delivery')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.delivery.kpiSubtitle',\n 'On-time delivery and scope adherence, for sprints/releases/milestones completed in the selected range.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.delivery.error', 'Delivery KPIs could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : !k ? null : (\n <div className=\"space-y-4\">\n <div>\n <h3 className=\"text-sm font-medium mb-1\">\n {t('planmagnet.analytics.delivery.onTimeTitle', 'On-time rate')}\n </h3>\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.sprints', 'Sprints')}\n onTime={k.sprintsOnTime}\n total={k.sprintCount}\n withoutPlan={k.sprintsWithoutPlan}\n rate={k.sprintOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.sprintPlanNoun', 'planned end date')}\n />\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.releases', 'Releases')}\n onTime={k.releasesOnTime}\n total={k.releaseCount}\n withoutPlan={k.releasesWithoutPlannedDate}\n rate={k.releaseOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.releasePlanNoun', 'planned date')}\n />\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.milestones', 'Milestones')}\n onTime={k.milestonesOnTime}\n total={k.milestoneCount}\n withoutPlan={k.milestonesWithoutDueDate}\n rate={k.milestoneOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.milestonePlanNoun', 'due date')}\n />\n </div>\n\n <div>\n <h3 className=\"text-sm font-medium mb-1\">\n {t('planmagnet.analytics.delivery.scopeAdherence', 'Scope adherence')}\n </h3>\n {adherence && adherence.sprints.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-2\">\n {t(\n 'planmagnet.analytics.delivery.scopeAdherenceEmpty',\n 'No completed sprint with a committed plan in this range.'\n )}\n </p>\n ) : (\n adherence && (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-3 gap-3\">\n <StatTile\n label={t('planmagnet.analytics.delivery.committed', 'Committed')}\n value={`${adherence.committedPoints} pts`}\n />\n <StatTile\n label={t('planmagnet.analytics.delivery.delivered', 'Delivered')}\n value={`${adherence.deliveredPoints} pts`}\n />\n <StatTile\n label={t('planmagnet.analytics.delivery.adherenceRate', 'Adherence')}\n value={adherencePercent === null ? '—' : `${adherencePercent}%`}\n />\n </div>\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.delivery.sprint', 'Sprint')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.delivery.committed', 'Committed')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.delivery.delivered', 'Delivered')}\n </th>\n </tr>\n </thead>\n <tbody>\n {adherence.sprints.map((s) => (\n <tr key={s.id} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">{s.name}</td>\n <td className=\"py-1.5 pr-2 text-right tabular-nums\">{s.committed}</td>\n <td className=\"py-1.5 text-right tabular-nums\">{s.delivered}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n )}\n </div>\n\n <p\n className=\"flex items-start gap-1.5 text-xs text-muted-foreground border-t pt-2\"\n data-testid=\"delivery-no-csat-note\"\n >\n <Info className=\"h-3.5 w-3.5 shrink-0 mt-0.5\" aria-hidden={true} />\n {t(\n 'planmagnet.analytics.delivery.noCsat',\n 'Customer satisfaction is not shown here: PlanMagnet has no survey/NPS/CSAT data source to compute it from.'\n )}\n </p>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DeliveryKpisCard.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Target } from 'lucide-react';\nimport { DeliverySatisfactionSection } from '../../satisfaction/DeliverySatisfactionSection';\nimport { StatTile } from './DeliveryHealthCards';\n\n/**\n * ANR-21: on-time delivery rate, scope adherence and customer satisfaction (CSAT, the default delivery KPI, and NPS),\n * for the workspace or one project, over the page's date range. Satisfaction comes from recorded customer responses\n * and is read by its own query (see DeliverySatisfactionSection), so it can never take the on-time and scope figures\n * down with it.\n */\n\nconst GET_DELIVERY_KPIS = gql`\n query GetAnalyticsDeliveryKpis(\n $workspaceId: ID!\n $projectId: ID\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetDeliveryKpis(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n sprintCount\n sprintsOnTime\n sprintsWithoutPlan\n sprintOnTimeRate\n releaseCount\n releasesOnTime\n releasesWithoutPlannedDate\n releaseOnTimeRate\n milestoneCount\n milestonesOnTime\n milestonesWithoutDueDate\n milestoneOnTimeRate\n scopeAdherence {\n committedPoints\n deliveredPoints\n adherenceRate\n sprints {\n id\n name\n committed\n delivered\n }\n }\n }\n }\n`;\n\ninterface DeliveryKpisData {\n planmagnetDeliveryKpis: {\n sprintCount: number;\n sprintsOnTime: number;\n sprintsWithoutPlan: number;\n sprintOnTimeRate: number | null;\n releaseCount: number;\n releasesOnTime: number;\n releasesWithoutPlannedDate: number;\n releaseOnTimeRate: number | null;\n milestoneCount: number;\n milestonesOnTime: number;\n milestonesWithoutDueDate: number;\n milestoneOnTimeRate: number | null;\n scopeAdherence: {\n committedPoints: number;\n deliveredPoints: number;\n adherenceRate: number | null;\n sprints: Array<{ id: string; name: string; committed: number; delivered: number }>;\n };\n };\n}\n\n/** One on-time row: `x of y on time`, the count with no plan/due date to measure against, and the rate — or \"—\"\n * with the reason, never an invented 0%. */\nfunction OnTimeRow({\n label,\n onTime,\n total,\n withoutPlan,\n rate,\n noPlanNoun,\n}: {\n label: string;\n onTime: number;\n total: number;\n withoutPlan: number;\n rate: number | null;\n noPlanNoun: string;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-t py-2 first:border-t-0 first:pt-0\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium\">{label}</p>\n <p className=\"text-xs text-muted-foreground\">\n {onTime} / {total - withoutPlan} on time\n {withoutPlan > 0 ? ` · ${withoutPlan} with no ${noPlanNoun}` : ''}\n {total === 0 ? ' · none in range' : ''}\n </p>\n </div>\n <span className=\"text-lg font-semibold tabular-nums shrink-0\">\n {rate === null ? '—' : `${Math.round(rate * 100)}%`}\n </span>\n </div>\n );\n}\n\nexport function DeliveryKpisCard({\n workspaceId,\n projectId,\n startDate,\n endDate,\n}: {\n workspaceId?: string | null;\n /** Omitted = every project in the workspace (ANR-09 project scope set to \"All projects\"). */\n projectId?: string;\n startDate: string;\n endDate: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<DeliveryKpisData>(GET_DELIVERY_KPIS, {\n variables: { workspaceId, projectId, startDate, endDate },\n skip: !workspaceId,\n });\n const k = data?.planmagnetDeliveryKpis;\n const adherence = k?.scopeAdherence;\n const adherencePercent =\n adherence?.adherenceRate == null ? null : Math.round(adherence.adherenceRate * 100);\n\n return (\n <Card data-testid=\"delivery-card\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.delivery.kpiTitle', 'Delivery')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.delivery.kpiSubtitleWithSatisfaction',\n 'On-time delivery and scope adherence for sprints/releases/milestones completed in the selected range, and customer satisfaction from the responses given in it.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.delivery.error', 'Delivery KPIs could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : !k ? null : (\n <div className=\"space-y-4\">\n <div>\n <h3 className=\"text-sm font-medium mb-1\">\n {t('planmagnet.analytics.delivery.onTimeTitle', 'On-time rate')}\n </h3>\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.sprints', 'Sprints')}\n onTime={k.sprintsOnTime}\n total={k.sprintCount}\n withoutPlan={k.sprintsWithoutPlan}\n rate={k.sprintOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.sprintPlanNoun', 'planned end date')}\n />\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.releases', 'Releases')}\n onTime={k.releasesOnTime}\n total={k.releaseCount}\n withoutPlan={k.releasesWithoutPlannedDate}\n rate={k.releaseOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.releasePlanNoun', 'planned date')}\n />\n <OnTimeRow\n label={t('planmagnet.analytics.delivery.milestones', 'Milestones')}\n onTime={k.milestonesOnTime}\n total={k.milestoneCount}\n withoutPlan={k.milestonesWithoutDueDate}\n rate={k.milestoneOnTimeRate}\n noPlanNoun={t('planmagnet.analytics.delivery.milestonePlanNoun', 'due date')}\n />\n </div>\n\n <div>\n <h3 className=\"text-sm font-medium mb-1\">\n {t('planmagnet.analytics.delivery.scopeAdherence', 'Scope adherence')}\n </h3>\n {adherence && adherence.sprints.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-2\">\n {t(\n 'planmagnet.analytics.delivery.scopeAdherenceEmpty',\n 'No completed sprint with a committed plan in this range.'\n )}\n </p>\n ) : (\n adherence && (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-3 gap-3\">\n <StatTile\n label={t('planmagnet.analytics.delivery.committed', 'Committed')}\n value={`${adherence.committedPoints} pts`}\n />\n <StatTile\n label={t('planmagnet.analytics.delivery.delivered', 'Delivered')}\n value={`${adherence.deliveredPoints} pts`}\n />\n <StatTile\n label={t('planmagnet.analytics.delivery.adherenceRate', 'Adherence')}\n value={adherencePercent === null ? '—' : `${adherencePercent}%`}\n />\n </div>\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.delivery.sprint', 'Sprint')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.delivery.committed', 'Committed')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.delivery.delivered', 'Delivered')}\n </th>\n </tr>\n </thead>\n <tbody>\n {adherence.sprints.map((s) => (\n <tr key={s.id} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">{s.name}</td>\n <td className=\"py-1.5 pr-2 text-right tabular-nums\">{s.committed}</td>\n <td className=\"py-1.5 text-right tabular-nums\">{s.delivered}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n )}\n </div>\n </div>\n )}\n {workspaceId && (\n <div className=\"mt-4 border-t pt-3\">\n <DeliverySatisfactionSection\n workspaceId={workspaceId}\n projectId={projectId}\n startDate={startDate}\n endDate={endDate}\n />\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export interface FeatureEpicChild {
2
+ id: string;
3
+ key: string;
4
+ title: string;
5
+ type: string;
6
+ status: string;
7
+ }
8
+ export interface FeatureEpic extends FeatureEpicChild {
9
+ children: FeatureEpicChild[];
10
+ }
11
+ export declare function FeatureEpicsPanel({ featureId, archived, epics, }: {
12
+ featureId: string;
13
+ archived: boolean;
14
+ epics: FeatureEpic[];
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ export interface FeatureLinkedRequest {
17
+ id: string;
18
+ title: string;
19
+ status: string;
20
+ voteCount: number;
21
+ productId: string | null;
22
+ }
23
+ export declare function FeatureRequestsPanel({ featureId, productId, archived, requests, }: {
24
+ featureId: string;
25
+ productId: string;
26
+ archived: boolean;
27
+ requests: FeatureLinkedRequest[];
28
+ }): import("react/jsx-runtime").JSX.Element;
29
+ //# sourceMappingURL=FeatureLinkPanels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FeatureLinkPanels.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/product/FeatureLinkPanels.tsx"],"names":[],"mappings":"AAgDA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAY,SAAQ,gBAAgB;IACnD,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAUD,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,QAAQ,EACR,KAAK,GACN,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB,2CAuHA;AAoID,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAeD,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC,2CA0GA"}