@burdenoff/microfe-planmagnet 2026.927.2 → 2026.927.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +128 -124
  3. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  4. package/dist/planmagnet/controls/ConfirmButton.d.ts +25 -0
  5. package/dist/planmagnet/controls/ConfirmButton.d.ts.map +1 -0
  6. package/dist/planmagnet/controls/ConfirmButton.js +30 -0
  7. package/dist/planmagnet/controls/ConfirmButton.js.map +1 -0
  8. package/dist/planmagnet/controls/FeatureControls.d.ts +26 -0
  9. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -0
  10. package/dist/planmagnet/controls/FeatureControls.js +318 -0
  11. package/dist/planmagnet/controls/FeatureControls.js.map +1 -0
  12. package/dist/planmagnet/controls/MilestoneControls.d.ts +17 -0
  13. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -0
  14. package/dist/planmagnet/controls/MilestoneControls.js +200 -0
  15. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -0
  16. package/dist/planmagnet/controls/ObjectiveControls.d.ts +31 -0
  17. package/dist/planmagnet/controls/ObjectiveControls.d.ts.map +1 -0
  18. package/dist/planmagnet/controls/ObjectiveControls.js +296 -0
  19. package/dist/planmagnet/controls/ObjectiveControls.js.map +1 -0
  20. package/dist/planmagnet/controls/Pickers.d.ts +26 -0
  21. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -0
  22. package/dist/planmagnet/controls/Pickers.js +171 -0
  23. package/dist/planmagnet/controls/Pickers.js.map +1 -0
  24. package/dist/planmagnet/controls/ProjectControls.d.ts +13 -0
  25. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -0
  26. package/dist/planmagnet/controls/ProjectControls.js +104 -0
  27. package/dist/planmagnet/controls/ProjectControls.js.map +1 -0
  28. package/dist/planmagnet/controls/ReleaseControls.d.ts +14 -0
  29. package/dist/planmagnet/controls/ReleaseControls.d.ts.map +1 -0
  30. package/dist/planmagnet/controls/ReleaseControls.js +108 -0
  31. package/dist/planmagnet/controls/ReleaseControls.js.map +1 -0
  32. package/dist/planmagnet/controls/WorkItemControls.d.ts +52 -0
  33. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -0
  34. package/dist/planmagnet/controls/WorkItemControls.js +198 -0
  35. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -0
  36. package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
  37. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +188 -154
  38. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  39. package/dist/planmagnet/pages/DocsDocsPagePage.js +4 -4
  40. package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.d.ts.map +1 -1
  41. package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.js +63 -56
  42. package/dist/planmagnet/pages/GoalsCreateObjectivePagePage.js.map +1 -1
  43. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +229 -214
  45. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +142 -114
  48. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  49. package/dist/planmagnet/pages/ProductsEditReleasePagePage.d.ts.map +1 -1
  50. package/dist/planmagnet/pages/ProductsEditReleasePagePage.js +134 -105
  51. package/dist/planmagnet/pages/ProductsEditReleasePagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +102 -108
  54. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  56. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +119 -94
  57. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  58. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +97 -90
  60. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  61. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  62. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +107 -106
  63. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  64. package/dist/planmagnet/pages/ProductsReleasesPagePage.d.ts.map +1 -1
  65. package/dist/planmagnet/pages/ProductsReleasesPagePage.js +2 -0
  66. package/dist/planmagnet/pages/ProductsReleasesPagePage.js.map +1 -1
  67. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +150 -111
  69. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +116 -93
  72. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  73. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
  74. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +55 -63
  75. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
  76. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +4 -4
  77. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  78. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +134 -132
  79. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  81. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +6 -5
  82. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  83. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  84. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +167 -116
  85. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  86. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  87. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +139 -107
  88. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  89. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  90. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +205 -158
  91. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  92. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  93. package/dist/planmagnet/pages/TasksTasksPagePage.js +1 -0
  94. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  95. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  96. package/dist/planmagnet/pages/TeamTeamPagePage.js +1 -1
  97. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  98. package/dist/planmagnet/people/People.js +1 -1
  99. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts +34 -0
  100. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -0
  101. package/dist/planmagnet/roadmaps/RoadmapControls.js +301 -0
  102. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -0
  103. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  104. package/dist/planmagnet/sprints/SprintControls.js +194 -215
  105. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  106. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GoalsObjectiveDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/GoalsObjectiveDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } 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 { Flag, Calendar, User, Plus, Trash2, ArrowLeft } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\n\n// BOFF-4441: OKR detail — real Objective + KeyResult data via GraphQL.\n// `progress` (on both the objective and each key result) is computed live\n// server-side from current/target values; this page only ever reads it,\n// never derives it client-side. `linkedWorkItemRollup` surfaces a real,\n// live-computed completion ratio over WorkItems linked to a key result --\n// informational only, it does not drive `currentValue` (see the backend\n// module header for the explicit deferral of that automation).\nconst GET_OBJECTIVE = gql`\n query GetObjectiveDetail($id: ID!) {\n planmagnetObjective(id: $id) {\n id\n workspaceId\n title\n description\n ownerId\n periodLabel\n startDate\n endDate\n status\n progress\n createdAt\n keyResults {\n id\n title\n description\n metricType\n startValue\n targetValue\n currentValue\n unit\n status\n ownerId\n progress\n linkedWorkItemRollup {\n totalLinked\n doneCount\n completionRatio\n }\n }\n }\n }\n`;\n\nconst CREATE_KEY_RESULT = gql`\n mutation CreateKeyResult($input: PlanmagnetCreateKeyResultInput!) {\n planmagnetCreateKeyResult(input: $input) {\n id\n }\n }\n`;\n\nconst UPDATE_KEY_RESULT = gql`\n mutation UpdateKeyResult($id: ID!, $input: PlanmagnetUpdateKeyResultInput!) {\n planmagnetUpdateKeyResult(id: $id, input: $input) {\n id\n currentValue\n status\n }\n }\n`;\n\nconst DELETE_KEY_RESULT = gql`\n mutation DeleteKeyResult($id: ID!) {\n planmagnetDeleteKeyResult(id: $id)\n }\n`;\n\nconst DELETE_OBJECTIVE = gql`\n mutation DeleteObjective($id: ID!) {\n planmagnetDeleteObjective(id: $id)\n }\n`;\n\nconst objectiveStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n ACTIVE: 'bg-blue-100 text-blue-800',\n COMPLETED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n};\n\nconst krStatusColor: Record<string, string> = {\n NOT_STARTED: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n AT_RISK: 'bg-orange-100 text-orange-800',\n COMPLETED: 'bg-green-100 text-green-800',\n};\n\nfunction ProgressBar({ progress }: { progress: number }) {\n const pct = Math.max(0, Math.min(100, Math.round(progress)));\n return (\n <div className=\"h-2 w-full rounded-full bg-muted overflow-hidden\">\n <div className=\"h-full rounded-full bg-primary transition-all\" style={{ width: `${pct}%` }} />\n </div>\n );\n}\n\ninterface KeyResultNode {\n id: string;\n title: string;\n description: string | null;\n metricType: 'NUMERIC' | 'PERCENTAGE' | 'BOOLEAN';\n startValue: number;\n targetValue: number;\n currentValue: number;\n unit: string | null;\n status: string;\n ownerId: string | null;\n progress: number;\n linkedWorkItemRollup: { totalLinked: number; doneCount: number; completionRatio: number };\n}\n\nfunction KeyResultRow({\n kr,\n onUpdated,\n onDeleted,\n}: {\n kr: KeyResultNode;\n onUpdated: () => void;\n onDeleted: () => void;\n}) {\n const [draftValue, setDraftValue] = useState(String(kr.currentValue));\n const [updateKeyResult, { loading: updating }] = useMutation(UPDATE_KEY_RESULT);\n const [deleteKeyResult, { loading: deleting }] = useMutation(DELETE_KEY_RESULT);\n // fe-libs sets mutate errorPolicy 'all': a refused mutation RESOLVES with\n // { error }. These handlers used to carry on as if it had worked.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const handleSaveProgress = async () => {\n const parsed = kr.metricType === 'BOOLEAN' ? (draftValue === '1' ? 1 : 0) : Number(draftValue);\n if (Number.isNaN(parsed)) return;\n setActionError(null);\n try {\n await throwOnError(\n updateKeyResult({ variables: { id: kr.id, input: { currentValue: parsed } } })\n );\n onUpdated();\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The progress could not be saved.');\n }\n };\n\n const handleDelete = async () => {\n setActionError(null);\n try {\n await throwOnError(deleteKeyResult({ variables: { id: kr.id } }));\n onDeleted();\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The key result could not be deleted.');\n }\n };\n\n return (\n <div className=\"rounded-md border border-input p-4 space-y-3\">\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium\">{kr.title}</p>\n {kr.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{kr.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${krStatusColor[kr.status] ?? ''}`}\n >\n {kr.status.toLowerCase().replace('_', ' ')}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={deleting}\n onClick={handleDelete}\n aria-label={`Delete ${kr.title}`}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n <ProgressBar progress={kr.progress} />\n <div className=\"flex items-center justify-between text-xs text-muted-foreground\">\n <span>\n {kr.metricType === 'BOOLEAN'\n ? kr.currentValue > 0\n ? 'Done'\n : 'Not done'\n : `${kr.currentValue}${kr.unit ? ` ${kr.unit}` : ''} of ${kr.targetValue}${kr.unit ? ` ${kr.unit}` : ''}`}\n </span>\n <span>{Math.round(kr.progress)}%</span>\n </div>\n {kr.linkedWorkItemRollup.totalLinked > 0 && (\n <p className=\"text-xs text-muted-foreground\">\n {kr.linkedWorkItemRollup.doneCount}/{kr.linkedWorkItemRollup.totalLinked} linked work\n items done ({Math.round(kr.linkedWorkItemRollup.completionRatio * 100)}%) &mdash;\n informational, update progress above manually\n </p>\n )}\n <div className=\"flex items-center gap-2\">\n {kr.metricType === 'BOOLEAN' ? (\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={draftValue}\n onChange={(e) => setDraftValue(e.target.value)}\n >\n <option value=\"0\">Not done</option>\n <option value=\"1\">Done</option>\n </select>\n ) : (\n <Input\n type=\"number\"\n value={draftValue}\n onChange={(e) => setDraftValue(e.target.value)}\n className=\"w-32\"\n />\n )}\n <Button size=\"sm\" variant=\"outline\" disabled={updating} onClick={handleSaveProgress}>\n {updating ? 'Saving...' : 'Update progress'}\n </Button>\n </div>\n </div>\n );\n}\n\nexport function GoalsObjectiveDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const objectiveId = new URLSearchParams(window.location.search).get('objectiveId') ?? '';\n\n const [isAddingKr, setIsAddingKr] = useState(false);\n const [krForm, setKrForm] = useState({\n title: '',\n metricType: 'NUMERIC',\n targetValue: '100',\n unit: '',\n });\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetObjective: {\n id: string;\n title: string;\n description: string | null;\n ownerId: string;\n periodLabel: string;\n startDate: string | null;\n endDate: string | null;\n status: string;\n progress: number;\n createdAt: string;\n keyResults: KeyResultNode[];\n } | null;\n }>(GET_OBJECTIVE, {\n variables: { id: objectiveId },\n skip: !workspaceId || !objectiveId,\n });\n\n const [createKeyResult, { loading: creatingKr }] = useMutation(CREATE_KEY_RESULT);\n const [deleteObjective, { loading: deletingObjective }] = useMutation(DELETE_OBJECTIVE);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n if (error) return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n\n const objective = data?.planmagnetObjective;\n if (!objective) return <div className=\"p-6 text-muted-foreground\">Objective not found.</div>;\n\n const handleAddKeyResult = async () => {\n if (!krForm.title.trim() || !objectiveId) return;\n const target = Number(krForm.metricType === 'BOOLEAN' ? 1 : krForm.targetValue);\n if (Number.isNaN(target)) return;\n setActionError(null);\n try {\n await throwOnError(\n createKeyResult({\n variables: {\n input: {\n objectiveId,\n title: krForm.title.trim(),\n metricType: krForm.metricType,\n targetValue: target,\n unit: krForm.unit.trim() || undefined,\n },\n },\n })\n );\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The key result could not be added.');\n return;\n }\n setKrForm({ title: '', metricType: 'NUMERIC', targetValue: '100', unit: '' });\n setIsAddingKr(false);\n void refetch();\n };\n\n const handleDeleteObjective = async () => {\n setActionError(null);\n try {\n await throwOnError(deleteObjective({ variables: { id: objectiveId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The objective could not be deleted.');\n return;\n }\n navigate('/goals/goals-page');\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button variant=\"ghost\" size=\"icon\" onClick={() => navigate('/goals/goals-page')}>\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <Flag className=\"h-6 w-6 text-primary\" />\n <div>\n <h1 className=\"text-2xl font-semibold\">{objective.title}</h1>\n <p className=\"text-sm text-muted-foreground\">{objective.periodLabel}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span\n className={`text-xs px-3 py-1 rounded-full capitalize ${objectiveStatusColor[objective.status] ?? 'bg-gray-100'}`}\n >\n {objective.status.toLowerCase()}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={deletingObjective}\n onClick={handleDeleteObjective}\n >\n <Trash2 className=\"mr-1 h-4 w-4\" />\n Delete\n </Button>\n </div>\n </div>\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 className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Overview</CardTitle>\n <Badge variant=\"outline\">{Math.round(objective.progress)}% complete</Badge>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{objective.description ?? 'No description provided.'}</p>\n <ProgressBar progress={objective.progress} />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Key Results</CardTitle>\n {!isAddingKr && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsAddingKr(true)}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Add Key Result\n </Button>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isAddingKr && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Key result title, e.g. Sign 10 new enterprise customers\"\n value={krForm.title}\n onChange={(e) => setKrForm({ ...krForm, title: e.target.value })}\n />\n <div className=\"grid grid-cols-3 gap-3\">\n <select\n className=\"rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={krForm.metricType}\n onChange={(e) => setKrForm({ ...krForm, metricType: e.target.value })}\n >\n <option value=\"NUMERIC\">Numeric</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"BOOLEAN\">Yes / No</option>\n </select>\n {krForm.metricType !== 'BOOLEAN' && (\n <Input\n type=\"number\"\n placeholder=\"Target value\"\n value={krForm.targetValue}\n onChange={(e) => setKrForm({ ...krForm, targetValue: e.target.value })}\n />\n )}\n {krForm.metricType === 'NUMERIC' && (\n <Input\n placeholder=\"Unit (optional)\"\n value={krForm.unit}\n onChange={(e) => setKrForm({ ...krForm, unit: e.target.value })}\n />\n )}\n </div>\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsAddingKr(false);\n setKrForm({\n title: '',\n metricType: 'NUMERIC',\n targetValue: '100',\n unit: '',\n });\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creatingKr || !krForm.title.trim()}\n onClick={handleAddKeyResult}\n >\n {creatingKr ? 'Adding...' : 'Add Key Result'}\n </Button>\n </div>\n </div>\n )}\n {objective.keyResults.length === 0 && !isAddingKr && (\n <p className=\"text-sm text-muted-foreground\">\n No key results yet. Add one to make this objective measurable.\n </p>\n )}\n {objective.keyResults.map((kr) => (\n <KeyResultRow\n key={kr.id}\n kr={kr}\n onUpdated={() => void refetch()}\n onDeleted={() => void refetch()}\n />\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <User className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Owner:</span>\n <span className=\"font-medium\">\n <PersonName id={objective.ownerId} />\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Period:</span>\n <span className=\"font-medium\">{objective.periodLabel}</span>\n </div>\n {objective.endDate && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Target end:</span>\n <span className=\"font-medium\">\n {new Date(objective.endDate).toLocaleDateString()}\n </span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Created:</span>\n <span className=\"font-medium\">\n {objective.createdAt ? new Date(objective.createdAt).toLocaleDateString() : '—'}\n </span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"GoalsObjectiveDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/GoalsObjectiveDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } 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 { Flag, Calendar, User, Plus, Trash2, ArrowLeft } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { ConfirmButton } from '../controls/ConfirmButton';\nimport { KeyResultWorkItems, ObjectiveHeaderControls } from '../controls/ObjectiveControls';\n\n// BOFF-4441: OKR detail — real Objective + KeyResult data via GraphQL.\n// `progress` (on both the objective and each key result) is computed live\n// server-side from current/target values; this page only ever reads it,\n// never derives it client-side. `linkedWorkItemRollup` surfaces a real,\n// live-computed completion ratio over WorkItems linked to a key result --\n// informational only, it does not drive `currentValue` (see the backend\n// module header for the explicit deferral of that automation).\nconst GET_OBJECTIVE = gql`\n query GetObjectiveDetail($id: ID!) {\n planmagnetObjective(id: $id) {\n id\n workspaceId\n title\n description\n ownerId\n periodLabel\n startDate\n endDate\n status\n progress\n createdAt\n keyResults {\n id\n title\n description\n metricType\n startValue\n targetValue\n currentValue\n unit\n status\n ownerId\n progress\n linkedWorkItemRollup {\n totalLinked\n doneCount\n completionRatio\n }\n linkedWorkItems {\n id\n key\n title\n status\n }\n }\n }\n }\n`;\n\nconst CREATE_KEY_RESULT = gql`\n mutation CreateKeyResult($input: PlanmagnetCreateKeyResultInput!) {\n planmagnetCreateKeyResult(input: $input) {\n id\n }\n }\n`;\n\nconst UPDATE_KEY_RESULT = gql`\n mutation UpdateKeyResult($id: ID!, $input: PlanmagnetUpdateKeyResultInput!) {\n planmagnetUpdateKeyResult(id: $id, input: $input) {\n id\n currentValue\n status\n }\n }\n`;\n\nconst DELETE_KEY_RESULT = gql`\n mutation DeleteKeyResult($id: ID!) {\n planmagnetDeleteKeyResult(id: $id)\n }\n`;\n\nconst DELETE_OBJECTIVE = gql`\n mutation DeleteObjective($id: ID!) {\n planmagnetDeleteObjective(id: $id)\n }\n`;\n\nconst krStatusColor: Record<string, string> = {\n NOT_STARTED: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n AT_RISK: 'bg-orange-100 text-orange-800',\n COMPLETED: 'bg-green-100 text-green-800',\n};\n\nfunction ProgressBar({ progress }: { progress: number }) {\n const pct = Math.max(0, Math.min(100, Math.round(progress)));\n return (\n <div className=\"h-2 w-full rounded-full bg-muted overflow-hidden\">\n <div className=\"h-full rounded-full bg-primary transition-all\" style={{ width: `${pct}%` }} />\n </div>\n );\n}\n\ninterface KeyResultNode {\n id: string;\n title: string;\n description: string | null;\n metricType: 'NUMERIC' | 'PERCENTAGE' | 'BOOLEAN';\n startValue: number;\n targetValue: number;\n currentValue: number;\n unit: string | null;\n status: string;\n ownerId: string | null;\n progress: number;\n linkedWorkItemRollup: { totalLinked: number; doneCount: number; completionRatio: number };\n linkedWorkItems: { id: string; key: string; title: string; status: string }[];\n}\n\nfunction KeyResultRow({\n kr,\n onUpdated,\n onDeleted,\n}: {\n kr: KeyResultNode;\n onUpdated: () => void;\n onDeleted: () => void;\n}) {\n const [draftValue, setDraftValue] = useState(String(kr.currentValue));\n const [updateKeyResult, { loading: updating }] = useMutation(UPDATE_KEY_RESULT);\n const [deleteKeyResult, { loading: deleting }] = useMutation(DELETE_KEY_RESULT);\n // fe-libs sets mutate errorPolicy 'all': a refused mutation RESOLVES with\n // { error }. These handlers used to carry on as if it had worked.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const handleSaveProgress = async () => {\n const parsed = kr.metricType === 'BOOLEAN' ? (draftValue === '1' ? 1 : 0) : Number(draftValue);\n if (Number.isNaN(parsed)) return;\n setActionError(null);\n try {\n await throwOnError(\n updateKeyResult({ variables: { id: kr.id, input: { currentValue: parsed } } })\n );\n onUpdated();\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The progress could not be saved.');\n }\n };\n\n const handleDelete = async () => {\n setActionError(null);\n try {\n await throwOnError(deleteKeyResult({ variables: { id: kr.id } }));\n onDeleted();\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The key result could not be deleted.');\n }\n };\n\n return (\n <div className=\"rounded-md border border-input p-4 space-y-3\">\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium\">{kr.title}</p>\n {kr.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{kr.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${krStatusColor[kr.status] ?? ''}`}\n >\n {kr.status.toLowerCase().replace('_', ' ')}\n </span>\n {/* Deleting used to happen on the first click, with no confirmation. */}\n <ConfirmButton\n label=\"\"\n ariaLabel={`Delete ${kr.title}`}\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title={`Delete \"${kr.title}\"?`}\n description=\"The key result and its progress are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete key result\"\n destructive\n disabled={deleting}\n onConfirm={() => void handleDelete()}\n />\n </div>\n </div>\n <ProgressBar progress={kr.progress} />\n <div className=\"flex items-center justify-between text-xs text-muted-foreground\">\n <span>\n {kr.metricType === 'BOOLEAN'\n ? kr.currentValue > 0\n ? 'Done'\n : 'Not done'\n : `${kr.currentValue}${kr.unit ? ` ${kr.unit}` : ''} of ${kr.targetValue}${kr.unit ? ` ${kr.unit}` : ''}`}\n </span>\n <span>{Math.round(kr.progress)}%</span>\n </div>\n {kr.linkedWorkItemRollup.totalLinked > 0 && (\n <p className=\"text-xs text-muted-foreground\">\n {kr.linkedWorkItemRollup.doneCount}/{kr.linkedWorkItemRollup.totalLinked} linked work\n items done ({Math.round(kr.linkedWorkItemRollup.completionRatio * 100)}%) &mdash;\n informational, update progress above manually\n </p>\n )}\n {/* BOFF-7956: a key result could not be linked to work items, so the rollup above never appeared. */}\n <KeyResultWorkItems\n keyResultId={kr.id}\n keyResultTitle={kr.title}\n linked={kr.linkedWorkItems ?? []}\n />\n <div className=\"flex items-center gap-2\">\n {kr.metricType === 'BOOLEAN' ? (\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={draftValue}\n onChange={(e) => setDraftValue(e.target.value)}\n >\n <option value=\"0\">Not done</option>\n <option value=\"1\">Done</option>\n </select>\n ) : (\n <Input\n type=\"number\"\n value={draftValue}\n onChange={(e) => setDraftValue(e.target.value)}\n className=\"w-32\"\n />\n )}\n <Button size=\"sm\" variant=\"outline\" disabled={updating} onClick={handleSaveProgress}>\n {updating ? 'Saving...' : 'Update progress'}\n </Button>\n </div>\n </div>\n );\n}\n\nexport function GoalsObjectiveDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const objectiveId = new URLSearchParams(window.location.search).get('objectiveId') ?? '';\n\n const [isAddingKr, setIsAddingKr] = useState(false);\n const [krForm, setKrForm] = useState({\n title: '',\n metricType: 'NUMERIC',\n targetValue: '100',\n unit: '',\n });\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetObjective: {\n id: string;\n title: string;\n description: string | null;\n ownerId: string;\n periodLabel: string;\n startDate: string | null;\n endDate: string | null;\n status: string;\n progress: number;\n createdAt: string;\n keyResults: KeyResultNode[];\n } | null;\n }>(GET_OBJECTIVE, {\n variables: { id: objectiveId },\n skip: !workspaceId || !objectiveId,\n });\n\n const [createKeyResult, { loading: creatingKr }] = useMutation(CREATE_KEY_RESULT);\n const [deleteObjective, { loading: deletingObjective }] = useMutation(DELETE_OBJECTIVE);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n if (error) return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n\n const objective = data?.planmagnetObjective;\n if (!objective) return <div className=\"p-6 text-muted-foreground\">Objective not found.</div>;\n\n const handleAddKeyResult = async () => {\n if (!krForm.title.trim() || !objectiveId) return;\n const target = Number(krForm.metricType === 'BOOLEAN' ? 1 : krForm.targetValue);\n if (Number.isNaN(target)) return;\n setActionError(null);\n try {\n await throwOnError(\n createKeyResult({\n variables: {\n input: {\n objectiveId,\n title: krForm.title.trim(),\n metricType: krForm.metricType,\n targetValue: target,\n unit: krForm.unit.trim() || undefined,\n },\n },\n })\n );\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The key result could not be added.');\n return;\n }\n setKrForm({ title: '', metricType: 'NUMERIC', targetValue: '100', unit: '' });\n setIsAddingKr(false);\n void refetch();\n };\n\n const handleDeleteObjective = async () => {\n setActionError(null);\n try {\n await throwOnError(deleteObjective({ variables: { id: objectiveId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The objective could not be deleted.');\n return;\n }\n navigate('/goals/goals-page');\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <Button variant=\"ghost\" size=\"icon\" onClick={() => navigate('/goals/goals-page')}>\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <Flag className=\"h-6 w-6 text-primary\" />\n <div>\n <h1 className=\"text-2xl font-semibold\">{objective.title}</h1>\n <p className=\"text-sm text-muted-foreground\">{objective.periodLabel}</p>\n </div>\n </div>\n <div className=\"flex items-start gap-2\">\n {/* Status and Edit (BOFF-7956): an objective stayed ACTIVE forever. Delete now asks first. */}\n <ObjectiveHeaderControls objective={objective} />\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete \"${objective.title}\"?`}\n description=\"The objective and its key results are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete objective\"\n destructive\n disabled={deletingObjective}\n onConfirm={() => void handleDeleteObjective()}\n />\n </div>\n </div>\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 className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Overview</CardTitle>\n <Badge variant=\"outline\">{Math.round(objective.progress)}% complete</Badge>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm\">{objective.description ?? 'No description provided.'}</p>\n <ProgressBar progress={objective.progress} />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Key Results</CardTitle>\n {!isAddingKr && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsAddingKr(true)}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Add Key Result\n </Button>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isAddingKr && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Key result title, e.g. Sign 10 new enterprise customers\"\n value={krForm.title}\n onChange={(e) => setKrForm({ ...krForm, title: e.target.value })}\n />\n <div className=\"grid grid-cols-3 gap-3\">\n <select\n className=\"rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={krForm.metricType}\n onChange={(e) => setKrForm({ ...krForm, metricType: e.target.value })}\n >\n <option value=\"NUMERIC\">Numeric</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"BOOLEAN\">Yes / No</option>\n </select>\n {krForm.metricType !== 'BOOLEAN' && (\n <Input\n type=\"number\"\n placeholder=\"Target value\"\n value={krForm.targetValue}\n onChange={(e) => setKrForm({ ...krForm, targetValue: e.target.value })}\n />\n )}\n {krForm.metricType === 'NUMERIC' && (\n <Input\n placeholder=\"Unit (optional)\"\n value={krForm.unit}\n onChange={(e) => setKrForm({ ...krForm, unit: e.target.value })}\n />\n )}\n </div>\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsAddingKr(false);\n setKrForm({\n title: '',\n metricType: 'NUMERIC',\n targetValue: '100',\n unit: '',\n });\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creatingKr || !krForm.title.trim()}\n onClick={handleAddKeyResult}\n >\n {creatingKr ? 'Adding...' : 'Add Key Result'}\n </Button>\n </div>\n </div>\n )}\n {objective.keyResults.length === 0 && !isAddingKr && (\n <p className=\"text-sm text-muted-foreground\">\n No key results yet. Add one to make this objective measurable.\n </p>\n )}\n {objective.keyResults.map((kr) => (\n <KeyResultRow\n key={kr.id}\n kr={kr}\n onUpdated={() => void refetch()}\n onDeleted={() => void refetch()}\n />\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <User className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Owner:</span>\n <span className=\"font-medium\">\n <PersonName id={objective.ownerId} />\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Period:</span>\n <span className=\"font-medium\">{objective.periodLabel}</span>\n </div>\n {objective.endDate && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Target end:</span>\n <span className=\"font-medium\">\n {new Date(objective.endDate).toLocaleDateString()}\n </span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Created:</span>\n <span className=\"font-medium\">\n {objective.createdAt ? new Date(objective.createdAt).toLocaleDateString() : '—'}\n </span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsEditProductPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsEditProductPagePage.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,2BAA2B,4CAwN1C"}
