@burdenoff/microfe-planmagnet 2026.925.13 → 2026.925.15

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 (88) hide show
  1. package/dist/generated/wspace-types.d.ts +14 -0
  2. package/dist/generated/wspace-types.d.ts.map +1 -1
  3. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  4. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +1 -1
  5. package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  6. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  7. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +1 -1
  8. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  9. package/dist/planmagnet/pages/GoalsGoalsPagePage.d.ts.map +1 -1
  10. package/dist/planmagnet/pages/GoalsGoalsPagePage.js +1 -1
  11. package/dist/planmagnet/pages/GoalsGoalsPagePage.js.map +1 -1
  12. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.d.ts.map +1 -1
  13. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js +1 -1
  14. package/dist/planmagnet/pages/GoalsObjectiveDetailPagePage.js.map +1 -1
  15. package/dist/planmagnet/pages/ProductsEditProductPagePage.d.ts.map +1 -1
  16. package/dist/planmagnet/pages/ProductsEditProductPagePage.js +1 -1
  17. package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
  18. package/dist/planmagnet/pages/ProductsEditReleasePagePage.d.ts.map +1 -1
  19. package/dist/planmagnet/pages/ProductsEditReleasePagePage.js +1 -1
  20. package/dist/planmagnet/pages/ProductsEditReleasePagePage.js.map +1 -1
  21. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.d.ts.map +1 -1
  22. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +1 -1
  23. package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
  24. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
  25. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +1 -1
  26. package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
  27. package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
  28. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +1 -1
  29. package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
  30. package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
  31. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +1 -1
  32. package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
  33. package/dist/planmagnet/pages/ProductsProductsPagePage.d.ts.map +1 -1
  34. package/dist/planmagnet/pages/ProductsProductsPagePage.js +1 -1
  35. package/dist/planmagnet/pages/ProductsProductsPagePage.js.map +1 -1
  36. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
  37. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +100 -94
  38. package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
  39. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.d.ts.map +1 -1
  40. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +1 -1
  41. package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
  42. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
  43. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +1 -1
  44. package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
  45. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.d.ts.map +1 -1
  46. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js +1 -1
  47. package/dist/planmagnet/pages/ProductsRoadmapsPagePage.js.map +1 -1
  48. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.d.ts.map +1 -1
  49. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js +1 -1
  50. package/dist/planmagnet/pages/ProjectsActiveProjectsPagePage.js.map +1 -1
  51. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
  52. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +1 -1
  53. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
  54. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
  55. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +1 -1
  56. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
  57. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  58. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +1 -1
  59. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  60. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  61. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +1 -1
  62. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  63. package/dist/planmagnet/pages/ReportsCreateReportPagePage.d.ts.map +1 -1
  64. package/dist/planmagnet/pages/ReportsCreateReportPagePage.js +1 -1
  65. package/dist/planmagnet/pages/ReportsCreateReportPagePage.js.map +1 -1
  66. package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
  67. package/dist/planmagnet/pages/ReportsReportsPagePage.js +1 -1
  68. package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
  69. package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
  70. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +1 -1
  71. package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
  72. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  73. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +1 -1
  74. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  75. package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
  76. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +1 -1
  77. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
  78. package/dist/planmagnet/pages/TeamInviteMemberPagePage.d.ts.map +1 -1
  79. package/dist/planmagnet/pages/TeamInviteMemberPagePage.js +1 -1
  80. package/dist/planmagnet/pages/TeamInviteMemberPagePage.js.map +1 -1
  81. package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
  82. package/dist/planmagnet/pages/TeamTeamPagePage.js +1 -1
  83. package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
  84. package/dist/planmagnet/releases/ReleaseGatesCard.d.ts +6 -0
  85. package/dist/planmagnet/releases/ReleaseGatesCard.d.ts.map +1 -0
  86. package/dist/planmagnet/releases/ReleaseGatesCard.js +173 -0
  87. package/dist/planmagnet/releases/ReleaseGatesCard.js.map +1 -0
  88. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TeamCreateTeamPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamCreateTeamPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, Users } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_PROJECTS_FOR_TEAM = gql`\n query GetProjectsForTeamCreation($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n key\n ownerId\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n ownerId: string | null;\n}\n\ninterface ProjectsData {\n projects: { items: ProjectNode[]; totalCount: number };\n}\n\nexport function TeamCreateTeamPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [form, setForm] = useState({ name: '', description: '', lead: '', projectId: '' });\n\n const { data, loading, error } = useQuery<ProjectsData>(GET_PROJECTS_FOR_TEAM, {\n variables: {\n workspaceId,\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // NOTE: there is no Team model in wspace-planmagnet-svc, so this form has nothing\n // to save to. It previously had a handler that navigated to '/team' on submit,\n // which read as a successful save — the user filled the form, was redirected, and\n // nothing was persisted. The submit control is disabled until the model exists\n // (BOFF-7822); the notice above the form says so.\n\n if (loading) {\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 return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Create Team</h1>\n <p className=\"text-sm text-muted-foreground\">\n Set up a new team with members and a lead.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" disabled title=\"Not available yet — see the notice below\">\n <Save className=\"mr-1 h-4 w-4\" />\n Create Team\n </Button>\n </div>\n </div>\n <div className=\"rounded-md border border-border bg-muted p-4 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">Not available yet.</strong> Teams cannot be saved —\n there is no team model behind this form. It is shown so the fields and options are visible,\n but nothing entered here is stored. This page previously returned you to the list on submit,\n which looked like it had saved.\n </div>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Users className=\"h-4 w-4\" />\n Team Details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Team Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Platform Engineering\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Describe the team's mission and responsibilities...\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Team Lead</label>\n <Input\n value={form.lead}\n onChange={(e) => setForm({ ...form, lead: e.target.value })}\n className=\"mt-1\"\n placeholder=\"Team lead name or email\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Associated Project (optional)</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n <option value=\"\">No project</option>\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name} ({p.key})\n </option>\n ))}\n </select>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamCreateTeamPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamCreateTeamPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, Users } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_PROJECTS_FOR_TEAM = gql`\n query GetProjectsForTeamCreation($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n key\n ownerId\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n ownerId: string | null;\n}\n\ninterface ProjectsData {\n projects: { items: ProjectNode[]; totalCount: number };\n}\n\nexport function TeamCreateTeamPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const [form, setForm] = useState({ name: '', description: '', lead: '', projectId: '' });\n\n const { data, loading, error } = useQuery<ProjectsData>(GET_PROJECTS_FOR_TEAM, {\n variables: {\n workspaceId,\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // NOTE: there is no Team model in wspace-planmagnet-svc, so this form has nothing\n // to save to. It previously had a handler that navigated to '/team' on submit,\n // which read as a successful save — the user filled the form, was redirected, and\n // nothing was persisted. The submit control is disabled until the model exists\n // (BOFF-7822); the notice above the form says so.\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 return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Create Team</h1>\n <p className=\"text-sm text-muted-foreground\">\n Set up a new team with members and a lead.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" disabled title=\"Not available yet — see the notice below\">\n <Save className=\"mr-1 h-4 w-4\" />\n Create Team\n </Button>\n </div>\n </div>\n <div className=\"rounded-md border border-border bg-muted p-4 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">Not available yet.</strong> Teams cannot be saved —\n there is no team model behind this form. It is shown so the fields and options are visible,\n but nothing entered here is stored. This page previously returned you to the list on submit,\n which looked like it had saved.\n </div>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Users className=\"h-4 w-4\" />\n Team Details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Team Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Platform Engineering\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Describe the team's mission and responsibilities...\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Team Lead</label>\n <Input\n value={form.lead}\n onChange={(e) => setForm({ ...form, lead: e.target.value })}\n className=\"mt-1\"\n placeholder=\"Team lead name or email\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Associated Project (optional)</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n <option value=\"\">No project</option>\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name} ({p.key})\n </option>\n ))}\n </select>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,wBAAwB,4CA0JvC"}
1
+ {"version":3,"file":"TeamInviteMemberPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,wBAAwB,4CA8JvC"}
@@ -51,7 +51,7 @@ function _() {
51
51
  name: `${n.charAt(0).toUpperCase()}${n.slice(1)} Team`
52
52
  }), e;
53
53
  }, []);
