@burdenoff/microfe-planmagnet 2026.1001.4 → 2026.1001.6

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 (62) hide show
  1. package/dist/planmagnet/controls/CompetitorControls.d.ts +71 -0
  2. package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -1
  3. package/dist/planmagnet/controls/CompetitorControls.js +346 -12
  4. package/dist/planmagnet/controls/CompetitorControls.js.map +1 -1
  5. package/dist/planmagnet/controls/FeatureControls.d.ts +5 -0
  6. package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
  7. package/dist/planmagnet/controls/FeatureControls.js +363 -218
  8. package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
  9. package/dist/planmagnet/controls/Pickers.d.ts +15 -0
  10. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  11. package/dist/planmagnet/controls/Pickers.js +61 -1
  12. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  13. package/dist/planmagnet/controls/PrioritizationControls.d.ts +36 -0
  14. package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -1
  15. package/dist/planmagnet/controls/PrioritizationControls.js +118 -1
  16. package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -1
  17. package/dist/planmagnet/dashboards/DashboardUI.js +1 -1
  18. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
  19. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +67 -59
  20. package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
  21. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -1
  22. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +1 -1
  23. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -1
  24. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  25. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +289 -282
  26. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  27. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -1
  28. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +126 -63
  29. package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -1
  30. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -1
  31. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +14 -0
  32. package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -1
  33. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -1
  34. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +93 -57
  35. package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -1
  36. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -1
  37. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +125 -83
  38. package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -1
  39. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
  40. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +226 -126
  41. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
  42. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  43. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +162 -122
  44. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  45. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
  46. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +135 -109
  47. package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
  48. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +15 -3
  50. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/TeamTeamPagePage.js +59 -53
  53. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts +8 -0
  55. package/dist/planmagnet/pages/analytics/MetricsCard.d.ts.map +1 -0
  56. package/dist/planmagnet/pages/analytics/MetricsCard.js +164 -0
  57. package/dist/planmagnet/pages/analytics/MetricsCard.js.map +1 -0
  58. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts +2 -0
  59. package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts.map +1 -0
  60. package/dist/planmagnet/team/UnmatchedCrewIdentities.js +155 -0
  61. package/dist/planmagnet/team/UnmatchedCrewIdentities.js.map +1 -0
  62. package/package.json +1 -1
