@burdenoff/microfe-planmagnet 2026.1001.8 → 2026.1002.2

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 (143) hide show
  1. package/dist/generated/global-types.d.ts +5 -0
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-operations.d.ts +1814 -401
  4. package/dist/generated/wspace-operations.d.ts.map +1 -1
  5. package/dist/generated/wspace-types.d.ts +5167 -54
  6. package/dist/generated/wspace-types.d.ts.map +1 -1
  7. package/dist/index.js +8 -8
  8. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  9. package/dist/planmagnet/PlanMagnetRoot.js +172 -160
  10. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  11. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  12. package/dist/planmagnet/PlanMagnetRoutes.js +95 -83
  13. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  14. package/dist/planmagnet/comments/CommentAttachmentUpload.d.ts +16 -0
  15. package/dist/planmagnet/comments/CommentAttachmentUpload.d.ts.map +1 -0
  16. package/dist/planmagnet/comments/CommentAttachmentUpload.js +218 -0
  17. package/dist/planmagnet/comments/CommentAttachmentUpload.js.map +1 -0
  18. package/dist/planmagnet/comments/CommentAttachments.d.ts +15 -0
  19. package/dist/planmagnet/comments/CommentAttachments.d.ts.map +1 -0
  20. package/dist/planmagnet/comments/CommentAttachments.js +74 -0
  21. package/dist/planmagnet/comments/CommentAttachments.js.map +1 -0
  22. package/dist/planmagnet/comments/CommentBody.d.ts +11 -0
  23. package/dist/planmagnet/comments/CommentBody.d.ts.map +1 -0
  24. package/dist/planmagnet/comments/CommentBody.js +130 -0
  25. package/dist/planmagnet/comments/CommentBody.js.map +1 -0
  26. package/dist/planmagnet/comments/CommentThread.d.ts +7 -2
  27. package/dist/planmagnet/comments/CommentThread.d.ts.map +1 -1
  28. package/dist/planmagnet/comments/CommentThread.js +288 -233
  29. package/dist/planmagnet/comments/CommentThread.js.map +1 -1
  30. package/dist/planmagnet/comments/MentionComposer.d.ts +0 -7
  31. package/dist/planmagnet/comments/MentionComposer.d.ts.map +1 -1
  32. package/dist/planmagnet/comments/MentionComposer.js +225 -86
  33. package/dist/planmagnet/comments/MentionComposer.js.map +1 -1
  34. package/dist/planmagnet/comments/TargetComments.d.ts.map +1 -1
  35. package/dist/planmagnet/comments/TargetComments.js +44 -6
  36. package/dist/planmagnet/comments/TargetComments.js.map +1 -1
  37. package/dist/planmagnet/comments/WorkItemComments.d.ts.map +1 -1
  38. package/dist/planmagnet/comments/WorkItemComments.js +22 -3
  39. package/dist/planmagnet/comments/WorkItemComments.js.map +1 -1
  40. package/dist/planmagnet/comments/commentAttachmentUploadClient.d.ts +102 -0
  41. package/dist/planmagnet/comments/commentAttachmentUploadClient.d.ts.map +1 -0
  42. package/dist/planmagnet/comments/commentAttachmentUploadClient.js +298 -0
  43. package/dist/planmagnet/comments/commentAttachmentUploadClient.js.map +1 -0
  44. package/dist/planmagnet/comments/commentImageTokens.d.ts +5 -0
  45. package/dist/planmagnet/comments/commentImageTokens.d.ts.map +1 -0
  46. package/dist/planmagnet/comments/commentImageTokens.js +14 -0
  47. package/dist/planmagnet/comments/commentImageTokens.js.map +1 -0
  48. package/dist/planmagnet/comments/commentTokens.d.ts +31 -0
  49. package/dist/planmagnet/comments/commentTokens.d.ts.map +1 -0
  50. package/dist/planmagnet/comments/commentTokens.js +142 -0
  51. package/dist/planmagnet/comments/commentTokens.js.map +1 -0
  52. package/dist/planmagnet/comments/fileDisplay.d.ts +22 -0
  53. package/dist/planmagnet/comments/fileDisplay.d.ts.map +1 -0
  54. package/dist/planmagnet/comments/fileDisplay.js +48 -0
  55. package/dist/planmagnet/comments/fileDisplay.js.map +1 -0
  56. package/dist/planmagnet/comments/useCommentFileUrl.d.ts +22 -0
  57. package/dist/planmagnet/comments/useCommentFileUrl.d.ts.map +1 -0
  58. package/dist/planmagnet/comments/useCommentFileUrl.js +54 -0
  59. package/dist/planmagnet/comments/useCommentFileUrl.js.map +1 -0
  60. package/dist/planmagnet/controls/Pickers.d.ts +7 -2
  61. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  62. package/dist/planmagnet/controls/Pickers.js +10 -9
  63. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  64. package/dist/planmagnet/index.d.ts +1 -1
  65. package/dist/planmagnet/index.d.ts.map +1 -1
  66. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +4 -4
  67. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  68. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  69. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +133 -131
  70. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  71. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  72. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +5 -5
  73. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  74. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +6 -6
  75. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  76. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  77. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +105 -82
  78. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  79. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts +2 -0
  80. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.d.ts.map +1 -0
  81. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js +174 -0
  82. package/dist/planmagnet/pages/ProductsProductFeatureDetailPagePage.js.map +1 -0
  83. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts +2 -0
  84. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.d.ts.map +1 -0
  85. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js +350 -0
  86. package/dist/planmagnet/pages/ProductsProductHierarchyPagePage.js.map +1 -0
  87. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts +2 -0
  88. package/dist/planmagnet/pages/ProductsProductLinesPagePage.d.ts.map +1 -0
  89. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js +168 -0
  90. package/dist/planmagnet/pages/ProductsProductLinesPagePage.js.map +1 -0
  91. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  92. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  93. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  94. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  95. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +156 -154
  96. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  97. package/dist/planmagnet/product/FeatureLinkPanels.d.ts +29 -0
  98. package/dist/planmagnet/product/FeatureLinkPanels.d.ts.map +1 -0
  99. package/dist/planmagnet/product/FeatureLinkPanels.js +373 -0
  100. package/dist/planmagnet/product/FeatureLinkPanels.js.map +1 -0
  101. package/dist/planmagnet/product/ProductControls.js +1 -1
  102. package/dist/planmagnet/product/ProductControls.js.map +1 -1
  103. package/dist/planmagnet/product/ProductFeatureControls.d.ts +39 -0
  104. package/dist/planmagnet/product/ProductFeatureControls.d.ts.map +1 -0
  105. package/dist/planmagnet/product/ProductFeatureControls.js +315 -0
  106. package/dist/planmagnet/product/ProductFeatureControls.js.map +1 -0
  107. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts +4 -0
  108. package/dist/planmagnet/product/ProductFeatureLinkCard.d.ts.map +1 -0
  109. package/dist/planmagnet/product/ProductFeatureLinkCard.js +192 -0
  110. package/dist/planmagnet/product/ProductFeatureLinkCard.js.map +1 -0
  111. package/dist/planmagnet/product/ProductLineControls.d.ts +52 -0
  112. package/dist/planmagnet/product/ProductLineControls.d.ts.map +1 -0
  113. package/dist/planmagnet/product/ProductLineControls.js +417 -0
  114. package/dist/planmagnet/product/ProductLineControls.js.map +1 -0
  115. package/dist/planmagnet/product/ProductTeamsPanel.d.ts +4 -0
  116. package/dist/planmagnet/product/ProductTeamsPanel.d.ts.map +1 -0
  117. package/dist/planmagnet/product/ProductTeamsPanel.js +129 -0
  118. package/dist/planmagnet/product/ProductTeamsPanel.js.map +1 -0
  119. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts +4 -0
  120. package/dist/planmagnet/product/WorkItemProductFeatureChip.d.ts.map +1 -0
  121. package/dist/planmagnet/product/WorkItemProductFeatureChip.js +41 -0
  122. package/dist/planmagnet/product/WorkItemProductFeatureChip.js.map +1 -0
  123. package/dist/planmagnet/product/hierarchy.d.ts +127 -0
  124. package/dist/planmagnet/product/hierarchy.d.ts.map +1 -0
  125. package/dist/planmagnet/product/hierarchy.js +173 -0
  126. package/dist/planmagnet/product/hierarchy.js.map +1 -0
  127. package/dist/planmagnet/product/hierarchyOperations.d.ts +35 -0
  128. package/dist/planmagnet/product/hierarchyOperations.d.ts.map +1 -0
  129. package/dist/planmagnet/product/hierarchyOperations.js +440 -0
  130. package/dist/planmagnet/product/hierarchyOperations.js.map +1 -0
  131. package/dist/planmagnet/product/refusal.d.ts +14 -0
  132. package/dist/planmagnet/product/refusal.d.ts.map +1 -0
  133. package/dist/planmagnet/product/refusal.js +10 -0
  134. package/dist/planmagnet/product/refusal.js.map +1 -0
  135. package/dist/planmagnet/product/routes.d.ts +4 -0
  136. package/dist/planmagnet/product/routes.d.ts.map +1 -1
  137. package/dist/planmagnet/product/routes.js +2 -2
  138. package/dist/planmagnet/product/routes.js.map +1 -1
  139. package/dist/shared/utils/i18n.d.ts +3 -0
  140. package/dist/shared/utils/i18n.d.ts.map +1 -1
  141. package/dist/shared/utils/i18n.js +1 -1
  142. package/dist/shared/utils/i18n.js.map +1 -1
  143. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsFeaturesPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsFeaturesPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport {\n Star,\n Plus,\n ThumbsUp,\n MessageSquare,\n Target,\n Filter,\n ArrowUpDown,\n Loader2,\n Gauge,\n} from 'lucide-react';\nimport { countLabel, useFeatureCounts } from '../hooks/useCounts';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { FeatureTriageActions, isSnoozed } from '../controls/FeatureTriage';\nimport { CategoryBadge, RatingDisplay } from '../controls/FeedbackControls';\nimport {\n MOSCOW_VALUES,\n MOSCOW_META,\n KANO_VALUES,\n KANO_META,\n QUADRANT_VALUES,\n QUADRANT_META,\n QuadrantBadge,\n riceScoreMissingHint,\n} from '../controls/PrioritizationControls';\n\nconst LIST_FEATURE_REQUESTS = gql`\n query ListFeatureRequests(\n $workspaceId: ID!\n $filter: PlanmagnetFeatureRequestFilter\n $pagination: PlanmagnetPaginationInput\n $orderBy: PlanmagnetFeatureOrder\n ) {\n planmagnetFeatureRequests(\n workspaceId: $workspaceId\n filter: $filter\n pagination: $pagination\n orderBy: $orderBy\n ) {\n items {\n id\n title\n description\n status\n businessValue\n effortEstimate\n priorityScore\n voteCount\n submittedBy\n tags\n projectId\n snoozedUntil\n moscow\n kano\n valueEffortQuadrant\n reach\n impact\n confidence\n effortScore\n riceScore\n rating\n category\n createdAt\n updatedAt\n }\n totalCount\n }\n }\n`;\n\ninterface FeatureRequestNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n businessValue: number | null;\n effortEstimate: string | null;\n priorityScore: number | null;\n voteCount: number;\n submittedBy: string;\n tags: string[];\n reach: number | null;\n impact: number | null;\n confidence: number | null;\n effortScore: number | null;\n riceScore: number | null;\n rating: number | null;\n category: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nconst statusColor: Record<string, string> = {\n SUBMITTED: 'bg-gray-100 text-gray-800',\n UNDER_REVIEW: 'bg-yellow-100 text-yellow-800',\n PLANNED: 'bg-blue-100 text-blue-800',\n IN_PROGRESS: 'bg-purple-100 text-purple-800',\n COMPLETED: 'bg-green-100 text-green-800',\n DECLINED: 'bg-red-100 text-red-800',\n DUPLICATE: 'bg-orange-100 text-orange-800',\n};\n\nexport function ProductsFeaturesPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { workspaceId } = usePlanMagnet();\n const [statusFilter, setStatusFilter] = useState<string>('all');\n // Snooze state is read against the moment the page opened (Date.now() may not run during render).\n const [now] = useState(() => Date.now());\n // Sorting happens on the server over every request (BOFF-7965: it re-sorted only the 50 loaded rows).\n const [sortBy, setSortBy] = useState<'NEWEST' | 'MOST_VOTED'>('NEWEST');\n // PRM-26: riceScore is computed, not a stored column (utils/prioritization.ts#computeRiceScore) — there is\n // no server `orderBy` value for it (PlanmagnetFeatureOrder only has NEWEST/MOST_VOTED), so this can only ever\n // re-sort the page already loaded above, never every matching request. Kept as its own toggle, independent\n // of `sortBy`, specifically so it is never confused for a third server-side order — the BOFF-7965 regression\n // this file's own comment above warns about.\n const [riceSort, setRiceSort] = useState(false);\n // PRM-27: MoSCoW/Kano/quadrant are server filters too, combined with the status tabs above — not a\n // client-side re-filter of the 50 loaded rows (same reasoning as BOFF-7965 for sorting).\n const [moscowFilter, setMoscowFilter] = useState('');\n const [kanoFilter, setKanoFilter] = useState('');\n const [quadrantFilter, setQuadrantFilter] = useState('');\n const counts = useFeatureCounts();\n\n const baseFilter =\n statusFilter === 'TRIAGE'\n ? { triage: true }\n : statusFilter === 'SNOOZED'\n ? { snoozed: true }\n : statusFilter !== 'all'\n ? { status: statusFilter }\n : {};\n const filter = {\n ...baseFilter,\n ...(moscowFilter ? { moscow: moscowFilter } : {}),\n ...(kanoFilter ? { kano: kanoFilter } : {}),\n ...(quadrantFilter ? { quadrant: quadrantFilter } : {}),\n };\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>>(LIST_FEATURE_REQUESTS, {\n variables: {\n workspaceId,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\n pagination: { first: 50 },\n orderBy: sortBy,\n },\n skip: !workspaceId,\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 features: {error.message}</div>;\n }\n\n const features: FeatureRequestNode[] = data?.planmagnetFeatureRequests?.items ?? [];\n const totalCount = data?.planmagnetFeatureRequests?.totalCount ?? 0;\n\n // PRM-26 — client-side only (see the `riceSort` state comment above): never claims to be a full re-sort of\n // every matching request, only of the up-to-50 rows the server already returned in `sortBy` order. Unscored\n // (null) rows sort last either way, never first as if they scored 0.\n const sorted = riceSort\n ? [...features].sort((a, b) => {\n if (a.riceScore == null && b.riceScore == null) return 0;\n if (a.riceScore == null) return 1;\n if (b.riceScore == null) return -1;\n return b.riceScore - a.riceScore;\n })\n : features;\n\n const statuses = [\n 'all',\n 'TRIAGE',\n 'SNOOZED',\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DECLINED',\n 'DUPLICATE',\n ];\n\n // Tiles are unfiltered server counts; the tabs change only the list (BOFF-7965).\n const stats = {\n total: countLabel(counts?.all),\n open: countLabel(counts?.open),\n inProgress: countLabel(counts?.byStatus.IN_PROGRESS),\n completed: countLabel(counts?.byStatus.COMPLETED),\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Features</h1>\n {/* Under a tab, the count is of what matches it (BOFF-7990). */}\n <p className=\"text-sm text-muted-foreground\">\n {statusFilter === 'all'\n ? `${stats.total} feature requests`\n : statusFilter === 'TRIAGE'\n ? `${totalCount} waiting for triage`\n : `${totalCount} ${statusFilter.toLowerCase().replace('_', ' ')} match`}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/products/new-feature-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New Feature\n </Button>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Total Features</p>\n <p className=\"text-xl font-bold\">{stats.total}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">In Progress</p>\n <p className=\"text-xl font-bold\">{stats.inProgress}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Completed</p>\n <p className=\"text-xl font-bold\">{stats.completed}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Open</p>\n <p className=\"text-xl font-bold\">{stats.open}</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-muted-foreground\" />\n {statuses.map((s) => (\n <button\n key={s}\n onClick={() => setStatusFilter(s)}\n className={`px-2 py-1 text-xs rounded-md capitalize ${statusFilter === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'all'\n ? 'All'\n : s === 'TRIAGE'\n ? `Triage (${countLabel(counts?.triage)})`\n : s === 'SNOOZED'\n ? `Snoozed (${countLabel(counts?.snoozed)})`\n : s.toLowerCase().replace('_', ' ')}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <ArrowUpDown className=\"h-4 w-4 text-muted-foreground\" />\n {(['NEWEST', 'MOST_VOTED'] as const).map((s) => (\n <button\n key={s}\n onClick={() => setSortBy(s)}\n className={`px-2 py-1 text-xs rounded-md ${!riceSort && sortBy === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'NEWEST' ? 'Newest' : 'Most voted'}\n </button>\n ))}\n {/* PRM-26 — see the `riceSort` state comment: re-sorts only the rows already loaded above, never a\n server-side order (PlanmagnetFeatureOrder has no RICE value). */}\n <button\n type=\"button\"\n aria-pressed={riceSort}\n data-testid=\"feature-sort-rice\"\n onClick={() => setRiceSort((v) => !v)}\n className={`px-2 py-1 text-xs rounded-md ${riceSort ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n RICE score\n </button>\n </div>\n </div>\n {riceSort && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"feature-sort-rice-note\">\n Sorted by RICE score within the {sorted.length} loaded results above — not a server-wide\n sort.\n </p>\n )}\n\n {/* PRM-27: MoSCoW/Kano/Value-Effort quadrant filters — server-side, combined with the status tabs above. */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-moscow\" className=\"text-xs text-muted-foreground\">\n MoSCoW\n </label>\n <select\n id=\"feature-filter-moscow\"\n data-testid=\"feature-filter-moscow\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={moscowFilter}\n onChange={(e) => setMoscowFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {MOSCOW_VALUES.map((v) => (\n <option key={v} value={v}>\n {MOSCOW_META[v].label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-kano\" className=\"text-xs text-muted-foreground\">\n Kano\n </label>\n <select\n id=\"feature-filter-kano\"\n data-testid=\"feature-filter-kano\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={kanoFilter}\n onChange={(e) => setKanoFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {KANO_VALUES.map((v) => (\n <option key={v} value={v}>\n {KANO_META[v].label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-quadrant\" className=\"text-xs text-muted-foreground\">\n Quadrant\n </label>\n <select\n id=\"feature-filter-quadrant\"\n data-testid=\"feature-filter-quadrant\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={quadrantFilter}\n onChange={(e) => setQuadrantFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {QUADRANT_VALUES.map((v) => (\n <option key={v} value={v}>\n {QUADRANT_META[v].label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {sorted.map((feature) => (\n <Card\n key={feature.id}\n {...rowLinkProps(\n () => navigate(detailPath('/products/feature-detail-page', feature.id)),\n feature.title\n )}\n >\n <CardContent className=\"py-4\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{feature.title}</span>\n <span\n className={`text-[10px] px-1.5 py-0.5 rounded-full capitalize ${statusColor[feature.status] ?? 'bg-gray-100'}`}\n >\n {feature.status.toLowerCase().replace('_', ' ')}\n </span>\n {/* PRM-27 — hidden when unscored so most cards (not yet prioritized) stay uncluttered. */}\n <QuadrantBadge quadrant={feature.valueEffortQuadrant} hideWhenUnscored />\n </div>\n <p className=\"text-xs text-muted-foreground\">{feature.description}</p>\n <div className=\"flex items-center gap-4 text-xs text-muted-foreground\">\n <span>Effort: {feature.effortEstimate ?? 'N/A'}</span>\n {/* By name, not user UUID (BOFF-7950). */}\n <span>\n Submitted by: <PersonName id={feature.submittedBy} />\n </span>\n <span>{new Date(feature.updatedAt).toLocaleDateString()}</span>\n </div>\n {/* CWF-15: structured feedback — rating/category, hidden when neither is set. */}\n {(feature.rating != null || feature.category) && (\n <div className=\"flex items-center gap-2\">\n {feature.rating != null && <RatingDisplay rating={feature.rating} />}\n <CategoryBadge category={feature.category} />\n </div>\n )}\n {isSnoozed(feature.snoozedUntil, now) && (\n <p className=\"text-xs text-muted-foreground\">\n Snoozed until {new Date(feature.snoozedUntil).toLocaleDateString()}\n </p>\n )}\n <FeatureTriageActions feature={feature} now={now} />\n {feature.tags.length > 0 && (\n <div className=\"flex gap-1\">\n {feature.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-[10px]\">\n {tag}\n </Badge>\n ))}\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-4 shrink-0\">\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3 text-primary\" />\n <span className=\"text-sm font-bold\">{feature.priorityScore ?? 0}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Priority</p>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <ThumbsUp className=\"h-3 w-3 text-green-500\" />\n <span className=\"text-sm font-bold\">{feature.voteCount}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Votes</p>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-yellow-500\" />\n <span className=\"text-sm font-bold\">{feature.businessValue ?? 0}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Value</p>\n </div>\n {/* PRM-26 */}\n <div\n className=\"text-center\"\n title={\n feature.riceScore == null\n ? (riceScoreMissingHint(feature) ?? undefined)\n : undefined\n }\n >\n <div className=\"flex items-center gap-1\">\n <Gauge className=\"h-3 w-3 text-primary\" />\n <span className=\"text-sm font-bold\" data-testid=\"feature-rice-score-cell\">\n {feature.riceScore == null ? '—' : feature.riceScore.toFixed(1)}\n </span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">RICE</p>\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n {sorted.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {statusFilter === 'TRIAGE'\n ? 'Nothing waits for triage.'\n : 'No features match the selected filters.'}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductsFeaturesPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsFeaturesPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge, Button } from '@burdenoff/fe-libs/ui';\nimport {\n Star,\n Plus,\n ThumbsUp,\n MessageSquare,\n Target,\n Filter,\n ArrowUpDown,\n Loader2,\n Gauge,\n} from 'lucide-react';\nimport { countLabel, useFeatureCounts } from '../hooks/useCounts';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\nimport { PersonName } from '../people/People';\nimport { FeatureTriageActions, isSnoozed } from '../controls/FeatureTriage';\nimport { CategoryBadge, RatingDisplay } from '../controls/FeedbackControls';\nimport {\n MOSCOW_VALUES,\n MOSCOW_META,\n KANO_VALUES,\n KANO_META,\n QUADRANT_VALUES,\n QUADRANT_META,\n QuadrantBadge,\n riceScoreMissingHint,\n} from '../controls/PrioritizationControls';\n\nconst LIST_FEATURE_REQUESTS = gql`\n query ListFeatureRequests(\n $workspaceId: ID!\n $filter: PlanmagnetFeatureRequestFilter\n $pagination: PlanmagnetPaginationInput\n $orderBy: PlanmagnetFeatureOrder\n ) {\n planmagnetFeatureRequests(\n workspaceId: $workspaceId\n filter: $filter\n pagination: $pagination\n orderBy: $orderBy\n ) {\n items {\n id\n title\n description\n status\n businessValue\n effortEstimate\n priorityScore\n voteCount\n submittedBy\n tags\n projectId\n snoozedUntil\n moscow\n kano\n valueEffortQuadrant\n reach\n impact\n confidence\n effortScore\n riceScore\n rating\n category\n createdAt\n updatedAt\n }\n totalCount\n }\n }\n`;\n\ninterface FeatureRequestNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n businessValue: number | null;\n effortEstimate: string | null;\n priorityScore: number | null;\n voteCount: number;\n submittedBy: string;\n tags: string[];\n reach: number | null;\n impact: number | null;\n confidence: number | null;\n effortScore: number | null;\n riceScore: number | null;\n rating: number | null;\n category: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nconst statusColor: Record<string, string> = {\n SUBMITTED: 'bg-gray-100 text-gray-800',\n UNDER_REVIEW: 'bg-yellow-100 text-yellow-800',\n PLANNED: 'bg-blue-100 text-blue-800',\n IN_PROGRESS: 'bg-purple-100 text-purple-800',\n COMPLETED: 'bg-green-100 text-green-800',\n DECLINED: 'bg-red-100 text-red-800',\n DUPLICATE: 'bg-orange-100 text-orange-800',\n};\n\nexport function ProductsFeaturesPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { workspaceId } = usePlanMagnet();\n const [statusFilter, setStatusFilter] = useState<string>('all');\n // Snooze state is read against the moment the page opened (Date.now() may not run during render).\n const [now] = useState(() => Date.now());\n // Sorting happens on the server over every request (BOFF-7965: it re-sorted only the 50 loaded rows).\n const [sortBy, setSortBy] = useState<'NEWEST' | 'MOST_VOTED'>('NEWEST');\n // PRM-26: riceScore is computed, not a stored column (utils/prioritization.ts#computeRiceScore) — there is\n // no server `orderBy` value for it (PlanmagnetFeatureOrder only has NEWEST/MOST_VOTED), so this can only ever\n // re-sort the page already loaded above, never every matching request. Kept as its own toggle, independent\n // of `sortBy`, specifically so it is never confused for a third server-side order — the BOFF-7965 regression\n // this file's own comment above warns about.\n const [riceSort, setRiceSort] = useState(false);\n // PRM-27: MoSCoW/Kano/quadrant are server filters too, combined with the status tabs above — not a\n // client-side re-filter of the 50 loaded rows (same reasoning as BOFF-7965 for sorting).\n const [moscowFilter, setMoscowFilter] = useState('');\n const [kanoFilter, setKanoFilter] = useState('');\n const [quadrantFilter, setQuadrantFilter] = useState('');\n const counts = useFeatureCounts();\n\n const baseFilter =\n statusFilter === 'TRIAGE'\n ? { triage: true }\n : statusFilter === 'SNOOZED'\n ? { snoozed: true }\n : statusFilter !== 'all'\n ? { status: statusFilter }\n : {};\n const filter = {\n ...baseFilter,\n ...(moscowFilter ? { moscow: moscowFilter } : {}),\n ...(kanoFilter ? { kano: kanoFilter } : {}),\n ...(quadrantFilter ? { quadrant: quadrantFilter } : {}),\n };\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>>(LIST_FEATURE_REQUESTS, {\n variables: {\n workspaceId,\n filter: Object.keys(filter).length > 0 ? filter : undefined,\n pagination: { first: 50 },\n orderBy: sortBy,\n },\n skip: !workspaceId,\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 (\n <div className=\"p-6 text-destructive\">Failed to load feature requests: {error.message}</div>\n );\n }\n\n const features: FeatureRequestNode[] = data?.planmagnetFeatureRequests?.items ?? [];\n const totalCount = data?.planmagnetFeatureRequests?.totalCount ?? 0;\n\n // PRM-26 — client-side only (see the `riceSort` state comment above): never claims to be a full re-sort of\n // every matching request, only of the up-to-50 rows the server already returned in `sortBy` order. Unscored\n // (null) rows sort last either way, never first as if they scored 0.\n const sorted = riceSort\n ? [...features].sort((a, b) => {\n if (a.riceScore == null && b.riceScore == null) return 0;\n if (a.riceScore == null) return 1;\n if (b.riceScore == null) return -1;\n return b.riceScore - a.riceScore;\n })\n : features;\n\n const statuses = [\n 'all',\n 'TRIAGE',\n 'SNOOZED',\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'DECLINED',\n 'DUPLICATE',\n ];\n\n // Tiles are unfiltered server counts; the tabs change only the list (BOFF-7965).\n const stats = {\n total: countLabel(counts?.all),\n open: countLabel(counts?.open),\n inProgress: countLabel(counts?.byStatus.IN_PROGRESS),\n completed: countLabel(counts?.byStatus.COMPLETED),\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n {/* PRM-01: these are feature REQUESTS (customer/user feedback); a product's own features —\n capabilities the team builds — live on each product's page. Path and name unchanged. */}\n <h1 className=\"text-2xl font-semibold\">Feature requests</h1>\n {/* Under a tab, the count is of what matches it (BOFF-7990). */}\n <p className=\"text-sm text-muted-foreground\">\n {statusFilter === 'all'\n ? `${stats.total} feature requests`\n : statusFilter === 'TRIAGE'\n ? `${totalCount} waiting for triage`\n : `${totalCount} ${statusFilter.toLowerCase().replace('_', ' ')} match`}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/products/new-feature-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New feature request\n </Button>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Total feature requests</p>\n <p className=\"text-xl font-bold\">{stats.total}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">In Progress</p>\n <p className=\"text-xl font-bold\">{stats.inProgress}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Completed</p>\n <p className=\"text-xl font-bold\">{stats.completed}</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">Open</p>\n <p className=\"text-xl font-bold\">{stats.open}</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-muted-foreground\" />\n {statuses.map((s) => (\n <button\n key={s}\n onClick={() => setStatusFilter(s)}\n className={`px-2 py-1 text-xs rounded-md capitalize ${statusFilter === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'all'\n ? 'All'\n : s === 'TRIAGE'\n ? `Triage (${countLabel(counts?.triage)})`\n : s === 'SNOOZED'\n ? `Snoozed (${countLabel(counts?.snoozed)})`\n : s.toLowerCase().replace('_', ' ')}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <ArrowUpDown className=\"h-4 w-4 text-muted-foreground\" />\n {(['NEWEST', 'MOST_VOTED'] as const).map((s) => (\n <button\n key={s}\n onClick={() => setSortBy(s)}\n className={`px-2 py-1 text-xs rounded-md ${!riceSort && sortBy === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'NEWEST' ? 'Newest' : 'Most voted'}\n </button>\n ))}\n {/* PRM-26 — see the `riceSort` state comment: re-sorts only the rows already loaded above, never a\n server-side order (PlanmagnetFeatureOrder has no RICE value). */}\n <button\n type=\"button\"\n aria-pressed={riceSort}\n data-testid=\"feature-sort-rice\"\n onClick={() => setRiceSort((v) => !v)}\n className={`px-2 py-1 text-xs rounded-md ${riceSort ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n RICE score\n </button>\n </div>\n </div>\n {riceSort && (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"feature-sort-rice-note\">\n Sorted by RICE score within the {sorted.length} loaded results above — not a server-wide\n sort.\n </p>\n )}\n\n {/* PRM-27: MoSCoW/Kano/Value-Effort quadrant filters — server-side, combined with the status tabs above. */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-moscow\" className=\"text-xs text-muted-foreground\">\n MoSCoW\n </label>\n <select\n id=\"feature-filter-moscow\"\n data-testid=\"feature-filter-moscow\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={moscowFilter}\n onChange={(e) => setMoscowFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {MOSCOW_VALUES.map((v) => (\n <option key={v} value={v}>\n {MOSCOW_META[v].label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-kano\" className=\"text-xs text-muted-foreground\">\n Kano\n </label>\n <select\n id=\"feature-filter-kano\"\n data-testid=\"feature-filter-kano\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={kanoFilter}\n onChange={(e) => setKanoFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {KANO_VALUES.map((v) => (\n <option key={v} value={v}>\n {KANO_META[v].label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-1\">\n <label htmlFor=\"feature-filter-quadrant\" className=\"text-xs text-muted-foreground\">\n Quadrant\n </label>\n <select\n id=\"feature-filter-quadrant\"\n data-testid=\"feature-filter-quadrant\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-xs\"\n value={quadrantFilter}\n onChange={(e) => setQuadrantFilter(e.target.value)}\n >\n <option value=\"\">All</option>\n {QUADRANT_VALUES.map((v) => (\n <option key={v} value={v}>\n {QUADRANT_META[v].label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {sorted.map((feature) => (\n <Card\n key={feature.id}\n {...rowLinkProps(\n () => navigate(detailPath('/products/feature-detail-page', feature.id)),\n feature.title\n )}\n >\n <CardContent className=\"py-4\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{feature.title}</span>\n <span\n className={`text-[10px] px-1.5 py-0.5 rounded-full capitalize ${statusColor[feature.status] ?? 'bg-gray-100'}`}\n >\n {feature.status.toLowerCase().replace('_', ' ')}\n </span>\n {/* PRM-27 — hidden when unscored so most cards (not yet prioritized) stay uncluttered. */}\n <QuadrantBadge quadrant={feature.valueEffortQuadrant} hideWhenUnscored />\n </div>\n <p className=\"text-xs text-muted-foreground\">{feature.description}</p>\n <div className=\"flex items-center gap-4 text-xs text-muted-foreground\">\n <span>Effort: {feature.effortEstimate ?? 'N/A'}</span>\n {/* By name, not user UUID (BOFF-7950). */}\n <span>\n Submitted by: <PersonName id={feature.submittedBy} />\n </span>\n <span>{new Date(feature.updatedAt).toLocaleDateString()}</span>\n </div>\n {/* CWF-15: structured feedback — rating/category, hidden when neither is set. */}\n {(feature.rating != null || feature.category) && (\n <div className=\"flex items-center gap-2\">\n {feature.rating != null && <RatingDisplay rating={feature.rating} />}\n <CategoryBadge category={feature.category} />\n </div>\n )}\n {isSnoozed(feature.snoozedUntil, now) && (\n <p className=\"text-xs text-muted-foreground\">\n Snoozed until {new Date(feature.snoozedUntil).toLocaleDateString()}\n </p>\n )}\n <FeatureTriageActions feature={feature} now={now} />\n {feature.tags.length > 0 && (\n <div className=\"flex gap-1\">\n {feature.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-[10px]\">\n {tag}\n </Badge>\n ))}\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-4 shrink-0\">\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3 text-primary\" />\n <span className=\"text-sm font-bold\">{feature.priorityScore ?? 0}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Priority</p>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <ThumbsUp className=\"h-3 w-3 text-green-500\" />\n <span className=\"text-sm font-bold\">{feature.voteCount}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Votes</p>\n </div>\n <div className=\"text-center\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-yellow-500\" />\n <span className=\"text-sm font-bold\">{feature.businessValue ?? 0}</span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">Value</p>\n </div>\n {/* PRM-26 */}\n <div\n className=\"text-center\"\n title={\n feature.riceScore == null\n ? (riceScoreMissingHint(feature) ?? undefined)\n : undefined\n }\n >\n <div className=\"flex items-center gap-1\">\n <Gauge className=\"h-3 w-3 text-primary\" />\n <span className=\"text-sm font-bold\" data-testid=\"feature-rice-score-cell\">\n {feature.riceScore == null ? '—' : feature.riceScore.toFixed(1)}\n </span>\n </div>\n <p className=\"text-[10px] text-muted-foreground\">RICE</p>\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n {sorted.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {statusFilter === 'TRIAGE'\n ? 'Nothing waits for triage.'\n : 'No feature requests match the selected filters.'}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -65,7 +65,7 @@ function A() {
