@burdenoff/microfe-planmagnet 2026.928.9 → 2026.928.10
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/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +217 -209
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.js +198 -99
- package/dist/planmagnet/pages/ProductsCreateProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +260 -170
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js +283 -0
- package/dist/planmagnet/pages/ProductsEditRoadmapPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +166 -246
- 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 +147 -117
- package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
- package/dist/planmagnet/product/ProductControls.d.ts +52 -0
- package/dist/planmagnet/product/ProductControls.d.ts.map +1 -0
- package/dist/planmagnet/product/ProductControls.js +780 -0
- package/dist/planmagnet/product/ProductControls.js.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductControls.js","names":[],"sources":["../../../src/planmagnet/product/ProductControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Checkbox,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n Badge,\n} from '@burdenoff/fe-libs/ui';\nimport { Archive, ArchiveRestore, Plus, Trash2, X } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\n\n/**\n * Product controls (BOFF-8045). A Product LINKS roadmaps, releases, feature requests and\n * OKR objectives — it never owns their rows (child FKs are nullable, `onDelete: SetNull`\n * server-side) and archiving/deleting a Product never deletes what it links to. Every link\n * mutation here is a pairwise link/unlink (mirrors planmagnetLinkKeyResultToWorkItem in the\n * okr module), not a batch \"set list\" — so adding several candidates at once fires one\n * mutation call per selection.\n */\n\n// ─── Shared refs ────────────────────────────────────────────────────────────\n\nexport interface ProductRoadmapRef {\n id: string;\n name: string;\n}\nexport interface ProductReleaseRef {\n id: string;\n name: string;\n version: string;\n status: string;\n}\nexport interface ProductFeatureRequestRef {\n id: string;\n title: string;\n status: string;\n}\nexport interface ProductObjectiveRef {\n id: string;\n title: string;\n status: string;\n}\n\n/** Every query that lists products or shows a product's detail/links. */\nexport const PRODUCT_QUERIES = ['GetProducts', 'GetProductForEdit', 'GetProductDetail'];\n\n// ─── Header actions (archive / unarchive / delete) ─────────────────────────\n\nconst DELETE_PRODUCT = gql`\n mutation ProductControlsDelete($id: ID!) {\n planmagnetDeleteProduct(id: $id)\n }\n`;\nconst ARCHIVE_PRODUCT = gql`\n mutation ProductControlsArchive($id: ID!) {\n planmagnetArchiveProduct(id: $id) {\n id\n archivedAt\n }\n }\n`;\nconst UNARCHIVE_PRODUCT = gql`\n mutation ProductControlsUnarchive($id: ID!) {\n planmagnetUnarchiveProduct(id: $id) {\n id\n archivedAt\n }\n }\n`;\n\nexport function ProductHeaderActions({\n productId,\n name,\n archivedAt,\n}: {\n productId: string;\n name: string;\n archivedAt: string | null;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_PRODUCT, {\n refetchQueries: PRODUCT_QUERIES,\n context: LEAVES_PAGE_CONTEXT,\n });\n const [archive, archiveState] = useMutation(ARCHIVE_PRODUCT, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [unarchive, unarchiveState] = useMutation(UNARCHIVE_PRODUCT, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: productId } }));\n navigate('/products/products-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The product could not be deleted.');\n }\n };\n\n const onToggleArchive = async () => {\n setError(null);\n try {\n if (archivedAt) {\n await throwOnError(unarchive({ variables: { id: productId } }));\n } else {\n await throwOnError(archive({ variables: { id: productId } }));\n }\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The product could not be updated.');\n }\n };\n\n const archiving = archiveState.loading || unarchiveState.loading;\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=\"product-toggle-archive\"\n disabled={archiving}\n onClick={() => void onToggleArchive()}\n >\n {archivedAt ? (\n <ArchiveRestore className=\"mr-1 h-4 w-4\" />\n ) : (\n <Archive className=\"mr-1 h-4 w-4\" />\n )}\n {archivedAt ? 'Unarchive' : 'Archive'}\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=\"Every linked roadmap, release and feature request stays intact — only its link to this product is cleared. This cannot be undone here.\"\n confirmLabel=\"Delete product\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n// ─── Roadmaps panel ─────────────────────────────────────────────────────────\n\nconst LINK_PRODUCT_ROADMAP = gql`\n mutation ProductControlsLinkRoadmap($productId: ID!, $roadmapId: ID!) {\n planmagnetLinkProductRoadmap(productId: $productId, roadmapId: $roadmapId) {\n id\n }\n }\n`;\nconst UNLINK_PRODUCT_ROADMAP = gql`\n mutation ProductControlsUnlinkRoadmap($productId: ID!, $roadmapId: ID!) {\n planmagnetUnlinkProductRoadmap(productId: $productId, roadmapId: $roadmapId) {\n id\n }\n }\n`;\nconst PRODUCT_CANDIDATE_ROADMAPS = gql`\n query ProductControlsCandidateRoadmaps($workspaceId: ID!) {\n planmagnetRoadmaps(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\nexport function ProductRoadmapsPanel({\n productId,\n roadmaps,\n}: {\n productId: string;\n roadmaps: ProductRoadmapRef[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(UNLINK_PRODUCT_ROADMAP, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (roadmapId: string) => {\n setError(null);\n setRemovingId(roadmapId);\n try {\n await throwOnError(unlink({ variables: { productId, roadmapId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The roadmap could not be unlinked.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-roadmaps\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {roadmaps.length} roadmap{roadmaps.length === 1 ? '' : 's'}\n </p>\n <AddProductRoadmapsDialog productId={productId} existingIds={roadmaps.map((r) => r.id)} />\n </div>\n {error && <ErrorLine message={error} />}\n {roadmaps.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No roadmaps linked to this product yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {roadmaps.map((roadmap) => (\n <li\n key={roadmap.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(detailPath('/products/roadmap-detail-page', roadmap.id))}\n >\n {roadmap.name}\n </button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Unlink ${roadmap.name} from product`}\n disabled={unlinkState.loading}\n onClick={() => void handleRemove(roadmap.id)}\n >\n {removingId === roadmap.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddProductRoadmapsDialog({\n productId,\n existingIds,\n}: {\n productId: string;\n existingIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetRoadmaps: { items: ProductRoadmapRef[]; totalCount: number };\n }>(PRODUCT_CANDIDATE_ROADMAPS, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const candidates = (data?.planmagnetRoadmaps.items ?? []).filter(\n (r) => !existingIds.includes(r.id)\n );\n\n const [link, linkState] = useMutation(LINK_PRODUCT_ROADMAP, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await Promise.all(\n [...selected].map((roadmapId) =>\n throwOnError(link({ variables: { productId, roadmapId } }))\n )\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The roadmaps could not be linked.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"product-add-roadmaps\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add roadmap\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Link roadmaps</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Linking a roadmap does not move or copy it — it stays exactly where it is, now also\n reachable from this product.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div\n className=\"max-h-80 overflow-y-auto space-y-1\"\n data-testid=\"product-add-roadmap-candidates\"\n >\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n Every roadmap in this workspace is already linked to this product.\n </p>\n )}\n {candidates.map((roadmap) => {\n const inputId = `product-add-roadmap-${roadmap.id}`;\n return (\n <label\n key={roadmap.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(roadmap.id)}\n onCheckedChange={(checked) => toggle(roadmap.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">{roadmap.name}</span>\n </label>\n );\n })}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={() => void handleAdd()}\n disabled={selected.size === 0 || linkState.loading}\n data-testid=\"product-add-roadmaps-confirm\"\n >\n {linkState.loading\n ? 'Linking…'\n : `Link ${selected.size} roadmap${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── Releases panel ─────────────────────────────────────────────────────────\n\nconst LINK_PRODUCT_RELEASE = gql`\n mutation ProductControlsLinkRelease($productId: ID!, $releaseId: ID!) {\n planmagnetLinkProductRelease(productId: $productId, releaseId: $releaseId) {\n id\n }\n }\n`;\nconst UNLINK_PRODUCT_RELEASE = gql`\n mutation ProductControlsUnlinkRelease($productId: ID!, $releaseId: ID!) {\n planmagnetUnlinkProductRelease(productId: $productId, releaseId: $releaseId) {\n id\n }\n }\n`;\nconst PRODUCT_CANDIDATE_RELEASES = gql`\n query ProductControlsCandidateReleases($workspaceId: ID!) {\n planmagnetReleases(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n version\n status\n }\n totalCount\n }\n }\n`;\n\nexport function ProductReleasesPanel({\n productId,\n releases,\n}: {\n productId: string;\n releases: ProductReleaseRef[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(UNLINK_PRODUCT_RELEASE, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (releaseId: string) => {\n setError(null);\n setRemovingId(releaseId);\n try {\n await throwOnError(unlink({ variables: { productId, releaseId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The release could not be unlinked.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-releases\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {releases.length} release{releases.length === 1 ? '' : 's'}\n </p>\n <AddProductReleasesDialog productId={productId} existingIds={releases.map((r) => r.id)} />\n </div>\n {error && <ErrorLine message={error} />}\n {releases.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No releases linked to this product yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {releases.map((release) => (\n <li\n key={release.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(detailPath('/products/release-detail-page', release.id))}\n >\n <span className=\"text-muted-foreground mr-2\">{release.version}</span>\n {release.name}\n </button>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {enumLabel(release.status)}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Unlink ${release.name} from product`}\n disabled={unlinkState.loading}\n onClick={() => void handleRemove(release.id)}\n >\n {removingId === release.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddProductReleasesDialog({\n productId,\n existingIds,\n}: {\n productId: string;\n existingIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetReleases: { items: ProductReleaseRef[]; totalCount: number };\n }>(PRODUCT_CANDIDATE_RELEASES, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const candidates = (data?.planmagnetReleases.items ?? []).filter(\n (r) => !existingIds.includes(r.id)\n );\n\n const [link, linkState] = useMutation(LINK_PRODUCT_RELEASE, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await Promise.all(\n [...selected].map((releaseId) =>\n throwOnError(link({ variables: { productId, releaseId } }))\n )\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The releases could not be linked.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"product-add-releases\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add release\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Link releases</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Linking a release does not move or copy it — it stays exactly where it is, now also\n reachable from this product.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div\n className=\"max-h-80 overflow-y-auto space-y-1\"\n data-testid=\"product-add-release-candidates\"\n >\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n Every release in this workspace is already linked to this product.\n </p>\n )}\n {candidates.map((release) => {\n const inputId = `product-add-release-${release.id}`;\n return (\n <label\n key={release.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(release.id)}\n onCheckedChange={(checked) => toggle(release.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">\n <span className=\"text-muted-foreground mr-2\">{release.version}</span>\n {release.name}\n </span>\n </label>\n );\n })}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={() => void handleAdd()}\n disabled={selected.size === 0 || linkState.loading}\n data-testid=\"product-add-releases-confirm\"\n >\n {linkState.loading\n ? 'Linking…'\n : `Link ${selected.size} release${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── Feature requests panel ─────────────────────────────────────────────────\n\nconst LINK_PRODUCT_FEATURE_REQUEST = gql`\n mutation ProductControlsLinkFeatureRequest($productId: ID!, $featureRequestId: ID!) {\n planmagnetLinkProductFeatureRequest(\n productId: $productId\n featureRequestId: $featureRequestId\n ) {\n id\n }\n }\n`;\nconst UNLINK_PRODUCT_FEATURE_REQUEST = gql`\n mutation ProductControlsUnlinkFeatureRequest($productId: ID!, $featureRequestId: ID!) {\n planmagnetUnlinkProductFeatureRequest(\n productId: $productId\n featureRequestId: $featureRequestId\n ) {\n id\n }\n }\n`;\nconst PRODUCT_CANDIDATE_FEATURE_REQUESTS = gql`\n query ProductControlsCandidateFeatureRequests($workspaceId: ID!) {\n planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n title\n status\n }\n totalCount\n }\n }\n`;\n\nexport function ProductFeatureRequestsPanel({\n productId,\n featureRequests,\n}: {\n productId: string;\n featureRequests: ProductFeatureRequestRef[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(UNLINK_PRODUCT_FEATURE_REQUEST, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (featureRequestId: string) => {\n setError(null);\n setRemovingId(featureRequestId);\n try {\n await throwOnError(unlink({ variables: { productId, featureRequestId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The feature request could not be unlinked.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-feature-requests\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {featureRequests.length} feature request{featureRequests.length === 1 ? '' : 's'}\n </p>\n <AddProductFeatureRequestsDialog\n productId={productId}\n existingIds={featureRequests.map((f) => f.id)}\n />\n </div>\n {error && <ErrorLine message={error} />}\n {featureRequests.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No feature requests linked to this product yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {featureRequests.map((feature) => (\n <li\n key={feature.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(detailPath('/products/feature-detail-page', feature.id))}\n >\n {feature.title}\n </button>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"secondary\" className=\"text-xs capitalize\">\n {enumLabel(feature.status)}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Unlink ${feature.title} from product`}\n disabled={unlinkState.loading}\n onClick={() => void handleRemove(feature.id)}\n >\n {removingId === feature.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddProductFeatureRequestsDialog({\n productId,\n existingIds,\n}: {\n productId: string;\n existingIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetFeatureRequests: { items: ProductFeatureRequestRef[]; totalCount: number };\n }>(PRODUCT_CANDIDATE_FEATURE_REQUESTS, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const candidates = (data?.planmagnetFeatureRequests.items ?? []).filter(\n (f) => !existingIds.includes(f.id)\n );\n\n const [link, linkState] = useMutation(LINK_PRODUCT_FEATURE_REQUEST, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await Promise.all(\n [...selected].map((featureRequestId) =>\n throwOnError(link({ variables: { productId, featureRequestId } }))\n )\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The feature requests could not be linked.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"product-add-feature-requests\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add feature request\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Link feature requests</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Linking a feature request does not move or copy it — it stays exactly where it is, now\n also reachable from this product.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div\n className=\"max-h-80 overflow-y-auto space-y-1\"\n data-testid=\"product-add-feature-request-candidates\"\n >\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n Every feature request in this workspace is already linked to this product.\n </p>\n )}\n {candidates.map((feature) => {\n const inputId = `product-add-feature-${feature.id}`;\n return (\n <label\n key={feature.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(feature.id)}\n onCheckedChange={(checked) => toggle(feature.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">{feature.title}</span>\n </label>\n );\n })}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={() => void handleAdd()}\n disabled={selected.size === 0 || linkState.loading}\n data-testid=\"product-add-feature-requests-confirm\"\n >\n {linkState.loading\n ? 'Linking…'\n : `Link ${selected.size} request${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── Objectives (OKR) panel ─────────────────────────────────────────────────\n// Product<->Objective is a true many-to-many join (ProductObjectiveLink), same pairwise-link\n// shape as planmagnetLinkKeyResultToWorkItem. Unlink returns Boolean!, not Product! (§4).\n\nconst LINK_PRODUCT_OBJECTIVE = gql`\n mutation ProductControlsLinkObjective($productId: ID!, $objectiveId: ID!) {\n planmagnetLinkProductObjective(productId: $productId, objectiveId: $objectiveId) {\n id\n }\n }\n`;\nconst UNLINK_PRODUCT_OBJECTIVE = gql`\n mutation ProductControlsUnlinkObjective($productId: ID!, $objectiveId: ID!) {\n planmagnetUnlinkProductObjective(productId: $productId, objectiveId: $objectiveId)\n }\n`;\nconst PRODUCT_CANDIDATE_OBJECTIVES = gql`\n query ProductControlsCandidateObjectives($workspaceId: ID!) {\n planmagnetObjectives(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n title\n status\n }\n totalCount\n }\n }\n`;\n\nexport function ProductObjectivesPanel({\n productId,\n objectives,\n}: {\n productId: string;\n objectives: ProductObjectiveRef[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [unlink, unlinkState] = useMutation(UNLINK_PRODUCT_OBJECTIVE, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const [removingId, setRemovingId] = useState<string | null>(null);\n\n const handleRemove = async (objectiveId: string) => {\n setError(null);\n setRemovingId(objectiveId);\n try {\n await throwOnError(unlink({ variables: { productId, objectiveId } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The objective could not be unlinked.');\n } finally {\n setRemovingId(null);\n }\n };\n\n return (\n <div className=\"space-y-3\" data-testid=\"product-objectives\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {objectives.length} objective{objectives.length === 1 ? '' : 's'}\n </p>\n <AddProductObjectivesDialog\n productId={productId}\n existingIds={objectives.map((o) => o.id)}\n />\n </div>\n {error && <ErrorLine message={error} />}\n {objectives.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No OKR objectives linked to this product yet.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {objectives.map((objective) => (\n <li\n key={objective.id}\n className=\"flex items-center justify-between gap-2 border rounded-md p-2 text-sm\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left hover:underline\"\n onClick={() => navigate(`/goals/objective-detail-page?objectiveId=${objective.id}`)}\n >\n {objective.title}\n </button>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Badge variant=\"secondary\" className=\"text-xs capitalize\">\n {enumLabel(objective.status)}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Unlink ${objective.title} from product`}\n disabled={unlinkState.loading}\n onClick={() => void handleRemove(objective.id)}\n >\n {removingId === objective.id ? '…' : <X className=\"h-4 w-4\" />}\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nfunction AddProductObjectivesDialog({\n productId,\n existingIds,\n}: {\n productId: string;\n existingIds: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [error, setError] = useState<string | null>(null);\n\n const { data, loading } = useQuery<{\n planmagnetObjectives: { items: ProductObjectiveRef[]; totalCount: number };\n }>(PRODUCT_CANDIDATE_OBJECTIVES, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !open || !workspaceId,\n });\n const candidates = (data?.planmagnetObjectives.items ?? []).filter(\n (o) => !existingIds.includes(o.id)\n );\n\n const [link, linkState] = useMutation(LINK_PRODUCT_OBJECTIVE, {\n refetchQueries: PRODUCT_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const toggle = (id: string, checked: boolean) =>\n setSelected((prev) => {\n const next = new Set(prev);\n if (checked) next.add(id);\n else next.delete(id);\n return next;\n });\n\n const reset = () => {\n setSelected(new Set());\n setError(null);\n };\n\n const handleAdd = async () => {\n setError(null);\n try {\n await Promise.all(\n [...selected].map((objectiveId) =>\n throwOnError(link({ variables: { productId, objectiveId } }))\n )\n );\n reset();\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The objectives could not be linked.');\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) reset();\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" data-testid=\"product-add-objectives\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add objective\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Link OKR objectives</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Re-linking an objective that is already linked is a no-op, not an error.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div\n className=\"max-h-80 overflow-y-auto space-y-1\"\n data-testid=\"product-add-objective-candidates\"\n >\n {loading && <p className=\"text-sm text-muted-foreground py-4 text-center\">Loading…</p>}\n {!loading && candidates.length === 0 && (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n Every objective in this workspace is already linked to this product.\n </p>\n )}\n {candidates.map((objective) => {\n const inputId = `product-add-objective-${objective.id}`;\n return (\n <label\n key={objective.id}\n htmlFor={inputId}\n className=\"flex items-center gap-3 border rounded-md p-2 text-sm cursor-pointer hover:bg-muted\"\n >\n <Checkbox\n id={inputId}\n checked={selected.has(objective.id)}\n onCheckedChange={(checked) => toggle(objective.id, checked === true)}\n />\n <span className=\"min-w-0 flex-1 truncate\">{objective.title}</span>\n </label>\n );\n })}\n </div>\n {error && <ErrorLine message={error} />}\n <ResponsiveDialogFooter>\n <Button\n onClick={() => void handleAdd()}\n disabled={selected.size === 0 || linkState.loading}\n data-testid=\"product-add-objectives-confirm\"\n >\n {linkState.loading\n ? 'Linking…'\n : `Link ${selected.size} objective${selected.size === 1 ? '' : 's'}`}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAuDA,IAAa,IAAkB;CAAC;CAAe;CAAqB;CAAmB,EAIjF,IAAiB,CAAG;;;;GAKpB,IAAkB,CAAG;;;;;;;GAQrB,IAAoB,CAAG;;;;;;;;AAS7B,SAAgB,EAAqB,EACnC,cACA,SACA,iBAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAgB;EACxD,gBAAgB;EAChB,SAAS;EACV,CAAC,EACI,CAAC,GAAS,KAAgB,EAAY,GAAiB;EAC3D,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAW,KAAkB,EAAY,GAAmB;EACjE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,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;;IAI5E,IAAkB,YAAY;AAClC,IAAS,KAAK;AACd,MAAI;AACF,GAAI,IACF,MAAM,EAAa,EAAU,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,GAE/D,MAAM,EAAa,EAAQ,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC;WAExD,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAMlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,eAAY;IACZ,UATU,EAAa,WAAW,EAAe;IAUjD,eAAe,KAAK,GAAiB;cALvC,CAQI,EADD,IACE,IAEA,GAFD,EAAgB,WAAU,gBAAiB,CAEP,EAErC,IAAa,cAAc,UACrB;OACT,kBAAC,GAAD;IACE,OAAM;IACN,SAAQ;IACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;IACzC,OAAO,UAAU,EAAK;IACtB,aAAY;IACZ,cAAa;IACb,aAAA;IACA,UAAU,EAAY;IACtB,iBAAiB,KAAK,GAAU;IAChC,CAAA,CACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC;;;AAMV,IAAM,IAAuB,CAAG;;;;;;GAO1B,IAAyB,CAAG;;;;;;GAO5B,IAA6B,CAAG;;;;;;;;;;;AAYtC,SAAgB,EAAqB,EACnC,cACA,eAIC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAwB;EAChE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAe,OAAO,MAAsB;AAEhD,EADA,EAAS,KAAK,EACd,EAAc,EAAU;AACxB,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE;IAAW;IAAW,EAAE,CAAC,CAAC;WAC5D,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,qCAAqC;YACvE;AACR,KAAc,KAAK;;;AAIvB,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,eAAY;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAS;MAAO;MAAS,EAAS,WAAW,IAAI,KAAK;MACrD;QACJ,kBAAC,GAAD;KAAqC;KAAW,aAAa,EAAS,KAAK,MAAM,EAAE,GAAG;KAAI,CAAA,CACtF;;GACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;GACtC,EAAS,WAAW,IACnB,kBAAC,KAAD;IAAG,WAAU;cAAiD;IAE1D,CAAA,GAEJ,kBAAC,MAAD;IAAI,WAAU;cACX,EAAS,KAAK,MACb,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;gBAE/E,EAAQ;MACF,CAAA,EACT,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,cAAY,UAAU,EAAQ,KAAK;MACnC,UAAU,EAAY;MACtB,eAAe,KAAK,EAAa,EAAQ,GAAG;gBAE3C,MAAe,EAAQ,KAAK,MAAM,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MACrD,CAAA,CACN;OAnBE,EAAQ,GAmBV,CACL;IACC,CAAA;GAEH;;;AAIV,SAAS,EAAyB,EAChC,cACA,kBAIC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAU,KAAe,kBAAsB,IAAI,KAAK,CAAC,EAC1D,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,EAAE,SAAM,eAAY,EAEvB,GAA4B;EAC7B,WAAW,EAAE,aAAa,KAAe,IAAI;EAC7C,MAAM,CAAC,KAAQ,CAAC;EACjB,CAAC,EACI,KAAc,GAAM,mBAAmB,SAAS,EAAE,EAAE,QACvD,MAAM,CAAC,EAAY,SAAS,EAAE,GAAG,CACnC,EAEK,CAAC,GAAM,KAAa,EAAY,GAAsB;EAC1D,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EAEI,KAAU,GAAY,MAC1B,GAAa,MAAS;EACpB,IAAM,IAAO,IAAI,IAAI,EAAK;AAG1B,SAFI,IAAS,EAAK,IAAI,EAAG,GACpB,EAAK,OAAO,EAAG,EACb;GACP,EAEE,UAAc;AAElB,EADA,kBAAY,IAAI,KAAK,CAAC,EACtB,EAAS,KAAK;IAGV,IAAY,YAAY;AAC5B,IAAS,KAAK;AACd,MAAI;AAOF,GANA,MAAM,QAAQ,IACZ,CAAC,GAAG,EAAS,CAAC,KAAK,MACjB,EAAa,EAAK,EAAE,WAAW;IAAE;IAAW;IAAW,EAAE,CAAC,CAAC,CAC5D,CACF,EACD,GAAO,EACP,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACR,KAAM,GAAO;;YAJtB,CAOE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,cAE1B;;GACe,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,iBAAqC,CAAA,EAC5D,kBAAC,GAAD,EAAA,UAA6B,oHAGC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KACE,WAAU;KACV,eAAY;eAFd;MAIG,KAAW,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAAY,CAAA;MACrF,CAAC,KAAW,EAAW,WAAW,KACjC,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAE1D,CAAA;MAEL,EAAW,KAAK,MAAY;OAC3B,IAAM,IAAU,uBAAuB,EAAQ;AAC/C,cACE,kBAAC,SAAD;QAEE,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,GAAD;SACE,IAAI;SACJ,SAAS,EAAS,IAAI,EAAQ,GAAG;SACjC,kBAAkB,MAAY,EAAO,EAAQ,IAAI,MAAY,GAAK;SAClE,CAAA,EACF,kBAAC,QAAD;SAAM,WAAU;mBAA2B,EAAQ;SAAY,CAAA,CACzD;UAVD,EAAQ,GAUP;QAEV;MACE;;IACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;IACvC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KACE,eAAe,KAAK,GAAW;KAC/B,UAAU,EAAS,SAAS,KAAK,EAAU;KAC3C,eAAY;eAEX,EAAU,UACP,aACA,QAAQ,EAAS,KAAK,UAAU,EAAS,SAAS,IAAI,KAAK;KACxD,CAAA,EACc,CAAA;IACD;KACT;;;AAMvB,IAAM,IAAuB,CAAG;;;;;;GAO1B,IAAyB,CAAG;;;;;;GAO5B,IAA6B,CAAG;;;;;;;;;;;;;AActC,SAAgB,EAAqB,EACnC,cACA,eAIC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAwB;EAChE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAe,OAAO,MAAsB;AAEhD,EADA,EAAS,KAAK,EACd,EAAc,EAAU;AACxB,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE;IAAW;IAAW,EAAE,CAAC,CAAC;WAC5D,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,qCAAqC;YACvE;AACR,KAAc,KAAK;;;AAIvB,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,eAAY;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAS;MAAO;MAAS,EAAS,WAAW,IAAI,KAAK;MACrD;QACJ,kBAAC,GAAD;KAAqC;KAAW,aAAa,EAAS,KAAK,MAAM,EAAE,GAAG;KAAI,CAAA,CACtF;;GACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;GACtC,EAAS,WAAW,IACnB,kBAAC,KAAD;IAAG,WAAU;cAAiD;IAE1D,CAAA,GAEJ,kBAAC,MAAD;IAAI,WAAU;cACX,EAAS,KAAK,MACb,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;gBAHlF,CAKE,kBAAC,QAAD;OAAM,WAAU;iBAA8B,EAAQ;OAAe,CAAA,EACpE,EAAQ,KACF;SACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAU,EAAQ,OAAO;OACpB,CAAA,EACR,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,cAAY,UAAU,EAAQ,KAAK;OACnC,UAAU,EAAY;OACtB,eAAe,KAAK,EAAa,EAAQ,GAAG;iBAE3C,MAAe,EAAQ,KAAK,MAAM,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;OACrD,CAAA,CACL;QACH;OAzBE,EAAQ,GAyBV,CACL;IACC,CAAA;GAEH;;;AAIV,SAAS,EAAyB,EAChC,cACA,kBAIC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAU,KAAe,kBAAsB,IAAI,KAAK,CAAC,EAC1D,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,EAAE,SAAM,eAAY,EAEvB,GAA4B;EAC7B,WAAW,EAAE,aAAa,KAAe,IAAI;EAC7C,MAAM,CAAC,KAAQ,CAAC;EACjB,CAAC,EACI,KAAc,GAAM,mBAAmB,SAAS,EAAE,EAAE,QACvD,MAAM,CAAC,EAAY,SAAS,EAAE,GAAG,CACnC,EAEK,CAAC,GAAM,KAAa,EAAY,GAAsB;EAC1D,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EAEI,KAAU,GAAY,MAC1B,GAAa,MAAS;EACpB,IAAM,IAAO,IAAI,IAAI,EAAK;AAG1B,SAFI,IAAS,EAAK,IAAI,EAAG,GACpB,EAAK,OAAO,EAAG,EACb;GACP,EAEE,UAAc;AAElB,EADA,kBAAY,IAAI,KAAK,CAAC,EACtB,EAAS,KAAK;IAGV,IAAY,YAAY;AAC5B,IAAS,KAAK;AACd,MAAI;AAOF,GANA,MAAM,QAAQ,IACZ,CAAC,GAAG,EAAS,CAAC,KAAK,MACjB,EAAa,EAAK,EAAE,WAAW;IAAE;IAAW;IAAW,EAAE,CAAC,CAAC,CAC5D,CACF,EACD,GAAO,EACP,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACR,KAAM,GAAO;;YAJtB,CAOE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,cAE1B;;GACe,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,iBAAqC,CAAA,EAC5D,kBAAC,GAAD,EAAA,UAA6B,oHAGC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KACE,WAAU;KACV,eAAY;eAFd;MAIG,KAAW,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAAY,CAAA;MACrF,CAAC,KAAW,EAAW,WAAW,KACjC,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAE1D,CAAA;MAEL,EAAW,KAAK,MAAY;OAC3B,IAAM,IAAU,uBAAuB,EAAQ;AAC/C,cACE,kBAAC,SAAD;QAEE,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,GAAD;SACE,IAAI;SACJ,SAAS,EAAS,IAAI,EAAQ,GAAG;SACjC,kBAAkB,MAAY,EAAO,EAAQ,IAAI,MAAY,GAAK;SAClE,CAAA,EACF,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,QAAD;UAAM,WAAU;oBAA8B,EAAQ;UAAe,CAAA,EACpE,EAAQ,KACJ;WACD;UAbD,EAAQ,GAaP;QAEV;MACE;;IACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;IACvC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KACE,eAAe,KAAK,GAAW;KAC/B,UAAU,EAAS,SAAS,KAAK,EAAU;KAC3C,eAAY;eAEX,EAAU,UACP,aACA,QAAQ,EAAS,KAAK,UAAU,EAAS,SAAS,IAAI,KAAK;KACxD,CAAA,EACc,CAAA;IACD;KACT;;;AAMvB,IAAM,IAA+B,CAAG;;;;;;;;;GAUlC,IAAiC,CAAG;;;;;;;;;GAUpC,IAAqC,CAAG;;;;;;;;;;;;AAa9C,SAAgB,EAA4B,EAC1C,cACA,sBAIC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAgC;EACxE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAe,OAAO,MAA6B;AAEvD,EADA,EAAS,KAAK,EACd,EAAc,EAAiB;AAC/B,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE;IAAW;IAAkB,EAAE,CAAC,CAAC;WACnE,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,6CAA6C;YAC/E;AACR,KAAc,KAAK;;;AAIvB,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,eAAY;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAgB;MAAO;MAAiB,EAAgB,WAAW,IAAI,KAAK;MAC3E;QACJ,kBAAC,GAAD;KACa;KACX,aAAa,EAAgB,KAAK,MAAM,EAAE,GAAG;KAC7C,CAAA,CACE;;GACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;GACtC,EAAgB,WAAW,IAC1B,kBAAC,KAAD;IAAG,WAAU;cAAiD;IAE1D,CAAA,GAEJ,kBAAC,MAAD;IAAI,WAAU;cACX,EAAgB,KAAK,MACpB,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,EAAW,iCAAiC,EAAQ,GAAG,CAAC;gBAE/E,EAAQ;MACF,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;OAAY,WAAU;iBAClC,EAAU,EAAQ,OAAO;OACpB,CAAA,EACR,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,cAAY,UAAU,EAAQ,MAAM;OACpC,UAAU,EAAY;OACtB,eAAe,KAAK,EAAa,EAAQ,GAAG;iBAE3C,MAAe,EAAQ,KAAK,MAAM,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;OACrD,CAAA,CACL;QACH;OAxBE,EAAQ,GAwBV,CACL;IACC,CAAA;GAEH;;;AAIV,SAAS,EAAgC,EACvC,cACA,kBAIC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAU,KAAe,kBAAsB,IAAI,KAAK,CAAC,EAC1D,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,EAAE,SAAM,eAAY,EAEvB,GAAoC;EACrC,WAAW,EAAE,aAAa,KAAe,IAAI;EAC7C,MAAM,CAAC,KAAQ,CAAC;EACjB,CAAC,EACI,KAAc,GAAM,0BAA0B,SAAS,EAAE,EAAE,QAC9D,MAAM,CAAC,EAAY,SAAS,EAAE,GAAG,CACnC,EAEK,CAAC,GAAM,KAAa,EAAY,GAA8B;EAClE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EAEI,KAAU,GAAY,MAC1B,GAAa,MAAS;EACpB,IAAM,IAAO,IAAI,IAAI,EAAK;AAG1B,SAFI,IAAS,EAAK,IAAI,EAAG,GACpB,EAAK,OAAO,EAAG,EACb;GACP,EAEE,UAAc;AAElB,EADA,kBAAY,IAAI,KAAK,CAAC,EACtB,EAAS,KAAK;IAGV,IAAY,YAAY;AAC5B,IAAS,KAAK;AACd,MAAI;AAOF,GANA,MAAM,QAAQ,IACZ,CAAC,GAAG,EAAS,CAAC,KAAK,MACjB,EAAa,EAAK,EAAE,WAAW;IAAE;IAAW;IAAkB,EAAE,CAAC,CAAC,CACnE,CACF,EACD,GAAO,EACP,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,4CAA4C;;;AAI1F,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACR,KAAM,GAAO;;YAJtB,CAOE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B;;GACe,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,yBAA6C,CAAA,EACpE,kBAAC,GAAD,EAAA,UAA6B,4HAGC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KACE,WAAU;KACV,eAAY;eAFd;MAIG,KAAW,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAAY,CAAA;MACrF,CAAC,KAAW,EAAW,WAAW,KACjC,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAE1D,CAAA;MAEL,EAAW,KAAK,MAAY;OAC3B,IAAM,IAAU,uBAAuB,EAAQ;AAC/C,cACE,kBAAC,SAAD;QAEE,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,GAAD;SACE,IAAI;SACJ,SAAS,EAAS,IAAI,EAAQ,GAAG;SACjC,kBAAkB,MAAY,EAAO,EAAQ,IAAI,MAAY,GAAK;SAClE,CAAA,EACF,kBAAC,QAAD;SAAM,WAAU;mBAA2B,EAAQ;SAAa,CAAA,CAC1D;UAVD,EAAQ,GAUP;QAEV;MACE;;IACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;IACvC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KACE,eAAe,KAAK,GAAW;KAC/B,UAAU,EAAS,SAAS,KAAK,EAAU;KAC3C,eAAY;eAEX,EAAU,UACP,aACA,QAAQ,EAAS,KAAK,UAAU,EAAS,SAAS,IAAI,KAAK;KACxD,CAAA,EACc,CAAA;IACD;KACT;;;AAQvB,IAAM,IAAyB,CAAG;;;;;;GAO5B,IAA2B,CAAG;;;;GAK9B,IAA+B,CAAG;;;;;;;;;;;;AAaxC,SAAgB,EAAuB,EACrC,cACA,iBAIC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAA0B;EAClE,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAE3D,IAAe,OAAO,MAAwB;AAElD,EADA,EAAS,KAAK,EACd,EAAc,EAAY;AAC1B,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE;IAAW;IAAa,EAAE,CAAC,CAAC;WAC9D,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,uCAAuC;YACzE;AACR,KAAc,KAAK;;;AAIvB,QACE,kBAAC,OAAD;EAAK,WAAU;EAAY,eAAY;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAW;MAAO;MAAW,EAAW,WAAW,IAAI,KAAK;MAC3D;QACJ,kBAAC,GAAD;KACa;KACX,aAAa,EAAW,KAAK,MAAM,EAAE,GAAG;KACxC,CAAA,CACE;;GACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;GACtC,EAAW,WAAW,IACrB,kBAAC,KAAD;IAAG,WAAU;cAAiD;IAE1D,CAAA,GAEJ,kBAAC,MAAD;IAAI,WAAU;cACX,EAAW,KAAK,MACf,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,4CAA4C,EAAU,KAAK;gBAElF,EAAU;MACJ,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAQ;OAAY,WAAU;iBAClC,EAAU,EAAU,OAAO;OACtB,CAAA,EACR,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,cAAY,UAAU,EAAU,MAAM;OACtC,UAAU,EAAY;OACtB,eAAe,KAAK,EAAa,EAAU,GAAG;iBAE7C,MAAe,EAAU,KAAK,MAAM,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;OACvD,CAAA,CACL;QACH;OAxBE,EAAU,GAwBZ,CACL;IACC,CAAA;GAEH;;;AAIV,SAAS,EAA2B,EAClC,cACA,kBAIC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAU,KAAe,kBAAsB,IAAI,KAAK,CAAC,EAC1D,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,EAAE,SAAM,eAAY,EAEvB,GAA8B;EAC/B,WAAW,EAAE,aAAa,KAAe,IAAI;EAC7C,MAAM,CAAC,KAAQ,CAAC;EACjB,CAAC,EACI,KAAc,GAAM,qBAAqB,SAAS,EAAE,EAAE,QACzD,MAAM,CAAC,EAAY,SAAS,EAAE,GAAG,CACnC,EAEK,CAAC,GAAM,KAAa,EAAY,GAAwB;EAC5D,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EAEI,KAAU,GAAY,MAC1B,GAAa,MAAS;EACpB,IAAM,IAAO,IAAI,IAAI,EAAK;AAG1B,SAFI,IAAS,EAAK,IAAI,EAAG,GACpB,EAAK,OAAO,EAAG,EACb;GACP,EAEE,UAAc;AAElB,EADA,kBAAY,IAAI,KAAK,CAAC,EACtB,EAAS,KAAK;IAGV,IAAY,YAAY;AAC5B,IAAS,KAAK;AACd,MAAI;AAOF,GANA,MAAM,QAAQ,IACZ,CAAC,GAAG,EAAS,CAAC,KAAK,MACjB,EAAa,EAAK,EAAE,WAAW;IAAE;IAAW;IAAa,EAAE,CAAC,CAAC,CAC9D,CACF,EACD,GAAO,EACP,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,sCAAsC;;;AAIpF,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACR,KAAM,GAAO;;YAJtB,CAOE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,gBAE1B;;GACe,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,uBAA2C,CAAA,EAClE,kBAAC,GAAD,EAAA,UAA6B,4EAEC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KACE,WAAU;KACV,eAAY;eAFd;MAIG,KAAW,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAAY,CAAA;MACrF,CAAC,KAAW,EAAW,WAAW,KACjC,kBAAC,KAAD;OAAG,WAAU;iBAAiD;OAE1D,CAAA;MAEL,EAAW,KAAK,MAAc;OAC7B,IAAM,IAAU,yBAAyB,EAAU;AACnD,cACE,kBAAC,SAAD;QAEE,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,GAAD;SACE,IAAI;SACJ,SAAS,EAAS,IAAI,EAAU,GAAG;SACnC,kBAAkB,MAAY,EAAO,EAAU,IAAI,MAAY,GAAK;SACpE,CAAA,EACF,kBAAC,QAAD;SAAM,WAAU;mBAA2B,EAAU;SAAa,CAAA,CAC5D;UAVD,EAAU,GAUT;QAEV;MACE;;IACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;IACvC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KACE,eAAe,KAAK,GAAW;KAC/B,UAAU,EAAS,SAAS,KAAK,EAAU;KAC3C,eAAY;eAEX,EAAU,UACP,aACA,QAAQ,EAAS,KAAK,YAAY,EAAS,SAAS,IAAI,KAAK;KAC1D,CAAA,EACc,CAAA;IACD;KACT"}
|
|
@@ -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,2CAuD5F"}
|
|
@@ -283,7 +283,7 @@ function F({ roadmapId: a, name: s }) {
|
|
|
283
283
|
size: "sm",
|
|
284
284
|
variant: "outline",
|
|
285
285
|
"data-testid": "roadmap-edit",
|
|
286
|
-
onClick: () => d(o("/products/edit-
|
|
286
|
+
onClick: () => d(o("/products/edit-roadmap-page", a)),
|
|
287
287
|
children: [/* @__PURE__ */ h(u, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
288
288
|
}),
|
|
289
289
|
/* @__PURE__ */ g(_, {
|
|
@@ -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 { 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
|
+
{"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 // BOFF-8045: edit-product-page now edits a real Product, not a Roadmap —\n // this button owns the roadmap's own fields, so it has its own page.\n onClick={() => navigate(detailPath('/products/edit-roadmap-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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"}
|