@burdenoff/microfe-planmagnet 2026.1001.5 → 2026.1001.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +10 -6
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +31 -19
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -1
- package/dist/planmagnet/automation/automationRuleTypes.js +4 -8
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -1
- package/dist/planmagnet/controls/CompetitorControls.d.ts +71 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/CompetitorControls.js +346 -12
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts +23 -1
- package/dist/planmagnet/controls/FeatureControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/FeatureControls.js +431 -229
- package/dist/planmagnet/controls/FeatureControls.js.map +1 -1
- package/dist/planmagnet/controls/FeedbackControls.d.ts +70 -0
- package/dist/planmagnet/controls/FeedbackControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/FeedbackControls.js +153 -0
- package/dist/planmagnet/controls/FeedbackControls.js.map +1 -0
- package/dist/planmagnet/controls/Pickers.d.ts +15 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +61 -1
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PrioritizationControls.d.ts +36 -0
- package/dist/planmagnet/controls/PrioritizationControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/PrioritizationControls.js +118 -1
- package/dist/planmagnet/controls/PrioritizationControls.js.map +1 -1
- package/dist/planmagnet/controls/SyncControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/SyncControls.js +1 -0
- package/dist/planmagnet/controls/SyncControls.js.map +1 -1
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts +47 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js +542 -0
- package/dist/planmagnet/controls/WebhookSubscriptionControls.js.map +1 -0
- package/dist/planmagnet/dashboards/DashboardUI.js +8 -8
- package/dist/planmagnet/dashboards/cards/RiskSeverityCard.js +4 -4
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +67 -59
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +8 -8
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +10 -10
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +9 -9
- package/dist/planmagnet/forecast/ResourceForecastCard.js +6 -6
- package/dist/planmagnet/integrations/syncTypes.d.ts +2 -1
- package/dist/planmagnet/integrations/syncTypes.d.ts.map +1 -1
- package/dist/planmagnet/integrations/syncTypes.js +19 -9
- package/dist/planmagnet/integrations/syncTypes.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +290 -283
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +126 -63
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +14 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +93 -57
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -1
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +125 -83
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +23 -23
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +340 -248
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js +172 -167
- package/dist/planmagnet/pages/IntegrationsSyncDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +92 -83
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js +175 -0
- package/dist/planmagnet/pages/IntegrationsWebhooksPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +226 -126
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +151 -126
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +169 -122
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js +165 -109
- package/dist/planmagnet/pages/ProductsNewFeaturePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductsPagePage.js +13 -13
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +9 -9
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +8 -8
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +15 -3
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +12 -12
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +23 -23
- package/dist/planmagnet/pages/TasksMyDashboardPagePage.js +18 -18
- package/dist/planmagnet/pages/TasksMyWorkPagePage.js +10 -10
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +5 -5
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +59 -53
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +8 -8
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +6 -6
- package/dist/planmagnet/pages/analytics/MetricsCard.d.ts +8 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.js +164 -0
- package/dist/planmagnet/pages/analytics/MetricsCard.js.map +1 -0
- package/dist/planmagnet/pages/analytics/QualityMetricsCard.js +6 -6
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +8 -8
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +7 -7
- package/dist/planmagnet/pages/analytics/ThroughputCard.js +7 -7
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +7 -7
- package/dist/planmagnet/people/People.d.ts +14 -0
- package/dist/planmagnet/people/People.d.ts.map +1 -1
- package/dist/planmagnet/people/People.js +36 -22
- package/dist/planmagnet/people/People.js.map +1 -1
- package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts +2 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.d.ts.map +1 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.js +155 -0
- package/dist/planmagnet/team/UnmatchedCrewIdentities.js.map +1 -0
- package/dist/planmagnet/webhooks/webhookTypes.d.ts +54 -0
- package/dist/planmagnet/webhooks/webhookTypes.d.ts.map +1 -0
- package/dist/planmagnet/webhooks/webhookTypes.js +17 -0
- package/dist/planmagnet/webhooks/webhookTypes.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n totalCount\n }\n }\n`;\n\n/** Projects are read page by page up to this many, so the picker never silently stops at the first 100. */\nconst MAX_PROJECT_OPTIONS = 500;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n disabled?: boolean;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error, fetchMore } = useQuery<{\n planmagnetProjects: {\n items: { id: string; name: string }[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n totalCount: number;\n };\n }>(PICKER_PROJECTS, {\n variables: { workspaceId: workspaceId ?? '', pagination: { first: 100 } },\n skip: !workspaceId,\n });\n const loaded = data?.planmagnetProjects.items ?? [];\n const hasNextPage = data?.planmagnetProjects.pageInfo.hasNextPage ?? false;\n const endCursor = data?.planmagnetProjects.pageInfo.endCursor ?? null;\n useEffect(() => {\n if (!hasNextPage || !endCursor || loaded.length >= MAX_PROJECT_OPTIONS) return;\n fetchMore({\n variables: { pagination: { first: 100, after: endCursor } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetProjects: {\n ...fetchMoreResult.planmagnetProjects,\n items: [...prev.planmagnetProjects.items, ...fetchMoreResult.planmagnetProjects.items],\n },\n }),\n }).catch(() => {\n // A failed extra page leaves the projects already loaded usable.\n });\n }, [hasNextPage, endCursor, loaded.length, fetchMore]);\n const projects = loaded.filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n {/* Never a silent cut-off: say so when the workspace has more projects than the picker lists. */}\n {hasNextPage && loaded.length >= MAX_PROJECT_OPTIONS && (\n <option value=\"\" disabled>\n {`Only the first ${MAX_PROJECT_OPTIONS} projects are listed`}\n </option>\n )}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_SPRINTS = gql`\n query PickerSprints($workspaceId: ID!, $filter: PlanmagnetSprintFilter) {\n planmagnetSprints(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n status\n }\n totalCount\n }\n }\n`;\n\n/** A sprint select, optionally scoped to one project (report-builder widgets: BURNDOWN reads one sprint). '' means\n * \"no sprint\" when `noneLabel` is given. */\nexport function SprintSelect({\n id,\n value,\n onChange,\n projectId,\n noneLabel,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (sprintId: string) => void;\n projectId?: string;\n noneLabel?: string;\n disabled?: boolean;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetSprints: {\n items: { id: string; name: string; status: string }[];\n totalCount: number;\n };\n }>(PICKER_SPRINTS, {\n variables: { workspaceId: workspaceId ?? '', filter: projectId ? { projectId } : undefined },\n skip: !workspaceId,\n });\n const sprints = data?.planmagnetSprints.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Sprint'}\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {sprints.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading sprints…'\n : error\n ? 'Sprints could not be loaded'\n : 'No sprints yet'}\n </option>\n )}\n {sprints.map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status.toLowerCase()})\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_PORTFOLIOS = gql`\n query PickerPortfolios($workspaceId: ID!) {\n planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A portfolio select (BOFF-8047). '' means \"no portfolio\" when `noneLabel` is given. */\nexport function PortfolioSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (portfolioId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPortfolios: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PORTFOLIOS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const portfolios = data?.planmagnetPortfolios.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {portfolios.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading portfolios…'\n : error\n ? 'Portfolios could not be loaded'\n : 'No portfolios yet'}\n </option>\n )}\n {portfolios.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PRODUCTS = gql`\n query PickerProducts($workspaceId: ID!) {\n planmagnetProducts(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A product select (PRM-27). '' means \"every product\" when `noneLabel` is given. */\nexport function ProductSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (productId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProducts: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PRODUCTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const products = data?.planmagnetProducts.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {products.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading products…'\n : error\n ? 'Products could not be loaded'\n : 'No products yet'}\n </option>\n )}\n {products.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_ROADMAPS = gql`\n query PickerRoadmaps($workspaceId: ID!, $filter: PlanmagnetRoadmapFilter) {\n planmagnetRoadmaps(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/**\n * A roadmap select (PRM-27). '' means \"every roadmap\" when `noneLabel` is given. `productId` narrows the list\n * to that product's roadmaps, same filter FeatureInitiativeCard uses for its own roadmap picker.\n */\nexport function RoadmapSelect({\n id,\n value,\n onChange,\n noneLabel,\n productId,\n}: {\n id?: string;\n value: string;\n onChange: (roadmapId: string) => void;\n noneLabel?: string;\n productId?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetRoadmaps: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_ROADMAPS, {\n variables: { workspaceId: workspaceId ?? '', filter: productId ? { productId } : undefined },\n skip: !workspaceId,\n });\n const roadmaps = data?.planmagnetRoadmaps.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {roadmaps.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading roadmaps…'\n : error\n ? 'Roadmaps could not be loaded'\n : 'No roadmaps yet'}\n </option>\n )}\n {roadmaps.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useEffect, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n totalCount\n }\n }\n`;\n\n/** Projects are read page by page up to this many, so the picker never silently stops at the first 100. */\nconst MAX_PROJECT_OPTIONS = 500;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n disabled?: boolean;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error, fetchMore } = useQuery<{\n planmagnetProjects: {\n items: { id: string; name: string }[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n totalCount: number;\n };\n }>(PICKER_PROJECTS, {\n variables: { workspaceId: workspaceId ?? '', pagination: { first: 100 } },\n skip: !workspaceId,\n });\n const loaded = data?.planmagnetProjects.items ?? [];\n const hasNextPage = data?.planmagnetProjects.pageInfo.hasNextPage ?? false;\n const endCursor = data?.planmagnetProjects.pageInfo.endCursor ?? null;\n useEffect(() => {\n if (!hasNextPage || !endCursor || loaded.length >= MAX_PROJECT_OPTIONS) return;\n fetchMore({\n variables: { pagination: { first: 100, after: endCursor } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetProjects: {\n ...fetchMoreResult.planmagnetProjects,\n items: [...prev.planmagnetProjects.items, ...fetchMoreResult.planmagnetProjects.items],\n },\n }),\n }).catch(() => {\n // A failed extra page leaves the projects already loaded usable.\n });\n }, [hasNextPage, endCursor, loaded.length, fetchMore]);\n const projects = loaded.filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n {/* Never a silent cut-off: say so when the workspace has more projects than the picker lists. */}\n {hasNextPage && loaded.length >= MAX_PROJECT_OPTIONS && (\n <option value=\"\" disabled>\n {`Only the first ${MAX_PROJECT_OPTIONS} projects are listed`}\n </option>\n )}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_SPRINTS = gql`\n query PickerSprints($workspaceId: ID!, $filter: PlanmagnetSprintFilter) {\n planmagnetSprints(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n status\n }\n totalCount\n }\n }\n`;\n\n/** A sprint select, optionally scoped to one project (report-builder widgets: BURNDOWN reads one sprint). '' means\n * \"no sprint\" when `noneLabel` is given. */\nexport function SprintSelect({\n id,\n value,\n onChange,\n projectId,\n noneLabel,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (sprintId: string) => void;\n projectId?: string;\n noneLabel?: string;\n disabled?: boolean;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetSprints: {\n items: { id: string; name: string; status: string }[];\n totalCount: number;\n };\n }>(PICKER_SPRINTS, {\n variables: { workspaceId: workspaceId ?? '', filter: projectId ? { projectId } : undefined },\n skip: !workspaceId,\n });\n const sprints = data?.planmagnetSprints.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Sprint'}\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {sprints.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading sprints…'\n : error\n ? 'Sprints could not be loaded'\n : 'No sprints yet'}\n </option>\n )}\n {sprints.map((s) => (\n <option key={s.id} value={s.id}>\n {s.name} ({s.status.toLowerCase()})\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_PORTFOLIOS = gql`\n query PickerPortfolios($workspaceId: ID!) {\n planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A portfolio select (BOFF-8047). '' means \"no portfolio\" when `noneLabel` is given. */\nexport function PortfolioSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (portfolioId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPortfolios: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PORTFOLIOS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const portfolios = data?.planmagnetPortfolios.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {portfolios.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading portfolios…'\n : error\n ? 'Portfolios could not be loaded'\n : 'No portfolios yet'}\n </option>\n )}\n {portfolios.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PRODUCTS = gql`\n query PickerProducts($workspaceId: ID!) {\n planmagnetProducts(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A product select (PRM-27). '' means \"every product\" when `noneLabel` is given. */\nexport function ProductSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (productId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProducts: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PRODUCTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const products = data?.planmagnetProducts.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {products.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading products…'\n : error\n ? 'Products could not be loaded'\n : 'No products yet'}\n </option>\n )}\n {products.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_ROADMAPS = gql`\n query PickerRoadmaps($workspaceId: ID!, $filter: PlanmagnetRoadmapFilter) {\n planmagnetRoadmaps(workspaceId: $workspaceId, filter: $filter, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/**\n * A roadmap select (PRM-27). '' means \"every roadmap\" when `noneLabel` is given. `productId` narrows the list\n * to that product's roadmaps, same filter FeatureInitiativeCard uses for its own roadmap picker.\n */\nexport function RoadmapSelect({\n id,\n value,\n onChange,\n noneLabel,\n productId,\n}: {\n id?: string;\n value: string;\n onChange: (roadmapId: string) => void;\n noneLabel?: string;\n productId?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetRoadmaps: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_ROADMAPS, {\n variables: { workspaceId: workspaceId ?? '', filter: productId ? { productId } : undefined },\n skip: !workspaceId,\n });\n const roadmaps = data?.planmagnetRoadmaps.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {roadmaps.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading roadmaps…'\n : error\n ? 'Roadmaps could not be loaded'\n : 'No roadmaps yet'}\n </option>\n )}\n {roadmaps.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_CREW_EMPLOYEES = gql`\n query PickerCrewEmployees($pagination: PaginationInput) {\n crewEmployees(pagination: $pagination) {\n id\n firstName\n lastName\n email\n }\n }\n`;\n\n/** CrewFoundry's roster is normally a few hundred people at most; past this the search box narrows what the\n * select shows rather than ever paging further. */\nconst MAX_CREW_EMPLOYEE_OPTIONS = 200;\n\n/**\n * Search (client-side — `CrewEmployeeFilterInput` has no text-search field of its own), then pick one\n * CrewFoundry employee (CWF-30). Unlike every other picker in this file this one never takes a `workspaceId`\n * variable: `crewEmployees` resolves the roster from the caller's own `x-workspace-id` header, same as the rest\n * of the CrewFoundry subgraph. A workspace with no CrewFoundry module provisioned, or an unreachable\n * CrewFoundry, surfaces as an empty list here rather than a page crash — the same fails-open contract\n * `planmagnetSprintCapacity.crewfoundryUnavailable` documents for the read side.\n */\nexport function CrewEmployeeSelect({\n id,\n value,\n onChange,\n noneLabel,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (crewEmployeeId: string) => void;\n noneLabel?: string;\n disabled?: boolean;\n}) {\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim().toLowerCase());\n const { data, loading, error } = useQuery<{\n crewEmployees: { id: string; firstName: string; lastName: string; email: string }[];\n }>(PICKER_CREW_EMPLOYEES, {\n variables: { pagination: { limit: MAX_CREW_EMPLOYEE_OPTIONS } },\n });\n const loaded = error ? [] : (data?.crewEmployees ?? []);\n const employees = deferred\n ? loaded.filter(\n (e) =>\n `${e.firstName} ${e.lastName}`.toLowerCase().includes(deferred) ||\n e.email.toLowerCase().includes(deferred)\n )\n : loaded;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search CrewFoundry employees\"\n placeholder=\"Search by name or e-mail\"\n value={search}\n disabled={disabled}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {employees.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data ? 'Loading…' : error ? 'CrewFoundry roster unavailable' : 'No match'}\n </option>\n )}\n {employees.map((e) => (\n <option key={e.id} value={e.id}>\n {e.firstName} {e.lastName} — {e.email}\n </option>\n ))}\n </select>\n {!deferred && loaded.length >= MAX_CREW_EMPLOYEE_OPTIONS && (\n <p className=\"text-xs text-muted-foreground\">\n Showing the first {MAX_CREW_EMPLOYEE_OPTIONS} employees — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -68,5 +68,41 @@ export declare function PrioritizationScoreFields({ idPrefix, values, onChange,
|
|
|
68
68
|
}): import("react/jsx-runtime").JSX.Element;
|
|
69
69
|
/** True when neither of the two score fields holds an out-of-range value (empty is always valid — it means "clear"). */
|
|
70
70
|
export declare function prioritizationScoresValid(values: PrioritizationFormValues): boolean;
|
|
71
|
+
export interface RiceFormValues {
|
|
72
|
+
/** '' = not set, else a whole number 1-10 as text */
|
|
73
|
+
reach: string;
|
|
74
|
+
/** '' = not set, else a whole number 1-10 as text */
|
|
75
|
+
impact: string;
|
|
76
|
+
/** '' = not set, else a whole number 1-10 as text */
|
|
77
|
+
confidence: string;
|
|
78
|
+
}
|
|
79
|
+
export declare const EMPTY_RICE_FORM: RiceFormValues;
|
|
80
|
+
/** True when none of the three inputs holds an out-of-range value (empty is always valid — it means "clear"). */
|
|
81
|
+
export declare function riceInputsValid(values: RiceFormValues): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Why `FeatureRequest.riceScore` reads null, for a feature that has one (reach/impact/confidence/effortScore
|
|
84
|
+
* read straight off the record, never from in-progress form state) — null otherwise, there is nothing missing
|
|
85
|
+
* to explain. Shared by the feature detail card and anywhere else riceScore is listed, so the reason is worded
|
|
86
|
+
* identically everywhere it appears.
|
|
87
|
+
*/
|
|
88
|
+
export declare function riceScoreMissingHint(feature: {
|
|
89
|
+
reach?: number | null;
|
|
90
|
+
impact?: number | null;
|
|
91
|
+
confidence?: number | null;
|
|
92
|
+
effortScore?: number | null;
|
|
93
|
+
}): string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Reach/impact/confidence 1-10 inputs (PRM-26), rendered next to FeatureRequest's existing effort/value scores
|
|
96
|
+
* — never on its own: RICE reuses `effortScore` as its divisor, so `effortScoreMissing` tells the admin why a
|
|
97
|
+
* live preview below these three inputs may still read "—" even once all three are filled in.
|
|
98
|
+
*/
|
|
99
|
+
export declare function RiceScoreFields({ idPrefix, values, onChange, effortScoreMissing, disabled, }: {
|
|
100
|
+
idPrefix: string;
|
|
101
|
+
values: RiceFormValues;
|
|
102
|
+
onChange: (next: RiceFormValues) => void;
|
|
103
|
+
/** True when FeatureRequest.effortScore (PRM-27) is not set — riceScore has no divisor yet. */
|
|
104
|
+
effortScoreMissing: boolean;
|
|
105
|
+
disabled?: boolean;
|
|
106
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
71
107
|
export {};
|
|
72
108
|
//# sourceMappingURL=PrioritizationControls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrioritizationControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/PrioritizationControls.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AAEH,eAAO,MAAM,aAAa,8CAA+C,CAAC;AAC1E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW,0EAA2E,CAAC;AACpG,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,eAAO,MAAM,eAAe,iEAAkE,CAAC;AAC/F,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,KAAK,YAAY,GACb,SAAS,GACT,SAAS,GACT,SAAS,GACT,MAAM,GACN,WAAW,GACX,aAAa,GACb,SAAS,CAAC;AAEd,eAAO,MAAM,WAAW,EAAE,MAAM,CAC9B,WAAW,EACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAcvD,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CA2B7F,CAAC;AAEJ,eAAO,MAAM,aAAa,EAAE,MAAM,CAChC,aAAa,EACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAsBvD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MACsB,CAAC;AACtF,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MACkB,CAAC;AAChF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAC2B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,gBAAwB,EACxB,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,kDAmBA;AAED,MAAM,WAAW,wBAAwB;IACvC,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,yBAAyB,EAAE,wBAKvC,CAAC;AAKF;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,wBAAwB,CAAC;IACjC,QAAQ,EAAE,CAAC,IAAI,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,2CAoGA;AAED,wHAAwH;AACxH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAKnF"}
|
|
1
|
+
{"version":3,"file":"PrioritizationControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/PrioritizationControls.tsx"],"names":[],"mappings":"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"}
|
|
@@ -240,7 +240,124 @@ function v(e) {
|
|
|
240
240
|
max: 10
|
|
241
241
|
}).invalid;
|
|
242
242
|
}
|
|
243
|
+
var y = {
|
|
244
|
+
reach: "",
|
|
245
|
+
impact: "",
|
|
246
|
+
confidence: ""
|
|
247
|
+
};
|
|
248
|
+
function b(e) {
|
|
249
|
+
return !t(e.reach, {
|
|
250
|
+
min: 1,
|
|
251
|
+
max: 10
|
|
252
|
+
}).invalid && !t(e.impact, {
|
|
253
|
+
min: 1,
|
|
254
|
+
max: 10
|
|
255
|
+
}).invalid && !t(e.confidence, {
|
|
256
|
+
min: 1,
|
|
257
|
+
max: 10
|
|
258
|
+
}).invalid;
|
|
259
|
+
}
|
|
260
|
+
function x(e) {
|
|
261
|
+
let t = [];
|
|
262
|
+
return e.reach ?? t.push("reach"), e.impact ?? t.push("impact"), e.confidence ?? t.push("confidence"), e.effortScore ?? t.push("effort score"), t.length === 0 ? null : `Missing ${t.join(", ")}.`;
|
|
263
|
+
}
|
|
264
|
+
function S({ idPrefix: i, values: o, onChange: s, effortScoreMissing: c, disabled: l = !1 }) {
|
|
265
|
+
let u = t(o.reach, {
|
|
266
|
+
min: 1,
|
|
267
|
+
max: 10
|
|
268
|
+
}), d = t(o.impact, {
|
|
269
|
+
min: 1,
|
|
270
|
+
max: 10
|
|
271
|
+
}), f = t(o.confidence, {
|
|
272
|
+
min: 1,
|
|
273
|
+
max: 10
|
|
274
|
+
});
|
|
275
|
+
return /* @__PURE__ */ r("div", {
|
|
276
|
+
className: "space-y-3",
|
|
277
|
+
"data-testid": "rice-fields",
|
|
278
|
+
children: [/* @__PURE__ */ r("div", {
|
|
279
|
+
className: "grid grid-cols-3 gap-3",
|
|
280
|
+
children: [
|
|
281
|
+
/* @__PURE__ */ r("div", { children: [
|
|
282
|
+
/* @__PURE__ */ n("label", {
|
|
283
|
+
htmlFor: `${i}-rice-reach`,
|
|
284
|
+
className: "text-sm font-medium",
|
|
285
|
+
children: "Reach (1-10)"
|
|
286
|
+
}),
|
|
287
|
+
/* @__PURE__ */ n(a, {
|
|
288
|
+
id: `${i}-rice-reach`,
|
|
289
|
+
"data-testid": "rice-field-reach",
|
|
290
|
+
type: "number",
|
|
291
|
+
min: 1,
|
|
292
|
+
max: 10,
|
|
293
|
+
className: "mt-1",
|
|
294
|
+
placeholder: "Not set",
|
|
295
|
+
value: o.reach,
|
|
296
|
+
"aria-invalid": u.invalid,
|
|
297
|
+
disabled: l,
|
|
298
|
+
onChange: (e) => s({
|
|
299
|
+
...o,
|
|
300
|
+
reach: e.target.value
|
|
301
|
+
})
|
|
302
|
+
}),
|
|
303
|
+
u.invalid && /* @__PURE__ */ n(e, { message: "Reach is a whole number from 1 to 10." })
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ r("div", { children: [
|
|
306
|
+
/* @__PURE__ */ n("label", {
|
|
307
|
+
htmlFor: `${i}-rice-impact`,
|
|
308
|
+
className: "text-sm font-medium",
|
|
309
|
+
children: "Impact (1-10)"
|
|
310
|
+
}),
|
|
311
|
+
/* @__PURE__ */ n(a, {
|
|
312
|
+
id: `${i}-rice-impact`,
|
|
313
|
+
"data-testid": "rice-field-impact",
|
|
314
|
+
type: "number",
|
|
315
|
+
min: 1,
|
|
316
|
+
max: 10,
|
|
317
|
+
className: "mt-1",
|
|
318
|
+
placeholder: "Not set",
|
|
319
|
+
value: o.impact,
|
|
320
|
+
"aria-invalid": d.invalid,
|
|
321
|
+
disabled: l,
|
|
322
|
+
onChange: (e) => s({
|
|
323
|
+
...o,
|
|
324
|
+
impact: e.target.value
|
|
325
|
+
})
|
|
326
|
+
}),
|
|
327
|
+
d.invalid && /* @__PURE__ */ n(e, { message: "Impact is a whole number from 1 to 10." })
|
|
328
|
+
] }),
|
|
329
|
+
/* @__PURE__ */ r("div", { children: [
|
|
330
|
+
/* @__PURE__ */ n("label", {
|
|
331
|
+
htmlFor: `${i}-rice-confidence`,
|
|
332
|
+
className: "text-sm font-medium",
|
|
333
|
+
children: "Confidence (1-10)"
|
|
334
|
+
}),
|
|
335
|
+
/* @__PURE__ */ n(a, {
|
|
336
|
+
id: `${i}-rice-confidence`,
|
|
337
|
+
"data-testid": "rice-field-confidence",
|
|
338
|
+
type: "number",
|
|
339
|
+
min: 1,
|
|
340
|
+
max: 10,
|
|
341
|
+
className: "mt-1",
|
|
342
|
+
placeholder: "Not set",
|
|
343
|
+
value: o.confidence,
|
|
344
|
+
"aria-invalid": f.invalid,
|
|
345
|
+
disabled: l,
|
|
346
|
+
onChange: (e) => s({
|
|
347
|
+
...o,
|
|
348
|
+
confidence: e.target.value
|
|
349
|
+
})
|
|
350
|
+
}),
|
|
351
|
+
f.invalid && /* @__PURE__ */ n(e, { message: "Confidence is a whole number from 1 to 10." })
|
|
352
|
+
] })
|
|
353
|
+
]
|
|
354
|
+
}), c && (o.reach || o.impact || o.confidence) && /* @__PURE__ */ n("p", {
|
|
355
|
+
className: "text-xs text-muted-foreground",
|
|
356
|
+
children: "RICE score needs an effort score too (above) — it is the denominator."
|
|
357
|
+
})]
|
|
358
|
+
});
|
|
359
|
+
}
|
|
243
360
|
//#endregion
|
|
244
|
-
export { h as EMPTY_PRIORITIZATION_FORM, u as KANO_META, s as KANO_VALUES, l as MOSCOW_META, o as MOSCOW_VALUES, _ as PrioritizationScoreFields, d as QUADRANT_META, c as QUADRANT_VALUES, m as QuadrantBadge, p as kanoLabel, f as moscowLabel, v as prioritizationScoresValid };
|
|
361
|
+
export { h as EMPTY_PRIORITIZATION_FORM, y as EMPTY_RICE_FORM, u as KANO_META, s as KANO_VALUES, l as MOSCOW_META, o as MOSCOW_VALUES, _ as PrioritizationScoreFields, d as QUADRANT_META, c as QUADRANT_VALUES, m as QuadrantBadge, S as RiceScoreFields, p as kanoLabel, f as moscowLabel, v as prioritizationScoresValid, b as riceInputsValid, x as riceScoreMissingHint };
|
|
245
362
|
|
|
246
363
|
//# sourceMappingURL=PrioritizationControls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrioritizationControls.js","names":[],"sources":["../../../src/planmagnet/controls/PrioritizationControls.tsx"],"sourcesContent":["import { Badge, Input } from '@burdenoff/fe-libs/ui';\nimport { parseIntField } from '../utils/formValidation';\nimport { ErrorLine } from './ConfirmButton';\n\n/**\n * Prioritization (PRM-27): MoSCoW, Kano and Value-vs-Effort scoring shared by the feature request create/edit\n * form, the feature detail page, and the roadmap initiative (RoadmapItem) edit dialog — one presentational form\n * fragment plus the badge that shows the server-computed Value-vs-Effort quadrant, so the three surfaces (and the\n * prioritization page's own item cards) render it identically instead of three near-copies drifting apart.\n *\n * The quadrant itself is never computed client-side — `FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` are server-derived (and not stored), so `QuadrantBadge` only ever renders a\n * value that came back from a query or a mutation response.\n */\n\nexport const MOSCOW_VALUES = ['MUST', 'SHOULD', 'COULD', 'WONT'] as const;\nexport type MoscowValue = (typeof MOSCOW_VALUES)[number];\n\nexport const KANO_VALUES = ['BASIC', 'PERFORMANCE', 'DELIGHTER', 'INDIFFERENT', 'REVERSE'] as const;\nexport type KanoValue = (typeof KANO_VALUES)[number];\n\nexport const QUADRANT_VALUES = ['QUICK_WIN', 'MAJOR_PROJECT', 'FILL_IN', 'TIME_SINK'] as const;\nexport type QuadrantValue = (typeof QUADRANT_VALUES)[number];\n\ntype BadgeVariant =\n | 'default'\n | 'success'\n | 'warning'\n | 'info'\n | 'secondary'\n | 'destructive'\n | 'outline';\n\nexport const MOSCOW_META: Record<\n MoscowValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n MUST: {\n label: 'Must',\n hint: 'Non-negotiable — the release does not ship without it.',\n variant: 'default',\n },\n SHOULD: {\n label: 'Should',\n hint: 'Important, but the release can still ship without it.',\n variant: 'info',\n },\n COULD: { label: 'Could', hint: 'Nice to have if there is time.', variant: 'secondary' },\n WONT: { label: \"Won't\", hint: 'Explicitly out of scope this time around.', variant: 'outline' },\n};\n\nexport const KANO_META: Record<KanoValue, { label: string; hint: string; variant: BadgeVariant }> =\n {\n BASIC: {\n label: 'Basic',\n hint: 'Expected — its absence causes dissatisfaction, its presence goes unnoticed.',\n variant: 'secondary',\n },\n PERFORMANCE: {\n label: 'Performance',\n hint: 'Satisfaction scales with how well it is delivered.',\n variant: 'info',\n },\n DELIGHTER: {\n label: 'Delighter',\n hint: 'Unexpected — its absence is not missed, its presence delights.',\n variant: 'success',\n },\n INDIFFERENT: {\n label: 'Indifferent',\n hint: 'Users do not care either way.',\n variant: 'outline',\n },\n REVERSE: {\n label: 'Reverse',\n hint: 'Some users prefer this NOT be there.',\n variant: 'destructive',\n },\n };\n\nexport const QUADRANT_META: Record<\n QuadrantValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n QUICK_WIN: {\n label: 'Quick win',\n hint: 'High value, low effort (value ≥ 6, effort ≤ 5) — do these first.',\n variant: 'success',\n },\n MAJOR_PROJECT: {\n label: 'Major project',\n hint: 'High value, high effort (value ≥ 6, effort ≥ 6) — plan and staff deliberately.',\n variant: 'info',\n },\n FILL_IN: {\n label: 'Fill-in',\n hint: 'Low value, low effort (value ≤ 5, effort ≤ 5) — fine as filler work.',\n variant: 'secondary',\n },\n TIME_SINK: {\n label: 'Time sink',\n hint: 'Low value, high effort (value ≤ 5, effort ≥ 6) — avoid or deprioritize.',\n variant: 'destructive',\n },\n};\n\nexport const moscowLabel = (value: string | null | undefined): string =>\n value && value in MOSCOW_META ? MOSCOW_META[value as MoscowValue].label : 'Not set';\nexport const kanoLabel = (value: string | null | undefined): string =>\n value && value in KANO_META ? KANO_META[value as KanoValue].label : 'Not set';\nexport const quadrantLabel = (value: string | null | undefined): string =>\n value && value in QUADRANT_META ? QUADRANT_META[value as QuadrantValue].label : 'Unscored';\n\n/**\n * The Value-vs-Effort quadrant, read back from the server (`FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` / a `PlanmagnetPrioritizationBucket.bucket`) — never computed here. Renders\n * an \"Unscored\" badge when null (one or both of valueScore/effortScore is not set yet) unless `hideWhenUnscored`.\n */\nexport function QuadrantBadge({\n quadrant,\n hideWhenUnscored = false,\n className,\n}: {\n quadrant: string | null | undefined;\n hideWhenUnscored?: boolean;\n className?: string;\n}) {\n if (!quadrant) {\n if (hideWhenUnscored) return null;\n return (\n <Badge variant=\"outline\" className={className} data-testid=\"prio-quadrant-badge\">\n Unscored\n </Badge>\n );\n }\n const meta = quadrant in QUADRANT_META ? QUADRANT_META[quadrant as QuadrantValue] : null;\n return (\n <Badge\n variant={meta?.variant ?? 'outline'}\n className={className}\n data-testid=\"prio-quadrant-badge\"\n >\n {meta?.label ?? quadrant}\n </Badge>\n );\n}\n\nexport interface PrioritizationFormValues {\n /** '' = not set */\n moscow: string;\n /** '' = not set */\n kano: string;\n /** '' = not set, else a whole number 1-10 as text */\n valueScore: string;\n /** '' = not set, else a whole number 1-10 as text */\n effortScore: string;\n}\n\nexport const EMPTY_PRIORITIZATION_FORM: PrioritizationFormValues = {\n moscow: '',\n kano: '',\n valueScore: '',\n effortScore: '',\n};\n\nconst selectClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n/**\n * The four prioritization inputs (PRM-27): MoSCoW and Kano selects (each with a short explanation of the\n * selected value), and 1-10 value/effort number inputs. Fully controlled; clearing a select or number input back\n * to empty is how a caller sends `null` (cleared) rather than omitting the field. Shared by the feature request\n * create/edit form, the feature detail page, and the roadmap initiative edit dialog.\n */\nexport function PrioritizationScoreFields({\n idPrefix,\n values,\n onChange,\n disabled = false,\n}: {\n idPrefix: string;\n values: PrioritizationFormValues;\n onChange: (next: PrioritizationFormValues) => void;\n disabled?: boolean;\n}) {\n const value = parseIntField(values.valueScore, { min: 1, max: 10 });\n const effort = parseIntField(values.effortScore, { min: 1, max: 10 });\n\n return (\n <div className=\"space-y-3\" data-testid=\"prio-fields\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-moscow`} className=\"text-sm font-medium\">\n MoSCoW\n </label>\n <select\n id={`${idPrefix}-moscow`}\n data-testid=\"prio-field-moscow\"\n className={selectClass}\n value={values.moscow}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, moscow: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {MOSCOW_VALUES.map((v) => (\n <option key={v} value={v}>\n {MOSCOW_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.moscow && values.moscow in MOSCOW_META\n ? MOSCOW_META[values.moscow as MoscowValue].hint\n : \"Must/Should/Could/Won't for this release.\"}\n </p>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-kano`} className=\"text-sm font-medium\">\n Kano\n </label>\n <select\n id={`${idPrefix}-kano`}\n data-testid=\"prio-field-kano\"\n className={selectClass}\n value={values.kano}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, kano: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {KANO_VALUES.map((v) => (\n <option key={v} value={v}>\n {KANO_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.kano && values.kano in KANO_META\n ? KANO_META[values.kano as KanoValue].hint\n : 'How this shapes customer satisfaction.'}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-value-score`} className=\"text-sm font-medium\">\n Value score (1-10)\n </label>\n <Input\n id={`${idPrefix}-value-score`}\n data-testid=\"prio-field-value-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.valueScore}\n aria-invalid={value.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, valueScore: e.target.value })}\n />\n {value.invalid && <ErrorLine message=\"Value score is a whole number from 1 to 10.\" />}\n </div>\n <div>\n <label htmlFor={`${idPrefix}-effort-score`} className=\"text-sm font-medium\">\n Effort score (1-10)\n </label>\n <Input\n id={`${idPrefix}-effort-score`}\n data-testid=\"prio-field-effort-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.effortScore}\n aria-invalid={effort.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, effortScore: e.target.value })}\n />\n {effort.invalid && <ErrorLine message=\"Effort score is a whole number from 1 to 10.\" />}\n </div>\n </div>\n </div>\n );\n}\n\n/** True when neither of the two score fields holds an out-of-range value (empty is always valid — it means \"clear\"). */\nexport function prioritizationScoresValid(values: PrioritizationFormValues): boolean {\n return (\n !parseIntField(values.valueScore, { min: 1, max: 10 }).invalid &&\n !parseIntField(values.effortScore, { min: 1, max: 10 }).invalid\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PrioritizationControls.js","names":[],"sources":["../../../src/planmagnet/controls/PrioritizationControls.tsx"],"sourcesContent":["import { Badge, Input } from '@burdenoff/fe-libs/ui';\nimport { parseIntField } from '../utils/formValidation';\nimport { ErrorLine } from './ConfirmButton';\n\n/**\n * Prioritization (PRM-27): MoSCoW, Kano and Value-vs-Effort scoring shared by the feature request create/edit\n * form, the feature detail page, and the roadmap initiative (RoadmapItem) edit dialog — one presentational form\n * fragment plus the badge that shows the server-computed Value-vs-Effort quadrant, so the three surfaces (and the\n * prioritization page's own item cards) render it identically instead of three near-copies drifting apart.\n *\n * The quadrant itself is never computed client-side — `FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` are server-derived (and not stored), so `QuadrantBadge` only ever renders a\n * value that came back from a query or a mutation response.\n */\n\nexport const MOSCOW_VALUES = ['MUST', 'SHOULD', 'COULD', 'WONT'] as const;\nexport type MoscowValue = (typeof MOSCOW_VALUES)[number];\n\nexport const KANO_VALUES = ['BASIC', 'PERFORMANCE', 'DELIGHTER', 'INDIFFERENT', 'REVERSE'] as const;\nexport type KanoValue = (typeof KANO_VALUES)[number];\n\nexport const QUADRANT_VALUES = ['QUICK_WIN', 'MAJOR_PROJECT', 'FILL_IN', 'TIME_SINK'] as const;\nexport type QuadrantValue = (typeof QUADRANT_VALUES)[number];\n\ntype BadgeVariant =\n | 'default'\n | 'success'\n | 'warning'\n | 'info'\n | 'secondary'\n | 'destructive'\n | 'outline';\n\nexport const MOSCOW_META: Record<\n MoscowValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n MUST: {\n label: 'Must',\n hint: 'Non-negotiable — the release does not ship without it.',\n variant: 'default',\n },\n SHOULD: {\n label: 'Should',\n hint: 'Important, but the release can still ship without it.',\n variant: 'info',\n },\n COULD: { label: 'Could', hint: 'Nice to have if there is time.', variant: 'secondary' },\n WONT: { label: \"Won't\", hint: 'Explicitly out of scope this time around.', variant: 'outline' },\n};\n\nexport const KANO_META: Record<KanoValue, { label: string; hint: string; variant: BadgeVariant }> =\n {\n BASIC: {\n label: 'Basic',\n hint: 'Expected — its absence causes dissatisfaction, its presence goes unnoticed.',\n variant: 'secondary',\n },\n PERFORMANCE: {\n label: 'Performance',\n hint: 'Satisfaction scales with how well it is delivered.',\n variant: 'info',\n },\n DELIGHTER: {\n label: 'Delighter',\n hint: 'Unexpected — its absence is not missed, its presence delights.',\n variant: 'success',\n },\n INDIFFERENT: {\n label: 'Indifferent',\n hint: 'Users do not care either way.',\n variant: 'outline',\n },\n REVERSE: {\n label: 'Reverse',\n hint: 'Some users prefer this NOT be there.',\n variant: 'destructive',\n },\n };\n\nexport const QUADRANT_META: Record<\n QuadrantValue,\n { label: string; hint: string; variant: BadgeVariant }\n> = {\n QUICK_WIN: {\n label: 'Quick win',\n hint: 'High value, low effort (value ≥ 6, effort ≤ 5) — do these first.',\n variant: 'success',\n },\n MAJOR_PROJECT: {\n label: 'Major project',\n hint: 'High value, high effort (value ≥ 6, effort ≥ 6) — plan and staff deliberately.',\n variant: 'info',\n },\n FILL_IN: {\n label: 'Fill-in',\n hint: 'Low value, low effort (value ≤ 5, effort ≤ 5) — fine as filler work.',\n variant: 'secondary',\n },\n TIME_SINK: {\n label: 'Time sink',\n hint: 'Low value, high effort (value ≤ 5, effort ≥ 6) — avoid or deprioritize.',\n variant: 'destructive',\n },\n};\n\nexport const moscowLabel = (value: string | null | undefined): string =>\n value && value in MOSCOW_META ? MOSCOW_META[value as MoscowValue].label : 'Not set';\nexport const kanoLabel = (value: string | null | undefined): string =>\n value && value in KANO_META ? KANO_META[value as KanoValue].label : 'Not set';\nexport const quadrantLabel = (value: string | null | undefined): string =>\n value && value in QUADRANT_META ? QUADRANT_META[value as QuadrantValue].label : 'Unscored';\n\n/**\n * The Value-vs-Effort quadrant, read back from the server (`FeatureRequest.valueEffortQuadrant` /\n * `RoadmapItem.valueEffortQuadrant` / a `PlanmagnetPrioritizationBucket.bucket`) — never computed here. Renders\n * an \"Unscored\" badge when null (one or both of valueScore/effortScore is not set yet) unless `hideWhenUnscored`.\n */\nexport function QuadrantBadge({\n quadrant,\n hideWhenUnscored = false,\n className,\n}: {\n quadrant: string | null | undefined;\n hideWhenUnscored?: boolean;\n className?: string;\n}) {\n if (!quadrant) {\n if (hideWhenUnscored) return null;\n return (\n <Badge variant=\"outline\" className={className} data-testid=\"prio-quadrant-badge\">\n Unscored\n </Badge>\n );\n }\n const meta = quadrant in QUADRANT_META ? QUADRANT_META[quadrant as QuadrantValue] : null;\n return (\n <Badge\n variant={meta?.variant ?? 'outline'}\n className={className}\n data-testid=\"prio-quadrant-badge\"\n >\n {meta?.label ?? quadrant}\n </Badge>\n );\n}\n\nexport interface PrioritizationFormValues {\n /** '' = not set */\n moscow: string;\n /** '' = not set */\n kano: string;\n /** '' = not set, else a whole number 1-10 as text */\n valueScore: string;\n /** '' = not set, else a whole number 1-10 as text */\n effortScore: string;\n}\n\nexport const EMPTY_PRIORITIZATION_FORM: PrioritizationFormValues = {\n moscow: '',\n kano: '',\n valueScore: '',\n effortScore: '',\n};\n\nconst selectClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n/**\n * The four prioritization inputs (PRM-27): MoSCoW and Kano selects (each with a short explanation of the\n * selected value), and 1-10 value/effort number inputs. Fully controlled; clearing a select or number input back\n * to empty is how a caller sends `null` (cleared) rather than omitting the field. Shared by the feature request\n * create/edit form, the feature detail page, and the roadmap initiative edit dialog.\n */\nexport function PrioritizationScoreFields({\n idPrefix,\n values,\n onChange,\n disabled = false,\n}: {\n idPrefix: string;\n values: PrioritizationFormValues;\n onChange: (next: PrioritizationFormValues) => void;\n disabled?: boolean;\n}) {\n const value = parseIntField(values.valueScore, { min: 1, max: 10 });\n const effort = parseIntField(values.effortScore, { min: 1, max: 10 });\n\n return (\n <div className=\"space-y-3\" data-testid=\"prio-fields\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-moscow`} className=\"text-sm font-medium\">\n MoSCoW\n </label>\n <select\n id={`${idPrefix}-moscow`}\n data-testid=\"prio-field-moscow\"\n className={selectClass}\n value={values.moscow}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, moscow: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {MOSCOW_VALUES.map((v) => (\n <option key={v} value={v}>\n {MOSCOW_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.moscow && values.moscow in MOSCOW_META\n ? MOSCOW_META[values.moscow as MoscowValue].hint\n : \"Must/Should/Could/Won't for this release.\"}\n </p>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-kano`} className=\"text-sm font-medium\">\n Kano\n </label>\n <select\n id={`${idPrefix}-kano`}\n data-testid=\"prio-field-kano\"\n className={selectClass}\n value={values.kano}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, kano: e.target.value })}\n >\n <option value=\"\">Not set</option>\n {KANO_VALUES.map((v) => (\n <option key={v} value={v}>\n {KANO_META[v].label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {values.kano && values.kano in KANO_META\n ? KANO_META[values.kano as KanoValue].hint\n : 'How this shapes customer satisfaction.'}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-value-score`} className=\"text-sm font-medium\">\n Value score (1-10)\n </label>\n <Input\n id={`${idPrefix}-value-score`}\n data-testid=\"prio-field-value-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.valueScore}\n aria-invalid={value.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, valueScore: e.target.value })}\n />\n {value.invalid && <ErrorLine message=\"Value score is a whole number from 1 to 10.\" />}\n </div>\n <div>\n <label htmlFor={`${idPrefix}-effort-score`} className=\"text-sm font-medium\">\n Effort score (1-10)\n </label>\n <Input\n id={`${idPrefix}-effort-score`}\n data-testid=\"prio-field-effort-score\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.effortScore}\n aria-invalid={effort.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, effortScore: e.target.value })}\n />\n {effort.invalid && <ErrorLine message=\"Effort score is a whole number from 1 to 10.\" />}\n </div>\n </div>\n </div>\n );\n}\n\n/** True when neither of the two score fields holds an out-of-range value (empty is always valid — it means \"clear\"). */\nexport function prioritizationScoresValid(values: PrioritizationFormValues): boolean {\n return (\n !parseIntField(values.valueScore, { min: 1, max: 10 }).invalid &&\n !parseIntField(values.effortScore, { min: 1, max: 10 }).invalid\n );\n}\n\n// ─── RICE (PRM-26) ──────────────────────────────────────────────────────────\n// Deliberately NOT folded into PrioritizationFormValues/PrioritizationScoreFields above: those two are shared\n// with RoadmapControls.tsx's RoadmapItem editor, and RICE (reach/impact/confidence/riceScore) exists only on\n// FeatureRequest — a RoadmapItem mutation has no such fields to send. Kept as its own form fragment, used only\n// where a FeatureRequest is actually being edited.\n//\n// `riceScore = (reach * impact * confidence) / effortScore` is computed server-side only\n// (utils/prioritization.ts#computeRiceScore) — same \"never a live client-side guess\" rule\n// `valueEffortQuadrant` already follows here. This file has no client-side RICE calculator to match: every\n// `riceScore` reader below (FeatureControls.tsx) reads the value last read back from the server.\n\nexport interface RiceFormValues {\n /** '' = not set, else a whole number 1-10 as text */\n reach: string;\n /** '' = not set, else a whole number 1-10 as text */\n impact: string;\n /** '' = not set, else a whole number 1-10 as text */\n confidence: string;\n}\n\nexport const EMPTY_RICE_FORM: RiceFormValues = { reach: '', impact: '', confidence: '' };\n\n/** True when none of the three inputs holds an out-of-range value (empty is always valid — it means \"clear\"). */\nexport function riceInputsValid(values: RiceFormValues): boolean {\n return (\n !parseIntField(values.reach, { min: 1, max: 10 }).invalid &&\n !parseIntField(values.impact, { min: 1, max: 10 }).invalid &&\n !parseIntField(values.confidence, { min: 1, max: 10 }).invalid\n );\n}\n\n/**\n * Why `FeatureRequest.riceScore` reads null, for a feature that has one (reach/impact/confidence/effortScore\n * read straight off the record, never from in-progress form state) — null otherwise, there is nothing missing\n * to explain. Shared by the feature detail card and anywhere else riceScore is listed, so the reason is worded\n * identically everywhere it appears.\n */\nexport function riceScoreMissingHint(feature: {\n reach?: number | null;\n impact?: number | null;\n confidence?: number | null;\n effortScore?: number | null;\n}): string | null {\n const missing: string[] = [];\n if (feature.reach == null) missing.push('reach');\n if (feature.impact == null) missing.push('impact');\n if (feature.confidence == null) missing.push('confidence');\n if (feature.effortScore == null) missing.push('effort score');\n return missing.length === 0 ? null : `Missing ${missing.join(', ')}.`;\n}\n\n/**\n * Reach/impact/confidence 1-10 inputs (PRM-26), rendered next to FeatureRequest's existing effort/value scores\n * — never on its own: RICE reuses `effortScore` as its divisor, so `effortScoreMissing` tells the admin why a\n * live preview below these three inputs may still read \"—\" even once all three are filled in.\n */\nexport function RiceScoreFields({\n idPrefix,\n values,\n onChange,\n effortScoreMissing,\n disabled = false,\n}: {\n idPrefix: string;\n values: RiceFormValues;\n onChange: (next: RiceFormValues) => void;\n /** True when FeatureRequest.effortScore (PRM-27) is not set — riceScore has no divisor yet. */\n effortScoreMissing: boolean;\n disabled?: boolean;\n}) {\n const reach = parseIntField(values.reach, { min: 1, max: 10 });\n const impact = parseIntField(values.impact, { min: 1, max: 10 });\n const confidence = parseIntField(values.confidence, { min: 1, max: 10 });\n return (\n <div className=\"space-y-3\" data-testid=\"rice-fields\">\n <div className=\"grid grid-cols-3 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-rice-reach`} className=\"text-sm font-medium\">\n Reach (1-10)\n </label>\n <Input\n id={`${idPrefix}-rice-reach`}\n data-testid=\"rice-field-reach\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.reach}\n aria-invalid={reach.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, reach: e.target.value })}\n />\n {reach.invalid && <ErrorLine message=\"Reach is a whole number from 1 to 10.\" />}\n </div>\n <div>\n <label htmlFor={`${idPrefix}-rice-impact`} className=\"text-sm font-medium\">\n Impact (1-10)\n </label>\n <Input\n id={`${idPrefix}-rice-impact`}\n data-testid=\"rice-field-impact\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.impact}\n aria-invalid={impact.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, impact: e.target.value })}\n />\n {impact.invalid && <ErrorLine message=\"Impact is a whole number from 1 to 10.\" />}\n </div>\n <div>\n <label htmlFor={`${idPrefix}-rice-confidence`} className=\"text-sm font-medium\">\n Confidence (1-10)\n </label>\n <Input\n id={`${idPrefix}-rice-confidence`}\n data-testid=\"rice-field-confidence\"\n type=\"number\"\n min={1}\n max={10}\n className=\"mt-1\"\n placeholder=\"Not set\"\n value={values.confidence}\n aria-invalid={confidence.invalid}\n disabled={disabled}\n onChange={(e) => onChange({ ...values, confidence: e.target.value })}\n />\n {confidence.invalid && <ErrorLine message=\"Confidence is a whole number from 1 to 10.\" />}\n </div>\n </div>\n {effortScoreMissing && (values.reach || values.impact || values.confidence) && (\n <p className=\"text-xs text-muted-foreground\">\n RICE score needs an effort score too (above) — it is the denominator.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/SyncControls.tsx"],"names":[],"mappings":"AAgCA,OAAO,EASL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EAEd,KAAK,aAAa,EAClB,KAAK,aAAa,EAEnB,MAAM,2BAA2B,CAAC;AAEnC;;;;;;;GAOG;AAEH,eAAO,MAAM,iBAAiB,UAAgB,CAAC;AAC/C,eAAO,MAAM,mBAAmB,UAA8C,CAAC;AAc/E,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAM7D;AAQD,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAMhE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAMjE;AAED,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,GAAS,EACT,SAAmC,EACnC,MAAM,GACP,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,2CAwBA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,2CAyBlE;AAmBD;;yBAEyB;AACzB,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,EACR,IAAW,GACZ,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACzB,2CAuCA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,2CA4BzE;AAmBD,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,2CA6CvF;AAYD;4FAC4F;AAC5F,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CA+ClE;AAQD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,KAAK;AACL,0GAA0G;AAC1G,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,2CAmCA;AAcD;;;uDAGuD;AACvD,wBAAgB,sBAAsB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,2CAyD5E;AAED,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"SyncControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/SyncControls.tsx"],"names":[],"mappings":"AAgCA,OAAO,EASL,KAAK,kBAAkB,EACvB,KAAK,SAAS,EAEd,KAAK,aAAa,EAClB,KAAK,aAAa,EAEnB,MAAM,2BAA2B,CAAC;AAEnC;;;;;;;GAOG;AAEH,eAAO,MAAM,iBAAiB,UAAgB,CAAC;AAC/C,eAAO,MAAM,mBAAmB,UAA8C,CAAC;AAc/E,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAM7D;AAQD,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAMhE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAMjE;AAED,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,GAAS,EACT,SAAmC,EACnC,MAAM,GACP,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,2CAwBA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,2CAyBlE;AAmBD;;yBAEyB;AACzB,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,EACR,IAAW,GACZ,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3C,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACzB,2CAuCA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,2CA4BzE;AAmBD,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,2CA6CvF;AAYD;4FAC4F;AAC5F,wBAAgB,oBAAoB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CA+ClE;AAQD,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,KAAK;AACL,0GAA0G;AAC1G,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,2CAmCA;AAcD;;;uDAGuD;AACvD,wBAAgB,sBAAsB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,2CAyD5E;AAED,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAQ5E,CAAC;AA2BF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;IACzB,gBAAgB,EAAE,kBAAkB,CAAC;IACrC,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,2CA+S9D"}
|
|
@@ -348,6 +348,7 @@ function Y({ conflictId: r }) {
|
|
|
348
348
|
var pe = {
|
|
349
349
|
SOURCE_WINS: "PlanMagnet wins — the local work item overwrites the provider on a detected conflict.",
|
|
350
350
|
TARGET_WINS: "Provider wins — the external tool overwrites PlanMagnet on a detected conflict.",
|
|
351
|
+
MERGE: "Merge — keep this project's title and description, take the remote status, priority, labels and due date.",
|
|
351
352
|
MANUAL: "Flag and skip — a conflict is recorded and left alone until resolved here."
|
|
352
353
|
}, me = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm", he = w`
|
|
353
354
|
query SyncControlsConnection($id: ID!) {
|