@burdenoff/microfe-planmagnet 2026.1002.2 → 2026.1008.1
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/generated/wspace-operations.d.ts +574 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +486 -16
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +174 -168
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +63 -59
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/board/BulkActions.d.ts.map +1 -1
- package/dist/planmagnet/board/BulkActions.js +106 -55
- package/dist/planmagnet/board/BulkActions.js.map +1 -1
- package/dist/planmagnet/controls/Pickers.d.ts +3 -3
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/dashboards/DashboardUI.d.ts +0 -4
- package/dist/planmagnet/dashboards/DashboardUI.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/DashboardUI.js +6 -13
- package/dist/planmagnet/dashboards/DashboardUI.js.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.d.ts.map +1 -1
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js +70 -59
- package/dist/planmagnet/dashboards/cards/SprintCapacityCard.js.map +1 -1
- package/dist/planmagnet/forecast/ResourceForecastCard.js +1 -1
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -1
- package/dist/planmagnet/index.d.ts +1 -1
- package/dist/planmagnet/index.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts +3 -3
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +26 -19
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts +2 -2
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +18 -12
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js +251 -0
- package/dist/planmagnet/pages/ProductsCustomerSatisfactionPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +94 -92
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +116 -114
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +174 -159
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +159 -145
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +161 -143
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +157 -155
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamInviteMemberPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamInviteMemberPagePage.js +142 -82
- package/dist/planmagnet/pages/TeamInviteMemberPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js +97 -99
- package/dist/planmagnet/pages/analytics/DeliveryKpisCard.js.map +1 -1
- package/dist/planmagnet/product/FeatureLinkPanels.js +66 -66
- package/dist/planmagnet/product/ProductFeatureControls.js +38 -38
- package/dist/planmagnet/product/ProductFeatureLinkCard.js +8 -8
- package/dist/planmagnet/product/ProductLineControls.js +83 -83
- package/dist/planmagnet/product/ProductTeamsPanel.js +6 -6
- package/dist/planmagnet/product/routes.d.ts +6 -0
- package/dist/planmagnet/product/routes.d.ts.map +1 -1
- package/dist/planmagnet/product/routes.js +2 -2
- package/dist/planmagnet/product/routes.js.map +1 -1
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts +20 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js +55 -0
- package/dist/planmagnet/satisfaction/DeliverySatisfactionSection.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts +30 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.js +282 -0
- package/dist/planmagnet/satisfaction/SatisfactionFigures.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts +9 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js +390 -0
- package/dist/planmagnet/satisfaction/SatisfactionImportDialog.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts +17 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js +77 -0
- package/dist/planmagnet/satisfaction/SatisfactionLinkFields.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts +18 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js +87 -0
- package/dist/planmagnet/satisfaction/SatisfactionReleaseSelect.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts +13 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js +326 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponseDialog.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts +23 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js +258 -0
- package/dist/planmagnet/satisfaction/SatisfactionResponsesTable.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts +6 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js +56 -0
- package/dist/planmagnet/satisfaction/SatisfactionSummaryCard.js.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts +16 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js +102 -0
- package/dist/planmagnet/satisfaction/SatisfactionWindowPicker.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfaction.d.ts +154 -0
- package/dist/planmagnet/satisfaction/satisfaction.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfaction.js +210 -0
- package/dist/planmagnet/satisfaction/satisfaction.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts +108 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.js +211 -0
- package/dist/planmagnet/satisfaction/satisfactionCsv.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.d.ts +90 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.js +90 -0
- package/dist/planmagnet/satisfaction/satisfactionForm.js.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts +116 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.js +86 -0
- package/dist/planmagnet/satisfaction/satisfactionOperations.js.map +1 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts +20 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.d.ts.map +1 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.js +31 -0
- package/dist/planmagnet/satisfaction/useSatisfactionAccess.js.map +1 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts +35 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.d.ts.map +1 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.js +124 -0
- package/dist/planmagnet/skills/RequiredSkillsEditor.js.map +1 -0
- package/dist/planmagnet/skills/SkillChips.d.ts +30 -0
- package/dist/planmagnet/skills/SkillChips.d.ts.map +1 -0
- package/dist/planmagnet/skills/SkillChips.js +57 -0
- package/dist/planmagnet/skills/SkillChips.js.map +1 -0
- package/dist/planmagnet/skills/SkillCoverageCard.d.ts +24 -0
- package/dist/planmagnet/skills/SkillCoverageCard.d.ts.map +1 -0
- package/dist/planmagnet/skills/SkillCoverageCard.js +256 -0
- package/dist/planmagnet/skills/SkillCoverageCard.js.map +1 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts +10 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.d.ts.map +1 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.js +65 -0
- package/dist/planmagnet/skills/WorkItemRequiredSkills.js.map +1 -0
- package/dist/planmagnet/skills/skillOperations.d.ts +52 -0
- package/dist/planmagnet/skills/skillOperations.d.ts.map +1 -0
- package/dist/planmagnet/skills/skillOperations.js +44 -0
- package/dist/planmagnet/skills/skillOperations.js.map +1 -0
- package/dist/planmagnet/skills/skillTags.d.ts +103 -0
- package/dist/planmagnet/skills/skillTags.d.ts.map +1 -0
- package/dist/planmagnet/skills/skillTags.js +115 -0
- package/dist/planmagnet/skills/skillTags.js.map +1 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts +28 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.d.ts.map +1 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.js +30 -0
- package/dist/planmagnet/skills/useRequiredSkillsDraft.js.map +1 -0
- package/dist/planmagnet/team/PendingInvitations.d.ts +27 -0
- package/dist/planmagnet/team/PendingInvitations.d.ts.map +1 -0
- package/dist/planmagnet/team/PendingInvitations.js +220 -0
- package/dist/planmagnet/team/PendingInvitations.js.map +1 -0
- package/dist/planmagnet/team/invitationEmail.d.ts +35 -0
- package/dist/planmagnet/team/invitationEmail.d.ts.map +1 -0
- package/dist/planmagnet/team/invitationEmail.js +73 -0
- package/dist/planmagnet/team/invitationEmail.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { TaskTimePanel } from '../time/TaskTimePanel';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport { CustomFieldsPanel } from '../controls/CustomFieldValuesPanel';\nimport { DependencyPanel } from '../dependency/DependencyPanel';\nimport { IssueLinksPanel } from '../links/IssueLinksPanel';\nimport { WorkItemMilestoneSelect } from '../milestones/MilestoneControls';\nimport {\n WORK_ITEM_PRIORITIES,\n WorkItemStatusSelect,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { usePresence } from '../hooks/usePresence';\nimport { PresenceAvatars } from '../presence/PresenceAvatars';\nimport { WorkItemDiscussionPanel } from '../discussion/WorkItemDiscussionPanel';\nimport { CodeLinksPanel, type WorkItemCodeLink } from '../links/CodeLinksPanel';\nimport { WorkItemProductFeatureChip } from '../product/WorkItemProductFeatureChip';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n projectId\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n milestoneId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n customFieldValues {\n id\n definitionId\n value\n }\n discussionConversationId\n codeLinks {\n id\n kind\n title\n url\n state\n number\n authorLogin\n mergedAt\n ref\n }\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n milestoneId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n customFieldValues: { id: string; definitionId: string; value: unknown }[];\n discussionConversationId: string | null;\n codeLinks: WorkItemCodeLink[];\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // CWF-24: who else is looking at this task right now, and who's mid-comment.\n const presence = usePresence('WORK_ITEM', workItemId || null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} · {task.type.toLowerCase()}\n </p>\n {/* PRM-01: read-only — an epic's product feature, or a story's through its epic. */}\n <WorkItemProductFeatureChip workItemId={task.id} />\n <PresenceAvatars others={presence.others} />\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <CustomFieldsPanel\n workItemId={task.id}\n projectId={task.projectId}\n values={task.customFieldValues ?? []}\n />\n <LinkedDocsPanel workItemId={task.id} />\n <TaskTimePanel\n workspaceId={workspaceId}\n workItemId={task.id}\n projectId={task.projectId}\n taskLabel={`${task.key} — ${task.title}`}\n />\n <DependencyPanel\n workspaceId={workspaceId ?? ''}\n workItemId={task.id}\n projectId={task.projectId}\n />\n {/* PDM-24: semantic links (blocks / duplicates / causes / relates to), not scheduling. */}\n <IssueLinksPanel workItemId={task.id} />\n {/* CWF-26: a real group conversation in Conversations, not a comment thread of its own. */}\n <WorkItemDiscussionPanel\n workItemId={task.id}\n workItemKey={task.key}\n workItemTitle={task.title}\n assigneeId={task.assigneeId}\n discussionConversationId={task.discussionConversationId}\n />\n {/* AUT-12: PRs/commits/releases GitHub sync discovered that mention this item's key. */}\n <CodeLinksPanel codeLinks={task.codeLinks ?? []} />\n <WorkItemComments\n workItemId={task.id}\n presence={{\n typingActorIds: presence.typingActorIds,\n notifyTyping: presence.notifyTyping,\n }}\n />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemStatusSelect\n workItemId={task.id}\n projectId={task.projectId}\n value={task.status}\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Milestone</span>\n {/* PDM-22: a milestone of this task's project, or none. */}\n <WorkItemMilestoneSelect\n workItemId={task.id}\n projectId={task.projectId}\n milestoneId={task.milestoneId}\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TasksTaskDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TasksTaskDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { WorkItemComments } from '../comments/WorkItemComments';\nimport { WorkItemActivity } from '../comments/WorkItemActivity';\nimport { TaskTimePanel } from '../time/TaskTimePanel';\nimport { PersonName } from '../people/People';\nimport { DocSearchSelect } from '../controls/Pickers';\nimport { CustomFieldsPanel } from '../controls/CustomFieldValuesPanel';\nimport { DependencyPanel } from '../dependency/DependencyPanel';\nimport { IssueLinksPanel } from '../links/IssueLinksPanel';\nimport { WorkItemMilestoneSelect } from '../milestones/MilestoneControls';\nimport {\n WORK_ITEM_PRIORITIES,\n WorkItemStatusSelect,\n WorkItemAssigneeSelect,\n WorkItemEnumSelect,\n WorkItemHeaderActions,\n} from '../controls/WorkItemControls';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { FileText, Link2, Plus, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { usePresence } from '../hooks/usePresence';\nimport { PresenceAvatars } from '../presence/PresenceAvatars';\nimport { WorkItemDiscussionPanel } from '../discussion/WorkItemDiscussionPanel';\nimport { CodeLinksPanel, type WorkItemCodeLink } from '../links/CodeLinksPanel';\nimport { WorkItemProductFeatureChip } from '../product/WorkItemProductFeatureChip';\nimport { WorkItemRequiredSkills } from '../skills/WorkItemRequiredSkills';\n\nconst GET_WORK_ITEM = gql`\n query GetWorkItemDetail($workItemId: ID!) {\n planmagnetWorkItem(id: $workItemId) {\n id\n projectId\n title\n description\n type\n status\n priority\n key\n assigneeId\n reporterId\n createdBy\n sprintId\n milestoneId\n storyPoints\n estimateMinutes\n dueDate\n labels\n tags\n createdAt\n updatedAt\n customFieldValues {\n id\n definitionId\n value\n }\n discussionConversationId\n codeLinks {\n id\n kind\n title\n url\n state\n number\n authorLogin\n mergedAt\n ref\n }\n }\n }\n`;\n\nconst GET_WORK_ITEM_DOCS = gql`\n query GetWorkItemDocs($workItemId: ID!) {\n planmagnetWorkItemDocs(workItemId: $workItemId) {\n id\n title\n status\n version\n updatedAt\n }\n }\n`;\n\nconst CREATE_DOC = gql`\n mutation CreateDocForWorkItem($input: PlanmagnetCreateDocInput!) {\n planmagnetCreateDoc(input: $input) {\n id\n }\n }\n`;\n\nconst LINK_DOC_TO_WORK_ITEM = gql`\n mutation LinkDocToWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetLinkDocToWorkItem(docId: $docId, workItemId: $workItemId) {\n id\n }\n }\n`;\n\nconst UNLINK_DOC_FROM_WORK_ITEM = gql`\n mutation UnlinkDocFromWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface WorkItemNode {\n id: string;\n title: string;\n description: string | null;\n type: string;\n status: string;\n priority: string;\n key: string;\n assigneeId: string | null;\n reporterId: string | null;\n createdBy: string;\n sprintId: string | null;\n milestoneId: string | null;\n storyPoints: number | null;\n estimateMinutes: number | null;\n dueDate: string | null;\n labels: string[];\n tags: string[];\n createdAt: string;\n updatedAt: string;\n customFieldValues: { id: string; definitionId: string; value: unknown }[];\n discussionConversationId: string | null;\n codeLinks: WorkItemCodeLink[];\n}\n\ninterface WorkItemDocNode {\n id: string;\n title: string;\n status: string;\n version: number;\n updatedAt: string;\n}\n\nconst docStatusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PUBLISHED: 'bg-green-100 text-green-800',\n ARCHIVED: 'bg-gray-100 text-gray-500',\n};\n\n/**\n * Linked-docs panel (BOFF-4443): create a doc and link it to this work item\n * in one action, view currently-linked docs, and unlink one. Reads/writes go\n * through the real planmagnetWorkItemDocs / planmagnetCreateDoc /\n * planmagnetLinkDocToWorkItem / planmagnetUnlinkDocFromWorkItem GraphQL\n * fields — no fabricated data. Full doc editing (revisions, revert) lives on\n * a dedicated doc detail view; this panel is scoped to linking.\n */\nfunction LinkedDocsPanel({ workItemId }: { workItemId: string }) {\n const navigate = usePlanMagnetNavigate();\n const [isComposing, setIsComposing] = useState(false);\n // BOFF-7961: only \"New Doc\" existed, so a doc written on the Docs page could never be linked to a task.\n const [isLinking, setIsLinking] = useState(false);\n const [existingDocId, setExistingDocId] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newContent, setNewContent] = useState('');\n\n const { data, loading, error, refetch } = useQuery<{\n planmagnetWorkItemDocs: WorkItemDocNode[];\n }>(GET_WORK_ITEM_DOCS, {\n variables: { workItemId },\n skip: !workItemId,\n });\n\n const [createDoc, { loading: creating }] = useMutation(CREATE_DOC);\n const [linkDoc, { loading: linking }] = useMutation(LINK_DOC_TO_WORK_ITEM);\n const [unlinkDoc, { loading: unlinking }] = useMutation(UNLINK_DOC_FROM_WORK_ITEM);\n // A refused mutation resolves with { error } (fe-libs errorPolicy 'all');\n // the composer used to clear itself as if the doc had been saved.\n const [actionError, setActionError] = useState<string | null>(null);\n\n const docs = data?.planmagnetWorkItemDocs ?? [];\n\n const handleCreateAndLink = async () => {\n if (!newTitle.trim() || !newContent.trim()) return;\n setActionError(null);\n try {\n const result = await throwOnError(\n createDoc({ variables: { input: { title: newTitle.trim(), content: newContent.trim() } } })\n );\n const docId = result.data?.planmagnetCreateDoc?.id;\n if (docId) {\n await throwOnError(linkDoc({ variables: { docId, workItemId } }));\n }\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be saved.');\n return;\n }\n setNewTitle('');\n setNewContent('');\n setIsComposing(false);\n void refetch();\n };\n\n const handleLinkExisting = async () => {\n if (!existingDocId) return;\n setActionError(null);\n try {\n await throwOnError(linkDoc({ variables: { docId: existingDocId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be linked.');\n return;\n }\n setExistingDocId('');\n setIsLinking(false);\n void refetch();\n };\n\n const handleUnlink = async (docId: string) => {\n setActionError(null);\n try {\n await throwOnError(unlinkDoc({ variables: { docId, workItemId } }));\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'The doc could not be unlinked.');\n return;\n }\n void refetch();\n };\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Linked Docs</CardTitle>\n {!isComposing && !isLinking && (\n <div className=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsLinking(true)}>\n <Link2 className=\"h-4 w-4 mr-1\" />\n Link existing doc\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={() => setIsComposing(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Doc\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {isLinking && (\n <div className=\"space-y-2 rounded-md border border-input p-3\">\n <DocSearchSelect\n id=\"task-link-doc\"\n value={existingDocId}\n onChange={setExistingDocId}\n exclude={docs.map((d) => d.id)}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsLinking(false);\n setExistingDocId('');\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" disabled={!existingDocId || linking} onClick={handleLinkExisting}>\n {linking ? 'Linking...' : 'Link'}\n </Button>\n </div>\n </div>\n )}\n {isComposing && (\n <div className=\"space-y-3 rounded-md border border-input p-3\">\n <Input\n placeholder=\"Doc title\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n />\n <MarkdownEditor\n value={newContent}\n onChange={setNewContent}\n placeholder=\"Write the spec/doc content...\"\n minHeight={140}\n />\n <div className=\"flex gap-2 justify-end\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setIsComposing(false);\n setNewTitle('');\n setNewContent('');\n }}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={creating || !newTitle.trim() || !newContent.trim()}\n onClick={handleCreateAndLink}\n >\n {creating ? 'Creating...' : 'Create & Link'}\n </Button>\n </div>\n </div>\n )}\n\n {loading && <p className=\"text-sm text-muted-foreground\">Loading docs...</p>}\n {error && <p className=\"text-sm text-red-500\">Error: {error.message}</p>}\n {actionError && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {actionError}\n </p>\n )}\n {!loading && !error && docs.length === 0 && !isComposing && !isLinking && (\n <p className=\"text-sm text-muted-foreground\">No docs linked to this work item yet.</p>\n )}\n {docs.map((doc) => (\n <div\n key={doc.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-input p-3\"\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <FileText className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n className=\"block max-w-full truncate text-left text-sm font-medium hover:underline\"\n onClick={() =>\n navigate(`/docs/doc-detail-page?docId=${encodeURIComponent(doc.id)}`)\n }\n >\n {doc.title}\n </button>\n <p className=\"text-xs text-muted-foreground\">v{doc.version}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${docStatusColor[doc.status] ?? ''}`}\n >\n {doc.status.toLowerCase()}\n </span>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinking}\n onClick={() => handleUnlink(doc.id)}\n aria-label={`Unlink ${doc.title}`}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n\nexport function TasksTaskDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n // In a real app, workItemId comes from route params.\n const workItemId = new URLSearchParams(window.location.search).get('taskId') ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetWorkItem: WorkItemNode;\n }>(GET_WORK_ITEM, {\n variables: { workItemId },\n skip: !workspaceId || !workItemId,\n });\n\n // CWF-24: who else is looking at this task right now, and who's mid-comment.\n const presence = usePresence('WORK_ITEM', workItemId || null);\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <div className=\"animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-red-500\">Error: {error.message}</div>;\n }\n\n const task = data?.planmagnetWorkItem;\n if (!task) return <div className=\"p-6 text-muted-foreground\">Task not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">{task.title}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {task.key} · {task.type.toLowerCase()}\n </p>\n {/* PRM-01: read-only — an epic's product feature, or a story's through its epic. */}\n <WorkItemProductFeatureChip workItemId={task.id} />\n <PresenceAvatars others={presence.others} />\n </div>\n <WorkItemHeaderActions workItemId={task.id} title={task.title} />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{task.description ?? 'No description provided.'}</p>\n </CardContent>\n </Card>\n <CustomFieldsPanel\n workItemId={task.id}\n projectId={task.projectId}\n values={task.customFieldValues ?? []}\n />\n <LinkedDocsPanel workItemId={task.id} />\n <TaskTimePanel\n workspaceId={workspaceId}\n workItemId={task.id}\n projectId={task.projectId}\n taskLabel={`${task.key} — ${task.title}`}\n />\n <DependencyPanel\n workspaceId={workspaceId ?? ''}\n workItemId={task.id}\n projectId={task.projectId}\n />\n {/* PDM-24: semantic links (blocks / duplicates / causes / relates to), not scheduling. */}\n <IssueLinksPanel workItemId={task.id} />\n {/* CWF-26: a real group conversation in Conversations, not a comment thread of its own. */}\n <WorkItemDiscussionPanel\n workItemId={task.id}\n workItemKey={task.key}\n workItemTitle={task.title}\n assigneeId={task.assigneeId}\n discussionConversationId={task.discussionConversationId}\n />\n {/* AUT-12: PRs/commits/releases GitHub sync discovered that mention this item's key. */}\n <CodeLinksPanel codeLinks={task.codeLinks ?? []} />\n <WorkItemComments\n workItemId={task.id}\n presence={{\n typingActorIds: presence.typingActorIds,\n notifyTyping: presence.notifyTyping,\n }}\n />\n <WorkItemActivity workItemId={task.id} />\n </div>\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Status</span>\n <WorkItemStatusSelect\n workItemId={task.id}\n projectId={task.projectId}\n value={task.status}\n />\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Priority</span>\n <WorkItemEnumSelect\n workItemId={task.id}\n field=\"priority\"\n value={task.priority}\n options={WORK_ITEM_PRIORITIES}\n label=\"Priority\"\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"capitalize\">\n {task.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Milestone</span>\n {/* PDM-22: a milestone of this task's project, or none. */}\n <WorkItemMilestoneSelect\n workItemId={task.id}\n projectId={task.projectId}\n milestoneId={task.milestoneId}\n />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Story Points</span>\n <span className=\"font-medium\">{task.storyPoints ?? 0}</span>\n </div>\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-muted-foreground\">Assignee</span>\n {/* Members by name (BOFF-7946); choosing one assigns the task (BOFF-7938). */}\n <WorkItemAssigneeSelect workItemId={task.id} assigneeId={task.assigneeId} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Reporter</span>\n {/* No PlanMagnet form sets reporterId, so a UI-created task has none; its creator reported it. */}\n <PersonName id={task.reporterId ?? task.createdBy} />\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Key</span>\n <span>{task.key}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Due Date</span>\n <span>{task.dueDate ?? 'Not set'}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{task.createdAt}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{task.updatedAt}</span>\n </div>\n <div>\n <span className=\"text-muted-foreground text-xs\">Labels</span>\n <div className=\"flex gap-1 mt-1 flex-wrap\">\n {(task.labels ?? []).map((l) => (\n <span key={l} className=\"text-xs px-2 py-0.5 rounded bg-muted\">\n {l}\n </span>\n ))}\n </div>\n </div>\n {/* ANR-07/ANR-28: read-only here (edited on the edit page); an epic also shows its children's. */}\n <WorkItemRequiredSkills workItemId={task.id} />\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAM,IAAgB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CnB,IAAqB,CAAG;;;;;;;;;;GAYxB,IAAa,CAAG;;;;;;GAQhB,IAAwB,CAAG;;;;;;GAQ3B,IAA4B,CAAG;;;;GAuC/B,IAAyC;CAC7C,OAAO;CACP,WAAW;CACX,UAAU;CACX;AAUD,SAAS,EAAgB,EAAE,iBAAsC;CAC/D,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAE/C,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAe,KAAoB,EAAS,GAAG,EAChD,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAY,KAAiB,EAAS,GAAG,EAE1C,EAAE,SAAM,YAAS,UAAO,eAAY,EAEvC,GAAoB;EACrB,WAAW,EAAE,eAAY;EACzB,MAAM,CAAC;EACR,CAAC,EAEI,CAAC,GAAW,EAAE,SAAS,OAAc,EAAY,EAAW,EAC5D,CAAC,GAAS,EAAE,SAAS,OAAa,EAAY,EAAsB,EACpE,CAAC,GAAW,EAAE,SAAS,OAAe,EAAY,EAA0B,EAG5E,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAO,GAAM,0BAA0B,EAAE,EAEzC,IAAsB,YAAY;AAClC,SAAC,EAAS,MAAM,IAAI,CAAC,EAAW,MAAM,GAC1C;KAAe,KAAK;AACpB,OAAI;IAIF,IAAM,KAHS,MAAM,EACnB,EAAU,EAAE,WAAW,EAAE,OAAO;KAAE,OAAO,EAAS,MAAM;KAAE,SAAS,EAAW,MAAM;KAAE,EAAE,EAAE,CAAC,CAC5F,EACoB,MAAM,qBAAqB;AAChD,IAAI,KACF,MAAM,EAAa,EAAQ,EAAE,WAAW;KAAE;KAAO;KAAY,EAAE,CAAC,CAAC;YAE5D,GAAG;AACV,MAAe,aAAa,QAAQ,EAAE,UAAU,8BAA8B;AAC9E;;AAKG,GAHL,EAAY,GAAG,EACf,EAAc,GAAG,EACjB,EAAe,GAAM,EAChB,GAAS;;IAGV,IAAqB,YAAY;AAChC,SACL;KAAe,KAAK;AACpB,OAAI;AACF,UAAM,EAAa,EAAQ,EAAE,WAAW;KAAE,OAAO;KAAe;KAAY,EAAE,CAAC,CAAC;YACzE,GAAG;AACV,MAAe,aAAa,QAAQ,EAAE,UAAU,+BAA+B;AAC/E;;AAIG,GAFL,EAAiB,GAAG,EACpB,EAAa,GAAM,EACd,GAAS;;IAGV,IAAe,OAAO,MAAkB;AAC5C,IAAe,KAAK;AACpB,MAAI;AACF,SAAM,EAAa,EAAU,EAAE,WAAW;IAAE;IAAO;IAAY,EAAE,CAAC,CAAC;WAC5D,GAAG;AACV,KAAe,aAAa,QAAQ,EAAE,UAAU,iCAAiC;AACjF;;AAEG,KAAS;;AAGhB,QACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAY,WAAU;YAAtB,CACE,kBAAC,GAAD,EAAA,UAAW,eAAuB,CAAA,EACjC,CAAC,KAAe,CAAC,KAChB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAe,EAAa,GAAK;cAArE,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,oBAE3B;OACT,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAe,EAAe,GAAK;cAAvE,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,UAE1B;MACL;KAEG;KACb,kBAAC,GAAD;EAAa,WAAU;YAAvB;GACG,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,IAAG;KACH,OAAO;KACP,UAAU;KACV,SAAS,EAAK,KAAK,MAAM,EAAE,GAAG;KAC9B,CAAA,EACF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,eAAe;AAEb,OADA,EAAa,GAAM,EACnB,EAAiB,GAAG;;gBAEvB;MAEQ,CAAA,EACT,kBAAC,GAAD;MAAQ,MAAK;MAAK,UAAU,CAAC,KAAiB;MAAS,SAAS;gBAC7D,IAAU,eAAe;MACnB,CAAA,CACL;OACF;;GAEP,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;MAC5C,CAAA;KACF,kBAAC,GAAD;MACE,OAAO;MACP,UAAU;MACV,aAAY;MACZ,WAAW;MACX,CAAA;KACF,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,eAAe;AAGb,QAFA,EAAe,GAAM,EACrB,EAAY,GAAG,EACf,EAAc,GAAG;;iBAEpB;OAEQ,CAAA,EACT,kBAAC,GAAD;OACE,MAAK;OACL,UAAU,KAAY,CAAC,EAAS,MAAM,IAAI,CAAC,EAAW,MAAM;OAC5D,SAAS;iBAER,IAAW,gBAAgB;OACrB,CAAA,CACL;;KACF;;GAGP,KAAW,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAAmB,CAAA;GAC3E,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAb,CAAoC,WAAQ,EAAM,QAAY;;GACvE,KACC,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;cACvB;IACC,CAAA;GAEL,CAAC,KAAW,CAAC,KAAS,EAAK,WAAW,KAAK,CAAC,KAAe,CAAC,KAC3D,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAAyC,CAAA;GAEvF,EAAK,KAAK,MACT,kBAAC,OAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,WAAU,0CAA2C,CAAA,EAC/D,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,eACE,EAAS,+BAA+B,mBAAmB,EAAI,GAAG,GAAG;iBAGtE,EAAI;OACE,CAAA,EACT,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA6C,KAAE,EAAI,QAAY;SAC3D;QACF;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,WAAW,+CAA+C,EAAe,EAAI,WAAW;gBAEvF,EAAI,OAAO,aAAa;MACpB,CAAA,EACP,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,UAAU;MACV,eAAe,EAAa,EAAI,GAAG;MACnC,cAAY,UAAU,EAAI;gBAE1B,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CACL;OACF;MAlCC,EAAI,GAkCL,CACN;GACU;IACT,EAAA,CAAA;;AAIX,SAAgB,IAA0B;CACxC,IAAM,EAAE,mBAAgB,GAAe,EAGjC,IAAa,IAAI,gBAAgB,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,IAAI,IAE1E,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAe;EAChB,WAAW,EAAE,eAAY;EACzB,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EAGI,IAAW,EAAY,aAAa,KAAc,KAAK;AAM7D,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,kFAAmF,CAAA;EAC9F,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAkC,WAAQ,EAAM,QAAc;;CAGvE,IAAM,IAAO,GAAM;AAGnB,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eAA0B,EAAK;KAAW,CAAA;IACxD,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAK;MAAI;MAAW,EAAK,KAAK,aAAa;MAC1C;;IAEJ,kBAAC,GAAD,EAA4B,YAAY,EAAK,IAAM,CAAA;IACnD,kBAAC,GAAD,EAAiB,QAAQ,EAAS,QAAU,CAAA;IACxC,EAAA,CAAA,EACN,kBAAC,GAAD;IAAuB,YAAY,EAAK;IAAI,OAAO,EAAK;IAAS,CAAA,CAC7D;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,eAAuB,CAAA,EACvB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;MAAG,WAAU;gBAAW,EAAK,eAAe;MAA+B,CAAA,EAC/D,CAAA,CACT,EAAA,CAAA;KACP,kBAAC,GAAD;MACE,YAAY,EAAK;MACjB,WAAW,EAAK;MAChB,QAAQ,EAAK,qBAAqB,EAAE;MACpC,CAAA;KACF,kBAAC,GAAD,EAAiB,YAAY,EAAK,IAAM,CAAA;KACxC,kBAAC,GAAD;MACe;MACb,YAAY,EAAK;MACjB,WAAW,EAAK;MAChB,WAAW,GAAG,EAAK,IAAI,KAAK,EAAK;MACjC,CAAA;KACF,kBAAC,GAAD;MACE,aAAa,KAAe;MAC5B,YAAY,EAAK;MACjB,WAAW,EAAK;MAChB,CAAA;KAEF,kBAAC,GAAD,EAAiB,YAAY,EAAK,IAAM,CAAA;KAExC,kBAAC,GAAD;MACE,YAAY,EAAK;MACjB,aAAa,EAAK;MAClB,eAAe,EAAK;MACpB,YAAY,EAAK;MACjB,0BAA0B,EAAK;MAC/B,CAAA;KAEF,kBAAC,GAAD,EAAgB,WAAW,EAAK,aAAa,EAAE,EAAI,CAAA;KACnD,kBAAC,GAAD;MACE,YAAY,EAAK;MACjB,UAAU;OACR,gBAAgB,EAAS;OACzB,cAAc,EAAS;OACxB;MACD,CAAA;KACF,kBAAC,GAAD,EAAkB,YAAY,EAAK,IAAM,CAAA;KACrC;OACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA,EACnB,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAa,CAAA,EACrD,kBAAC,GAAD;QACE,YAAY,EAAK;QACjB,WAAW,EAAK;QAChB,OAAO,EAAK;QACZ,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAe,CAAA,EACvD,kBAAC,GAAD;QACE,YAAY,EAAK;QACjB,OAAM;QACN,OAAO,EAAK;QACZ,SAAS;QACT,OAAM;QACN,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAW,CAAA,EACnD,kBAAC,GAAD;QAAO,SAAQ;QAAU,WAAU;kBAChC,EAAK,KAAK,aAAa;QAClB,CAAA,CACJ;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAgB,CAAA,EAExD,kBAAC,GAAD;QACE,YAAY,EAAK;QACjB,WAAW,EAAK;QAChB,aAAa,EAAK;QAClB,CAAA,CACE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAmB,CAAA,EAC3D,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAK,eAAe;QAAS,CAAA,CACxD;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAe,CAAA,EAEvD,kBAAC,GAAD;QAAwB,YAAY,EAAK;QAAI,YAAY,EAAK;QAAc,CAAA,CACxE;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAe,CAAA,EAEvD,kBAAC,GAAD,EAAY,IAAI,EAAK,cAAc,EAAK,WAAa,CAAA,CACjD;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAU,CAAA,EAClD,kBAAC,QAAD,EAAA,UAAO,EAAK,KAAW,CAAA,CACnB;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAe,CAAA,EACvD,kBAAC,QAAD,EAAA,UAAO,EAAK,WAAW,WAAiB,CAAA,CACpC;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,EAAK,WAAiB,CAAA,CACzB;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAwB;QAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,EAAK,WAAiB,CAAA,CACzB;;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAa,CAAA,EAC7D,kBAAC,OAAD;OAAK,WAAU;kBACX,EAAK,UAAU,EAAE,EAAE,KAAK,MACxB,kBAAC,QAAD;QAAc,WAAU;kBACrB;QACI,EAFI,EAEJ,CACP;OACE,CAAA,CACF,EAAA,CAAA;MAEN,kBAAC,GAAD,EAAwB,YAAY,EAAK,IAAM,CAAA;MACnC;OACT,EAAA,CAAA;IACH,CAAA,CACF;KACF;MArJU,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAAqB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,wBAAwB,4CAiRvC"}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { usePlanMagnet as e, usePlanMagnetNavigate as
|
|
2
|
-
import {
|
|
1
|
+
import { usePlanMagnet as e, usePlanMagnetNavigate as ee } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { useTr as te } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
4
|
+
import { refusalMessage as n } from "../product/refusal.js";
|
|
3
5
|
import { isValidInviteEmail as r } from "../utils/email.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { invitationEmailReason as i, invitationEmailSent as a } from "../team/invitationEmail.js";
|
|
7
|
+
import { PENDING_INVITATIONS_QUERY as o, PendingInvitations as s, RESEND_INVITATION as ne } from "../team/PendingInvitations.js";
|
|
8
|
+
import { useMemo as c, useState as l } from "react";
|
|
9
|
+
import { RefreshCw as u, Send as d, UserPlus as f, X as re } from "lucide-react";
|
|
10
|
+
import { gql as p } from "@apollo/client";
|
|
11
|
+
import { useMutation as m, useQuery as h } from "@apollo/client/react";
|
|
12
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
13
|
+
import { Button as y, Card as b, CardContent as x, CardHeader as S, CardTitle as C, Input as w } from "@burdenoff/fe-libs/ui";
|
|
10
14
|
//#region src/planmagnet/pages/TeamInviteMemberPagePage.tsx
|
|
11
|
-
var
|
|
15
|
+
var T = p`
|
|
12
16
|
query PlanmagnetInviteRoles($scopeId: String!) {
|
|
13
17
|
roles(
|
|
14
18
|
scopeId: $scopeId
|
|
@@ -20,151 +24,207 @@ var b = c`
|
|
|
20
24
|
description
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
|
-
`,
|
|
27
|
+
`, E = p`
|
|
24
28
|
mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {
|
|
25
29
|
createInvitation(input: $input) {
|
|
26
30
|
id
|
|
27
31
|
email
|
|
28
32
|
status
|
|
29
33
|
expiresAt
|
|
34
|
+
emailStatus
|
|
35
|
+
emailLastAttemptAt
|
|
36
|
+
emailErrorCode
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
`, D = {
|
|
40
|
+
refetchQueries: [o],
|
|
41
|
+
awaitRefetchQueries: !0
|
|
42
|
+
};
|
|
43
|
+
function O() {
|
|
44
|
+
let o = te(), { workspaceId: p, currentUser: O } = e(), k = ee(), A = h(T, {
|
|
45
|
+
variables: { scopeId: p },
|
|
46
|
+
skip: !p
|
|
47
|
+
}), [ie, j] = m(E, D), [M, N] = m(ne, D), [P, F] = l(""), [I, L] = l(!1), [R, z] = l(""), [B, V] = l(null), [H, U] = l(null), W = H?.workspaceId === (p ?? null) ? H : null, [G, K] = l(null), q = c(() => A.data?.roles ?? [], [A.data]), J = c(() => new Map(q.map((e) => [e.id, e.name])), [q]), Y = q.find((e) => e.id === R) ?? null, X = r(P), Z = X && !!Y && !j.loading && !!p, Q = o("planmagnet.invite.pending.forbidden", "Your access level does not allow managing invitations in this workspace."), ae = async () => {
|
|
48
|
+
if (!(!Z || !Y)) {
|
|
49
|
+
V(null), U(null), K(null);
|
|
50
|
+
try {
|
|
51
|
+
let e = (await t(ie({ variables: { input: {
|
|
52
|
+
email: P.trim(),
|
|
53
|
+
roleIds: [Y.id],
|
|
54
|
+
workspaceId: p
|
|
55
|
+
} } }))).data?.createInvitation;
|
|
56
|
+
e && U({
|
|
57
|
+
...e,
|
|
58
|
+
workspaceId: p ?? null
|
|
59
|
+
}), F(""), L(!1);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
V(n(e, o("planmagnet.invite.errors.create", "The invitation could not be created."), Q));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, oe = async () => {
|
|
65
|
+
if (W) {
|
|
66
|
+
K(null);
|
|
67
|
+
try {
|
|
68
|
+
let e = (await t(M({ variables: { id: W.id } }))).data?.resendInvitation;
|
|
69
|
+
e && U({
|
|
70
|
+
...W,
|
|
71
|
+
emailStatus: e.emailStatus,
|
|
72
|
+
emailLastAttemptAt: e.emailLastAttemptAt,
|
|
73
|
+
emailErrorCode: e.emailErrorCode
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
K(n(e, o("planmagnet.invite.errors.resend", "The invitation email could not be resent."), Q));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, $ = W ? i(W.emailErrorCode) : null;
|
|
80
|
+
return /* @__PURE__ */ v("div", {
|
|
39
81
|
className: "max-w-3xl space-y-6 p-6",
|
|
40
82
|
children: [
|
|
41
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ v("div", {
|
|
42
84
|
className: "flex items-center justify-between",
|
|
43
|
-
children: [/* @__PURE__ */
|
|
85
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
|
|
44
86
|
className: "text-2xl font-semibold",
|
|
45
|
-
children: "Invite Team Member"
|
|
46
|
-
}), /* @__PURE__ */
|
|
87
|
+
children: o("planmagnet.invite.title", "Invite Team Member")
|
|
88
|
+
}), /* @__PURE__ */ _("p", {
|
|
47
89
|
className: "text-sm text-muted-foreground",
|
|
48
|
-
children: "Invite someone to this workspace with a PlanMagnet access level."
|
|
49
|
-
})] }), /* @__PURE__ */
|
|
90
|
+
children: o("planmagnet.invite.subtitle", "Invite someone to this workspace with a PlanMagnet access level.")
|
|
91
|
+
})] }), /* @__PURE__ */ v(y, {
|
|
50
92
|
variant: "outline",
|
|
51
93
|
size: "sm",
|
|
52
|
-
onClick: () =>
|
|
53
|
-
children: [/* @__PURE__ */
|
|
94
|
+
onClick: () => k("/team/team-page"),
|
|
95
|
+
children: [/* @__PURE__ */ _(re, { className: "mr-1 h-4 w-4" }), o("planmagnet.invite.back", "Back to team")]
|
|
54
96
|
})]
|
|
55
97
|
}),
|
|
56
|
-
|
|
98
|
+
W && a(W) && /* @__PURE__ */ _("div", {
|
|
57
99
|
role: "status",
|
|
58
100
|
className: "rounded-md border border-border bg-muted p-3 text-sm text-status-success-text",
|
|
59
101
|
"data-testid": "invite-sent",
|
|
102
|
+
children: o("planmagnet.invite.sent", "Invitation sent to {{email}}. They get this access when they accept it.", { email: W.email })
|
|
103
|
+
}),
|
|
104
|
+
W && !a(W) && $ && /* @__PURE__ */ v("div", {
|
|
105
|
+
role: "status",
|
|
106
|
+
className: "space-y-2 rounded-md border border-border bg-muted p-3 text-sm",
|
|
107
|
+
"data-testid": "invite-email-not-sent",
|
|
60
108
|
children: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
/* @__PURE__ */ _("p", {
|
|
110
|
+
className: "font-medium text-status-warning-text",
|
|
111
|
+
children: o("planmagnet.invite.emailNotSent", "Invitation created for {{email}}, but the email was not sent.", { email: W.email })
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ _("p", {
|
|
114
|
+
className: "text-muted-foreground",
|
|
115
|
+
"data-testid": "invite-email-not-sent-reason",
|
|
116
|
+
children: o($.key, $.fallback)
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ v(y, {
|
|
119
|
+
size: "sm",
|
|
120
|
+
variant: "outline",
|
|
121
|
+
onClick: () => void oe(),
|
|
122
|
+
disabled: N.loading,
|
|
123
|
+
"data-testid": "invite-resend",
|
|
124
|
+
children: [/* @__PURE__ */ _(u, { className: "mr-1 h-4 w-4" }), N.loading ? o("planmagnet.invite.resending", "Resending…") : o("planmagnet.invite.resend", "Resend email")]
|
|
125
|
+
}),
|
|
126
|
+
G && /* @__PURE__ */ _("p", {
|
|
127
|
+
role: "alert",
|
|
128
|
+
className: "text-destructive",
|
|
129
|
+
"data-testid": "invite-resend-error",
|
|
130
|
+
children: G
|
|
131
|
+
})
|
|
64
132
|
]
|
|
65
133
|
}),
|
|
66
|
-
|
|
134
|
+
B && /* @__PURE__ */ _("p", {
|
|
67
135
|
role: "alert",
|
|
68
136
|
className: "text-sm text-destructive",
|
|
69
137
|
"data-testid": "invite-error",
|
|
70
|
-
children:
|
|
138
|
+
children: B
|
|
71
139
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ v(b, { children: [/* @__PURE__ */ _(S, { children: /* @__PURE__ */ v(C, {
|
|
73
141
|
className: "flex items-center gap-2",
|
|
74
|
-
children: [/* @__PURE__ */ f
|
|
75
|
-
}) }), /* @__PURE__ */
|
|
142
|
+
children: [/* @__PURE__ */ _(f, { className: "h-4 w-4" }), o("planmagnet.invite.details", "Invitation details")]
|
|
143
|
+
}) }), /* @__PURE__ */ v(x, {
|
|
76
144
|
className: "space-y-4",
|
|
77
145
|
children: [
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ v("div", { children: [
|
|
147
|
+
/* @__PURE__ */ _("label", {
|
|
80
148
|
htmlFor: "invite-email",
|
|
81
149
|
className: "text-sm font-medium",
|
|
82
|
-
children: "Email address"
|
|
150
|
+
children: o("planmagnet.invite.emailLabel", "Email address")
|
|
83
151
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ _(w, {
|
|
85
153
|
id: "invite-email",
|
|
86
154
|
type: "email",
|
|
87
|
-
value:
|
|
155
|
+
value: P,
|
|
88
156
|
onChange: (e) => {
|
|
89
|
-
|
|
157
|
+
F(e.target.value), U(null), K(null);
|
|
90
158
|
},
|
|
91
|
-
onBlur: () =>
|
|
92
|
-
"aria-invalid":
|
|
159
|
+
onBlur: () => L(!0),
|
|
160
|
+
"aria-invalid": I && !X,
|
|
93
161
|
"aria-describedby": "invite-email-help",
|
|
94
162
|
className: "mt-1",
|
|
95
163
|
placeholder: "colleague@company.com"
|
|
96
164
|
}),
|
|
97
|
-
|
|
165
|
+
I && P.trim() !== "" && !X ? /* @__PURE__ */ _("p", {
|
|
98
166
|
id: "invite-email-help",
|
|
99
167
|
role: "alert",
|
|
100
168
|
className: "mt-1 text-xs text-destructive",
|
|
101
|
-
children: "Enter a full email address, like name@company.com."
|
|
102
|
-
}) : /* @__PURE__ */
|
|
169
|
+
children: o("planmagnet.invite.emailInvalid", "Enter a full email address, like name@company.com.")
|
|
170
|
+
}) : /* @__PURE__ */ _("p", {
|
|
103
171
|
id: "invite-email-help",
|
|
104
172
|
className: "mt-1 text-xs text-muted-foreground",
|
|
105
|
-
children: "
|
|
173
|
+
children: o("planmagnet.invite.emailHelp", "An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.")
|
|
106
174
|
})
|
|
107
175
|
] }),
|
|
108
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
109
177
|
htmlFor: "invite-role",
|
|
110
178
|
className: "text-sm font-medium",
|
|
111
|
-
children: "Access level"
|
|
112
|
-
}),
|
|
179
|
+
children: o("planmagnet.invite.roleLabel", "Access level")
|
|
180
|
+
}), A.loading && !A.data ? /* @__PURE__ */ _("p", {
|
|
113
181
|
className: "mt-1 text-sm text-muted-foreground",
|
|
114
|
-
children: "Loading access levels…"
|
|
115
|
-
}) :
|
|
182
|
+
children: o("planmagnet.invite.rolesLoading", "Loading access levels…")
|
|
183
|
+
}) : A.error ? /* @__PURE__ */ _("p", {
|
|
116
184
|
role: "alert",
|
|
117
185
|
className: "mt-1 text-sm text-destructive",
|
|
118
|
-
children:
|
|
119
|
-
}) :
|
|
186
|
+
children: o("planmagnet.invite.rolesFailed", "Access levels could not be loaded: {{reason}}", { reason: A.error.message })
|
|
187
|
+
}) : q.length === 0 ? /* @__PURE__ */ _("p", {
|
|
120
188
|
className: "mt-1 text-sm text-muted-foreground",
|
|
121
|
-
children: "No PlanMagnet access levels are set up for this workspace yet."
|
|
122
|
-
}) : /* @__PURE__ */
|
|
189
|
+
children: o("planmagnet.invite.rolesNone", "No PlanMagnet access levels are set up for this workspace yet.")
|
|
190
|
+
}) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ v("select", {
|
|
123
191
|
id: "invite-role",
|
|
124
192
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
125
|
-
value:
|
|
126
|
-
onChange: (e) =>
|
|
193
|
+
value: R,
|
|
194
|
+
onChange: (e) => z(e.target.value),
|
|
127
195
|
"data-testid": "invite-role",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ _("option", {
|
|
129
197
|
value: "",
|
|
130
|
-
children: "Choose an access level"
|
|
131
|
-
}),
|
|
198
|
+
children: o("planmagnet.invite.rolePlaceholder", "Choose an access level")
|
|
199
|
+
}), q.map((e) => /* @__PURE__ */ _("option", {
|
|
132
200
|
value: e.id,
|
|
133
201
|
children: e.name
|
|
134
202
|
}, e.id))]
|
|
135
|
-
}),
|
|
203
|
+
}), Y?.description && /* @__PURE__ */ _("p", {
|
|
136
204
|
className: "mt-1 text-xs text-muted-foreground",
|
|
137
|
-
children:
|
|
205
|
+
children: Y.description
|
|
138
206
|
})] })] }),
|
|
139
|
-
/* @__PURE__ */
|
|
207
|
+
/* @__PURE__ */ _("div", {
|
|
140
208
|
className: "flex justify-end",
|
|
141
|
-
children: /* @__PURE__ */
|
|
209
|
+
children: /* @__PURE__ */ v(y, {
|
|
142
210
|
size: "sm",
|
|
143
|
-
onClick:
|
|
144
|
-
|
|
145
|
-
N(null), F(null);
|
|
146
|
-
try {
|
|
147
|
-
F((await n(w({ variables: { input: {
|
|
148
|
-
email: E.trim(),
|
|
149
|
-
roleIds: [L.id],
|
|
150
|
-
workspaceId: c
|
|
151
|
-
} } }))).data?.createInvitation.email ?? E.trim()), D(""), k(!1);
|
|
152
|
-
} catch (e) {
|
|
153
|
-
N(e instanceof Error ? e.message : "The invitation could not be sent.");
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
disabled: !z,
|
|
211
|
+
onClick: ae,
|
|
212
|
+
disabled: !Z,
|
|
158
213
|
"data-testid": "invite-send",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
214
|
+
children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), j.loading ? o("planmagnet.invite.sending", "Sending…") : o("planmagnet.invite.send", "Send invite")]
|
|
160
215
|
})
|
|
161
216
|
})
|
|
162
217
|
]
|
|
163
|
-
})] })
|
|
218
|
+
})] }),
|
|
219
|
+
/* @__PURE__ */ _(s, {
|
|
220
|
+
workspaceId: p,
|
|
221
|
+
currentUserId: O?.id,
|
|
222
|
+
roleNames: J
|
|
223
|
+
}, p ?? "no-workspace")
|
|
164
224
|
]
|
|
165
225
|
});
|
|
166
226
|
}
|
|
167
227
|
//#endregion
|
|
168
|
-
export {
|
|
228
|
+
export { O as TeamInviteMemberPagePage };
|
|
169
229
|
|
|
170
230
|
//# sourceMappingURL=TeamInviteMemberPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamInviteMemberPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { Send, UserPlus, X } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isValidInviteEmail } from '../utils/email';\n\n/**\n * Invite a member (H12, BOFF-7833). The previous page offered job titles (developer, designer, …) in its\n * \"Role\" picker plus invented Department and Team pickers, none of which any backend accepts, and its Send\n * button was disabled on the belief that no invite mutation existed. The platform's createInvitation does\n * exist, validates the email and the role ids, and assigns the roles when the invitee accepts. The access\n * levels offered here are PlanMagnet's own RBAC roles for this workspace, as the platform returns them.\n */\n\nconst INVITE_ROLES = gql`\n query PlanmagnetInviteRoles($scopeId: String!) {\n roles(\n scopeId: $scopeId\n filter: { product: \"planmagnet\", includePlatformRoles: false }\n pagination: { limit: 50 }\n ) {\n id\n name\n description\n }\n }\n`;\n\nconst CREATE_INVITATION = gql`\n mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {\n createInvitation(input: $input) {\n id\n email\n status\n expiresAt\n }\n }\n`;\n\ninterface InviteRole {\n id: string;\n name: string;\n description: string | null;\n}\n\nexport function TeamInviteMemberPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const roles = useQuery<{ roles: InviteRole[] }>(INVITE_ROLES, {\n variables: { scopeId: workspaceId },\n skip: !workspaceId,\n });\n const [invite, inviteState] = useMutation<{\n createInvitation: { id: string; email: string; status: string; expiresAt: string | null };\n }>(CREATE_INVITATION);\n\n const [email, setEmail] = useState('');\n const [emailTouched, setEmailTouched] = useState(false);\n const [roleId, setRoleId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [sentTo, setSentTo] = useState<string | null>(null);\n\n const available = roles.data?.roles ?? [];\n const chosen = available.find((r) => r.id === roleId) ?? null;\n const emailOk = isValidInviteEmail(email);\n const canSend = emailOk && !!chosen && !inviteState.loading && !!workspaceId;\n\n const send = async () => {\n if (!canSend || !chosen) return;\n setError(null);\n setSentTo(null);\n try {\n const result = await throwOnError(\n invite({ variables: { input: { email: email.trim(), roleIds: [chosen.id], workspaceId } } })\n );\n setSentTo(result.data?.createInvitation.email ?? email.trim());\n setEmail('');\n setEmailTouched(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The invitation could not be sent.');\n }\n };\n\n return (\n <div className=\"max-w-3xl space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Invite Team Member</h1>\n <p className=\"text-sm text-muted-foreground\">\n Invite someone to this workspace with a PlanMagnet access level.\n </p>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Back to team\n </Button>\n </div>\n\n {sentTo && (\n <div\n role=\"status\"\n className=\"rounded-md border border-border bg-muted p-3 text-sm text-status-success-text\"\n data-testid=\"invite-sent\"\n >\n Invitation sent to {sentTo}. They get this access when they accept it.\n </div>\n )}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"invite-error\">\n {error}\n </p>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <UserPlus className=\"h-4 w-4\" />\n Invitation details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"invite-email\" className=\"text-sm font-medium\">\n Email address\n </label>\n <Input\n id=\"invite-email\"\n type=\"email\"\n value={email}\n onChange={(e) => {\n setEmail(e.target.value);\n setSentTo(null);\n }}\n onBlur={() => setEmailTouched(true)}\n aria-invalid={emailTouched && !emailOk}\n aria-describedby=\"invite-email-help\"\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n {emailTouched && email.trim() !== '' && !emailOk ? (\n <p id=\"invite-email-help\" role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n Enter a full email address, like name@company.com.\n </p>\n ) : (\n <p id=\"invite-email-help\" className=\"mt-1 text-xs text-muted-foreground\">\n They receive an email with a link to accept.\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"invite-role\" className=\"text-sm font-medium\">\n Access level\n </label>\n {roles.loading && !roles.data ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">Loading access levels…</p>\n ) : roles.error ? (\n <p role=\"alert\" className=\"mt-1 text-sm text-destructive\">\n Access levels could not be loaded: {roles.error.message}\n </p>\n ) : available.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n No PlanMagnet access levels are set up for this workspace yet.\n </p>\n ) : (\n <>\n <select\n id=\"invite-role\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={roleId}\n onChange={(e) => setRoleId(e.target.value)}\n data-testid=\"invite-role\"\n >\n <option value=\"\">Choose an access level</option>\n {available.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n {chosen?.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{chosen.description}</p>\n )}\n </>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <Button size=\"sm\" onClick={send} disabled={!canSend} data-testid=\"invite-send\">\n <Send className=\"mr-1 h-4 w-4\" />\n {inviteState.loading ? 'Sending…' : 'Send invite'}\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamInviteMemberPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { RefreshCw, Send, UserPlus, X } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { isValidInviteEmail } from '../utils/email';\nimport { refusalMessage } from '../product/refusal';\nimport {\n PENDING_INVITATIONS_QUERY,\n PendingInvitations,\n RESEND_INVITATION,\n type InvitationEmailResult,\n} from '../team/PendingInvitations';\nimport {\n invitationEmailReason,\n invitationEmailSent,\n type InvitationEmailFields,\n} from '../team/invitationEmail';\n\n/**\n * Invite a member (H12, BOFF-7833). The previous page offered job titles (developer, designer, …) in its\n * \"Role\" picker plus invented Department and Team pickers, none of which any backend accepts, and its Send\n * button was disabled on the belief that no invite mutation existed. The platform's createInvitation does\n * exist, validates the email and the role ids, and assigns the roles when the invitee accepts. The access\n * levels offered here are PlanMagnet's own RBAC roles for this workspace, as the platform returns them.\n *\n * BOFF-8866: creating an invitation does not mean its email went out. The service records the email outcome on\n * the invitation (emailStatus / emailErrorCode); the page says \"sent\" only for SENT, otherwise that the invitation\n * exists but the email was not sent, why, and offers Resend. The pending invitations are listed below the form.\n */\n\nconst INVITE_ROLES = gql`\n query PlanmagnetInviteRoles($scopeId: String!) {\n roles(\n scopeId: $scopeId\n filter: { product: \"planmagnet\", includePlatformRoles: false }\n pagination: { limit: 50 }\n ) {\n id\n name\n description\n }\n }\n`;\n\nconst CREATE_INVITATION = gql`\n mutation PlanmagnetInviteMember($input: CreateInvitationInput!) {\n createInvitation(input: $input) {\n id\n email\n status\n expiresAt\n emailStatus\n emailLastAttemptAt\n emailErrorCode\n }\n }\n`;\n\ninterface InviteRole {\n id: string;\n name: string;\n description: string | null;\n}\n\ninterface CreatedInvitation extends InvitationEmailFields {\n id: string;\n email: string;\n /** The workspace it was created in; its Resend is only valid there (the write is scoped by header). */\n workspaceId: string | null;\n}\n\nconst WRITE_OPTIONS = { refetchQueries: [PENDING_INVITATIONS_QUERY], awaitRefetchQueries: true };\n\nexport function TeamInviteMemberPagePage() {\n const tr = useTr();\n const { workspaceId, currentUser } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const roles = useQuery<{ roles: InviteRole[] }>(INVITE_ROLES, {\n variables: { scopeId: workspaceId },\n skip: !workspaceId,\n });\n const [invite, inviteState] = useMutation<{\n createInvitation: InvitationEmailResult & { email: string };\n }>(CREATE_INVITATION, WRITE_OPTIONS);\n const [resend, resendState] = useMutation<{ resendInvitation: InvitationEmailResult }>(\n RESEND_INVITATION,\n WRITE_OPTIONS\n );\n\n const [email, setEmail] = useState('');\n const [emailTouched, setEmailTouched] = useState(false);\n const [roleId, setRoleId] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [createdState, setCreated] = useState<CreatedInvitation | null>(null);\n const created = createdState?.workspaceId === (workspaceId ?? null) ? createdState : null;\n const [resendError, setResendError] = useState<string | null>(null);\n\n const available = useMemo(() => roles.data?.roles ?? [], [roles.data]);\n const roleNames = useMemo(() => new Map(available.map((r) => [r.id, r.name])), [available]);\n const chosen = available.find((r) => r.id === roleId) ?? null;\n const emailOk = isValidInviteEmail(email);\n const canSend = emailOk && !!chosen && !inviteState.loading && !!workspaceId;\n const forbidden = tr(\n 'planmagnet.invite.pending.forbidden',\n 'Your access level does not allow managing invitations in this workspace.'\n );\n\n const send = async () => {\n if (!canSend || !chosen) return;\n setError(null);\n setCreated(null);\n setResendError(null);\n try {\n const result = await throwOnError(\n invite({ variables: { input: { email: email.trim(), roleIds: [chosen.id], workspaceId } } })\n );\n const invitation = result.data?.createInvitation;\n if (invitation) setCreated({ ...invitation, workspaceId: workspaceId ?? null });\n setEmail('');\n setEmailTouched(false);\n } catch (e) {\n setError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.create', 'The invitation could not be created.'),\n forbidden\n )\n );\n }\n };\n\n const resendCreated = async () => {\n if (!created) return;\n setResendError(null);\n try {\n const result = await throwOnError(resend({ variables: { id: created.id } }));\n const updated = result.data?.resendInvitation;\n if (updated) {\n setCreated({\n ...created,\n emailStatus: updated.emailStatus,\n emailLastAttemptAt: updated.emailLastAttemptAt,\n emailErrorCode: updated.emailErrorCode,\n });\n }\n } catch (e) {\n setResendError(\n refusalMessage(\n e,\n tr('planmagnet.invite.errors.resend', 'The invitation email could not be resent.'),\n forbidden\n )\n );\n }\n };\n\n const notSentReason = created ? invitationEmailReason(created.emailErrorCode) : null;\n\n return (\n <div className=\"max-w-3xl space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {tr('planmagnet.invite.title', 'Invite Team Member')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.subtitle',\n 'Invite someone to this workspace with a PlanMagnet access level.'\n )}\n </p>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n {tr('planmagnet.invite.back', 'Back to team')}\n </Button>\n </div>\n\n {created && invitationEmailSent(created) && (\n <div\n role=\"status\"\n className=\"rounded-md border border-border bg-muted p-3 text-sm text-status-success-text\"\n data-testid=\"invite-sent\"\n >\n {tr(\n 'planmagnet.invite.sent',\n 'Invitation sent to {{email}}. They get this access when they accept it.',\n { email: created.email }\n )}\n </div>\n )}\n {created && !invitationEmailSent(created) && notSentReason && (\n <div\n role=\"status\"\n className=\"space-y-2 rounded-md border border-border bg-muted p-3 text-sm\"\n data-testid=\"invite-email-not-sent\"\n >\n <p className=\"font-medium text-status-warning-text\">\n {tr(\n 'planmagnet.invite.emailNotSent',\n 'Invitation created for {{email}}, but the email was not sent.',\n { email: created.email }\n )}\n </p>\n <p className=\"text-muted-foreground\" data-testid=\"invite-email-not-sent-reason\">\n {tr(notSentReason.key, notSentReason.fallback)}\n </p>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void resendCreated()}\n disabled={resendState.loading}\n data-testid=\"invite-resend\"\n >\n <RefreshCw className=\"mr-1 h-4 w-4\" />\n {resendState.loading\n ? tr('planmagnet.invite.resending', 'Resending…')\n : tr('planmagnet.invite.resend', 'Resend email')}\n </Button>\n {resendError && (\n <p role=\"alert\" className=\"text-destructive\" data-testid=\"invite-resend-error\">\n {resendError}\n </p>\n )}\n </div>\n )}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"invite-error\">\n {error}\n </p>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <UserPlus className=\"h-4 w-4\" />\n {tr('planmagnet.invite.details', 'Invitation details')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label htmlFor=\"invite-email\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.emailLabel', 'Email address')}\n </label>\n <Input\n id=\"invite-email\"\n type=\"email\"\n value={email}\n onChange={(e) => {\n setEmail(e.target.value);\n setCreated(null);\n setResendError(null);\n }}\n onBlur={() => setEmailTouched(true)}\n aria-invalid={emailTouched && !emailOk}\n aria-describedby=\"invite-email-help\"\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n {emailTouched && email.trim() !== '' && !emailOk ? (\n <p id=\"invite-email-help\" role=\"alert\" className=\"mt-1 text-xs text-destructive\">\n {tr(\n 'planmagnet.invite.emailInvalid',\n 'Enter a full email address, like name@company.com.'\n )}\n </p>\n ) : (\n <p id=\"invite-email-help\" className=\"mt-1 text-xs text-muted-foreground\">\n {tr(\n 'planmagnet.invite.emailHelp',\n 'An invitation email with a link to accept is sent when possible. Pending invitations can be resent below.'\n )}\n </p>\n )}\n </div>\n\n <div>\n <label htmlFor=\"invite-role\" className=\"text-sm font-medium\">\n {tr('planmagnet.invite.roleLabel', 'Access level')}\n </label>\n {roles.loading && !roles.data ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr('planmagnet.invite.rolesLoading', 'Loading access levels…')}\n </p>\n ) : roles.error ? (\n <p role=\"alert\" className=\"mt-1 text-sm text-destructive\">\n {tr(\n 'planmagnet.invite.rolesFailed',\n 'Access levels could not be loaded: {{reason}}',\n {\n reason: roles.error.message,\n }\n )}\n </p>\n ) : available.length === 0 ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.invite.rolesNone',\n 'No PlanMagnet access levels are set up for this workspace yet.'\n )}\n </p>\n ) : (\n <>\n <select\n id=\"invite-role\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={roleId}\n onChange={(e) => setRoleId(e.target.value)}\n data-testid=\"invite-role\"\n >\n <option value=\"\">\n {tr('planmagnet.invite.rolePlaceholder', 'Choose an access level')}\n </option>\n {available.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n {chosen?.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{chosen.description}</p>\n )}\n </>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <Button size=\"sm\" onClick={send} disabled={!canSend} data-testid=\"invite-send\">\n <Send className=\"mr-1 h-4 w-4\" />\n {inviteState.loading\n ? tr('planmagnet.invite.sending', 'Sending…')\n : tr('planmagnet.invite.send', 'Send invite')}\n </Button>\n </div>\n </CardContent>\n </Card>\n\n <PendingInvitations\n key={workspaceId ?? 'no-workspace'}\n workspaceId={workspaceId}\n currentUserId={currentUser?.id}\n roleNames={roleNames}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeliveryKpisCard.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DeliveryKpisCard.d.ts","sourceRoot":"","sources":["../../../../src/planmagnet/pages/analytics/DeliveryKpisCard.tsx"],"names":[],"mappings":"AAgHA,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,SAAS,EACT,SAAS,EACT,OAAO,GACR,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,2CA0IA"}
|