65
65
  className: "flex items-center justify-between",
66
66
  children: [/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("h1", {
67
67
  className: "text-2xl font-semibold",
68
- children: "New Feature"
68
+ children: "New feature request"
69
69
  }), /* @__PURE__ */ x("p", {
70
70
  className: "text-sm text-muted-foreground",
71
71
  children: "Define a new feature request with business value metrics."
@@ -103,21 +103,21 @@ function A() {
103
103
  isAnonymous: z
104
104
  } } })), A("/products/features-page");
105
105
  } catch (e) {
106
- W(e instanceof Error ? e.message : "The feature could not be created.");
106
+ W(e instanceof Error ? e.message : "The feature request could not be created.");
107
107
  }
108
108
  }
109
109
  },
110
110
  disabled: H || !j.title.trim() || G.invalid || !K || !Y || $.invalid,
111
- children: [H ? /* @__PURE__ */ x(g, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ x(_, { className: "mr-1 h-4 w-4" }), "Create Feature"]
111
+ children: [H ? /* @__PURE__ */ x(g, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ x(_, { className: "mr-1 h-4 w-4" }), "Create feature request"]
112
112
  })]
113
113
  })]
114
114
  }),
115
115
  U && /* @__PURE__ */ S("div", {
116
116
  role: "alert",
117
117
  className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
118
- children: ["Failed to create feature: ", U]
118
+ children: ["Failed to create feature request: ", U]
119
119
  }),