1
+ {"version":3,"file":"ProductsEditProductPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsEditProductPagePage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,2BAA2B,4CA2P1C"}
@@ -1,15 +1,17 @@
1
1
  import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
2
  import { PersonName as n } from "../people/People.js";
3
- import { PlanningFocusBadge as r, PlanningFocusSelect as i } from "../roadmaps/planningFocus.js";
4
- import { useMemo as a, useState as o } from "react";
5
- import { ArrowLeft as s, Loader2 as c, Package as l, Save as u } from "lucide-react";
6
- import { useParams as d } from "react-router";
7
- import { gql as f } from "@apollo/client";
8
- import { useMutation as p, useQuery as m } from "@apollo/client/react";
9
- import { jsx as h, jsxs as g } from "react/jsx-runtime";
10
- import { Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, Input as S } from "@burdenoff/fe-libs/ui";
3
+ import { throwOnError as r } from "../utils/mutationResult.js";
4
+ import { ConfirmButton as i } from "../controls/ConfirmButton.js";
5
+ import { PlanningFocusBadge as a, PlanningFocusSelect as o } from "../roadmaps/planningFocus.js";
6
+ import { useMemo as s, useState as c } from "react";
7
+ import { ArrowLeft as l, Loader2 as u, Package as d, Save as f, Trash2 as p } from "lucide-react";
8
+ import { useParams as m } from "react-router";
9
+ import { gql as h } from "@apollo/client";
10
+ import { useMutation as g, useQuery as _ } from "@apollo/client/react";
11
+ import { jsx as v, jsxs as y } from "react/jsx-runtime";
12
+ import { Button as b, Card as x, CardContent as S, CardHeader as C, CardTitle as w, Input as T } from "@burdenoff/fe-libs/ui";
11
13
  //#region src/planmagnet/pages/ProductsEditProductPagePage.tsx