@@ -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} 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 {\n MOSCOW_VALUES,\n MOSCOW_META,\n KANO_VALUES,\n KANO_META,\n QUADRANT_VALUES,\n QUADRANT_META,\n QuadrantBadge,\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 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 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-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 const sorted = 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 ${sortBy === s ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground'}`}\n >\n {s === 'NEWEST' ? 'Newest' : 'Most voted'}\n </button>\n ))}\n </div>\n </div>\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 {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 </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 {\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 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 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 {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 +1 @@
1
- {"version":3,"file":"ProductsNewFeaturePagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsNewFeaturePagePage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,0BAA0B,4CAyQzC"}
1
+ {"version":3,"file":"ProductsNewFeaturePagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsNewFeaturePagePage.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,0BAA0B,4CAkSzC"}
@@ -2,15 +2,15 @@ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/Pla
2
2
  import { throwOnError as n } from "../utils/mutationResult.js";
3
3
  import { ProjectSelect as r, WorkItemSearchSelect as i } from "../controls/Pickers.js";
4
4
  import { parseIntField as a } from "../utils/formValidation.js";
5
- import { EMPTY_PRIORITIZATION_FORM as o, PrioritizationScoreFields as s, prioritizationScoresValid as c } from "../controls/PrioritizationControls.js";
6
- import { useState as l } from "react";
7
- import { Loader2 as u, Save as d, Star as f, X as p } from "lucide-react";
8
- import { gql as m } from "@apollo/client";
9
- import { useMutation as h } from "@apollo/client/react";
10
- import { jsx as g, jsxs as _ } from "react/jsx-runtime";
11
- import { Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
5
+ import { EMPTY_PRIORITIZATION_FORM as o, EMPTY_RICE_FORM as s, PrioritizationScoreFields as c, RiceScoreFields as l, prioritizationScoresValid as u, riceInputsValid as d } from "../controls/PrioritizationControls.js";
6
+ import { useState as f } from "react";
7
+ import { Loader2 as p, Save as m, Star as h, X as g } from "lucide-react";
8
+ import { gql as _ } from "@apollo/client";
9
+ import { useMutation as v } from "@apollo/client/react";
10
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
11
+ import { Button as x, Card as S, CardContent as C, CardHeader as w, CardTitle as T, Input as E } from "@burdenoff/fe-libs/ui";
12
12
  //#region src/planmagnet/pages/ProductsNewFeaturePagePage.tsx
13
- var w = m`
13
+ var D = _`
14
14
  mutation CreateFeatureRequest($input: PlanmagnetCreateFeatureRequestInput!) {
15
15
  planmagnetCreateFeatureRequest(input: $input) {
16
16
  id
@@ -22,8 +22,8 @@ var w = m`
22
22
  }
23
23
  }
24
24
  `;
25
- function T() {
26
- let { workspaceId: m } = e(), T = t(), [E, D] = l({
25
+ function O() {
26
+ let { workspaceId: _ } = e(), O = t(), [k, A] = f({
27
27
  title: "",
28
28
  description: "",
29
29
  status: "SUBMITTED",
@@ -32,106 +32,118 @@ function T() {
32
32
  tags: "",
33
33
  projectId: "",
34
34
  workItemId: ""
35
- }), [O, k] = l(o), [A, { loading: j }] = h(w, { refetchQueries: ["ListFeatureRequests"] }), [M, N] = l(null), P = a(E.businessValue, {
35
+ }), [j, M] = f(o), [N, P] = f(s), [F, { loading: I }] = v(D, { refetchQueries: ["ListFeatureRequests"] }), [L, R] = f(null), z = a(k.businessValue, {
36
36
  min: 0,
37
37
  max: 100
38
- }), F = c(O), I = a(O.valueScore, {
38
+ }), B = u(j), V = a(j.valueScore, {
39
39
  min: 1,
40
40
  max: 10
41
- }), L = a(O.effortScore, {
41
+ }), H = a(j.effortScore, {
42
+ min: 1,
43
+ max: 10
44
+ }), U = d(N), W = a(N.reach, {
45
+ min: 1,
46
+ max: 10
47
+ }), G = a(N.impact, {
48
+ min: 1,
49
+ max: 10
50
+ }), K = a(N.confidence, {
42
51
  min: 1,
43
52
  max: 10
44
53
  });
45
- return /* @__PURE__ */ _("div", {
54
+ return /* @__PURE__ */ b("div", {
46
55
  className: "space-y-6 p-6 max-w-4xl",
47
56
  children: [
48
- /* @__PURE__ */ _("div", {
57
+ /* @__PURE__ */ b("div", {
49
58
  className: "flex items-center justify-between",
50
- children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h1", {
59
+ children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h1", {
51
60
  className: "text-2xl font-semibold",
52
61
  children: "New Feature"
53
- }), /* @__PURE__ */ g("p", {
62
+ }), /* @__PURE__ */ y("p", {
54
63
  className: "text-sm text-muted-foreground",
55
64
  children: "Define a new feature request with business value metrics."
56
- })] }), /* @__PURE__ */ _("div", {
65
+ })] }), /* @__PURE__ */ b("div", {
57
66
  className: "flex gap-2",
58
- children: [/* @__PURE__ */ _(v, {
67
+ children: [/* @__PURE__ */ b(x, {
59
68
  variant: "outline",
60
69
  size: "sm",
61
- onClick: () => T("/products/features-page"),
62
- children: [/* @__PURE__ */ g(p, { className: "mr-1 h-4 w-4" }), "Cancel"]
63
- }), /* @__PURE__ */ _(v, {
70
+ onClick: () => O("/products/features-page"),
71
+ children: [/* @__PURE__ */ y(g, { className: "mr-1 h-4 w-4" }), "Cancel"]
72
+ }), /* @__PURE__ */ b(x, {
64
73
  size: "sm",
65
74
  onClick: async () => {
66
- if (!(!E.title.trim() || P.invalid || !F)) {
67
- N(null);
75
+ if (!(!k.title.trim() || z.invalid || !B || !U)) {
76
+ R(null);
68
77
  try {
69
- await n(A({ variables: { input: {
70
- title: E.title,
71
- description: E.description || void 0,
72
- status: E.status,
73
- businessValue: P.value,
74
- effortEstimate: E.effortEstimate,
75
- projectId: E.projectId || void 0,
76
- workItemId: E.workItemId || void 0,
77
- tags: E.tags ? E.tags.split(",").map((e) => e.trim()).filter(Boolean) : void 0,
78
- moscow: O.moscow || void 0,
79
- kano: O.kano || void 0,
80
- valueScore: I.value,
81
- effortScore: L.value
82
- } } })), T("/products/features-page");
78
+ await n(F({ variables: { input: {
79
+ title: k.title,
80
+ description: k.description || void 0,
81
+ status: k.status,
82
+ businessValue: z.value,
83
+ effortEstimate: k.effortEstimate,
84
+ projectId: k.projectId || void 0,
85
+ workItemId: k.workItemId || void 0,
86
+ tags: k.tags ? k.tags.split(",").map((e) => e.trim()).filter(Boolean) : void 0,
87
+ moscow: j.moscow || void 0,
88
+ kano: j.kano || void 0,
89
+ valueScore: V.value,
90
+ effortScore: H.value,
91
+ reach: W.value,
92
+ impact: G.value,
93
+ confidence: K.value
94
+ } } })), O("/products/features-page");
83
95
  } catch (e) {
84
- N(e instanceof Error ? e.message : "The feature could not be created.");
96
+ R(e instanceof Error ? e.message : "The feature could not be created.");
85
97
  }
86
98
  }
87
99
  },
88
- disabled: j || !E.title.trim() || P.invalid || !F,
89
- children: [j ? /* @__PURE__ */ g(u, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "mr-1 h-4 w-4" }), "Create Feature"]
100
+ disabled: I || !k.title.trim() || z.invalid || !B || !U,
101
+ children: [I ? /* @__PURE__ */ y(p, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ y(m, { className: "mr-1 h-4 w-4" }), "Create Feature"]
90
102
  })]
91
103
  })]
92
104
  }),
93
- M && /* @__PURE__ */ _("div", {
105
+ L && /* @__PURE__ */ b("div", {
94
106
  role: "alert",
95
107
  className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
96
- children: ["Failed to create feature: ", M]
108
+ children: ["Failed to create feature: ", L]
97
109
  }),
98
- /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ g(S, { children: "Feature Details" }) }), /* @__PURE__ */ _(b, {
110
+ /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(T, { children: "Feature Details" }) }), /* @__PURE__ */ b(C, {
99
111
  className: "space-y-4",
100
112
  children: [
101
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
113
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
102
114
  className: "text-sm font-medium",
103
115
  children: "Title"
104
- }), /* @__PURE__ */ g(C, {
105
- value: E.title,
106
- onChange: (e) => D({
107
- ...E,
116
+ }), /* @__PURE__ */ y(E, {
117
+ value: k.title,
118
+ onChange: (e) => A({
119
+ ...k,
108
120
  title: e.target.value
109
121
  }),
110
122
  className: "mt-1",
111
123
  placeholder: "e.g. Advanced Search Filters"
112
124
  })] }),
113
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
125
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
114
126
  className: "text-sm font-medium",
115
127
  children: "Description"
116
- }), /* @__PURE__ */ g("textarea", {
128
+ }), /* @__PURE__ */ y("textarea", {
117
129
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[100px]",
118
- value: E.description,
119
- onChange: (e) => D({
120
- ...E,
130
+ value: k.description,
131
+ onChange: (e) => A({
132
+ ...k,
121
133
  description: e.target.value
122
134
  }),
123
135
  placeholder: "Describe the feature and its user value..."
124
136
  })] }),
125
- /* @__PURE__ */ _("div", {
137
+ /* @__PURE__ */ b("div", {
126
138
  className: "grid grid-cols-2 gap-4",
127
- children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
139
+ children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
128
140
  className: "text-sm font-medium",
129
141
  children: "Status"
130
- }), /* @__PURE__ */ g("select", {
142
+ }), /* @__PURE__ */ y("select", {
131
143
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize",
132
- value: E.status,
133
- onChange: (e) => D({
134
- ...E,
144
+ value: k.status,
145
+ onChange: (e) => A({
146
+ ...k,
135
147
  status: e.target.value
136
148
  }),
137
149
  children: [
@@ -140,18 +152,18 @@ function T() {
140
152
  "PLANNED",
141
153
  "IN_PROGRESS",
142
154
  "COMPLETED"
143
- ].map((e) => /* @__PURE__ */ g("option", {
155
+ ].map((e) => /* @__PURE__ */ y("option", {
144
156
  value: e,
145
157
  children: e.toLowerCase().replace("_", " ")
146
158
  }, e))
147
- })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
159
+ })] }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
148
160
  className: "text-sm font-medium",
149
161
  children: "Effort Estimate"
150
- }), /* @__PURE__ */ g("select", {
162
+ }), /* @__PURE__ */ y("select", {
151
163
  className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize",
152
- value: E.effortEstimate,
153
- onChange: (e) => D({
154
- ...E,
164
+ value: k.effortEstimate,
165
+ onChange: (e) => A({
166
+ ...k,
155
167
  effortEstimate: e.target.value
156
168
  }),
157
169
  children: [
@@ -159,54 +171,54 @@ function T() {
159
171
  "medium",
160
172
  "large",
161
173
  "epic"
162
- ].map((e) => /* @__PURE__ */ g("option", {
174
+ ].map((e) => /* @__PURE__ */ y("option", {
163
175
  value: e,
164
176
  children: e
165
177
  }, e))
166
178
  })] })]
167
179
  }),
168
- /* @__PURE__ */ _("div", {
180
+ /* @__PURE__ */ b("div", {
169
181
  className: "grid grid-cols-2 gap-4",
170
- children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
182
+ children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
171
183
  htmlFor: "feature-project",
172
184
  className: "text-sm font-medium",
173
185
  children: "Project"
174
- }), /* @__PURE__ */ g("div", {
186
+ }), /* @__PURE__ */ y("div", {
175
187
  className: "mt-1",
176
- children: /* @__PURE__ */ g(r, {
188
+ children: /* @__PURE__ */ y(r, {
177
189
  id: "feature-project",
178
- value: E.projectId,
190
+ value: k.projectId,
179
191
  noneLabel: "No project",
180
- onChange: (e) => D({
181
- ...E,
192
+ onChange: (e) => A({
193
+ ...k,
182
194
  projectId: e,
183
195
  workItemId: ""
184
196
  })
185
197
  })
186
- })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
198
+ })] }), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
187
199
  htmlFor: "feature-work-item",
188
200
  className: "text-sm font-medium",
189
201
  children: "Delivered by work item"
190
- }), /* @__PURE__ */ g("div", {
202
+ }), /* @__PURE__ */ y("div", {
191
203
  className: "mt-1",
192
- children: /* @__PURE__ */ g(i, {
204
+ children: /* @__PURE__ */ y(i, {
193
205
  id: "feature-work-item",
194
- projectId: E.projectId || void 0,
195
- value: E.workItemId,
196
- onChange: (e) => D({
197
- ...E,
206
+ projectId: k.projectId || void 0,
207
+ value: k.workItemId,
208
+ onChange: (e) => A({
209
+ ...k,
198
210
  workItemId: e
199
211
  })
200
212
  })
201
213
  })] })]
202
214
  }),
203
- /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
215
+ /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
204
216
  className: "text-sm font-medium",
205
217
  children: "Tags (comma-separated)"
206
- }), /* @__PURE__ */ g(C, {
207
- value: E.tags,
208
- onChange: (e) => D({
209
- ...E,
218
+ }), /* @__PURE__ */ y(E, {
219
+ value: k.tags,
220
+ onChange: (e) => A({
221
+ ...k,
210
222
  tags: e.target.value
211
223
  }),
212
224
  className: "mt-1",
@@ -214,56 +226,70 @@ function T() {
214
226
  })] })
215
227
  ]
216
228
  })] }),