120
- /* @__PURE__ */ S(w, { children: [/* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(D, { children: "Feature Details" }) }), /* @__PURE__ */ S(T, {
120
+ /* @__PURE__ */ S(w, { children: [/* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(D, { children: "Request details" }) }), /* @__PURE__ */ S(T, {
121
121
  className: "space-y-4",
122
122
  children: [
123
123
  /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("label", {
@@ -142,7 +142,7 @@ function A() {
142
142
  ...j,
143
143
  description: e.target.value
144
144
  }),
145
- placeholder: "Describe the feature and its user value..."
145
+ placeholder: "Describe what is being asked for and why it matters..."
146
146
  })] }),
147
147
  /* @__PURE__ */ S("div", {
148
148
  className: "grid grid-cols-2 gap-4",
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsNewFeaturePagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsNewFeaturePagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, Star, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { parseIntField } from '../utils/formValidation';\nimport { ProjectSelect, WorkItemSearchSelect } from '../controls/Pickers';\nimport {\n EMPTY_PRIORITIZATION_FORM,\n EMPTY_RICE_FORM,\n PrioritizationScoreFields,\n RiceScoreFields,\n prioritizationScoresValid,\n riceInputsValid,\n type PrioritizationFormValues,\n type RiceFormValues,\n} from '../controls/PrioritizationControls';\nimport {\n AnonymousSubmissionCheckbox,\n EMPTY_FEEDBACK_FORM,\n FeedbackFields,\n type FeedbackFormValues,\n} from '../controls/FeedbackControls';\n\nconst CREATE_FEATURE_REQUEST = gql`\n mutation CreateFeatureRequest($input: PlanmagnetCreateFeatureRequestInput!) {\n planmagnetCreateFeatureRequest(input: $input) {\n id\n title\n status\n businessValue\n effortEstimate\n priorityScore\n rating\n category\n isAnonymous\n }\n }\n`;\n\nexport function ProductsNewFeaturePagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n title: '',\n description: '',\n status: 'SUBMITTED' as string,\n businessValue: '50',\n effortEstimate: 'medium',\n tags: '',\n projectId: '',\n workItemId: '',\n });\n // PRM-27: MoSCoW/Kano/value/effort scoring, set at creation just like at edit time.\n const [prioritization, setPrioritization] =\n useState<PrioritizationFormValues>(EMPTY_PRIORITIZATION_FORM);\n // PRM-26: RICE, next to the value/effort scores above — effortScore is riceScore's own divisor.\n const [rice, setRice] = useState<RiceFormValues>(EMPTY_RICE_FORM);\n // CWF-15: structured feedback — rating, category, and whether to hide the requester.\n const [feedback, setFeedback] = useState<FeedbackFormValues>(EMPTY_FEEDBACK_FORM);\n const [isAnonymous, setIsAnonymous] = useState(false);\n\n // Navigate only once the service has accepted the feature: with fe-libs' errorPolicy 'all' a refused\n // mutation resolves, so the old onCompleted navigation could leave the form as if it had saved.\n const [createFeature, { loading: saving }] = useMutation(CREATE_FEATURE_REQUEST, {\n refetchQueries: ['ListFeatureRequests'],\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n // Business value is a whole number from 0 to 100 (BOFF-7833: prod had stored 150).\n const businessValue = parseIntField(form.businessValue, { min: 0, max: 100 });\n const prioritizationValid = prioritizationScoresValid(prioritization);\n const value = parseIntField(prioritization.valueScore, { min: 1, max: 10 });\n const effort = parseIntField(prioritization.effortScore, { min: 1, max: 10 });\n const riceValid = riceInputsValid(rice);\n const reach = parseIntField(rice.reach, { min: 1, max: 10 });\n const impact = parseIntField(rice.impact, { min: 1, max: 10 });\n const confidence = parseIntField(rice.confidence, { min: 1, max: 10 });\n // CWF-15: 1-5, empty means \"not set\".\n const rating = parseIntField(feedback.rating, { min: 1, max: 5 });\n\n const statusOptions = [\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n ] as const;\n const efforts = ['small', 'medium', 'large', 'epic'] as const;\n\n const handleCreate = async () => {\n if (\n !form.title.trim() ||\n businessValue.invalid ||\n !prioritizationValid ||\n !riceValid ||\n rating.invalid\n )\n return;\n setSaveError(null);\n try {\n await throwOnError(\n createFeature({\n variables: {\n input: {\n title: form.title,\n description: form.description || undefined,\n status: form.status,\n businessValue: businessValue.value,\n effortEstimate: form.effortEstimate,\n projectId: form.projectId || undefined,\n workItemId: form.workItemId || undefined,\n tags: form.tags\n ? form.tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean)\n : undefined,\n moscow: prioritization.moscow || undefined,\n kano: prioritization.kano || undefined,\n valueScore: value.value,\n effortScore: effort.value,\n reach: reach.value,\n impact: impact.value,\n confidence: confidence.value,\n rating: rating.value,\n category: feedback.category.trim() || undefined,\n isAnonymous,\n },\n },\n })\n );\n navigate('/products/features-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The feature could not be created.');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">New Feature</h1>\n <p className=\"text-sm text-muted-foreground\">\n Define a new feature request with business value metrics.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/products/features-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={handleCreate}\n disabled={\n saving ||\n !form.title.trim() ||\n businessValue.invalid ||\n !prioritizationValid ||\n !riceValid ||\n rating.invalid\n }\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 Create Feature\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to create feature: {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Feature Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Title</label>\n <Input\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Advanced Search Filters\"\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-[100px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Describe the feature and its user value...\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Status</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statusOptions.map((s) => (\n <option key={s} value={s}>\n {s.toLowerCase().replace('_', ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Effort Estimate</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.effortEstimate}\n onChange={(e) => setForm({ ...form, effortEstimate: e.target.value })}\n >\n {efforts.map((e) => (\n <option key={e} value={e}>\n {e}\n </option>\n ))}\n </select>\n </div>\n </div>\n {/* BOFF-7966: a request could not be tied to a project or to the work item that delivers it, so the\n release readiness card's \"customer requests riding on unfinished work\" could never fill. */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"feature-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"feature-project\"\n value={form.projectId}\n noneLabel=\"No project\"\n onChange={(projectId) => setForm({ ...form, projectId, workItemId: '' })}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"feature-work-item\" className=\"text-sm font-medium\">\n Delivered by work item\n </label>\n <div className=\"mt-1\">\n <WorkItemSearchSelect\n id=\"feature-work-item\"\n projectId={form.projectId || undefined}\n value={form.workItemId}\n onChange={(workItemId) => setForm({ ...form, workItemId })}\n />\n </div>\n </div>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. search, ux, performance\"\n />\n </div>\n </CardContent>\n </Card>\n\n {/* PRM-27: MoSCoW, Kano and Value-vs-Effort scoring — an alternative to the Business Value card below. */}\n <Card>\n <CardHeader>\n <CardTitle>Prioritization</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <PrioritizationScoreFields\n idPrefix=\"feature-new\"\n values={prioritization}\n onChange={setPrioritization}\n />\n {/* PRM-26: RICE, next to the value/effort scores above. */}\n <div className=\"border-t pt-3\">\n <p className=\"text-sm font-medium mb-1\">RICE</p>\n <RiceScoreFields\n idPrefix=\"feature-new\"\n values={rice}\n onChange={setRice}\n effortScoreMissing={effort.value == null}\n />\n </div>\n </CardContent>\n </Card>\n\n {/* CWF-15: structured feedback — rating, category and whether to hide the requester. */}\n <Card>\n <CardHeader>\n <CardTitle>Feedback</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <FeedbackFields idPrefix=\"feature-new\" values={feedback} onChange={setFeedback} />\n {rating.invalid && (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n Rating is a whole number from 1 to 5.\n </p>\n )}\n <AnonymousSubmissionCheckbox\n idPrefix=\"feature-new\"\n checked={isAnonymous}\n onChange={setIsAnonymous}\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Star className=\"h-4 w-4\" />\n Business Value\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"feature-business-value\" className=\"text-sm font-medium\">\n Business Value (0-100)\n </label>\n <Input\n id=\"feature-business-value\"\n type=\"number\"\n min={0}\n max={100}\n step={1}\n value={form.businessValue}\n onChange={(e) => setForm({ ...form, businessValue: e.target.value })}\n aria-invalid={businessValue.invalid}\n className=\"mt-1\"\n />\n {businessValue.invalid ? (\n <p role=\"alert\" className=\"text-xs text-destructive mt-1\">\n Business value must be a whole number from 0 to 100.\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground mt-1\">\n How much business value does this feature deliver?\n </p>\n )}\n </div>\n\n <div className=\"p-4 bg-muted rounded-md text-center\">\n <p className=\"text-xs text-muted-foreground\">Business Value Score</p>\n <p className=\"text-3xl font-bold text-primary mt-2\">\n {businessValue.invalid || businessValue.value === undefined\n ? '—'\n : businessValue.value}\n </p>\n <p className=\"text-xs text-muted-foreground\">out of 100</p>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductsNewFeaturePagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsNewFeaturePagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, Star, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { parseIntField } from '../utils/formValidation';\nimport { ProjectSelect, WorkItemSearchSelect } from '../controls/Pickers';\nimport {\n EMPTY_PRIORITIZATION_FORM,\n EMPTY_RICE_FORM,\n PrioritizationScoreFields,\n RiceScoreFields,\n prioritizationScoresValid,\n riceInputsValid,\n type PrioritizationFormValues,\n type RiceFormValues,\n} from '../controls/PrioritizationControls';\nimport {\n AnonymousSubmissionCheckbox,\n EMPTY_FEEDBACK_FORM,\n FeedbackFields,\n type FeedbackFormValues,\n} from '../controls/FeedbackControls';\n\nconst CREATE_FEATURE_REQUEST = gql`\n mutation CreateFeatureRequest($input: PlanmagnetCreateFeatureRequestInput!) {\n planmagnetCreateFeatureRequest(input: $input) {\n id\n title\n status\n businessValue\n effortEstimate\n priorityScore\n rating\n category\n isAnonymous\n }\n }\n`;\n\nexport function ProductsNewFeaturePagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n title: '',\n description: '',\n status: 'SUBMITTED' as string,\n businessValue: '50',\n effortEstimate: 'medium',\n tags: '',\n projectId: '',\n workItemId: '',\n });\n // PRM-27: MoSCoW/Kano/value/effort scoring, set at creation just like at edit time.\n const [prioritization, setPrioritization] =\n useState<PrioritizationFormValues>(EMPTY_PRIORITIZATION_FORM);\n // PRM-26: RICE, next to the value/effort scores above — effortScore is riceScore's own divisor.\n const [rice, setRice] = useState<RiceFormValues>(EMPTY_RICE_FORM);\n // CWF-15: structured feedback — rating, category, and whether to hide the requester.\n const [feedback, setFeedback] = useState<FeedbackFormValues>(EMPTY_FEEDBACK_FORM);\n const [isAnonymous, setIsAnonymous] = useState(false);\n\n // Navigate only once the service has accepted the feature: with fe-libs' errorPolicy 'all' a refused\n // mutation resolves, so the old onCompleted navigation could leave the form as if it had saved.\n const [createFeature, { loading: saving }] = useMutation(CREATE_FEATURE_REQUEST, {\n refetchQueries: ['ListFeatureRequests'],\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n // Business value is a whole number from 0 to 100 (BOFF-7833: prod had stored 150).\n const businessValue = parseIntField(form.businessValue, { min: 0, max: 100 });\n const prioritizationValid = prioritizationScoresValid(prioritization);\n const value = parseIntField(prioritization.valueScore, { min: 1, max: 10 });\n const effort = parseIntField(prioritization.effortScore, { min: 1, max: 10 });\n const riceValid = riceInputsValid(rice);\n const reach = parseIntField(rice.reach, { min: 1, max: 10 });\n const impact = parseIntField(rice.impact, { min: 1, max: 10 });\n const confidence = parseIntField(rice.confidence, { min: 1, max: 10 });\n // CWF-15: 1-5, empty means \"not set\".\n const rating = parseIntField(feedback.rating, { min: 1, max: 5 });\n\n const statusOptions = [\n 'SUBMITTED',\n 'UNDER_REVIEW',\n 'PLANNED',\n 'IN_PROGRESS',\n 'COMPLETED',\n ] as const;\n const efforts = ['small', 'medium', 'large', 'epic'] as const;\n\n const handleCreate = async () => {\n if (\n !form.title.trim() ||\n businessValue.invalid ||\n !prioritizationValid ||\n !riceValid ||\n rating.invalid\n )\n return;\n setSaveError(null);\n try {\n await throwOnError(\n createFeature({\n variables: {\n input: {\n title: form.title,\n description: form.description || undefined,\n status: form.status,\n businessValue: businessValue.value,\n effortEstimate: form.effortEstimate,\n projectId: form.projectId || undefined,\n workItemId: form.workItemId || undefined,\n tags: form.tags\n ? form.tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean)\n : undefined,\n moscow: prioritization.moscow || undefined,\n kano: prioritization.kano || undefined,\n valueScore: value.value,\n effortScore: effort.value,\n reach: reach.value,\n impact: impact.value,\n confidence: confidence.value,\n rating: rating.value,\n category: feedback.category.trim() || undefined,\n isAnonymous,\n },\n },\n })\n );\n navigate('/products/features-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The feature request could not be created.');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">New feature request</h1>\n <p className=\"text-sm text-muted-foreground\">\n Define a new feature request with business value metrics.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/products/features-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={handleCreate}\n disabled={\n saving ||\n !form.title.trim() ||\n businessValue.invalid ||\n !prioritizationValid ||\n !riceValid ||\n rating.invalid\n }\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 Create feature request\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to create feature request: {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Request details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Title</label>\n <Input\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Advanced Search Filters\"\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-[100px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Describe what is being asked for and why it matters...\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Status</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statusOptions.map((s) => (\n <option key={s} value={s}>\n {s.toLowerCase().replace('_', ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Effort Estimate</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.effortEstimate}\n onChange={(e) => setForm({ ...form, effortEstimate: e.target.value })}\n >\n {efforts.map((e) => (\n <option key={e} value={e}>\n {e}\n </option>\n ))}\n </select>\n </div>\n </div>\n {/* BOFF-7966: a request could not be tied to a project or to the work item that delivers it, so the\n release readiness card's \"customer requests riding on unfinished work\" could never fill. */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"feature-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"feature-project\"\n value={form.projectId}\n noneLabel=\"No project\"\n onChange={(projectId) => setForm({ ...form, projectId, workItemId: '' })}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"feature-work-item\" className=\"text-sm font-medium\">\n Delivered by work item\n </label>\n <div className=\"mt-1\">\n <WorkItemSearchSelect\n id=\"feature-work-item\"\n projectId={form.projectId || undefined}\n value={form.workItemId}\n onChange={(workItemId) => setForm({ ...form, workItemId })}\n />\n </div>\n </div>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. search, ux, performance\"\n />\n </div>\n </CardContent>\n </Card>\n\n {/* PRM-27: MoSCoW, Kano and Value-vs-Effort scoring — an alternative to the Business Value card below. */}\n <Card>\n <CardHeader>\n <CardTitle>Prioritization</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <PrioritizationScoreFields\n idPrefix=\"feature-new\"\n values={prioritization}\n onChange={setPrioritization}\n />\n {/* PRM-26: RICE, next to the value/effort scores above. */}\n <div className=\"border-t pt-3\">\n <p className=\"text-sm font-medium mb-1\">RICE</p>\n <RiceScoreFields\n idPrefix=\"feature-new\"\n values={rice}\n onChange={setRice}\n effortScoreMissing={effort.value == null}\n />\n </div>\n </CardContent>\n </Card>\n\n {/* CWF-15: structured feedback — rating, category and whether to hide the requester. */}\n <Card>\n <CardHeader>\n <CardTitle>Feedback</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <FeedbackFields idPrefix=\"feature-new\" values={feedback} onChange={setFeedback} />\n {rating.invalid && (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n Rating is a whole number from 1 to 5.\n </p>\n )}\n <AnonymousSubmissionCheckbox\n idPrefix=\"feature-new\"\n checked={isAnonymous}\n onChange={setIsAnonymous}\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Star className=\"h-4 w-4\" />\n Business Value\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"feature-business-value\" className=\"text-sm font-medium\">\n Business Value (0-100)\n </label>\n <Input\n id=\"feature-business-value\"\n type=\"number\"\n min={0}\n max={100}\n step={1}\n value={form.businessValue}\n onChange={(e) => setForm({ ...form, businessValue: e.target.value })}\n aria-invalid={businessValue.invalid}\n className=\"mt-1\"\n />\n {businessValue.invalid ? (\n <p role=\"alert\" className=\"text-xs text-destructive mt-1\">\n Business value must be a whole number from 0 to 100.\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground mt-1\">\n How much business value does this feature deliver?\n </p>\n )}\n </div>\n\n <div className=\"p-4 bg-muted rounded-md text-center\">\n <p className=\"text-xs text-muted-foreground\">Business Value Score</p>\n <p className=\"text-3xl font-bold text-primary mt-2\">\n {businessValue.invalid || businessValue.value === undefined\n ? '—'\n : businessValue.value}\n </p>\n <p className=\"text-xs text-muted-foreground\">out of 100</p>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA4BA,IAAM,IAAyB,CAAG;;;;;;;;;;;;;;;AAgBlC,SAAgB,IAA6B;CAC3C,IAAM,EAAE,mBAAgB,GAAe,EACjC,IAAW,GAAuB,EAElC,CAAC,GAAM,KAAW,EAAS;EAC/B,OAAO;EACP,aAAa;EACb,QAAQ;EACR,eAAe;EACf,gBAAgB;EAChB,MAAM;EACN,WAAW;EACX,YAAY;EACb,CAAC,EAEI,CAAC,GAAgB,KACrB,EAAmC,EAA0B,EAEzD,CAAC,GAAM,KAAW,EAAyB,EAAgB,EAE3D,CAAC,GAAU,KAAe,EAA6B,EAAoB,EAC3E,CAAC,GAAa,KAAkB,EAAS,GAAM,EAI/C,CAAC,GAAe,EAAE,SAAS,OAAY,EAAY,GAAwB,EAC/E,gBAAgB,CAAC,sBAAsB,EACxC,CAAC,EACI,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,IAAgB,EAAc,EAAK,eAAe;EAAE,KAAK;EAAG,KAAK;EAAK,CAAC,EACvE,IAAsB,EAA0B,EAAe,EAC/D,IAAQ,EAAc,EAAe,YAAY;EAAE,KAAK;EAAG,KAAK;EAAI,CAAC,EACrE,IAAS,EAAc,EAAe,aAAa;EAAE,KAAK;EAAG,KAAK;EAAI,CAAC,EACvE,IAAY,EAAgB,EAAK,EACjC,IAAQ,EAAc,EAAK,OAAO;EAAE,KAAK;EAAG,KAAK;EAAI,CAAC,EACtD,IAAS,EAAc,EAAK,QAAQ;EAAE,KAAK;EAAG,KAAK;EAAI,CAAC,EACxD,IAAa,EAAc,EAAK,YAAY;EAAE,KAAK;EAAG,KAAK;EAAI,CAAC,EAEhE,IAAS,EAAc,EAAS,QAAQ;EAAE,KAAK;EAAG,KAAK;EAAG,CAAC;AA2DjE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAAyB;KAAwB,CAAA,EAC/D,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAEzC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAe,EAAS,0BAA0B;gBAAtF,CACE,kBAAC,IAAD,EAAG,WAAU,gBAAiB,CAAA,EAAA,SAEvB;SACT,kBAAC,GAAD;MACE,MAAK;MACL,SAhEW,YAAY;AAE7B,cAAC,EAAK,MAAM,MAAM,IAClB,EAAc,WACd,CAAC,KACD,CAAC,KACD,EAAO,UAGT;UAAa,KAAK;AAClB,YAAI;AAgCF,SA/BA,MAAM,EACJ,EAAc,EACZ,WAAW,EACT,OAAO;UACL,OAAO,EAAK;UACZ,aAAa,EAAK,eAAe,KAAA;UACjC,QAAQ,EAAK;UACb,eAAe,EAAc;UAC7B,gBAAgB,EAAK;UACrB,WAAW,EAAK,aAAa,KAAA;UAC7B,YAAY,EAAK,cAAc,KAAA;UAC/B,MAAM,EAAK,OACP,EAAK,KACF,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ,GAClB,KAAA;UACJ,QAAQ,EAAe,UAAU,KAAA;UACjC,MAAM,EAAe,QAAQ,KAAA;UAC7B,YAAY,EAAM;UAClB,aAAa,EAAO;UACpB,OAAO,EAAM;UACb,QAAQ,EAAO;UACf,YAAY,EAAW;UACvB,QAAQ,EAAO;UACf,UAAU,EAAS,SAAS,MAAM,IAAI,KAAA;UACtC;UACD,EACF,EACF,CAAC,CACH,EACD,EAAS,0BAA0B;iBAC5B,GAAG;AACV,WAAa,aAAa,QAAQ,EAAE,UAAU,4CAA4C;;;;MAqBpF,UACE,KACA,CAAC,EAAK,MAAM,MAAM,IAClB,EAAc,WACd,CAAC,KACD,CAAC,KACD,EAAO;gBATX,CAYG,IACC,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,GAEjD,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EACjC,yBAEK;QACL;OACF;;GAEL,KACC,kBAAC,OAAD;IAAK,MAAK;IAAQ,WAAU;cAA5B,CAAwF,sCACnD,EAC/B;;GAGR,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,mBAA2B,CAAA,EAC3B,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAsB;MAAa,CAAA,EACpD,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,OAAO,EAAE,OAAO;OAAO,CAAC;MAC5D,WAAU;MACV,aAAY;MACZ,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAsB;MAAmB,CAAA,EAC1D,kBAAC,YAAD;MACE,WAAU;MACV,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,aAAa,EAAE,OAAO;OAAO,CAAC;MAClE,aAAY;MACZ,CAAA,CACE,EAAA,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAsB;OAAc,CAAA,EACrD,kBAAC,UAAD;OACE,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,QAAQ,EAAE,OAAO;QAAO,CAAC;iBAhIrD;QACpB;QACA;QACA;QACA;QACA;QACD,CA4H4B,KAAK,MAClB,kBAAC,UAAD;QAAgB,OAAO;kBACpB,EAAE,aAAa,CAAC,QAAQ,KAAK,IAAI;QAC3B,EAFI,EAEJ,CACT;OACK,CAAA,CACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAsB;OAAuB,CAAA,EAC9D,kBAAC,UAAD;OACE,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,gBAAgB,EAAE,OAAO;QAAO,CAAC;iBAvInE;QAAC;QAAS;QAAU;QAAS;QAAO,CAyI7B,KAAK,MACZ,kBAAC,UAAD;QAAgB,OAAO;kBACpB;QACM,EAFI,EAEJ,CACT;OACK,CAAA,CACL,EAAA,CAAA,CACF;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAkB,WAAU;iBAAsB;OAEzD,CAAA,EACR,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,IAAG;QACH,OAAO,EAAK;QACZ,WAAU;QACV,WAAW,MAAc,EAAQ;SAAE,GAAG;SAAM;SAAW,YAAY;SAAI,CAAC;QACxE,CAAA;OACE,CAAA,CACF,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAoB,WAAU;iBAAsB;OAE3D,CAAA,EACR,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,IAAG;QACH,WAAW,EAAK,aAAa,KAAA;QAC7B,OAAO,EAAK;QACZ,WAAW,MAAe,EAAQ;SAAE,GAAG;SAAM;SAAY,CAAC;QAC1D,CAAA;OACE,CAAA,CACF,EAAA,CAAA,CACF;;KACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAsB;MAA8B,CAAA,EACrE,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;OAAO,CAAC;MAC3D,WAAU;MACV,aAAY;MACZ,CAAA,CACE,EAAA,CAAA;KACM;MACT,EAAA,CAAA;GAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,kBAA0B,CAAA,EAC1B,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,GAAD;KACE,UAAS;KACT,QAAQ;KACR,UAAU;KACV,CAAA,EAEF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA2B;MAAQ,CAAA,EAChD,kBAAC,GAAD;MACE,UAAS;MACT,QAAQ;MACR,UAAU;MACV,oBAAoB,EAAO,SAAS;MACpC,CAAA,CACE;OACM;MACT,EAAA,CAAA;GAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,GAAD;MAAgB,UAAS;MAAc,QAAQ;MAAU,UAAU;MAAe,CAAA;KACjF,EAAO,WACN,kBAAC,KAAD;MAAG,MAAK;MAAQ,WAAU;gBAA2B;MAEjD,CAAA;KAEN,kBAAC,GAAD;MACE,UAAS;MACT,SAAS;MACT,UAAU;MACV,CAAA;KACU;MACT,EAAA,CAAA;GAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAW,WAAU;cAArB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,iBAElB;OACD,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,SAAD;MAAO,SAAQ;MAAyB,WAAU;gBAAsB;MAEhE,CAAA;KACR,kBAAC,GAAD;MACE,IAAG;MACH,MAAK;MACL,KAAK;MACL,KAAK;MACL,MAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,eAAe,EAAE,OAAO;OAAO,CAAC;MACpE,gBAAc,EAAc;MAC5B,WAAU;MACV,CAAA;KACD,EAAc,UACb,kBAAC,KAAD;MAAG,MAAK;MAAQ,WAAU;gBAAgC;MAEtD,CAAA,GAEJ,kBAAC,KAAD;MAAG,WAAU;gBAAqC;MAE9C,CAAA;KAEF,EAAA,CAAA,EAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAgC;OAAwB,CAAA;MACrE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAc,WAAW,EAAc,UAAU,KAAA,IAC9C,MACA,EAAc;OAChB,CAAA;MACJ,kBAAC,KAAD;OAAG,WAAU;iBAAgC;OAAc,CAAA;MACvD;OACM;MACT,EAAA,CAAA;GACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"AAyHA,wBAAgB,6BAA6B,4CAqL5C"}
1
+ {"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"AAiIA,wBAAgB,6BAA6B,4CA0N5C"}
@@ -5,13 +5,16 @@ import { detailPath as r } from "../utils/rowNavigation.js";
5
5
  import { useEntityId as i } from "../hooks/useEntityId.js";
6
6
  import { PlanMagnetNotFoundPage as a } from "./NotFoundPage.js";
7
7
  import { ProductAboutPanel as o, ProductCompetitorsPanel as s, ProductFeatureRequestsPanel as c, ProductHeaderActions as l, ProductObjectivesPanel as u, ProductReleasesPanel as d, ProductRoadmapsPanel as f } from "../product/ProductControls.js";
8
- import { Calendar as p, Loader2 as m, Package as h, Pencil as g, Target as _, Users as v } from "lucide-react";
9
- import { gql as y } from "@apollo/client";
10
- import { useQuery as b } from "@apollo/client/react";
11
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
12
- import { Badge as C, Button as w, Card as T, CardContent as E, CardHeader as D, CardTitle as O } from "@burdenoff/fe-libs/ui";
8
+ import { ProductFeaturesPanel as p } from "../product/ProductFeatureControls.js";
9
+ import { ProductLinePicker as m } from "../product/ProductLineControls.js";
10
+ import { ProductTeamsPanel as h } from "../product/ProductTeamsPanel.js";
11
+ import { Calendar as g, ListTree as _, Loader2 as v, Package as y, Pencil as b, Target as x, Users as S } from "lucide-react";
12
+ import { gql as C } from "@apollo/client";
13
+ import { useQuery as w } from "@apollo/client/react";
14
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
15
+ import { Badge as D, Button as O, Card as k, CardContent as A, CardHeader as j, CardTitle as M } from "@burdenoff/fe-libs/ui";
13
16
  //#region src/planmagnet/pages/ProductsProductDetailPagePage.tsx
14
- var k = y`
17
+ var N = C`
15
18
  query GetProductDetail($id: ID!) {
16
19
  planmagnetProduct(id: $id) {
17
20
  id
@@ -66,158 +69,178 @@ var k = y`
66
69
  }
67
70
  }
68
71
  `;
69
- function A() {
70
- let y = e(), A = i("productId") ?? "", { data: j, loading: M, error: N } = b(k, {
71
- variables: { id: A },
72
- skip: !A
72
+ function P() {
73
+ let C = e(), P = i("productId") ?? "", { data: F, loading: I, error: L } = w(N, {
74
+ variables: { id: P },
75
+ skip: !P
73
76
  });
74
- if (!A) return /* @__PURE__ */ x("div", {
77
+ if (!P) return /* @__PURE__ */ T("div", {
75
78
  className: "p-6 text-muted-foreground",
76
79
  children: "No product selected. Open one from the list."
77
80
  });
78
- if (M && !j) return /* @__PURE__ */ x("div", {
81
+ if (I && !F) return /* @__PURE__ */ T("div", {
79
82
  className: "flex items-center justify-center p-12",
80
- children: /* @__PURE__ */ x(m, { className: "h-8 w-8 animate-spin text-muted-foreground" })
83
+ children: /* @__PURE__ */ T(v, { className: "h-8 w-8 animate-spin text-muted-foreground" })
81
84
  });
82
- if (N) return /* @__PURE__ */ S("div", {
85
+ if (L) return /* @__PURE__ */ E("div", {
83
86
  className: "p-6 text-destructive",
84
- children: ["Failed to load product: ", N.message]
87
+ children: ["Failed to load product: ", L.message]
85
88
  });
86
- let P = j?.planmagnetProduct;
87
- return P ? /* @__PURE__ */ S("div", {
89
+ let R = F?.planmagnetProduct;
90
+ return R ? /* @__PURE__ */ E("div", {
88
91
  className: "space-y-6 p-6",
89
92
  children: [
90
- /* @__PURE__ */ S("div", {
93
+ /* @__PURE__ */ E("div", {
91
94
  className: "flex items-start justify-between gap-4",
92
- children: [/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ S("div", {
95
+ children: [/* @__PURE__ */ E("div", { children: [/* @__PURE__ */ E("div", {
93
96
  className: "flex items-center gap-2",
94
97
  children: [
95
- /* @__PURE__ */ x(h, { className: "h-5 w-5 text-primary" }),
96
- /* @__PURE__ */ x("h1", {
98
+ /* @__PURE__ */ T(y, { className: "h-5 w-5 text-primary" }),
99
+ /* @__PURE__ */ T("h1", {
97
100
  className: "text-2xl font-semibold",
98
- children: P.name
101
+ children: R.name
99
102
  }),
100
- P.archivedAt && /* @__PURE__ */ x(C, {
103
+ R.archivedAt && /* @__PURE__ */ T(D, {
101
104
  variant: "outline",
102
105
  children: "Archived"
103
106
  }),
104
- /* @__PURE__ */ x(C, {
107
+ /* @__PURE__ */ T(D, {
105
108
  variant: "secondary",
106
109
  className: "capitalize",
107
- children: n(P.lifecycleStage)
110
+ children: n(R.lifecycleStage)
108
111
  })
109
112
  ]
110
- }), /* @__PURE__ */ x("p", {
113
+ }), /* @__PURE__ */ T("p", {
111
114
  className: "text-sm text-muted-foreground mt-1",
112
- children: P.vision || "No vision set"
113
- })] }), /* @__PURE__ */ x("div", {
115
+ children: R.vision || "No vision set"
116
+ })] }), /* @__PURE__ */ T("div", {
114
117
  className: "flex flex-col items-end gap-2",
115
- children: /* @__PURE__ */ S("div", {
118
+ children: /* @__PURE__ */ E("div", {
116
119
  className: "flex gap-2",
117
- children: [/* @__PURE__ */ S(w, {
120
+ children: [/* @__PURE__ */ E(O, {
118
121
  size: "sm",
119
122
  variant: "outline",
120
- onClick: () => y(r("/products/edit-product-page", P.id)),
121
- children: [/* @__PURE__ */ x(g, { className: "mr-1 h-4 w-4" }), "Edit"]
122
- }), /* @__PURE__ */ x(l, {
123
- productId: P.id,
124
- name: P.name,
125
- archivedAt: P.archivedAt
123
+ onClick: () => C(r("/products/edit-product-page", R.id)),
124
+ children: [/* @__PURE__ */ T(b, { className: "mr-1 h-4 w-4" }), "Edit"]
125
+ }), /* @__PURE__ */ T(l, {
126
+ productId: R.id,
127
+ name: R.name,
128
+ archivedAt: R.archivedAt
126
129
  })]
127
130
  })
128
131
  })]
129
132
  }),
130
- /* @__PURE__ */ S("div", {
133
+ /* @__PURE__ */ E("div", {
131
134
  className: "grid grid-cols-2 md:grid-cols-4 gap-4",
132
135
  children: [
133
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
136
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
134
137
  className: "pt-4",
135
- children: [/* @__PURE__ */ S("div", {
138
+ children: [/* @__PURE__ */ E("div", {
136
139
  className: "flex items-center gap-2 text-muted-foreground text-xs",
137
- children: [/* @__PURE__ */ x(_, { className: "h-4 w-4" }), "North Star"]
138
- }), P.northStar ? /* @__PURE__ */ S("p", {
140
+ children: [/* @__PURE__ */ T(x, { className: "h-4 w-4" }), "North Star"]
141
+ }), R.northStar ? /* @__PURE__ */ E("p", {
139
142
  className: "text-sm font-bold mt-1 truncate",
140
143
  children: [
141
- P.northStar.current,
144
+ R.northStar.current,
142
145
  " / ",
143
- P.northStar.target,
146
+ R.northStar.target,
144
147
  " ",
145
- P.northStar.unit,
146
- P.northStar.progressRatio == null ? "" : ` (${Math.round(P.northStar.progressRatio * 100)}%)`
148
+ R.northStar.unit,
149
+ R.northStar.progressRatio == null ? "" : ` (${Math.round(R.northStar.progressRatio * 100)}%)`
147
150
  ]
148
- }) : /* @__PURE__ */ x("p", {
151
+ }) : /* @__PURE__ */ T("p", {
149
152
  className: "text-sm font-medium mt-1 text-muted-foreground",
150
153
  children: "Not set"
151
154
  })]
152
155
  }) }),
153
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
156
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
154
157
  className: "pt-4",
155
- children: [/* @__PURE__ */ S("div", {
158
+ children: [/* @__PURE__ */ E("div", {
156
159
  className: "flex items-center gap-2 text-muted-foreground text-xs",
157
- children: [/* @__PURE__ */ x(v, { className: "h-4 w-4" }), "Owner"]
158
- }), /* @__PURE__ */ x("p", {
160
+ children: [/* @__PURE__ */ T(S, { className: "h-4 w-4" }), "Owner"]
161
+ }), /* @__PURE__ */ T("p", {
159
162
  className: "text-sm font-bold mt-1 truncate",
160
- children: /* @__PURE__ */ x(t, { id: P.ownerId })
163
+ children: /* @__PURE__ */ T(t, { id: R.ownerId })
161
164
  })]
162
165
  }) }),
163
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
166
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
164
167
  className: "pt-4",
165
- children: [/* @__PURE__ */ S("div", {
168
+ children: [/* @__PURE__ */ E("div", {
166
169
  className: "flex items-center gap-2 text-muted-foreground text-xs",
167
- children: [/* @__PURE__ */ x(p, { className: "h-4 w-4" }), "Created"]
168
- }), /* @__PURE__ */ x("p", {
170
+ children: [/* @__PURE__ */ T(g, { className: "h-4 w-4" }), "Created"]
171
+ }), /* @__PURE__ */ T("p", {
169
172
  className: "text-xl font-bold mt-1",
170
- children: new Date(P.createdAt).toLocaleDateString()
173
+ children: new Date(R.createdAt).toLocaleDateString()
171
174
  })]
172
175
  }) }),
173
- /* @__PURE__ */ x(T, { children: /* @__PURE__ */ S(E, {
176
+ /* @__PURE__ */ T(k, { children: /* @__PURE__ */ E(A, {
174
177
  className: "pt-4",
175
- children: [/* @__PURE__ */ S("div", {
178
+ children: [/* @__PURE__ */ E("div", {
176
179
  className: "flex items-center gap-2 text-muted-foreground text-xs",
177
- children: [/* @__PURE__ */ x(p, { className: "h-4 w-4" }), "Updated"]
178
- }), /* @__PURE__ */ x("p", {
180
+ children: [/* @__PURE__ */ T(g, { className: "h-4 w-4" }), "Updated"]
181
+ }), /* @__PURE__ */ T("p", {
179
182
  className: "text-xl font-bold mt-1",
180
- children: new Date(P.updatedAt).toLocaleDateString()
183
+ children: new Date(R.updatedAt).toLocaleDateString()
181
184
  })]
182
185
  }) })
183
186
  ]
184
187
  }),
185
- P.description && /* @__PURE__ */ x(T, { children: /* @__PURE__ */ x(E, {
188
+ R.description && /* @__PURE__ */ T(k, { children: /* @__PURE__ */ T(A, {
186
189
  className: "pt-4 text-sm text-muted-foreground",
187
- children: P.description
190
+ children: R.description
188
191
  }) }),
189
- /* @__PURE__ */ S("div", {
192
+ /* @__PURE__ */ E("div", {
193
+ className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
194
+ children: [/* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Product line" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(m, { productId: R.id }) })] }), /* @__PURE__ */ E(k, {
195
+ className: "lg:col-span-2",
196
+ children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Teams" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(h, { productId: R.id }) })]
197
+ })]
198
+ }),
199
+ /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ E(j, {
200
+ className: "flex flex-row items-center justify-between gap-2",
201
+ children: [/* @__PURE__ */ T(M, { children: "Features" }), /* @__PURE__ */ E(O, {
202
+ size: "sm",
203
+ variant: "ghost",
204
+ onClick: () => C("/products/product-hierarchy-page"),
205
+ "data-testid": "product-view-hierarchy",
206
+ children: [/* @__PURE__ */ T(_, { className: "mr-1 h-4 w-4" }), "View hierarchy"]
207
+ })]
208
+ }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(p, {
209
+ productId: R.id,
210
+ productArchived: !!R.archivedAt
211
+ }) })] }),
212
+ /* @__PURE__ */ E("div", {
190
213
  className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
191
- children: [/* @__PURE__ */ x(o, { product: P }), /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Competitive landscape" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(s, {
192
- productId: P.id,
193
- competitors: P.competitors
214
+ children: [/* @__PURE__ */ T(o, { product: R }), /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Competitive landscape" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(s, {
215
+ productId: R.id,
216
+ competitors: R.competitors
194
217
  }) })] })]
195
218
  }),
196
- /* @__PURE__ */ S("div", {
219
+ /* @__PURE__ */ E("div", {
197
220
  className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
198
221
  children: [
199
- /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Roadmaps" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(f, {
200
- productId: P.id,
201
- roadmaps: P.roadmaps
222
+ /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Roadmaps" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(f, {
223
+ productId: R.id,
224
+ roadmaps: R.roadmaps
202
225
  }) })] }),
203
- /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Releases" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(d, {
204
- productId: P.id,
205
- releases: P.releases
226
+ /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Releases" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(d, {
227
+ productId: R.id,
228
+ releases: R.releases
206
229
  }) })] }),
207
- /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "Feature Requests" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(c, {
208
- productId: P.id,
209
- featureRequests: P.featureRequests
230
+ /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "Feature requests" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(c, {
231
+ productId: R.id,
232
+ featureRequests: R.featureRequests
210
233
  }) })] }),
211
- /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(D, { children: /* @__PURE__ */ x(O, { children: "OKR Objectives" }) }), /* @__PURE__ */ x(E, { children: /* @__PURE__ */ x(u, {
212
- productId: P.id,
213
- objectives: P.objectives
234
+ /* @__PURE__ */ E(k, { children: [/* @__PURE__ */ T(j, { children: /* @__PURE__ */ T(M, { children: "OKR Objectives" }) }), /* @__PURE__ */ T(A, { children: /* @__PURE__ */ T(u, {
235
+ productId: R.id,
236
+ objectives: R.objectives
214
237
  }) })] })
215
238
  ]
216
239
  })
217
240
  ]
218
- }) : /* @__PURE__ */ x(a, {});
241
+ }) : /* @__PURE__ */ T(a, {});
219
242
  }
220
243
  //#endregion
221
- export { A as ProductsProductDetailPagePage };
244
+ export { P as ProductsProductDetailPagePage };
222
245
 
223
246
  //# sourceMappingURL=ProductsProductDetailPagePage.js.map