12
- var C = f`
14
+ var E = h`
13
15
  query GetRoadmapForEdit($id: ID!) {
14
16
  planmagnetRoadmap(id: $id) {
15
17
  id
@@ -28,7 +30,7 @@ var C = f`
28
30
  }
29
31
  }
30
32
  }
31
- `, w = f`
33
+ `, D = h`
32
34
  mutation UpdateRoadmap($id: ID!, $input: PlanmagnetUpdateRoadmapInput!) {
33
35
  planmagnetUpdateRoadmap(id: $id, input: $input) {
34
36
  id
@@ -41,207 +43,233 @@ var C = f`
41
43
  updatedAt
42
44
  }
43
45
  }
44
- `, T = f`
46
+ `, O = h`
45
47
  mutation DeleteRoadmap($id: ID!) {
46
48
  planmagnetDeleteRoadmap(id: $id)
47
49
  }
48
50
  `;
49
- function E() {
50
- let { workspaceId: f } = e(), E = t(), D = d().id ?? "", { data: O, loading: k, error: A } = m(C, {
51
- variables: { id: D },
52
- skip: !D
53
- }), [j, { loading: M, error: N }] = p(w, { refetchQueries: ["ListRoadmaps", "GetRoadmapForEdit"] }), [P, { loading: F }] = p(T, {
54
- refetchQueries: ["ListRoadmaps"],
55
- onCompleted: () => E("/products/products-page")
56
- }), I = O?.planmagnetRoadmap, L = a(() => ({
57
- name: I?.name ?? "",
58
- description: I?.description ?? "",
59
- planningFocus: I?.planningFocus ?? null,
60
- isPublic: I?.isPublic ?? !1
61
- }), [I]), [R, z] = o({}), B = {
62
- ...L,
63
- ...R
64
- }, V = (e) => z(e);
65
- return k && !O ? /* @__PURE__ */ h("div", {
51
+ function k() {
52
+ let { workspaceId: h } = e(), k = t(), A = m().id ?? "", { data: j, loading: M, error: N } = _(E, {
53
+ variables: { id: A },
54
+ skip: !A
55
+ }), [P, { loading: F }] = g(D, {
56
+ refetchQueries: [
57
+ "ListRoadmaps",
58
+ "ListRoadmapsPage",
59
+ "GetRoadmapForEdit",
60
+ "GetRoadmapDetail",
61
+ "GetRoadmapDetailPage"
62
+ ],
63
+ awaitRefetchQueries: !0
64
+ }), [I, { loading: L }] = g(O, { refetchQueries: ["ListRoadmaps", "ListRoadmapsPage"] }), [R, z] = c(null), [B, V] = c(!1), H = j?.planmagnetRoadmap, U = s(() => ({
65
+ name: H?.name ?? "",
66
+ description: H?.description ?? "",
67
+ planningFocus: H?.planningFocus ?? null,
68
+ isPublic: H?.isPublic ?? !1
69
+ }), [H]), [W, G] = c({}), K = {
70
+ ...U,
71
+ ...W
72
+ }, q = (e) => G(e), J = async () => {
73
+ z(null), V(!1);
74
+ try {
75
+ await r(P({ variables: {
76
+ id: A,
77
+ input: {
78
+ name: K.name,
79
+ description: K.description.trim() || null,
80
+ planningFocus: K.planningFocus,
81
+ isPublic: K.isPublic
82
+ }
83
+ } })), V(!0);
84
+ } catch (e) {
85
+ z(e instanceof Error ? e.message : "The product could not be saved.");
86
+ }
87
+ }, Y = async () => {
88
+ z(null);
89
+ try {
90
+ await r(I({ variables: { id: A } })), k("/products/products-page");
91
+ } catch (e) {
92
+ z(e instanceof Error ? e.message : "The product could not be deleted.");
93
+ }
94
+ };
95
+ return M && !j ? /* @__PURE__ */ v("div", {
66
96
  className: "flex items-center justify-center p-12",
67
- children: /* @__PURE__ */ h(c, { className: "h-8 w-8 animate-spin text-muted-foreground" })
68
- }) : A ? /* @__PURE__ */ g("div", {
97
+ children: /* @__PURE__ */ v(u, { className: "h-8 w-8 animate-spin text-muted-foreground" })
98
+ }) : N ? /* @__PURE__ */ y("div", {
69
99
  className: "p-6 text-destructive",
70
- children: ["Failed to load product: ", A.message]
71
- }) : I ? /* @__PURE__ */ g("div", {
100
+ children: ["Failed to load product: ", N.message]
101
+ }) : H ? /* @__PURE__ */ y("div", {
72
102
  className: "space-y-6 p-6",
73
103
  children: [
74
- /* @__PURE__ */ g("div", {
104
+ /* @__PURE__ */ y("div", {
75
105
  className: "flex items-center justify-between",
76
- children: [/* @__PURE__ */ g("div", {
106
+ children: [/* @__PURE__ */ y("div", {
77
107
  className: "flex items-center gap-3",
78
- children: [/* @__PURE__ */ h(_, {
108
+ children: [/* @__PURE__ */ v(b, {
79
109
  variant: "ghost",
80
110
  size: "sm",
81
- onClick: () => E("/products/products-page"),
82
- children: /* @__PURE__ */ h(s, { className: "h-4 w-4" })
83
- }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
111
+ onClick: () => k("/products/products-page"),
112
+ children: /* @__PURE__ */ v(l, { className: "h-4 w-4" })
113
+ }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
84
114
  className: "text-2xl font-semibold",
85
115
  children: "Edit Product"
86
- }), /* @__PURE__ */ g("p", {
116
+ }), /* @__PURE__ */ y("p", {
87
117
  className: "text-sm text-muted-foreground",
88
- children: ["Editing ", I.name]
118
+ children: ["Editing ", H.name]
89
119
  })] })]
90
- }), /* @__PURE__ */ g(_, {
120
+ }), /* @__PURE__ */ y(b, {
91
121
  size: "sm",
92
- onClick: () => {
93
- j({ variables: {
94
- id: D,
95
- input: {
96
- name: B.name,
97
- description: B.description || void 0,
98
- planningFocus: B.planningFocus,
99
- isPublic: B.isPublic
100
- }
101
- } });
102
- },
103
- disabled: M,
104
- children: [M ? /* @__PURE__ */ h(c, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ h(u, { className: "mr-1 h-4 w-4" }), "Save Changes"]
122
+ onClick: J,
123
+ disabled: F,
124
+ children: [F ? /* @__PURE__ */ v(u, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ v(f, { className: "mr-1 h-4 w-4" }), "Save Changes"]
105
125
  })]
106
126
  }),
107
- N && /* @__PURE__ */ g("div", {
127
+ B && /* @__PURE__ */ v("div", {
128
+ role: "status",
129
+ className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
130
+ children: "Changes saved."
131
+ }),
132
+ R && /* @__PURE__ */ v("div", {
108
133
  role: "alert",
109
134
  className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
110
- children: ["Failed to save: ", N.message]
135
+ children: R
111
136
  }),
112
- /* @__PURE__ */ g("div", {
137
+ /* @__PURE__ */ y("div", {
113
138
  className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
114
- children: [/* @__PURE__ */ h("div", {
139
+ children: [/* @__PURE__ */ v("div", {
115
140
  className: "lg:col-span-2 space-y-6",
116
- children: /* @__PURE__ */ g(v, { children: [/* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(x, { children: "Basic Information" }) }), /* @__PURE__ */ g(y, {
141
+ children: /* @__PURE__ */ y(x, { children: [/* @__PURE__ */ v(C, { children: /* @__PURE__ */ v(w, { children: "Basic Information" }) }), /* @__PURE__ */ y(S, {
117
142
  className: "space-y-4",
118
143
  children: [
119
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
144
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
120
145
  className: "text-sm font-medium",
121
146
  children: "Product Name"
122
- }), /* @__PURE__ */ h(S, {
123
- value: B.name,
124
- onChange: (e) => V({
125
- ...B,
147
+ }), /* @__PURE__ */ v(T, {
148
+ value: K.name,
149
+ onChange: (e) => q({
150
+ ...K,
126
151
  name: e.target.value
127
152
  }),
128
153
  className: "mt-1"
129
154
  })] }),
130
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
155
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
131
156
  className: "text-sm font-medium",
132
157
  children: "Description"
133
- }), /* @__PURE__ */ h("textarea", {
158
+ }), /* @__PURE__ */ v("textarea", {
134
159
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
135
- value: B.description,
136
- onChange: (e) => V({
137
- ...B,
160
+ value: K.description,
161
+ onChange: (e) => q({
162
+ ...K,
138
163
  description: e.target.value
139
164
  })
140
165
  })] }),
141
- /* @__PURE__ */ h(i, {
142
- value: B.planningFocus,
143
- onChange: (e) => V({
144
- ...B,
166
+ /* @__PURE__ */ v(o, {
167
+ value: K.planningFocus,
168
+ onChange: (e) => q({
169
+ ...K,
145
170
  planningFocus: e
146
171
  })
147
172
  }),
148
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
173
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
149
174
  className: "text-sm font-medium",
150
175
  children: "Visibility"
151
- }), /* @__PURE__ */ h("div", {
176
+ }), /* @__PURE__ */ v("div", {
152
177
  className: "mt-1 flex gap-2",
153
- children: [!1, !0].map((e) => /* @__PURE__ */ h("button", {
154
- onClick: () => V({
155
- ...B,
178
+ children: [!1, !0].map((e) => /* @__PURE__ */ v("button", {
179
+ onClick: () => q({
180
+ ...K,
156
181
  isPublic: e
157
182
  }),
158
- className: `rounded-md px-3 py-1.5 text-sm ${B.isPublic === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
183
+ className: `rounded-md px-3 py-1.5 text-sm ${K.isPublic === e ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`,
159
184
  children: e ? "Public" : "Private"
160
185
  }, String(e)))
