@burdenoff/microfe-planmagnet 2026.1001.1 → 2026.1001.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +156 -150
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +19 -15
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/comments/CommentThread.js +36 -36
- package/dist/planmagnet/comments/RoadmapComments.js +21 -21
- package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
- package/dist/planmagnet/controls/FeatureControls.js +60 -60
- package/dist/planmagnet/controls/SyncControls.js +59 -59
- package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
- package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
- package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.js +37 -0
- package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +206 -182
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
- package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
- package/dist/planmagnet/product/ProductControls.js +91 -91
- package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
- package/dist/planmagnet/reports/ShareDialog.js +20 -20
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
- package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
- package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
- package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
- package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntegrationsNewSyncPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/IntegrationsNewSyncPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, CheckCircle2, Github, Loader2, Plug, ShieldQuestion } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isForbidden, mutationErrorMessage } from '../utils/graphqlErrors';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { ProjectSelect } from '../controls/Pickers';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport {\n RunNowButton,\n SyncRunResultSummary,\n TestConnectionButton,\n CONFLICT_STRATEGY_EXPLANATION,\n} from '../controls/SyncControls';\nimport { useSyncProviderCapabilities } from '../integrations/useSyncProviderCapabilities';\nimport {\n CONFLICT_RESOLUTIONS,\n directionLabel,\n directionSupported,\n GITHUB_FIELD_MAPPING_DEFAULTS,\n isValidGithubOwner,\n isValidGithubRepo,\n providerIsLive,\n providerLabel,\n SYNC_DIRECTIONS,\n type ConflictResolution,\n type SyncDirection,\n type SyncProvider,\n type SyncProviderCapability,\n type SyncRunResult,\n} from '../integrations/syncTypes';\n\n/**\n * Sync setup wizard (AUT-08): create an ExternalSync step by step, driven entirely by\n * `planmagnetSupportedSyncProviders` — a provider is selectable, and a direction is offered, only when the\n * capability list says it is real, never a hard-coded \"GitHub only\" rule. Today that capability list happens to\n * make GITHUB the only live choice (BOFF-4446); the connection fields below (owner/repo/token) are GitHub's own\n * documented `connectionConfig` shape and only make sense for it — there is nothing to generalize them to until\n * a second connector ships.\n */\n\nconst label = 'text-sm font-medium';\n\nconst CREATE_SYNC = gql`\n mutation NewSyncCreate($input: PlanmagnetCreateExternalSyncInput!) {\n planmagnetCreateExternalSync(input: $input) {\n id\n provider\n status\n project {\n id\n name\n }\n }\n }\n`;\n\ninterface CreatedSync {\n id: string;\n provider: string;\n status: string;\n project: { id: string; name: string } | null;\n}\n\nfunction ProviderStep({\n capabilities,\n capabilitiesLoading,\n provider,\n onSelect,\n}: {\n capabilities: SyncProviderCapability[];\n capabilitiesLoading: boolean;\n provider: SyncProvider | '';\n onSelect: (p: SyncProvider) => void;\n}) {\n return (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>1. Provider</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {capabilitiesLoading && <p className=\"text-sm text-muted-foreground\">Loading providers…</p>}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {capabilities.map((c) => {\n const live = providerIsLive(c);\n const selected = provider === c.provider;\n return (\n <button\n key={c.provider}\n type=\"button\"\n disabled={!live}\n aria-pressed={selected}\n data-testid={`sync-wizard-provider-${c.provider}`}\n onClick={() => live && onSelect(c.provider)}\n className={`flex items-start gap-2 rounded-md border p-3 text-left text-sm transition-colors ${\n selected ? 'border-primary bg-primary/5' : 'border-input'\n } ${live ? 'hover:bg-muted' : 'cursor-not-allowed opacity-50'}`}\n >\n {c.provider === 'GITHUB' ? (\n <Github className=\"mt-0.5 h-4 w-4 shrink-0\" />\n ) : (\n <Plug className=\"mt-0.5 h-4 w-4 shrink-0\" />\n )}\n <span>\n <span className=\"block font-medium\">{providerLabel(c.provider)}</span>\n <span className=\"block text-xs text-muted-foreground\">\n {live\n ? [\n c.supportsImport && 'inbound',\n c.supportsExport && 'outbound',\n c.supportsBidirectional && 'bidirectional',\n ]\n .filter(Boolean)\n .join(', ') + ' sync'\n : 'No live connector yet — inbound, outbound and bidirectional sync are all unavailable for this provider.'}\n </span>\n </span>\n </button>\n );\n })}\n </div>\n </CardContent>\n </Card>\n );\n}\n\nexport function IntegrationsNewSyncPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { capabilities, loading: capabilitiesLoading } = useSyncProviderCapabilities();\n\n const [provider, setProvider] = useState<SyncProvider | ''>('');\n const [projectId, setProjectId] = useState('');\n const [owner, setOwner] = useState('');\n const [repo, setRepo] = useState('');\n const [direction, setDirection] = useState<SyncDirection | ''>('');\n const [token, setToken] = useState('');\n const [conflictStrategy, setConflictStrategy] = useState<ConflictResolution>('MANUAL');\n const [priorityPrefix, setPriorityPrefix] = useState('');\n const [dueDatePrefix, setDueDatePrefix] = useState('');\n const [verifyBeforeCreate, setVerifyBeforeCreate] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n const [created, setCreated] = useState<CreatedSync | null>(null);\n const [runResult, setRunResult] = useState<SyncRunResult | null>(null);\n\n const [create, state] = useMutation<{ planmagnetCreateExternalSync: CreatedSync }>(CREATE_SYNC, {\n refetchQueries: ['SyncsList'],\n });\n\n const capability = capabilities.find((c) => c.provider === provider);\n const isGithub = provider === 'GITHUB';\n const allowedDirections = capability\n ? SYNC_DIRECTIONS.filter((d) => directionSupported(capability, d))\n : [];\n\n const ownerValid = owner.trim() !== '' && isValidGithubOwner(owner);\n const repoValid = repo.trim() !== '' && isValidGithubRepo(repo);\n const step2Complete =\n !!provider && !!projectId && (!isGithub || (ownerValid && repoValid)) && !!direction;\n const step3Complete = step2Complete && token.trim() !== '';\n\n const buildInput = () => {\n const fieldMapping: Record<string, string> = {};\n if (priorityPrefix.trim()) fieldMapping.priority = priorityPrefix.trim();\n if (dueDatePrefix.trim()) fieldMapping.dueDate = dueDatePrefix.trim();\n return {\n projectId,\n provider,\n direction,\n externalProjectId: isGithub ? `${owner.trim()}/${repo.trim()}` : '',\n externalProjectUrl: isGithub\n ? `https://github.com/${owner.trim()}/${repo.trim()}`\n : undefined,\n connectionConfig: isGithub\n ? { token: token.trim(), owner: owner.trim(), repo: repo.trim() }\n : {},\n conflictStrategy,\n ...(Object.keys(fieldMapping).length > 0 ? { fieldMapping } : {}),\n testConnection: verifyBeforeCreate,\n };\n };\n\n const submit = async () => {\n setFormError(null);\n if (!step3Complete) {\n setFormError(\n 'Fill in the project, external project and credentials above before creating the sync.'\n );\n return;\n }\n try {\n const res = await throwOnError(create({ variables: { input: buildInput() } }));\n if (res.data) setCreated(res.data.planmagnetCreateExternalSync);\n } catch (e) {\n setFormError(\n isForbidden(e)\n ? 'Your role can view integrations but not set them up.'\n : mutationErrorMessage(e, 'The sync could not be created.')\n );\n }\n };\n\n if (created) {\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <Card data-testid=\"sync-wizard-created\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text\" />\n Sync created\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm text-muted-foreground\">\n {providerLabel(created.provider)} sync for {created.project?.name ?? 'the project'} is\n set up and idle. Test the connection and run the first sync now, or do either later\n from the sync's own page.\n </p>\n <div className=\"flex flex-wrap items-center gap-3\">\n <TestConnectionButton syncId={created.id} />\n <RunNowButton syncId={created.id} onResult={setRunResult} />\n </div>\n {runResult && <SyncRunResultSummary result={runResult} />}\n <Button\n onClick={() => navigate(detailPath('/integrations/sync-detail-page', created.id))}\n data-testid=\"sync-wizard-view\"\n >\n View sync\n </Button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <div className=\"flex items-start gap-3\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Back to Integrations\"\n onClick={() => navigate('/integrations/syncs-page')}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Set up a sync</h1>\n <p className=\"text-sm text-muted-foreground\">\n Connect a PlanMagnet project to an external issue tracker so work items stay in step on\n both sides.\n </p>\n </div>\n </div>\n\n <ProviderStep\n capabilities={capabilities}\n capabilitiesLoading={capabilitiesLoading}\n provider={provider}\n onSelect={(p) => {\n setProvider(p);\n setDirection('');\n setOwner('');\n setRepo('');\n setToken('');\n }}\n />\n\n {provider && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>2. Project, external project and direction</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"sync-wizard-project\" className={label}>\n PlanMagnet project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"sync-wizard-project\"\n value={projectId}\n noneLabel=\"Choose a project\"\n onChange={setProjectId}\n />\n </div>\n </div>\n {isGithub && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"sync-wizard-owner\" className={label}>\n GitHub owner (user or org)\n </label>\n <Input\n id=\"sync-wizard-owner\"\n className=\"mt-1\"\n placeholder=\"e.g. algoshred\"\n value={owner}\n data-testid=\"sync-wizard-owner\"\n onChange={(e) => setOwner(e.target.value)}\n />\n {owner.trim() !== '' && !ownerValid && (\n <ErrorLine message=\"Not a valid GitHub owner name.\" />\n )}\n </div>\n <div>\n <label htmlFor=\"sync-wizard-repo\" className={label}>\n Repository\n </label>\n <Input\n id=\"sync-wizard-repo\"\n className=\"mt-1\"\n placeholder=\"e.g. wspace-planmagnet-svc\"\n value={repo}\n data-testid=\"sync-wizard-repo\"\n onChange={(e) => setRepo(e.target.value)}\n />\n {repo.trim() !== '' && !repoValid && (\n <ErrorLine message=\"Not a valid repository name.\" />\n )}\n </div>\n </div>\n )}\n <div>\n <span className={label}>Direction</span>\n {allowedDirections.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n This provider offers no live sync direction yet.\n </p>\n ) : (\n <div className=\"mt-1 flex flex-col gap-1\" role=\"radiogroup\" aria-label=\"Direction\">\n {allowedDirections.map((d) => (\n <label key={d} className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"sync-wizard-direction\"\n checked={direction === d}\n data-testid={`sync-wizard-direction-${d}`}\n onChange={() => setDirection(d)}\n />\n {directionLabel(d)}\n </label>\n ))}\n </div>\n )}\n </div>\n </CardContent>\n </Card>\n )}\n\n {step2Complete && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>3. Credentials</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {isGithub && (\n <>\n <p className=\"text-sm text-muted-foreground\">\n Use a GitHub fine-grained personal access token scoped to{' '}\n <strong>\n {owner}/{repo}\n </strong>\n , with Issues set to <strong>Read and write</strong> — no other repository\n permission is needed.\n </p>\n <div className=\"max-w-sm\">\n <label htmlFor=\"sync-wizard-token\" className={label}>\n Personal access token\n </label>\n <Input\n id=\"sync-wizard-token\"\n type=\"password\"\n autoComplete=\"off\"\n className=\"mt-1\"\n value={token}\n data-testid=\"sync-wizard-token\"\n onChange={(e) => setToken(e.target.value)}\n />\n </div>\n </>\n )}\n </CardContent>\n </Card>\n )}\n\n {step3Complete && (\n <>\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>4. Conflict strategy</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground\">\n What happens when the same item changed on both sides since the last sync.\n </p>\n <div className=\"flex flex-col gap-1\" role=\"radiogroup\" aria-label=\"Conflict strategy\">\n {CONFLICT_RESOLUTIONS.map((r) => (\n <label key={r} className=\"flex items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"sync-wizard-strategy\"\n className=\"mt-0.5\"\n checked={conflictStrategy === r}\n data-testid={`sync-wizard-strategy-${r}`}\n onChange={() => setConflictStrategy(r)}\n />\n <span>\n <span className=\"font-medium\">{enumLabel(r)}</span>\n <span className=\"block text-xs text-muted-foreground\">\n {CONFLICT_STRATEGY_EXPLANATION[r]}\n </span>\n </span>\n </label>\n ))}\n </div>\n </CardContent>\n </Card>\n\n {isGithub && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>5. Field mapping (optional)</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n GitHub issues have no native priority or due-date field — PlanMagnet encodes each\n as a label of the form <code>prefix+value</code> (e.g. <code>priority:HIGH</code>,{' '}\n <code>due:2026-10-01</code>\n ). Leave both blank to use the built-in prefixes.\n </p>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"sync-wizard-priority-prefix\"\n className=\"text-xs text-muted-foreground\"\n >\n Priority label prefix (default "{GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n ")\n </label>\n <Input\n id=\"sync-wizard-priority-prefix\"\n className=\"mt-1\"\n placeholder={GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n value={priorityPrefix}\n onChange={(e) => setPriorityPrefix(e.target.value)}\n />\n </div>\n <div>\n <label\n htmlFor=\"sync-wizard-duedate-prefix\"\n className=\"text-xs text-muted-foreground\"\n >\n Due-date label prefix (default "{GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n ")\n </label>\n <Input\n id=\"sync-wizard-duedate-prefix\"\n className=\"mt-1\"\n placeholder={GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n value={dueDatePrefix}\n onChange={(e) => setDueDatePrefix(e.target.value)}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>6. Review & create</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <dl className=\"grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1 text-sm\">\n <dt className=\"text-muted-foreground\">Provider</dt>\n <dd>{providerLabel(provider)}</dd>\n <dt className=\"text-muted-foreground\">Direction</dt>\n <dd>{direction ? directionLabel(direction) : '—'}</dd>\n <dt className=\"text-muted-foreground\">External project</dt>\n <dd>{isGithub ? `${owner}/${repo}` : '—'}</dd>\n <dt className=\"text-muted-foreground\">Conflict strategy</dt>\n <dd>{enumLabel(conflictStrategy)}</dd>\n </dl>\n <label className=\"flex items-start gap-2 text-sm\">\n <Checkbox\n checked={verifyBeforeCreate}\n onCheckedChange={(checked) => setVerifyBeforeCreate(checked === true)}\n data-testid=\"sync-wizard-verify\"\n />\n <span>\n Verify the connection before creating (recommended)\n <span className=\"block text-xs text-muted-foreground\">\n Probes the credentials first; the sync is not created if the probe fails.\n </span>\n </span>\n </label>\n {formError && <ErrorLine message={formError} />}\n <Button\n onClick={() => void submit()}\n disabled={state.loading}\n data-testid=\"sync-wizard-create\"\n >\n {state.loading ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <ShieldQuestion className=\"mr-1 h-4 w-4\" />\n )}\n Create sync\n </Button>\n </CardContent>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IntegrationsNewSyncPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/IntegrationsNewSyncPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, CheckCircle2, Github, Loader2, Plug, ShieldQuestion } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isForbidden, mutationErrorMessage } from '../utils/graphqlErrors';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { ProjectSelect } from '../controls/Pickers';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport {\n RunNowButton,\n SyncRunResultSummary,\n TestConnectionButton,\n CONFLICT_STRATEGY_EXPLANATION,\n} from '../controls/SyncControls';\nimport { useSyncProviderCapabilities } from '../integrations/useSyncProviderCapabilities';\nimport {\n CONFLICT_RESOLUTIONS,\n directionLabel,\n directionSupported,\n GITHUB_FIELD_MAPPING_DEFAULTS,\n isValidGithubOwner,\n isValidGithubRepo,\n providerIsLive,\n providerLabel,\n SYNC_DIRECTIONS,\n type ConflictResolution,\n type SyncDirection,\n type SyncProvider,\n type SyncProviderCapability,\n type SyncRunResult,\n} from '../integrations/syncTypes';\n\n/**\n * Sync setup wizard (AUT-08): create an ExternalSync step by step, driven entirely by\n * `planmagnetSupportedSyncProviders` — a provider is selectable, and a direction is offered, only when the\n * capability list says it is real, never a hard-coded \"GitHub only\" rule. Today that capability list happens to\n * make GITHUB the only live choice (BOFF-4446); the connection fields below (owner/repo/token) are GitHub's own\n * documented `connectionConfig` shape and only make sense for it — there is nothing to generalize them to until\n * a second connector ships.\n */\n\nconst label = 'text-sm font-medium';\n\nconst CREATE_SYNC = gql`\n mutation NewSyncCreate($input: PlanmagnetCreateExternalSyncInput!) {\n planmagnetCreateExternalSync(input: $input) {\n id\n provider\n status\n project {\n id\n name\n }\n }\n }\n`;\n\ninterface CreatedSync {\n id: string;\n provider: string;\n status: string;\n project: { id: string; name: string } | null;\n}\n\nfunction ProviderStep({\n capabilities,\n capabilitiesLoading,\n provider,\n onSelect,\n}: {\n capabilities: SyncProviderCapability[];\n capabilitiesLoading: boolean;\n provider: SyncProvider | '';\n onSelect: (p: SyncProvider) => void;\n}) {\n return (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>1. Provider</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {capabilitiesLoading && <p className=\"text-sm text-muted-foreground\">Loading providers…</p>}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {capabilities.map((c) => {\n const live = providerIsLive(c);\n const selected = provider === c.provider;\n return (\n <button\n key={c.provider}\n type=\"button\"\n disabled={!live}\n aria-pressed={selected}\n data-testid={`sync-wizard-provider-${c.provider}`}\n onClick={() => live && onSelect(c.provider)}\n className={`flex items-start gap-2 rounded-md border p-3 text-left text-sm transition-colors ${\n selected ? 'border-primary bg-primary/5' : 'border-input'\n } ${live ? 'hover:bg-muted' : 'cursor-not-allowed opacity-50'}`}\n >\n {c.provider === 'GITHUB' ? (\n <Github className=\"mt-0.5 h-4 w-4 shrink-0\" />\n ) : (\n <Plug className=\"mt-0.5 h-4 w-4 shrink-0\" />\n )}\n <span>\n <span className=\"block font-medium\">{providerLabel(c.provider)}</span>\n <span className=\"block text-xs text-muted-foreground\">\n {live\n ? [\n c.supportsImport && 'inbound',\n c.supportsExport && 'outbound',\n c.supportsBidirectional && 'bidirectional',\n ]\n .filter(Boolean)\n .join(', ') + ' sync'\n : 'No live connector yet — inbound, outbound and bidirectional sync are all unavailable for this provider.'}\n </span>\n </span>\n </button>\n );\n })}\n </div>\n </CardContent>\n </Card>\n );\n}\n\nexport function IntegrationsNewSyncPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { capabilities, loading: capabilitiesLoading } = useSyncProviderCapabilities();\n\n const [provider, setProvider] = useState<SyncProvider | ''>('');\n const [projectId, setProjectId] = useState('');\n const [owner, setOwner] = useState('');\n const [repo, setRepo] = useState('');\n const [direction, setDirection] = useState<SyncDirection | ''>('');\n const [token, setToken] = useState('');\n const [conflictStrategy, setConflictStrategy] = useState<ConflictResolution>('MANUAL');\n const [priorityPrefix, setPriorityPrefix] = useState('');\n const [dueDatePrefix, setDueDatePrefix] = useState('');\n const [verifyBeforeCreate, setVerifyBeforeCreate] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n const [created, setCreated] = useState<CreatedSync | null>(null);\n const [runResult, setRunResult] = useState<SyncRunResult | null>(null);\n\n const [create, state] = useMutation<{ planmagnetCreateExternalSync: CreatedSync }>(CREATE_SYNC, {\n refetchQueries: ['SyncsList'],\n });\n\n const capability = capabilities.find((c) => c.provider === provider);\n const isGithub = provider === 'GITHUB';\n const allowedDirections = capability\n ? SYNC_DIRECTIONS.filter((d) => directionSupported(capability, d))\n : [];\n\n const ownerValid = owner.trim() !== '' && isValidGithubOwner(owner);\n const repoValid = repo.trim() !== '' && isValidGithubRepo(repo);\n const step2Complete =\n !!provider && !!projectId && (!isGithub || (ownerValid && repoValid)) && !!direction;\n const step3Complete = step2Complete && token.trim() !== '';\n\n const buildInput = () => {\n const fieldMapping: Record<string, string> = {};\n if (priorityPrefix.trim()) fieldMapping.priority = priorityPrefix.trim();\n if (dueDatePrefix.trim()) fieldMapping.dueDate = dueDatePrefix.trim();\n return {\n projectId,\n provider,\n direction,\n externalProjectId: isGithub ? `${owner.trim()}/${repo.trim()}` : '',\n externalProjectUrl: isGithub\n ? `https://github.com/${owner.trim()}/${repo.trim()}`\n : undefined,\n connectionConfig: isGithub\n ? { token: token.trim(), owner: owner.trim(), repo: repo.trim() }\n : {},\n conflictStrategy,\n ...(Object.keys(fieldMapping).length > 0 ? { fieldMapping } : {}),\n testConnection: verifyBeforeCreate,\n };\n };\n\n const submit = async () => {\n setFormError(null);\n if (!step3Complete) {\n setFormError(\n 'Fill in the project, external project and credentials above before creating the sync.'\n );\n return;\n }\n try {\n const res = await throwOnError(create({ variables: { input: buildInput() } }));\n if (res.data) setCreated(res.data.planmagnetCreateExternalSync);\n } catch (e) {\n setFormError(\n isForbidden(e)\n ? 'Your role can view integrations but not set them up.'\n : mutationErrorMessage(e, 'The sync could not be created.')\n );\n }\n };\n\n if (created) {\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <Card data-testid=\"sync-wizard-created\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <CheckCircle2 className=\"h-5 w-5 text-status-success-text\" />\n Sync created\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm text-muted-foreground\">\n {providerLabel(created.provider)} sync for {created.project?.name ?? 'the project'} is\n set up and idle. Test the connection and run the first sync now, or do either later\n from the sync's own page.\n </p>\n <div className=\"flex flex-wrap items-center gap-3\">\n <TestConnectionButton syncId={created.id} />\n <RunNowButton syncId={created.id} onResult={setRunResult} />\n </div>\n {runResult && <SyncRunResultSummary result={runResult} />}\n <Button\n onClick={() => navigate(detailPath('/integrations/sync-detail-page', created.id))}\n data-testid=\"sync-wizard-view\"\n >\n View sync\n </Button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <div className=\"flex items-start gap-3\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Back to Integrations\"\n onClick={() => navigate('/integrations/syncs-page')}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-2xl font-semibold\">Set up a sync</h1>\n <p className=\"text-sm text-muted-foreground\">\n Connect a PlanMagnet project to an external issue tracker so work items stay in step on\n both sides.\n </p>\n </div>\n </div>\n\n <ProviderStep\n capabilities={capabilities}\n capabilitiesLoading={capabilitiesLoading}\n provider={provider}\n onSelect={(p) => {\n setProvider(p);\n setDirection('');\n setOwner('');\n setRepo('');\n setToken('');\n }}\n />\n\n {provider && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>2. Project, external project and direction</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"sync-wizard-project\" className={label}>\n PlanMagnet project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"sync-wizard-project\"\n value={projectId}\n noneLabel=\"Choose a project\"\n onChange={setProjectId}\n />\n </div>\n </div>\n {isGithub && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"sync-wizard-owner\" className={label}>\n GitHub owner (user or org)\n </label>\n <Input\n id=\"sync-wizard-owner\"\n className=\"mt-1\"\n placeholder=\"e.g. algoshred\"\n value={owner}\n data-testid=\"sync-wizard-owner\"\n onChange={(e) => setOwner(e.target.value)}\n />\n {owner.trim() !== '' && !ownerValid && (\n <ErrorLine message=\"Not a valid GitHub owner name.\" />\n )}\n </div>\n <div>\n <label htmlFor=\"sync-wizard-repo\" className={label}>\n Repository\n </label>\n <Input\n id=\"sync-wizard-repo\"\n className=\"mt-1\"\n placeholder=\"e.g. wspace-planmagnet-svc\"\n value={repo}\n data-testid=\"sync-wizard-repo\"\n onChange={(e) => setRepo(e.target.value)}\n />\n {repo.trim() !== '' && !repoValid && (\n <ErrorLine message=\"Not a valid repository name.\" />\n )}\n </div>\n </div>\n )}\n <div>\n <span className={label}>Direction</span>\n {allowedDirections.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n This provider offers no live sync direction yet.\n </p>\n ) : (\n <div className=\"mt-1 flex flex-col gap-1\" role=\"radiogroup\" aria-label=\"Direction\">\n {allowedDirections.map((d) => (\n <label key={d} className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"sync-wizard-direction\"\n checked={direction === d}\n data-testid={`sync-wizard-direction-${d}`}\n onChange={() => setDirection(d)}\n />\n {directionLabel(d)}\n </label>\n ))}\n </div>\n )}\n </div>\n </CardContent>\n </Card>\n )}\n\n {step2Complete && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>3. Credentials</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {isGithub && (\n <>\n <p className=\"text-sm text-muted-foreground\">\n Use a GitHub fine-grained personal access token scoped to{' '}\n <strong>\n {owner}/{repo}\n </strong>\n , with Issues set to <strong>Read and write</strong> — no other repository\n permission is needed.\n </p>\n <div className=\"max-w-sm\">\n <label htmlFor=\"sync-wizard-token\" className={label}>\n Personal access token\n </label>\n <Input\n id=\"sync-wizard-token\"\n type=\"password\"\n autoComplete=\"off\"\n className=\"mt-1\"\n value={token}\n data-testid=\"sync-wizard-token\"\n onChange={(e) => setToken(e.target.value)}\n />\n </div>\n </>\n )}\n </CardContent>\n </Card>\n )}\n\n {step3Complete && (\n <>\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>4. Conflict strategy</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground\">\n What happens when the same item changed on both sides since the last sync.\n </p>\n <div className=\"flex flex-col gap-1\" role=\"radiogroup\" aria-label=\"Conflict strategy\">\n {CONFLICT_RESOLUTIONS.map((r) => (\n <label key={r} className=\"flex items-start gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"sync-wizard-strategy\"\n className=\"mt-0.5\"\n checked={conflictStrategy === r}\n data-testid={`sync-wizard-strategy-${r}`}\n onChange={() => setConflictStrategy(r)}\n />\n <span>\n <span className=\"font-medium\">{enumLabel(r)}</span>\n <span className=\"block text-xs text-muted-foreground\">\n {CONFLICT_STRATEGY_EXPLANATION[r]}\n </span>\n </span>\n </label>\n ))}\n </div>\n </CardContent>\n </Card>\n\n {isGithub && (\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>5. Field mapping (optional)</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n GitHub issues have no native priority or due-date field — PlanMagnet encodes each\n as a label of the form <code>prefix+value</code> (e.g. <code>priority:HIGH</code>,{' '}\n <code>due:2026-10-01</code>\n ). Leave both blank to use the built-in prefixes.\n </p>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n <div>\n <label\n htmlFor=\"sync-wizard-priority-prefix\"\n className=\"text-xs text-muted-foreground\"\n >\n Priority label prefix (default "{GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n ")\n </label>\n <Input\n id=\"sync-wizard-priority-prefix\"\n className=\"mt-1\"\n placeholder={GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n value={priorityPrefix}\n onChange={(e) => setPriorityPrefix(e.target.value)}\n />\n </div>\n <div>\n <label\n htmlFor=\"sync-wizard-duedate-prefix\"\n className=\"text-xs text-muted-foreground\"\n >\n Due-date label prefix (default "{GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n ")\n </label>\n <Input\n id=\"sync-wizard-duedate-prefix\"\n className=\"mt-1\"\n placeholder={GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n value={dueDatePrefix}\n onChange={(e) => setDueDatePrefix(e.target.value)}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n <Card data-testid=\"sync-wizard-step\">\n <CardHeader>\n <CardTitle>6. Review & create</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <dl className=\"grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-1 text-sm\">\n <dt className=\"text-muted-foreground\">Provider</dt>\n <dd>{providerLabel(provider)}</dd>\n <dt className=\"text-muted-foreground\">Direction</dt>\n <dd>{direction ? directionLabel(direction) : '—'}</dd>\n <dt className=\"text-muted-foreground\">External project</dt>\n <dd>{isGithub ? `${owner}/${repo}` : '—'}</dd>\n <dt className=\"text-muted-foreground\">Conflict strategy</dt>\n <dd>{enumLabel(conflictStrategy)}</dd>\n </dl>\n <label className=\"flex items-start gap-2 text-sm\">\n <Checkbox\n checked={verifyBeforeCreate}\n onCheckedChange={(checked) => setVerifyBeforeCreate(checked === true)}\n data-testid=\"sync-wizard-verify\"\n />\n <span>\n Verify the connection before creating (recommended)\n <span className=\"block text-xs text-muted-foreground\">\n Probes the credentials first; the sync is not created if the probe fails.\n </span>\n </span>\n </label>\n {formError && <ErrorLine message={formError} />}\n <Button\n onClick={() => void submit()}\n disabled={state.loading}\n data-testid=\"sync-wizard-create\"\n >\n {state.loading ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <ShieldQuestion className=\"mr-1 h-4 w-4\" />\n )}\n Create sync\n </Button>\n </CardContent>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectsAtRiskPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsAtRiskPagePage.tsx"],"names":[],"mappings":"AA4GA,wBAAgB,sBAAsB,4CA8PrC"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { isForbidden as n } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { enumLabel as r } from "../controls/WorkItemControls.js";
|
|
4
|
+
import { ROW_LINK_CLASS as i, rowLinkProps as a } from "../utils/rowNavigation.js";
|
|
5
|
+
import { ProjectSelect as o } from "../controls/Pickers.js";
|
|
6
|
+
import { RISK_FLAG_KIND_ORDER as s, riskFlagKindIcon as c, riskFlagSeverityBadgeVariant as l } from "../forecast/riskFlags.js";
|
|
7
|
+
import { useState as u } from "react";
|
|
8
|
+
import { ShieldAlert as d } from "lucide-react";
|
|
9
|
+
import { useSearchParams as f } from "react-router";
|
|
10
|
+
import { gql as p } from "@apollo/client";
|
|
11
|
+
import { useQuery as m } from "@apollo/client/react";
|
|
12
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
14
|
+
import { Badge as y, Card as b, CardContent as x, CardHeader as S, CardTitle as C } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
//#region src/planmagnet/pages/ProjectsAtRiskPagePage.tsx
|
|
16
|
+
var w = 100, T = p`
|
|
17
|
+
query AtRiskProjectSprints($workspaceId: ID!, $projectId: String) {
|
|
18
|
+
planmagnetSprints(
|
|
19
|
+
workspaceId: $workspaceId
|
|
20
|
+
filter: { projectId: $projectId }
|
|
21
|
+
pagination: { first: 100 }
|
|
22
|
+
) {
|
|
23
|
+
items {
|
|
24
|
+
id
|
|
25
|
+
name
|
|
26
|
+
status
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`, E = p`
|
|
31
|
+
query GetAtRiskWorkItems(
|
|
32
|
+
$workspaceId: ID!
|
|
33
|
+
$projectId: ID!
|
|
34
|
+
$sprintId: ID
|
|
35
|
+
$pagination: PlanmagnetPaginationInput
|
|
36
|
+
) {
|
|
37
|
+
planmagnetAtRiskWorkItems(
|
|
38
|
+
workspaceId: $workspaceId
|
|
39
|
+
projectId: $projectId
|
|
40
|
+
sprintId: $sprintId
|
|
41
|
+
pagination: $pagination
|
|
42
|
+
) {
|
|
43
|
+
items {
|
|
44
|
+
workItem {
|
|
45
|
+
id
|
|
46
|
+
key
|
|
47
|
+
title
|
|
48
|
+
status
|
|
49
|
+
priority
|
|
50
|
+
storyPoints
|
|
51
|
+
}
|
|
52
|
+
flags {
|
|
53
|
+
kind
|
|
54
|
+
severity
|
|
55
|
+
reason
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
totalCount
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`, D = "ALL";
|
|
62
|
+
function O() {
|
|
63
|
+
let { workspaceId: p } = e(), O = t(), { t: k } = v(), [A, j] = f(), M = A.get("projectId") ?? "", N = A.get("sprintId") ?? "", [P, F] = u(D);
|
|
64
|
+
function I(e) {
|
|
65
|
+
let t = new URLSearchParams(A);
|
|
66
|
+
e ? t.set("projectId", e) : t.delete("projectId"), t.delete("sprintId"), j(t, { replace: !0 });
|
|
67
|
+
}
|
|
68
|
+
function L(e) {
|
|
69
|
+
let t = new URLSearchParams(A);
|
|
70
|
+
e ? t.set("sprintId", e) : t.delete("sprintId"), j(t, { replace: !0 });
|
|
71
|
+
}
|
|
72
|
+
let { data: R } = m(T, {
|
|
73
|
+
variables: {
|
|
74
|
+
workspaceId: p,
|
|
75
|
+
projectId: M
|
|
76
|
+
},
|
|
77
|
+
skip: !p || !M
|
|
78
|
+
}), z = R?.planmagnetSprints.items ?? [], { data: B, loading: V, error: H } = m(E, {
|
|
79
|
+
variables: {
|
|
80
|
+
workspaceId: p,
|
|
81
|
+
projectId: M,
|
|
82
|
+
sprintId: N || void 0,
|
|
83
|
+
pagination: { first: w }
|
|
84
|
+
},
|
|
85
|
+
skip: !p || !M
|
|
86
|
+
}), U = B?.planmagnetAtRiskWorkItems, W = U?.items ?? [], G = P === D ? W : W.filter((e) => e.flags.some((e) => e.kind === P)), K = /* @__PURE__ */ new Map();
|
|
87
|
+
for (let e of W) for (let t of e.flags) K.set(t.kind, (K.get(t.kind) ?? 0) + 1);
|
|
88
|
+
return /* @__PURE__ */ _("div", {
|
|
89
|
+
className: "space-y-6 p-6",
|
|
90
|
+
"data-testid": "at-risk-page",
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ _("div", {
|
|
93
|
+
className: "flex flex-wrap items-start justify-between gap-2",
|
|
94
|
+
children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("h1", {
|
|
95
|
+
className: "text-2xl font-semibold flex items-center gap-2",
|
|
96
|
+
children: [/* @__PURE__ */ g(d, { className: "h-5 w-5 text-primary" }), k("planmagnet.forecast.atRisk.title", "At-risk work")]
|
|
97
|
+
}), /* @__PURE__ */ g("p", {
|
|
98
|
+
className: "text-sm text-muted-foreground",
|
|
99
|
+
children: k("planmagnet.forecast.atRisk.subtitle", "Open work items carrying one or more risk flags, each raised by an explicit threshold rule — never AI-generated.")
|
|
100
|
+
})] }), /* @__PURE__ */ g(y, {
|
|
101
|
+
variant: "outline",
|
|
102
|
+
children: k("planmagnet.forecast.atRisk.method", "Rule-based")
|
|
103
|
+
})]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ g(b, { children: /* @__PURE__ */ _(x, {
|
|
106
|
+
className: "flex flex-wrap items-end gap-3 pt-4",
|
|
107
|
+
children: [/* @__PURE__ */ _("div", {
|
|
108
|
+
className: "space-y-1",
|
|
109
|
+
children: [/* @__PURE__ */ g("label", {
|
|
110
|
+
htmlFor: "at-risk-project",
|
|
111
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
112
|
+
children: k("planmagnet.forecast.atRisk.project", "Project")
|
|
113
|
+
}), /* @__PURE__ */ g(o, {
|
|
114
|
+
id: "at-risk-project",
|
|
115
|
+
value: M,
|
|
116
|
+
onChange: I,
|
|
117
|
+
noneLabel: k("planmagnet.forecast.atRisk.choose", "Choose a project…")
|
|
118
|
+
})]
|
|
119
|
+
}), M && /* @__PURE__ */ _("div", {
|
|
120
|
+
className: "space-y-1",
|
|
121
|
+
children: [/* @__PURE__ */ g("label", {
|
|
122
|
+
htmlFor: "at-risk-sprint",
|
|
123
|
+
className: "text-xs font-medium text-muted-foreground",
|
|
124
|
+
children: k("planmagnet.forecast.atRisk.sprint", "Sprint (optional)")
|
|
125
|
+
}), /* @__PURE__ */ _("select", {
|
|
126
|
+
id: "at-risk-sprint",
|
|
127
|
+
className: "flex h-9 w-56 rounded-md border border-input bg-background px-3 py-1 text-sm",
|
|
128
|
+
value: N,
|
|
129
|
+
onChange: (e) => L(e.target.value),
|
|
130
|
+
children: [/* @__PURE__ */ g("option", {
|
|
131
|
+
value: "",
|
|
132
|
+
children: k("planmagnet.forecast.atRisk.allSprints", "Every open item (whole project)")
|
|
133
|
+
}), z.map((e) => /* @__PURE__ */ g("option", {
|
|
134
|
+
value: e.id,
|
|
135
|
+
children: e.name
|
|
136
|
+
}, e.id))]
|
|
137
|
+
})]
|
|
138
|
+
})]
|
|
139
|
+
}) }),
|
|
140
|
+
M ? /* @__PURE__ */ _(h, { children: [W.length > 0 && /* @__PURE__ */ _("div", {
|
|
141
|
+
className: "flex flex-wrap items-center gap-1 rounded-md border p-1",
|
|
142
|
+
role: "group",
|
|
143
|
+
"aria-label": k("planmagnet.forecast.atRisk.filterByKind", "Filter by flag kind"),
|
|
144
|
+
children: [/* @__PURE__ */ g("button", {
|
|
145
|
+
type: "button",
|
|
146
|
+
"aria-pressed": P === D,
|
|
147
|
+
onClick: () => F(D),
|
|
148
|
+
className: `px-2.5 py-1 text-xs rounded ${P === D ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
|
|
149
|
+
children: k("planmagnet.forecast.atRisk.all", "All ({{count}})", { count: W.length })
|
|
150
|
+
}), s.filter((e) => (K.get(e) ?? 0) > 0).map((e) => /* @__PURE__ */ _("button", {
|
|
151
|
+
type: "button",
|
|
152
|
+
"aria-pressed": P === e,
|
|
153
|
+
onClick: () => F(e),
|
|
154
|
+
className: `px-2.5 py-1 text-xs rounded ${P === e ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"}`,
|
|
155
|
+
children: [
|
|
156
|
+
r(e),
|
|
157
|
+
" (",
|
|
158
|
+
K.get(e),
|
|
159
|
+
")"
|
|
160
|
+
]
|
|
161
|
+
}, e))]
|
|
162
|
+
}), /* @__PURE__ */ _(b, { children: [/* @__PURE__ */ g(S, { children: /* @__PURE__ */ g(C, { children: V && !B ? k("planmagnet.forecast.loading", "Loading…") : k("planmagnet.forecast.atRisk.count", "{{count}} flagged item(s)", { count: G.length }) }) }), /* @__PURE__ */ _(x, {
|
|
163
|
+
className: "space-y-3",
|
|
164
|
+
children: [V && !B ? /* @__PURE__ */ g("div", {
|
|
165
|
+
className: "flex justify-center py-12",
|
|
166
|
+
children: /* @__PURE__ */ g("div", { className: "animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full" })
|
|
167
|
+
}) : H ? /* @__PURE__ */ g("p", {
|
|
168
|
+
className: "text-sm text-status-error-text",
|
|
169
|
+
children: n(H) ? k("planmagnet.forecast.atRisk.forbidden", "You do not have access to at-risk work for this project.") : `${k("planmagnet.forecast.atRisk.error", "At-risk work could not be loaded")}: ${H.message}`
|
|
170
|
+
}) : G.length === 0 ? /* @__PURE__ */ g("p", {
|
|
171
|
+
className: "text-sm text-muted-foreground py-8 text-center",
|
|
172
|
+
children: W.length === 0 ? k("planmagnet.forecast.atRisk.empty", "No flagged work items right now.") : k("planmagnet.forecast.atRisk.emptyFilter", "No items match this filter.")
|
|
173
|
+
}) : G.map((e) => /* @__PURE__ */ _("div", {
|
|
174
|
+
"data-testid": "at-risk-item",
|
|
175
|
+
...a(() => O(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.workItem.id)}`), e.workItem.title),
|
|
176
|
+
className: `rounded-md border p-3 ${i}`,
|
|
177
|
+
children: [/* @__PURE__ */ _("div", {
|
|
178
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
179
|
+
children: [/* @__PURE__ */ _("p", {
|
|
180
|
+
className: "text-sm font-medium",
|
|
181
|
+
children: [
|
|
182
|
+
e.workItem.key,
|
|
183
|
+
" ",
|
|
184
|
+
/* @__PURE__ */ g("span", {
|
|
185
|
+
className: "font-normal",
|
|
186
|
+
children: e.workItem.title
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
}), /* @__PURE__ */ _("span", {
|
|
190
|
+
className: "text-xs text-muted-foreground",
|
|
191
|
+
children: [r(e.workItem.status), e.workItem.storyPoints == null ? "" : ` · ${e.workItem.storyPoints} pts`]
|
|
192
|
+
})]
|
|
193
|
+
}), /* @__PURE__ */ g("div", {
|
|
194
|
+
className: "mt-2 space-y-1.5",
|
|
195
|
+
children: e.flags.map((e) => /* @__PURE__ */ _("div", {
|
|
196
|
+
"data-testid": "at-risk-flag",
|
|
197
|
+
className: "flex items-start gap-2 text-xs",
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ g(c(e.kind), { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
|
|
200
|
+
/* @__PURE__ */ g(y, {
|
|
201
|
+
variant: l(e.severity),
|
|
202
|
+
className: "shrink-0",
|
|
203
|
+
children: r(e.severity)
|
|
204
|
+
}),
|
|
205
|
+
/* @__PURE__ */ g(y, {
|
|
206
|
+
variant: "outline",
|
|
207
|
+
className: "shrink-0",
|
|
208
|
+
children: r(e.kind)
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ g("span", {
|
|
211
|
+
className: "text-muted-foreground",
|
|
212
|
+
children: e.reason
|
|
213
|
+
})
|
|
214
|
+
]
|
|
215
|
+
}, e.kind))
|
|
216
|
+
})]
|
|
217
|
+
}, e.workItem.id)), U && U.totalCount > G.length && P === D && /* @__PURE__ */ g("p", {
|
|
218
|
+
className: "text-xs text-muted-foreground text-center pt-2",
|
|
219
|
+
children: k("planmagnet.forecast.atRisk.moreNote", "Showing the first {{shown}} of {{total}} flagged items.", {
|
|
220
|
+
shown: G.length,
|
|
221
|
+
total: U.totalCount
|
|
222
|
+
})
|
|
223
|
+
})]
|
|
224
|
+
})] })] }) : /* @__PURE__ */ g(b, { children: /* @__PURE__ */ g(x, {
|
|
225
|
+
className: "py-10 text-center text-sm text-muted-foreground",
|
|
226
|
+
children: k("planmagnet.forecast.atRisk.noProject", "Choose a project to see its at-risk work.")
|
|
227
|
+
}) })
|
|
228
|
+
]
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { O as ProjectsAtRiskPagePage };
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=ProjectsAtRiskPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectsAtRiskPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProjectsAtRiskPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ShieldAlert } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\nimport { ROW_LINK_CLASS, rowLinkProps } from '../utils/rowNavigation';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport {\n RISK_FLAG_KIND_ORDER,\n riskFlagKindIcon,\n riskFlagSeverityBadgeVariant,\n type RiskFlagKindValue,\n type RiskFlagSeverityValue,\n} from '../forecast/riskFlags';\n\n/**\n * AI-10: every open work item of a project (optionally narrowed to one sprint) carrying one or\n * more at-risk flags — the full list `ProjectRiskSummaryCard`'s compact tile links through to.\n * Rule-based (threshold rules over real data), never AI/LLM — see graphql/wspace.graphql's\n * `PlanmagnetRiskFlagKind` for exactly which rule (and threshold) raises each flag kind.\n */\n\nconst AT_RISK_PAGE_SIZE = 100;\n\nconst GET_PROJECT_SPRINTS = gql`\n query AtRiskProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n }\n }\n }\n`;\n\nconst GET_AT_RISK_WORK_ITEMS = gql`\n query GetAtRiskWorkItems(\n $workspaceId: ID!\n $projectId: ID!\n $sprintId: ID\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetAtRiskWorkItems(\n workspaceId: $workspaceId\n projectId: $projectId\n sprintId: $sprintId\n pagination: $pagination\n ) {\n items {\n workItem {\n id\n key\n title\n status\n priority\n storyPoints\n }\n flags {\n kind\n severity\n reason\n }\n }\n totalCount\n }\n }\n`;\n\ninterface SprintOption {\n id: string;\n name: string;\n status: string;\n}\n\ninterface AtRiskFlag {\n kind: RiskFlagKindValue;\n severity: RiskFlagSeverityValue;\n reason: string;\n}\n\ninterface AtRiskItem {\n workItem: {\n id: string;\n key: string;\n title: string;\n status: string;\n priority: string;\n storyPoints: number | null;\n };\n flags: AtRiskFlag[];\n}\n\ninterface AtRiskWorkItemsData {\n planmagnetAtRiskWorkItems: { items: AtRiskItem[]; totalCount: number };\n}\n\nconst KIND_FILTER_ALL = 'ALL' as const;\n\nexport function ProjectsAtRiskPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const { t } = useI18n();\n const [searchParams, setSearchParams] = useSearchParams();\n const projectId = searchParams.get('projectId') ?? '';\n const sprintId = searchParams.get('sprintId') ?? '';\n const [kindFilter, setKindFilter] = useState<RiskFlagKindValue | typeof KIND_FILTER_ALL>(\n KIND_FILTER_ALL\n );\n\n function setProjectId(next: string) {\n const params = new URLSearchParams(searchParams);\n if (next) params.set('projectId', next);\n else params.delete('projectId');\n // A sprint id from the old project would silently scope the new project's query to a\n // sprint it may not even have.\n params.delete('sprintId');\n setSearchParams(params, { replace: true });\n }\n\n function setSprintId(next: string) {\n const params = new URLSearchParams(searchParams);\n if (next) params.set('sprintId', next);\n else params.delete('sprintId');\n setSearchParams(params, { replace: true });\n }\n\n const { data: sprintData } = useQuery<{ planmagnetSprints: { items: SprintOption[] } }>(\n GET_PROJECT_SPRINTS,\n { variables: { workspaceId, projectId }, skip: !workspaceId || !projectId }\n );\n const sprints = sprintData?.planmagnetSprints.items ?? [];\n\n const { data, loading, error } = useQuery<AtRiskWorkItemsData>(GET_AT_RISK_WORK_ITEMS, {\n variables: {\n workspaceId,\n projectId,\n sprintId: sprintId || undefined,\n pagination: { first: AT_RISK_PAGE_SIZE },\n },\n skip: !workspaceId || !projectId,\n });\n const result = data?.planmagnetAtRiskWorkItems;\n const allItems = result?.items ?? [];\n const items =\n kindFilter === KIND_FILTER_ALL\n ? allItems\n : allItems.filter((item) => item.flags.some((f) => f.kind === kindFilter));\n const kindCounts = new Map<RiskFlagKindValue, number>();\n for (const item of allItems) {\n for (const flag of item.flags) {\n kindCounts.set(flag.kind, (kindCounts.get(flag.kind) ?? 0) + 1);\n }\n }\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"at-risk-page\">\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <ShieldAlert className=\"h-5 w-5 text-primary\" />\n {t('planmagnet.forecast.atRisk.title', 'At-risk work')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.forecast.atRisk.subtitle',\n 'Open work items carrying one or more risk flags, each raised by an explicit threshold rule — never AI-generated.'\n )}\n </p>\n </div>\n <Badge variant=\"outline\">{t('planmagnet.forecast.atRisk.method', 'Rule-based')}</Badge>\n </div>\n\n <Card>\n <CardContent className=\"flex flex-wrap items-end gap-3 pt-4\">\n <div className=\"space-y-1\">\n <label htmlFor=\"at-risk-project\" className=\"text-xs font-medium text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.project', 'Project')}\n </label>\n <ProjectSelect\n id=\"at-risk-project\"\n value={projectId}\n onChange={setProjectId}\n noneLabel={t('planmagnet.forecast.atRisk.choose', 'Choose a project…')}\n />\n </div>\n {projectId && (\n <div className=\"space-y-1\">\n <label htmlFor=\"at-risk-sprint\" className=\"text-xs font-medium text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.sprint', 'Sprint (optional)')}\n </label>\n <select\n id=\"at-risk-sprint\"\n className=\"flex h-9 w-56 rounded-md border border-input bg-background px-3 py-1 text-sm\"\n value={sprintId}\n onChange={(e) => setSprintId(e.target.value)}\n >\n <option value=\"\">\n {t('planmagnet.forecast.atRisk.allSprints', 'Every open item (whole project)')}\n </option>\n {sprints.map((s) => (\n <option key={s.id} value={s.id}>\n {s.name}\n </option>\n ))}\n </select>\n </div>\n )}\n </CardContent>\n </Card>\n\n {!projectId ? (\n <Card>\n <CardContent className=\"py-10 text-center text-sm text-muted-foreground\">\n {t('planmagnet.forecast.atRisk.noProject', 'Choose a project to see its at-risk work.')}\n </CardContent>\n </Card>\n ) : (\n <>\n {allItems.length > 0 && (\n <div\n className=\"flex flex-wrap items-center gap-1 rounded-md border p-1\"\n role=\"group\"\n aria-label={t('planmagnet.forecast.atRisk.filterByKind', 'Filter by flag kind')}\n >\n <button\n type=\"button\"\n aria-pressed={kindFilter === KIND_FILTER_ALL}\n onClick={() => setKindFilter(KIND_FILTER_ALL)}\n className={`px-2.5 py-1 text-xs rounded ${\n kindFilter === KIND_FILTER_ALL\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.forecast.atRisk.all', 'All ({{count}})', { count: allItems.length })}\n </button>\n {RISK_FLAG_KIND_ORDER.filter((k) => (kindCounts.get(k) ?? 0) > 0).map((k) => (\n <button\n key={k}\n type=\"button\"\n aria-pressed={kindFilter === k}\n onClick={() => setKindFilter(k)}\n className={`px-2.5 py-1 text-xs rounded ${\n kindFilter === k\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {enumLabel(k)} ({kindCounts.get(k)})\n </button>\n ))}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>\n {loading && !data\n ? t('planmagnet.forecast.loading', 'Loading…')\n : t('planmagnet.forecast.atRisk.count', '{{count}} flagged item(s)', {\n count: items.length,\n })}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {loading && !data ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {isForbidden(error)\n ? t(\n 'planmagnet.forecast.atRisk.forbidden',\n 'You do not have access to at-risk work for this project.'\n )\n : `${t('planmagnet.forecast.atRisk.error', 'At-risk work could not be loaded')}: ${error.message}`}\n </p>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {allItems.length === 0\n ? t('planmagnet.forecast.atRisk.empty', 'No flagged work items right now.')\n : t('planmagnet.forecast.atRisk.emptyFilter', 'No items match this filter.')}\n </p>\n ) : (\n items.map((item) => (\n <div\n key={item.workItem.id}\n data-testid=\"at-risk-item\"\n {...rowLinkProps(\n () =>\n navigate(\n `/tasks/task-detail-page?taskId=${encodeURIComponent(item.workItem.id)}`\n ),\n item.workItem.title\n )}\n className={`rounded-md border p-3 ${ROW_LINK_CLASS}`}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">\n {item.workItem.key}{' '}\n <span className=\"font-normal\">{item.workItem.title}</span>\n </p>\n <span className=\"text-xs text-muted-foreground\">\n {enumLabel(item.workItem.status)}\n {item.workItem.storyPoints != null\n ? ` · ${item.workItem.storyPoints} pts`\n : ''}\n </span>\n </div>\n <div className=\"mt-2 space-y-1.5\">\n {item.flags.map((flag) => {\n const Icon = riskFlagKindIcon(flag.kind);\n return (\n <div\n key={flag.kind}\n data-testid=\"at-risk-flag\"\n className=\"flex items-start gap-2 text-xs\"\n >\n <Icon className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <Badge\n variant={riskFlagSeverityBadgeVariant(flag.severity)}\n className=\"shrink-0\"\n >\n {enumLabel(flag.severity)}\n </Badge>\n <Badge variant=\"outline\" className=\"shrink-0\">\n {enumLabel(flag.kind)}\n </Badge>\n <span className=\"text-muted-foreground\">{flag.reason}</span>\n </div>\n );\n })}\n </div>\n </div>\n ))\n )}\n {result && result.totalCount > items.length && kindFilter === KIND_FILTER_ALL && (\n <p className=\"text-xs text-muted-foreground text-center pt-2\">\n {t(\n 'planmagnet.forecast.atRisk.moreNote',\n 'Showing the first {{shown}} of {{total}} flagged items.',\n { shown: items.length, total: result.totalCount }\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsProjectDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsProjectDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsProjectDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsProjectDetailPagePage.tsx"],"names":[],"mappings":"AAuIA,wBAAgB,6BAA6B,4CAslB5C"}
|