@burdenoff/microfe-planmagnet 2026.1001.1 → 2026.1001.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +163 -150
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +124 -108
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/comments/CommentThread.js +36 -36
  8. package/dist/planmagnet/comments/RoadmapComments.js +21 -21
  9. package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
  10. package/dist/planmagnet/controls/FeatureControls.js +60 -60
  11. package/dist/planmagnet/controls/MilestoneControls.d.ts +2 -0
  12. package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
  13. package/dist/planmagnet/controls/MilestoneControls.js +89 -72
  14. package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
  15. package/dist/planmagnet/controls/SyncControls.js +59 -59
  16. package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
  17. package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
  18. package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
  19. package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
  20. package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
  21. package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
  22. package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
  23. package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
  24. package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
  25. package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
  26. package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
  27. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
  28. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
  29. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
  30. package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
  31. package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
  32. package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
  33. package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
  34. package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
  35. package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
  36. package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
  37. package/dist/planmagnet/forecast/riskFlags.js +37 -0
  38. package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
  39. package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts +17 -0
  40. package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts.map +1 -0
  41. package/dist/planmagnet/meetings/MeetingActionItemsPanel.js +218 -0
  42. package/dist/planmagnet/meetings/MeetingActionItemsPanel.js.map +1 -0
  43. package/dist/planmagnet/meetings/MeetingsCard.d.ts +7 -0
  44. package/dist/planmagnet/meetings/MeetingsCard.d.ts.map +1 -0
  45. package/dist/planmagnet/meetings/MeetingsCard.js +108 -0
  46. package/dist/planmagnet/meetings/MeetingsCard.js.map +1 -0
  47. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts +9 -0
  48. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts.map +1 -0
  49. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js +232 -0
  50. package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js.map +1 -0
  51. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts +19 -0
  52. package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -0
  53. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +226 -0
  54. package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -0
  55. package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts +14 -0
  56. package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts.map +1 -0
  57. package/dist/planmagnet/meetings/UnlinkMeetingDialog.js +81 -0
  58. package/dist/planmagnet/meetings/UnlinkMeetingDialog.js.map +1 -0
  59. package/dist/planmagnet/meetings/meetingActions.d.ts +57 -0
  60. package/dist/planmagnet/meetings/meetingActions.d.ts.map +1 -0
  61. package/dist/planmagnet/meetings/meetingActions.js +51 -0
  62. package/dist/planmagnet/meetings/meetingActions.js.map +1 -0
  63. package/dist/planmagnet/meetings/meetingGraphql.d.ts +29 -0
  64. package/dist/planmagnet/meetings/meetingGraphql.d.ts.map +1 -0
  65. package/dist/planmagnet/meetings/meetingGraphql.js +166 -0
  66. package/dist/planmagnet/meetings/meetingGraphql.js.map +1 -0
  67. package/dist/planmagnet/meetings/meetingModel.d.ts +74 -0
  68. package/dist/planmagnet/meetings/meetingModel.d.ts.map +1 -0
  69. package/dist/planmagnet/meetings/meetingModel.js +88 -0
  70. package/dist/planmagnet/meetings/meetingModel.js.map +1 -0
  71. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  72. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
  73. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  74. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
  75. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
  76. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
  77. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
  79. package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
  80. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts +2 -0
  81. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts.map +1 -0
  82. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js +197 -0
  83. package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js.map +1 -0
  84. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts +2 -0
  85. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts.map +1 -0
  86. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js +173 -0
  87. package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js.map +1 -0
  88. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
  89. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
  90. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
  91. package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
  92. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
  93. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +73 -52
  94. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
  95. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  96. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +205 -190
  97. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  98. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  99. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +211 -182
  100. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  101. package/dist/planmagnet/pages/ReportsReportPagePage.js +22 -22
  102. package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -1
  103. package/dist/planmagnet/pages/ReportsReportsPagePage.js +21 -21
  104. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
  105. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
  106. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
  107. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
  108. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
  109. package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
  110. package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
  111. package/dist/planmagnet/product/ProductControls.js +91 -91
  112. package/dist/planmagnet/reports/SchedulesPanel.js +33 -33
  113. package/dist/planmagnet/reports/ShareDialog.js +76 -76
  114. package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
  115. package/dist/planmagnet/reports/sharedReportRoute.d.ts +3 -2
  116. package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -1
  117. package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -1
  118. package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
  119. package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
  120. package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
  121. package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
  122. 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&apos;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 &quot;{GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n &quot;)\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 &quot;{GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n &quot;)\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 &amp; 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&apos;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 &quot;{GITHUB_FIELD_MAPPING_DEFAULTS.priority}\n &quot;)\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 &quot;{GITHUB_FIELD_MAPPING_DEFAULTS.dueDate}\n &quot;)\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 &amp; 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,2 @@
