@burdenoff/microfe-planmagnet 2026.731.1 → 2026.801.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.d.ts +54 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +35 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsEditProductPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsEditReleasePagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsEditReleasePagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsFeatureAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +101 -47
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/providers/PlanMagnetProvider.js +1 -1
- package/dist/planmagnet/providers/PlanMagnetProvider.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsReleaseDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsReleaseDetailPagePage.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 { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { GitBranch, Calendar, Clock, Server, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router-dom';\n\nconst GET_RELEASE = gql`\n query GetRelease($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n workspaceId\n projectId\n name\n description\n version\n status\n plannedDate\n releasedAt\n releaseNotes\n ownerId\n tags\n metadata\n createdAt\n updatedAt\n workItems {\n id\n title\n status\n type\n priority\n }\n }\n }\n`;\n\ninterface WorkItemSummary {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PLANNING: 'bg-yellow-100 text-yellow-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n READY: 'bg-orange-100 text-orange-800',\n RELEASED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n ROLLED_BACK: 'bg-red-100 text-red-800',\n};\n\nexport function ProductsReleaseDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const releaseId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_RELEASE, {\n variables: { id: releaseId },\n skip: !releaseId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load release: {error.message}</div>;\n }\n\n const release = data?.planmagnetRelease;\n if (!release) return <div className=\"p-6 text-muted-foreground\">Release not found.</div>;\n\n const workItems: WorkItemSummary[] = release.workItems ?? [];\n const featureItems = workItems.filter((w) => w.type === 'STORY' || w.type === 'FEATURE');\n const bugItems = workItems.filter((w) => w.type === 'BUG');\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <GitBranch className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{release.version}</h1>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[release.status] ?? 'bg-gray-100'}`}\n >\n {release.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">{release.name}</p>\n </div>\n <Badge variant=\"outline\" className=\"capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n {release.description ? (\n <Markdown>{release.description}</Markdown>\n ) : (\n <p>No description provided.</p>\n )}\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{featureItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Features</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{bugItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Bug Fixes</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{workItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Total Items</p>\n </div>\n </div>\n </CardContent>\n </Card>\n\n {release.releaseNotes && (\n <Card>\n <CardHeader>\n <CardTitle>Release Notes</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n <Markdown>{release.releaseNotes}</Markdown>\n </div>\n </CardContent>\n </Card>\n )}\n\n {workItems.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Work Items ({workItems.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {workItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {item.type.toLowerCase()} · {item.priority.toLowerCase()} priority\n </p>\n </div>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Timeline</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Planned:</span>\n <span className=\"font-medium\">\n {release.plannedDate\n ? new Date(release.plannedDate).toLocaleDateString()\n : 'Not set'}\n </span>\n </div>\n {release.releasedAt && (\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4 text-green-500\" />\n <span className=\"text-muted-foreground\">Released:</span>\n <span className=\"font-medium\">\n {new Date(release.releasedAt).toLocaleDateString()}\n </span>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Version</span>\n <span className=\"font-medium\">{release.version}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>{release.ownerId}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(release.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(release.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {release.tags?.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {release.tags.map((tag: string) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsReleaseDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsReleaseDetailPagePage.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 { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { GitBranch, Calendar, Clock, Server, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\n\nconst GET_RELEASE = gql`\n query GetRelease($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n workspaceId\n projectId\n name\n description\n version\n status\n plannedDate\n releasedAt\n releaseNotes\n ownerId\n tags\n metadata\n createdAt\n updatedAt\n workItems {\n id\n title\n status\n type\n priority\n }\n }\n }\n`;\n\ninterface WorkItemSummary {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PLANNING: 'bg-yellow-100 text-yellow-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n READY: 'bg-orange-100 text-orange-800',\n RELEASED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n ROLLED_BACK: 'bg-red-100 text-red-800',\n};\n\nexport function ProductsReleaseDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const releaseId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_RELEASE, {\n variables: { id: releaseId },\n skip: !releaseId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load release: {error.message}</div>;\n }\n\n const release = data?.planmagnetRelease;\n if (!release) return <div className=\"p-6 text-muted-foreground\">Release not found.</div>;\n\n const workItems: WorkItemSummary[] = release.workItems ?? [];\n const featureItems = workItems.filter((w) => w.type === 'STORY' || w.type === 'FEATURE');\n const bugItems = workItems.filter((w) => w.type === 'BUG');\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <GitBranch className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{release.version}</h1>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[release.status] ?? 'bg-gray-100'}`}\n >\n {release.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">{release.name}</p>\n </div>\n <Badge variant=\"outline\" className=\"capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n {release.description ? (\n <Markdown>{release.description}</Markdown>\n ) : (\n <p>No description provided.</p>\n )}\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{featureItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Features</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{bugItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Bug Fixes</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{workItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Total Items</p>\n </div>\n </div>\n </CardContent>\n </Card>\n\n {release.releaseNotes && (\n <Card>\n <CardHeader>\n <CardTitle>Release Notes</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n <Markdown>{release.releaseNotes}</Markdown>\n </div>\n </CardContent>\n </Card>\n )}\n\n {workItems.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Work Items ({workItems.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {workItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {item.type.toLowerCase()} · {item.priority.toLowerCase()} priority\n </p>\n </div>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Timeline</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Planned:</span>\n <span className=\"font-medium\">\n {release.plannedDate\n ? new Date(release.plannedDate).toLocaleDateString()\n : 'Not set'}\n </span>\n </div>\n {release.releasedAt && (\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4 text-green-500\" />\n <span className=\"text-muted-foreground\">Released:</span>\n <span className=\"font-medium\">\n {new Date(release.releasedAt).toLocaleDateString()}\n </span>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Version</span>\n <span className=\"font-medium\">{release.version}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>{release.ownerId}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(release.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(release.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {release.tags?.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {release.tags.map((tag: string) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { AlertTriangle as t, BarChart3 as n, CheckCircle as r, Loader2 as i, Target as a, TrendingUp as o } from "lucide-react";
|
|
3
|
-
import { useParams as s } from "react-router
|
|
3
|
+
import { useParams as s } from "react-router";
|
|
4
4
|
import { gql as c } from "@apollo/client";
|
|
5
5
|
import { useQuery as l } from "@apollo/client/react";
|
|
6
6
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsRoadmapAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.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 } from '@burdenoff/fe-libs/ui';\nimport { TrendingUp, Target, CheckCircle, AlertTriangle, BarChart3, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router-dom';\n\nconst GET_ROADMAP_ANALYTICS = gql`\n query GetRoadmapAnalytics($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n name\n description\n type\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n title\n status\n startDate\n endDate\n swimlane\n }\n }\n }\n`;\n\nconst LIST_FEATURE_REQUESTS_FOR_ANALYTICS = gql`\n query ListFeatureRequestsForRoadmapAnalytics(\n $workspaceId: ID!\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n status\n voteCount\n businessValue\n priorityScore\n }\n totalCount\n }\n }\n`;\n\ninterface RoadmapItemNode {\n id: string;\n title: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n swimlane: string | null;\n}\n\nexport function ProductsRoadmapAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP_ANALYTICS, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n const {\n data: featuresData,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(LIST_FEATURE_REQUESTS_FOR_ANALYTICS, {\n variables: { workspaceId, pagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load analytics: {error.message}</div>;\n }\n\n const roadmap = data?.planmagnetRoadmap;\n if (!roadmap) return <div className=\"p-6 text-muted-foreground\">Roadmap not found.</div>;\n\n const items: RoadmapItemNode[] = roadmap.items ?? [];\n const allFeatures = featuresData?.planmagnetFeatureRequests?.items ?? [];\n\n const completedItems = items.filter((i) => i.status === 'COMPLETED' || i.status === 'DONE');\n const progress = items.length > 0 ? Math.round((completedItems.length / items.length) * 100) : 0;\n\n // Items by status\n const itemsByStatus: Record<string, number> = {};\n for (const item of items) {\n itemsByStatus[item.status] = (itemsByStatus[item.status] || 0) + 1;\n }\n\n // Feature status distribution\n const featuresByStatus: Record<string, number> = {};\n for (const f of allFeatures) {\n featuresByStatus[f.status] = (featuresByStatus[f.status] || 0) + 1;\n }\n\n // Items by swimlane\n const itemsBySwimlane: Record<string, number> = {};\n for (const item of items) {\n const lane = item.swimlane ?? 'Uncategorized';\n itemsBySwimlane[lane] = (itemsBySwimlane[lane] || 0) + 1;\n }\n\n // Items with overdue end dates\n const now = new Date();\n const overdueItems = items.filter(\n (i) => i.endDate && new Date(i.endDate) < now && i.status !== 'COMPLETED' && i.status !== 'DONE'\n );\n\n // Health score based on progress and overdue items\n const healthScore = Math.max(\n 0,\n Math.round(progress * 0.7 + (1 - overdueItems.length / Math.max(items.length, 1)) * 30)\n );\n\n // Monthly velocity (items completed by month based on endDate)\n const velocityMap: Record<string, { planned: number; completed: number }> = {};\n for (const item of items) {\n if (item.endDate) {\n const month = new Date(item.endDate).toLocaleDateString('en-US', {\n month: 'short',\n year: '2-digit',\n });\n if (!velocityMap[month]) velocityMap[month] = { planned: 0, completed: 0 };\n velocityMap[month].planned += 1;\n if (item.status === 'COMPLETED' || item.status === 'DONE') {\n velocityMap[month].completed += 1;\n }\n }\n }\n const velocityData = Object.entries(velocityMap)\n .map(([month, vals]) => ({ month, ...vals }))\n .slice(-6);\n const maxVal = Math.max(...velocityData.map((d) => Math.max(d.planned, d.completed)), 1);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Roadmap Analytics</h1>\n <p className=\"text-sm text-muted-foreground\">\n {roadmap.name} - {roadmap.type.toLowerCase()}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n Health Score\n </div>\n <p className=\"text-2xl font-bold mt-1\">{healthScore}/100</p>\n <div className=\"w-full h-2 rounded-full bg-muted mt-2 overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-green-500\"\n style={{ width: `${healthScore}%` }}\n />\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n Progress\n </div>\n <p className=\"text-2xl font-bold mt-1\">{progress}%</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{items.length} items total</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <CheckCircle className=\"h-4 w-4\" />\n Completed\n </div>\n <p className=\"text-2xl font-bold mt-1\">{completedItems.length}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">items completed</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <AlertTriangle className=\"h-4 w-4\" />\n Overdue\n </div>\n <p className=\"text-2xl font-bold mt-1\">{overdueItems.length}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">items past end date</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n Item Velocity\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-8\">\n No timeline data available\n </p>\n ) : (\n <div className=\"flex items-end gap-2 h-48\">\n {velocityData.map((d) => (\n <div key={d.month} className=\"flex-1 flex flex-col items-center gap-1\">\n <div className=\"flex gap-0.5 items-end w-full justify-center h-40\">\n <div\n className=\"w-3 bg-primary/30 rounded-t\"\n style={{ height: `${(d.planned / maxVal) * 100}%` }}\n title={`Planned: ${d.planned}`}\n />\n <div\n className=\"w-3 bg-primary rounded-t\"\n style={{ height: `${(d.completed / maxVal) * 100}%` }}\n title={`Completed: ${d.completed}`}\n />\n </div>\n <span className=\"text-xs text-muted-foreground\">{d.month}</span>\n </div>\n ))}\n </div>\n )}\n <div className=\"flex gap-4 justify-center mt-3 text-xs\">\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded bg-primary/30\" />\n Planned\n </span>\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded bg-primary\" />\n Completed\n </span>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Item Status Breakdown</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(itemsByStatus).map(([status, count]) => {\n const total = items.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const colors: Record<string, string> = {\n TODO: 'bg-gray-400',\n PLANNED: 'bg-yellow-400',\n IN_PROGRESS: 'bg-blue-400',\n BLOCKED: 'bg-red-400',\n DONE: 'bg-green-400',\n COMPLETED: 'bg-green-400',\n };\n return (\n <div key={status}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span className=\"capitalize\">{status.toLowerCase().replace('_', ' ')}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${colors[status] ?? 'bg-gray-300'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Feature Request Status</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(featuresByStatus).length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No feature request data\n </p>\n ) : (\n Object.entries(featuresByStatus).map(([status, count]) => {\n const total = allFeatures.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const colors: Record<string, string> = {\n SUBMITTED: 'bg-gray-400',\n UNDER_REVIEW: 'bg-yellow-400',\n PLANNED: 'bg-blue-400',\n IN_PROGRESS: 'bg-purple-400',\n COMPLETED: 'bg-green-400',\n DECLINED: 'bg-red-400',\n };\n return (\n <div key={status}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span className=\"capitalize\">{status.toLowerCase().replace('_', ' ')}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${colors[status] ?? 'bg-gray-300'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Swimlane Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(itemsBySwimlane).length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">No swimlane data</p>\n ) : (\n Object.entries(itemsBySwimlane).map(([lane, count]) => {\n const total = items.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n return (\n <div key={lane}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span>{lane}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-primary\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })\n )}\n </CardContent>\n </Card>\n </div>\n\n {overdueItems.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Overdue Items</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {overdueItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground\">\n Due: {item.endDate ? new Date(item.endDate).toLocaleDateString() : 'N/A'}\n </p>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-800 capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsRoadmapAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsRoadmapAnalyticsPagePage.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 } from '@burdenoff/fe-libs/ui';\nimport { TrendingUp, Target, CheckCircle, AlertTriangle, BarChart3, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\n\nconst GET_ROADMAP_ANALYTICS = gql`\n query GetRoadmapAnalytics($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n name\n description\n type\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n title\n status\n startDate\n endDate\n swimlane\n }\n }\n }\n`;\n\nconst LIST_FEATURE_REQUESTS_FOR_ANALYTICS = gql`\n query ListFeatureRequestsForRoadmapAnalytics(\n $workspaceId: ID!\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: $pagination) {\n items {\n id\n status\n voteCount\n businessValue\n priorityScore\n }\n totalCount\n }\n }\n`;\n\ninterface RoadmapItemNode {\n id: string;\n title: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n swimlane: string | null;\n}\n\nexport function ProductsRoadmapAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP_ANALYTICS, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n const {\n data: featuresData,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(LIST_FEATURE_REQUESTS_FOR_ANALYTICS, {\n variables: { workspaceId, pagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load analytics: {error.message}</div>;\n }\n\n const roadmap = data?.planmagnetRoadmap;\n if (!roadmap) return <div className=\"p-6 text-muted-foreground\">Roadmap not found.</div>;\n\n const items: RoadmapItemNode[] = roadmap.items ?? [];\n const allFeatures = featuresData?.planmagnetFeatureRequests?.items ?? [];\n\n const completedItems = items.filter((i) => i.status === 'COMPLETED' || i.status === 'DONE');\n const progress = items.length > 0 ? Math.round((completedItems.length / items.length) * 100) : 0;\n\n // Items by status\n const itemsByStatus: Record<string, number> = {};\n for (const item of items) {\n itemsByStatus[item.status] = (itemsByStatus[item.status] || 0) + 1;\n }\n\n // Feature status distribution\n const featuresByStatus: Record<string, number> = {};\n for (const f of allFeatures) {\n featuresByStatus[f.status] = (featuresByStatus[f.status] || 0) + 1;\n }\n\n // Items by swimlane\n const itemsBySwimlane: Record<string, number> = {};\n for (const item of items) {\n const lane = item.swimlane ?? 'Uncategorized';\n itemsBySwimlane[lane] = (itemsBySwimlane[lane] || 0) + 1;\n }\n\n // Items with overdue end dates\n const now = new Date();\n const overdueItems = items.filter(\n (i) => i.endDate && new Date(i.endDate) < now && i.status !== 'COMPLETED' && i.status !== 'DONE'\n );\n\n // Health score based on progress and overdue items\n const healthScore = Math.max(\n 0,\n Math.round(progress * 0.7 + (1 - overdueItems.length / Math.max(items.length, 1)) * 30)\n );\n\n // Monthly velocity (items completed by month based on endDate)\n const velocityMap: Record<string, { planned: number; completed: number }> = {};\n for (const item of items) {\n if (item.endDate) {\n const month = new Date(item.endDate).toLocaleDateString('en-US', {\n month: 'short',\n year: '2-digit',\n });\n if (!velocityMap[month]) velocityMap[month] = { planned: 0, completed: 0 };\n velocityMap[month].planned += 1;\n if (item.status === 'COMPLETED' || item.status === 'DONE') {\n velocityMap[month].completed += 1;\n }\n }\n }\n const velocityData = Object.entries(velocityMap)\n .map(([month, vals]) => ({ month, ...vals }))\n .slice(-6);\n const maxVal = Math.max(...velocityData.map((d) => Math.max(d.planned, d.completed)), 1);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Roadmap Analytics</h1>\n <p className=\"text-sm text-muted-foreground\">\n {roadmap.name} - {roadmap.type.toLowerCase()}\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <Target className=\"h-4 w-4\" />\n Health Score\n </div>\n <p className=\"text-2xl font-bold mt-1\">{healthScore}/100</p>\n <div className=\"w-full h-2 rounded-full bg-muted mt-2 overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-green-500\"\n style={{ width: `${healthScore}%` }}\n />\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <TrendingUp className=\"h-4 w-4\" />\n Progress\n </div>\n <p className=\"text-2xl font-bold mt-1\">{progress}%</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{items.length} items total</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <CheckCircle className=\"h-4 w-4\" />\n Completed\n </div>\n <p className=\"text-2xl font-bold mt-1\">{completedItems.length}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">items completed</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <AlertTriangle className=\"h-4 w-4\" />\n Overdue\n </div>\n <p className=\"text-2xl font-bold mt-1\">{overdueItems.length}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">items past end date</p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n Item Velocity\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityData.length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-8\">\n No timeline data available\n </p>\n ) : (\n <div className=\"flex items-end gap-2 h-48\">\n {velocityData.map((d) => (\n <div key={d.month} className=\"flex-1 flex flex-col items-center gap-1\">\n <div className=\"flex gap-0.5 items-end w-full justify-center h-40\">\n <div\n className=\"w-3 bg-primary/30 rounded-t\"\n style={{ height: `${(d.planned / maxVal) * 100}%` }}\n title={`Planned: ${d.planned}`}\n />\n <div\n className=\"w-3 bg-primary rounded-t\"\n style={{ height: `${(d.completed / maxVal) * 100}%` }}\n title={`Completed: ${d.completed}`}\n />\n </div>\n <span className=\"text-xs text-muted-foreground\">{d.month}</span>\n </div>\n ))}\n </div>\n )}\n <div className=\"flex gap-4 justify-center mt-3 text-xs\">\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded bg-primary/30\" />\n Planned\n </span>\n <span className=\"flex items-center gap-1\">\n <span className=\"h-2 w-2 rounded bg-primary\" />\n Completed\n </span>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Item Status Breakdown</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(itemsByStatus).map(([status, count]) => {\n const total = items.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const colors: Record<string, string> = {\n TODO: 'bg-gray-400',\n PLANNED: 'bg-yellow-400',\n IN_PROGRESS: 'bg-blue-400',\n BLOCKED: 'bg-red-400',\n DONE: 'bg-green-400',\n COMPLETED: 'bg-green-400',\n };\n return (\n <div key={status}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span className=\"capitalize\">{status.toLowerCase().replace('_', ' ')}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${colors[status] ?? 'bg-gray-300'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Feature Request Status</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(featuresByStatus).length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n No feature request data\n </p>\n ) : (\n Object.entries(featuresByStatus).map(([status, count]) => {\n const total = allFeatures.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const colors: Record<string, string> = {\n SUBMITTED: 'bg-gray-400',\n UNDER_REVIEW: 'bg-yellow-400',\n PLANNED: 'bg-blue-400',\n IN_PROGRESS: 'bg-purple-400',\n COMPLETED: 'bg-green-400',\n DECLINED: 'bg-red-400',\n };\n return (\n <div key={status}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span className=\"capitalize\">{status.toLowerCase().replace('_', ' ')}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${colors[status] ?? 'bg-gray-300'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Swimlane Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(itemsBySwimlane).length === 0 ? (\n <p className=\"text-sm text-muted-foreground text-center py-4\">No swimlane data</p>\n ) : (\n Object.entries(itemsBySwimlane).map(([lane, count]) => {\n const total = items.length;\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n return (\n <div key={lane}>\n <div className=\"flex justify-between text-sm mb-1\">\n <span>{lane}</span>\n <span className=\"font-medium\">\n {count} ({pct}%)\n </span>\n </div>\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-primary\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })\n )}\n </CardContent>\n </Card>\n </div>\n\n {overdueItems.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Overdue Items</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {overdueItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground\">\n Due: {item.endDate ? new Date(item.endDate).toLocaleDateString() : 'N/A'}\n </p>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-800 capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { Calendar as t, Loader2 as n, Map as r } from "lucide-react";
|
|
3
|
-
import { useParams as i } from "react-router
|
|
3
|
+
import { useParams as i } from "react-router";
|
|
4
4
|
import { gql as a } from "@apollo/client";
|
|
5
5
|
import { useQuery as o } from "@apollo/client/react";
|
|
6
6
|
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsRoadmapDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsRoadmapDetailPagePage.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 { Map, Calendar, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router-dom';\n\nconst GET_ROADMAP = gql`\n query GetRoadmapDetailPage($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n workspaceId\n name\n description\n type\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n title\n description\n status\n startDate\n endDate\n color\n swimlane\n sortOrder\n projectId\n milestoneId\n }\n }\n }\n`;\n\ninterface RoadmapItemNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n startDate: string | null;\n endDate: string | null;\n color: string | null;\n swimlane: string | null;\n sortOrder: number;\n projectId: string | null;\n milestoneId: string | null;\n}\n\nconst statusColor: Record<string, string> = {\n TODO: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n DONE: 'bg-green-100 text-green-800',\n COMPLETED: 'bg-green-100 text-green-800',\n BLOCKED: 'bg-red-100 text-red-800',\n PLANNED: 'bg-yellow-100 text-yellow-800',\n};\n\nexport function ProductsRoadmapDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load roadmap: {error.message}</div>;\n }\n\n const roadmap = data?.planmagnetRoadmap;\n if (!roadmap) return <div className=\"p-6 text-muted-foreground\">Roadmap not found.</div>;\n\n const items: RoadmapItemNode[] = (roadmap.items ?? [])\n .slice()\n .sort((a: RoadmapItemNode, b: RoadmapItemNode) => a.sortOrder - b.sortOrder);\n const completedItems = items.filter((i) => i.status === 'COMPLETED' || i.status === 'DONE');\n const progress = items.length > 0 ? Math.round((completedItems.length / items.length) * 100) : 0;\n\n // Group items by swimlane\n const swimlaneGroups: Record<string, RoadmapItemNode[]> = {};\n for (const item of items) {\n const lane = item.swimlane ?? 'Uncategorized';\n if (!swimlaneGroups[lane]) swimlaneGroups[lane] = [];\n swimlaneGroups[lane].push(item);\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Map className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{roadmap.name}</h1>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {roadmap.description ?? 'No description'}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Badge variant={roadmap.isPublic ? 'default' : 'secondary'} className=\"capitalize\">\n {roadmap.isPublic ? 'public' : 'private'}\n </Badge>\n <Badge variant=\"outline\" className=\"capitalize\">\n {roadmap.type.toLowerCase()}\n </Badge>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-5 gap-4\">\n {[\n { label: 'Progress', value: `${progress}%` },\n { label: 'Total Items', value: items.length },\n { label: 'Completed', value: completedItems.length },\n { label: 'Type', value: roadmap.type.toLowerCase() },\n { label: 'Visibility', value: roadmap.isPublic ? 'Public' : 'Private' },\n ].map((s) => (\n <Card key={s.label}>\n <CardContent className=\"pt-4 text-center\">\n <p className=\"text-xs text-muted-foreground\">{s.label}</p>\n <p className=\"text-lg font-semibold capitalize\">{s.value}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Progress Overview</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex justify-between text-sm mb-2\">\n <span className=\"text-muted-foreground\">Overall Completion</span>\n <span className=\"font-medium\">{progress}%</span>\n </div>\n <div className=\"w-full h-4 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary rounded-full transition-all\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </CardContent>\n </Card>\n\n {Object.entries(swimlaneGroups).map(([lane, laneItems]) => (\n <Card key={lane}>\n <CardHeader>\n <CardTitle>\n {lane} ({laneItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {laneItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n {item.description && (\n <p className=\"text-xs text-muted-foreground\">\n {item.description.slice(0, 60)}...\n </p>\n )}\n {(item.startDate || item.endDate) && (\n <div className=\"flex items-center gap-1 mt-1 text-xs text-muted-foreground\">\n <Calendar className=\"h-3 w-3\" />\n {item.startDate ? new Date(item.startDate).toLocaleDateString() : '?'}\n {' - '}\n {item.endDate ? new Date(item.endDate).toLocaleDateString() : '?'}\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {item.color && (\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{ backgroundColor: item.color }}\n />\n )}\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[item.status] ?? 'bg-gray-100'}`}\n >\n {item.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {roadmap.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>{roadmap.ownerId}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(roadmap.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(roadmap.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsRoadmapDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsRoadmapDetailPagePage.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 { Map, Calendar, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\n\nconst GET_ROADMAP = gql`\n query GetRoadmapDetailPage($id: ID!) {\n planmagnetRoadmap(id: $id) {\n id\n workspaceId\n name\n description\n type\n isPublic\n ownerId\n createdAt\n updatedAt\n items {\n id\n title\n description\n status\n startDate\n endDate\n color\n swimlane\n sortOrder\n projectId\n milestoneId\n }\n }\n }\n`;\n\ninterface RoadmapItemNode {\n id: string;\n title: string;\n description: string | null;\n status: string;\n startDate: string | null;\n endDate: string | null;\n color: string | null;\n swimlane: string | null;\n sortOrder: number;\n projectId: string | null;\n milestoneId: string | null;\n}\n\nconst statusColor: Record<string, string> = {\n TODO: 'bg-gray-100 text-gray-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n DONE: 'bg-green-100 text-green-800',\n COMPLETED: 'bg-green-100 text-green-800',\n BLOCKED: 'bg-red-100 text-red-800',\n PLANNED: 'bg-yellow-100 text-yellow-800',\n};\n\nexport function ProductsRoadmapDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const roadmapId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_ROADMAP, {\n variables: { id: roadmapId },\n skip: !roadmapId,\n });\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load roadmap: {error.message}</div>;\n }\n\n const roadmap = data?.planmagnetRoadmap;\n if (!roadmap) return <div className=\"p-6 text-muted-foreground\">Roadmap not found.</div>;\n\n const items: RoadmapItemNode[] = (roadmap.items ?? [])\n .slice()\n .sort((a: RoadmapItemNode, b: RoadmapItemNode) => a.sortOrder - b.sortOrder);\n const completedItems = items.filter((i) => i.status === 'COMPLETED' || i.status === 'DONE');\n const progress = items.length > 0 ? Math.round((completedItems.length / items.length) * 100) : 0;\n\n // Group items by swimlane\n const swimlaneGroups: Record<string, RoadmapItemNode[]> = {};\n for (const item of items) {\n const lane = item.swimlane ?? 'Uncategorized';\n if (!swimlaneGroups[lane]) swimlaneGroups[lane] = [];\n swimlaneGroups[lane].push(item);\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Map className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{roadmap.name}</h1>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {roadmap.description ?? 'No description'}\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Badge variant={roadmap.isPublic ? 'default' : 'secondary'} className=\"capitalize\">\n {roadmap.isPublic ? 'public' : 'private'}\n </Badge>\n <Badge variant=\"outline\" className=\"capitalize\">\n {roadmap.type.toLowerCase()}\n </Badge>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-5 gap-4\">\n {[\n { label: 'Progress', value: `${progress}%` },\n { label: 'Total Items', value: items.length },\n { label: 'Completed', value: completedItems.length },\n { label: 'Type', value: roadmap.type.toLowerCase() },\n { label: 'Visibility', value: roadmap.isPublic ? 'Public' : 'Private' },\n ].map((s) => (\n <Card key={s.label}>\n <CardContent className=\"pt-4 text-center\">\n <p className=\"text-xs text-muted-foreground\">{s.label}</p>\n <p className=\"text-lg font-semibold capitalize\">{s.value}</p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Progress Overview</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex justify-between text-sm mb-2\">\n <span className=\"text-muted-foreground\">Overall Completion</span>\n <span className=\"font-medium\">{progress}%</span>\n </div>\n <div className=\"w-full h-4 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary rounded-full transition-all\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </CardContent>\n </Card>\n\n {Object.entries(swimlaneGroups).map(([lane, laneItems]) => (\n <Card key={lane}>\n <CardHeader>\n <CardTitle>\n {lane} ({laneItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {laneItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n {item.description && (\n <p className=\"text-xs text-muted-foreground\">\n {item.description.slice(0, 60)}...\n </p>\n )}\n {(item.startDate || item.endDate) && (\n <div className=\"flex items-center gap-1 mt-1 text-xs text-muted-foreground\">\n <Calendar className=\"h-3 w-3\" />\n {item.startDate ? new Date(item.startDate).toLocaleDateString() : '?'}\n {' - '}\n {item.endDate ? new Date(item.endDate).toLocaleDateString() : '?'}\n </div>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {item.color && (\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{ backgroundColor: item.color }}\n />\n )}\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[item.status] ?? 'bg-gray-100'}`}\n >\n {item.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Type</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {roadmap.type.toLowerCase()}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>{roadmap.ownerId}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(roadmap.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(roadmap.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectsPlanningDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProjectsPlanningDetailPagePage.tsx"],"names":[],"mappings":"AA0DA,wBAAgB,8BAA8B,4CAwO7C"}
|
|
@@ -29,48 +29,77 @@ var m = a`
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
`, h = a`
|
|
33
|
+
query GetSprintCapacity($sprintId: ID!) {
|
|
34
|
+
planmagnetSprintCapacity(sprintId: $sprintId) {
|
|
35
|
+
sprintId
|
|
36
|
+
crewfoundryUnavailable
|
|
37
|
+
unmatchedAssigneeIds
|
|
38
|
+
members {
|
|
39
|
+
actorId
|
|
40
|
+
employeeName
|
|
41
|
+
department
|
|
42
|
+
position
|
|
43
|
+
allocatedHours
|
|
44
|
+
billableHours
|
|
45
|
+
utilizationPercent
|
|
46
|
+
assignedStoryPoints
|
|
47
|
+
assignedWorkItemCount
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
32
51
|
`;
|
|
33
|
-
function
|
|
34
|
-
let { workspaceId: a } = e(), { data:
|
|
35
|
-
|
|
52
|
+
function g() {
|
|
53
|
+
let { workspaceId: a } = e(), { data: g, loading: _, error: v } = o(m, { variables: { workspaceId: a ?? "default" } }), y = g?.planmagnetSprints?.items?.[0]?.id, { data: b } = o(h, {
|
|
54
|
+
variables: { sprintId: y },
|
|
55
|
+
skip: !y
|
|
56
|
+
});
|
|
57
|
+
if (_) return /* @__PURE__ */ s("div", {
|
|
36
58
|
className: "flex items-center justify-center p-12",
|
|
37
59
|
children: /* @__PURE__ */ s("div", { className: "animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" })
|
|
38
60
|
});
|
|
39
|
-
if (
|
|
61
|
+
if (v) return /* @__PURE__ */ c("div", {
|
|
40
62
|
className: "p-6 text-red-500",
|
|
41
|
-
children: ["Error loading planning session: ",
|
|
63
|
+
children: ["Error loading planning session: ", v.message]
|
|
42
64
|
});
|
|
43
|
-
let
|
|
44
|
-
if (!
|
|
65
|
+
let x = g?.planmagnetSprints?.items?.[0];
|
|
66
|
+
if (!x) return /* @__PURE__ */ s("div", {
|
|
45
67
|
className: "p-6 text-muted-foreground",
|
|
46
68
|
children: "Planning session not found."
|
|
47
69
|
});
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
let S = b?.planmagnetSprintCapacity, C = {};
|
|
71
|
+
for (let e of S?.members ?? []) C[e.actorId] = {
|
|
72
|
+
employeeName: e.employeeName,
|
|
73
|
+
allocatedHours: e.allocatedHours,
|
|
74
|
+
billableHours: e.billableHours,
|
|
75
|
+
utilizationPercent: e.utilizationPercent
|
|
76
|
+
};
|
|
77
|
+
let w = x.workItems ?? [], T = w.reduce((e, t) => e + (t.storyPoints ?? 0), 0);
|
|
78
|
+
w.filter((e) => e.status === "DONE").reduce((e, t) => e + (t.storyPoints ?? 0), 0);
|
|
79
|
+
let E = x.capacity ?? T, D = E > 0 ? Math.round(T / E * 100) : 0, O = E - T, k = [...new Set(w.map((e) => e.assigneeId).filter(Boolean))];
|
|
51
80
|
return /* @__PURE__ */ c("div", {
|
|
52
81
|
className: "space-y-6 p-6",
|
|
53
82
|
children: [/* @__PURE__ */ c("div", {
|
|
54
83
|
className: "flex items-center justify-between",
|
|
55
84
|
children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("h1", {
|
|
56
85
|
className: "text-2xl font-semibold",
|
|
57
|
-
children:
|
|
86
|
+
children: x.name
|
|
58
87
|
}), /* @__PURE__ */ c("p", {
|
|
59
88
|
className: "text-sm text-muted-foreground",
|
|
60
89
|
children: [
|
|
61
|
-
|
|
90
|
+
x.projectId,
|
|
62
91
|
" ·",
|
|
63
92
|
" ",
|
|
64
|
-
|
|
93
|
+
x.startDate ? new Date(x.startDate).toLocaleDateString() : "—",
|
|
65
94
|
" -",
|
|
66
95
|
" ",
|
|
67
|
-
|
|
96
|
+
x.endDate ? new Date(x.endDate).toLocaleDateString() : "—"
|
|
68
97
|
]
|
|
69
98
|
})] }), /* @__PURE__ */ s("div", {
|
|
70
99
|
className: "flex gap-2",
|
|
71
100
|
children: /* @__PURE__ */ s(l, {
|
|
72
101
|
className: "capitalize",
|
|
73
|
-
children:
|
|
102
|
+
children: x.status.toLowerCase().replace("_", " ")
|
|
74
103
|
})
|
|
75
104
|
})]
|
|
76
105
|
}), /* @__PURE__ */ c("div", {
|
|
@@ -86,7 +115,7 @@ function h() {
|
|
|
86
115
|
className: "p-4 bg-muted rounded-md",
|
|
87
116
|
children: [/* @__PURE__ */ s("p", {
|
|
88
117
|
className: "text-2xl font-bold",
|
|
89
|
-
children:
|
|
118
|
+
children: E
|
|
90
119
|
}), /* @__PURE__ */ s("p", {
|
|
91
120
|
className: "text-xs text-muted-foreground",
|
|
92
121
|
children: "Capacity"
|
|
@@ -96,7 +125,7 @@ function h() {
|
|
|
96
125
|
className: "p-4 bg-primary/10 rounded-md",
|
|
97
126
|
children: [/* @__PURE__ */ s("p", {
|
|
98
127
|
className: "text-2xl font-bold text-primary",
|
|
99
|
-
children:
|
|
128
|
+
children: T
|
|
100
129
|
}), /* @__PURE__ */ s("p", {
|
|
101
130
|
className: "text-xs text-muted-foreground",
|
|
102
131
|
children: "Allocated"
|
|
@@ -106,7 +135,7 @@ function h() {
|
|
|
106
135
|
className: "p-4 bg-muted rounded-md",
|
|
107
136
|
children: [/* @__PURE__ */ s("p", {
|
|
108
137
|
className: "text-2xl font-bold",
|
|
109
|
-
children:
|
|
138
|
+
children: O > 0 ? O : 0
|
|
110
139
|
}), /* @__PURE__ */ s("p", {
|
|
111
140
|
className: "text-xs text-muted-foreground",
|
|
112
141
|
children: "Remaining"
|
|
@@ -120,35 +149,60 @@ function h() {
|
|
|
120
149
|
children: "Utilization"
|
|
121
150
|
}), /* @__PURE__ */ c("span", {
|
|
122
151
|
className: "font-medium",
|
|
123
|
-
children: [
|
|
152
|
+
children: [D, "%"]
|
|
124
153
|
})]
|
|
125
154
|
}), /* @__PURE__ */ s("div", {
|
|
126
155
|
className: "w-full h-4 rounded-full bg-muted overflow-hidden",
|
|
127
156
|
children: /* @__PURE__ */ s("div", {
|
|
128
|
-
className: `h-full rounded-full ${
|
|
129
|
-
style: { width: `${Math.min(
|
|
157
|
+
className: `h-full rounded-full ${D > 90 ? "bg-red-400" : "bg-green-400"}`,
|
|
158
|
+
style: { width: `${Math.min(D, 100)}%` }
|
|
130
159
|
})
|
|
131
160
|
})] })]
|
|
132
161
|
})] }), /* @__PURE__ */ c(u, { children: [/* @__PURE__ */ s(f, { children: /* @__PURE__ */ c(p, { children: [
|
|
133
162
|
"Team Members (",
|
|
134
|
-
|
|
163
|
+
k.length,
|
|
135
164
|
")"
|
|
136
|
-
] }) }), /* @__PURE__ */
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
165
|
+
] }) }), /* @__PURE__ */ c(d, { children: [
|
|
166
|
+
/* @__PURE__ */ c("div", {
|
|
167
|
+
className: "grid grid-cols-2 gap-3",
|
|
168
|
+
children: [k.map((e) => {
|
|
169
|
+
let t = C[e];
|
|
170
|
+
return /* @__PURE__ */ c("div", {
|
|
171
|
+
className: "flex items-center gap-2 border rounded-md p-2.5",
|
|
172
|
+
children: [/* @__PURE__ */ s("div", {
|
|
173
|
+
className: "h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-medium text-primary",
|
|
174
|
+
children: e.slice(0, 2).toUpperCase()
|
|
175
|
+
}), /* @__PURE__ */ c("div", {
|
|
176
|
+
className: "min-w-0",
|
|
177
|
+
children: [/* @__PURE__ */ s("span", {
|
|
178
|
+
className: "text-sm truncate block",
|
|
179
|
+
children: t?.employeeName ?? e
|
|
180
|
+
}), t && /* @__PURE__ */ c("span", {
|
|
181
|
+
className: "text-xs text-muted-foreground",
|
|
182
|
+
children: [
|
|
183
|
+
t.allocatedHours,
|
|
184
|
+
"h allocated (",
|
|
185
|
+
t.billableHours,
|
|
186
|
+
"h billable)",
|
|
187
|
+
t.utilizationPercent != null && ` · ${t.utilizationPercent}% util`
|
|
188
|
+
]
|
|
189
|
+
})]
|
|
190
|
+
})]
|
|
191
|
+
}, e);
|
|
192
|
+
}), k.length === 0 && /* @__PURE__ */ s("p", {
|
|
193
|
+
className: "text-sm text-muted-foreground col-span-2 text-center py-4",
|
|
194
|
+
children: "No team members assigned."
|
|
146
195
|
})]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
196
|
+
}),
|
|
197
|
+
S?.crewfoundryUnavailable && k.length > 0 && /* @__PURE__ */ s("p", {
|
|
198
|
+
className: "text-xs text-muted-foreground mt-3 border-t pt-3",
|
|
199
|
+
children: "Workforce allocation from CrewFoundry is unavailable for this workspace — no CrewFoundry module provisioned yet."
|
|
200
|
+
}),
|
|
201
|
+
!S?.crewfoundryUnavailable && (S?.unmatchedAssigneeIds?.length ?? 0) > 0 && /* @__PURE__ */ c("p", {
|
|
202
|
+
className: "text-xs text-muted-foreground mt-3 border-t pt-3",
|
|
203
|
+
children: [S.unmatchedAssigneeIds.length, " assignee(s) have no matching CrewFoundry employee record — allocation hours shown only for matched employees."]
|
|
204
|
+
})
|
|
205
|
+
] })] })]
|
|
152
206
|
}), /* @__PURE__ */ c(u, { children: [/* @__PURE__ */ s(f, { children: /* @__PURE__ */ s(p, { children: "Session Info" }) }), /* @__PURE__ */ c(d, {
|
|
153
207
|
className: "space-y-3 text-sm",
|
|
154
208
|
children: [
|
|
@@ -162,7 +216,7 @@ function h() {
|
|
|
162
216
|
}),
|
|
163
217
|
/* @__PURE__ */ s("span", {
|
|
164
218
|
className: "font-medium",
|
|
165
|
-
children:
|
|
219
|
+
children: x.projectId
|
|
166
220
|
})
|
|
167
221
|
]
|
|
168
222
|
}),
|
|
@@ -176,7 +230,7 @@ function h() {
|
|
|
176
230
|
}),
|
|
177
231
|
/* @__PURE__ */ s("span", {
|
|
178
232
|
className: "font-medium",
|
|
179
|
-
children:
|
|
233
|
+
children: x.startDate ? new Date(x.startDate).toLocaleDateString() : "Not set"
|
|
180
234
|
})
|
|
181
235
|
]
|
|
182
236
|
}),
|
|
@@ -190,7 +244,7 @@ function h() {
|
|
|
190
244
|
}),
|
|
191
245
|
/* @__PURE__ */ s("span", {
|
|
192
246
|
className: "font-medium",
|
|
193
|
-
children:
|
|
247
|
+
children: x.endDate ? new Date(x.endDate).toLocaleDateString() : "Not set"
|
|
194
248
|
})
|
|
195
249
|
]
|
|
196
250
|
}),
|
|
@@ -204,7 +258,7 @@ function h() {
|
|
|
204
258
|
}),
|
|
205
259
|
/* @__PURE__ */ s("span", {
|
|
206
260
|
className: "font-medium",
|
|
207
|
-
children:
|
|
261
|
+
children: k.length
|
|
208
262
|
})
|
|
209
263
|
]
|
|
210
264
|
}),
|
|
@@ -218,11 +272,11 @@ function h() {
|
|
|
218
272
|
}),
|
|
219
273
|
/* @__PURE__ */ c("span", {
|
|
220
274
|
className: "font-medium",
|
|
221
|
-
children: [
|
|
275
|
+
children: [E, " pts"]
|
|
222
276
|
})
|
|
223
277
|
]
|
|
224
278
|
}),
|
|
225
|
-
|
|
279
|
+
x.velocity != null && /* @__PURE__ */ c("div", {
|
|
226
280
|
className: "flex items-center gap-2",
|
|
227
281
|
children: [
|
|
228
282
|
/* @__PURE__ */ s(t, { className: "h-4 w-4 text-muted-foreground" }),
|
|
@@ -232,18 +286,18 @@ function h() {
|
|
|
232
286
|
}),
|
|
233
287
|
/* @__PURE__ */ c("span", {
|
|
234
288
|
className: "font-medium",
|
|
235
|
-
children: [
|
|
289
|
+
children: [x.velocity, " pts"]
|
|
236
290
|
})
|
|
237
291
|
]
|
|
238
292
|
}),
|
|
239
|
-
|
|
293
|
+
x.goal && /* @__PURE__ */ c("div", {
|
|
240
294
|
className: "border-t pt-3 mt-3",
|
|
241
295
|
children: [/* @__PURE__ */ s("p", {
|
|
242
296
|
className: "text-muted-foreground text-xs mb-1",
|
|
243
297
|
children: "Goal"
|
|
244
298
|
}), /* @__PURE__ */ s("p", {
|
|
245
299
|
className: "text-sm",
|
|
246
|
-
children:
|
|
300
|
+
children: x.goal
|
|
247
301
|
})]
|
|
248
302
|
})
|
|
249
303
|
]
|
|
@@ -252,6 +306,6 @@ function h() {
|
|
|
252
306
|
});
|
|
253
307
|
}
|
|
254
308
|
//#endregion
|
|
255
|
-
export {
|
|
309
|
+
export { g as ProjectsPlanningDetailPagePage };
|
|
256
310
|
|
|
257
311
|
//# sourceMappingURL=ProjectsPlanningDetailPagePage.js.map
|