161
186
  })] })
162
187
  ]
163
188
  })] })
164
- }), /* @__PURE__ */ g("div", {
189
+ }), /* @__PURE__ */ y("div", {
165
190
  className: "space-y-6",
166
- children: [/* @__PURE__ */ g(v, { children: [/* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(x, { children: "Product Info" }) }), /* @__PURE__ */ g(y, {
191
+ children: [/* @__PURE__ */ y(x, { children: [/* @__PURE__ */ v(C, { children: /* @__PURE__ */ v(w, { children: "Product Info" }) }), /* @__PURE__ */ y(S, {
167
192
  className: "space-y-3 text-sm",
168
193
  children: [
169
- /* @__PURE__ */ g("div", {
194
+ /* @__PURE__ */ y("div", {
170
195
  className: "flex items-center gap-2",
171
- children: [/* @__PURE__ */ h(l, { className: "h-4 w-4 text-primary" }), /* @__PURE__ */ h("span", {
196
+ children: [/* @__PURE__ */ v(d, { className: "h-4 w-4 text-primary" }), /* @__PURE__ */ v("span", {
172
197
  className: "font-medium",
173
- children: I.name
198
+ children: H.name
174
199
  })]
175
200
  }),
176
- /* @__PURE__ */ g("div", {
201
+ /* @__PURE__ */ y("div", {
177
202
  className: "flex justify-between",
178
- children: [/* @__PURE__ */ h("span", {
203
+ children: [/* @__PURE__ */ v("span", {
179
204
  className: "text-muted-foreground",
180
205
  children: "Items"
181
- }), /* @__PURE__ */ h("span", {
206
+ }), /* @__PURE__ */ v("span", {
182
207
  className: "font-medium",
183
- children: I.items?.length ?? 0
208
+ children: H.items?.length ?? 0
184
209
  })]
185
210
  }),
186
- /* @__PURE__ */ g("div", {
211
+ /* @__PURE__ */ y("div", {
187
212
  className: "flex justify-between",
188
- children: [/* @__PURE__ */ h("span", {
213
+ children: [/* @__PURE__ */ v("span", {
189
214
  className: "text-muted-foreground",
190
215
  children: "Planning focus"
191
- }), /* @__PURE__ */ h(r, { focus: I.planningFocus })]
216
+ }), /* @__PURE__ */ v(a, { focus: H.planningFocus })]
192
217
  }),
193
- /* @__PURE__ */ g("div", {
218
+ /* @__PURE__ */ y("div", {
194
219
  className: "flex justify-between",
195
- children: [/* @__PURE__ */ h("span", {
220
+ children: [/* @__PURE__ */ v("span", {
196
221
  className: "text-muted-foreground",
197
222
  children: "Visibility"
198
- }), /* @__PURE__ */ h("span", {
223
+ }), /* @__PURE__ */ v("span", {
199
224
  className: "font-medium",
200
- children: I.isPublic ? "Public" : "Private"
225
+ children: H.isPublic ? "Public" : "Private"
201
226
  })]
202
227
  }),
203
- /* @__PURE__ */ g("div", {
228
+ /* @__PURE__ */ y("div", {
204
229
  className: "flex justify-between",
205
- children: [/* @__PURE__ */ h("span", {
230
+ children: [/* @__PURE__ */ v("span", {
206
231
  className: "text-muted-foreground",
207
232
  children: "Owner"
208
- }), /* @__PURE__ */ h("span", {
233
+ }), /* @__PURE__ */ v("span", {
209
234
  className: "font-medium",
210
- children: /* @__PURE__ */ h(n, { id: I.ownerId })
235
+ children: /* @__PURE__ */ v(n, { id: H.ownerId })
211
236
  })]
212
237
  }),
213
- /* @__PURE__ */ g("div", {
238
+ /* @__PURE__ */ y("div", {
214
239
  className: "flex justify-between",
215
- children: [/* @__PURE__ */ h("span", {
240
+ children: [/* @__PURE__ */ v("span", {
216
241
  className: "text-muted-foreground",
217
242
  children: "Created"
218
- }), /* @__PURE__ */ h("span", { children: new Date(I.createdAt).toLocaleDateString() })]
243
+ }), /* @__PURE__ */ v("span", { children: new Date(H.createdAt).toLocaleDateString() })]
219
244
  }),
220
- /* @__PURE__ */ g("div", {
245
+ /* @__PURE__ */ y("div", {
221
246
  className: "flex justify-between",
222
- children: [/* @__PURE__ */ h("span", {
247
+ children: [/* @__PURE__ */ v("span", {
223
248
  className: "text-muted-foreground",
224
249
  children: "Updated"
225
- }), /* @__PURE__ */ h("span", { children: new Date(I.updatedAt).toLocaleDateString() })]
250
+ }), /* @__PURE__ */ v("span", { children: new Date(H.updatedAt).toLocaleDateString() })]
226
251
  })
227
252
  ]
228
- })] }), /* @__PURE__ */ g(v, { children: [/* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(x, { children: "Danger Zone" }) }), /* @__PURE__ */ h(y, { children: /* @__PURE__ */ h(_, {
229
- variant: "destructive",
230
- size: "sm",
231
- className: "w-full",
232
- disabled: F,
233
- onClick: () => P({ variables: { id: D } }),
234
- children: F ? "Archiving..." : "Archive Product"
253
+ })] }), /* @__PURE__ */ y(x, { children: [/* @__PURE__ */ v(C, { children: /* @__PURE__ */ v(w, { children: "Danger Zone" }) }), /* @__PURE__ */ v(S, { children: /* @__PURE__ */ v(i, {
254
+ label: L ? "Deleting…" : "Delete product",
255
+ variant: "outline",
256
+ icon: /* @__PURE__ */ v(p, { className: "mr-1 h-4 w-4" }),
257
+ title: `Delete ${H.name}?`,
258
+ description: "The product's roadmap and its items are deleted. This cannot be undone here.",
259
+ confirmLabel: "Delete product",
260
+ destructive: !0,
261
+ disabled: L,
262
+ onConfirm: () => void Y()
235
263
  }) })] })]
236
264
  })]
237
265
  })
238
266
  ]
239
- }) : /* @__PURE__ */ h("div", {
267
+ }) : /* @__PURE__ */ v("div", {
240
268
  className: "p-6 text-muted-foreground",
241
269
  children: "No product found."
242
270
  });
243
271
  }
244
272
  //#endregion
245
- export { E as ProductsEditProductPagePage };
273
+ export { k as ProductsEditProductPagePage };
246
274
 
247
275
  //# sourceMappingURL=ProductsEditProductPagePage.js.map
@@ -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 } from 'lucide-react';\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, error: saveError }] = useMutation(UPDATE_ROADMAP, {\n refetchQueries: ['ListRoadmaps', 'GetRoadmapForEdit'],\n });\n\n const [deleteRoadmap, { loading: deleting }] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmaps'],\n onCompleted: () => navigate('/products/products-page'),\n });\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 const handleSave = () => {\n updateRoadmap({\n variables: {\n id: roadmapId,\n input: {\n name: form.name,\n description: form.description || undefined,\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\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 {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to save: {saveError.message}\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 <Button\n variant=\"destructive\"\n size=\"sm\"\n className=\"w-full\"\n disabled={deleting}\n onClick={() => deleteRoadmap({ variables: { id: roadmapId } })}\n >\n {deleting ? 'Archiving...' : 'Archive Product'}\n </Button>\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":["// 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 } 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 });\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 +1 @@
1
- {"version":3,"file":"ProductsEditReleasePagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsEditReleasePagePage.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,2BAA2B,4CAqM1C"}
1
+ {"version":3,"file":"ProductsEditReleasePagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsEditReleasePagePage.tsx"],"names":[],"mappings":"AA8CA,wBAAgB,2BAA2B,4CAiO1C"}