1
+ export declare function MeetingsMeetingDetailPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=MeetingsMeetingDetailPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsMeetingDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/MeetingsMeetingDetailPagePage.tsx"],"names":[],"mappings":"AAuHA,wBAAgB,6BAA6B,4CA4J5C"}
@@ -0,0 +1,197 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { isForbidden as t, mutationErrorMessage as n } from "../utils/graphqlErrors.js";
3
+ import { throwOnError as r } from "../utils/mutationResult.js";
4
+ import { ErrorLine as i } from "../controls/ConfirmButton.js";
5
+ import { enumLabel as a } from "../controls/WorkItemControls.js";
6
+ import { detailPath as o } from "../utils/rowNavigation.js";
7
+ import { useEntityId as s } from "../hooks/useEntityId.js";
8
+ import { summarizeRecurrence as c } from "../meetings/meetingModel.js";
9
+ import { GET_PLANMAGNET_MEETING as l, UPDATE_PLANMAGNET_MEETING as u } from "../meetings/meetingGraphql.js";
10
+ import { MeetingActionItemsPanel as d } from "../meetings/MeetingActionItemsPanel.js";
11
+ import { UnlinkMeetingDialog as f } from "../meetings/UnlinkMeetingDialog.js";
12
+ import { useState as p } from "react";
13
+ import { CalendarClock as m, ExternalLink as h, FolderKanban as g, Repeat as _, Target as v, Zap as y } from "lucide-react";
14
+ import { useMutation as b, useQuery as x } from "@apollo/client/react";
15
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
16
+ import { Badge as w, Button as T, Card as E, CardContent as D, CardHeader as O, CardTitle as k, Textarea as A } from "@burdenoff/fe-libs/ui";
17
+ //#region src/planmagnet/pages/MeetingsMeetingDetailPagePage.tsx
18
+ function j({ id: e, label: t, value: r, placeholder: a, onSave: o }) {
19
+ let [s, c] = p(r ?? ""), [l, u] = p(!1), [d, f] = p(null), m = r ?? "", h = s !== m, g = async () => {
20
+ f(null), u(!0);
21
+ try {
22
+ await o(s.trim());
23
+ } catch (e) {
24
+ f(n(e, `The ${t.toLowerCase()} could not be saved.`));
25
+ } finally {
26
+ u(!1);
27
+ }
28
+ };
29
+ return /* @__PURE__ */ C("div", {
30
+ className: "space-y-1",
31
+ children: [
32
+ /* @__PURE__ */ S("label", {
33
+ htmlFor: e,
34
+ className: "text-sm font-medium",
35
+ children: t
36
+ }),
37
+ /* @__PURE__ */ S(A, {
38
+ id: e,
39
+ className: "min-h-[120px]",
40
+ placeholder: a,
41
+ value: s,
42
+ onChange: (e) => c(e.target.value),
43
+ "data-testid": e
44
+ }),
45
+ d && /* @__PURE__ */ S(i, { message: d }),
46
+ h && /* @__PURE__ */ C("div", {
47
+ className: "flex justify-end gap-2",
48
+ children: [/* @__PURE__ */ S(T, {
49
+ size: "sm",
50
+ variant: "ghost",
51
+ onClick: () => c(m),
52
+ disabled: l,
53
+ children: "Cancel"
54
+ }), /* @__PURE__ */ S(T, {
55
+ size: "sm",
56
+ onClick: () => void g(),
57
+ disabled: l,
58
+ "data-testid": `${e}-save`,
59
+ children: l ? "Saving…" : "Save"
60
+ })]
61
+ })
62
+ ]
63
+ });
64
+ }
65
+ function M() {
66
+ let n = s("meetingId"), i = e(), { data: p, loading: T, error: A } = x(l, {
67
+ variables: { id: n },
68
+ skip: !n
69
+ }), [M] = b(u, {
70
+ refetchQueries: ["GetPlanmagnetMeeting"],
71
+ awaitRefetchQueries: !0
72
+ });
73
+ if (!n) return /* @__PURE__ */ S("div", {
74
+ className: "p-6 text-muted-foreground",
75
+ children: "No meeting selected. Open one from the list."
76
+ });
77
+ if (T && !p) return /* @__PURE__ */ S("div", {
78
+ className: "flex items-center justify-center p-12",
79
+ children: /* @__PURE__ */ S("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
80
+ });
81
+ if (A) return /* @__PURE__ */ S("div", {
82
+ role: "alert",
83
+ className: "p-6 text-destructive",
84
+ children: t(A) ? "You don't have access to this meeting." : `Error loading meeting: ${A.message}`
85
+ });
86
+ let N = p?.planmagnetMeeting;
87
+ if (!N) return /* @__PURE__ */ S("div", {
88
+ className: "p-6 text-muted-foreground",
89
+ children: "Meeting not found."
90
+ });
91
+ let P = async (e, t) => {
92
+ await r(M({ variables: {
93
+ id: N.id,
94
+ input: { [e]: t || null }
95
+ } }));
96
+ };
97
+ return /* @__PURE__ */ C("div", {
98
+ className: "space-y-6 p-6",
99
+ children: [
100
+ /* @__PURE__ */ C("div", {
101
+ className: "flex flex-wrap items-start justify-between gap-3",
102
+ children: [/* @__PURE__ */ C("div", { children: [
103
+ /* @__PURE__ */ C("div", {
104
+ className: "flex items-center gap-2",
105
+ children: [
106
+ /* @__PURE__ */ S(m, { className: "h-5 w-5 text-primary" }),
107
+ /* @__PURE__ */ S("h1", {
108
+ className: "text-2xl font-semibold",
109
+ children: N.title
110
+ }),
111
+ /* @__PURE__ */ S(w, {
112
+ variant: "outline",
113
+ children: a(N.kind)
114
+ })
115
+ ]
116
+ }),
117
+ /* @__PURE__ */ C("div", {
118
+ className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground",
119
+ children: [
120
+ /* @__PURE__ */ C("span", { children: [
121
+ new Date(N.startsAt).toLocaleString(void 0, {
122
+ dateStyle: "medium",
123
+ timeStyle: "short"
124
+ }),
125
+ " – ",
126
+ new Date(N.endsAt).toLocaleString(void 0, { timeStyle: "short" })
127
+ ] }),
128
+ /* @__PURE__ */ C("span", {
129
+ className: "inline-flex items-center gap-1",
130
+ children: [/* @__PURE__ */ S(_, { className: "h-3.5 w-3.5" }), c(N.recurrenceRule)]
131
+ }),
132
+ /* @__PURE__ */ C("a", {
133
+ href: `/calendar/event/${encodeURIComponent(N.calendarEventId)}`,
134
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
135
+ "data-testid": "meeting-open-in-calendar",
136
+ children: ["Open in calendar", /* @__PURE__ */ S(h, { className: "h-3.5 w-3.5" })]
137
+ })
138
+ ]
139
+ }),
140
+ /* @__PURE__ */ C("div", {
141
+ className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
142
+ children: [
143
+ N.project && /* @__PURE__ */ C("button", {
144
+ type: "button",
145
+ className: "inline-flex items-center gap-1 hover:underline",
146
+ onClick: () => i(o("/projects/project-detail-page", N.project.id)),
147
+ children: [/* @__PURE__ */ S(g, { className: "h-3 w-3" }), N.project.name]
148
+ }),
149
+ N.sprint && /* @__PURE__ */ C("button", {
150
+ type: "button",
151
+ className: "inline-flex items-center gap-1 hover:underline",
152
+ onClick: () => i(o("/projects/planning-detail-page", N.sprint.id)),
153
+ children: [/* @__PURE__ */ S(y, { className: "h-3 w-3" }), N.sprint.name]
154
+ }),
155
+ N.milestone && /* @__PURE__ */ C("button", {
156
+ type: "button",
157
+ className: "inline-flex items-center gap-1 hover:underline",
158
+ onClick: () => i(o("/projects/milestone-detail-page", N.milestone.id)),
159
+ children: [/* @__PURE__ */ S(v, { className: "h-3 w-3" }), N.milestone.name]
160
+ })
161
+ ]
162
+ })
163
+ ] }), /* @__PURE__ */ S(f, {
164
+ meetingId: N.id,
165
+ calendarEventId: N.calendarEventId,
166
+ meetingTitle: N.title,
167
+ onUnlinked: () => i("/meetings/meetings-page")
168
+ })]
169
+ }),
170
+ /* @__PURE__ */ C("div", {
171
+ className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
172
+ children: [/* @__PURE__ */ C(E, { children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ S(k, { children: "Agenda" }) }), /* @__PURE__ */ S(D, { children: /* @__PURE__ */ S(j, {
173
+ id: "meeting-agenda",
174
+ label: "Agenda",
175
+ value: N.agenda,
176
+ placeholder: "What this meeting is for…",
177
+ onSave: (e) => P("agenda", e)
178
+ }) })] }), /* @__PURE__ */ C(E, { children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ S(k, { children: "Notes" }) }), /* @__PURE__ */ S(D, { children: /* @__PURE__ */ S(j, {
179
+ id: "meeting-notes",
180
+ label: "Notes",
181
+ value: N.notes,
182
+ placeholder: "What happened…",
183
+ onSave: (e) => P("notes", e)
184
+ }) })] })]
185
+ }),
186
+ /* @__PURE__ */ S(d, {
187
+ meetingId: N.id,
188
+ projectId: N.projectId,
189
+ actionItems: N.actionItems
190
+ })
191
+ ]
192
+ });
193
+ }
194
+ //#endregion
195
+ export { M as MeetingsMeetingDetailPagePage };
196
+
197
+ //# sourceMappingURL=MeetingsMeetingDetailPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsMeetingDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/MeetingsMeetingDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, FolderKanban, Repeat, Target, Zap } from 'lucide-react';\nimport { useEntityId } from '../hooks/useEntityId';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { mutationErrorMessage, isForbidden } from '../utils/graphqlErrors';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { summarizeRecurrence, type MeetingKind } from '../meetings/meetingModel';\nimport { GET_PLANMAGNET_MEETING, UPDATE_PLANMAGNET_MEETING } from '../meetings/meetingGraphql';\nimport { MeetingActionItemsPanel } from '../meetings/MeetingActionItemsPanel';\nimport { UnlinkMeetingDialog } from '../meetings/UnlinkMeetingDialog';\n\n/**\n * A single meeting's detail page (GAP-06, CWF-22): agenda + notes (PlanMagnet-owned content), its action items,\n * and Unlink. The schedule itself (start/end/recurrence) is shown read-only here — editing it is a calendar\n * concern (\"Open in calendar\"), not this page's (`PlanmagnetMeeting`'s doc comment: the calendar event stays\n * the source of truth for the real schedule).\n */\n\ninterface MeetingDetail {\n id: string;\n title: string;\n kind: MeetingKind;\n agenda: string | null;\n notes: string | null;\n startsAt: string;\n endsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n projectId: string;\n project: { id: string; name: string } | null;\n sprintId: string | null;\n sprint: { id: string; name: string } | null;\n milestoneId: string | null;\n milestone: { id: string; name: string } | null;\n actionItems: {\n id: string;\n workItemId: string;\n workItem: { id: string; key: string; title: string; status: string } | null;\n }[];\n}\n\n/** A plain-text field (agenda or notes) with its own dirty-tracked Save/Cancel — never rendered as HTML. */\nfunction EditableTextField({\n id,\n label,\n value,\n placeholder,\n onSave,\n}: {\n id: string;\n label: string;\n value: string | null;\n placeholder: string;\n onSave: (next: string) => Promise<void>;\n}) {\n const [draft, setDraft] = useState(value ?? '');\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const original = value ?? '';\n const dirty = draft !== original;\n\n const save = async () => {\n setError(null);\n setSaving(true);\n try {\n await onSave(draft.trim());\n } catch (e) {\n setError(mutationErrorMessage(e, `The ${label.toLowerCase()} could not be saved.`));\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"space-y-1\">\n <label htmlFor={id} className=\"text-sm font-medium\">\n {label}\n </label>\n <Textarea\n id={id}\n className=\"min-h-[120px]\"\n placeholder={placeholder}\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n data-testid={id}\n />\n {error && <ErrorLine message={error} />}\n {dirty && (\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setDraft(original)} disabled={saving}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={saving}\n data-testid={`${id}-save`}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nexport function MeetingsMeetingDetailPagePage() {\n const meetingId = useEntityId('meetingId');\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<{ planmagnetMeeting: MeetingDetail | null }>(\n GET_PLANMAGNET_MEETING,\n { variables: { id: meetingId }, skip: !meetingId }\n );\n const [update] = useMutation(UPDATE_PLANMAGNET_MEETING, {\n refetchQueries: ['GetPlanmagnetMeeting'],\n awaitRefetchQueries: true,\n });\n\n if (!meetingId)\n return (\n <div className=\"p-6 text-muted-foreground\">No meeting selected. Open one from the list.</div>\n );\n // Only before the first load: a refetch after Save/Unlink must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n if (error)\n return (\n <div role=\"alert\" className=\"p-6 text-destructive\">\n {isForbidden(error)\n ? \"You don't have access to this meeting.\"\n : `Error loading meeting: ${error.message}`}\n </div>\n );\n\n const meeting = data?.planmagnetMeeting;\n if (!meeting) return <div className=\"p-6 text-muted-foreground\">Meeting not found.</div>;\n\n const saveField = async (field: 'agenda' | 'notes', next: string) => {\n await throwOnError(update({ variables: { id: meeting.id, input: { [field]: next || null } } }));\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{meeting.title}</h1>\n <Badge variant=\"outline\">{enumLabel(meeting.kind)}</Badge>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground\">\n <span>\n {new Date(meeting.startsAt).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n {' – '}\n {new Date(meeting.endsAt).toLocaleString(undefined, { timeStyle: 'short' })}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Repeat className=\"h-3.5 w-3.5\" />\n {summarizeRecurrence(meeting.recurrenceRule)}\n </span>\n <a\n href={`/calendar/event/${encodeURIComponent(meeting.calendarEventId)}`}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n data-testid=\"meeting-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3.5 w-3.5\" />\n </a>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n {meeting.project && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/project-detail-page', meeting.project!.id))\n }\n >\n <FolderKanban className=\"h-3 w-3\" />\n {meeting.project.name}\n </button>\n )}\n {meeting.sprint && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/planning-detail-page', meeting.sprint!.id))\n }\n >\n <Zap className=\"h-3 w-3\" />\n {meeting.sprint.name}\n </button>\n )}\n {meeting.milestone && (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 hover:underline\"\n onClick={() =>\n navigate(detailPath('/projects/milestone-detail-page', meeting.milestone!.id))\n }\n >\n <Target className=\"h-3 w-3\" />\n {meeting.milestone.name}\n </button>\n )}\n </div>\n </div>\n <UnlinkMeetingDialog\n meetingId={meeting.id}\n calendarEventId={meeting.calendarEventId}\n meetingTitle={meeting.title}\n onUnlinked={() => navigate('/meetings/meetings-page')}\n />\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Agenda</CardTitle>\n </CardHeader>\n <CardContent>\n <EditableTextField\n id=\"meeting-agenda\"\n label=\"Agenda\"\n value={meeting.agenda}\n placeholder=\"What this meeting is for…\"\n onSave={(next) => saveField('agenda', next)}\n />\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Notes</CardTitle>\n </CardHeader>\n <CardContent>\n <EditableTextField\n id=\"meeting-notes\"\n label=\"Notes\"\n value={meeting.notes}\n placeholder=\"What happened…\"\n onSave={(next) => saveField('notes', next)}\n />\n </CardContent>\n </Card>\n </div>\n\n <MeetingActionItemsPanel\n meetingId={meeting.id}\n projectId={meeting.projectId}\n actionItems={meeting.actionItems}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function MeetingsMeetingsPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=MeetingsMeetingsPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsMeetingsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/MeetingsMeetingsPagePage.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,wBAAwB,4CAgMvC"}
@@ -0,0 +1,173 @@
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, detailPath as a, rowLinkProps as o, stopRowActivation as s } from "../utils/rowNavigation.js";
5
+ import { ProjectSelect as c } from "../controls/Pickers.js";
6
+ import { readUrlParam as l, writeUrlParam as u } from "../utils/urlParam.js";
7
+ import { MEETING_KINDS as d, summarizeRecurrence as f } from "../meetings/meetingModel.js";
8
+ import { LIST_PLANMAGNET_MEETINGS as p } from "../meetings/meetingGraphql.js";
9
+ import { ScheduleMeetingDialog as m } from "../meetings/ScheduleMeetingDialog.js";
10
+ import { useState as h } from "react";
11
+ import { CalendarClock as g, ExternalLink as _, ListChecks as v } from "lucide-react";
12
+ import { useQuery as y } from "@apollo/client/react";
13
+ import { jsx as b, jsxs as x } from "react/jsx-runtime";
14
+ import { Badge as S } from "@burdenoff/fe-libs/ui";
15
+ //#region src/planmagnet/pages/MeetingsMeetingsPagePage.tsx
16
+ function C() {
17
+ let { workspaceId: C } = e(), w = t(), [T, E] = h(() => l("projectId") ?? ""), [D, O] = h(() => l("kind") ?? "all"), [k, A] = h(() => l("from") ?? ""), [j, M] = h(() => l("to") ?? ""), N = (e) => {
18
+ E(e), u("projectId", e || null);
19
+ }, P = (e) => {
20
+ O(e), u("kind", e === "all" ? null : e);
21
+ }, F = (e) => {
22
+ A(e), u("from", e || null);
23
+ }, I = (e) => {
24
+ M(e), u("to", e || null);
25
+ }, L = {};
26
+ T && (L.projectId = T), D !== "all" && (L.kind = D), k && (L.from = (/* @__PURE__ */ new Date(`${k}T00:00:00.000Z`)).toISOString()), j && (L.to = (/* @__PURE__ */ new Date(`${j}T23:59:59.999Z`)).toISOString());
27
+ let { data: R, loading: z, error: B } = y(p, {
28
+ variables: {
29
+ workspaceId: C ?? "default",
30
+ filter: L,
31
+ pagination: { first: 100 }
32
+ },
33
+ skip: !C
34
+ });
35
+ if (z && !R) return /* @__PURE__ */ b("div", {
36
+ className: "flex items-center justify-center p-12",
37
+ children: /* @__PURE__ */ b("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
38
+ });
39
+ let V = [...R?.planmagnetMeetings.items ?? []].sort((e, t) => e.startsAt.localeCompare(t.startsAt)), H = R?.planmagnetMeetings.totalCount ?? 0;
40
+ return /* @__PURE__ */ x("div", {
41
+ className: "space-y-6 p-6",
42
+ children: [
43
+ /* @__PURE__ */ x("div", {
44
+ className: "flex flex-wrap items-center justify-between gap-3",
45
+ children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ x("h1", {
46
+ className: "text-2xl font-semibold flex items-center gap-2",
47
+ children: [/* @__PURE__ */ b(g, { className: "h-5 w-5 text-primary" }), "Meetings"]
48
+ }), /* @__PURE__ */ x("p", {
49
+ className: "text-sm text-muted-foreground",
50
+ children: [
51
+ H,
52
+ " meeting",
53
+ H === 1 ? "" : "s",
54
+ T ? " for this project" : " across all projects"
55
+ ]
56
+ })] }), T && /* @__PURE__ */ b(m, { projectId: T })]
57
+ }),
58
+ /* @__PURE__ */ x("div", {
59
+ className: "flex flex-wrap items-end gap-3",
60
+ children: [
61
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
62
+ htmlFor: "meetings-filter-project",
63
+ className: "text-xs text-muted-foreground",
64
+ children: "Project"
65
+ }), /* @__PURE__ */ b(c, {
66
+ id: "meetings-filter-project",
67
+ value: T,
68
+ onChange: N,
69
+ noneLabel: "All projects"
70
+ })] }),
71
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
72
+ htmlFor: "meetings-filter-kind",
73
+ className: "text-xs text-muted-foreground",
74
+ children: "Kind"
75
+ }), /* @__PURE__ */ x("select", {
76
+ id: "meetings-filter-kind",
77
+ className: "mt-1 rounded-md border border-input bg-background px-2 py-1.5 text-sm",
78
+ value: D,
79
+ onChange: (e) => P(e.target.value),
80
+ children: [/* @__PURE__ */ b("option", {
81
+ value: "all",
82
+ children: "All kinds"
83
+ }), d.map((e) => /* @__PURE__ */ b("option", {
84
+ value: e,
85
+ children: r(e)
86
+ }, e))]
87
+ })] }),
88
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
89
+ htmlFor: "meetings-filter-from",
90
+ className: "text-xs text-muted-foreground",
91
+ children: "From"
92
+ }), /* @__PURE__ */ b("input", {
93
+ id: "meetings-filter-from",
94
+ type: "date",
95
+ className: "mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm",
96
+ value: k,
97
+ onChange: (e) => F(e.target.value)
98
+ })] }),
99
+ /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("label", {
100
+ htmlFor: "meetings-filter-to",
101
+ className: "text-xs text-muted-foreground",
102
+ children: "To"
103
+ }), /* @__PURE__ */ b("input", {
104
+ id: "meetings-filter-to",
105
+ type: "date",
106
+ className: "mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm",
107
+ value: j,
108
+ onChange: (e) => I(e.target.value)
109
+ })] })
110
+ ]
111
+ }),
112
+ B && /* @__PURE__ */ b("div", {
113
+ role: "alert",
114
+ className: "rounded-md border border-destructive/40 p-4 text-sm text-destructive",
115
+ children: n(B) ? "You don't have access to these meetings." : `Meetings could not be loaded: ${B.message}`
116
+ }),
117
+ /* @__PURE__ */ x("div", {
118
+ className: "space-y-2",
119
+ children: [V.map((e) => /* @__PURE__ */ x("div", {
120
+ ...o(() => w(a("/meetings/meeting-detail-page", e.id)), e.title),
121
+ className: `flex flex-wrap items-center justify-between gap-2 rounded-md border p-3 ${i}`,
122
+ "data-testid": "meetings-list-row",
123
+ children: [/* @__PURE__ */ x("div", {
124
+ className: "min-w-0 space-y-1",
125
+ children: [/* @__PURE__ */ x("div", {
126
+ className: "flex items-center gap-2",
127
+ children: [/* @__PURE__ */ b("span", {
128
+ className: "text-sm font-medium truncate",
129
+ children: e.title
130
+ }), /* @__PURE__ */ b(S, {
131
+ variant: "outline",
132
+ className: "text-xs shrink-0",
133
+ children: r(e.kind)
134
+ })]
135
+ }), /* @__PURE__ */ x("div", {
136
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
137
+ children: [
138
+ /* @__PURE__ */ b("span", { children: new Date(e.startsAt).toLocaleString(void 0, {
139
+ dateStyle: "medium",
140
+ timeStyle: "short"
141
+ }) }),
142
+ /* @__PURE__ */ b("span", { children: e.project?.name ?? "Unknown project" }),
143
+ /* @__PURE__ */ b("span", { children: f(e.recurrenceRule) }),
144
+ e.actionItems.length > 0 && /* @__PURE__ */ x("span", {
145
+ className: "inline-flex items-center gap-1",
146
+ children: [
147
+ /* @__PURE__ */ b(v, { className: "h-3 w-3" }),
148
+ e.actionItems.length,
149
+ " action item",
150
+ e.actionItems.length === 1 ? "" : "s"
151
+ ]
152
+ })
153
+ ]
154
+ })]
155
+ }), /* @__PURE__ */ x("a", {
156
+ href: `/calendar/event/${encodeURIComponent(e.calendarEventId)}`,
157
+ onClick: s,
158
+ className: "inline-flex shrink-0 items-center gap-1 text-sm text-primary hover:underline",
159
+ "data-testid": "meetings-list-open-in-calendar",
160
+ children: ["Open in calendar", /* @__PURE__ */ b(_, { className: "h-3.5 w-3.5" })]
161
+ })]
162
+ }, e.id)), !B && V.length === 0 && /* @__PURE__ */ b("p", {
163
+ className: "text-center text-muted-foreground py-8",
164
+ children: T ? "No meetings match these filters." : "No meetings yet. Pick a project above to schedule one."
165
+ })]
166
+ })
167
+ ]
168
+ });
169
+ }
170
+ //#endregion
171
+ export { C as MeetingsMeetingsPagePage };
172
+
173
+ //# sourceMappingURL=MeetingsMeetingsPagePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeetingsMeetingsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/MeetingsMeetingsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, ListChecks } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect } from '../controls/Pickers';\nimport {\n ROW_LINK_CLASS,\n rowLinkProps,\n detailPath,\n stopRowActivation,\n} from '../utils/rowNavigation';\nimport { readUrlParam, writeUrlParam } from '../utils/urlParam';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { MEETING_KINDS, type MeetingKind, summarizeRecurrence } from '../meetings/meetingModel';\nimport { LIST_PLANMAGNET_MEETINGS } from '../meetings/meetingGraphql';\nimport { ScheduleMeetingDialog } from '../meetings/ScheduleMeetingDialog';\n\n/**\n * Every meeting in the workspace (GAP-06, CWF-22), filterable by project/kind/date. \"Schedule meeting\" needs a\n * fixed project (the dialog's own contract — see `ScheduleMeetingDialog`), so it only appears once a project\n * filter is chosen; otherwise picking one is the first step the empty state points at.\n */\n\ninterface MeetingRow {\n id: string;\n title: string;\n kind: MeetingKind;\n startsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n project: { id: string; name: string } | null;\n actionItems: { id: string }[];\n}\n\nexport function MeetingsMeetingsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [projectId, setProjectIdState] = useState(() => readUrlParam('projectId') ?? '');\n const [kind, setKindState] = useState(() => readUrlParam('kind') ?? 'all');\n const [from, setFromState] = useState(() => readUrlParam('from') ?? '');\n const [to, setToState] = useState(() => readUrlParam('to') ?? '');\n\n const setProjectId = (next: string) => {\n setProjectIdState(next);\n writeUrlParam('projectId', next || null);\n };\n const setKind = (next: string) => {\n setKindState(next);\n writeUrlParam('kind', next === 'all' ? null : next);\n };\n const setFrom = (next: string) => {\n setFromState(next);\n writeUrlParam('from', next || null);\n };\n const setTo = (next: string) => {\n setToState(next);\n writeUrlParam('to', next || null);\n };\n\n const filter: Record<string, unknown> = {};\n if (projectId) filter.projectId = projectId;\n if (kind !== 'all') filter.kind = kind;\n // A bare `from` is UTC midnight; `to` is pushed to the end of that day so picking one day as both bounds\n // still matches meetings that start later that same day (same convention as ActivityActivityPagePage).\n if (from) filter.from = new Date(`${from}T00:00:00.000Z`).toISOString();\n if (to) filter.to = new Date(`${to}T23:59:59.999Z`).toISOString();\n\n const { data, loading, error } = useQuery<{\n planmagnetMeetings: { items: MeetingRow[]; totalCount: number };\n }>(LIST_PLANMAGNET_MEETINGS, {\n variables: { workspaceId: workspaceId ?? 'default', filter, pagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch after scheduling must not blank the page.\n if (loading && !data)\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n\n const meetings = [...(data?.planmagnetMeetings.items ?? [])].sort((a, b) =>\n a.startsAt.localeCompare(b.startsAt)\n );\n const totalCount = data?.planmagnetMeetings.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold flex items-center gap-2\">\n <CalendarClock className=\"h-5 w-5 text-primary\" />\n Meetings\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {totalCount} meeting{totalCount === 1 ? '' : 's'}\n {projectId ? ' for this project' : ' across all projects'}\n </p>\n </div>\n {projectId && <ScheduleMeetingDialog projectId={projectId} />}\n </div>\n\n <div className=\"flex flex-wrap items-end gap-3\">\n <div>\n <label htmlFor=\"meetings-filter-project\" className=\"text-xs text-muted-foreground\">\n Project\n </label>\n <ProjectSelect\n id=\"meetings-filter-project\"\n value={projectId}\n onChange={setProjectId}\n noneLabel=\"All projects\"\n />\n </div>\n <div>\n <label htmlFor=\"meetings-filter-kind\" className=\"text-xs text-muted-foreground\">\n Kind\n </label>\n <select\n id=\"meetings-filter-kind\"\n className=\"mt-1 rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={kind}\n onChange={(e) => setKind(e.target.value)}\n >\n <option value=\"all\">All kinds</option>\n {MEETING_KINDS.map((k) => (\n <option key={k} value={k}>\n {enumLabel(k)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"meetings-filter-from\" className=\"text-xs text-muted-foreground\">\n From\n </label>\n <input\n id=\"meetings-filter-from\"\n type=\"date\"\n className=\"mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={from}\n onChange={(e) => setFrom(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor=\"meetings-filter-to\" className=\"text-xs text-muted-foreground\">\n To\n </label>\n <input\n id=\"meetings-filter-to\"\n type=\"date\"\n className=\"mt-1 block rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n value={to}\n onChange={(e) => setTo(e.target.value)}\n />\n </div>\n </div>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-destructive/40 p-4 text-sm text-destructive\"\n >\n {isForbidden(error)\n ? \"You don't have access to these meetings.\"\n : `Meetings could not be loaded: ${error.message}`}\n </div>\n )}\n\n <div className=\"space-y-2\">\n {meetings.map((m) => (\n <div\n key={m.id}\n {...rowLinkProps(\n () => navigate(detailPath('/meetings/meeting-detail-page', m.id)),\n m.title\n )}\n className={`flex flex-wrap items-center justify-between gap-2 rounded-md border p-3 ${ROW_LINK_CLASS}`}\n data-testid=\"meetings-list-row\"\n >\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium truncate\">{m.title}</span>\n <Badge variant=\"outline\" className=\"text-xs shrink-0\">\n {enumLabel(m.kind)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n <span>\n {new Date(m.startsAt).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n </span>\n <span>{m.project?.name ?? 'Unknown project'}</span>\n <span>{summarizeRecurrence(m.recurrenceRule)}</span>\n {m.actionItems.length > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <ListChecks className=\"h-3 w-3\" />\n {m.actionItems.length} action item{m.actionItems.length === 1 ? '' : 's'}\n </span>\n )}\n </div>\n </div>\n <a\n href={`/calendar/event/${encodeURIComponent(m.calendarEventId)}`}\n onClick={stopRowActivation}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm text-primary hover:underline\"\n data-testid=\"meetings-list-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3.5 w-3.5\" />\n </a>\n </div>\n ))}\n {!error && meetings.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {projectId\n ? 'No meetings match these filters.'\n : 'No meetings yet. Pick a project above to schedule one.'}\n </p>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function ProjectsAtRiskPagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ProjectsAtRiskPagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectsAtRiskPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsAtRiskPagePage.tsx"],"names":[],"mappings":"AA4GA,wBAAgB,sBAAsB,4CA8PrC"}