@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.
- package/dist/generated/global-types.d.ts +9 -1
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-operations.d.ts +0 -12
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +14 -10
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +149 -127
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +142 -139
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeatureTriage.d.ts +21 -0
- package/dist/planmagnet/controls/FeatureTriage.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeatureTriage.js +398 -0
- package/dist/planmagnet/controls/FeatureTriage.js.map +1 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.js +76 -73
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +47 -44
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +47 -44
- package/dist/planmagnet/controls/ReleaseControls.js.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +68 -65
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/hooks/useCounts.d.ts +39 -0
- package/dist/planmagnet/hooks/useCounts.d.ts.map +1 -0
- package/dist/planmagnet/hooks/useCounts.js +209 -0
- package/dist/planmagnet/hooks/useCounts.js.map +1 -0
- package/dist/planmagnet/import/exportColumns.d.ts +52 -0
- package/dist/planmagnet/import/exportColumns.d.ts.map +1 -0
- package/dist/planmagnet/import/exportColumns.js +81 -0
- package/dist/planmagnet/import/exportColumns.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +255 -242
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +199 -160
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +147 -144
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js +2 -7
- package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +173 -173
- package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +120 -117
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +97 -117
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +168 -103
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +117 -103
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +4 -8
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +85 -90
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +12 -2
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -2
- package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +48 -71
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +17 -13
- package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +11 -8
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +152 -139
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +75 -74
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts +7 -0
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +39 -201
- package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +118 -99
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksImportPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js +309 -0
- package/dist/planmagnet/pages/TasksImportPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +110 -104
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +99 -177
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -4
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -1
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +10 -4
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +151 -138
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +32 -30
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/dist/planmagnet/utils/mutationResult.d.ts +10 -0
- package/dist/planmagnet/utils/mutationResult.d.ts.map +1 -1
- package/dist/planmagnet/utils/mutationResult.js +2 -1
- package/dist/planmagnet/utils/mutationResult.js.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.d.ts +2 -0
- package/dist/planmagnet/utils/rowNavigation.d.ts.map +1 -1
- package/dist/planmagnet/utils/rowNavigation.js +9 -8
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- 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()} · {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()} · {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,
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { useState as
|
|
8
|
-
import { Pencil as
|
|
9
|
-
import { gql as
|
|
10
|
-
import { useMutation as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { Button as
|
|
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
|
|
14
|
+
var E = [
|
|
15
15
|
"PLANNED",
|
|
16
16
|
"IN_PROGRESS",
|
|
17
17
|
"AT_RISK",
|
|
18
18
|
"DELAYED",
|
|
19
19
|
"COMPLETED",
|
|
20
20
|
"CANCELLED"
|
|
21
|
-
],
|
|
21
|
+
], D = [
|
|
22
22
|
"GetRoadmapDetailPage",
|
|
23
23
|
"GetRoadmapDetail",
|
|
24
24
|
"ListRoadmapsPage",
|
|
25
25
|
"ListRoadmaps",
|
|
26
26
|
"GetRoadmapAnalytics"
|
|
27
|
-
],
|
|
27
|
+
], O = p`
|
|
28
28
|
mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {
|
|
29
29
|
planmagnetCreateRoadmapItem(input: $input) {
|
|
30
30
|
id
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
`,
|
|
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
|
-
`,
|
|
45
|
+
`, A = p`
|
|
46
46
|
mutation RoadmapControlsDeleteItem($id: ID!) {
|
|
47
47
|
planmagnetDeleteRoadmapItem(id: $id)
|
|
48
48
|
}
|
|
49
|
-
`,
|
|
49
|
+
`, j = p`
|
|
50
50
|
mutation RoadmapControlsDelete($id: ID!) {
|
|
51
51
|
planmagnetDeleteRoadmap(id: $id)
|
|
52
52
|
}
|
|
53
|
-
`,
|
|
54
|
-
function
|
|
55
|
-
let
|
|
56
|
-
title:
|
|
57
|
-
description:
|
|
58
|
-
status:
|
|
59
|
-
swimlane:
|
|
60
|
-
startDate:
|
|
61
|
-
endDate:
|
|
62
|
-
}, [
|
|
63
|
-
refetchQueries:
|
|
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] =
|
|
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:
|
|
69
|
-
description:
|
|
70
|
-
status:
|
|
71
|
-
swimlane:
|
|
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 (
|
|
74
|
+
if (t) {
|
|
75
75
|
let e = {
|
|
76
|
-
...
|
|
77
|
-
...
|
|
76
|
+
...p.startDate !== i.startDate && { startDate: p.startDate || null },
|
|
77
|
+
...p.endDate !== i.endDate && { endDate: p.endDate || null }
|
|
78
78
|
};
|
|
79
|
-
await
|
|
80
|
-
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
|
|
86
|
+
} else await n(F({ variables: { input: {
|
|
87
87
|
roadmapId: e,
|
|
88
88
|
...r,
|
|
89
|
-
startDate:
|
|
90
|
-
endDate:
|
|
91
|
-
} } })),
|
|
92
|
-
|
|
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__ */
|
|
98
|
-
open:
|
|
97
|
+
return /* @__PURE__ */ g(y, {
|
|
98
|
+
open: l,
|
|
99
99
|
onOpenChange: (e) => {
|
|
100
|
-
|
|
100
|
+
f(e), e ? A(i) : N(null);
|
|
101
101
|
},
|
|
102
|
-
children: [/* @__PURE__ */
|
|
102
|
+
children: [/* @__PURE__ */ h(T, {
|
|
103
103
|
asChild: !0,
|
|
104
|
-
children:
|
|
104
|
+
children: t ? /* @__PURE__ */ h(_, {
|
|
105
105
|
size: "sm",
|
|
106
106
|
variant: "ghost",
|
|
107
|
-
"aria-label": `Edit ${
|
|
108
|
-
children: /* @__PURE__ */
|
|
109
|
-
}) : /* @__PURE__ */
|
|
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__ */
|
|
112
|
+
children: [/* @__PURE__ */ h(d, { className: "mr-1 h-4 w-4" }), "Add item"]
|
|
113
113
|
})
|
|
114
|
-
}), /* @__PURE__ */
|
|
114
|
+
}), /* @__PURE__ */ g(b, {
|
|
115
115
|
className: "max-w-lg",
|
|
116
116
|
children: [
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
125
|
+
}), /* @__PURE__ */ h(v, {
|
|
126
126
|
id: `${U}-title`,
|
|
127
127
|
className: "mt-1",
|
|
128
|
-
value:
|
|
129
|
-
onChange: (e) =>
|
|
130
|
-
...
|
|
128
|
+
value: p.title,
|
|
129
|
+
onChange: (e) => A({
|
|
130
|
+
...p,
|
|
131
131
|
title: e.target.value
|
|
132
132
|
})
|
|
133
133
|
})] }),
|
|
134
|
-
/* @__PURE__ */
|
|
134
|
+
/* @__PURE__ */ g("div", {
|
|
135
135
|
className: "grid grid-cols-2 gap-3",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
137
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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:
|
|
148
|
-
onChange: (e) =>
|
|
149
|
-
...
|
|
147
|
+
value: p.swimlane,
|
|
148
|
+
onChange: (e) => A({
|
|
149
|
+
...p,
|
|
150
150
|
swimlane: e.target.value
|
|
151
151
|
})
|
|
152
152
|
}),
|
|
153
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ h("datalist", {
|
|
154
154
|
id: `${U}-lanes`,
|
|
155
|
-
children:
|
|
155
|
+
children: r.map((e) => /* @__PURE__ */ h("option", { value: e }, e))
|
|
156
156
|
})
|
|
157
|
-
] }), /* @__PURE__ */
|
|
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__ */
|
|
161
|
+
}), /* @__PURE__ */ h("select", {
|
|
162
162
|
id: `${U}-status`,
|
|
163
163
|
className: G,
|
|
164
|
-
value:
|
|
165
|
-
onChange: (e) =>
|
|
166
|
-
...
|
|
164
|
+
value: p.status,
|
|
165
|
+
onChange: (e) => A({
|
|
166
|
+
...p,
|
|
167
167
|
status: e.target.value
|
|
168
168
|
}),
|
|
169
|
-
children: H.map((e) => /* @__PURE__ */
|
|
169
|
+
children: H.map((e) => /* @__PURE__ */ h("option", {
|
|
170
170
|
value: e,
|
|
171
|
-
children:
|
|
171
|
+
children: s(e)
|
|
172
172
|
}, e))
|
|
173
173
|
})] })]
|
|
174
174
|
}),
|
|
175
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ g("div", {
|
|
176
176
|
className: "grid grid-cols-2 gap-3",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
181
|
+
}), /* @__PURE__ */ h(v, {
|
|
182
182
|
id: `${U}-start`,
|
|
183
183
|
type: "date",
|
|
184
184
|
className: "mt-1",
|
|
185
|
-
value:
|
|
186
|
-
max:
|
|
187
|
-
onChange: (e) =>
|
|
188
|
-
...
|
|
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__ */
|
|
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__ */
|
|
195
|
+
}), /* @__PURE__ */ h(v, {
|
|
196
196
|
id: `${U}-end`,
|
|
197
197
|
type: "date",
|
|
198
198
|
className: "mt-1",
|
|
199
|
-
value:
|
|
200
|
-
min:
|
|
201
|
-
onChange: (e) =>
|
|
202
|
-
...
|
|
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__ */
|
|
208
|
-
/* @__PURE__ */
|
|
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__ */
|
|
212
|
+
}), /* @__PURE__ */ h("textarea", {
|
|
213
213
|
id: `${U}-description`,
|
|
214
214
|
className: `${G} min-h-[72px]`,
|
|
215
|
-
value:
|
|
216
|
-
onChange: (e) =>
|
|
217
|
-
...
|
|
215
|
+
value: p.description,
|
|
216
|
+
onChange: (e) => A({
|
|
217
|
+
...p,
|
|
218
218
|
description: e.target.value
|
|
219
219
|
})
|
|
220
220
|
})] }),
|
|
221
|
-
|
|
221
|
+
j && /* @__PURE__ */ h(a, { message: j })
|
|
222
222
|
]
|
|
223
223
|
}),
|
|
224
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ g(S, { children: [/* @__PURE__ */ h(_, {
|
|
225
225
|
variant: "outline",
|
|
226
|
-
onClick: () =>
|
|
226
|
+
onClick: () => f(!1),
|
|
227
227
|
children: "Cancel"
|
|
228
|
-
}), /* @__PURE__ */
|
|
228
|
+
}), /* @__PURE__ */ h(_, {
|
|
229
229
|
onClick: () => void W(),
|
|
230
230
|
disabled: !V,
|
|
231
|
-
children: z ? "Saving…" :
|
|
231
|
+
children: z ? "Saving…" : t ? "Save item" : "Add item"
|
|
232
232
|
})] })
|
|
233
233
|
]
|
|
234
234
|
})]
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
|
-
function
|
|
238
|
-
let [
|
|
239
|
-
refetchQueries:
|
|
237
|
+
function P({ id: e, title: t }) {
|
|
238
|
+
let [r, o] = m(A, {
|
|
239
|
+
refetchQueries: D,
|
|
240
240
|
awaitRefetchQueries: !0
|
|
241
|
-
}), [
|
|
241
|
+
}), [s, l] = c(null), u = async () => {
|
|
242
242
|
l(null);
|
|
243
243
|
try {
|
|
244
|
-
await
|
|
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__ */
|
|
249
|
+
return /* @__PURE__ */ g("div", {
|
|
250
250
|
className: "flex flex-col items-end",
|
|
251
|
-
children: [/* @__PURE__ */
|
|
251
|
+
children: [/* @__PURE__ */ h(i, {
|
|
252
252
|
label: "",
|
|
253
|
-
ariaLabel: `Remove ${
|
|
253
|
+
ariaLabel: `Remove ${t}`,
|
|
254
254
|
variant: "outline",
|
|
255
|
-
icon: /* @__PURE__ */
|
|
256
|
-
title: `Remove "${
|
|
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
|
|
262
|
-
}),
|
|
261
|
+
onConfirm: () => void u()
|
|
262
|
+
}), s && /* @__PURE__ */ h(a, { message: s })]
|
|
263
263
|
});
|
|
264
264
|
}
|
|
265
|
-
function
|
|
266
|
-
let
|
|
267
|
-
|
|
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
|
|
272
|
+
await n(p({ variables: { id: o } })), d("/products/roadmaps-page");
|
|
270
273
|
} catch (e) {
|
|
271
|
-
|
|
274
|
+
b(e instanceof Error ? e.message : "The roadmap could not be deleted.");
|
|
272
275
|
}
|
|
273
276
|
};
|
|
274
|
-
return /* @__PURE__ */
|
|
277
|
+
return /* @__PURE__ */ g("div", {
|
|
275
278
|
className: "flex flex-col items-end gap-2",
|
|
276
|
-
children: [/* @__PURE__ */
|
|
279
|
+
children: [/* @__PURE__ */ g("div", {
|
|
277
280
|
className: "flex gap-2",
|
|
278
|
-
children: [
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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 {
|
|
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":";;;;;;;;;;;;;AA4BA,IAAa,IAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACD,EAaK,IAAkB;CACtB;CACA;CACA;CACA;CACA;CACD,EAEK,IAAc,CAAG;;;;;;GAOjB,IAAc,CAAG;;;;;;;;;;;;GAajB,IAAc,CAAG;;;;GAKjB,IAAiB,CAAG;;;;GAMpB,KAAO,MAAoC,IAAM,EAAI,MAAM,GAAG,GAAG,GAAG;AAM1E,SAAgB,EAAkB,EAChC,cACA,SACA,YAKC;CACD,IAAM,IAAU;EACd,OAAO,GAAM,SAAS;EACtB,aAAa,GAAM,eAAe;EAClC,QAAQ,GAAM,UAAU;EACxB,UAAU,GAAM,YAAY;EAC5B,WAAW,EAAI,GAAM,UAAU;EAC/B,SAAS,EAAI,GAAM,QAAQ;EAC5B,EACK,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAM,KAAW,EAAS,EAAQ,EACnC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAU;EAAE,gBAAgB;EAAiB,qBAAqB;EAAM,EACxE,CAAC,GAAQ,KAAe,EAAY,GAAa,EAAQ,EACzD,CAAC,GAAQ,KAAe,EAAY,GAAa,EAAQ,EACzD,IAAS,EAAY,WAAW,EAAY,SAC5C,IAAe,EAAe,EAAK,WAAW,EAAK,QAAQ,EAC3D,IAAU,EAAK,MAAM,MAAM,KAAK,MAAM,CAAC,KAAgB,CAAC,GACxD,IAAY,EAA4C,SAAS,EAAK,OAAO,GAC/E,IACA,CAAC,EAAK,QAAQ,GAAG,EAAsB,EACrC,IAAW,IAAO,gBAAgB,EAAK,OAAO,oBAE9C,IAAO,YAAY;AACvB,IAAS,KAAK;EACd,IAAM,IAAS;GACb,OAAO,EAAK,MAAM,MAAM;GACxB,aAAa,EAAK,YAAY,MAAM,IAAI;GACxC,QAAQ,EAAK;GACb,UAAU,EAAK,SAAS,MAAM,IAAI;GACnC;AACD,MAAI;AACF,OAAI,GAAM;IACR,IAAM,IAAQ;KACZ,GAAI,EAAK,cAAc,EAAQ,aAAa,EAAE,WAAW,EAAK,aAAa,MAAM;KACjF,GAAI,EAAK,YAAY,EAAQ,WAAW,EAAE,SAAS,EAAK,WAAW,MAAM;KAC1E;AACD,UAAM,EAAa,EAAO,EAAE,WAAW;KAAE,IAAI,EAAK;KAAI,OAAO;MAAE,GAAG;MAAQ,GAAG;MAAO;KAAE,EAAE,CAAC,CAAC;SAc1F,CAZA,MAAM,EACJ,EAAO,EACL,WAAW,EACT,OAAO;IACL;IACA,GAAG;IACH,WAAW,EAAK,aAAa,KAAA;IAC7B,SAAS,EAAK,WAAW,KAAA;IAC1B,EACF,EACF,CAAC,CACH,EACD,EAAQ,EAAQ;AAElB,KAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,+BAA+B;;IAIvE,IAAQ;AACd,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACT,IAAM,EAAQ,EAAQ,GACrB,EAAS,KAAK;;YALvB,CAQE,kBAAC,GAAD;GAAyB,SAAA;aACtB,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,cAAY,QAAQ,EAAK;cACzD,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;IACvB,CAAA,GAET,kBAAC,GAAD;IAAQ,MAAK;IAAK,eAAY;cAA9B,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,WAE1B;;GAEa,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,IAAO,sBAAsB,oBACR,CAAA,EACxB,kBAAC,GAAD,EAAA,UAA6B,mEAEC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS,GAAG,EAAS;OAAS,WAAU;iBAAsB;OAE7D,CAAA,EACR,kBAAC,GAAD;OACE,IAAI,GAAG,EAAS;OAChB,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,OAAO,EAAE,OAAO;QAAO,CAAC;OAC5D,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAS,GAAG,EAAS;SAAQ,WAAU;mBAAsB;SAE5D,CAAA;QACR,kBAAC,GAAD;SACE,IAAI,GAAG,EAAS;SAChB,WAAU;SACV,MAAM,GAAG,EAAS;SAClB,aAAY;SACZ,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,UAAU,EAAE,OAAO;UAAO,CAAC;SAC/D,CAAA;QACF,kBAAC,YAAD;SAAU,IAAI,GAAG,EAAS;mBACvB,EAAM,KAAK,MACV,kBAAC,UAAD,EAAmB,OAAO,GAAQ,EAArB,EAAqB,CAClC;SACO,CAAA;QACP,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAS,GAAG,EAAS;QAAU,WAAU;kBAAsB;QAE9D,CAAA,EACR,kBAAC,UAAD;QACE,IAAI,GAAG,EAAS;QAChB,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAQ;SAAE,GAAG;SAAM,QAAQ,EAAE,OAAO;SAAO,CAAC;kBAE5D,EAAS,KAAK,MACb,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAU,EAAE;SACN,EAFI,EAEJ,CACT;QACK,CAAA,CACL,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAS,GAAG,EAAS;QAAS,WAAU;kBAAsB;QAE7D,CAAA,EACR,kBAAC,GAAD;QACE,IAAI,GAAG,EAAS;QAChB,MAAK;QACL,WAAU;QACV,OAAO,EAAK;QACZ,KAAK,EAAK,WAAW,KAAA;QACrB,WAAW,MAAM,EAAQ;SAAE,GAAG;SAAM,WAAW,EAAE,OAAO;SAAO,CAAC;QAChE,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAS,GAAG,EAAS;QAAO,WAAU;kBAAsB;QAE3D,CAAA,EACR,kBAAC,GAAD;QACE,IAAI,GAAG,EAAS;QAChB,MAAK;QACL,WAAU;QACV,OAAO,EAAK;QACZ,KAAK,EAAK,aAAa,KAAA;QACvB,WAAW,MAAM,EAAQ;SAAE,GAAG;SAAM,SAAS,EAAE,OAAO;SAAO,CAAC;QAC9D,CAAA,CACE,EAAA,CAAA,CACF;;MACL,KAAgB,kBAAC,GAAD,EAAW,SAAQ,0CAA2C,CAAA;MAC/E,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAS,GAAG,EAAS;OAAe,WAAU;iBAAsB;OAEnE,CAAA,EACR,kBAAC,YAAD;OACE,IAAI,GAAG,EAAS;OAChB,WAAW,GAAG,EAAM;OACpB,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,aAAa,EAAE,OAAO;QAAO,CAAC;OAClE,CAAA,CACE,EAAA,CAAA;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;MACnC;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAQ,GAAM;eAAE;KAEhD,CAAA,EACT,kBAAC,GAAD;KAAQ,eAAe,KAAK,GAAM;KAAE,UAAU,CAAC;eAC5C,IAAS,YAAY,IAAO,cAAc;KACpC,CAAA,CACc,EAAA,CAAA;IACD;KACT;;;AAIvB,SAAgB,EAAwB,EAAE,OAAI,YAAwC;CACpF,IAAM,CAAC,GAAQ,KAAS,EAAY,GAAa;EAC/C,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,CAAC;WAC1C,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,iCAAiC;;;AAG/E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GACE,OAAM;GACN,WAAW,UAAU;GACrB,SAAQ;GACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;GACpC,OAAO,WAAW,EAAM;GACxB,aAAY;GACZ,cAAa;GACb,aAAA;GACA,UAAU,EAAM;GAChB,iBAAiB,KAAK,GAAU;GAChC,CAAA,EACD,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC;;;AAKV,SAAgB,EAAqB,EAAE,cAAW,WAA6C;CAC7F,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAS,EAAY,GAAgB;EAClD,gBAAgB,CAAC,oBAAoB,eAAe;EACpD,SAAS;EACV,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,EAC5D,EAAS,0BAA0B;WAC5B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAGlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,+BAA+B,EAAU,CAAC;eAJ/E,CAME,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;IAET,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,oCAAoC,EAAU,CAAC;eAJpF,CAME,kBAAC,GAAD,EAAW,WAAU,gBAAiB,CAAA,EAAA,YAE/B;;IACT,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,UAAU,EAAK;KACtB,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU,EAAM;KAChB,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC"}
|
|
@@ -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,
|
|
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"}
|