@burdenoff/microfe-planmagnet 2026.930.25 → 2026.1001.2
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/_virtual/__vite-browser-external.js +9 -0
- package/dist/_virtual/__vite-browser-external.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +5 -2
- package/dist/index.js +3 -1
- package/dist/node_modules/fflate/esm/browser.js +554 -0
- package/dist/node_modules/fflate/esm/browser.js.map +1 -0
- package/dist/node_modules/jszip/dist/jszip.min.js +3038 -0
- package/dist/node_modules/jszip/dist/jszip.min.js.map +1 -0
- package/dist/node_modules/pptxgenjs/dist/pptxgen.es.js +2151 -0
- package/dist/node_modules/pptxgenjs/dist/pptxgen.es.js.map +1 -0
- package/dist/node_modules/write-excel-file/browser/index.js +3 -0
- package/dist/node_modules/write-excel-file/modules/export/convertFileContentToUint8ArrayBrowser.js +13 -0
- package/dist/node_modules/write-excel-file/modules/export/convertFileContentToUint8ArrayBrowser.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/export/convertFilesContentToUint8Arrays.js +21 -0
- package/dist/node_modules/write-excel-file/modules/export/convertFilesContentToUint8Arrays.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/export/downloadBlob.js +11 -0
- package/dist/node_modules/write-excel-file/modules/export/downloadBlob.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/export/writeXlsxFileBrowser.js +23 -0
- package/dist/node_modules/write-excel-file/modules/export/writeXlsxFileBrowser.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/export/writeXlsxFileUniversal.js +22 -0
- package/dist/node_modules/write-excel-file/modules/export/writeXlsxFileUniversal.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/getSheetData/getSheetData.js +18 -0
- package/dist/node_modules/write-excel-file/modules/getSheetData/getSheetData.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/conditionalFormatting.js +131 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/conditionalFormatting.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/images.js +188 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/images.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/stickyRowsOrColumns.js +52 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/features/stickyRowsOrColumns.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/_Content_Types_.xml.js +18 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/_Content_Types_.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/drawing.xml.js +18 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/drawing.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/drawing.xml.rels.js +17 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/drawing.xml.rels.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/rels.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/rels.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sharedStrings.xml.js +42 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sharedStrings.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/cell.js +102 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/cell.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/column.js +17 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/column.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/columns.js +15 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/columns.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/drawingReference.js +8 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/drawingReference.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/helpers/getCellStyleProperties.js +71 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/helpers/getCellStyleProperties.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/mergedCellsDescription.js +56 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/mergedCellsDescription.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/pageMargins.js +15 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/pageMargins.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/pageSetup.js +13 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/pageSetup.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/processMergedCells.js +44 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/processMergedCells.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/row.js +123 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/row.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheet.xml.js +41 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheet.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheetData.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheetData.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheetViews.js +17 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml/sheetViews.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml.rels.js +20 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/sheet.xml.rels.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/styles.xml.js +77 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/styles.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/workbook.xml.js +22 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/workbook.xml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/workbook.xml.rels.js +13 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/files/workbook.xml.rels.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/generateXlsxFileContents.js +242 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/generateXlsxFileContents.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/getFeatures.js +29 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/getFeatures.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/convertDateToSerialNumber.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/convertDateToSerialNumber.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getAdditionalContent.js +42 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getAdditionalContent.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getElementXml.js +43 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getElementXml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getWrittenFiles.js +93 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/getWrittenFiles.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/transformContent.js +39 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/features/transformContent.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getAlignmentXml.js +17 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getAlignmentXml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getBorderXml.js +29 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getBorderXml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getCellAddress.js +14 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getCellAddress.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFileExtensionForContentType.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFileExtensionForContentType.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFillXml.js +14 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFillXml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFontXml.js +12 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getFontXml.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getOrderOfSiblings.js +24 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getOrderOfSiblings.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getXlsxColorForHexColor.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/getXlsxColorForHexColor.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasAlignment.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasAlignment.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasBorder.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasBorder.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasFill.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasFill.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasFont.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/hasFont.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/isCellObject.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/isCellObject.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/isObject.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/isObject.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/orderOfSiblings.js +27 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/helpers/orderOfSiblings.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeSharedStrings.js +17 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeSharedStrings.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeSheets.js +74 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeSheets.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeStyles.js +138 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/initializeStyles.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/validateSheetData.js +40 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/validateSheetData.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/validateSheetName.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xlsx/validateSheetName.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/appendMarkupInsideElement.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/appendMarkupInsideElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/applyEnclosingElementOffset.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/applyEnclosingElementOffset.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/escapeXmlSpecialCharacters.js +12 -0
- package/dist/node_modules/write-excel-file/modules/xml/escapeXmlSpecialCharacters.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElement.js +12 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElementInsideElement.js +12 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElementInsideElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElementsNonRecursive_.js +41 -0
- package/dist/node_modules/write-excel-file/modules/xml/findElementsNonRecursive_.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getAttributesString.js +14 -0
- package/dist/node_modules/write-excel-file/modules/xml/getAttributesString.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getChildElements.js +42 -0
- package/dist/node_modules/write-excel-file/modules/xml/getChildElements.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getClosingTagMarkup.js +8 -0
- package/dist/node_modules/write-excel-file/modules/xml/getClosingTagMarkup.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getMarkupInsideElement.js +8 -0
- package/dist/node_modules/write-excel-file/modules/xml/getMarkupInsideElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getOpeningTagMarkup.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/getOpeningTagMarkup.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/getSelfClosingTagMarkup.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/getSelfClosingTagMarkup.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/insertElementMarkupAccordingToOrderOfSiblings.js +64 -0
- package/dist/node_modules/write-excel-file/modules/xml/insertElementMarkupAccordingToOrderOfSiblings.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/prependMarkupInsideElement.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/prependMarkupInsideElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/removeInvalidXmlCharacters.js +9 -0
- package/dist/node_modules/write-excel-file/modules/xml/removeInvalidXmlCharacters.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/replaceElement.js +8 -0
- package/dist/node_modules/write-excel-file/modules/xml/replaceElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeAttributeName.js +10 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeAttributeName.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeAttributeValue.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeAttributeValue.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeTextContent.js +11 -0
- package/dist/node_modules/write-excel-file/modules/xml/sanitizeTextContent.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/xml/setMarkupInsideElement.js +8 -0
- package/dist/node_modules/write-excel-file/modules/xml/setMarkupInsideElement.js.map +1 -0
- package/dist/node_modules/write-excel-file/modules/zip/zipToArrayBuffer.js +18 -0
- package/dist/node_modules/write-excel-file/modules/zip/zipToArrayBuffer.js.map +1 -0
- package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +160 -147
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +126 -106
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/comments/CommentThread.js +36 -36
- package/dist/planmagnet/comments/RoadmapComments.js +21 -21
- package/dist/planmagnet/controls/AutomationRuleControls.js +24 -24
- package/dist/planmagnet/controls/FeatureControls.js +60 -60
- package/dist/planmagnet/controls/Pickers.d.ts +10 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +75 -27
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/SyncControls.js +59 -59
- package/dist/planmagnet/discussion/WorkItemDiscussionPanel.js +15 -15
- package/dist/planmagnet/docs/DocSuggestionsPanel.js +12 -12
- package/dist/planmagnet/docs/DocVersionCompare.js +3 -3
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts +12 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js +208 -0
- package/dist/planmagnet/forecast/DeliveryForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js +190 -0
- package/dist/planmagnet/forecast/ProjectOutlookCard.js.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts +5 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js +99 -0
- package/dist/planmagnet/forecast/ProjectRiskSummaryCard.js.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts +8 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.d.ts.map +1 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js +185 -0
- package/dist/planmagnet/forecast/ResourceForecastCard.js.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts +35 -0
- package/dist/planmagnet/forecast/riskFlags.d.ts.map +1 -0
- package/dist/planmagnet/forecast/riskFlags.js +37 -0
- package/dist/planmagnet/forecast/riskFlags.js.map +1 -0
- package/dist/planmagnet/index.d.ts +2 -0
- package/dist/planmagnet/index.d.ts.map +1 -1
- package/dist/planmagnet/index.js +2 -0
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +216 -182
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +11 -11
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +163 -163
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +165 -165
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js +234 -0
- package/dist/planmagnet/pages/ProjectsAtRiskPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +206 -182
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsCreateReportPagePage.d.ts +6 -0
- package/dist/planmagnet/pages/ReportsCreateReportPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsCreateReportPagePage.js +5 -177
- package/dist/planmagnet/pages/ReportsCreateReportPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsEditReportPagePage.d.ts +3 -0
- package/dist/planmagnet/pages/ReportsEditReportPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ReportsEditReportPagePage.js +15 -0
- package/dist/planmagnet/pages/ReportsEditReportPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ReportsReportPagePage.d.ts +7 -0
- package/dist/planmagnet/pages/ReportsReportPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ReportsReportPagePage.js +226 -0
- package/dist/planmagnet/pages/ReportsReportPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +224 -496
- package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +88 -83
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js +139 -112
- package/dist/planmagnet/pages/analytics/SprintDeliveryCards.js.map +1 -1
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +21 -21
- package/dist/planmagnet/product/ProductControls.js +91 -91
- package/dist/planmagnet/reports/ExportMenu.d.ts +9 -0
- package/dist/planmagnet/reports/ExportMenu.d.ts.map +1 -0
- package/dist/planmagnet/reports/ExportMenu.js +116 -0
- package/dist/planmagnet/reports/ExportMenu.js.map +1 -0
- package/dist/planmagnet/reports/PrintReportView.d.ts +24 -0
- package/dist/planmagnet/reports/PrintReportView.d.ts.map +1 -0
- package/dist/planmagnet/reports/PrintReportView.js +75 -0
- package/dist/planmagnet/reports/PrintReportView.js.map +1 -0
- package/dist/planmagnet/reports/ReportBuilderCanvas.d.ts +8 -0
- package/dist/planmagnet/reports/ReportBuilderCanvas.d.ts.map +1 -0
- package/dist/planmagnet/reports/ReportBuilderCanvas.js +261 -0
- package/dist/planmagnet/reports/ReportBuilderCanvas.js.map +1 -0
- package/dist/planmagnet/reports/ReportBuilderPage.d.ts +5 -0
- package/dist/planmagnet/reports/ReportBuilderPage.d.ts.map +1 -0
- package/dist/planmagnet/reports/ReportBuilderPage.js +198 -0
- package/dist/planmagnet/reports/ReportBuilderPage.js.map +1 -0
- package/dist/planmagnet/reports/ReportRunHistory.d.ts +8 -0
- package/dist/planmagnet/reports/ReportRunHistory.d.ts.map +1 -0
- package/dist/planmagnet/reports/ReportRunHistory.js +63 -0
- package/dist/planmagnet/reports/ReportRunHistory.js.map +1 -0
- package/dist/planmagnet/reports/ReportWidgetRenderer.d.ts +7 -0
- package/dist/planmagnet/reports/ReportWidgetRenderer.d.ts.map +1 -0
- package/dist/planmagnet/reports/ReportWidgetRenderer.js +251 -0
- package/dist/planmagnet/reports/ReportWidgetRenderer.js.map +1 -0
- package/dist/planmagnet/reports/SchedulesPanel.d.ts +4 -0
- package/dist/planmagnet/reports/SchedulesPanel.d.ts.map +1 -0
- package/dist/planmagnet/reports/SchedulesPanel.js +457 -0
- package/dist/planmagnet/reports/SchedulesPanel.js.map +1 -0
- package/dist/planmagnet/reports/ShareDialog.d.ts +5 -0
- package/dist/planmagnet/reports/ShareDialog.d.ts.map +1 -0
- package/dist/planmagnet/reports/ShareDialog.js +234 -0
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -0
- package/dist/planmagnet/reports/SharedReportPage.d.ts +4 -0
- package/dist/planmagnet/reports/SharedReportPage.d.ts.map +1 -0
- package/dist/planmagnet/reports/SharedReportPage.js +180 -0
- package/dist/planmagnet/reports/SharedReportPage.js.map +1 -0
- package/dist/planmagnet/reports/WidgetEditorDialog.d.ts +10 -0
- package/dist/planmagnet/reports/WidgetEditorDialog.d.ts.map +1 -0
- package/dist/planmagnet/reports/WidgetEditorDialog.js +220 -0
- package/dist/planmagnet/reports/WidgetEditorDialog.js.map +1 -0
- package/dist/planmagnet/reports/reportExportPptx.d.ts +4 -0
- package/dist/planmagnet/reports/reportExportPptx.d.ts.map +1 -0
- package/dist/planmagnet/reports/reportExportPptx.js +118 -0
- package/dist/planmagnet/reports/reportExportPptx.js.map +1 -0
- package/dist/planmagnet/reports/reportExportXlsx.d.ts +4 -0
- package/dist/planmagnet/reports/reportExportXlsx.d.ts.map +1 -0
- package/dist/planmagnet/reports/reportExportXlsx.js +42 -0
- package/dist/planmagnet/reports/reportExportXlsx.js.map +1 -0
- package/dist/planmagnet/reports/reportGraphql.d.ts +28 -0
- package/dist/planmagnet/reports/reportGraphql.d.ts.map +1 -0
- package/dist/planmagnet/reports/reportGraphql.js +360 -0
- package/dist/planmagnet/reports/reportGraphql.js.map +1 -0
- package/dist/planmagnet/reports/reportModel.d.ts +198 -0
- package/dist/planmagnet/reports/reportModel.d.ts.map +1 -0
- package/dist/planmagnet/reports/reportModel.js +328 -0
- package/dist/planmagnet/reports/reportModel.js.map +1 -0
- package/dist/planmagnet/reports/sharedReportRoute.d.ts +15 -0
- package/dist/planmagnet/reports/sharedReportRoute.d.ts.map +1 -0
- package/dist/planmagnet/reports/sharedReportRoute.js +9 -0
- package/dist/planmagnet/reports/sharedReportRoute.js.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js +60 -60
- package/dist/planmagnet/roadmaps/RoadmapNowNextLater.js +30 -30
- package/dist/planmagnet/roadmaps/RoadmapReviewPanel.js +21 -21
- package/dist/planmagnet/roadmaps/RoadmapVersionsPanel.js +22 -22
- package/package.json +6 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { countLabel, useProjectCounts, useSprintCounts } from '../hooks/useCounts';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\nimport { QualityMetricsCard } from './analytics/QualityMetricsCard';\nimport { DeliveryKpisCard } from './analytics/DeliveryKpisCard';\nimport { ThroughputCard } from './analytics/ThroughputCard';\nimport { StatusFlowCard } from './analytics/StatusFlowCard';\nimport { WorkBreakdownCard } from './analytics/WorkBreakdownCard';\nimport {\n ALL_PROJECTS,\n activePreset,\n isValidRange,\n presetRange,\n scopeToProjectId,\n toRangeVariables,\n type DateRange,\n type RangePreset,\n} from './analytics/analyticsRange';\n\n// Server-side totals (QA H2). The work-item list returns at most 100 rows per\n// page, so every figure computed from the loaded page undercounted past 100\n// items — and \"Project Health\" did not filter by project at all.\n// The chosen project's sprints, queried by project (BOFF-7964: the picker was built from the 100 newest sprints\n// of the whole workspace, so an older project's list could be empty).\nconst GET_PROJECT_SPRINTS = gql`\n query AnalyticsProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n completedAt\n endDate\n }\n }\n }\n`;\n\n// At risk = missed, or still open and past due — counted on the server over every milestone.\nconst GET_MILESTONE_RISK = gql`\n query AnalyticsMilestoneRisk($workspaceId: ID!, $yesterday: DateTime) {\n missed: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { status: MISSED }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n overdue: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { open: true, dueTo: $yesterday }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n }\n`;\n\nconst GET_ANALYTICS_COUNTS = gql`\n query GetAnalyticsWorkItemCounts($workspaceId: ID!) {\n planmagnetWorkItemCounts(workspaceId: $workspaceId) {\n total\n points\n blocked\n byStatus {\n status\n count\n points\n }\n byProject {\n projectId\n status\n count\n }\n }\n }\n`;\n\ninterface WorkItemCounts {\n total: number;\n points: number;\n blocked: number;\n byStatus: Array<{ status: string; count: number; points: number }>;\n byProject: Array<{ projectId: string; status: string; count: number }>;\n}\n\n// Only the project list (for the pickers and per-project health). Counts come from the server; the 500 work items,\n// 100 sprints and 100 milestones this used to load only fed page-derived counts (BOFF-7997).\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData($workspaceId: ID!, $projectPagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow(\n $workspaceId: ID!\n $projectId: ID!\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetCumulativeFlow(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n // ANR-09: date range + project scope live in the URL (?from=&to=&project=) so a view is linkable, and so a\n // Dashboard KPI tile can deep-link straight into a pre-filtered view (see DashboardDashboardPagePage.tsx).\n const [searchParams, setSearchParams] = useSearchParams();\n const urlFrom = searchParams.get('from');\n const urlTo = searchParams.get('to');\n const [range, setRange] = useState<DateRange>(() =>\n urlFrom && urlTo && isValidRange(urlFrom, urlTo)\n ? { from: urlFrom, to: urlTo }\n : presetRange('30')\n );\n // The last range that was actually valid (from <= to), kept separate from `range` itself so an in-progress,\n // still-invalid edit in the inputs below can never leak into the query variables. `range` is always a valid\n // pair on mount (see the initializer above), so seeding this with it is safe.\n const [lastValidRange, setLastValidRange] = useState<DateRange>(range);\n const [scope, setScope] = useState<string>(searchParams.get('project') || ALL_PROJECTS);\n const scopeProjectId = scopeToProjectId(scope);\n const preset = activePreset(range);\n // Derived straight from `range` on every render — never a separately-tracked flag — so it can never go stale\n // relative to what the queries below actually send, however `range` got into an invalid state.\n const rangeIsValid = isValidRange(range.from, range.to);\n const rangeError = rangeIsValid\n ? null\n : t('planmagnet.analytics.range.invalid', '\"From\" must be on or before \"To\".');\n\n function applyRange(next: DateRange, nextScope: string) {\n setRange(next);\n if (isValidRange(next.from, next.to)) setLastValidRange(next);\n const params = new URLSearchParams(searchParams);\n params.set('from', next.from);\n params.set('to', next.to);\n if (nextScope !== ALL_PROJECTS) params.set('project', nextScope);\n else params.delete('project');\n setSearchParams(params, { replace: true });\n }\n\n function onPreset(p: RangePreset) {\n applyRange(presetRange(p), scope);\n }\n\n function onFromChange(value: string) {\n if (value && range.to && isValidRange(value, range.to)) {\n applyRange({ from: value, to: range.to }, scope);\n } else {\n setRange((r) => ({ ...r, from: value }));\n }\n }\n\n function onToChange(value: string) {\n if (value && range.from && isValidRange(range.from, value)) {\n applyRange({ from: range.from, to: value }, scope);\n } else {\n setRange((r) => ({ ...r, to: value }));\n }\n }\n\n function onScopeChange(value: string) {\n setScope(value);\n // The sprint select below is always scoped to the current project — an old selection would otherwise\n // silently carry over into whichever project happens to load first for the new scope.\n setSelectedSprintId('');\n applyRange(range, value);\n }\n\n // ANR-09 fix: while the visible `range` is invalid (rangeError above), every query below keeps using the last\n // known-good range instead of re-firing with an inverted from/to — `applyRange` only ever advances\n // `lastValidRange` on a genuinely valid pair, so this can never itself go out of range.\n const { startDate, endDate } = toRangeVariables(rangeIsValid ? range : lastValidRange);\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: { workspaceId, projectPagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n const { data: countsData } = useQuery<{ planmagnetWorkItemCounts: WorkItemCounts }>(\n GET_ANALYTICS_COUNTS,\n { variables: { workspaceId }, skip: !workspaceId }\n );\n const counts = countsData?.planmagnetWorkItemCounts;\n const projectCounts = useProjectCounts();\n const sprintCounts = useSprintCounts();\n // Fixed for the life of the page (render must stay pure).\n const [yesterday] = useState(() => new Date(Date.now() - 86_400_000).toISOString().slice(0, 10));\n const { data: riskData } = useQuery<{\n missed: { totalCount: number };\n overdue: { totalCount: number };\n }>(GET_MILESTONE_RISK, { variables: { workspaceId, yesterday }, skip: !workspaceId });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n // Burndown/velocity/CFD/cycle-time/throughput all require a concrete project (the server fields take\n // `projectId: ID!`, not optional): \"All projects\" scope still needs SOME project for this section, so it falls\n // back to the first loaded project — the same fallback this page always used before ANR-09 added a scope select.\n const effectiveProjectId = scopeProjectId || projects[0]?.id || '';\n const { data: projectSprintsData } = useQuery<{\n planmagnetSprints: {\n items: {\n id: string;\n name: string;\n status: string;\n completedAt: string | null;\n endDate: string | null;\n }[];\n };\n }>(GET_PROJECT_SPRINTS, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n const sprintsForProject = projectSprintsData?.planmagnetSprints.items ?? [];\n // Default to the running sprint (a PLANNING one has a future start and an empty burndown), else the latest\n // completed one, else whatever there is.\n const lastCompleted = sprintsForProject\n .filter((s) => s.status === 'COMPLETED')\n .sort((a, b) =>\n (b.completedAt ?? b.endDate ?? '').localeCompare(a.completedAt ?? a.endDate ?? '')\n )[0];\n const defaultSprint =\n sprintsForProject.find((s) => s.status === 'ACTIVE' || s.status === 'REVIEW') ??\n lastCompleted ??\n sprintsForProject[0];\n const effectiveSprintId = selectedSprintId || defaultSprint?.id || '';\n const effectiveProjectName = projects.find((p) => p.id === effectiveProjectId)?.name ?? '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n // ANR-09: the page's shared date range, not the field's own unbounded default.\n variables: { workspaceId, projectId: effectiveProjectId, startDate, endDate },\n skip: !workspaceId || !effectiveProjectId,\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 (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n // Every figure comes from the server counts; while they load (or if they fail) the tiles say \"—\" instead of\n // quietly falling back to the loaded page (BOFF-7997).\n const statusCount = (status: string) => counts?.byStatus.find((c) => c.status === status);\n const cancelled = statusCount('CANCELLED')?.count ?? 0;\n const doneTasks = counts ? (statusCount('DONE')?.count ?? 0) : null;\n const totalTasks = counts ? counts.total : null;\n // Blocked = held by a dependency (the release readiness rule), not a status nobody can set (BOFF-7973).\n const blockedTasks = counts ? counts.blocked : null;\n const totalPoints = counts ? counts.points : null;\n const completedPoints = counts ? (statusCount('DONE')?.points ?? 0) : null;\n const projectProgress = (projectId: string) => {\n const rows = counts?.byProject.filter((r) => r.projectId === projectId) ?? [];\n // Cancelled work no longer needs doing (BOFF-7989).\n const live = rows.filter((r) => r.status !== 'CANCELLED').reduce((sum, r) => sum + r.count, 0);\n const done = rows.filter((r) => r.status === 'DONE').reduce((sum, r) => sum + r.count, 0);\n return live > 0 ? Math.round((done / live) * 100) : 0;\n };\n\n const activeSprints = sprintCounts?.running ?? null;\n const atRiskMs = riskData ? riskData.missed.totalCount + riskData.overdue.totalCount : null;\n const activeProjects = projectCounts?.active ?? null;\n const completionRate =\n totalTasks != null && doneTasks != null && totalTasks - cancelled > 0\n ? Math.round((doneTasks / (totalTasks - cancelled)) * 100)\n : null;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n const forecastNextSprint = velocity ? Math.round(velocity.forecastNextSprintPoints) : 0;\n\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: countLabel(totalTasks),\n trend: `${countLabel(completionRate)}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: countLabel(totalPoints),\n trend: `${countLabel(completedPoints)} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: countLabel(activeProjects),\n trend: `${countLabel(projectCounts?.all)} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: countLabel(blockedTasks),\n trend:\n blockedTasks == null\n ? ''\n : blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n {/* ANR-09: date range + project scope. Both live in the URL, so this view is linkable and a Dashboard tile\n can deep-link straight into it pre-filtered. Everything below — CFD, cycle time, throughput, the new\n Quality/Delivery cards, and the budget card — reads this same range and scope. */}\n <div\n className=\"flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between\"\n data-testid=\"analytics-range\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <div\n className=\"flex items-center gap-1 rounded-md border p-0.5\"\n role=\"group\"\n aria-label={t('planmagnet.analytics.range.presetLabel', 'Date range preset')}\n data-testid=\"analytics-range-preset\"\n >\n {(['7', '30', '90'] as const).map((p) => (\n <button\n key={p}\n type=\"button\"\n aria-pressed={preset === p}\n onClick={() => onPreset(p)}\n className={`px-2.5 py-1 text-xs rounded ${\n preset === p\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.analytics.range.last', 'Last {{days}} days', { days: p })}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor=\"analytics-range-from\" className=\"sr-only\">\n {t('planmagnet.analytics.range.from', 'From')}\n </label>\n <Input\n id=\"analytics-range-from\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.from}\n max={range.to}\n onChange={(e) => onFromChange(e.target.value)}\n />\n <span className=\"text-muted-foreground text-sm\">–</span>\n <label htmlFor=\"analytics-range-to\" className=\"sr-only\">\n {t('planmagnet.analytics.range.to', 'To')}\n </label>\n <Input\n id=\"analytics-range-to\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.to}\n min={range.from}\n onChange={(e) => onToChange(e.target.value)}\n />\n </div>\n {rangeError && (\n <p role=\"alert\" className=\"text-xs text-status-error-text\">\n {rangeError}\n </p>\n )}\n </div>\n\n <Select value={scope} onValueChange={onScopeChange}>\n <SelectTrigger className=\"h-8 w-56\" data-testid=\"analytics-project\">\n <SelectValue\n placeholder={t('planmagnet.analytics.range.allProjects', 'All projects')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_PROJECTS}>\n {t('planmagnet.analytics.range.allProjects', 'All projects')}\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\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 <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeProjects)}</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 <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeSprints)}</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 <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n {/* This one is per project — name it (BOFF-7987: it silently followed the picker below). */}\n <p className=\"text-xs text-muted-foreground truncate\">{effectiveProjectName || '—'}</p>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </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 <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {countLabel(atRiskMs)} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n {/* Project comes from the scope select above (ANR-09) — a sprint is always per-project, so burndown\n follows it here rather than repeating its own project picker. */}\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.delivery.forProject', 'For {{project}}', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </div>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\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 {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.analytics.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {/* ANR-02: this is arithmetic (the mean of the completed sprints shown), so it is labelled as an\n average, not a forecast. A real delivery forecast is separate work (full-spec phase G). */}\n {velocityChartData.length === 1\n ? t(\n 'planmagnet.analytics.velocity.trailingAverageOne',\n 'Average of the last completed sprint: {{points}} pts per sprint',\n { points: forecastNextSprint }\n )\n : t(\n 'planmagnet.analytics.velocity.trailingAverage',\n 'Average of the last {{count}} completed sprints: {{points}} pts per sprint',\n { points: forecastNextSprint, count: velocityChartData.length }\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cfd.subtitle', 'Selected date range, for {{project}}.', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* ANR-09: throughput's own group-by (day/week bucket). */}\n <ThroughputCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n\n {/* ANR-20 / ANR-21: quality and delivery KPIs, scoped to the page's project select (undefined = every\n project) and the shared date range. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <QualityMetricsCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <DeliveryKpisCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n </div>\n\n {/* Cycle time follows the delivery selector above; budgets follow the project scope select. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <BudgetPerformanceCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n {/* ANR-24: advanced visualizations — status-flow Sankey (one project, the delivery selector above) and the\n work-breakdown Treemap (project scope select; live counts, not date-ranged). */}\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.advanced.title', 'Advanced visualizations')}\n </h2>\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <StatusFlowCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <WorkBreakdownCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n // Per project, from the server. This used every ASSIGNED item in\n // the workspace for every project, so all bars were identical.\n const progress = projectProgress(p.id);\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TrendingUp, Zap, FolderKanban, BarChart3, Target } from 'lucide-react';\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n Line,\n LineChart,\n ReferenceArea,\n ReferenceLine,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts';\nimport { countLabel, useProjectCounts, useSprintCounts } from '../hooks/useCounts';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { DeliveryForecastCard } from '../forecast/DeliveryForecastCard';\nimport { BudgetPerformanceCard, CycleTimeCard } from './analytics/DeliveryHealthCards';\nimport { QualityMetricsCard } from './analytics/QualityMetricsCard';\nimport { DeliveryKpisCard } from './analytics/DeliveryKpisCard';\nimport { ThroughputCard } from './analytics/ThroughputCard';\nimport { StatusFlowCard } from './analytics/StatusFlowCard';\nimport { WorkBreakdownCard } from './analytics/WorkBreakdownCard';\nimport {\n ALL_PROJECTS,\n activePreset,\n isValidRange,\n presetRange,\n scopeToProjectId,\n toRangeVariables,\n type DateRange,\n type RangePreset,\n} from './analytics/analyticsRange';\n\n// Server-side totals (QA H2). The work-item list returns at most 100 rows per\n// page, so every figure computed from the loaded page undercounted past 100\n// items — and \"Project Health\" did not filter by project at all.\n// The chosen project's sprints, queried by project (BOFF-7964: the picker was built from the 100 newest sprints\n// of the whole workspace, so an older project's list could be empty).\nconst GET_PROJECT_SPRINTS = gql`\n query AnalyticsProjectSprints($workspaceId: ID!, $projectId: String) {\n planmagnetSprints(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n name\n status\n completedAt\n endDate\n }\n }\n }\n`;\n\n// At risk = missed, or still open and past due — counted on the server over every milestone.\nconst GET_MILESTONE_RISK = gql`\n query AnalyticsMilestoneRisk($workspaceId: ID!, $yesterday: DateTime) {\n missed: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { status: MISSED }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n overdue: planmagnetMilestones(\n workspaceId: $workspaceId\n filter: { open: true, dueTo: $yesterday }\n pagination: { first: 1 }\n ) {\n totalCount\n }\n }\n`;\n\nconst GET_ANALYTICS_COUNTS = gql`\n query GetAnalyticsWorkItemCounts($workspaceId: ID!) {\n planmagnetWorkItemCounts(workspaceId: $workspaceId) {\n total\n points\n blocked\n byStatus {\n status\n count\n points\n }\n byProject {\n projectId\n status\n count\n }\n }\n }\n`;\n\ninterface WorkItemCounts {\n total: number;\n points: number;\n blocked: number;\n byStatus: Array<{ status: string; count: number; points: number }>;\n byProject: Array<{ projectId: string; status: string; count: number }>;\n}\n\n// Only the project list (for the pickers and per-project health). Counts come from the server; the 500 work items,\n// 100 sprints and 100 milestones this used to load only fed page-derived counts (BOFF-7997).\nconst GET_ANALYTICS_DATA = gql`\n query GetAnalyticsData($workspaceId: ID!, $projectPagination: PlanmagnetPaginationInput) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: $projectPagination) {\n items {\n id\n name\n key\n status\n startDate\n endDate\n tags\n }\n totalCount\n }\n }\n`;\n\n// BOFF-4447: real server-computed sprint delivery analytics — burndown,\n// velocity, and cumulative flow are all aggregated in wspace-planmagnet-svc\n// (src/modules/analytics) from the Sprint/WorkItem data model plus the\n// WorkItemStatusTransition audit trail, not derived client-side from raw\n// lists like the summary cards above.\nconst GET_SPRINT_BURNDOWN = gql`\n query GetSprintBurndown($workspaceId: ID!, $sprintId: ID!) {\n planmagnetSprintBurndown(workspaceId: $workspaceId, sprintId: $sprintId) {\n sprintId\n startDate\n endDate\n totalPoints\n totalCount\n points {\n date\n remainingPoints\n remainingCount\n completedPoints\n completedCount\n }\n }\n }\n`;\n\nconst GET_VELOCITY = gql`\n query GetVelocity($workspaceId: ID!, $projectId: ID!, $trailingSprintCount: Int) {\n planmagnetVelocity(\n workspaceId: $workspaceId\n projectId: $projectId\n trailingSprintCount: $trailingSprintCount\n ) {\n projectId\n averageVelocity\n forecastNextSprintPoints\n forecastRange {\n p15\n p85\n }\n insufficientHistory\n sprints {\n sprintId\n sprintName\n sequenceNumber\n completedPoints\n completedCount\n plannedPoints\n }\n }\n }\n`;\n\nconst GET_CUMULATIVE_FLOW = gql`\n query GetCumulativeFlow(\n $workspaceId: ID!\n $projectId: ID!\n $startDate: DateTime\n $endDate: DateTime\n ) {\n planmagnetCumulativeFlow(\n workspaceId: $workspaceId\n projectId: $projectId\n startDate: $startDate\n endDate: $endDate\n ) {\n projectId\n startDate\n endDate\n points {\n date\n statuses {\n status\n count\n }\n }\n }\n }\n`;\n\ninterface ProjectNode {\n id: string;\n name: string;\n key: string;\n status: string;\n startDate: string | null;\n endDate: string | null;\n tags: string[];\n}\n\ninterface AnalyticsData {\n planmagnetProjects: { items: ProjectNode[]; totalCount: number };\n}\n\ninterface BurndownData {\n planmagnetSprintBurndown: {\n sprintId: string;\n startDate: string | null;\n endDate: string | null;\n totalPoints: number;\n totalCount: number;\n points: {\n date: string;\n remainingPoints: number;\n remainingCount: number;\n completedPoints: number;\n completedCount: number;\n }[];\n } | null;\n}\n\ninterface VelocityData {\n planmagnetVelocity: {\n projectId: string;\n averageVelocity: number;\n forecastNextSprintPoints: number;\n forecastRange: { p15: number; p85: number } | null;\n insufficientHistory: boolean;\n sprints: {\n sprintId: string;\n sprintName: string;\n sequenceNumber: number;\n completedPoints: number;\n completedCount: number;\n plannedPoints: number | null;\n }[];\n };\n}\n\ninterface CumulativeFlowData {\n planmagnetCumulativeFlow: {\n projectId: string;\n startDate: string;\n endDate: string;\n points: {\n date: string;\n statuses: { status: string; count: number }[];\n }[];\n };\n}\n\n// recharts' `stroke`/`fill` props render as literal SVG paint attributes, not\n// Tailwind classNames — they cannot consume the semantic `bg-*`/`text-*`\n// utility tokens the lint rule expects everywhere else on this page. This is\n// the one place on the page raw color values are load-bearing rather than\n// stylistic, matching the same constraint every other MFE chart primitive in\n// the fleet (e.g. microfe-assethandler's `ChartCard`/`DonutChart`) hits.\nconst CFD_STATUS_COLORS: Record<string, string> = {\n BACKLOG: '#9ca3af',\n TODO: '#60a5fa',\n IN_PROGRESS: '#fbbf24',\n IN_REVIEW: '#a78bfa',\n TESTING: '#f472b6',\n DONE: '#4ade80',\n CANCELLED: '#f87171',\n};\n\nfunction formatDay(iso: string): string {\n const d = new Date(iso);\n return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { workspaceId } = usePlanMagnet();\n const { t } = useI18n();\n const [selectedSprintId, setSelectedSprintId] = useState<string>('');\n\n // ANR-09: date range + project scope live in the URL (?from=&to=&project=) so a view is linkable, and so a\n // Dashboard KPI tile can deep-link straight into a pre-filtered view (see DashboardDashboardPagePage.tsx).\n const [searchParams, setSearchParams] = useSearchParams();\n const urlFrom = searchParams.get('from');\n const urlTo = searchParams.get('to');\n const [range, setRange] = useState<DateRange>(() =>\n urlFrom && urlTo && isValidRange(urlFrom, urlTo)\n ? { from: urlFrom, to: urlTo }\n : presetRange('30')\n );\n // The last range that was actually valid (from <= to), kept separate from `range` itself so an in-progress,\n // still-invalid edit in the inputs below can never leak into the query variables. `range` is always a valid\n // pair on mount (see the initializer above), so seeding this with it is safe.\n const [lastValidRange, setLastValidRange] = useState<DateRange>(range);\n const [scope, setScope] = useState<string>(searchParams.get('project') || ALL_PROJECTS);\n const scopeProjectId = scopeToProjectId(scope);\n const preset = activePreset(range);\n // Derived straight from `range` on every render — never a separately-tracked flag — so it can never go stale\n // relative to what the queries below actually send, however `range` got into an invalid state.\n const rangeIsValid = isValidRange(range.from, range.to);\n const rangeError = rangeIsValid\n ? null\n : t('planmagnet.analytics.range.invalid', '\"From\" must be on or before \"To\".');\n\n function applyRange(next: DateRange, nextScope: string) {\n setRange(next);\n if (isValidRange(next.from, next.to)) setLastValidRange(next);\n const params = new URLSearchParams(searchParams);\n params.set('from', next.from);\n params.set('to', next.to);\n if (nextScope !== ALL_PROJECTS) params.set('project', nextScope);\n else params.delete('project');\n setSearchParams(params, { replace: true });\n }\n\n function onPreset(p: RangePreset) {\n applyRange(presetRange(p), scope);\n }\n\n function onFromChange(value: string) {\n if (value && range.to && isValidRange(value, range.to)) {\n applyRange({ from: value, to: range.to }, scope);\n } else {\n setRange((r) => ({ ...r, from: value }));\n }\n }\n\n function onToChange(value: string) {\n if (value && range.from && isValidRange(range.from, value)) {\n applyRange({ from: range.from, to: value }, scope);\n } else {\n setRange((r) => ({ ...r, to: value }));\n }\n }\n\n function onScopeChange(value: string) {\n setScope(value);\n // The sprint select below is always scoped to the current project — an old selection would otherwise\n // silently carry over into whichever project happens to load first for the new scope.\n setSelectedSprintId('');\n applyRange(range, value);\n }\n\n // ANR-09 fix: while the visible `range` is invalid (rangeError above), every query below keeps using the last\n // known-good range instead of re-firing with an inverted from/to — `applyRange` only ever advances\n // `lastValidRange` on a genuinely valid pair, so this can never itself go out of range.\n const { startDate, endDate } = toRangeVariables(rangeIsValid ? range : lastValidRange);\n\n const { data, loading, error } = useQuery<AnalyticsData>(GET_ANALYTICS_DATA, {\n variables: { workspaceId, projectPagination: { first: 100 } },\n skip: !workspaceId,\n });\n\n const { data: countsData } = useQuery<{ planmagnetWorkItemCounts: WorkItemCounts }>(\n GET_ANALYTICS_COUNTS,\n { variables: { workspaceId }, skip: !workspaceId }\n );\n const counts = countsData?.planmagnetWorkItemCounts;\n const projectCounts = useProjectCounts();\n const sprintCounts = useSprintCounts();\n // Fixed for the life of the page (render must stay pure).\n const [yesterday] = useState(() => new Date(Date.now() - 86_400_000).toISOString().slice(0, 10));\n const { data: riskData } = useQuery<{\n missed: { totalCount: number };\n overdue: { totalCount: number };\n }>(GET_MILESTONE_RISK, { variables: { workspaceId, yesterday }, skip: !workspaceId });\n\n const projects = data?.planmagnetProjects?.items ?? [];\n\n // Default the project/sprint selectors to the first available option once\n // the summary data loads, so the delivery-analytics charts render without\n // requiring a manual pick every time the page opens. Filtering a list of\n // at most a few hundred sprints is cheap enough to redo on every render —\n // memoizing it would only need its own dependency-array workaround since\n // the `?? []` fallback above hands back a fresh array identity per render\n // whenever the query hasn't resolved yet.\n // Burndown/velocity/CFD/cycle-time/throughput all require a concrete project (the server fields take\n // `projectId: ID!`, not optional): \"All projects\" scope still needs SOME project for this section, so it falls\n // back to the first loaded project — the same fallback this page always used before ANR-09 added a scope select.\n const effectiveProjectId = scopeProjectId || projects[0]?.id || '';\n const { data: projectSprintsData } = useQuery<{\n planmagnetSprints: {\n items: {\n id: string;\n name: string;\n status: string;\n completedAt: string | null;\n endDate: string | null;\n }[];\n };\n }>(GET_PROJECT_SPRINTS, {\n variables: { workspaceId, projectId: effectiveProjectId },\n skip: !workspaceId || !effectiveProjectId,\n });\n const sprintsForProject = projectSprintsData?.planmagnetSprints.items ?? [];\n // Default to the running sprint (a PLANNING one has a future start and an empty burndown), else the latest\n // completed one, else whatever there is.\n const lastCompleted = sprintsForProject\n .filter((s) => s.status === 'COMPLETED')\n .sort((a, b) =>\n (b.completedAt ?? b.endDate ?? '').localeCompare(a.completedAt ?? a.endDate ?? '')\n )[0];\n const defaultSprint =\n sprintsForProject.find((s) => s.status === 'ACTIVE' || s.status === 'REVIEW') ??\n lastCompleted ??\n sprintsForProject[0];\n const effectiveSprintId = selectedSprintId || defaultSprint?.id || '';\n const effectiveProjectName = projects.find((p) => p.id === effectiveProjectId)?.name ?? '';\n\n const { data: burndownData, loading: burndownLoading } = useQuery<BurndownData>(\n GET_SPRINT_BURNDOWN,\n {\n variables: { workspaceId, sprintId: effectiveSprintId },\n skip: !workspaceId || !effectiveSprintId,\n }\n );\n\n const { data: velocityData, loading: velocityLoading } = useQuery<VelocityData>(GET_VELOCITY, {\n variables: { workspaceId, projectId: effectiveProjectId, trailingSprintCount: 6 },\n skip: !workspaceId || !effectiveProjectId,\n });\n\n const { data: cfdData, loading: cfdLoading } = useQuery<CumulativeFlowData>(GET_CUMULATIVE_FLOW, {\n // ANR-09: the page's shared date range, not the field's own unbounded default.\n variables: { workspaceId, projectId: effectiveProjectId, startDate, endDate },\n skip: !workspaceId || !effectiveProjectId,\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 (\n <div className=\"p-6 text-red-500\">\n {t('planmagnet.analytics.errorLoading', 'Error loading analytics')}: {error.message}\n </div>\n );\n }\n\n // Every figure comes from the server counts; while they load (or if they fail) the tiles say \"—\" instead of\n // quietly falling back to the loaded page (BOFF-7997).\n const statusCount = (status: string) => counts?.byStatus.find((c) => c.status === status);\n const cancelled = statusCount('CANCELLED')?.count ?? 0;\n const doneTasks = counts ? (statusCount('DONE')?.count ?? 0) : null;\n const totalTasks = counts ? counts.total : null;\n // Blocked = held by a dependency (the release readiness rule), not a status nobody can set (BOFF-7973).\n const blockedTasks = counts ? counts.blocked : null;\n const totalPoints = counts ? counts.points : null;\n const completedPoints = counts ? (statusCount('DONE')?.points ?? 0) : null;\n const projectProgress = (projectId: string) => {\n const rows = counts?.byProject.filter((r) => r.projectId === projectId) ?? [];\n // Cancelled work no longer needs doing (BOFF-7989).\n const live = rows.filter((r) => r.status !== 'CANCELLED').reduce((sum, r) => sum + r.count, 0);\n const done = rows.filter((r) => r.status === 'DONE').reduce((sum, r) => sum + r.count, 0);\n return live > 0 ? Math.round((done / live) * 100) : 0;\n };\n\n const activeSprints = sprintCounts?.running ?? null;\n const atRiskMs = riskData ? riskData.missed.totalCount + riskData.overdue.totalCount : null;\n const activeProjects = projectCounts?.active ?? null;\n const completionRate =\n totalTasks != null && doneTasks != null && totalTasks - cancelled > 0\n ? Math.round((doneTasks / (totalTasks - cancelled)) * 100)\n : null;\n\n const burndown = burndownData?.planmagnetSprintBurndown;\n const burndownChartData =\n burndown?.points.map((p) => ({\n day: formatDay(p.date),\n remaining: p.remainingPoints,\n completed: p.completedPoints,\n })) ?? [];\n\n const velocity = velocityData?.planmagnetVelocity;\n const velocityChartData =\n velocity?.sprints.map((s) => ({\n name: s.sprintName,\n completed: s.completedPoints,\n planned: s.plannedPoints ?? undefined,\n })) ?? [];\n const avgVelocity = velocity ? Math.round(velocity.averageVelocity) : 0;\n const forecastNextSprint = velocity ? Math.round(velocity.forecastNextSprintPoints) : 0;\n const velocityForecastRange = velocity?.forecastRange ?? null;\n const velocityInsufficientHistory = velocity?.insufficientHistory ?? false;\n\n const cfd = cfdData?.planmagnetCumulativeFlow;\n const cfdStatusKeys = Array.from(\n new Set(cfd?.points.flatMap((p) => p.statuses.map((s) => s.status)) ?? [])\n );\n const cfdChartData =\n cfd?.points.map((p) => {\n const row: Record<string, string | number> = { day: formatDay(p.date) };\n for (const s of p.statuses) row[s.status] = s.count;\n return row;\n }) ?? [];\n\n const metrics = [\n {\n label: t('planmagnet.analytics.metrics.totalWorkItems.label', 'Total Work Items'),\n value: countLabel(totalTasks),\n trend: `${countLabel(completionRate)}% ${t('planmagnet.analytics.metrics.totalWorkItems.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.storyPoints.label', 'Story Points'),\n value: countLabel(totalPoints),\n trend: `${countLabel(completedPoints)} ${t('planmagnet.analytics.metrics.storyPoints.trend', 'completed')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.activeProjects.label', 'Active Projects'),\n value: countLabel(activeProjects),\n trend: `${countLabel(projectCounts?.all)} ${t('planmagnet.analytics.metrics.activeProjects.trend', 'total')}`,\n },\n {\n label: t('planmagnet.analytics.metrics.blockedItems.label', 'Blocked Items'),\n value: countLabel(blockedTasks),\n trend:\n blockedTasks == null\n ? ''\n : blockedTasks > 0\n ? t('planmagnet.analytics.metrics.blockedItems.attention', 'Needs attention')\n : t('planmagnet.analytics.metrics.blockedItems.clear', 'All clear'),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {t('planmagnet.analytics.title', 'Analytics Dashboard')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.subtitle', 'Overview of project health and team performance')}\n </p>\n </div>\n\n {/* ANR-09: date range + project scope. Both live in the URL, so this view is linkable and a Dashboard tile\n can deep-link straight into it pre-filtered. Everything below — CFD, cycle time, throughput, the new\n Quality/Delivery cards, and the budget card — reads this same range and scope. */}\n <div\n className=\"flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between\"\n data-testid=\"analytics-range\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <div\n className=\"flex items-center gap-1 rounded-md border p-0.5\"\n role=\"group\"\n aria-label={t('planmagnet.analytics.range.presetLabel', 'Date range preset')}\n data-testid=\"analytics-range-preset\"\n >\n {(['7', '30', '90'] as const).map((p) => (\n <button\n key={p}\n type=\"button\"\n aria-pressed={preset === p}\n onClick={() => onPreset(p)}\n className={`px-2.5 py-1 text-xs rounded ${\n preset === p\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`}\n >\n {t('planmagnet.analytics.range.last', 'Last {{days}} days', { days: p })}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor=\"analytics-range-from\" className=\"sr-only\">\n {t('planmagnet.analytics.range.from', 'From')}\n </label>\n <Input\n id=\"analytics-range-from\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.from}\n max={range.to}\n onChange={(e) => onFromChange(e.target.value)}\n />\n <span className=\"text-muted-foreground text-sm\">–</span>\n <label htmlFor=\"analytics-range-to\" className=\"sr-only\">\n {t('planmagnet.analytics.range.to', 'To')}\n </label>\n <Input\n id=\"analytics-range-to\"\n type=\"date\"\n className=\"h-8 w-36\"\n value={range.to}\n min={range.from}\n onChange={(e) => onToChange(e.target.value)}\n />\n </div>\n {rangeError && (\n <p role=\"alert\" className=\"text-xs text-status-error-text\">\n {rangeError}\n </p>\n )}\n </div>\n\n <Select value={scope} onValueChange={onScopeChange}>\n <SelectTrigger className=\"h-8 w-56\" data-testid=\"analytics-project\">\n <SelectValue\n placeholder={t('planmagnet.analytics.range.allProjects', 'All projects')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_PROJECTS}>\n {t('planmagnet.analytics.range.allProjects', 'All projects')}\n </SelectItem>\n {projects.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4\">\n {metrics.map((m) => (\n <Card key={m.label}>\n <CardContent className=\"pt-4\">\n <p className=\"text-xs text-muted-foreground\">{m.label}</p>\n <p className=\"text-xl font-bold mt-1\">{m.value}</p>\n <p className=\"text-xs text-green-600 mt-1\">{m.trend}</p>\n </CardContent>\n </Card>\n ))}\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 <FolderKanban className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeProjects', 'Active Projects')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeProjects)}</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 <Zap className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.activeSprints', 'Active Sprints')}\n </div>\n <p className=\"text-xl font-bold mt-1\">{countLabel(activeSprints)}</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 <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.avgVelocity', 'Avg Velocity')}\n </div>\n {/* This one is per project — name it (BOFF-7987: it silently followed the picker below). */}\n <p className=\"text-xs text-muted-foreground truncate\">{effectiveProjectName || '—'}</p>\n <p className=\"text-xl font-bold mt-1\">\n {avgVelocity} {t('planmagnet.analytics.summary.pointsShort', 'pts')}\n </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 <Target className=\"h-4 w-4\" />\n {t('planmagnet.analytics.summary.atRisk', 'At Risk')}\n </div>\n <p className=\"text-xl font-bold mt-1\">\n {countLabel(atRiskMs)} {t('planmagnet.analytics.summary.milestones', 'milestones')}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-start sm:items-center\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.delivery.title', 'Sprint Delivery Analytics')}\n </h2>\n {/* Project comes from the scope select above (ANR-09) — a sprint is always per-project, so burndown\n follows it here rather than repeating its own project picker. */}\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.delivery.forProject', 'For {{project}}', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </div>\n <div className=\"flex gap-2 sm:ml-auto\">\n <Select value={effectiveSprintId} onValueChange={setSelectedSprintId}>\n <SelectTrigger className=\"h-8 w-48\">\n <SelectValue\n placeholder={t('planmagnet.analytics.delivery.sprintPlaceholder', 'Select sprint')}\n />\n </SelectTrigger>\n <SelectContent>\n {sprintsForProject.map((s) => (\n <SelectItem key={s.id} value={s.id}>\n {s.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\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 {t('planmagnet.analytics.burndown.title', 'Sprint Burndown')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {burndownLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : burndownChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={220}>\n <LineChart data={burndownChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n <Line\n type=\"monotone\"\n dataKey=\"remaining\"\n name={t('planmagnet.analytics.burndown.remaining', 'Remaining points')}\n stroke=\"#f59e0b\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"completed\"\n name={t('planmagnet.analytics.burndown.completed', 'Completed points')}\n stroke=\"#22c55e\"\n strokeWidth={2}\n dot={{ r: 2 }}\n />\n </LineChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.burndown.empty',\n 'No sprint selected, or the sprint has no work items yet'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <TrendingUp className=\"h-4 w-4\" />\n {t('planmagnet.analytics.velocity.title', 'Sprint Velocity')} (\n {velocityChartData.length} {t('planmagnet.analytics.velocity.sprints', 'Sprints')})\n </CardTitle>\n </CardHeader>\n <CardContent>\n {velocityLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : velocityChartData.length > 0 ? (\n <>\n <ResponsiveContainer width=\"100%\" height={190}>\n <BarChart data={velocityChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"name\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {/* ANR-02: the empirical p15-p85 band around next sprint's forecast, drawn as a\n full-width shaded region (no x1/x2) since it isn't tied to a historical sprint's\n x position. */}\n {velocityForecastRange && (\n <ReferenceArea\n y1={velocityForecastRange.p15}\n y2={velocityForecastRange.p85}\n fill=\"var(--color-chart-2)\"\n fillOpacity={0.12}\n stroke=\"none\"\n />\n )}\n <Bar\n dataKey=\"planned\"\n name={t('planmagnet.analytics.velocity.planned', 'Planned')}\n fill=\"#cbd5e1\"\n />\n <Bar\n dataKey=\"completed\"\n name={t('planmagnet.analytics.velocity.completed', 'Completed')}\n fill=\"#6366f1\"\n />\n <ReferenceLine\n y={avgVelocity}\n stroke=\"#22c55e\"\n strokeDasharray=\"4 4\"\n label={{\n value: t('planmagnet.analytics.velocity.avgLabel', 'avg'),\n fontSize: 10,\n position: 'right',\n }}\n />\n </BarChart>\n </ResponsiveContainer>\n {/* Accessible text restatement of the shaded band, for screen readers. */}\n <p className=\"text-xs text-muted-foreground text-center mt-2\">\n {velocityChartData.length === 1\n ? t(\n 'planmagnet.analytics.velocity.trailingAverageOne',\n 'Median of the last completed sprint: {{points}} pts per sprint',\n { points: forecastNextSprint }\n )\n : t(\n 'planmagnet.analytics.velocity.trailingAverage',\n 'Median of the last {{count}} completed sprints: {{points}} pts per sprint',\n { points: forecastNextSprint, count: velocityChartData.length }\n )}\n {velocityForecastRange &&\n ` · ${t(\n 'planmagnet.analytics.velocity.range',\n 'likely range (p15–p85): {{low}}–{{high}} pts',\n {\n low: Math.round(velocityForecastRange.p15),\n high: Math.round(velocityForecastRange.p85),\n }\n )}`}\n </p>\n {velocityInsufficientHistory && (\n <p className=\"text-xs text-status-warning-text text-center mt-1\">\n {t(\n 'planmagnet.analytics.velocity.insufficientHistory',\n 'Fewer than 3 sprints with completed points — this forecast is low-confidence.'\n )}\n </p>\n )}\n <p className=\"text-xs text-muted-foreground text-center mt-1\">\n {/* Median + empirical percentile of real past sprints — statistical, never AI.\n Not Monte Carlo (that label is for planmagnetForecastDelivery's multi-sprint\n simulation below): a single next-period estimate reads straight off this\n sample's own percentiles. */}\n {t(\n 'planmagnet.analytics.velocity.methodLabel',\n 'Statistical forecast (not AI-generated).'\n )}\n </p>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t(\n 'planmagnet.analytics.velocity.empty',\n 'No completed sprints yet for this project'\n )}\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* ANR-02: the Monte Carlo delivery forecast for the same project — the \"only this sprint\" toggle\n scopes it to the sprint selected above instead of the project's full open scope. */}\n <DeliveryForecastCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n sprintId={effectiveSprintId || undefined}\n sprintName={sprintsForProject.find((s) => s.id === effectiveSprintId)?.name}\n />\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cfd.title', 'Cumulative Flow Diagram')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cfd.subtitle', 'Selected date range, for {{project}}.', {\n project:\n effectiveProjectName || t('planmagnet.analytics.delivery.noProject', 'no project'),\n })}\n </p>\n </CardHeader>\n <CardContent>\n {cfdLoading ? (\n <div className=\"flex justify-center py-12\">\n <div className=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\" />\n </div>\n ) : cfdChartData.length > 0 ? (\n <ResponsiveContainer width=\"100%\" height={260}>\n <AreaChart data={cfdChartData}>\n <CartesianGrid strokeDasharray=\"3 3\" opacity={0.2} />\n <XAxis dataKey=\"day\" tick={{ fontSize: 11 }} />\n <YAxis tick={{ fontSize: 11 }} allowDecimals={false} />\n <Tooltip />\n <Legend wrapperStyle={{ fontSize: 12 }} />\n {cfdStatusKeys.map((status) => (\n <Area\n key={status}\n type=\"monotone\"\n dataKey={status}\n name={status.toLowerCase().replace('_', ' ')}\n stackId=\"cfd\"\n stroke={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fill={CFD_STATUS_COLORS[status] ?? '#a3a3a3'}\n fillOpacity={0.7}\n />\n ))}\n </AreaChart>\n </ResponsiveContainer>\n ) : (\n <p className=\"text-sm text-muted-foreground py-8 text-center\">\n {t('planmagnet.analytics.cfd.empty', 'No work items found for this project')}\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* ANR-09: throughput's own group-by (day/week bucket). */}\n <ThroughputCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n\n {/* ANR-20 / ANR-21: quality and delivery KPIs, scoped to the page's project select (undefined = every\n project) and the shared date range. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <QualityMetricsCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <DeliveryKpisCard\n workspaceId={workspaceId}\n projectId={scopeProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n </div>\n\n {/* Cycle time follows the delivery selector above; budgets follow the project scope select. */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <CycleTimeCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <BudgetPerformanceCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n {/* ANR-24: advanced visualizations — status-flow Sankey (one project, the delivery selector above) and the\n work-breakdown Treemap (project scope select; live counts, not date-ranged). */}\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('planmagnet.analytics.advanced.title', 'Advanced visualizations')}\n </h2>\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n <StatusFlowCard\n workspaceId={workspaceId}\n projectId={effectiveProjectId}\n startDate={startDate}\n endDate={endDate}\n />\n <WorkBreakdownCard workspaceId={workspaceId} projectId={scopeProjectId} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>{t('planmagnet.analytics.projectHealth.title', 'Project Health')}</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {projects\n .filter((p) => p.status === 'ACTIVE')\n .map((p) => {\n // Per project, from the server. This used every ASSIGNED item in\n // the workspace for every project, so all bars were identical.\n const progress = projectProgress(p.id);\n const hasOverdue = p.endDate && new Date(p.endDate) < new Date() && progress < 100;\n const health = hasOverdue ? 'at_risk' : progress >= 75 ? 'on_track' : 'in_progress';\n return (\n <div key={p.id} className=\"flex items-center gap-4\">\n <span className=\"text-sm font-medium w-40 truncate\">{p.name}</span>\n <div className=\"flex-1\">\n <div className=\"w-full h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full ${health === 'at_risk' ? 'bg-orange-400' : health === 'on_track' ? 'bg-green-400' : 'bg-blue-400'}`}\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n <span className=\"text-sm w-12 text-right\">{progress}%</span>\n <Badge variant=\"outline\" className=\"capitalize text-xs w-20 justify-center\">\n {health.replace('_', ' ')}\n </Badge>\n </div>\n );\n })}\n {projects.filter((p) => p.status === 'ACTIVE').length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-4\">\n {t('planmagnet.analytics.projectHealth.empty', 'No active projects found')}\n </p>\n )}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { isForbidden as t, mutationErrorMessage as n } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
4
|
+
import { ErrorLine as i } from "../controls/ConfirmButton.js";
|
|
5
5
|
import { useEntityId as a } from "../hooks/useEntityId.js";
|
|
6
6
|
import { actionsFromApi as o, actionsToApi as s, conditionsFromApi as c, conditionsToApi as l, saveBlockReason as u, scheduleConditionsFromApi as d, scheduleConditionsToApi as f } from "../automation/automationRuleTypes.js";
|
|
7
7
|
import { AUTOMATION_RULE_QUERIES as p, AutomationRuleDeleteButton as m, AutomationRuleStatusSwitch as h, RunAutomationRuleDialog as g } from "../controls/AutomationRuleControls.js";
|
|
@@ -86,22 +86,22 @@ function L() {
|
|
|
86
86
|
let q = u(U), J = async () => {
|
|
87
87
|
if (q) return;
|
|
88
88
|
z(null), V(!1);
|
|
89
|
-
let e = F(H),
|
|
90
|
-
U.name.trim() !== e.name && (
|
|
89
|
+
let e = F(H), i = {};
|
|
90
|
+
U.name.trim() !== e.name && (i.name = U.name.trim()), U.description.trim() !== e.description.trim() && (i.description = U.description.trim() || null), U.triggerType !== e.triggerType && (i.triggerType = U.triggerType);
|
|
91
91
|
let a = I(U);
|
|
92
|
-
JSON.stringify(a) !== JSON.stringify(I(e)) && (
|
|
92
|
+
JSON.stringify(a) !== JSON.stringify(I(e)) && (i.conditions = a);
|
|
93
93
|
let o = s(U.actions);
|
|
94
|
-
if (JSON.stringify(o) !== JSON.stringify(s(e.actions)) && (
|
|
94
|
+
if (JSON.stringify(o) !== JSON.stringify(s(e.actions)) && (i.actions = o), Object.keys(i).length === 0) {
|
|
95
95
|
V(!0);
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
try {
|
|
99
|
-
await
|
|
99
|
+
await r(C({ variables: {
|
|
100
100
|
id: H.id,
|
|
101
|
-
input:
|
|
101
|
+
input: i
|
|
102
102
|
} })), V(!0);
|
|
103
103
|
} catch (e) {
|
|
104
|
-
z(
|
|
104
|
+
z(t(e) ? "Your role can view automation rules but not change them." : n(e, "The rule could not be saved."));
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
return /* @__PURE__ */ D("div", {
|
|
@@ -136,7 +136,7 @@ function L() {
|
|
|
136
136
|
})
|
|
137
137
|
})]
|
|
138
138
|
}),
|
|
139
|
-
R && /* @__PURE__ */ E(
|
|
139
|
+
R && /* @__PURE__ */ E(i, { message: R }),
|
|
140
140
|
B && !R && /* @__PURE__ */ E("p", {
|
|
141
141
|
className: "text-sm text-muted-foreground",
|
|
142
142
|
children: "Saved."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { isForbidden as n, mutationErrorMessage as r } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { throwOnError as i } from "../utils/mutationResult.js";
|
|
4
4
|
import { detailPath as a } from "../utils/rowNavigation.js";
|
|
5
5
|
import { actionsToApi as o, conditionsToApi as s, saveBlockReason as c, scheduleConditionsToApi as l } from "../automation/automationRuleTypes.js";
|
|
6
6
|
import { AUTOMATION_RULE_QUERIES as u } from "../controls/AutomationRuleControls.js";
|
|
@@ -33,7 +33,7 @@ function D() {
|
|
|
33
33
|
if (!(!F || !g)) {
|
|
34
34
|
j(null);
|
|
35
35
|
try {
|
|
36
|
-
let e = (await
|
|
36
|
+
let e = (await i(M({ variables: { input: {
|
|
37
37
|
projectId: O.projectId,
|
|
38
38
|
name: O.name.trim(),
|
|
39
39
|
description: O.description.trim() || void 0,
|
|
@@ -44,7 +44,7 @@ function D() {
|
|
|
44
44
|
} } }))).data?.planmagnetCreateAutomationRule.id;
|
|
45
45
|
D(e ? a("/automation/automation-rule-detail-page", e) : "/automation/automation-rules-page");
|
|
46
46
|
} catch (e) {
|
|
47
|
-
j(
|
|
47
|
+
j(n(e) ? "Your role can view automation rules but not change them." : r(e, "The rule could not be created."));
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
};
|