@burdenoff/microfe-planmagnet 2026.927.1 → 2026.927.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 (87) hide show
  1. package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +128 -124
  3. package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
  4. package/dist/planmagnet/controls/ConfirmButton.d.ts +25 -0
  5. package/dist/planmagnet/controls/ConfirmButton.d.ts.map +1 -0
  6. package/dist/planmagnet/controls/ConfirmButton.js +30 -0
  7. package/dist/planmagnet/controls/ConfirmButton.js.map +1 -0
  8. package/dist/planmagnet/controls/ProjectControls.d.ts +13 -0
  9. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -0
  10. package/dist/planmagnet/controls/ProjectControls.js +104 -0
  11. package/dist/planmagnet/controls/ProjectControls.js.map +1 -0
  12. package/dist/planmagnet/controls/WorkItemControls.d.ts +52 -0
  13. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -0
  14. package/dist/planmagnet/controls/WorkItemControls.js +198 -0
  15. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -0
  16. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  17. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +14 -4
  18. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  19. package/dist/planmagnet/pages/DocsDocDetailPagePage.js +4 -4
  20. package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
  21. package/dist/planmagnet/pages/DocsDocsPagePage.js +4 -4
  22. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  23. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +102 -100
  24. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  25. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  26. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +95 -94
  27. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  28. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  29. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +97 -90
  30. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  31. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  32. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +150 -111
  33. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  34. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  35. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +121 -94
  36. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  37. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
  38. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +84 -73
  39. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
  40. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
  41. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +13 -8
  42. package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
  43. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  44. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +129 -125
  45. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  46. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
  47. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +112 -109
  48. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  50. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +134 -132
  51. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  52. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  53. package/dist/planmagnet/pages/ReportsReportsPagePage.js +72 -83
  54. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  55. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  56. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +109 -108
  57. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  58. package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
  59. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +167 -111
  60. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
  61. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  62. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +101 -99
  63. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  64. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  65. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +149 -147
  66. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  67. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  68. package/dist/planmagnet/pages/TasksTasksPagePage.js +4 -8
  69. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  70. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  71. package/dist/planmagnet/pages/TeamTeamPagePage.js +68 -69
  72. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  73. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.d.ts.map +1 -1
  74. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js +1 -5
  75. package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js.map +1 -1
  76. package/dist/planmagnet/people/People.d.ts +18 -0
  77. package/dist/planmagnet/people/People.d.ts.map +1 -1
  78. package/dist/planmagnet/people/People.js +26 -12
  79. package/dist/planmagnet/people/People.js.map +1 -1
  80. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts +34 -0
  81. package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -0
  82. package/dist/planmagnet/roadmaps/RoadmapControls.js +301 -0
  83. package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -0
  84. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  85. package/dist/planmagnet/sprints/SprintControls.js +194 -215
  86. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  87. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PlanMagnetAdminRoot.js","names":[],"sources":["../../../src/planmagnet/admin/PlanMagnetAdminRoot.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AlertCircle, Archive, FolderKanban, LayoutTemplate, Loader2, Zap } from 'lucide-react';\n\nimport { PlanMagnetProvider, usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { cn } from '../utils/cn';\nimport type { PlanMagnetRootProps } from '../types';\nimport { PersonName } from '../people/People';\n\n// ------------------------------------------------------------------------------\n// Domain types (mirror the planmagnet subgraph until generated types are fixed)\n// ------------------------------------------------------------------------------\n\ntype ProjectStatus = 'ACTIVE' | 'ARCHIVED' | 'CANCELLED' | 'COMPLETED' | 'ON_HOLD' | 'PLANNING';\n\ninterface AdminProject {\n id: string;\n name: string;\n key: string;\n status: ProjectStatus;\n methodology: string;\n ownerId: string;\n createdAt: string;\n}\n\ninterface AdminBoard {\n id: string;\n name: string;\n type: string;\n projectId?: string | null;\n}\n\ninterface AdminAutomationRule {\n id: string;\n name: string;\n status: string;\n triggerType: string;\n executionCount: number;\n lastExecutedAt?: string | null;\n createdAt: string;\n}\n\n// ------------------------------------------------------------------------------\n// GraphQL documents (inline so the admin surface does not depend on generated\n// hooks while the rest of the MFE's operation files are being reconciled with\n// the schema).\n// ------------------------------------------------------------------------------\n\nconst ADMIN_PROJECTS_QUERY = gql`\n query AdminPlanmagnetProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n key\n status\n methodology\n ownerId\n createdAt\n }\n totalCount\n }\n }\n`;\n\nconst ADMIN_BOARDS_QUERY = gql`\n query AdminPlanmagnetBoards($workspaceId: ID!) {\n planmagnetBoards(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n type\n projectId\n }\n totalCount\n }\n }\n`;\n\nconst ADMIN_AUTOMATION_RULES_QUERY = gql`\n query AdminPlanmagnetAutomationRules($workspaceId: ID!) {\n planmagnetAutomationRules(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n status\n triggerType\n executionCount\n lastExecutedAt\n createdAt\n }\n totalCount\n }\n }\n`;\n\nconst ARCHIVE_PROJECT_MUTATION = gql`\n mutation AdminArchiveProject($id: ID!) {\n planmagnetUpdateProject(id: $id, input: { status: ARCHIVED }) {\n id\n status\n }\n }\n`;\n\n// ------------------------------------------------------------------------------\n// Presentation helpers\n// ------------------------------------------------------------------------------\n\nfunction projectStatusVariantr(status: ProjectStatus) {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'ARCHIVED':\n return 'secondary';\n case 'CANCELLED':\n return 'destructive';\n case 'COMPLETED':\n return 'info';\n case 'ON_HOLD':\n return 'warning';\n case 'PLANNING':\n return 'outline';\n default:\n return 'default';\n }\n}\n\nfunction automationStatusVariantr(status: string) {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'PAUSED':\n return 'warning';\n case 'ERROR':\n return 'destructive';\n case 'DISABLED':\n return 'secondary';\n default:\n return 'default';\n }\n}\n\nfunction formatDate(value: string | null | undefined) {\n if (!value) return '—';\n try {\n return new Date(value).toLocaleDateString();\n } catch {\n return value;\n }\n}\n\nfunction localizeMethodology(value: string) {\n return value.toLowerCase().replace(/_/g, ' ');\n}\n\nfunction localizeTrigger(value: string) {\n return value.toLowerCase().replace(/_/g, ' ');\n}\n\n// ------------------------------------------------------------------------------\n// Sub-components\n// ------------------------------------------------------------------------------\n\nfunction QuotaCard({\n icon: Icon,\n label,\n used,\n limit,\n className,\n}: {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n used: number;\n limit: number;\n className?: string;\n}) {\n const percentage = Math.min(100, Math.round((used / Math.max(1, limit)) * 100));\n\n return (\n <Card className={className}>\n <CardContent className=\"pt-6\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Icon className=\"h-4 w-4\" />\n <span className=\"text-sm\">{label}</span>\n </div>\n <div className=\"mt-2 flex items-baseline gap-2\">\n <span className=\"text-2xl font-semibold\">{used}</span>\n <span className=\"text-sm text-muted-foreground\">/ {limit}</span>\n </div>\n <div className=\"mt-3 h-2 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\n 'h-full rounded-full transition-all',\n percentage >= 90 ? 'bg-destructive' : percentage >= 70 ? 'bg-warning' : 'bg-success'\n )}\n style={{ width: `${percentage}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-muted-foreground\">{percentage}% used</p>\n </CardContent>\n </Card>\n );\n}\n\nfunction SectionLoader() {\n return (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-8 w-1/3\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n );\n}\n\ninterface SimpleTableColumn<T> {\n key: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n className?: string;\n}\n\nfunction SimpleTable<T extends { id: string }>({\n columns,\n data,\n emptyState,\n}: {\n columns: SimpleTableColumn<T>[];\n data: T[];\n emptyState: React.ReactNode;\n}) {\n if (data.length === 0) {\n return <div className=\"py-8 text-center text-sm text-muted-foreground\">{emptyState}</div>;\n }\n\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50\">\n <tr>\n {columns.map((column) => (\n <th\n key={column.key}\n className={cn(\n 'px-4 py-3 text-left font-medium text-muted-foreground',\n column.className\n )}\n >\n {column.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y\">\n {data.map((row) => (\n <tr key={row.id} className=\"hover:bg-muted/30\">\n {columns.map((column) => (\n <td key={`${row.id}-${column.key}`} className={cn('px-4 py-3', column.className)}>\n {column.cell(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n// ------------------------------------------------------------------------------\n// Main dashboard\n// ------------------------------------------------------------------------------\n\nfunction PlanMagnetAdminDashboard() {\n const { workspaceId } = usePlanMagnet();\n const tr = useTr();\n\n const [selectedProjectIds, setSelectedProjectIds] = useState<Set<string>>(new Set());\n const [archiving, setArchiving] = useState(false);\n const [archiveError, setArchiveError] = useState<string | null>(null);\n\n const projectsQuery = useQuery<{\n planmagnetProjects: { items: AdminProject[]; totalCount: number };\n }>(ADMIN_PROJECTS_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const boardsQuery = useQuery<{\n planmagnetBoards: { items: AdminBoard[]; totalCount: number };\n }>(ADMIN_BOARDS_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const rulesQuery = useQuery<{\n planmagnetAutomationRules: { items: AdminAutomationRule[]; totalCount: number };\n }>(ADMIN_AUTOMATION_RULES_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const [archiveProject] = useMutation(ARCHIVE_PROJECT_MUTATION);\n\n const projects = projectsQuery.data?.planmagnetProjects.items ?? [];\n const boards = boardsQuery.data?.planmagnetBoards.items ?? [];\n const rules = rulesQuery.data?.planmagnetAutomationRules.items ?? [];\n\n const activeProjects = useMemo(\n () => projects.filter((project) => project.status === 'ACTIVE'),\n [projects]\n );\n\n const toggleProjectSelection = (id: string) => {\n setSelectedProjectIds((previous) => {\n const next = new Set(previous);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n };\n\n const toggleAll = () => {\n const selectable = activeProjects.filter((project) => project.status !== 'ARCHIVED');\n if (selectedProjectIds.size === selectable.length) {\n setSelectedProjectIds(new Set());\n } else {\n setSelectedProjectIds(new Set(selectable.map((project) => project.id)));\n }\n };\n\n const handleBulkArchive = async () => {\n if (selectedProjectIds.size === 0) return;\n setArchiving(true);\n setArchiveError(null);\n try {\n await Promise.all(\n Array.from(selectedProjectIds).map((id) => archiveProject({ variables: { id } }))\n );\n await projectsQuery.refetch();\n setSelectedProjectIds(new Set());\n } catch (error) {\n setArchiveError(\n error instanceof Error\n ? error.message\n : tr('planmagnet.admin.archive.unknownError', 'Bulk archive failed')\n );\n } finally {\n setArchiving(false);\n }\n };\n\n if (!workspaceId) {\n return (\n <div className=\"min-h-screen bg-background\">\n <div className=\"flex min-h-[320px] flex-col items-center justify-center gap-3 text-muted-foreground\">\n <AlertCircle className=\"h-8 w-8\" />\n <p>\n {tr(\n 'planmagnet.admin.noWorkspace',\n 'Select a workspace to manage PlanMagnet admin settings.'\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const isLoading = projectsQuery.loading || boardsQuery.loading || rulesQuery.loading;\n\n return (\n <div className=\"min-h-screen bg-background\">\n <div className=\"mx-auto max-w-7xl space-y-6 p-6\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n {tr('planmagnet.admin.title', 'PlanMagnet Admin')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.purpose',\n 'Workspace quota overview, bulk project archive, and automation rule audit.'\n )}\n </p>\n </div>\n </div>\n\n {/* Quota overview — counts are real; limits are placeholder until a\n dedicated admin quota query exists in the backend. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <QuotaCard\n icon={FolderKanban}\n label={tr('planmagnet.admin.quota.projects', 'Projects')}\n used={projects.length}\n limit={50}\n />\n <QuotaCard\n icon={LayoutTemplate}\n label={tr('planmagnet.admin.quota.boards', 'Boards')}\n used={boards.length}\n limit={100}\n />\n <QuotaCard\n icon={Zap}\n label={tr('planmagnet.admin.quota.automationRules', 'Automation Rules')}\n used={rules.length}\n limit={25}\n />\n </div>\n\n {isLoading ? (\n <SectionLoader />\n ) : (\n <>\n {/* Bulk project archive */}\n <Card>\n <CardHeader className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <CardTitle>\n {tr('planmagnet.admin.archive.title', 'Bulk Project Archive')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.archive.subtitle',\n 'Select active projects and archive them in one action.'\n )}\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={selectedProjectIds.size === 0 || archiving}\n onClick={handleBulkArchive}\n >\n {archiving ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"mr-2 h-4 w-4\" />\n )}\n {tr('planmagnet.admin.archive.action', 'Archive')} ({selectedProjectIds.size})\n </Button>\n </CardHeader>\n <CardContent>\n {archiveError && (\n <div className=\"mb-4 flex items-center gap-2 rounded-md bg-destructive/10 p-3 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4\" />\n {archiveError}\n </div>\n )}\n <div className=\"rounded-md border\">\n <SimpleTable<AdminProject>\n data={activeProjects}\n columns={[\n {\n key: 'select',\n header: (\n <Checkbox\n checked={\n activeProjects.length > 0 &&\n selectedProjectIds.size === activeProjects.length\n }\n onCheckedChange={toggleAll}\n aria-label={tr(\n 'planmagnet.admin.archive.selectAll',\n 'Select all active projects'\n )}\n />\n ),\n cell: (row) => (\n <Checkbox\n checked={selectedProjectIds.has(row.id)}\n onCheckedChange={() => toggleProjectSelection(row.id)}\n aria-label={tr(\n 'planmagnet.admin.archive.selectProject',\n 'Select project'\n )}\n />\n ),\n className: 'w-12',\n },\n {\n key: 'name',\n header: tr('planmagnet.admin.archive.column.name', 'Project'),\n cell: (row) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.name}</span>\n <span className=\"text-xs text-muted-foreground font-mono\">\n {row.key}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: tr('planmagnet.admin.archive.column.status', 'Status'),\n cell: (row) => (\n <Badge variant={projectStatusVariantr(row.status)}>\n {row.status.toLowerCase().replace('_', ' ')}\n </Badge>\n ),\n },\n {\n key: 'methodology',\n header: tr('planmagnet.admin.archive.column.methodology', 'Methodology'),\n cell: (row) => (\n <span className=\"text-sm capitalize\">\n {localizeMethodology(row.methodology)}\n </span>\n ),\n },\n {\n key: 'owner',\n header: tr('planmagnet.admin.archive.column.owner', 'Owner'),\n cell: (row) => (\n <span className=\"text-sm text-muted-foreground font-mono\">\n <PersonName id={row.ownerId} />\n </span>\n ),\n },\n {\n key: 'created',\n header: tr('planmagnet.admin.archive.column.created', 'Created'),\n cell: (row) => <span className=\"text-sm\">{formatDate(row.createdAt)}</span>,\n },\n ]}\n emptyState={tr(\n 'planmagnet.admin.archive.empty',\n 'No active projects to archive.'\n )}\n />\n </div>\n </CardContent>\n </Card>\n\n {/* Automation rule audit */}\n <Card>\n <CardHeader>\n <CardTitle>\n {tr('planmagnet.admin.automation.title', 'Automation Rule Audit')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.automation.subtitle',\n 'Review rule health, execution counts, and last run timestamps.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n <div className=\"rounded-md border\">\n <SimpleTable<AdminAutomationRule>\n data={rules}\n columns={[\n {\n key: 'name',\n header: tr('planmagnet.admin.automation.column.name', 'Rule'),\n cell: (row) => <span className=\"font-medium\">{row.name}</span>,\n },\n {\n key: 'trigger',\n header: tr('planmagnet.admin.automation.column.trigger', 'Trigger'),\n cell: (row) => (\n <span className=\"text-sm\">{localizeTrigger(row.triggerType)}</span>\n ),\n },\n {\n key: 'status',\n header: tr('planmagnet.admin.automation.column.status', 'Status'),\n cell: (row) => (\n <Badge variant={automationStatusVariantr(row.status)}>\n {row.status.toLowerCase()}\n </Badge>\n ),\n },\n {\n key: 'executions',\n header: tr('planmagnet.admin.automation.column.executions', 'Executions'),\n cell: (row) => <span className=\"text-sm\">{row.executionCount}</span>,\n },\n {\n key: 'lastRun',\n header: tr('planmagnet.admin.automation.column.lastRun', 'Last Run'),\n cell: (row) => (\n <span className=\"text-sm\">{formatDate(row.lastExecutedAt)}</span>\n ),\n },\n {\n key: 'created',\n header: tr('planmagnet.admin.automation.column.created', 'Created'),\n cell: (row) => <span className=\"text-sm\">{formatDate(row.createdAt)}</span>,\n },\n ]}\n emptyState={tr(\n 'planmagnet.admin.automation.empty',\n 'No automation rules found.'\n )}\n />\n </div>\n </CardContent>\n </Card>\n </>\n )}\n </div>\n </div>\n );\n}\n\n// ------------------------------------------------------------------------------\n// Public root export consumed by the admin app shell\n// ------------------------------------------------------------------------------\n\nexport function PlanMagnetAdminRoot(props: PlanMagnetRootProps) {\n return (\n <PlanMagnetProvider {...props}>\n <PlanMagnetAdminDashboard />\n </PlanMagnetProvider>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlanMagnetAdminRoot.js","names":[],"sources":["../../../src/planmagnet/admin/PlanMagnetAdminRoot.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AlertCircle, Archive, FolderKanban, LayoutTemplate, Loader2, Zap } from 'lucide-react';\n\nimport { PlanMagnetProvider, usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { cn } from '../utils/cn';\nimport { throwOnError } from '../utils/mutationResult';\nimport type { PlanMagnetRootProps } from '../types';\nimport { PersonName } from '../people/People';\n\n// ------------------------------------------------------------------------------\n// Domain types (mirror the planmagnet subgraph until generated types are fixed)\n// ------------------------------------------------------------------------------\n\ntype ProjectStatus = 'ACTIVE' | 'ARCHIVED' | 'CANCELLED' | 'COMPLETED' | 'ON_HOLD' | 'PLANNING';\n\ninterface AdminProject {\n id: string;\n name: string;\n key: string;\n status: ProjectStatus;\n methodology: string;\n ownerId: string;\n createdAt: string;\n}\n\ninterface AdminBoard {\n id: string;\n name: string;\n type: string;\n projectId?: string | null;\n}\n\ninterface AdminAutomationRule {\n id: string;\n name: string;\n status: string;\n triggerType: string;\n executionCount: number;\n lastExecutedAt?: string | null;\n createdAt: string;\n}\n\n// ------------------------------------------------------------------------------\n// GraphQL documents (inline so the admin surface does not depend on generated\n// hooks while the rest of the MFE's operation files are being reconciled with\n// the schema).\n// ------------------------------------------------------------------------------\n\nconst ADMIN_PROJECTS_QUERY = gql`\n query AdminPlanmagnetProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n key\n status\n methodology\n ownerId\n createdAt\n }\n totalCount\n }\n }\n`;\n\nconst ADMIN_BOARDS_QUERY = gql`\n query AdminPlanmagnetBoards($workspaceId: ID!) {\n planmagnetBoards(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n type\n projectId\n }\n totalCount\n }\n }\n`;\n\nconst ADMIN_AUTOMATION_RULES_QUERY = gql`\n query AdminPlanmagnetAutomationRules($workspaceId: ID!) {\n planmagnetAutomationRules(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n status\n triggerType\n executionCount\n lastExecutedAt\n createdAt\n }\n totalCount\n }\n }\n`;\n\nconst ARCHIVE_PROJECT_MUTATION = gql`\n mutation AdminArchiveProject($id: ID!) {\n planmagnetUpdateProject(id: $id, input: { status: ARCHIVED }) {\n id\n status\n }\n }\n`;\n\n// ------------------------------------------------------------------------------\n// Presentation helpers\n// ------------------------------------------------------------------------------\n\nfunction projectStatusVariantr(status: ProjectStatus) {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'ARCHIVED':\n return 'secondary';\n case 'CANCELLED':\n return 'destructive';\n case 'COMPLETED':\n return 'info';\n case 'ON_HOLD':\n return 'warning';\n case 'PLANNING':\n return 'outline';\n default:\n return 'default';\n }\n}\n\nfunction automationStatusVariantr(status: string) {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'PAUSED':\n return 'warning';\n case 'ERROR':\n return 'destructive';\n case 'DISABLED':\n return 'secondary';\n default:\n return 'default';\n }\n}\n\nfunction formatDate(value: string | null | undefined) {\n if (!value) return '—';\n try {\n return new Date(value).toLocaleDateString();\n } catch {\n return value;\n }\n}\n\nfunction localizeMethodology(value: string) {\n return value.toLowerCase().replace(/_/g, ' ');\n}\n\nfunction localizeTrigger(value: string) {\n return value.toLowerCase().replace(/_/g, ' ');\n}\n\n// ------------------------------------------------------------------------------\n// Sub-components\n// ------------------------------------------------------------------------------\n\nfunction QuotaCard({\n icon: Icon,\n label,\n used,\n limit,\n className,\n}: {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n used: number;\n limit: number;\n className?: string;\n}) {\n const percentage = Math.min(100, Math.round((used / Math.max(1, limit)) * 100));\n\n return (\n <Card className={className}>\n <CardContent className=\"pt-6\">\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Icon className=\"h-4 w-4\" />\n <span className=\"text-sm\">{label}</span>\n </div>\n <div className=\"mt-2 flex items-baseline gap-2\">\n <span className=\"text-2xl font-semibold\">{used}</span>\n <span className=\"text-sm text-muted-foreground\">/ {limit}</span>\n </div>\n <div className=\"mt-3 h-2 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\n 'h-full rounded-full transition-all',\n percentage >= 90 ? 'bg-destructive' : percentage >= 70 ? 'bg-warning' : 'bg-success'\n )}\n style={{ width: `${percentage}%` }}\n />\n </div>\n <p className=\"mt-2 text-xs text-muted-foreground\">{percentage}% used</p>\n </CardContent>\n </Card>\n );\n}\n\nfunction SectionLoader() {\n return (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-8 w-1/3\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n );\n}\n\ninterface SimpleTableColumn<T> {\n key: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n className?: string;\n}\n\nfunction SimpleTable<T extends { id: string }>({\n columns,\n data,\n emptyState,\n}: {\n columns: SimpleTableColumn<T>[];\n data: T[];\n emptyState: React.ReactNode;\n}) {\n if (data.length === 0) {\n return <div className=\"py-8 text-center text-sm text-muted-foreground\">{emptyState}</div>;\n }\n\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50\">\n <tr>\n {columns.map((column) => (\n <th\n key={column.key}\n className={cn(\n 'px-4 py-3 text-left font-medium text-muted-foreground',\n column.className\n )}\n >\n {column.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody className=\"divide-y\">\n {data.map((row) => (\n <tr key={row.id} className=\"hover:bg-muted/30\">\n {columns.map((column) => (\n <td key={`${row.id}-${column.key}`} className={cn('px-4 py-3', column.className)}>\n {column.cell(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n// ------------------------------------------------------------------------------\n// Main dashboard\n// ------------------------------------------------------------------------------\n\nfunction PlanMagnetAdminDashboard() {\n const { workspaceId } = usePlanMagnet();\n const tr = useTr();\n\n const [selectedProjectIds, setSelectedProjectIds] = useState<Set<string>>(new Set());\n const [archiving, setArchiving] = useState(false);\n const [archiveError, setArchiveError] = useState<string | null>(null);\n\n const projectsQuery = useQuery<{\n planmagnetProjects: { items: AdminProject[]; totalCount: number };\n }>(ADMIN_PROJECTS_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const boardsQuery = useQuery<{\n planmagnetBoards: { items: AdminBoard[]; totalCount: number };\n }>(ADMIN_BOARDS_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const rulesQuery = useQuery<{\n planmagnetAutomationRules: { items: AdminAutomationRule[]; totalCount: number };\n }>(ADMIN_AUTOMATION_RULES_QUERY, {\n variables: { workspaceId: workspaceId ?? '' },\n skip: !workspaceId,\n });\n\n const [archiveProject] = useMutation(ARCHIVE_PROJECT_MUTATION);\n\n const projects = projectsQuery.data?.planmagnetProjects.items ?? [];\n const boards = boardsQuery.data?.planmagnetBoards.items ?? [];\n const rules = rulesQuery.data?.planmagnetAutomationRules.items ?? [];\n\n // Every project that is not archived yet (BOFF-7940). Only ACTIVE ones were offered, so completed projects — the\n // natural candidates — and anything left in PLANNING or ON_HOLD could never be archived here.\n const archivableProjects = useMemo(\n () => projects.filter((project) => project.status !== 'ARCHIVED'),\n [projects]\n );\n\n const toggleProjectSelection = (id: string) => {\n setSelectedProjectIds((previous) => {\n const next = new Set(previous);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n };\n\n const toggleAll = () => {\n if (selectedProjectIds.size === archivableProjects.length) {\n setSelectedProjectIds(new Set());\n } else {\n setSelectedProjectIds(new Set(archivableProjects.map((project) => project.id)));\n }\n };\n\n const handleBulkArchive = async () => {\n if (selectedProjectIds.size === 0) return;\n setArchiving(true);\n setArchiveError(null);\n // Each archive is checked on its own (BOFF-7958): a refused mutation RESOLVES with { error } (errorPolicy 'all'),\n // so Promise.all over the raw calls reported success for archives the service refused. Failures stay selected\n // and are named, with the reason.\n const ids = Array.from(selectedProjectIds);\n try {\n const outcomes = await Promise.allSettled(\n ids.map((id) => throwOnError(archiveProject({ variables: { id } })))\n );\n const failed = ids.filter((_, index) => outcomes[index]!.status === 'rejected');\n await projectsQuery.refetch();\n setSelectedProjectIds(new Set(failed));\n if (failed.length > 0) {\n const first = outcomes.find((o) => o.status === 'rejected') as PromiseRejectedResult;\n const reason = first.reason instanceof Error ? first.reason.message : String(first.reason);\n const names = failed.map((id) => projects.find((p) => p.id === id)?.name ?? id).join(', ');\n setArchiveError(\n `${failed.length} of ${ids.length} could not be archived (${names}): ${reason}`\n );\n }\n } catch (error) {\n setArchiveError(\n error instanceof Error\n ? error.message\n : tr('planmagnet.admin.archive.unknownError', 'Bulk archive failed')\n );\n } finally {\n setArchiving(false);\n }\n };\n\n if (!workspaceId) {\n return (\n <div className=\"min-h-screen bg-background\">\n <div className=\"flex min-h-[320px] flex-col items-center justify-center gap-3 text-muted-foreground\">\n <AlertCircle className=\"h-8 w-8\" />\n <p>\n {tr(\n 'planmagnet.admin.noWorkspace',\n 'Select a workspace to manage PlanMagnet admin settings.'\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const isLoading = projectsQuery.loading || boardsQuery.loading || rulesQuery.loading;\n\n return (\n <div className=\"min-h-screen bg-background\">\n <div className=\"mx-auto max-w-7xl space-y-6 p-6\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n {tr('planmagnet.admin.title', 'PlanMagnet Admin')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.purpose',\n 'Workspace quota overview, bulk project archive, and automation rule audit.'\n )}\n </p>\n </div>\n </div>\n\n {/* Quota overview — counts are real; limits are placeholder until a\n dedicated admin quota query exists in the backend. */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <QuotaCard\n icon={FolderKanban}\n label={tr('planmagnet.admin.quota.projects', 'Projects')}\n used={projects.length}\n limit={50}\n />\n <QuotaCard\n icon={LayoutTemplate}\n label={tr('planmagnet.admin.quota.boards', 'Boards')}\n used={boards.length}\n limit={100}\n />\n <QuotaCard\n icon={Zap}\n label={tr('planmagnet.admin.quota.automationRules', 'Automation Rules')}\n used={rules.length}\n limit={25}\n />\n </div>\n\n {isLoading ? (\n <SectionLoader />\n ) : (\n <>\n {/* Bulk project archive */}\n <Card>\n <CardHeader className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <CardTitle>\n {tr('planmagnet.admin.archive.title', 'Bulk Project Archive')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.archive.subtitle',\n 'Select projects and archive them in one action.'\n )}\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={selectedProjectIds.size === 0 || archiving}\n onClick={handleBulkArchive}\n >\n {archiving ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"mr-2 h-4 w-4\" />\n )}\n {tr('planmagnet.admin.archive.action', 'Archive')} ({selectedProjectIds.size})\n </Button>\n </CardHeader>\n <CardContent>\n {archiveError && (\n <div className=\"mb-4 flex items-center gap-2 rounded-md bg-destructive/10 p-3 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4\" />\n {archiveError}\n </div>\n )}\n <div className=\"rounded-md border\">\n <SimpleTable<AdminProject>\n data={archivableProjects}\n columns={[\n {\n key: 'select',\n header: (\n <Checkbox\n checked={\n archivableProjects.length > 0 &&\n selectedProjectIds.size === archivableProjects.length\n }\n onCheckedChange={toggleAll}\n aria-label={tr(\n 'planmagnet.admin.archive.selectAll',\n 'Select all projects'\n )}\n />\n ),\n cell: (row) => (\n <Checkbox\n checked={selectedProjectIds.has(row.id)}\n onCheckedChange={() => toggleProjectSelection(row.id)}\n aria-label={tr(\n 'planmagnet.admin.archive.selectProject',\n 'Select project'\n )}\n />\n ),\n className: 'w-12',\n },\n {\n key: 'name',\n header: tr('planmagnet.admin.archive.column.name', 'Project'),\n cell: (row) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.name}</span>\n <span className=\"text-xs text-muted-foreground font-mono\">\n {row.key}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: tr('planmagnet.admin.archive.column.status', 'Status'),\n cell: (row) => (\n <Badge variant={projectStatusVariantr(row.status)}>\n {row.status.toLowerCase().replace('_', ' ')}\n </Badge>\n ),\n },\n {\n key: 'methodology',\n header: tr('planmagnet.admin.archive.column.methodology', 'Methodology'),\n cell: (row) => (\n <span className=\"text-sm capitalize\">\n {localizeMethodology(row.methodology)}\n </span>\n ),\n },\n {\n key: 'owner',\n header: tr('planmagnet.admin.archive.column.owner', 'Owner'),\n cell: (row) => (\n <span className=\"text-sm text-muted-foreground font-mono\">\n <PersonName id={row.ownerId} />\n </span>\n ),\n },\n {\n key: 'created',\n header: tr('planmagnet.admin.archive.column.created', 'Created'),\n cell: (row) => <span className=\"text-sm\">{formatDate(row.createdAt)}</span>,\n },\n ]}\n emptyState={tr(\n 'planmagnet.admin.archive.empty',\n 'No active projects to archive.'\n )}\n />\n </div>\n </CardContent>\n </Card>\n\n {/* Automation rule audit */}\n <Card>\n <CardHeader>\n <CardTitle>\n {tr('planmagnet.admin.automation.title', 'Automation Rule Audit')}\n </CardTitle>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'planmagnet.admin.automation.subtitle',\n 'Review rule health, execution counts, and last run timestamps.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n <div className=\"rounded-md border\">\n <SimpleTable<AdminAutomationRule>\n data={rules}\n columns={[\n {\n key: 'name',\n header: tr('planmagnet.admin.automation.column.name', 'Rule'),\n cell: (row) => <span className=\"font-medium\">{row.name}</span>,\n },\n {\n key: 'trigger',\n header: tr('planmagnet.admin.automation.column.trigger', 'Trigger'),\n cell: (row) => (\n <span className=\"text-sm\">{localizeTrigger(row.triggerType)}</span>\n ),\n },\n {\n key: 'status',\n header: tr('planmagnet.admin.automation.column.status', 'Status'),\n cell: (row) => (\n <Badge variant={automationStatusVariantr(row.status)}>\n {row.status.toLowerCase()}\n </Badge>\n ),\n },\n {\n key: 'executions',\n header: tr('planmagnet.admin.automation.column.executions', 'Executions'),\n cell: (row) => <span className=\"text-sm\">{row.executionCount}</span>,\n },\n {\n key: 'lastRun',\n header: tr('planmagnet.admin.automation.column.lastRun', 'Last Run'),\n cell: (row) => (\n <span className=\"text-sm\">{formatDate(row.lastExecutedAt)}</span>\n ),\n },\n {\n key: 'created',\n header: tr('planmagnet.admin.automation.column.created', 'Created'),\n cell: (row) => <span className=\"text-sm\">{formatDate(row.createdAt)}</span>,\n },\n ]}\n emptyState={tr(\n 'planmagnet.admin.automation.empty',\n 'No automation rules found.'\n )}\n />\n </div>\n </CardContent>\n </Card>\n </>\n )}\n </div>\n </div>\n );\n}\n\n// ------------------------------------------------------------------------------\n// Public root export consumed by the admin app shell\n// ------------------------------------------------------------------------------\n\nexport function PlanMagnetAdminRoot(props: PlanMagnetRootProps) {\n return (\n <PlanMagnetProvider {...props}>\n <PlanMagnetAdminDashboard />\n </PlanMagnetProvider>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * A button that asks before it acts. Used for anything that cannot be undone from the UI (complete or cancel a
5
+ * sprint, delete a task, a project or a roadmap).
6
+ */
7
+ export declare function ConfirmButton({ label, icon, title, description, confirmLabel, onConfirm, disabled, destructive, variant, cancelLabel, ariaLabel, }: {
8
+ label: string;
9
+ icon: ReactNode;
10
+ title: string;
11
+ description: string;
12
+ confirmLabel: string;
13
+ onConfirm: () => void;
14
+ disabled: boolean;
15
+ destructive?: boolean;
16
+ variant?: 'default' | 'outline';
17
+ cancelLabel?: string;
18
+ /** Required when `label` is empty (an icon-only button): icons are aria-hidden. */
19
+ ariaLabel?: string;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ /** An inline refusal reason, announced to screen readers. */
22
+ export declare function ErrorLine({ message }: {
23
+ message: string;
24
+ }): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=ConfirmButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmButton.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/ConfirmButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAcvC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,IAAI,EACJ,KAAK,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,WAAmB,EACnB,OAAmB,EACnB,WAAuB,EACvB,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,2CAMzD"}
@@ -0,0 +1,30 @@
1
+ import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
+ import { AlertDialog as n, AlertDialogAction as r, AlertDialogCancel as i, AlertDialogContent as a, AlertDialogDescription as o, AlertDialogFooter as s, AlertDialogHeader as c, AlertDialogTitle as l, AlertDialogTrigger as u, Button as d } from "@burdenoff/fe-libs/ui";
3
+ //#region src/planmagnet/controls/ConfirmButton.tsx
4
+ function f({ label: f, icon: p, title: m, description: h, confirmLabel: g, onConfirm: _, disabled: v, destructive: y = !1, variant: b = "default", cancelLabel: x = "Keep it", ariaLabel: S }) {
5
+ return /* @__PURE__ */ t(n, { children: [/* @__PURE__ */ e(u, {
6
+ asChild: !0,
7
+ children: /* @__PURE__ */ t(d, {
8
+ size: "sm",
9
+ variant: b,
10
+ disabled: v,
11
+ "aria-label": S,
12
+ children: [p, f]
13
+ })
14
+ }), /* @__PURE__ */ t(a, { children: [/* @__PURE__ */ t(c, { children: [/* @__PURE__ */ e(l, { children: m }), /* @__PURE__ */ e(o, { children: h })] }), /* @__PURE__ */ t(s, { children: [/* @__PURE__ */ e(i, { children: x }), /* @__PURE__ */ e(r, {
15
+ onClick: _,
16
+ className: y ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "",
17
+ children: g
18
+ })] })] })] });
19
+ }
20
+ function p({ message: t }) {
21
+ return /* @__PURE__ */ e("p", {
22
+ role: "alert",
23
+ className: "text-sm text-destructive",
24
+ children: t
25
+ });
26
+ }
27
+ //#endregion
28
+ export { f as ConfirmButton, p as ErrorLine };
29
+
30
+ //# sourceMappingURL=ConfirmButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmButton.js","names":[],"sources":["../../../src/planmagnet/controls/ConfirmButton.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n} from '@burdenoff/fe-libs/ui';\n\n/**\n * A button that asks before it acts. Used for anything that cannot be undone from the UI (complete or cancel a\n * sprint, delete a task, a project or a roadmap).\n */\nexport function ConfirmButton({\n label,\n icon,\n title,\n description,\n confirmLabel,\n onConfirm,\n disabled,\n destructive = false,\n variant = 'default',\n cancelLabel = 'Keep it',\n ariaLabel,\n}: {\n label: string;\n icon: ReactNode;\n title: string;\n description: string;\n confirmLabel: string;\n onConfirm: () => void;\n disabled: boolean;\n destructive?: boolean;\n variant?: 'default' | 'outline';\n cancelLabel?: string;\n /** Required when `label` is empty (an icon-only button): icons are aria-hidden. */\n ariaLabel?: string;\n}) {\n return (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant={variant} disabled={disabled} aria-label={ariaLabel}>\n {icon}\n {label}\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{title}</AlertDialogTitle>\n <AlertDialogDescription>{description}</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>{cancelLabel}</AlertDialogCancel>\n <AlertDialogAction\n onClick={onConfirm}\n className={\n destructive\n ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'\n : ''\n }\n >\n {confirmLabel}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n/** An inline refusal reason, announced to screen readers. */\nexport function ErrorLine({ message }: { message: string }) {\n return (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {message}\n </p>\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,EAAc,EAC5B,UACA,SACA,UACA,gBACA,iBACA,cACA,aACA,iBAAc,IACd,aAAU,WACV,iBAAc,WACd,gBAcC;AACD,QACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAoB,SAAA;YAClB,kBAAC,GAAD;GAAQ,MAAK;GAAc;GAAmB;GAAU,cAAY;aAApE,CACG,GACA,EACM;;EACU,CAAA,EACrB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAmB,GAAyB,CAAA,EAC5C,kBAAC,GAAD,EAAA,UAAyB,GAAqC,CAAA,CAC5C,EAAA,CAAA,EACpB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAoB,GAAgC,CAAA,EACpD,kBAAC,GAAD;EACE,SAAS;EACT,WACE,IACI,uEACA;YAGL;EACiB,CAAA,CACF,EAAA,CAAA,CACD,EAAA,CAAA,CACT,EAAA,CAAA;;AAKlB,SAAgB,EAAU,EAAE,cAAgC;AAC1D,QACE,kBAAC,KAAD;EAAG,MAAK;EAAQ,WAAU;YACvB;EACC,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Project controls (BOFF-7940). A new project starts in PLANNING and, until 2026-09-27, nothing in the UI could
3
+ * move it: no status control, and the Edit Project page (which had no Status field) was linked from nowhere. So
4
+ * Projects > Active, the Dashboard's project status card and the admin Bulk Archive never showed a project created
5
+ * in the UI.
6
+ */
7
+ export declare const PROJECT_STATUSES: readonly ["PLANNING", "ACTIVE", "ON_HOLD", "COMPLETED", "CANCELLED", "ARCHIVED"];
8
+ export declare function ProjectHeaderActions({ projectId, name, status, }: {
9
+ projectId: string;
10
+ name: string;
11
+ status: string;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=ProjectControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/ProjectControls.tsx"],"names":[],"mappings":"AAWA;;;;;GAKG;AAEH,eAAO,MAAM,gBAAgB,kFAOnB,CAAC;AAyBX,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,IAAI,EACJ,MAAM,GACP,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,2CA4EA"}
@@ -0,0 +1,104 @@
1
+ import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
2
+ import { throwOnError as t } from "../utils/mutationResult.js";
3
+ import { detailPath as n } from "../utils/rowNavigation.js";
4
+ import { ConfirmButton as r, ErrorLine as i } from "./ConfirmButton.js";
5
+ import { enumLabel as a } from "./WorkItemControls.js";
6
+ import { useState as o } from "react";
7
+ import { Pencil as s, Trash2 as c } from "lucide-react";
8
+ import { gql as l } from "@apollo/client";
9
+ import { useMutation as u } from "@apollo/client/react";
10
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
11
+ import { Button as p } from "@burdenoff/fe-libs/ui";
12
+ //#region src/planmagnet/controls/ProjectControls.tsx
13
+ var m = [
14
+ "PLANNING",
15
+ "ACTIVE",
16
+ "ON_HOLD",
17
+ "COMPLETED",
18
+ "CANCELLED",
19
+ "ARCHIVED"
20
+ ], h = l`
21
+ mutation ProjectControlsStatus($id: ID!, $input: PlanmagnetUpdateProjectInput!) {
22
+ planmagnetUpdateProject(id: $id, input: $input) {
23
+ id
24
+ status
25
+ }
26
+ }
27
+ `, g = l`
28
+ mutation ProjectControlsDelete($id: ID!) {
29
+ planmagnetDeleteProject(id: $id)
30
+ }
31
+ `, _ = [
32
+ "GetProjects",
33
+ "GetActiveProjects",
34
+ "GetBoardProjects",
35
+ "GetDashboardData",
36
+ "AdminPlanmagnetProjects"
37
+ ];
38
+ function v({ projectId: l, name: v, status: y }) {
39
+ let b = e(), [x, S] = u(h, {
40
+ refetchQueries: ["GetProjectDetail", ..._],
41
+ awaitRefetchQueries: !0
42
+ }), [C, w] = u(g, { refetchQueries: _ }), [T, E] = o(null), D = S.loading || w.loading, O = m.includes(y) ? m : [y, ...m], k = async (e) => {
43
+ E(null);
44
+ try {
45
+ await t(x({ variables: {
46
+ id: l,
47
+ input: { status: e }
48
+ } }));
49
+ } catch (e) {
50
+ E(e instanceof Error ? e.message : "The status could not be changed.");
51
+ }
52
+ }, A = async () => {
53
+ E(null);
54
+ try {
55
+ await t(C({ variables: { id: l } })), b("/projects/projects-page");
56
+ } catch (e) {
57
+ E(e instanceof Error ? e.message : "The project could not be deleted.");
58
+ }
59
+ };
60
+ return /* @__PURE__ */ f("div", {
61
+ className: "flex flex-col items-end gap-2",
62
+ children: [/* @__PURE__ */ f("div", {
63
+ className: "flex flex-wrap items-center justify-end gap-2",
64
+ children: [
65
+ /* @__PURE__ */ d("select", {
66
+ "aria-label": "Project status",
67
+ "data-testid": "project-status",
68
+ className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
69
+ value: y,
70
+ disabled: D,
71
+ onChange: (e) => {
72
+ e.target.value !== y && k(e.target.value);
73
+ },
74
+ children: O.map((e) => /* @__PURE__ */ d("option", {
75
+ value: e,
76
+ children: a(e)
77
+ }, e))
78
+ }),
79
+ /* @__PURE__ */ f(p, {
80
+ size: "sm",
81
+ variant: "outline",
82
+ "data-testid": "project-edit",
83
+ onClick: () => b(n("/projects/edit-project-page", l)),
84
+ children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), "Edit"]
85
+ }),
86
+ /* @__PURE__ */ d(r, {
87
+ label: "Delete",
88
+ variant: "outline",
89
+ icon: /* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }),
90
+ title: `Delete ${v}?`,
91
+ description: "The project is deleted. Its tasks, sprints, boards, milestones and budgets disappear from every list, and so do releases that belong to no other project. This cannot be undone here.",
92
+ confirmLabel: "Delete project",
93
+ destructive: !0,
94
+ disabled: D,
95
+ onConfirm: () => void A()
96
+ })
97
+ ]
98
+ }), T && /* @__PURE__ */ d(i, { message: T })]
99
+ });
100
+ }
101
+ //#endregion
102
+ export { m as PROJECT_STATUSES, v as ProjectHeaderActions };
103
+
104
+ //# sourceMappingURL=ProjectControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectControls.js","names":[],"sources":["../../../src/planmagnet/controls/ProjectControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Project controls (BOFF-7940). A new project starts in PLANNING and, until 2026-09-27, nothing in the UI could\n * move it: no status control, and the Edit Project page (which had no Status field) was linked from nowhere. So\n * Projects > Active, the Dashboard's project status card and the admin Bulk Archive never showed a project created\n * in the UI.\n */\n\nexport const PROJECT_STATUSES = [\n 'PLANNING',\n 'ACTIVE',\n 'ON_HOLD',\n 'COMPLETED',\n 'CANCELLED',\n 'ARCHIVED',\n] as const;\n\nconst UPDATE_PROJECT_STATUS = gql`\n mutation ProjectControlsStatus($id: ID!, $input: PlanmagnetUpdateProjectInput!) {\n planmagnetUpdateProject(id: $id, input: $input) {\n id\n status\n }\n }\n`;\nconst DELETE_PROJECT = gql`\n mutation ProjectControlsDelete($id: ID!) {\n planmagnetDeleteProject(id: $id)\n }\n`;\n\n/** Every query that lists projects or shows a project's status. */\nconst PROJECT_LIST_QUERIES = [\n 'GetProjects',\n 'GetActiveProjects',\n 'GetBoardProjects',\n 'GetDashboardData',\n 'AdminPlanmagnetProjects',\n];\n\nexport function ProjectHeaderActions({\n projectId,\n name,\n status,\n}: {\n projectId: string;\n name: string;\n status: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [updateStatus, statusState] = useMutation(UPDATE_PROJECT_STATUS, {\n refetchQueries: ['GetProjectDetail', ...PROJECT_LIST_QUERIES],\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE_PROJECT, {\n refetchQueries: PROJECT_LIST_QUERIES,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = statusState.loading || removeState.loading;\n const statuses = (PROJECT_STATUSES as readonly string[]).includes(status)\n ? PROJECT_STATUSES\n : [status, ...PROJECT_STATUSES];\n\n const onStatus = async (next: string) => {\n setError(null);\n try {\n await throwOnError(updateStatus({ variables: { id: projectId, input: { status: next } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: projectId } }));\n navigate('/projects/projects-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The project could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Project status\"\n data-testid=\"project-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"project-edit\"\n onClick={() => navigate(detailPath('/projects/edit-project-page', projectId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${name}?`}\n description=\"The project is deleted. Its tasks, sprints, boards, milestones and budgets disappear from every list, and so do releases that belong to no other project. This cannot be undone here.\"\n confirmLabel=\"Delete project\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAkBA,IAAa,IAAmB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAwB,CAAG;;;;;;;GAQ3B,IAAiB,CAAG;;;;GAOpB,IAAuB;CAC3B;CACA;CACA;CACA;CACA;CACD;AAED,SAAgB,EAAqB,EACnC,cACA,SACA,aAKC;CACD,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAc,KAAe,EAAY,GAAuB;EACrE,gBAAgB,CAAC,oBAAoB,GAAG,EAAqB;EAC7D,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAgB,EACxD,gBAAgB,GACjB,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAO,EAAY,WAAW,EAAY,SAC1C,IAAY,EAAuC,SAAS,EAAO,GACrE,IACA,CAAC,GAAQ,GAAG,EAAiB,EAE3B,IAAW,OAAO,MAAiB;AACvC,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAa,EAAE,WAAW;IAAE,IAAI;IAAW,OAAO,EAAE,QAAQ,GAAM;IAAE,EAAE,CAAC,CAAC;WACpF,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;IAG3E,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,GAAW,EAAE,CAAC,CAAC,EAC5D,EAAS,0BAA0B;WAC5B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,cAAW;KACX,eAAY;KACZ,WAAU;KACV,OAAO;KACP,UAAU;KACV,WAAW,MAAM;AACf,MAAI,EAAE,OAAO,UAAU,KAAa,EAAS,EAAE,OAAO,MAAM;;eAG7D,EAAS,KAAK,MACb,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAU,EAAE;MACN,EAFI,EAEJ,CACT;KACK,CAAA;IACT,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAY;KACZ,eAAe,EAAS,EAAW,+BAA+B,EAAU,CAAC;eAJ/E,CAME,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;IACT,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,UAAU,EAAK;KACtB,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU;KACV,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or
3
+ * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status
4
+ * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team
5
+ * page, sprint "Team Members" list and board card read "Unassigned".
6
+ *
7
+ * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records
8
+ * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.
9
+ */
10
+ export declare const WORK_ITEM_STATUSES: readonly ["BACKLOG", "TODO", "IN_PROGRESS", "IN_REVIEW", "TESTING", "DONE", "CANCELLED"];
11
+ export declare const WORK_ITEM_PRIORITIES: readonly ["NONE", "LOW", "MEDIUM", "HIGH", "CRITICAL"];
12
+ export declare function enumLabel(value: string): string;
13
+ /** A status or priority select that saves on change. */
14
+ export declare function WorkItemEnumSelect({ workItemId, field, value, options, label, }: {
15
+ workItemId: string;
16
+ field: 'status' | 'priority';
17
+ value: string;
18
+ options: readonly string[];
19
+ label: string;
20
+ }): import("react/jsx-runtime").JSX.Element;
21
+ /**
22
+ * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any
23
+ * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet
24
+ * admin rights, so an ordinary member would get an empty picker.
25
+ */
26
+ export declare function useAssigneeOptions(currentId: string | null | undefined): {
27
+ options: {
28
+ id: string;
29
+ name: string;
30
+ }[];
31
+ loading: boolean;
32
+ hasMore: boolean;
33
+ fetchMore: () => void;
34
+ };
35
+ /** A plain assignee select (for forms). '' means unassigned. */
36
+ export declare function AssigneeSelect({ id, value, onChange, disabled, }: {
37
+ id?: string;
38
+ value: string;
39
+ onChange: (userId: string) => void;
40
+ disabled?: boolean;
41
+ }): import("react/jsx-runtime").JSX.Element;
42
+ /** The task detail's assignee control: saves on change. */
43
+ export declare function WorkItemAssigneeSelect({ workItemId, assigneeId, }: {
44
+ workItemId: string;
45
+ assigneeId: string | null;
46
+ }): import("react/jsx-runtime").JSX.Element;
47
+ /** Edit and Delete for the task detail header. */
48
+ export declare function WorkItemHeaderActions({ workItemId, title, }: {
49
+ workItemId: string;
50
+ title: string;
51
+ }): import("react/jsx-runtime").JSX.Element;
52
+ //# sourceMappingURL=WorkItemControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkItemControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"names":[],"mappings":"AAWA;;;;;;;;GAQG;AAEH,eAAO,MAAM,kBAAkB,0FAQrB,CAAC;AACX,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAE3F,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C;AA0CD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,EACjC,UAAU,EACV,KAAK,EACL,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,2CAyBA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;;;;;;;;EAmBtE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,2CAkCA;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,EACrC,UAAU,EACV,UAAU,GACX,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,2CAcA;AAED,kDAAkD;AAClD,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,KAAK,GACN,EAAE;IACD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,2CA0CA"}
@@ -0,0 +1,198 @@
1
+ import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
2
+ import { usePersonProfile as n } from "../people/People.js";
3
+ import { throwOnError as r } from "../utils/mutationResult.js";
4
+ import { ConfirmButton as i, ErrorLine as a } from "./ConfirmButton.js";
5
+ import { useState as o } from "react";
6
+ import { Pencil as s, Trash2 as c } from "lucide-react";
7
+ import { gql as l } from "@apollo/client";
8
+ import { useMutation as u } from "@apollo/client/react";
9
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
+ import { Button as p } from "@burdenoff/fe-libs/ui";
11
+ import { useWorkspaceDirectory as m } from "@burdenoff/fe-libs/sharing";
12
+ //#region src/planmagnet/controls/WorkItemControls.tsx
13
+ var h = [
14
+ "BACKLOG",
15
+ "TODO",
16
+ "IN_PROGRESS",
17
+ "IN_REVIEW",
18
+ "TESTING",
19
+ "DONE",
20
+ "CANCELLED"
21
+ ], g = [
22
+ "NONE",
23
+ "LOW",
24
+ "MEDIUM",
25
+ "HIGH",
26
+ "CRITICAL"
27
+ ];
28
+ function _(e) {
29
+ let t = e.toLowerCase().replace(/_/g, " ");
30
+ return t.charAt(0).toUpperCase() + t.slice(1);
31
+ }
32
+ var v = l`
33
+ mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {
34
+ planmagnetUpdateWorkItem(id: $id, input: $input) {
35
+ id
36
+ status
37
+ priority
38
+ assigneeId
39
+ }
40
+ }
41
+ `, y = l`
42
+ mutation WorkItemControlsDelete($id: ID!) {
43
+ planmagnetDeleteWorkItem(id: $id)
44
+ }
45
+ `, b = [
46
+ "GetWorkItems",
47
+ "GetWorkItemCounts",
48
+ "ProjectBoard",
49
+ "GetDashboardData"
50
+ ], x = [
51
+ "GetWorkItemDetail",
52
+ "WorkItemActivity",
53
+ ...b
54
+ ], S = "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]";
55
+ function C(e) {
56
+ let [t, n] = u(v, {
57
+ refetchQueries: x,
58
+ awaitRefetchQueries: !0
59
+ }), [i, a] = o(null);
60
+ return {
61
+ save: async (n) => {
62
+ a(null);
63
+ try {
64
+ await r(t({ variables: {
65
+ id: e,
66
+ input: n
67
+ } }));
68
+ } catch (e) {
69
+ a(e instanceof Error ? e.message : "The task could not be updated.");
70
+ }
71
+ },
72
+ saving: n.loading,
73
+ error: i
74
+ };
75
+ }
76
+ function w({ workItemId: e, field: t, value: n, options: r, label: i }) {
77
+ let { save: o, saving: s, error: c } = C(e), l = r.includes(n) ? r : [n, ...r];
78
+ return /* @__PURE__ */ f("div", {
79
+ className: "flex flex-col items-end gap-1",
80
+ children: [/* @__PURE__ */ d("select", {
81
+ "aria-label": i,
82
+ "data-testid": `task-${t}`,
83
+ className: S,
84
+ value: n,
85
+ disabled: s,
86
+ onChange: (e) => {
87
+ e.target.value !== n && o({ [t]: e.target.value });
88
+ },
89
+ children: l.map((e) => /* @__PURE__ */ d("option", {
90
+ value: e,
91
+ children: _(e)
92
+ }, e))
93
+ }), c && /* @__PURE__ */ d(a, { message: c })]
94
+ });
95
+ }
96
+ function T(t) {
97
+ let { workspaceId: r, authToken: i, apiGatewayUrl: a } = e(), o = m({
98
+ workspaceId: r ?? "",
99
+ authToken: i ?? void 0,
100
+ apiGatewayUrl: a ?? void 0
101
+ }), s = n(t), c = o.members.map((e) => ({
102
+ id: e.id,
103
+ name: e.name
104
+ }));
105
+ return t && s && !c.some((e) => e.id === t) && c.unshift({
106
+ id: t,
107
+ name: s.displayName
108
+ }), {
109
+ options: c,
110
+ loading: o.loading,
111
+ hasMore: o.hasMore,
112
+ fetchMore: o.fetchMore
113
+ };
114
+ }
115
+ function E({ id: e, value: t, onChange: n, disabled: r = !1 }) {
116
+ let { options: i, loading: a, hasMore: o, fetchMore: s } = T(t || null);
117
+ return /* @__PURE__ */ f("div", {
118
+ className: "flex flex-col gap-1",
119
+ children: [
120
+ /* @__PURE__ */ f("select", {
121
+ id: e,
122
+ "aria-label": e ? void 0 : "Assignee",
123
+ "data-testid": "assignee-select",
124
+ className: S,
125
+ value: t,
126
+ disabled: r,
127
+ onChange: (e) => n(e.target.value),
128
+ children: [/* @__PURE__ */ d("option", {
129
+ value: "",
130
+ children: "Unassigned"
131
+ }), i.map((e) => /* @__PURE__ */ d("option", {
132
+ value: e.id,
133
+ children: e.name
134
+ }, e.id))]
135
+ }),
136
+ a && i.length === 0 && /* @__PURE__ */ d("span", {
137
+ className: "text-xs text-muted-foreground",
138
+ children: "Loading members…"
139
+ }),
140
+ o && /* @__PURE__ */ d("button", {
141
+ type: "button",
142
+ className: "self-start text-xs text-primary hover:underline",
143
+ onClick: s,
144
+ children: "Show more members"
145
+ })
146
+ ]
147
+ });
148
+ }
149
+ function D({ workItemId: e, assigneeId: t }) {
150
+ let { save: n, saving: r, error: i } = C(e);
151
+ return /* @__PURE__ */ f("div", {
152
+ className: "flex flex-col items-end gap-1",
153
+ children: [/* @__PURE__ */ d(E, {
154
+ value: t ?? "",
155
+ disabled: r,
156
+ onChange: (e) => {
157
+ e !== (t ?? "") && n({ assigneeId: e || null });
158
+ }
159
+ }), i && /* @__PURE__ */ d(a, { message: i })]
160
+ });
161
+ }
162
+ function O({ workItemId: e, title: n }) {
163
+ let l = t(), [m, h] = u(y, { refetchQueries: b }), [g, _] = o(null), v = async () => {
164
+ _(null);
165
+ try {
166
+ await r(m({ variables: { id: e } })), l("/tasks/tasks-page");
167
+ } catch (e) {
168
+ _(e instanceof Error ? e.message : "The task could not be deleted.");
169
+ }
170
+ };
171
+ return /* @__PURE__ */ f("div", {
172
+ className: "flex flex-col items-end gap-2",
173
+ children: [/* @__PURE__ */ f("div", {
174
+ className: "flex gap-2",
175
+ children: [/* @__PURE__ */ f(p, {
176
+ size: "sm",
177
+ variant: "outline",
178
+ "data-testid": "task-edit",
179
+ onClick: () => l(`/tasks/edit-task-page?taskId=${encodeURIComponent(e)}`),
180
+ children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), "Edit"]
181
+ }), /* @__PURE__ */ d(i, {
182
+ label: "Delete",
183
+ variant: "outline",
184
+ icon: /* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }),
185
+ title: `Delete "${n}"?`,
186
+ description: "The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.",
187
+ confirmLabel: "Delete task",
188
+ destructive: !0,
189
+ disabled: h.loading,
190
+ onConfirm: () => void v()
191
+ })]
192
+ }), g && /* @__PURE__ */ d(a, { message: g })]
193
+ });
194
+ }
195
+ //#endregion
196
+ export { E as AssigneeSelect, g as WORK_ITEM_PRIORITIES, h as WORK_ITEM_STATUSES, D as WorkItemAssigneeSelect, w as WorkItemEnumSelect, O as WorkItemHeaderActions, _ as enumLabel };
197
+
198
+ //# sourceMappingURL=WorkItemControls.js.map