217
- /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ g(S, { children: "Prioritization" }) }), /* @__PURE__ */ g(b, { children: /* @__PURE__ */ g(s, {
218
- idPrefix: "feature-new",
219
- values: O,
220
- onChange: k
221
- }) })] }),
222
- /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ g(x, { children: /* @__PURE__ */ _(S, {
229
+ /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ y(T, { children: "Prioritization" }) }), /* @__PURE__ */ b(C, {
230
+ className: "space-y-4",
231
+ children: [/* @__PURE__ */ y(c, {
232
+ idPrefix: "feature-new",
233
+ values: j,
234
+ onChange: M
235
+ }), /* @__PURE__ */ b("div", {
236
+ className: "border-t pt-3",
237
+ children: [/* @__PURE__ */ y("p", {
238
+ className: "text-sm font-medium mb-1",
239
+ children: "RICE"
240
+ }), /* @__PURE__ */ y(l, {
241
+ idPrefix: "feature-new",
242
+ values: N,
243
+ onChange: P,
244
+ effortScoreMissing: H.value == null
245
+ })]
246
+ })]
247
+ })] }),
248
+ /* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(w, { children: /* @__PURE__ */ b(T, {
223
249
  className: "flex items-center gap-2",
224
- children: [/* @__PURE__ */ g(f, { className: "h-4 w-4" }), "Business Value"]
225
- }) }), /* @__PURE__ */ _(b, {
250
+ children: [/* @__PURE__ */ y(h, { className: "h-4 w-4" }), "Business Value"]
251
+ }) }), /* @__PURE__ */ b(C, {
226
252
  className: "space-y-4",
227
- children: [/* @__PURE__ */ _("div", { children: [
228
- /* @__PURE__ */ g("label", {
253
+ children: [/* @__PURE__ */ b("div", { children: [
254
+ /* @__PURE__ */ y("label", {
229
255
  htmlFor: "feature-business-value",
230
256
  className: "text-sm font-medium",
231
257
  children: "Business Value (0-100)"
232
258
  }),
233
- /* @__PURE__ */ g(C, {
259
+ /* @__PURE__ */ y(E, {
234
260
  id: "feature-business-value",
235
261
  type: "number",
236
262
  min: 0,
237
263
  max: 100,
238
264
  step: 1,
239
- value: E.businessValue,
240
- onChange: (e) => D({
241
- ...E,
265
+ value: k.businessValue,
266
+ onChange: (e) => A({
267
+ ...k,
242
268
  businessValue: e.target.value
243
269
  }),
244
- "aria-invalid": P.invalid,
270
+ "aria-invalid": z.invalid,
245
271
  className: "mt-1"
246
272
  }),
247
- P.invalid ? /* @__PURE__ */ g("p", {
273
+ z.invalid ? /* @__PURE__ */ y("p", {
248
274
  role: "alert",
249
275
  className: "text-xs text-destructive mt-1",
250
276
  children: "Business value must be a whole number from 0 to 100."
251
- }) : /* @__PURE__ */ g("p", {
277
+ }) : /* @__PURE__ */ y("p", {
252
278
  className: "text-xs text-muted-foreground mt-1",
253
279
  children: "How much business value does this feature deliver?"
254
280
  })
255
- ] }), /* @__PURE__ */ _("div", {
281
+ ] }), /* @__PURE__ */ b("div", {
256
282
  className: "p-4 bg-muted rounded-md text-center",
257
283
  children: [
258
- /* @__PURE__ */ g("p", {
284
+ /* @__PURE__ */ y("p", {
259
285
  className: "text-xs text-muted-foreground",
260
286
  children: "Business Value Score"
261
287
  }),
262
- /* @__PURE__ */ g("p", {
288
+ /* @__PURE__ */ y("p", {
263
289
  className: "text-3xl font-bold text-primary mt-2",
264
- children: P.invalid || P.value === void 0 ? "—" : P.value
290
+ children: z.invalid || z.value === void 0 ? "—" : z.value
265
291
  }),
266
- /* @__PURE__ */ g("p", {
292
+ /* @__PURE__ */ y("p", {
267
293
  className: "text-xs text-muted-foreground",
268
294
  children: "out of 100"
269
295
  })
@@ -274,6 +300,6 @@ function T() {
274
300
  });
275
301
  }
276
302
  //#endregion
277
- export { T as ProductsNewFeaturePagePage };
303
+ export { O as ProductsNewFeaturePagePage };
278
304
 
279
305
  //# sourceMappingURL=ProductsNewFeaturePagePage.js.map
@@ -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 PrioritizationScoreFields,\n prioritizationScoresValid,\n type PrioritizationFormValues,\n} from '../controls/PrioritizationControls';\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 }\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\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\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 (!form.title.trim() || businessValue.invalid || !prioritizationValid) 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 },\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={saving || !form.title.trim() || businessValue.invalid || !prioritizationValid}\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>\n <PrioritizationScoreFields\n idPrefix=\"feature-new\"\n values={prioritization}\n onChange={setPrioritization}\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';\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 }\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\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\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 (!form.title.trim() || businessValue.invalid || !prioritizationValid || !riceValid) 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 },\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 }\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 <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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"}