54
- return y ? /* @__PURE__ */ c("div", {
54
+ return y && !v ? /* @__PURE__ */ c("div", {
55
55
  className: "flex items-center justify-center p-12",
56
56
  children: /* @__PURE__ */ c("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
57
57
  }) : b ? /* @__PURE__ */ l("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"TeamInviteMemberPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamInviteMemberPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, UserPlus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_PROJECTS_FOR_INVITE = gql`\n query GetProjectsForInvite($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n key\n methodology\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n methodology: string | null;\n}\n\ninterface ProjectsData {\n projects: { items: ProjectNode[]; totalCount: number };\n}\n\nexport function TeamInviteMemberPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<ProjectsData>(GET_PROJECTS_FOR_INVITE, {\n variables: {\n workspaceId,\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const [form, setForm] = useState({\n email: '',\n role: 'developer',\n projectId: '',\n department: 'Engineering',\n });\n const roles = [\n 'developer',\n 'designer',\n 'product manager',\n 'qa engineer',\n 'engineering manager',\n 'data engineer',\n ] as const;\n const departments = ['Engineering', 'Product', 'Design', 'Quality', 'Data'] as const;\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n const teams = projects.reduce(\n (acc, p) => {\n const method = p.methodology ?? 'general';\n if (!acc.some((t) => t.id === method)) {\n acc.push({\n id: method,\n name: `${method.charAt(0).toUpperCase()}${method.slice(1)} Team`,\n });\n }\n return acc;\n },\n [] as Array<{ id: string; name: string }>\n );\n\n // NOTE: nothing here can send an invitation. wspace-planmagnet-svc has no Team or\n // membership model, and this MFE only talks to the workspaces gateway, which exposes\n // no invite mutation — the \"teams\" on the Team page are derived from project\n // methodologies, not real teams. The previous handler only called navigate('/team/team-page'),\n // so a user filled in an email, clicked Send Invite, was redirected, and no invitation\n // existed — indistinguishable from success. The submit control is disabled until there\n // is something to invite into (BOFF-7822); the notice above the form says so.\n\n if (loading) {\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 return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\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\">Add a new member to your workspace.</p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" disabled title=\"Not available yet — see the notice below\">\n <Save className=\"mr-1 h-4 w-4\" />\n Send Invite\n </Button>\n </div>\n </div>\n <div className=\"rounded-md border border-border bg-muted p-4 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">Not available yet.</strong> Invitations cannot be sent —\n there is no team or membership model behind this form, so nothing entered here is stored and\n no email is sent. The form is shown so the fields and options are visible.\n </div>\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 className=\"text-sm font-medium\">Email Address</label>\n <Input\n type=\"email\"\n value={form.email}\n onChange={(e) => setForm({ ...form, email: e.target.value })}\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Role</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.role}\n onChange={(e) => setForm({ ...form, role: e.target.value })}\n >\n {roles.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Department</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.department}\n onChange={(e) => setForm({ ...form, department: e.target.value })}\n >\n {departments.map((d) => (\n <option key={d} value={d}>\n {d}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Team</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n <option value=\"\">No team</option>\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n </div>\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":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, UserPlus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\n\nconst GET_PROJECTS_FOR_INVITE = gql`\n query GetProjectsForInvite($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n name\n key\n methodology\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n methodology: string | null;\n}\n\ninterface ProjectsData {\n projects: { items: ProjectNode[]; totalCount: number };\n}\n\nexport function TeamInviteMemberPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const { data, loading, error } = useQuery<ProjectsData>(GET_PROJECTS_FOR_INVITE, {\n variables: {\n workspaceId,\n pagination: { first: 100 },\n },\n skip: !workspaceId,\n });\n\n const [form, setForm] = useState({\n email: '',\n role: 'developer',\n projectId: '',\n department: 'Engineering',\n });\n const roles = [\n 'developer',\n 'designer',\n 'product manager',\n 'qa engineer',\n 'engineering manager',\n 'data engineer',\n ] as const;\n const departments = ['Engineering', 'Product', 'Design', 'Quality', 'Data'] as const;\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n const teams = projects.reduce(\n (acc, p) => {\n const method = p.methodology ?? 'general';\n if (!acc.some((t) => t.id === method)) {\n acc.push({\n id: method,\n name: `${method.charAt(0).toUpperCase()}${method.slice(1)} Team`,\n });\n }\n return acc;\n },\n [] as Array<{ id: string; name: string }>\n );\n\n // NOTE: nothing here can send an invitation. wspace-planmagnet-svc has no Team or\n // membership model, and this MFE only talks to the workspaces gateway, which exposes\n // no invite mutation — the \"teams\" on the Team page are derived from project\n // methodologies, not real teams. The previous handler only called navigate('/team/team-page'),\n // so a user filled in an email, clicked Send Invite, was redirected, and no invitation\n // existed — indistinguishable from success. The submit control is disabled until there\n // is something to invite into (BOFF-7822); the notice above the form says so.\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 return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\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\">Add a new member to your workspace.</p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/team/team-page')}>\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" disabled title=\"Not available yet — see the notice below\">\n <Save className=\"mr-1 h-4 w-4\" />\n Send Invite\n </Button>\n </div>\n </div>\n <div className=\"rounded-md border border-border bg-muted p-4 text-sm text-muted-foreground\">\n <strong className=\"text-foreground\">Not available yet.</strong> Invitations cannot be sent —\n there is no team or membership model behind this form, so nothing entered here is stored and\n no email is sent. The form is shown so the fields and options are visible.\n </div>\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 className=\"text-sm font-medium\">Email Address</label>\n <Input\n type=\"email\"\n value={form.email}\n onChange={(e) => setForm({ ...form, email: e.target.value })}\n className=\"mt-1\"\n placeholder=\"colleague@company.com\"\n />\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className=\"text-sm font-medium\">Role</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm capitalize\"\n value={form.role}\n onChange={(e) => setForm({ ...form, role: e.target.value })}\n >\n {roles.map((r) => (\n <option key={r} value={r}>\n {r}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Department</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.department}\n onChange={(e) => setForm({ ...form, department: e.target.value })}\n >\n {departments.map((d) => (\n <option key={d} value={d}>\n {d}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"text-sm font-medium\">Team</label>\n <select\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.projectId}\n onChange={(e) => setForm({ ...form, projectId: e.target.value })}\n >\n <option value=\"\">No team</option>\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamTeamPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamTeamPagePage.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,gBAAgB,4CAqK/B"}
1
+ {"version":3,"file":"TeamTeamPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/TeamTeamPagePage.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,gBAAgB,4CAyK/B"}
@@ -47,7 +47,7 @@ function p() {
47
47
  },
48
48
  skip: !n
49
49
  });
50
- if (m) return /* @__PURE__ */ i("div", {
50
+ if (m && !p) return /* @__PURE__ */ i("div", {
51
51
  className: "flex items-center justify-center p-12",
52
52
  children: /* @__PURE__ */ i("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
53
53
  });
@@ -1 +1 @@
1
- {"version":3,"file":"TeamTeamPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamTeamPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Users } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\n\nconst GET_TEAM_DATA = gql`\n query GetTeamData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n ) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n ownerId\n methodology\n }\n totalCount\n }\n planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n assigneeId\n storyPoints\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n ownerId: string | null;\n methodology: string | null;\n}\n\ninterface WorkItemNode {\n id: string;\n status: string;\n assigneeId: string | null;\n storyPoints: number | null;\n}\n\ninterface TeamData {\n projects: { items: ProjectNode[]; totalCount: number };\n workItems: { items: WorkItemNode[]; totalCount: number };\n}\n\nconst statusColor: Record<string, string> = {\n active: 'bg-green-400',\n away: 'bg-yellow-400',\n busy: 'bg-red-400',\n offline: 'bg-gray-300',\n};\n\nexport function TeamTeamPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n const { data, loading, error } = useQuery<TeamData>(GET_TEAM_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n },\n skip: !workspaceId,\n });\n\n if (loading) {\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 projects = data?.planmagnetProjects?.items ?? [];\n const workItems = data?.planmagnetWorkItems?.items ?? [];\n\n const assigneeIds = new Set(\n workItems.filter((wi) => wi.assigneeId).map((wi) => wi.assigneeId as string)\n );\n const ownerIds = new Set(projects.filter((p) => p.ownerId).map((p) => p.ownerId as string));\n const allMemberIds = new Set([...assigneeIds, ...ownerIds]);\n\n const members = Array.from(allMemberIds).map((memberId) => {\n const memberWorkItems = workItems.filter((wi) => wi.assigneeId === memberId);\n const assigned = memberWorkItems.length;\n const completed = memberWorkItems.filter((wi) => wi.status === 'DONE').length;\n const velocity = memberWorkItems\n .filter((wi) => wi.status === 'DONE')\n .reduce((s, wi) => s + (wi.storyPoints ?? 0), 0);\n const memberProjects = projects.filter((p) => p.ownerId === memberId);\n\n return {\n id: memberId,\n name: memberId,\n role: memberProjects.length > 0 ? 'Project Owner' : 'Contributor',\n department: 'Engineering',\n tasksAssigned: assigned,\n tasksCompleted: completed,\n velocity,\n status: assigned > 0 ? 'active' : 'offline',\n };\n });\n\n const teamsByMethodology = projects.reduce(\n (acc, p) => {\n const method = p.methodology ?? 'unassigned';\n if (!acc[method]) {\n acc[method] = { projects: [], ownerIds: new Set<string>() };\n }\n acc[method].projects.push(p);\n if (p.ownerId) acc[method].ownerIds.add(p.ownerId);\n return acc;\n },\n {} as Record<string, { projects: ProjectNode[]; ownerIds: Set<string> }>\n );\n\n const teams = Object.entries(teamsByMethodology).map(([methodology, group]) => ({\n id: methodology,\n name: `${methodology.charAt(0).toUpperCase()}${methodology.slice(1)} Team`,\n description: `Projects using ${methodology} methodology`,\n projectsCount: group.projects.length,\n leadId: Array.from(group.ownerIds)[0] ?? 'Unassigned',\n }));\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Team Overview</h1>\n <p className=\"text-sm text-muted-foreground\">\n {members.length} contributors across {teams.length} teams\n </p>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-medium\">Teams</h2>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {teams.map((team) => (\n <Card key={team.id}>\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge variant=\"outline\">{team.projectsCount} projects</Badge>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">{team.description}</p>\n <p className=\"text-xs text-muted-foreground mt-2\">\n Lead: <PersonName id={team.leadId} />\n </p>\n </CardContent>\n </Card>\n ))}\n {teams.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-2 text-center py-4\">\n No teams found. Create projects to see team groupings.\n </p>\n )}\n </div>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-medium\">Members</h2>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {members.map((member) => (\n <Card key={member.id}>\n <CardContent className=\"pt-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"relative\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center text-sm font-medium text-primary\">\n {member.name\n .split(/[\\s-]+/)\n .slice(0, 2)\n .map((n) => n[0]?.toUpperCase() ?? '')\n .join('')}\n </div>\n <div\n className={`absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-background ${statusColor[member.status]}`}\n />\n </div>\n <div>\n <p className=\"text-sm font-medium\">{member.name}</p>\n <p className=\"text-xs text-muted-foreground\">{member.role}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-3 gap-2 text-center text-xs\">\n <div>\n <p className=\"font-semibold\">{member.tasksAssigned}</p>\n <p className=\"text-muted-foreground\">Assigned</p>\n </div>\n <div>\n <p className=\"font-semibold\">{member.tasksCompleted}</p>\n <p className=\"text-muted-foreground\">Done</p>\n </div>\n <div>\n <p className=\"font-semibold\">{member.velocity}</p>\n <p className=\"text-muted-foreground\">Velocity</p>\n </div>\n </div>\n <div className=\"flex justify-between text-xs\">\n <span className=\"text-muted-foreground\">{member.department}</span>\n <span className=\"capitalize text-muted-foreground\">{member.status}</span>\n </div>\n </CardContent>\n </Card>\n ))}\n {members.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-3 text-center py-4\">\n No team members found. Assign work items to see members here.\n </p>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamTeamPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/TeamTeamPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Users } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\n\nconst GET_TEAM_DATA = gql`\n query GetTeamData(\n $workspaceId: ID!\n $projectPagination: PlanmagnetPaginationInput\n $workItemPagination: PlanmagnetPaginationInput\n ) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n ownerId\n methodology\n }\n totalCount\n }\n planmagnetWorkItems(workspaceId: $workspaceId, pagination: $workItemPagination) {\n items {\n id\n status\n assigneeId\n storyPoints\n }\n totalCount\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n ownerId: string | null;\n methodology: string | null;\n}\n\ninterface WorkItemNode {\n id: string;\n status: string;\n assigneeId: string | null;\n storyPoints: number | null;\n}\n\ninterface TeamData {\n projects: { items: ProjectNode[]; totalCount: number };\n workItems: { items: WorkItemNode[]; totalCount: number };\n}\n\nconst statusColor: Record<string, string> = {\n active: 'bg-green-400',\n away: 'bg-yellow-400',\n busy: 'bg-red-400',\n offline: 'bg-gray-300',\n};\n\nexport function TeamTeamPagePage() {\n const { workspaceId } = usePlanMagnet();\n\n const { data, loading, error } = useQuery<TeamData>(GET_TEAM_DATA, {\n variables: {\n workspaceId,\n projectPagination: { first: 100 },\n workItemPagination: { first: 500 },\n },\n skip: !workspaceId,\n });\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 projects = data?.planmagnetProjects?.items ?? [];\n const workItems = data?.planmagnetWorkItems?.items ?? [];\n\n const assigneeIds = new Set(\n workItems.filter((wi) => wi.assigneeId).map((wi) => wi.assigneeId as string)\n );\n const ownerIds = new Set(projects.filter((p) => p.ownerId).map((p) => p.ownerId as string));\n const allMemberIds = new Set([...assigneeIds, ...ownerIds]);\n\n const members = Array.from(allMemberIds).map((memberId) => {\n const memberWorkItems = workItems.filter((wi) => wi.assigneeId === memberId);\n const assigned = memberWorkItems.length;\n const completed = memberWorkItems.filter((wi) => wi.status === 'DONE').length;\n const velocity = memberWorkItems\n .filter((wi) => wi.status === 'DONE')\n .reduce((s, wi) => s + (wi.storyPoints ?? 0), 0);\n const memberProjects = projects.filter((p) => p.ownerId === memberId);\n\n return {\n id: memberId,\n name: memberId,\n role: memberProjects.length > 0 ? 'Project Owner' : 'Contributor',\n department: 'Engineering',\n tasksAssigned: assigned,\n tasksCompleted: completed,\n velocity,\n status: assigned > 0 ? 'active' : 'offline',\n };\n });\n\n const teamsByMethodology = projects.reduce(\n (acc, p) => {\n const method = p.methodology ?? 'unassigned';\n if (!acc[method]) {\n acc[method] = { projects: [], ownerIds: new Set<string>() };\n }\n acc[method].projects.push(p);\n if (p.ownerId) acc[method].ownerIds.add(p.ownerId);\n return acc;\n },\n {} as Record<string, { projects: ProjectNode[]; ownerIds: Set<string> }>\n );\n\n const teams = Object.entries(teamsByMethodology).map(([methodology, group]) => ({\n id: methodology,\n name: `${methodology.charAt(0).toUpperCase()}${methodology.slice(1)} Team`,\n description: `Projects using ${methodology} methodology`,\n projectsCount: group.projects.length,\n leadId: Array.from(group.ownerIds)[0] ?? 'Unassigned',\n }));\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Team Overview</h1>\n <p className=\"text-sm text-muted-foreground\">\n {members.length} contributors across {teams.length} teams\n </p>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-medium\">Teams</h2>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {teams.map((team) => (\n <Card key={team.id}>\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge variant=\"outline\">{team.projectsCount} projects</Badge>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">{team.description}</p>\n <p className=\"text-xs text-muted-foreground mt-2\">\n Lead: <PersonName id={team.leadId} />\n </p>\n </CardContent>\n </Card>\n ))}\n {teams.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-2 text-center py-4\">\n No teams found. Create projects to see team groupings.\n </p>\n )}\n </div>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-medium\">Members</h2>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {members.map((member) => (\n <Card key={member.id}>\n <CardContent className=\"pt-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"relative\">\n <div className=\"h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center text-sm font-medium text-primary\">\n {member.name\n .split(/[\\s-]+/)\n .slice(0, 2)\n .map((n) => n[0]?.toUpperCase() ?? '')\n .join('')}\n </div>\n <div\n className={`absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-background ${statusColor[member.status]}`}\n />\n </div>\n <div>\n <p className=\"text-sm font-medium\">{member.name}</p>\n <p className=\"text-xs text-muted-foreground\">{member.role}</p>\n </div>\n </div>\n <div className=\"grid grid-cols-3 gap-2 text-center text-xs\">\n <div>\n <p className=\"font-semibold\">{member.tasksAssigned}</p>\n <p className=\"text-muted-foreground\">Assigned</p>\n </div>\n <div>\n <p className=\"font-semibold\">{member.tasksCompleted}</p>\n <p className=\"text-muted-foreground\">Done</p>\n </div>\n <div>\n <p className=\"font-semibold\">{member.velocity}</p>\n <p className=\"text-muted-foreground\">Velocity</p>\n </div>\n </div>\n <div className=\"flex justify-between text-xs\">\n <span className=\"text-muted-foreground\">{member.department}</span>\n <span className=\"capitalize text-muted-foreground\">{member.status}</span>\n </div>\n </CardContent>\n </Card>\n ))}\n {members.length === 0 && (\n <p className=\"text-sm text-muted-foreground col-span-3 text-center py-4\">\n No team members found. Assign work items to see members here.\n </p>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function ReleaseGatesCard({ releaseId, projectId, releaseStatus, }: {
2
+ releaseId: string;
3
+ projectId: string | null;
4
+ releaseStatus: string;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=ReleaseGatesCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseGatesCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/releases/ReleaseGatesCard.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,SAAS,EACT,aAAa,GACd,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB,2CA8HA"}
@@ -0,0 +1,173 @@
1
+ import { PersonName as e } from "../people/People.js";
2
+ import { throwOnError as t } from "../utils/mutationResult.js";
3
+ import { useState as n } from "react";
4
+ import { CheckCircle2 as r, CircleDashed as i, ShieldAlert as a, XCircle as o } from "lucide-react";
5
+ import { gql as s } from "@apollo/client";
6
+ import { useMutation as c, useQuery as l } from "@apollo/client/react";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
+ import { Button as f, Card as p, CardContent as m, CardHeader as h, CardTitle as g, Input as _ } from "@burdenoff/fe-libs/ui";
9
+ //#region src/planmagnet/releases/ReleaseGatesCard.tsx
10
+ var v = s`
11
+ query ReleaseGates($id: ID!) {
12
+ planmagnetRelease(id: $id) {
13
+ id
14
+ qualityGates {
15
+ id
16
+ name
17
+ status
18
+ reviewerId
19
+ reviewedAt
20
+ }
21
+ }
22
+ }
23
+ `, y = s`
24
+ mutation ReleaseGateCreate($input: PlanmagnetCreateQualityGateInput!) {
25
+ planmagnetCreateQualityGate(input: $input) {
26
+ id
27
+ }
28
+ }
29
+ `, b = s`
30
+ mutation ReleaseGateReview($id: ID!, $input: PlanmagnetReviewQualityGateInput!) {
31
+ planmagnetReviewQualityGate(id: $id, input: $input) {
32
+ id
33
+ status
34
+ }
35
+ }
36
+ `, x = new Set(["PASSED", "WAIVED"]);
37
+ function S({ status: e }) {
38
+ return e === "PASSED" ? /* @__PURE__ */ d("span", {
39
+ className: "inline-flex items-center gap-1 text-xs text-status-success-text",
40
+ children: [/* @__PURE__ */ u(r, { className: "h-3.5 w-3.5" }), " Passed"]
41
+ }) : e === "WAIVED" ? /* @__PURE__ */ d("span", {
42
+ className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
43
+ children: [/* @__PURE__ */ u(r, { className: "h-3.5 w-3.5" }), " Waived"]
44
+ }) : e === "FAILED" ? /* @__PURE__ */ d("span", {
45
+ className: "inline-flex items-center gap-1 text-xs text-status-error-text",
46
+ children: [/* @__PURE__ */ u(o, { className: "h-3.5 w-3.5" }), " Failed"]
47
+ }) : /* @__PURE__ */ d("span", {
48
+ className: "inline-flex items-center gap-1 text-xs text-status-warning-text",
49
+ children: [
50
+ /* @__PURE__ */ u(i, { className: "h-3.5 w-3.5" }),
51
+ " ",
52
+ e === "IN_REVIEW" ? "In review" : "Pending"
53
+ ]
54
+ });
55
+ }
56
+ function C({ releaseId: r, projectId: i, releaseStatus: o }) {
57
+ let [s, C] = n(""), [w, T] = n(null), { data: E } = l(v, { variables: { id: r } }), D = {
58
+ refetchQueries: ["ReleaseGates", "GetRelease"],
59
+ awaitRefetchQueries: !0
60
+ }, [O, k] = c(y, D), [A, j] = c(b, D), M = E?.planmagnetRelease?.qualityGates ?? [], N = M.filter((e) => !x.has(e.status)), P = o === "RELEASED", F = async (e, n, r) => {
61
+ T(null);
62
+ try {
63
+ await t(e()), r?.();
64
+ } catch (e) {
65
+ T(e instanceof Error ? e.message : n);
66
+ }
67
+ };
68
+ return /* @__PURE__ */ d(p, {
69
+ "data-testid": "release-gates",
70
+ children: [/* @__PURE__ */ u(h, { children: /* @__PURE__ */ d(g, {
71
+ className: "flex items-center gap-2",
72
+ children: [/* @__PURE__ */ u(a, { className: "h-4 w-4" }), " Quality Gates"]
73
+ }) }), /* @__PURE__ */ d(m, {
74
+ className: "space-y-3",
75
+ children: [
76
+ w && /* @__PURE__ */ u("p", {
77
+ role: "alert",
78
+ className: "text-sm text-destructive",
79
+ children: w
80
+ }),
81
+ !P && N.length > 0 && /* @__PURE__ */ d("p", {
82
+ className: "text-sm text-status-warning-text",
83
+ "data-testid": "release-held",
84
+ children: [
85
+ "This release cannot be released until ",
86
+ N.length,
87
+ " gate",
88
+ N.length === 1 ? "" : "s",
89
+ " ",
90
+ N.length === 1 ? "is" : "are",
91
+ " passed or waived:",
92
+ " ",
93
+ N.map((e) => e.name).join(", "),
94
+ "."
95
+ ]
96
+ }),
97
+ M.length === 0 && /* @__PURE__ */ u("p", {
98
+ className: "text-sm text-muted-foreground",
99
+ children: "No quality gates hold this release."
100
+ }),
101
+ /* @__PURE__ */ u("ul", {
102
+ className: "space-y-2",
103
+ children: M.map((t) => /* @__PURE__ */ d("li", {
104
+ className: "rounded-md border p-2 space-y-1",
105
+ "data-testid": "release-gate",
106
+ children: [
107
+ /* @__PURE__ */ d("div", {
108
+ className: "flex items-center justify-between gap-2",
109
+ children: [/* @__PURE__ */ u("span", {
110
+ className: "text-sm font-medium",
111
+ children: t.name
112
+ }), /* @__PURE__ */ u(S, { status: t.status })]
113
+ }),
114
+ t.reviewerId && /* @__PURE__ */ d("p", {
115
+ className: "text-xs text-muted-foreground",
116
+ children: [
117
+ "Reviewed by ",
118
+ /* @__PURE__ */ u(e, { id: t.reviewerId }),
119
+ t.reviewedAt ? ` · ${new Date(t.reviewedAt).toLocaleString()}` : ""
120
+ ]
121
+ }),
122
+ /* @__PURE__ */ u("div", {
123
+ className: "flex gap-1",
124
+ children: [
125
+ "PASSED",
126
+ "FAILED",
127
+ "WAIVED"
128
+ ].map((e) => /* @__PURE__ */ u(f, {
129
+ size: "sm",
130
+ variant: "outline",
131
+ className: "h-7 text-xs",
132
+ disabled: j.loading || t.status === e,
133
+ onClick: () => F(() => A({ variables: {
134
+ id: t.id,
135
+ input: { status: e }
136
+ } }), "The gate could not be reviewed."),
137
+ children: e === "PASSED" ? "Pass" : e === "FAILED" ? "Fail" : "Waive"
138
+ }, e))
139
+ })
140
+ ]
141
+ }, t.id))
142
+ }),
143
+ i ? /* @__PURE__ */ d("div", {
144
+ className: "flex gap-2 border-t pt-3",
145
+ children: [/* @__PURE__ */ u(_, {
146
+ "aria-label": "New gate name",
147
+ placeholder: "Add a gate, e.g. Security review",
148
+ value: s,
149
+ onChange: (e) => C(e.target.value)
150
+ }), /* @__PURE__ */ u(f, {
151
+ size: "sm",
152
+ disabled: !s.trim() || k.loading,
153
+ "data-testid": "release-gate-add",
154
+ onClick: () => F(() => O({ variables: { input: {
155
+ projectId: i,
156
+ releaseId: r,
157
+ name: s.trim(),
158
+ criteria: {}
159
+ } } }), "The gate could not be added.", () => C("")),
160
+ children: "Add gate"
161
+ })]
162
+ }) : /* @__PURE__ */ u("p", {
163
+ className: "text-xs text-muted-foreground",
164
+ children: "This release has no project, so gates cannot be added here."
165
+ })
166
+ ]
167
+ })]
168
+ });
169
+ }
170
+ //#endregion
171
+ export { C as ReleaseGatesCard };
172
+
173
+ //# sourceMappingURL=ReleaseGatesCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseGatesCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseGatesCard.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 { CheckCircle2, CircleDashed, ShieldAlert, XCircle } from 'lucide-react';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * Quality gates holding a release (P1.4). The service refuses RELEASED while\n * any gate here is not PASSED or WAIVED; this card shows which gates hold the\n * release, and lets a reviewer add, pass, fail or waive them. There was no\n * quality-gate UI anywhere before this.\n */\n\nconst GATES = gql`\n query ReleaseGates($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n qualityGates {\n id\n name\n status\n reviewerId\n reviewedAt\n }\n }\n }\n`;\nconst CREATE_GATE = gql`\n mutation ReleaseGateCreate($input: PlanmagnetCreateQualityGateInput!) {\n planmagnetCreateQualityGate(input: $input) {\n id\n }\n }\n`;\nconst REVIEW_GATE = gql`\n mutation ReleaseGateReview($id: ID!, $input: PlanmagnetReviewQualityGateInput!) {\n planmagnetReviewQualityGate(id: $id, input: $input) {\n id\n status\n }\n }\n`;\n\ninterface Gate {\n id: string;\n name: string;\n status: string;\n reviewerId: string | null;\n reviewedAt: string | null;\n}\n\nconst CLEARING = new Set(['PASSED', 'WAIVED']);\n\nfunction StatusMark({ status }: { status: string }) {\n // Status carries an icon AND a label, never colour alone.\n if (status === 'PASSED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Passed\n </span>\n );\n if (status === 'WAIVED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Waived\n </span>\n );\n if (status === 'FAILED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <XCircle className=\"h-3.5 w-3.5\" /> Failed\n </span>\n );\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-warning-text\">\n <CircleDashed className=\"h-3.5 w-3.5\" /> {status === 'IN_REVIEW' ? 'In review' : 'Pending'}\n </span>\n );\n}\n\nexport function ReleaseGatesCard({\n releaseId,\n projectId,\n releaseStatus,\n}: {\n releaseId: string;\n projectId: string | null;\n releaseStatus: string;\n}) {\n const [name, setName] = useState('');\n const [error, setError] = useState<string | null>(null);\n const { data } = useQuery<{ planmagnetRelease: { qualityGates: Gate[] } | null }>(GATES, {\n variables: { id: releaseId },\n });\n const refetch = { refetchQueries: ['ReleaseGates', 'GetRelease'], awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_GATE, refetch);\n const [review, reviewState] = useMutation(REVIEW_GATE, refetch);\n\n const gates = data?.planmagnetRelease?.qualityGates ?? [];\n const open = gates.filter((g) => !CLEARING.has(g.status));\n const shipped = releaseStatus === 'RELEASED';\n\n const run = async (\n action: () => Promise<{ error?: unknown }>,\n fallback: string,\n after?: () => void\n ) => {\n setError(null);\n try {\n await throwOnError(action());\n after?.();\n } catch (e) {\n setError(e instanceof Error ? e.message : fallback);\n }\n };\n\n return (\n <Card data-testid=\"release-gates\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <ShieldAlert className=\"h-4 w-4\" /> Quality Gates\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n )}\n {!shipped && open.length > 0 && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"release-held\">\n This release cannot be released until {open.length} gate{open.length === 1 ? '' : 's'}{' '}\n {open.length === 1 ? 'is' : 'are'} passed or waived:{' '}\n {open.map((g) => g.name).join(', ')}.\n </p>\n )}\n {gates.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No quality gates hold this release.</p>\n )}\n <ul className=\"space-y-2\">\n {gates.map((gate) => (\n <li\n key={gate.id}\n className=\"rounded-md border p-2 space-y-1\"\n data-testid=\"release-gate\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">{gate.name}</span>\n <StatusMark status={gate.status} />\n </div>\n {gate.reviewerId && (\n <p className=\"text-xs text-muted-foreground\">\n Reviewed by <PersonName id={gate.reviewerId} />\n {gate.reviewedAt ? ` · ${new Date(gate.reviewedAt).toLocaleString()}` : ''}\n </p>\n )}\n <div className=\"flex gap-1\">\n {(['PASSED', 'FAILED', 'WAIVED'] as const).map((status) => (\n <Button\n key={status}\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={reviewState.loading || gate.status === status}\n onClick={() =>\n run(\n () => review({ variables: { id: gate.id, input: { status } } }),\n 'The gate could not be reviewed.'\n )\n }\n >\n {status === 'PASSED' ? 'Pass' : status === 'FAILED' ? 'Fail' : 'Waive'}\n </Button>\n ))}\n </div>\n </li>\n ))}\n </ul>\n {projectId ? (\n <div className=\"flex gap-2 border-t pt-3\">\n <Input\n aria-label=\"New gate name\"\n placeholder=\"Add a gate, e.g. Security review\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n <Button\n size=\"sm\"\n disabled={!name.trim() || createState.loading}\n data-testid=\"release-gate-add\"\n onClick={() =>\n run(\n () =>\n create({\n variables: {\n input: { projectId, releaseId, name: name.trim(), criteria: {} },\n },\n }),\n 'The gate could not be added.',\n () => setName('')\n )\n }\n >\n Add gate\n </Button>\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n This release has no project, so gates cannot be added here.\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.925.13",
3
+ "version": "2026.925.15",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -26,10 +26,11 @@
26
26
  "lint": "eslint .",
27
27
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
28
28
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
29
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema && bun run check:nav && bun run check:mutations && bun run lint && bun run format && bun run type:check && bun run build && bun run build-storybook'",
29
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema && bun run check:nav && bun run check:mutations && bun run check:refetch && bun run lint && bun run format && bun run type:check && bun run build && bun run build-storybook'",
30
30
  "validate:schema": "graphql-schema-drift",
31
31
  "check:nav": "bun scripts/check-nav-targets.ts",
32
32
  "check:mutations": "bun scripts/check-mutation-errors.ts",
33
+ "check:refetch": "bun scripts/check-refetch-blanking.ts",
33
34
  "schema:pull": "graphql-codegen --project schema-wspace && graphql-codegen --project schema-global && bun run schema:roots",
34
35
  "schema:roots": "bun scripts/gen-gateway-roots.mjs",
35
36
  "codegen": "graphql-codegen --project wspace && graphql-codegen --project global",