@burdenoff/microfe-planmagnet 2026.927.4 → 2026.927.5

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 (137) hide show
  1. package/dist/generated/global-types.d.ts +9 -1
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.d.ts +0 -12
  4. package/dist/generated/wspace-operations.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  6. package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
  7. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  8. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  9. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
  10. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  11. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  12. package/dist/planmagnet/controls/FeatureControls.js +142 -139
  13. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  14. package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
  15. package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
  16. package/dist/planmagnet/controls/FeatureTriage.js +398 -0
  17. package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
  18. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
  19. package/dist/planmagnet/controls/MilestoneControls.js +76 -73
  20. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
  21. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  22. package/dist/planmagnet/controls/ProjectControls.js +47 -44
  23. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  24. package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
  25. package/dist/planmagnet/controls/ReleaseControls.js +47 -44
  26. package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
  27. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  28. package/dist/planmagnet/controls/WorkItemControls.js +68 -65
  29. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  30. package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
  31. package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
  32. package/dist/planmagnet/hooks/useCounts.js +209 -0
  33. package/dist/planmagnet/hooks/useCounts.js.map +1 -0
  34. package/dist/planmagnet/import/exportColumns.d.ts +52 -0
  35. package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
  36. package/dist/planmagnet/import/exportColumns.js +81 -0
  37. package/dist/planmagnet/import/exportColumns.js.map +1 -0
  38. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  39. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
  40. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  41. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  42. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
  43. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  44. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  45. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
  46. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  47. package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
  48. package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
  49. package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
  50. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  51. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
  52. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  53. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  54. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
  55. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  56. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
  57. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
  58. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  59. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  60. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
  61. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  62. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  63. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
  64. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  65. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  66. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
  67. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  68. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  69. package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
  70. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  71. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  72. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
  73. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  74. package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
  75. package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
  76. package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
  77. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
  78. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
  79. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  81. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
  82. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  83. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
  84. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
  85. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
  86. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
  87. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  88. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
  89. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  90. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
  91. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  92. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
  93. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  94. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  95. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +152 -139
  96. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  97. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  98. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +75 -74
  99. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  100. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
  101. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  102. package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
  103. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  104. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  105. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
  106. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  107. package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
  108. package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
  109. package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
  110. package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
  111. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  112. package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
  113. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  114. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  115. package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
  116. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  117. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  118. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
  119. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  120. package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
  121. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
  122. package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
  123. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
  124. package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
  125. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
  126. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  127. package/dist/planmagnet/sprints/SprintControls.js +32 -30
  128. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  129. package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
  130. package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
  131. package/dist/planmagnet/utils/mutationResult.js +2 -1
  132. package/dist/planmagnet/utils/mutationResult.js.map +1 -1
  133. package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
  134. package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
  135. package/dist/planmagnet/utils/rowNavigation.js +9 -8
  136. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  137. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ReleaseWorkItemsCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseWorkItemsCard.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Plus, X } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * What is in this release (BOFF-7931). Until the service could write a\n * release's work items this card never rendered: every release was empty and\n * its scope silently fell back to its whole project. Items can be added from\n * the release's project only; the service refuses anything else and says why.\n */\n\nconst CANDIDATES = gql`\n query ReleaseWorkItemCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n status\n }\n }\n }\n`;\nconst ADD = gql`\n mutation ReleaseAddWorkItems($releaseId: ID!, $workItemIds: [ID!]!) {\n planmagnetAddWorkItemsToRelease(releaseId: $releaseId, workItemIds: $workItemIds) {\n id\n }\n }\n`;\nconst REMOVE = gql`\n mutation ReleaseRemoveWorkItems($releaseId: ID!, $workItemIds: [ID!]!) {\n planmagnetRemoveWorkItemsFromRelease(releaseId: $releaseId, workItemIds: $workItemIds) {\n id\n }\n }\n`;\n\nexport interface ReleaseWorkItem {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nexport function ReleaseWorkItemsCard({\n releaseId,\n projectId,\n items,\n}: {\n releaseId: string;\n projectId: string | null;\n items: ReleaseWorkItem[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [adding, setAdding] = useState(false);\n const [search, setSearch] = useState('');\n const [error, setError] = useState<string | null>(null);\n const term = useDeferredValue(search.trim());\n\n const candidates = useQuery<{\n planmagnetWorkItems: { items: { id: string; key: string; title: string; status: string }[] };\n }>(CANDIDATES, {\n variables: { workspaceId, filter: { projectId, ...(term ? { search: term } : {}) } },\n skip: !adding || !workspaceId || !projectId,\n });\n const refetch = {\n refetchQueries: ['GetRelease', 'ReleaseReadiness'],\n awaitRefetchQueries: true,\n };\n const [add, addState] = useMutation(ADD, refetch);\n const [remove, removeState] = useMutation(REMOVE, refetch);\n\n const inRelease = new Set(items.map((i) => i.id));\n const offered = (candidates.data?.planmagnetWorkItems.items ?? []).filter(\n (c) => !inRelease.has(c.id)\n );\n\n const change = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(e instanceof Error ? e.message : fallback);\n }\n };\n\n return (\n <Card data-testid=\"release-work-items\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Work Items ({items.length})</CardTitle>\n {projectId && !adding && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setAdding(true)}\n data-testid=\"release-items-add\"\n >\n <Plus className=\"mr-1 h-4 w-4\" /> Add work items\n </Button>\n )}\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n )}\n {items.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No work items are in this release yet, so its overview and release notes cover\n everything in its project.\n </p>\n )}\n {items.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-3\"\n data-testid=\"release-item\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 text-left\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <p className=\"truncate text-sm font-medium hover:underline\">{item.title}</p>\n <p className=\"text-xs capitalize text-muted-foreground\">\n {item.type.toLowerCase()} &middot; {item.priority.toLowerCase()} priority\n </p>\n </button>\n <span className=\"flex shrink-0 items-center gap-1\">\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label={`Remove ${item.title} from this release`}\n disabled={removeState.loading}\n onClick={() =>\n change(\n () => remove({ variables: { releaseId, workItemIds: [item.id] } }),\n 'The work item could not be removed.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n ))}\n {adding && (\n <div className=\"space-y-2 border-t pt-3\">\n <div className=\"flex gap-2\">\n <Input\n aria-label=\"Find work items in this release's project\"\n placeholder=\"Find work items in this project\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setAdding(false);\n setSearch('');\n }}\n >\n Done\n </Button>\n </div>\n {candidates.loading && !candidates.data && (\n <p className=\"text-sm text-muted-foreground\">Loading…</p>\n )}\n {candidates.error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Work items could not be loaded: {candidates.error.message}\n </p>\n )}\n {candidates.data && offered.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No other work items match.</p>\n )}\n <ul className=\"space-y-1\">\n {offered.map((c) => (\n <li key={c.id} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"min-w-0 truncate\">\n <span className=\"text-muted-foreground\">{c.key}</span> {c.title}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 shrink-0 text-xs\"\n disabled={addState.loading}\n data-testid=\"release-item-candidate-add\"\n onClick={() =>\n change(\n () => add({ variables: { releaseId, workItemIds: [c.id] } }),\n 'The work item could not be added.'\n )\n }\n >\n Add\n </Button>\n </li>\n ))}\n </ul>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReleaseWorkItemsCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseWorkItemsCard.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Plus, X } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * What is in this release (BOFF-7931). Until the service could write a\n * release's work items this card never rendered: every release was empty and\n * its scope silently fell back to its whole project. Items can be added from\n * the release's project only; the service refuses anything else and says why.\n */\n\nconst CANDIDATES = gql`\n query ReleaseWorkItemCandidates($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n status\n }\n totalCount\n }\n }\n`;\nconst ADD = gql`\n mutation ReleaseAddWorkItems($releaseId: ID!, $workItemIds: [ID!]!) {\n planmagnetAddWorkItemsToRelease(releaseId: $releaseId, workItemIds: $workItemIds) {\n id\n }\n }\n`;\nconst REMOVE = gql`\n mutation ReleaseRemoveWorkItems($releaseId: ID!, $workItemIds: [ID!]!) {\n planmagnetRemoveWorkItemsFromRelease(releaseId: $releaseId, workItemIds: $workItemIds) {\n id\n }\n }\n`;\n\nexport interface ReleaseWorkItem {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nexport function ReleaseWorkItemsCard({\n releaseId,\n projectId,\n items,\n}: {\n releaseId: string;\n projectId: string | null;\n items: ReleaseWorkItem[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [adding, setAdding] = useState(false);\n const [search, setSearch] = useState('');\n const [error, setError] = useState<string | null>(null);\n const term = useDeferredValue(search.trim());\n\n const candidates = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string; status: string }[];\n totalCount: number;\n };\n }>(CANDIDATES, {\n // Members are excluded on the SERVER (BOFF-7984): it took 20 and dropped members afterwards, so 20 members\n // read as \"No other work items match\" while others existed. Search also matches the key now.\n variables: {\n workspaceId,\n filter: { projectId, excludeIds: items.map((i) => i.id), ...(term ? { search: term } : {}) },\n },\n skip: !adding || !workspaceId || !projectId,\n });\n const refetch = {\n refetchQueries: ['GetRelease', 'ReleaseReadiness'],\n awaitRefetchQueries: true,\n };\n const [add, addState] = useMutation(ADD, refetch);\n const [remove, removeState] = useMutation(REMOVE, refetch);\n\n const offered = candidates.data?.planmagnetWorkItems.items ?? [];\n const moreCandidates = (candidates.data?.planmagnetWorkItems.totalCount ?? 0) - offered.length;\n\n const change = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n } catch (e) {\n setError(e instanceof Error ? e.message : fallback);\n }\n };\n\n return (\n <Card data-testid=\"release-work-items\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Work Items ({items.length})</CardTitle>\n {projectId && !adding && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setAdding(true)}\n data-testid=\"release-items-add\"\n >\n <Plus className=\"mr-1 h-4 w-4\" /> Add work items\n </Button>\n )}\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n )}\n {items.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No work items are in this release yet, so its overview and release notes cover\n everything in its project.\n </p>\n )}\n {items.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-3\"\n data-testid=\"release-item\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 text-left\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <p className=\"truncate text-sm font-medium hover:underline\">{item.title}</p>\n <p className=\"text-xs capitalize text-muted-foreground\">\n {item.type.toLowerCase()} &middot; {item.priority.toLowerCase()} priority\n </p>\n </button>\n <span className=\"flex shrink-0 items-center gap-1\">\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label={`Remove ${item.title} from this release`}\n disabled={removeState.loading}\n onClick={() =>\n change(\n () => remove({ variables: { releaseId, workItemIds: [item.id] } }),\n 'The work item could not be removed.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n ))}\n {adding && (\n <div className=\"space-y-2 border-t pt-3\">\n <div className=\"flex gap-2\">\n <Input\n aria-label=\"Find work items in this release's project\"\n placeholder=\"Find work items in this project\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setAdding(false);\n setSearch('');\n }}\n >\n Done\n </Button>\n </div>\n {candidates.loading && !candidates.data && (\n <p className=\"text-sm text-muted-foreground\">Loading…</p>\n )}\n {candidates.error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Work items could not be loaded: {candidates.error.message}\n </p>\n )}\n {candidates.data && offered.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n {term\n ? 'No other work items match.'\n : 'Every work item in this project is already in the release.'}\n </p>\n )}\n {moreCandidates > 0 && (\n <p className=\"text-xs text-muted-foreground\">\n {moreCandidates} more — type a key or title to narrow the list.\n </p>\n )}\n <ul className=\"space-y-1\">\n {offered.map((c) => (\n <li key={c.id} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"min-w-0 truncate\">\n <span className=\"text-muted-foreground\">{c.key}</span> {c.title}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 shrink-0 text-xs\"\n disabled={addState.loading}\n data-testid=\"release-item-candidate-add\"\n onClick={() =>\n change(\n () => add({ variables: { releaseId, workItemIds: [c.id] } }),\n 'The work item could not be added.'\n )\n }\n >\n Add\n </Button>\n </li>\n ))}\n </ul>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RoadmapControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"names":[],"mappings":"AAsBA;;;;GAIG;AAEH,eAAO,MAAM,qBAAqB,qFAOxB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,IAAI,EACJ,KAAK,GACN,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,2CAgMA;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,2CA+BnF;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,2CA0C5F"}
1
+ {"version":3,"file":"RoadmapControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"names":[],"mappings":"AAsBA;;;;GAIG;AAEH,eAAO,MAAM,qBAAqB,qFAOxB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,IAAI,EACJ,KAAK,GACN,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,2CAgMA;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,2CA+BnF;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,2CAqD5F"}
@@ -1,36 +1,36 @@
1
1
  import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
- import { throwOnError as t } from "../utils/mutationResult.js";
3
- import { ConfirmButton as n, ErrorLine as r } from "../controls/ConfirmButton.js";
4
- import { endBeforeStart as i } from "../utils/formValidation.js";
5
- import { enumLabel as a } from "../controls/WorkItemControls.js";
6
- import { detailPath as o } from "../utils/rowNavigation.js";
7
- import { useState as s } from "react";
8
- import { Pencil as c, Plus as l, Trash2 as u } from "lucide-react";
9
- import { gql as d } from "@apollo/client";
10
- import { useMutation as f } from "@apollo/client/react";
11
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
12
- import { Button as h, Input as g, ResponsiveDialog as _, ResponsiveDialogContent as v, ResponsiveDialogDescription as y, ResponsiveDialogFooter as b, ResponsiveDialogHeader as x, ResponsiveDialogTitle as S, ResponsiveDialogTrigger as C } from "@burdenoff/fe-libs/ui";
2
+ import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
3
+ import { detailPath as r } from "../utils/rowNavigation.js";
4
+ import { ConfirmButton as i, ErrorLine as a } from "../controls/ConfirmButton.js";
5
+ import { endBeforeStart as o } from "../utils/formValidation.js";
6
+ import { enumLabel as s } from "../controls/WorkItemControls.js";
7
+ import { useState as c } from "react";
8
+ import { BarChart3 as l, Pencil as u, Plus as d, Trash2 as f } from "lucide-react";
9
+ import { gql as p } from "@apollo/client";
10
+ import { useMutation as m } from "@apollo/client/react";
11
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
12
+ import { Button as _, Input as v, ResponsiveDialog as y, ResponsiveDialogContent as b, ResponsiveDialogDescription as x, ResponsiveDialogFooter as S, ResponsiveDialogHeader as C, ResponsiveDialogTitle as w, ResponsiveDialogTrigger as T } from "@burdenoff/fe-libs/ui";
13
13
  //#region src/planmagnet/roadmaps/RoadmapControls.tsx
14
- var w = [
14
+ var E = [
15
15
  "PLANNED",
16
16
  "IN_PROGRESS",
17
17
  "AT_RISK",
18
18
  "DELAYED",
19
19
  "COMPLETED",
20
20
  "CANCELLED"
21
- ], T = [
21
+ ], D = [
22
22
  "GetRoadmapDetailPage",
23
23
  "GetRoadmapDetail",
24
24
  "ListRoadmapsPage",
25
25
  "ListRoadmaps",
26
26
  "GetRoadmapAnalytics"
27
- ], E = d`
27
+ ], O = p`
28
28
  mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {
29
29
  planmagnetCreateRoadmapItem(input: $input) {
30
30
  id
31
31
  }
32
32
  }
33
- `, D = d`
33
+ `, k = p`
34
34
  mutation RoadmapControlsUpdateItem($id: ID!, $input: PlanmagnetUpdateRoadmapItemInput!) {
35
35
  planmagnetUpdateRoadmapItem(id: $id, input: $input) {
36
36
  id
@@ -42,260 +42,273 @@ var w = [
42
42
  endDate
43
43
  }
44
44
  }
45
- `, O = d`
45
+ `, A = p`
46
46
  mutation RoadmapControlsDeleteItem($id: ID!) {
47
47
  planmagnetDeleteRoadmapItem(id: $id)
48
48
  }
49
- `, k = d`
49
+ `, j = p`
50
50
  mutation RoadmapControlsDelete($id: ID!) {
51
51
  planmagnetDeleteRoadmap(id: $id)
52
52
  }
53
- `, A = (e) => e ? e.slice(0, 10) : "";
54
- function j({ roadmapId: e, item: n, lanes: o }) {
55
- let u = {
56
- title: n?.title ?? "",
57
- description: n?.description ?? "",
58
- status: n?.status ?? "PLANNED",
59
- swimlane: n?.swimlane ?? "",
60
- startDate: A(n?.startDate),
61
- endDate: A(n?.endDate)
62
- }, [d, O] = s(!1), [k, j] = s(u), [M, N] = s(null), P = {
63
- refetchQueries: T,
53
+ `, M = (e) => e ? e.slice(0, 10) : "";
54
+ function N({ roadmapId: e, item: t, lanes: r }) {
55
+ let i = {
56
+ title: t?.title ?? "",
57
+ description: t?.description ?? "",
58
+ status: t?.status ?? "PLANNED",
59
+ swimlane: t?.swimlane ?? "",
60
+ startDate: M(t?.startDate),
61
+ endDate: M(t?.endDate)
62
+ }, [l, f] = c(!1), [p, A] = c(i), [j, N] = c(null), P = {
63
+ refetchQueries: D,
64
64
  awaitRefetchQueries: !0
65
- }, [F, I] = f(E, P), [L, R] = f(D, P), z = I.loading || R.loading, B = i(k.startDate, k.endDate), V = k.title.trim() !== "" && !B && !z, H = w.includes(k.status) ? w : [k.status, ...w], U = n ? `roadmap-item-${n.id}` : "roadmap-item-new", W = async () => {
65
+ }, [F, I] = m(O, P), [L, R] = m(k, P), z = I.loading || R.loading, B = o(p.startDate, p.endDate), V = p.title.trim() !== "" && !B && !z, H = E.includes(p.status) ? E : [p.status, ...E], U = t ? `roadmap-item-${t.id}` : "roadmap-item-new", W = async () => {
66
66
  N(null);
67
67
  let r = {
68
- title: k.title.trim(),
69
- description: k.description.trim() || null,
70
- status: k.status,
71
- swimlane: k.swimlane.trim() || null
68
+ title: p.title.trim(),
69
+ description: p.description.trim() || null,
70
+ status: p.status,
71
+ swimlane: p.swimlane.trim() || null
72
72
  };
73
73
  try {
74
- if (n) {
74
+ if (t) {
75
75
  let e = {
76
- ...k.startDate !== u.startDate && { startDate: k.startDate || null },
77
- ...k.endDate !== u.endDate && { endDate: k.endDate || null }
76
+ ...p.startDate !== i.startDate && { startDate: p.startDate || null },
77
+ ...p.endDate !== i.endDate && { endDate: p.endDate || null }
78
78
  };
79
- await t(L({ variables: {
80
- id: n.id,
79
+ await n(L({ variables: {
80
+ id: t.id,
81
81
  input: {
82
82
  ...r,
83
83
  ...e
84
84
  }
85
85
  } }));
86
- } else await t(F({ variables: { input: {
86
+ } else await n(F({ variables: { input: {
87
87
  roadmapId: e,
88
88
  ...r,
89
- startDate: k.startDate || void 0,
90
- endDate: k.endDate || void 0
91
- } } })), j(u);
92
- O(!1);
89
+ startDate: p.startDate || void 0,
90
+ endDate: p.endDate || void 0
91
+ } } })), A(i);
92
+ f(!1);
93
93
  } catch (e) {
94
94
  N(e instanceof Error ? e.message : "The item could not be saved.");
95
95
  }
96
96
  }, G = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm";
97
- return /* @__PURE__ */ m(_, {
98
- open: d,
97
+ return /* @__PURE__ */ g(y, {
98
+ open: l,
99
99
  onOpenChange: (e) => {
100
- O(e), e ? j(u) : N(null);
100
+ f(e), e ? A(i) : N(null);
101
101
  },
102
- children: [/* @__PURE__ */ p(C, {
102
+ children: [/* @__PURE__ */ h(T, {
103
103
  asChild: !0,
104
- children: n ? /* @__PURE__ */ p(h, {
104
+ children: t ? /* @__PURE__ */ h(_, {
105
105
  size: "sm",
106
106
  variant: "ghost",
107
- "aria-label": `Edit ${n.title}`,
108
- children: /* @__PURE__ */ p(c, { className: "h-4 w-4" })
109
- }) : /* @__PURE__ */ m(h, {
107
+ "aria-label": `Edit ${t.title}`,
108
+ children: /* @__PURE__ */ h(u, { className: "h-4 w-4" })
109
+ }) : /* @__PURE__ */ g(_, {
110
110
  size: "sm",
111
111
  "data-testid": "roadmap-add-item",
112
- children: [/* @__PURE__ */ p(l, { className: "mr-1 h-4 w-4" }), "Add item"]
112
+ children: [/* @__PURE__ */ h(d, { className: "mr-1 h-4 w-4" }), "Add item"]
113
113
  })
114
- }), /* @__PURE__ */ m(v, {
114
+ }), /* @__PURE__ */ g(b, {
115
115
  className: "max-w-lg",
116
116
  children: [
117
- /* @__PURE__ */ m(x, { children: [/* @__PURE__ */ p(S, { children: n ? "Edit roadmap item" : "Add roadmap item" }), /* @__PURE__ */ p(y, { children: "Items in the same swimlane are grouped together on the roadmap." })] }),
118
- /* @__PURE__ */ m("div", {
117
+ /* @__PURE__ */ g(C, { children: [/* @__PURE__ */ h(w, { children: t ? "Edit roadmap item" : "Add roadmap item" }), /* @__PURE__ */ h(x, { children: "Items in the same swimlane are grouped together on the roadmap." })] }),
118
+ /* @__PURE__ */ g("div", {
119
119
  className: "space-y-3",
120
120
  children: [
121
- /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
121
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
122
122
  htmlFor: `${U}-title`,
123
123
  className: "text-sm font-medium",
124
124
  children: "Title"
125
- }), /* @__PURE__ */ p(g, {
125
+ }), /* @__PURE__ */ h(v, {
126
126
  id: `${U}-title`,
127
127
  className: "mt-1",
128
- value: k.title,
129
- onChange: (e) => j({
130
- ...k,
128
+ value: p.title,
129
+ onChange: (e) => A({
130
+ ...p,
131
131
  title: e.target.value
132
132
  })
133
133
  })] }),
134
- /* @__PURE__ */ m("div", {
134
+ /* @__PURE__ */ g("div", {
135
135
  className: "grid grid-cols-2 gap-3",
136
- children: [/* @__PURE__ */ m("div", { children: [
137
- /* @__PURE__ */ p("label", {
136
+ children: [/* @__PURE__ */ g("div", { children: [
137
+ /* @__PURE__ */ h("label", {
138
138
  htmlFor: `${U}-lane`,
139
139
  className: "text-sm font-medium",
140
140
  children: "Swimlane"
141
141
  }),
142
- /* @__PURE__ */ p(g, {
142
+ /* @__PURE__ */ h(v, {
143
143
  id: `${U}-lane`,
144
144
  className: "mt-1",
145
145
  list: `${U}-lanes`,
146
146
  placeholder: "e.g. Q4",
147
- value: k.swimlane,
148
- onChange: (e) => j({
149
- ...k,
147
+ value: p.swimlane,
148
+ onChange: (e) => A({
149
+ ...p,
150
150
  swimlane: e.target.value
151
151
  })
152
152
  }),
153
- /* @__PURE__ */ p("datalist", {
153
+ /* @__PURE__ */ h("datalist", {
154
154
  id: `${U}-lanes`,
155
- children: o.map((e) => /* @__PURE__ */ p("option", { value: e }, e))
155
+ children: r.map((e) => /* @__PURE__ */ h("option", { value: e }, e))
156
156
  })
157
- ] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
157
+ ] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
158
158
  htmlFor: `${U}-status`,
159
159
  className: "text-sm font-medium",
160
160
  children: "Status"
161
- }), /* @__PURE__ */ p("select", {
161
+ }), /* @__PURE__ */ h("select", {
162
162
  id: `${U}-status`,
163
163
  className: G,
164
- value: k.status,
165
- onChange: (e) => j({
166
- ...k,
164
+ value: p.status,
165
+ onChange: (e) => A({
166
+ ...p,
167
167
  status: e.target.value
168
168
  }),
169
- children: H.map((e) => /* @__PURE__ */ p("option", {
169
+ children: H.map((e) => /* @__PURE__ */ h("option", {
170
170
  value: e,
171
- children: a(e)
171
+ children: s(e)
172
172
  }, e))
173
173
  })] })]
174
174
  }),
175
- /* @__PURE__ */ m("div", {
175
+ /* @__PURE__ */ g("div", {
176
176
  className: "grid grid-cols-2 gap-3",
177
- children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
177
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
178
178
  htmlFor: `${U}-start`,
179
179
  className: "text-sm font-medium",
180
180
  children: "Start date"
181
- }), /* @__PURE__ */ p(g, {
181
+ }), /* @__PURE__ */ h(v, {
182
182
  id: `${U}-start`,
183
183
  type: "date",
184
184
  className: "mt-1",
185
- value: k.startDate,
186
- max: k.endDate || void 0,
187
- onChange: (e) => j({
188
- ...k,
185
+ value: p.startDate,
186
+ max: p.endDate || void 0,
187
+ onChange: (e) => A({
188
+ ...p,
189
189
  startDate: e.target.value
190
190
  })
191
- })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
191
+ })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
192
192
  htmlFor: `${U}-end`,
193
193
  className: "text-sm font-medium",
194
194
  children: "End date"
195
- }), /* @__PURE__ */ p(g, {
195
+ }), /* @__PURE__ */ h(v, {
196
196
  id: `${U}-end`,
197
197
  type: "date",
198
198
  className: "mt-1",
199
- value: k.endDate,
200
- min: k.startDate || void 0,
201
- onChange: (e) => j({
202
- ...k,
199
+ value: p.endDate,
200
+ min: p.startDate || void 0,
201
+ onChange: (e) => A({
202
+ ...p,
203
203
  endDate: e.target.value
204
204
  })
205
205
  })] })]
206
206
  }),
207
- B && /* @__PURE__ */ p(r, { message: "The end date is before the start date." }),
208
- /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
207
+ B && /* @__PURE__ */ h(a, { message: "The end date is before the start date." }),
208
+ /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
209
209
  htmlFor: `${U}-description`,
210
210
  className: "text-sm font-medium",
211
211
  children: "Description"
212
- }), /* @__PURE__ */ p("textarea", {
212
+ }), /* @__PURE__ */ h("textarea", {
213
213
  id: `${U}-description`,
214
214
  className: `${G} min-h-[72px]`,
215
- value: k.description,
216
- onChange: (e) => j({
217
- ...k,
215
+ value: p.description,
216
+ onChange: (e) => A({
217
+ ...p,
218
218
  description: e.target.value
219
219
  })
220
220
  })] }),
221
- M && /* @__PURE__ */ p(r, { message: M })
221
+ j && /* @__PURE__ */ h(a, { message: j })
222
222
  ]
223
223
  }),
224
- /* @__PURE__ */ m(b, { children: [/* @__PURE__ */ p(h, {
224
+ /* @__PURE__ */ g(S, { children: [/* @__PURE__ */ h(_, {
225
225
  variant: "outline",
226
- onClick: () => O(!1),
226
+ onClick: () => f(!1),
227
227
  children: "Cancel"
228
- }), /* @__PURE__ */ p(h, {
228
+ }), /* @__PURE__ */ h(_, {
229
229
  onClick: () => void W(),
230
230
  disabled: !V,
231
- children: z ? "Saving…" : n ? "Save item" : "Add item"
231
+ children: z ? "Saving…" : t ? "Save item" : "Add item"
232
232
  })] })
233
233
  ]
234
234
  })]
235
235
  });
236
236
  }
237
- function M({ id: e, title: i }) {
238
- let [a, o] = f(O, {
239
- refetchQueries: T,
237
+ function P({ id: e, title: t }) {
238
+ let [r, o] = m(A, {
239
+ refetchQueries: D,
240
240
  awaitRefetchQueries: !0
241
- }), [c, l] = s(null), d = async () => {
241
+ }), [s, l] = c(null), u = async () => {
242
242
  l(null);
243
243
  try {
244
- await t(a({ variables: { id: e } }));
244
+ await n(r({ variables: { id: e } }));
245
245
  } catch (e) {
246
246
  l(e instanceof Error ? e.message : "The item could not be removed.");
247
247
  }
248
248
  };
249
- return /* @__PURE__ */ m("div", {
249
+ return /* @__PURE__ */ g("div", {
250
250
  className: "flex flex-col items-end",
251
- children: [/* @__PURE__ */ p(n, {
251
+ children: [/* @__PURE__ */ h(i, {
252
252
  label: "",
253
- ariaLabel: `Remove ${i}`,
253
+ ariaLabel: `Remove ${t}`,
254
254
  variant: "outline",
255
- icon: /* @__PURE__ */ p(u, { className: "h-4 w-4" }),
256
- title: `Remove "${i}" from the roadmap?`,
255
+ icon: /* @__PURE__ */ h(f, { className: "h-4 w-4" }),
256
+ title: `Remove "${t}" from the roadmap?`,
257
257
  description: "The item is removed from this roadmap. This cannot be undone here.",
258
258
  confirmLabel: "Remove item",
259
259
  destructive: !0,
260
260
  disabled: o.loading,
261
- onConfirm: () => void d()
262
- }), c && /* @__PURE__ */ p(r, { message: c })]
261
+ onConfirm: () => void u()
262
+ }), s && /* @__PURE__ */ h(a, { message: s })]
263
263
  });
264
264
  }
265
- function N({ roadmapId: i, name: a }) {
266
- let l = e(), [d, g] = f(k, { refetchQueries: ["ListRoadmapsPage", "ListRoadmaps"] }), [_, v] = s(null), y = async () => {
267
- v(null);
265
+ function F({ roadmapId: o, name: s }) {
266
+ let d = e(), [p, v] = m(j, {
267
+ refetchQueries: ["ListRoadmapsPage", "ListRoadmaps"],
268
+ context: t
269
+ }), [y, b] = c(null), x = async () => {
270
+ b(null);
268
271
  try {
269
- await t(d({ variables: { id: i } })), l("/products/roadmaps-page");
272
+ await n(p({ variables: { id: o } })), d("/products/roadmaps-page");
270
273
  } catch (e) {
271
- v(e instanceof Error ? e.message : "The roadmap could not be deleted.");
274
+ b(e instanceof Error ? e.message : "The roadmap could not be deleted.");
272
275
  }
273
276
  };
274
- return /* @__PURE__ */ m("div", {
277
+ return /* @__PURE__ */ g("div", {
275
278
  className: "flex flex-col items-end gap-2",
276
- children: [/* @__PURE__ */ m("div", {
279
+ children: [/* @__PURE__ */ g("div", {
277
280
  className: "flex gap-2",
278
- children: [/* @__PURE__ */ m(h, {
279
- size: "sm",
280
- variant: "outline",
281
- "data-testid": "roadmap-edit",
282
- onClick: () => l(o("/products/edit-product-page", i)),
283
- children: [/* @__PURE__ */ p(c, { className: "mr-1 h-4 w-4" }), "Edit"]
284
- }), /* @__PURE__ */ p(n, {
285
- label: "Delete",
286
- variant: "outline",
287
- icon: /* @__PURE__ */ p(u, { className: "mr-1 h-4 w-4" }),
288
- title: `Delete ${a}?`,
289
- description: "The roadmap and its items are deleted. This cannot be undone here.",
290
- confirmLabel: "Delete roadmap",
291
- destructive: !0,
292
- disabled: g.loading,
293
- onConfirm: () => void y()
294
- })]
295
- }), _ && /* @__PURE__ */ p(r, { message: _ })]
281
+ children: [
282
+ /* @__PURE__ */ g(_, {
283
+ size: "sm",
284
+ variant: "outline",
285
+ "data-testid": "roadmap-edit",
286
+ onClick: () => d(r("/products/edit-product-page", o)),
287
+ children: [/* @__PURE__ */ h(u, { className: "mr-1 h-4 w-4" }), "Edit"]
288
+ }),
289
+ /* @__PURE__ */ g(_, {
290
+ size: "sm",
291
+ variant: "outline",
292
+ "data-testid": "roadmap-analytics",
293
+ onClick: () => d(r("/products/roadmap-analytics-page", o)),
294
+ children: [/* @__PURE__ */ h(l, { className: "mr-1 h-4 w-4" }), "Analytics"]
295
+ }),
296
+ /* @__PURE__ */ h(i, {
297
+ label: "Delete",
298
+ variant: "outline",
299
+ icon: /* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }),
300
+ title: `Delete ${s}?`,
301
+ description: "The roadmap and its items are deleted. This cannot be undone here.",
302
+ confirmLabel: "Delete roadmap",
303
+ destructive: !0,
304
+ disabled: v.loading,
305
+ onConfirm: () => void x()
306
+ })
307
+ ]
308
+ }), y && /* @__PURE__ */ h(a, { message: y })]
296
309
  });
297
310
  }
298
311
  //#endregion
299
- export { M as RemoveRoadmapItemButton, N as RoadmapHeaderActions, j as RoadmapItemDialog };
312
+ export { P as RemoveRoadmapItemButton, F as RoadmapHeaderActions, N as RoadmapItemDialog };
300
313
 
301
314
  //# sourceMappingURL=RoadmapControls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RoadmapControls.js","names":[],"sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { Pencil, Plus, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\n\n/**\n * Roadmap editing (BOFF-7941). Until 2026-09-27 a roadmap was a dead end: no page called\n * planmagnetCreateRoadmapItem / UpdateRoadmapItem / DeleteRoadmapItem, so a new roadmap stayed at 0 items and 0%\n * for good, and the Edit page for the roadmap itself was linked from nowhere.\n */\n\nexport const ROADMAP_ITEM_STATUSES = [\n 'PLANNED',\n 'IN_PROGRESS',\n 'AT_RISK',\n 'DELAYED',\n 'COMPLETED',\n 'CANCELLED',\n] as const;\n\nexport interface RoadmapItemValues {\n id: string;\n title: string;\n description: string | null;\n status: string;\n swimlane: string | null;\n startDate: string | null;\n endDate: string | null;\n}\n\n/** Every query that shows a roadmap's items, counts or progress. */\nconst ROADMAP_QUERIES = [\n 'GetRoadmapDetailPage',\n 'GetRoadmapDetail',\n 'ListRoadmapsPage',\n 'ListRoadmaps',\n 'GetRoadmapAnalytics',\n];\n\nconst CREATE_ITEM = gql`\n mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {\n planmagnetCreateRoadmapItem(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_ITEM = gql`\n mutation RoadmapControlsUpdateItem($id: ID!, $input: PlanmagnetUpdateRoadmapItemInput!) {\n planmagnetUpdateRoadmapItem(id: $id, input: $input) {\n id\n title\n description\n status\n swimlane\n startDate\n endDate\n }\n }\n`;\nconst DELETE_ITEM = gql`\n mutation RoadmapControlsDeleteItem($id: ID!) {\n planmagnetDeleteRoadmapItem(id: $id)\n }\n`;\nconst DELETE_ROADMAP = gql`\n mutation RoadmapControlsDelete($id: ID!) {\n planmagnetDeleteRoadmap(id: $id)\n }\n`;\n\nconst day = (iso: string | null | undefined) => (iso ? iso.slice(0, 10) : '');\n\n/**\n * Add an item (no `item`) or edit one. Dates are sent only when they changed: the service checks the pair the item\n * would end up with, so re-sending untouched dates of an older item stored inverted would refuse a plain rename.\n */\nexport function RoadmapItemDialog({\n roadmapId,\n item,\n lanes,\n}: {\n roadmapId: string;\n item?: RoadmapItemValues;\n lanes: string[];\n}) {\n const initial = {\n title: item?.title ?? '',\n description: item?.description ?? '',\n status: item?.status ?? 'PLANNED',\n swimlane: item?.swimlane ?? '',\n startDate: day(item?.startDate),\n endDate: day(item?.endDate),\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: ROADMAP_QUERIES, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_ITEM, options);\n const [update, updateState] = useMutation(UPDATE_ITEM, options);\n const saving = createState.loading || updateState.loading;\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n const canSave = form.title.trim() !== '' && !invalidDates && !saving;\n const statuses = (ROADMAP_ITEM_STATUSES as readonly string[]).includes(form.status)\n ? ROADMAP_ITEM_STATUSES\n : [form.status, ...ROADMAP_ITEM_STATUSES];\n const idPrefix = item ? `roadmap-item-${item.id}` : 'roadmap-item-new';\n\n const save = async () => {\n setError(null);\n const common = {\n title: form.title.trim(),\n description: form.description.trim() || null,\n status: form.status,\n swimlane: form.swimlane.trim() || null,\n };\n try {\n if (item) {\n const dates = {\n ...(form.startDate !== initial.startDate && { startDate: form.startDate || null }),\n ...(form.endDate !== initial.endDate && { endDate: form.endDate || null }),\n };\n await throwOnError(update({ variables: { id: item.id, input: { ...common, ...dates } } }));\n } else {\n await throwOnError(\n create({\n variables: {\n input: {\n roadmapId,\n ...common,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n },\n },\n })\n );\n setForm(initial);\n }\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be saved.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {item ? (\n <Button size=\"sm\" variant=\"ghost\" aria-label={`Edit ${item.title}`}>\n <Pencil className=\"h-4 w-4\" />\n </Button>\n ) : (\n <Button size=\"sm\" data-testid=\"roadmap-add-item\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add item\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {item ? 'Edit roadmap item' : 'Add roadmap item'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Items in the same swimlane are grouped together on the roadmap.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-title`} className=\"text-sm font-medium\">\n Title\n </label>\n <Input\n id={`${idPrefix}-title`}\n className=\"mt-1\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-lane`} className=\"text-sm font-medium\">\n Swimlane\n </label>\n <Input\n id={`${idPrefix}-lane`}\n className=\"mt-1\"\n list={`${idPrefix}-lanes`}\n placeholder=\"e.g. Q4\"\n value={form.swimlane}\n onChange={(e) => setForm({ ...form, swimlane: e.target.value })}\n />\n <datalist id={`${idPrefix}-lanes`}>\n {lanes.map((lane) => (\n <option key={lane} value={lane} />\n ))}\n </datalist>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id={`${idPrefix}-status`}\n className={field}\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-start`} className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id={`${idPrefix}-start`}\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-end`} className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id={`${idPrefix}-end`}\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor={`${idPrefix}-description`} className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id={`${idPrefix}-description`}\n className={`${field} min-h-[72px]`}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSave}>\n {saving ? 'Saving…' : item ? 'Save item' : 'Add item'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function RemoveRoadmapItemButton({ id, title }: { id: string; title: string }) {\n const [remove, state] = useMutation(DELETE_ITEM, {\n refetchQueries: ROADMAP_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const onRemove = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be removed.');\n }\n };\n return (\n <div className=\"flex flex-col items-end\">\n <ConfirmButton\n label=\"\"\n ariaLabel={`Remove ${title}`}\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title={`Remove \"${title}\" from the roadmap?`}\n description=\"The item is removed from this roadmap. This cannot be undone here.\"\n confirmLabel=\"Remove item\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onRemove()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit (the roadmap's own fields live on the Edit page) and Delete for the roadmap header. */\nexport function RoadmapHeaderActions({ roadmapId, name }: { roadmapId: string; name: string }) {\n const navigate = usePlanMagnetNavigate();\n const [remove, state] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmapsPage', 'ListRoadmaps'],\n });\n const [error, setError] = useState<string | null>(null);\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: roadmapId } }));\n navigate('/products/roadmaps-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The roadmap could not be deleted.');\n }\n };\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"roadmap-edit\"\n onClick={() => navigate(detailPath('/products/edit-product-page', roadmapId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${name}?`}\n description=\"The roadmap and its items are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete roadmap\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RoadmapControls.js","names":[],"sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { BarChart3, Pencil, Plus, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\n\n/**\n * Roadmap editing (BOFF-7941). Until 2026-09-27 a roadmap was a dead end: no page called\n * planmagnetCreateRoadmapItem / UpdateRoadmapItem / DeleteRoadmapItem, so a new roadmap stayed at 0 items and 0%\n * for good, and the Edit page for the roadmap itself was linked from nowhere.\n */\n\nexport const ROADMAP_ITEM_STATUSES = [\n 'PLANNED',\n 'IN_PROGRESS',\n 'AT_RISK',\n 'DELAYED',\n 'COMPLETED',\n 'CANCELLED',\n] as const;\n\nexport interface RoadmapItemValues {\n id: string;\n title: string;\n description: string | null;\n status: string;\n swimlane: string | null;\n startDate: string | null;\n endDate: string | null;\n}\n\n/** Every query that shows a roadmap's items, counts or progress. */\nconst ROADMAP_QUERIES = [\n 'GetRoadmapDetailPage',\n 'GetRoadmapDetail',\n 'ListRoadmapsPage',\n 'ListRoadmaps',\n 'GetRoadmapAnalytics',\n];\n\nconst CREATE_ITEM = gql`\n mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {\n planmagnetCreateRoadmapItem(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_ITEM = gql`\n mutation RoadmapControlsUpdateItem($id: ID!, $input: PlanmagnetUpdateRoadmapItemInput!) {\n planmagnetUpdateRoadmapItem(id: $id, input: $input) {\n id\n title\n description\n status\n swimlane\n startDate\n endDate\n }\n }\n`;\nconst DELETE_ITEM = gql`\n mutation RoadmapControlsDeleteItem($id: ID!) {\n planmagnetDeleteRoadmapItem(id: $id)\n }\n`;\nconst DELETE_ROADMAP = gql`\n mutation RoadmapControlsDelete($id: ID!) {\n planmagnetDeleteRoadmap(id: $id)\n }\n`;\n\nconst day = (iso: string | null | undefined) => (iso ? iso.slice(0, 10) : '');\n\n/**\n * Add an item (no `item`) or edit one. Dates are sent only when they changed: the service checks the pair the item\n * would end up with, so re-sending untouched dates of an older item stored inverted would refuse a plain rename.\n */\nexport function RoadmapItemDialog({\n roadmapId,\n item,\n lanes,\n}: {\n roadmapId: string;\n item?: RoadmapItemValues;\n lanes: string[];\n}) {\n const initial = {\n title: item?.title ?? '',\n description: item?.description ?? '',\n status: item?.status ?? 'PLANNED',\n swimlane: item?.swimlane ?? '',\n startDate: day(item?.startDate),\n endDate: day(item?.endDate),\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: ROADMAP_QUERIES, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_ITEM, options);\n const [update, updateState] = useMutation(UPDATE_ITEM, options);\n const saving = createState.loading || updateState.loading;\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n const canSave = form.title.trim() !== '' && !invalidDates && !saving;\n const statuses = (ROADMAP_ITEM_STATUSES as readonly string[]).includes(form.status)\n ? ROADMAP_ITEM_STATUSES\n : [form.status, ...ROADMAP_ITEM_STATUSES];\n const idPrefix = item ? `roadmap-item-${item.id}` : 'roadmap-item-new';\n\n const save = async () => {\n setError(null);\n const common = {\n title: form.title.trim(),\n description: form.description.trim() || null,\n status: form.status,\n swimlane: form.swimlane.trim() || null,\n };\n try {\n if (item) {\n const dates = {\n ...(form.startDate !== initial.startDate && { startDate: form.startDate || null }),\n ...(form.endDate !== initial.endDate && { endDate: form.endDate || null }),\n };\n await throwOnError(update({ variables: { id: item.id, input: { ...common, ...dates } } }));\n } else {\n await throwOnError(\n create({\n variables: {\n input: {\n roadmapId,\n ...common,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n },\n },\n })\n );\n setForm(initial);\n }\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be saved.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {item ? (\n <Button size=\"sm\" variant=\"ghost\" aria-label={`Edit ${item.title}`}>\n <Pencil className=\"h-4 w-4\" />\n </Button>\n ) : (\n <Button size=\"sm\" data-testid=\"roadmap-add-item\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add item\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {item ? 'Edit roadmap item' : 'Add roadmap item'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Items in the same swimlane are grouped together on the roadmap.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-title`} className=\"text-sm font-medium\">\n Title\n </label>\n <Input\n id={`${idPrefix}-title`}\n className=\"mt-1\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-lane`} className=\"text-sm font-medium\">\n Swimlane\n </label>\n <Input\n id={`${idPrefix}-lane`}\n className=\"mt-1\"\n list={`${idPrefix}-lanes`}\n placeholder=\"e.g. Q4\"\n value={form.swimlane}\n onChange={(e) => setForm({ ...form, swimlane: e.target.value })}\n />\n <datalist id={`${idPrefix}-lanes`}>\n {lanes.map((lane) => (\n <option key={lane} value={lane} />\n ))}\n </datalist>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id={`${idPrefix}-status`}\n className={field}\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-start`} className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id={`${idPrefix}-start`}\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-end`} className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id={`${idPrefix}-end`}\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor={`${idPrefix}-description`} className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id={`${idPrefix}-description`}\n className={`${field} min-h-[72px]`}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSave}>\n {saving ? 'Saving…' : item ? 'Save item' : 'Add item'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function RemoveRoadmapItemButton({ id, title }: { id: string; title: string }) {\n const [remove, state] = useMutation(DELETE_ITEM, {\n refetchQueries: ROADMAP_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const onRemove = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be removed.');\n }\n };\n return (\n <div className=\"flex flex-col items-end\">\n <ConfirmButton\n label=\"\"\n ariaLabel={`Remove ${title}`}\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title={`Remove \"${title}\" from the roadmap?`}\n description=\"The item is removed from this roadmap. This cannot be undone here.\"\n confirmLabel=\"Remove item\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onRemove()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit (the roadmap's own fields live on the Edit page) and Delete for the roadmap header. */\nexport function RoadmapHeaderActions({ roadmapId, name }: { roadmapId: string; name: string }) {\n const navigate = usePlanMagnetNavigate();\n const [remove, state] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmapsPage', 'ListRoadmaps'],\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: roadmapId } }));\n navigate('/products/roadmaps-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The roadmap could not be deleted.');\n }\n };\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"roadmap-edit\"\n onClick={() => navigate(detailPath('/products/edit-product-page', roadmapId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n {/* BOFF-7983: the analytics page existed with no way in. */}\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"roadmap-analytics\"\n onClick={() => navigate(detailPath('/products/roadmap-analytics-page', roadmapId))}\n >\n <BarChart3 className=\"mr-1 h-4 w-4\" />\n Analytics\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${name}?`}\n description=\"The roadmap and its items are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete roadmap\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AACxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQ3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AA8BD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,UAAU,EACV,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,kDA2DA;AAkCD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAQD,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,SAAS,EACT,MAAM,EACN,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,2CA2EA;AAqKD,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,2CAwMvF"}
1
+ {"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AACxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQ3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AA8BD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,UAAU,EACV,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,kDA2DA;AAkCD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAQD,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,SAAS,EACT,MAAM,EACN,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,2CAiFA;AAmLD,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,2CAwMvF"}