@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsEditProductPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsEditProductPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Button,\n Input,\n Badge,\n} from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft, Package, Loader2, Trash2 } from 'lucide-react';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton } from '../controls/ConfirmButton';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\nimport { PersonName } from '../people/People';\nimport {\n PlanningFocusBadge,\n PlanningFocusSelect,\n type PlanningFocus,\n} from '../roadmaps/planningFocus';\n\nconst GET_ROADMAP = gql`\n query GetRoadmapForEdit($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n workspaceId\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n status\n }\n }\n }\n`;\n\nconst UPDATE_ROADMAP = gql`\n mutation UpdateRoadmap($id: ID!, $input: PlanmagnetUpdateRoadmapInput!) {\n planmagnetUpdateRoadmap(id: $id, input: $input) {\n id\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n updatedAt\n }\n }\n`;\n\nconst DELETE_ROADMAP = gql`\n mutation DeleteRoadmap($id: ID!) {\n planmagnetDeleteRoadmap(id: $id)\n }\n`;\n\nexport function ProductsEditProductPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n const [updateRoadmap, { loading: saving }] = useMutation(UPDATE_ROADMAP, {\n refetchQueries: [\n 'ListRoadmaps',\n 'ListRoadmapsPage',\n 'GetRoadmapForEdit',\n 'GetRoadmapDetail',\n 'GetRoadmapDetailPage',\n ],\n awaitRefetchQueries: true,\n });\n // BOFF-7980: the button said \"Archive\" but deleted, on the first click, without awaiting — a refusal showed nothing.\n const [deleteRoadmap, { loading: deleting }] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmaps', 'ListRoadmapsPage'],\n context: LEAVES_PAGE_CONTEXT,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const roadmap = data?.planmagnetRoadmap;\n\n const defaultForm = useMemo(\n () => ({\n name: roadmap?.name ?? '',\n description: roadmap?.description ?? '',\n planningFocus: (roadmap?.planningFocus ?? null) as PlanningFocus | null,\n isPublic: roadmap?.isPublic ?? false,\n }),\n [roadmap]\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const [formOverrides, setFormOverrides] = useState<Record<string, any>>({});\n const form = { ...defaultForm, ...formOverrides } as {\n name: string;\n description: string;\n planningFocus: PlanningFocus | null;\n isPublic: boolean;\n };\n const setForm = (val: typeof form) => setFormOverrides(val);\n\n // BOFF-7979: an emptied description is cleared (null), not silently kept; success is confirmed.\n const handleSave = async () => {\n setSaveError(null);\n setSaved(false);\n try {\n await throwOnError(\n updateRoadmap({\n variables: {\n id: roadmapId,\n input: {\n name: form.name,\n description: form.description.trim() || null,\n // The display is not editable: the page has one layout (BOFF-7934).\n // null clears a focus that was set by mistake.\n planningFocus: form.planningFocus,\n isPublic: form.isPublic,\n },\n },\n })\n );\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The product could not be saved.');\n }\n };\n\n const handleDelete = async () => {\n setSaveError(null);\n try {\n await throwOnError(deleteRoadmap({ variables: { id: roadmapId } }));\n navigate('/products/products-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The product could not be deleted.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load product: {error.message}</div>;\n }\n\n if (!roadmap) {\n return <div className=\"p-6 text-muted-foreground\">No product found.</div>;\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigate('/products/products-page')}>\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Product</h1>\n <p className=\"text-sm text-muted-foreground\">Editing {roadmap.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Save Changes\n </Button>\n </div>\n\n {saved && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n Changes saved.\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n {saveError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Basic Information</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Product Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <PlanningFocusSelect\n value={form.planningFocus}\n onChange={(planningFocus) => setForm({ ...form, planningFocus })}\n />\n <div>\n <label className=\"text-sm font-medium\">Visibility</label>\n <div className=\"mt-1 flex gap-2\">\n {[false, true].map((pub) => (\n <button\n key={String(pub)}\n onClick={() => setForm({ ...form, isPublic: pub })}\n className={`rounded-md px-3 py-1.5 text-sm ${form.isPublic === pub ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {pub ? 'Public' : 'Private'}\n </button>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Product Info</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"h-4 w-4 text-primary\" />\n <span className=\"font-medium\">{roadmap.name}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Items</span>\n <span className=\"font-medium\">{roadmap.items?.length ?? 0}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Planning focus</span>\n <PlanningFocusBadge focus={roadmap.planningFocus} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Visibility</span>\n <span className=\"font-medium\">{roadmap.isPublic ? 'Public' : 'Private'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span className=\"font-medium\">\n <PersonName id={roadmap.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(roadmap.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(roadmap.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Danger Zone</CardTitle>\n </CardHeader>\n <CardContent>\n <ConfirmButton\n label={deleting ? 'Deleting…' : 'Delete product'}\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${roadmap.name}?`}\n description=\"The product's roadmap and its items are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete product\"\n destructive\n disabled={deleting}\n onConfirm={() => void handleDelete()}\n />\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsEditProductPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsEditProductPagePage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft, Package, Loader2 } from 'lucide-react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ProductHeaderActions } from '../product/ProductControls';\n\n/**\n * Edit a Product's own fields (BOFF-8045): name, description, vision, lifecycle stage,\n * owner and North Star metric. Archive/unarchive and delete live in ProductHeaderActions\n * (shared with the detail page). Lifecycle stage has no enforced transition graph — any\n * stage to any stage is a valid save, deliberately (the owner decision only asks for the\n * field, not a workflow).\n */\n\nconst PRODUCT_LIFECYCLE_STAGES = [\n 'CONCEPT',\n 'DEVELOPMENT',\n 'BETA',\n 'LAUNCHED',\n 'MATURE',\n 'DEPRECATED',\n] as const;\ntype ProductLifecycleStage = (typeof PRODUCT_LIFECYCLE_STAGES)[number];\n\nconst GET_PRODUCT = gql`\n query GetProductForEdit($id: ID!) {\n planmagnetProduct(id: $id) {\n id\n workspaceId\n name\n description\n vision\n lifecycleStage\n ownerId\n archivedAt\n northStar {\n name\n unit\n target\n current\n window\n }\n createdAt\n updatedAt\n }\n }\n`;\n\nconst UPDATE_PRODUCT = gql`\n mutation UpdateProduct($id: ID!, $input: PlanmagnetUpdateProductInput!) {\n planmagnetUpdateProduct(id: $id, input: $input) {\n id\n name\n description\n vision\n lifecycleStage\n ownerId\n northStar {\n name\n unit\n target\n current\n window\n }\n updatedAt\n }\n }\n`;\n\ninterface ProductNorthStarFull {\n name: string;\n unit: string;\n target: number;\n current: number;\n window: string;\n}\n\ninterface ProductForEdit {\n id: string;\n workspaceId: string;\n name: string;\n description: string | null;\n vision: string;\n lifecycleStage: string;\n ownerId: string;\n archivedAt: string | null;\n northStar: ProductNorthStarFull | null;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface EditProductForm {\n name: string;\n description: string;\n vision: string;\n lifecycleStage: ProductLifecycleStage;\n}\n\ninterface NorthStarForm {\n name: string;\n unit: string;\n target: string;\n current: string;\n window: string;\n}\n\nconst EMPTY_NORTH_STAR: NorthStarForm = { name: '', unit: '', target: '', current: '', window: '' };\n\nexport function ProductsEditProductPagePage() {\n const navigate = usePlanMagnetNavigate();\n const productId = useEntityId('productId') ?? '';\n\n const { data, loading, error } = useQuery<{ planmagnetProduct: ProductForEdit | null }>(\n GET_PRODUCT,\n { variables: { id: productId }, skip: !productId }\n );\n\n const [updateProduct, { loading: saving }] = useMutation(UPDATE_PRODUCT, {\n refetchQueries: ['GetProducts', 'GetProductForEdit', 'GetProductDetail'],\n awaitRefetchQueries: true,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const product = data?.planmagnetProduct;\n\n const defaultForm = useMemo<EditProductForm>(\n () => ({\n name: product?.name ?? '',\n description: product?.description ?? '',\n vision: product?.vision ?? '',\n lifecycleStage: (product?.lifecycleStage ?? 'CONCEPT') as ProductLifecycleStage,\n }),\n [product]\n );\n const defaultNorthStar = useMemo<NorthStarForm>(\n () =>\n product?.northStar\n ? {\n name: product.northStar.name,\n unit: product.northStar.unit,\n target: String(product.northStar.target),\n current: String(product.northStar.current),\n window: product.northStar.window,\n }\n : EMPTY_NORTH_STAR,\n [product]\n );\n\n const [formOverrides, setFormOverrides] = useState<Partial<EditProductForm>>({});\n const form: EditProductForm = { ...defaultForm, ...formOverrides };\n const setForm = (val: EditProductForm) => setFormOverrides(val);\n\n const [northStarOverrides, setNorthStarOverrides] = useState<Partial<NorthStarForm>>({});\n const northStar: NorthStarForm = { ...defaultNorthStar, ...northStarOverrides };\n const setNorthStar = (val: NorthStarForm) => setNorthStarOverrides(val);\n\n const northStarStarted = Object.values(northStar).some((v) => v.trim() !== '');\n const northStarComplete =\n northStar.name.trim() &&\n northStar.unit.trim() &&\n northStar.window.trim() &&\n northStar.target.trim() !== '' &&\n !Number.isNaN(Number(northStar.target)) &&\n northStar.current.trim() !== '' &&\n !Number.isNaN(Number(northStar.current));\n\n // An emptied description is cleared (null), not silently kept; success is confirmed.\n const handleSave = async () => {\n setSaveError(null);\n setSaved(false);\n try {\n await throwOnError(\n updateProduct({\n variables: {\n id: productId,\n input: {\n name: form.name,\n description: form.description.trim() || null,\n vision: form.vision,\n lifecycleStage: form.lifecycleStage,\n ...(northStarComplete\n ? {\n northStar: {\n name: northStar.name,\n unit: northStar.unit,\n target: Number(northStar.target),\n current: Number(northStar.current),\n window: northStar.window,\n },\n }\n : {}),\n },\n },\n })\n );\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The product could not be saved.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it\n // unmounts every component on it and loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load product: {error.message}</div>;\n }\n\n if (!product) {\n return <div className=\"p-6 text-muted-foreground\">No product found.</div>;\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => navigate(detailPath('/products/product-detail-page', product.id))}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Product</h1>\n <p className=\"text-sm text-muted-foreground\">Editing {product.name}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <Button\n size=\"sm\"\n onClick={() => void handleSave()}\n disabled={saving || (northStarStarted && !northStarComplete)}\n >\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Save Changes\n </Button>\n <ProductHeaderActions\n productId={product.id}\n name={product.name}\n archivedAt={product.archivedAt}\n />\n </div>\n </div>\n\n {saved && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n Changes saved.\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n {saveError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Basic Information</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Product Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[60px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Vision</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.vision}\n onChange={(e) => setForm({ ...form, vision: e.target.value })}\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Lifecycle Stage</label>\n <select\n aria-label=\"Lifecycle stage\"\n data-testid=\"product-lifecycle-stage\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={form.lifecycleStage}\n onChange={(e) =>\n setForm({ ...form, lifecycleStage: e.target.value as ProductLifecycleStage })\n }\n >\n {PRODUCT_LIFECYCLE_STAGES.map((stage) => (\n <option key={stage} value={stage}>\n {enumLabel(stage)}\n </option>\n ))}\n </select>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>North Star metric</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Metric name</label>\n <Input\n value={northStar.name}\n onChange={(e) => setNorthStar({ ...northStar, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Unit</label>\n <Input\n value={northStar.unit}\n onChange={(e) => setNorthStar({ ...northStar, unit: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Target</label>\n <Input\n type=\"number\"\n value={northStar.target}\n onChange={(e) => setNorthStar({ ...northStar, target: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Current</label>\n <Input\n type=\"number\"\n value={northStar.current}\n onChange={(e) => setNorthStar({ ...northStar, current: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div className=\"col-span-2\">\n <label className=\"text-sm font-medium\">Window</label>\n <Input\n value={northStar.window}\n onChange={(e) => setNorthStar({ ...northStar, window: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n </div>\n {northStarStarted && !northStarComplete && (\n <p className=\"text-xs text-destructive\">\n Fill in every North Star field (or clear them all) — name, unit, target, current\n and window are all required together.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Product Info</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"h-4 w-4 text-primary\" />\n <span className=\"font-medium\">{product.name}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Lifecycle stage</span>\n <span className=\"font-medium capitalize\">{enumLabel(product.lifecycleStage)}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <span className=\"font-medium\">{product.archivedAt ? 'Archived' : 'Active'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span className=\"font-medium\">\n <PersonName id={product.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(product.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(product.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductsEditRoadmapPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsEditRoadmapPagePage.tsx"],"names":[],"mappings":"AA2FA,wBAAgB,2BAA2B,4CA8O1C"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { PersonName as t } from "../people/People.js";
|
|
3
|
+
import { LEAVES_PAGE_CONTEXT as n, throwOnError as r } from "../utils/mutationResult.js";
|
|
4
|
+
import { useEntityId as i } from "../hooks/useEntityId.js";
|
|
5
|
+
import { ConfirmButton as a } from "../controls/ConfirmButton.js";
|
|
6
|
+
import { detailPath as o } from "../utils/rowNavigation.js";
|
|
7
|
+
import { PlanningFocusBadge as s, PlanningFocusSelect as c } from "../roadmaps/planningFocus.js";
|
|
8
|
+
import { useMemo as l, useState as u } from "react";
|
|
9
|
+
import { ArrowLeft as d, Loader2 as f, Map as p, Save as m, Trash2 as h } from "lucide-react";
|
|
10
|
+
import { gql as g } from "@apollo/client";
|
|
11
|
+
import { useMutation as _, useQuery as v } from "@apollo/client/react";
|
|
12
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
13
|
+
import { Button as x, Card as S, CardContent as C, CardHeader as w, CardTitle as T, Input as E } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
//#region src/planmagnet/pages/ProductsEditRoadmapPagePage.tsx
|
|
15
|
+
var D = g`
|
|
16
|
+
query GetRoadmapForRoadmapEdit($id: ID!) {
|
|
17
|
+
planmagnetRoadmap(id: $id) {
|
|
18
|
+
id
|
|
19
|
+
workspaceId
|
|
20
|
+
name
|
|
21
|
+
description
|
|
22
|
+
type
|
|
23
|
+
planningFocus
|
|
24
|
+
isPublic
|
|
25
|
+
ownerId
|
|
26
|
+
createdAt
|
|
27
|
+
updatedAt
|
|
28
|
+
items {
|
|
29
|
+
id
|
|
30
|
+
status
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`, O = g`
|
|
35
|
+
mutation UpdateRoadmapFromRoadmapEdit($id: ID!, $input: PlanmagnetUpdateRoadmapInput!) {
|
|
36
|
+
planmagnetUpdateRoadmap(id: $id, input: $input) {
|
|
37
|
+
id
|
|
38
|
+
name
|
|
39
|
+
description
|
|
40
|
+
type
|
|
41
|
+
planningFocus
|
|
42
|
+
isPublic
|
|
43
|
+
ownerId
|
|
44
|
+
updatedAt
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`, k = g`
|
|
48
|
+
mutation DeleteRoadmapFromRoadmapEdit($id: ID!) {
|
|
49
|
+
planmagnetDeleteRoadmap(id: $id)
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
function A() {
|
|
53
|
+
let g = e(), A = i("roadmapId") ?? "", { data: j, loading: M, error: N } = v(D, {
|
|
54
|
+
variables: { id: A },
|
|
55
|
+
skip: !A
|
|
56
|
+
}), [P, { loading: F }] = _(O, {
|
|
57
|
+
refetchQueries: [
|
|
58
|
+
"ListRoadmaps",
|
|
59
|
+
"ListRoadmapsPage",
|
|
60
|
+
"GetRoadmapForRoadmapEdit",
|
|
61
|
+
"GetRoadmapDetail",
|
|
62
|
+
"GetRoadmapDetailPage"
|
|
63
|
+
],
|
|
64
|
+
awaitRefetchQueries: !0
|
|
65
|
+
}), [I, { loading: L }] = _(k, {
|
|
66
|
+
refetchQueries: ["ListRoadmaps", "ListRoadmapsPage"],
|
|
67
|
+
context: n
|
|
68
|
+
}), [R, z] = u(null), [B, V] = u(!1), H = j?.planmagnetRoadmap, U = l(() => ({
|
|
69
|
+
name: H?.name ?? "",
|
|
70
|
+
description: H?.description ?? "",
|
|
71
|
+
planningFocus: H?.planningFocus ?? null,
|
|
72
|
+
isPublic: H?.isPublic ?? !1
|
|
73
|
+
}), [H]), [W, G] = u({}), K = {
|
|
74
|
+
...U,
|
|
75
|
+
...W
|
|
76
|
+
}, q = (e) => G(e), J = async () => {
|
|
77
|
+
z(null), V(!1);
|
|
78
|
+
try {
|
|
79
|
+
await r(P({ variables: {
|
|
80
|
+
id: A,
|
|
81
|
+
input: {
|
|
82
|
+
name: K.name,
|
|
83
|
+
description: K.description.trim() || null,
|
|
84
|
+
planningFocus: K.planningFocus,
|
|
85
|
+
isPublic: K.isPublic
|
|
86
|
+
}
|
|
87
|
+
} })), V(!0);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
z(e instanceof Error ? e.message : "The roadmap could not be saved.");
|
|
90
|
+
}
|
|
91
|
+
}, Y = async () => {
|
|
92
|
+
z(null);
|
|
93
|
+
try {
|
|
94
|
+
await r(I({ variables: { id: A } })), g("/products/roadmaps-page");
|
|
95
|
+
} catch (e) {
|
|
96
|
+
z(e instanceof Error ? e.message : "The roadmap could not be deleted.");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
if (M && !j) return /* @__PURE__ */ y("div", {
|
|
100
|
+
className: "flex items-center justify-center p-12",
|
|
101
|
+
children: /* @__PURE__ */ y(f, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
102
|
+
});
|
|
103
|
+
if (N) return /* @__PURE__ */ b("div", {
|
|
104
|
+
className: "p-6 text-destructive",
|
|
105
|
+
children: ["Failed to load roadmap: ", N.message]
|
|
106
|
+
});
|
|
107
|
+
if (!H) return /* @__PURE__ */ y("div", {
|
|
108
|
+
className: "p-6 text-muted-foreground",
|
|
109
|
+
children: "No roadmap found."
|
|
110
|
+
});
|
|
111
|
+
let X = o("/products/roadmap-detail-page", H.id);
|
|
112
|
+
return /* @__PURE__ */ b("div", {
|
|
113
|
+
className: "space-y-6 p-6",
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ b("div", {
|
|
116
|
+
className: "flex items-center justify-between",
|
|
117
|
+
children: [/* @__PURE__ */ b("div", {
|
|
118
|
+
className: "flex items-center gap-3",
|
|
119
|
+
children: [/* @__PURE__ */ y(x, {
|
|
120
|
+
variant: "ghost",
|
|
121
|
+
size: "sm",
|
|
122
|
+
onClick: () => g(X),
|
|
123
|
+
children: /* @__PURE__ */ y(d, { className: "h-4 w-4" })
|
|
124
|
+
}), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h1", {
|
|
125
|
+
className: "text-2xl font-semibold",
|
|
126
|
+
children: "Edit Roadmap"
|
|
127
|
+
}), /* @__PURE__ */ b("p", {
|
|
128
|
+
className: "text-sm text-muted-foreground",
|
|
129
|
+
children: ["Editing ", H.name]
|
|
130
|
+
})] })]
|
|
131
|
+
}), /* @__PURE__ */ b(x, {
|
|
132
|
+
size: "sm",
|
|
133
|
+
onClick: J,
|
|
134
|
+
disabled: F,
|
|
135
|
+
children: [F ? /* @__PURE__ */ y(f, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ y(m, { className: "mr-1 h-4 w-4" }), "Save Changes"]
|
|
136
|
+
})]
|
|
137
|
+
}),
|
|
138
|
+
B && /* @__PURE__ */ y("div", {
|
|
139
|
+
role: "status",
|
|
140
|
+
className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
|
|
141
|
+
children: "Changes saved."
|
|
142
|
+
}),
|
|
143
|
+
R && /* @__PURE__ */ y("div", {
|
|
144
|
+
role: "alert",
|
|
145
|
+
className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
|
|
146
|
+
children: R
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ b("div", {
|
|
149
|
+
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
150
|
+
children: [/* @__PURE__ */ y("div", {
|
|
151
|
+
className: "lg:col-span-2 space-y-6",
|
|
152
|
+
children: /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(T, { children: "Basic Information" }) }), /* @__PURE__ */ b(C, {
|
|
153
|
+
className: "space-y-4",
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
156
|
+
className: "text-sm font-medium",
|
|
157
|
+
children: "Roadmap Name"
|
|
158
|
+
}), /* @__PURE__ */ y(E, {
|
|
159
|
+
value: K.name,
|
|
160
|
+
onChange: (e) => q({
|
|
161
|
+
...K,
|
|
162
|
+
name: e.target.value
|
|
163
|
+
}),
|
|
164
|
+
className: "mt-1"
|
|
165
|
+
})] }),
|
|
166
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
167
|
+
className: "text-sm font-medium",
|
|
168
|
+
children: "Description"
|
|
169
|
+
}), /* @__PURE__ */ y("textarea", {
|
|
170
|
+
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
|
|
171
|
+
value: K.description,
|
|
172
|
+
onChange: (e) => q({
|
|
173
|
+
...K,
|
|
174
|
+
description: e.target.value
|
|
175
|
+
})
|
|
176
|
+
})] }),
|
|
177
|
+
/* @__PURE__ */ y(c, {
|
|
178
|
+
value: K.planningFocus,
|
|
179
|
+
onChange: (e) => q({
|
|
180
|
+
...K,
|
|
181
|
+
planningFocus: e
|
|
182
|
+
})
|
|
183
|
+
}),
|
|
184
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
185
|
+
className: "text-sm font-medium",
|
|
186
|
+
children: "Visibility"
|
|
187
|
+
}), /* @__PURE__ */ y("div", {
|
|
188
|
+
className: "mt-1 flex gap-2",
|
|
189
|
+
children: [!1, !0].map((e) => /* @__PURE__ */ y("button", {
|
|
190
|
+
onClick: () => q({
|
|
191
|
+
...K,
|
|
192
|
+
isPublic: e
|
|
193
|
+
}),
|
|
194
|
+
className: `rounded-md px-3 py-1.5 text-sm ${K.isPublic === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
|
|
195
|
+
children: e ? "Public" : "Private"
|
|
196
|
+
}, String(e)))
|
|
197
|
+
})] })
|
|
198
|
+
]
|
|
199
|
+
})] })
|
|
200
|
+
}), /* @__PURE__ */ b("div", {
|
|
201
|
+
className: "space-y-6",
|
|
202
|
+
children: [/* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(T, { children: "Roadmap Info" }) }), /* @__PURE__ */ b(C, {
|
|
203
|
+
className: "space-y-3 text-sm",
|
|
204
|
+
children: [
|
|
205
|
+
/* @__PURE__ */ b("div", {
|
|
206
|
+
className: "flex items-center gap-2",
|
|
207
|
+
children: [/* @__PURE__ */ y(p, { className: "h-4 w-4 text-primary" }), /* @__PURE__ */ y("span", {
|
|
208
|
+
className: "font-medium",
|
|
209
|
+
children: H.name
|
|
210
|
+
})]
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ b("div", {
|
|
213
|
+
className: "flex justify-between",
|
|
214
|
+
children: [/* @__PURE__ */ y("span", {
|
|
215
|
+
className: "text-muted-foreground",
|
|
216
|
+
children: "Items"
|
|
217
|
+
}), /* @__PURE__ */ y("span", {
|
|
218
|
+
className: "font-medium",
|
|
219
|
+
children: H.items?.length ?? 0
|
|
220
|
+
})]
|
|
221
|
+
}),
|
|
222
|
+
/* @__PURE__ */ b("div", {
|
|
223
|
+
className: "flex justify-between",
|
|
224
|
+
children: [/* @__PURE__ */ y("span", {
|
|
225
|
+
className: "text-muted-foreground",
|
|
226
|
+
children: "Planning focus"
|
|
227
|
+
}), /* @__PURE__ */ y(s, { focus: H.planningFocus })]
|
|
228
|
+
}),
|
|
229
|
+
/* @__PURE__ */ b("div", {
|
|
230
|
+
className: "flex justify-between",
|
|
231
|
+
children: [/* @__PURE__ */ y("span", {
|
|
232
|
+
className: "text-muted-foreground",
|
|
233
|
+
children: "Visibility"
|
|
234
|
+
}), /* @__PURE__ */ y("span", {
|
|
235
|
+
className: "font-medium",
|
|
236
|
+
children: H.isPublic ? "Public" : "Private"
|
|
237
|
+
})]
|
|
238
|
+
}),
|
|
239
|
+
/* @__PURE__ */ b("div", {
|
|
240
|
+
className: "flex justify-between",
|
|
241
|
+
children: [/* @__PURE__ */ y("span", {
|
|
242
|
+
className: "text-muted-foreground",
|
|
243
|
+
children: "Owner"
|
|
244
|
+
}), /* @__PURE__ */ y("span", {
|
|
245
|
+
className: "font-medium",
|
|
246
|
+
children: /* @__PURE__ */ y(t, { id: H.ownerId })
|
|
247
|
+
})]
|
|
248
|
+
}),
|
|
249
|
+
/* @__PURE__ */ b("div", {
|
|
250
|
+
className: "flex justify-between",
|
|
251
|
+
children: [/* @__PURE__ */ y("span", {
|
|
252
|
+
className: "text-muted-foreground",
|
|
253
|
+
children: "Created"
|
|
254
|
+
}), /* @__PURE__ */ y("span", { children: new Date(H.createdAt).toLocaleDateString() })]
|
|
255
|
+
}),
|
|
256
|
+
/* @__PURE__ */ b("div", {
|
|
257
|
+
className: "flex justify-between",
|
|
258
|
+
children: [/* @__PURE__ */ y("span", {
|
|
259
|
+
className: "text-muted-foreground",
|
|
260
|
+
children: "Updated"
|
|
261
|
+
}), /* @__PURE__ */ y("span", { children: new Date(H.updatedAt).toLocaleDateString() })]
|
|
262
|
+
})
|
|
263
|
+
]
|
|
264
|
+
})] }), /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(T, { children: "Danger Zone" }) }), /* @__PURE__ */ y(C, { children: /* @__PURE__ */ y(a, {
|
|
265
|
+
label: L ? "Deleting…" : "Delete roadmap",
|
|
266
|
+
variant: "outline",
|
|
267
|
+
icon: /* @__PURE__ */ y(h, { className: "mr-1 h-4 w-4" }),
|
|
268
|
+
title: `Delete ${H.name}?`,
|
|
269
|
+
description: "The roadmap and its items are deleted. This cannot be undone here.",
|
|
270
|
+
confirmLabel: "Delete roadmap",
|
|
271
|
+
destructive: !0,
|
|
272
|
+
disabled: L,
|
|
273
|
+
onConfirm: () => void Y()
|
|
274
|
+
}) })] })]
|
|
275
|
+
})]
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
export { A as ProductsEditRoadmapPagePage };
|
|
282
|
+
|
|
283
|
+
//# sourceMappingURL=ProductsEditRoadmapPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductsEditRoadmapPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsEditRoadmapPagePage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery, useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, ArrowLeft, Map, Loader2, Trash2 } from 'lucide-react';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton } from '../controls/ConfirmButton';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport {\n PlanningFocusBadge,\n PlanningFocusSelect,\n type PlanningFocus,\n} from '../roadmaps/planningFocus';\n\n/**\n * Edit a Roadmap's own fields (BOFF-8045). Split out of what used to be\n * ProductsEditProductPagePage.tsx: before the Product entity existed, \"Products\"\n * WERE roadmaps and this same form lived at /products/edit-product-page. Now\n * that edit-product-page edits a real Product (name, vision, lifecycle stage,\n * North Star — see ProductsEditProductPagePage.tsx), roadmap editing needs its\n * own home so RoadmapControls.tsx's \"Edit\" button (used from the real Roadmap\n * detail page) has somewhere correct to go instead of a Product it does not\n * own the fields of.\n */\n\nconst GET_ROADMAP = gql`\n query GetRoadmapForRoadmapEdit($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n workspaceId\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n status\n }\n }\n }\n`;\n\nconst UPDATE_ROADMAP = gql`\n mutation UpdateRoadmapFromRoadmapEdit($id: ID!, $input: PlanmagnetUpdateRoadmapInput!) {\n planmagnetUpdateRoadmap(id: $id, input: $input) {\n id\n name\n description\n type\n planningFocus\n isPublic\n ownerId\n updatedAt\n }\n }\n`;\n\nconst DELETE_ROADMAP = gql`\n mutation DeleteRoadmapFromRoadmapEdit($id: ID!) {\n planmagnetDeleteRoadmap(id: $id)\n }\n`;\n\ninterface RoadmapForEdit {\n id: string;\n workspaceId: string;\n name: string;\n description: string | null;\n type: string;\n planningFocus: string | null;\n isPublic: boolean;\n ownerId: string;\n createdAt: string;\n updatedAt: string;\n items: { id: string; status: string }[];\n}\n\ninterface EditRoadmapForm {\n name: string;\n description: string;\n planningFocus: PlanningFocus | null;\n isPublic: boolean;\n}\n\nexport function ProductsEditRoadmapPagePage() {\n const navigate = usePlanMagnetNavigate();\n const roadmapId = useEntityId('roadmapId') ?? '';\n\n const { data, loading, error } = useQuery<{ planmagnetRoadmap: RoadmapForEdit | null }>(\n GET_ROADMAP,\n { variables: { id: roadmapId }, skip: !roadmapId }\n );\n\n const [updateRoadmap, { loading: saving }] = useMutation(UPDATE_ROADMAP, {\n refetchQueries: [\n 'ListRoadmaps',\n 'ListRoadmapsPage',\n 'GetRoadmapForRoadmapEdit',\n 'GetRoadmapDetail',\n 'GetRoadmapDetailPage',\n ],\n awaitRefetchQueries: true,\n });\n // BOFF-7980: the button said \"Archive\" but deleted, on the first click, without awaiting — a refusal showed nothing.\n const [deleteRoadmap, { loading: deleting }] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmaps', 'ListRoadmapsPage'],\n context: LEAVES_PAGE_CONTEXT,\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const roadmap = data?.planmagnetRoadmap;\n\n const defaultForm = useMemo<EditRoadmapForm>(\n () => ({\n name: roadmap?.name ?? '',\n description: roadmap?.description ?? '',\n planningFocus: (roadmap?.planningFocus ?? null) as PlanningFocus | null,\n isPublic: roadmap?.isPublic ?? false,\n }),\n [roadmap]\n );\n\n const [formOverrides, setFormOverrides] = useState<Partial<EditRoadmapForm>>({});\n const form: EditRoadmapForm = { ...defaultForm, ...formOverrides };\n const setForm = (val: EditRoadmapForm) => setFormOverrides(val);\n\n // BOFF-7979: an emptied description is cleared (null), not silently kept; success is confirmed.\n const handleSave = async () => {\n setSaveError(null);\n setSaved(false);\n try {\n await throwOnError(\n updateRoadmap({\n variables: {\n id: roadmapId,\n input: {\n name: form.name,\n description: form.description.trim() || null,\n // The display is not editable: the page has one layout (BOFF-7934).\n // null clears a focus that was set by mistake.\n planningFocus: form.planningFocus,\n isPublic: form.isPublic,\n },\n },\n })\n );\n setSaved(true);\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The roadmap could not be saved.');\n }\n };\n\n const handleDelete = async () => {\n setSaveError(null);\n try {\n await throwOnError(deleteRoadmap({ variables: { id: roadmapId } }));\n navigate('/products/roadmaps-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The roadmap could not be deleted.');\n }\n };\n\n // Only before the first load: a refetch must not blank the page (it\n // unmounts every component on it and loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load roadmap: {error.message}</div>;\n }\n\n if (!roadmap) {\n return <div className=\"p-6 text-muted-foreground\">No roadmap found.</div>;\n }\n\n const backTarget = detailPath('/products/roadmap-detail-page', roadmap.id);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigate(backTarget)}>\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Edit Roadmap</h1>\n <p className=\"text-sm text-muted-foreground\">Editing {roadmap.name}</p>\n </div>\n </div>\n <Button size=\"sm\" onClick={handleSave} disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Save Changes\n </Button>\n </div>\n\n {saved && (\n <div role=\"status\" className=\"p-3 bg-muted text-status-success-text rounded-md text-sm\">\n Changes saved.\n </div>\n )}\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n {saveError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Basic Information</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Roadmap Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n <PlanningFocusSelect\n value={form.planningFocus}\n onChange={(planningFocus) => setForm({ ...form, planningFocus })}\n />\n <div>\n <label className=\"text-sm font-medium\">Visibility</label>\n <div className=\"mt-1 flex gap-2\">\n {[false, true].map((pub) => (\n <button\n key={String(pub)}\n onClick={() => setForm({ ...form, isPublic: pub })}\n className={`rounded-md px-3 py-1.5 text-sm ${form.isPublic === pub ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {pub ? 'Public' : 'Private'}\n </button>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Roadmap Info</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Map className=\"h-4 w-4 text-primary\" />\n <span className=\"font-medium\">{roadmap.name}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Items</span>\n <span className=\"font-medium\">{roadmap.items?.length ?? 0}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Planning focus</span>\n <PlanningFocusBadge focus={roadmap.planningFocus} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Visibility</span>\n <span className=\"font-medium\">{roadmap.isPublic ? 'Public' : 'Private'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span className=\"font-medium\">\n <PersonName id={roadmap.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(roadmap.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(roadmap.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Danger Zone</CardTitle>\n </CardHeader>\n <CardContent>\n <ConfirmButton\n label={deleting ? 'Deleting…' : 'Delete roadmap'}\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${roadmap.name}?`}\n description=\"The roadmap and its items are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete roadmap\"\n destructive\n disabled={deleting}\n onConfirm={() => void handleDelete()}\n />\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"AAuGA,wBAAgB,6BAA6B,4CAwK5C"}
|