@burdenoff/microfe-assethandler 2026.826.2 → 2026.826.4
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/_rolldown/runtime.js +1 -1
- package/dist/assethandler/AssetHandlerRoot.js.map +1 -1
- package/dist/assethandler/AssetHandlerRoutes.js.map +1 -1
- package/dist/assethandler/components/ActivityTimeline.js.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.js.map +1 -1
- package/dist/assethandler/components/ChartCard.js.map +1 -1
- package/dist/assethandler/components/CommentsPanel.js.map +1 -1
- package/dist/assethandler/components/NotFoundState.js.map +1 -1
- package/dist/assethandler/components/Pagination.js.map +1 -1
- package/dist/assethandler/components/SectionHeader.js.map +1 -1
- package/dist/assethandler/components/StatTile.js.map +1 -1
- package/dist/assethandler/components/charts/BarsChart.js.map +1 -1
- package/dist/assethandler/components/charts/ChartParts.js.map +1 -1
- package/dist/assethandler/components/charts/DonutChart.js.map +1 -1
- package/dist/assethandler/components/charts/ProgressMeter.js.map +1 -1
- package/dist/assethandler/components/charts/RadialGauge.js.map +1 -1
- package/dist/assethandler/components/charts/Sparkline.js.map +1 -1
- package/dist/assethandler/components/charts/TrendChart.js.map +1 -1
- package/dist/assethandler/components/charts/chartTheme.js.map +1 -1
- package/dist/assethandler/hooks/useAssetHandlerNavigate.js.map +1 -1
- package/dist/assethandler/hooks/useCopy.js.map +1 -1
- package/dist/assethandler/hooks/useMaintenancePlans.js.map +1 -1
- package/dist/assethandler/ops/MaintenancePlanDialog.js.map +1 -1
- package/dist/assethandler/ops/NewWorkOrderDialog.js.map +1 -1
- package/dist/assethandler/ops/ScheduleMaintenanceDialog.js.map +1 -1
- package/dist/assethandler/pages/AiPredictionsPage.js.map +1 -1
- package/dist/assethandler/pages/AnalyticsPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCategoriesPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCheckInOutPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetCreatePage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetListPage.js.map +1 -1
- package/dist/assethandler/pages/AssetsAssetTrackingPage.js.map +1 -1
- package/dist/assethandler/pages/CmdbBrowserPage.js.map +1 -1
- package/dist/assethandler/pages/DashboardPage.js.map +1 -1
- package/dist/assethandler/pages/DigitalTwinPage.js.map +1 -1
- package/dist/assethandler/pages/DiscoveryJobsPage.js.map +1 -1
- package/dist/assethandler/pages/EsgDashboardPage.js.map +1 -1
- package/dist/assethandler/pages/FinanceBudgetDetailPage.js.map +1 -1
- package/dist/assethandler/pages/FinanceFinanceHubPage.js.map +1 -1
- package/dist/assethandler/pages/FinanceInvoiceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/IotDevicesPage.js.map +1 -1
- package/dist/assethandler/pages/ItilChangeDetailPage.js.map +1 -1
- package/dist/assethandler/pages/ItilChangeManagementPage.js.map +1 -1
- package/dist/assethandler/pages/ItilDialogs.js.map +1 -1
- package/dist/assethandler/pages/ItilIncidentDetailPage.js.map +1 -1
- package/dist/assethandler/pages/ItilIncidentManagementPage.js.map +1 -1
- package/dist/assethandler/pages/ItilItilhubPage.js.map +1 -1
- package/dist/assethandler/pages/ItilProblemDetailPage.js.map +1 -1
- package/dist/assethandler/pages/ItilProblemManagementPage.js.map +1 -1
- package/dist/assethandler/pages/KnowledgeBasePage.js.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceCalendarPage.js.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenancePlansPage.d.ts.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenancePlansPage.js +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenancePlansPage.js.map +1 -1
- package/dist/assethandler/pages/MaintenanceMaintenanceSchedulerPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsMaintenanceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsOperationsHubPage.js.map +1 -1
- package/dist/assethandler/pages/OperationsWorkOrderDetailPage.js.map +1 -1
- package/dist/assethandler/pages/PortfolioPortfolioOverviewPage.js.map +1 -1
- package/dist/assethandler/pages/ProvenancePage.js.map +1 -1
- package/dist/assethandler/pages/ReportsDashboardPage.js.map +1 -1
- package/dist/assethandler/pages/SamLicensesPage.js.map +1 -1
- package/dist/assethandler/pages/SecurityComplianceDetailPage.js.map +1 -1
- package/dist/assethandler/pages/SecurityPolicyDetailPage.js.map +1 -1
- package/dist/assethandler/pages/SecuritySecurityEventDetailPage.js.map +1 -1
- package/dist/assethandler/pages/SecuritySecurityHubPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionDetailPage.js.map +1 -1
- package/dist/assethandler/pages/TransactionsTransactionListPage.js.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.js.map +1 -1
- package/dist/assethandler/store/assethandlerStore.js.map +1 -1
- package/dist/assethandler/utils/categoryPresentation.js.map +1 -1
- package/dist/assethandler/utils/cn.js.map +1 -1
- package/dist/assethandler/utils/csv.js.map +1 -1
- package/dist/assethandler/utils/i18n.js.map +1 -1
- package/dist/assethandler/utils/knowledgeBase.js.map +1 -1
- package/dist/assethandler/utils/meter.js.map +1 -1
- package/dist/assethandler/utils/money.js.map +1 -1
- package/dist/assethandler/utils/navigation.js.map +1 -1
- package/dist/assethandler/utils/printAssetTag.js.map +1 -1
- package/dist/assethandler/utils/toast.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/d3-array/src/ascending.js.map +1 -1
- package/dist/node_modules/d3-array/src/bisect.js.map +1 -1
- package/dist/node_modules/d3-array/src/bisector.js.map +1 -1
- package/dist/node_modules/d3-array/src/descending.js.map +1 -1
- package/dist/node_modules/d3-array/src/max.js.map +1 -1
- package/dist/node_modules/d3-array/src/min.js.map +1 -1
- package/dist/node_modules/d3-array/src/number.js.map +1 -1
- package/dist/node_modules/d3-array/src/quantile.js.map +1 -1
- package/dist/node_modules/d3-array/src/quickselect.js.map +1 -1
- package/dist/node_modules/d3-array/src/range.js.map +1 -1
- package/dist/node_modules/d3-array/src/sort.js.map +1 -1
- package/dist/node_modules/d3-array/src/ticks.js.map +1 -1
- package/dist/node_modules/d3-color/src/color.js.map +1 -1
- package/dist/node_modules/d3-color/src/define.js.map +1 -1
- package/dist/node_modules/d3-format/src/defaultLocale.js.map +1 -1
- package/dist/node_modules/d3-format/src/exponent.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatDecimal.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatGroup.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatNumerals.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatPrefixAuto.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatRounded.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatSpecifier.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatTrim.js.map +1 -1
- package/dist/node_modules/d3-format/src/formatTypes.js.map +1 -1
- package/dist/node_modules/d3-format/src/identity.js.map +1 -1
- package/dist/node_modules/d3-format/src/locale.js.map +1 -1
- package/dist/node_modules/d3-format/src/precisionFixed.js.map +1 -1
- package/dist/node_modules/d3-format/src/precisionPrefix.js.map +1 -1
- package/dist/node_modules/d3-format/src/precisionRound.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/array.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/basis.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/basisClosed.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/color.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/date.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/number.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/numberArray.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/object.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/piecewise.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/rgb.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/round.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/string.js.map +1 -1
- package/dist/node_modules/d3-interpolate/src/value.js.map +1 -1
- package/dist/node_modules/d3-path/src/path.js.map +1 -1
- package/dist/node_modules/d3-scale/src/band.js.map +1 -1
- package/dist/node_modules/d3-scale/src/constant.js.map +1 -1
- package/dist/node_modules/d3-scale/src/continuous.js.map +1 -1
- package/dist/node_modules/d3-scale/src/diverging.js.map +1 -1
- package/dist/node_modules/d3-scale/src/identity.js.map +1 -1
- package/dist/node_modules/d3-scale/src/init.js.map +1 -1
- package/dist/node_modules/d3-scale/src/linear.js.map +1 -1
- package/dist/node_modules/d3-scale/src/log.js.map +1 -1
- package/dist/node_modules/d3-scale/src/nice.js.map +1 -1
- package/dist/node_modules/d3-scale/src/number.js.map +1 -1
- package/dist/node_modules/d3-scale/src/ordinal.js.map +1 -1
- package/dist/node_modules/d3-scale/src/pow.js.map +1 -1
- package/dist/node_modules/d3-scale/src/quantile.js.map +1 -1
- package/dist/node_modules/d3-scale/src/quantize.js.map +1 -1
- package/dist/node_modules/d3-scale/src/radial.js.map +1 -1
- package/dist/node_modules/d3-scale/src/sequential.js.map +1 -1
- package/dist/node_modules/d3-scale/src/sequentialQuantile.js.map +1 -1
- package/dist/node_modules/d3-scale/src/symlog.js.map +1 -1
- package/dist/node_modules/d3-scale/src/threshold.js.map +1 -1
- package/dist/node_modules/d3-scale/src/tickFormat.js.map +1 -1
- package/dist/node_modules/d3-scale/src/time.js.map +1 -1
- package/dist/node_modules/d3-scale/src/utcTime.js.map +1 -1
- package/dist/node_modules/d3-shape/src/area.js.map +1 -1
- package/dist/node_modules/d3-shape/src/array.js.map +1 -1
- package/dist/node_modules/d3-shape/src/constant.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/basis.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/basisClosed.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/basisOpen.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/bump.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/linear.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/linearClosed.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/monotone.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/natural.js.map +1 -1
- package/dist/node_modules/d3-shape/src/curve/step.js.map +1 -1
- package/dist/node_modules/d3-shape/src/line.js.map +1 -1
- package/dist/node_modules/d3-shape/src/noop.js.map +1 -1
- package/dist/node_modules/d3-shape/src/offset/expand.js.map +1 -1
- package/dist/node_modules/d3-shape/src/offset/none.js.map +1 -1
- package/dist/node_modules/d3-shape/src/offset/silhouette.js.map +1 -1
- package/dist/node_modules/d3-shape/src/offset/wiggle.js.map +1 -1
- package/dist/node_modules/d3-shape/src/order/none.js.map +1 -1
- package/dist/node_modules/d3-shape/src/path.js.map +1 -1
- package/dist/node_modules/d3-shape/src/point.js.map +1 -1
- package/dist/node_modules/d3-shape/src/stack.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/circle.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/cross.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/diamond.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/square.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/star.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/triangle.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol/wye.js.map +1 -1
- package/dist/node_modules/d3-shape/src/symbol.js.map +1 -1
- package/dist/node_modules/d3-time/src/day.js.map +1 -1
- package/dist/node_modules/d3-time/src/hour.js.map +1 -1
- package/dist/node_modules/d3-time/src/interval.js.map +1 -1
- package/dist/node_modules/d3-time/src/millisecond.js.map +1 -1
- package/dist/node_modules/d3-time/src/minute.js.map +1 -1
- package/dist/node_modules/d3-time/src/month.js.map +1 -1
- package/dist/node_modules/d3-time/src/second.js.map +1 -1
- package/dist/node_modules/d3-time/src/ticks.js.map +1 -1
- package/dist/node_modules/d3-time/src/week.js.map +1 -1
- package/dist/node_modules/d3-time/src/year.js.map +1 -1
- package/dist/node_modules/d3-time-format/src/defaultLocale.js.map +1 -1
- package/dist/node_modules/d3-time-format/src/locale.js.map +1 -1
- package/dist/node_modules/decimal.js-light/decimal.js.map +1 -1
- package/dist/node_modules/dijkstrajs/dijkstra.js.map +1 -1
- package/dist/node_modules/eventemitter3/index.js.map +1 -1
- package/dist/node_modules/fast-equals/dist/es/index.js.map +1 -1
- package/dist/node_modules/internmap/src/index.js.map +1 -1
- package/dist/node_modules/lodash/_DataView.js.map +1 -1
- package/dist/node_modules/lodash/_Hash.js.map +1 -1
- package/dist/node_modules/lodash/_ListCache.js.map +1 -1
- package/dist/node_modules/lodash/_Map.js.map +1 -1
- package/dist/node_modules/lodash/_MapCache.js.map +1 -1
- package/dist/node_modules/lodash/_Promise.js.map +1 -1
- package/dist/node_modules/lodash/_Set.js.map +1 -1
- package/dist/node_modules/lodash/_SetCache.js.map +1 -1
- package/dist/node_modules/lodash/_Stack.js +2 -1
- package/dist/node_modules/lodash/_Stack.js.map +1 -1
- package/dist/node_modules/lodash/_Symbol.js.map +1 -1
- package/dist/node_modules/lodash/_Uint8Array.js.map +1 -1
- package/dist/node_modules/lodash/_WeakMap.js.map +1 -1
- package/dist/node_modules/lodash/_apply.js.map +1 -1
- package/dist/node_modules/lodash/_arrayEvery.js.map +1 -1
- package/dist/node_modules/lodash/_arrayFilter.js.map +1 -1
- package/dist/node_modules/lodash/_arrayIncludes.js.map +1 -1
- package/dist/node_modules/lodash/_arrayIncludesWith.js.map +1 -1
- package/dist/node_modules/lodash/_arrayLikeKeys.js.map +1 -1
- package/dist/node_modules/lodash/_arrayMap.js.map +1 -1
- package/dist/node_modules/lodash/_arrayPush.js.map +1 -1
- package/dist/node_modules/lodash/_arraySome.js.map +1 -1
- package/dist/node_modules/lodash/_asciiToArray.js.map +1 -1
- package/dist/node_modules/lodash/_assocIndexOf.js.map +1 -1
- package/dist/node_modules/lodash/_baseAssignValue.js.map +1 -1
- package/dist/node_modules/lodash/_baseEach.js.map +1 -1
- package/dist/node_modules/lodash/_baseEvery.js.map +1 -1
- package/dist/node_modules/lodash/_baseExtremum.js.map +1 -1
- package/dist/node_modules/lodash/_baseFindIndex.js.map +1 -1
- package/dist/node_modules/lodash/_baseFlatten.js.map +1 -1
- package/dist/node_modules/lodash/_baseFor.js.map +1 -1
- package/dist/node_modules/lodash/_baseForOwn.js.map +1 -1
- package/dist/node_modules/lodash/_baseGet.js.map +1 -1
- package/dist/node_modules/lodash/_baseGetAllKeys.js.map +1 -1
- package/dist/node_modules/lodash/_baseGetTag.js.map +1 -1
- package/dist/node_modules/lodash/_baseGt.js.map +1 -1
- package/dist/node_modules/lodash/_baseHasIn.js.map +1 -1
- package/dist/node_modules/lodash/_baseIndexOf.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsArguments.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsEqual.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsEqualDeep.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsMatch.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsNaN.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsNative.js.map +1 -1
- package/dist/node_modules/lodash/_baseIsTypedArray.js.map +1 -1
- package/dist/node_modules/lodash/_baseIteratee.js.map +1 -1
- package/dist/node_modules/lodash/_baseKeys.js.map +1 -1
- package/dist/node_modules/lodash/_baseLt.js.map +1 -1
- package/dist/node_modules/lodash/_baseMap.js.map +1 -1
- package/dist/node_modules/lodash/_baseMatches.js.map +1 -1
- package/dist/node_modules/lodash/_baseMatchesProperty.js.map +1 -1
- package/dist/node_modules/lodash/_baseOrderBy.js.map +1 -1
- package/dist/node_modules/lodash/_baseProperty.js.map +1 -1
- package/dist/node_modules/lodash/_basePropertyDeep.js.map +1 -1
- package/dist/node_modules/lodash/_baseRange.js.map +1 -1
- package/dist/node_modules/lodash/_baseRest.js.map +1 -1
- package/dist/node_modules/lodash/_baseSetToString.js.map +1 -1
- package/dist/node_modules/lodash/_baseSlice.js.map +1 -1
- package/dist/node_modules/lodash/_baseSome.js.map +1 -1
- package/dist/node_modules/lodash/_baseSortBy.js.map +1 -1
- package/dist/node_modules/lodash/_baseTimes.js.map +1 -1
- package/dist/node_modules/lodash/_baseToString.js.map +1 -1
- package/dist/node_modules/lodash/_baseTrim.js.map +1 -1
- package/dist/node_modules/lodash/_baseUnary.js.map +1 -1
- package/dist/node_modules/lodash/_baseUniq.js.map +1 -1
- package/dist/node_modules/lodash/_cacheHas.js.map +1 -1
- package/dist/node_modules/lodash/_castPath.js.map +1 -1
- package/dist/node_modules/lodash/_castSlice.js.map +1 -1
- package/dist/node_modules/lodash/_compareAscending.js.map +1 -1
- package/dist/node_modules/lodash/_compareMultiple.js.map +1 -1
- package/dist/node_modules/lodash/_coreJsData.js.map +1 -1
- package/dist/node_modules/lodash/_createBaseEach.js.map +1 -1
- package/dist/node_modules/lodash/_createBaseFor.js.map +1 -1
- package/dist/node_modules/lodash/_createCaseFirst.js.map +1 -1
- package/dist/node_modules/lodash/_createFind.js.map +1 -1
- package/dist/node_modules/lodash/_createRange.js.map +1 -1
- package/dist/node_modules/lodash/_createSet.js.map +1 -1
- package/dist/node_modules/lodash/_defineProperty.js.map +1 -1
- package/dist/node_modules/lodash/_equalArrays.js.map +1 -1
- package/dist/node_modules/lodash/_equalByTag.js.map +1 -1
- package/dist/node_modules/lodash/_equalObjects.js.map +1 -1
- package/dist/node_modules/lodash/_freeGlobal.js.map +1 -1
- package/dist/node_modules/lodash/_getAllKeys.js.map +1 -1
- package/dist/node_modules/lodash/_getMapData.js.map +1 -1
- package/dist/node_modules/lodash/_getMatchData.js.map +1 -1
- package/dist/node_modules/lodash/_getNative.js.map +1 -1
- package/dist/node_modules/lodash/_getPrototype.js.map +1 -1
- package/dist/node_modules/lodash/_getRawTag.js.map +1 -1
- package/dist/node_modules/lodash/_getSymbols.js.map +1 -1
- package/dist/node_modules/lodash/_getTag.js.map +1 -1
- package/dist/node_modules/lodash/_getValue.js.map +1 -1
- package/dist/node_modules/lodash/_hasPath.js.map +1 -1
- package/dist/node_modules/lodash/_hasUnicode.js.map +1 -1
- package/dist/node_modules/lodash/_hashClear.js.map +1 -1
- package/dist/node_modules/lodash/_hashDelete.js.map +1 -1
- package/dist/node_modules/lodash/_hashGet.js.map +1 -1
- package/dist/node_modules/lodash/_hashHas.js.map +1 -1
- package/dist/node_modules/lodash/_hashSet.js.map +1 -1
- package/dist/node_modules/lodash/_isFlattenable.js.map +1 -1
- package/dist/node_modules/lodash/_isIndex.js.map +1 -1
- package/dist/node_modules/lodash/_isIterateeCall.js.map +1 -1
- package/dist/node_modules/lodash/_isKey.js.map +1 -1
- package/dist/node_modules/lodash/_isKeyable.js.map +1 -1
- package/dist/node_modules/lodash/_isMasked.js.map +1 -1
- package/dist/node_modules/lodash/_isPrototype.js.map +1 -1
- package/dist/node_modules/lodash/_isStrictComparable.js.map +1 -1
- package/dist/node_modules/lodash/_listCacheClear.js.map +1 -1
- package/dist/node_modules/lodash/_listCacheDelete.js.map +1 -1
- package/dist/node_modules/lodash/_listCacheGet.js.map +1 -1
- package/dist/node_modules/lodash/_listCacheHas.js.map +1 -1
- package/dist/node_modules/lodash/_listCacheSet.js.map +1 -1
- package/dist/node_modules/lodash/_mapCacheClear.js.map +1 -1
- package/dist/node_modules/lodash/_mapCacheDelete.js.map +1 -1
- package/dist/node_modules/lodash/_mapCacheGet.js.map +1 -1
- package/dist/node_modules/lodash/_mapCacheHas.js.map +1 -1
- package/dist/node_modules/lodash/_mapCacheSet.js.map +1 -1
- package/dist/node_modules/lodash/_mapToArray.js.map +1 -1
- package/dist/node_modules/lodash/_matchesStrictComparable.js.map +1 -1
- package/dist/node_modules/lodash/_memoizeCapped.js.map +1 -1
- package/dist/node_modules/lodash/_nativeCreate.js.map +1 -1
- package/dist/node_modules/lodash/_nativeKeys.js.map +1 -1
- package/dist/node_modules/lodash/_nodeUtil.js.map +1 -1
- package/dist/node_modules/lodash/_objectToString.js.map +1 -1
- package/dist/node_modules/lodash/_overArg.js.map +1 -1
- package/dist/node_modules/lodash/_overRest.js.map +1 -1
- package/dist/node_modules/lodash/_root.js.map +1 -1
- package/dist/node_modules/lodash/_setCacheAdd.js.map +1 -1
- package/dist/node_modules/lodash/_setCacheHas.js.map +1 -1
- package/dist/node_modules/lodash/_setToArray.js.map +1 -1
- package/dist/node_modules/lodash/_setToString.js.map +1 -1
- package/dist/node_modules/lodash/_shortOut.js.map +1 -1
- package/dist/node_modules/lodash/_stackClear.js.map +1 -1
- package/dist/node_modules/lodash/_stackDelete.js.map +1 -1
- package/dist/node_modules/lodash/_stackGet.js.map +1 -1
- package/dist/node_modules/lodash/_stackHas.js.map +1 -1
- package/dist/node_modules/lodash/_stackSet.js.map +1 -1
- package/dist/node_modules/lodash/_strictIndexOf.js.map +1 -1
- package/dist/node_modules/lodash/_stringToArray.js.map +1 -1
- package/dist/node_modules/lodash/_stringToPath.js.map +1 -1
- package/dist/node_modules/lodash/_toKey.js.map +1 -1
- package/dist/node_modules/lodash/_toSource.js.map +1 -1
- package/dist/node_modules/lodash/_trimmedEndIndex.js.map +1 -1
- package/dist/node_modules/lodash/_unicodeToArray.js.map +1 -1
- package/dist/node_modules/lodash/constant.js.map +1 -1
- package/dist/node_modules/lodash/debounce.js.map +1 -1
- package/dist/node_modules/lodash/eq.js.map +1 -1
- package/dist/node_modules/lodash/every.js.map +1 -1
- package/dist/node_modules/lodash/find.js.map +1 -1
- package/dist/node_modules/lodash/findIndex.js.map +1 -1
- package/dist/node_modules/lodash/flatMap.js.map +1 -1
- package/dist/node_modules/lodash/get.js.map +1 -1
- package/dist/node_modules/lodash/hasIn.js.map +1 -1
- package/dist/node_modules/lodash/identity.js.map +1 -1
- package/dist/node_modules/lodash/isArguments.js.map +1 -1
- package/dist/node_modules/lodash/isArray.js.map +1 -1
- package/dist/node_modules/lodash/isArrayLike.js.map +1 -1
- package/dist/node_modules/lodash/isBoolean.js.map +1 -1
- package/dist/node_modules/lodash/isBuffer.js.map +1 -1
- package/dist/node_modules/lodash/isEqual.js.map +1 -1
- package/dist/node_modules/lodash/isFunction.js.map +1 -1
- package/dist/node_modules/lodash/isLength.js.map +1 -1
- package/dist/node_modules/lodash/isNaN.js.map +1 -1
- package/dist/node_modules/lodash/isNil.js.map +1 -1
- package/dist/node_modules/lodash/isNumber.js.map +1 -1
- package/dist/node_modules/lodash/isObject.js.map +1 -1
- package/dist/node_modules/lodash/isObjectLike.js.map +1 -1
- package/dist/node_modules/lodash/isPlainObject.js.map +1 -1
- package/dist/node_modules/lodash/isString.js.map +1 -1
- package/dist/node_modules/lodash/isSymbol.js.map +1 -1
- package/dist/node_modules/lodash/isTypedArray.js.map +1 -1
- package/dist/node_modules/lodash/keys.js.map +1 -1
- package/dist/node_modules/lodash/last.js.map +1 -1
- package/dist/node_modules/lodash/map.js.map +1 -1
- package/dist/node_modules/lodash/mapValues.js.map +1 -1
- package/dist/node_modules/lodash/max.js.map +1 -1
- package/dist/node_modules/lodash/maxBy.js.map +1 -1
- package/dist/node_modules/lodash/memoize.js.map +1 -1
- package/dist/node_modules/lodash/min.js.map +1 -1
- package/dist/node_modules/lodash/minBy.js.map +1 -1
- package/dist/node_modules/lodash/noop.js.map +1 -1
- package/dist/node_modules/lodash/now.js.map +1 -1
- package/dist/node_modules/lodash/property.js.map +1 -1
- package/dist/node_modules/lodash/range.js.map +1 -1
- package/dist/node_modules/lodash/some.js.map +1 -1
- package/dist/node_modules/lodash/sortBy.js.map +1 -1
- package/dist/node_modules/lodash/stubArray.js.map +1 -1
- package/dist/node_modules/lodash/stubFalse.js.map +1 -1
- package/dist/node_modules/lodash/throttle.js.map +1 -1
- package/dist/node_modules/lodash/toFinite.js.map +1 -1
- package/dist/node_modules/lodash/toInteger.js.map +1 -1
- package/dist/node_modules/lodash/toNumber.js.map +1 -1
- package/dist/node_modules/lodash/toString.js.map +1 -1
- package/dist/node_modules/lodash/uniqBy.js.map +1 -1
- package/dist/node_modules/lodash/upperFirst.js.map +1 -1
- package/dist/node_modules/object-assign/index.js.map +1 -1
- package/dist/node_modules/prop-types/checkPropTypes.js.map +1 -1
- package/dist/node_modules/prop-types/factoryWithThrowingShims.js.map +1 -1
- package/dist/node_modules/prop-types/factoryWithTypeCheckers.js.map +1 -1
- package/dist/node_modules/prop-types/index.js.map +1 -1
- package/dist/node_modules/prop-types/lib/ReactPropTypesSecret.js.map +1 -1
- package/dist/node_modules/prop-types/lib/has.js.map +1 -1
- package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.development.js.map +1 -1
- package/dist/node_modules/prop-types/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
- package/dist/node_modules/prop-types/node_modules/react-is/index.js.map +1 -1
- package/dist/node_modules/qrcode/lib/browser.js.map +1 -1
- package/dist/node_modules/qrcode/lib/can-promise.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/alignment-pattern.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/alphanumeric-data.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/bit-buffer.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/bit-matrix.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/byte-data.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/error-correction-code.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/error-correction-level.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/finder-pattern.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/format-info.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/galois-field.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/kanji-data.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/mask-pattern.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/mode.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/numeric-data.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/polynomial.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/qrcode.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/reed-solomon-encoder.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/regex.js +1 -1
- package/dist/node_modules/qrcode/lib/core/regex.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/segments.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/utils.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/version-check.js.map +1 -1
- package/dist/node_modules/qrcode/lib/core/version.js.map +1 -1
- package/dist/node_modules/qrcode/lib/renderer/canvas.js.map +1 -1
- package/dist/node_modules/qrcode/lib/renderer/svg-tag.js.map +1 -1
- package/dist/node_modules/qrcode/lib/renderer/utils.js.map +1 -1
- package/dist/node_modules/react-is/cjs/react-is.development.js.map +1 -1
- package/dist/node_modules/react-is/cjs/react-is.production.min.js.map +1 -1
- package/dist/node_modules/react-is/index.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/Animate.js +2 -2
- package/dist/node_modules/react-smooth/es6/Animate.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/AnimateManager.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/configUpdate.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/easing.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/setRafTimeout.js.map +1 -1
- package/dist/node_modules/react-smooth/es6/util.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Area.js +13 -13
- package/dist/node_modules/recharts/es6/cartesian/Area.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Bar.js +9 -9
- package/dist/node_modules/recharts/es6/cartesian/Bar.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Brush.js +12 -12
- package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js +7 -7
- package/dist/node_modules/recharts/es6/cartesian/CartesianAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js +13 -13
- package/dist/node_modules/recharts/es6/cartesian/CartesianGrid.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js +4 -4
- package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Line.js +10 -10
- package/dist/node_modules/recharts/es6/cartesian/Line.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js +3 -3
- package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js +3 -3
- package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js +4 -4
- package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/XAxis.js +3 -3
- package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/YAxis.js +3 -3
- package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/getEquidistantTicks.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/getTicks.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js +1 -1
- package/dist/node_modules/recharts/es6/chart/AccessibilityManager.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/AreaChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/LineChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/PieChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/RadialBarChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js +22 -22
- package/dist/node_modules/recharts/es6/chart/generateCategoricalChart.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Cell.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Cursor.js +1 -1
- package/dist/node_modules/recharts/es6/component/Cursor.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js +11 -11
- package/dist/node_modules/recharts/es6/component/DefaultLegendContent.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js +6 -6
- package/dist/node_modules/recharts/es6/component/DefaultTooltipContent.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Label.js +14 -14
- package/dist/node_modules/recharts/es6/component/Label.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/LabelList.js +6 -6
- package/dist/node_modules/recharts/es6/component/LabelList.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Legend.js +5 -5
- package/dist/node_modules/recharts/es6/component/Legend.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js +3 -3
- package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Text.js +2 -2
- package/dist/node_modules/recharts/es6/component/Text.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/Tooltip.js +3 -3
- package/dist/node_modules/recharts/es6/component/Tooltip.js.map +1 -1
- package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js +2 -2
- package/dist/node_modules/recharts/es6/component/TooltipBoundingBox.js.map +1 -1
- package/dist/node_modules/recharts/es6/container/Layer.js +2 -2
- package/dist/node_modules/recharts/es6/container/Layer.js.map +1 -1
- package/dist/node_modules/recharts/es6/container/Surface.js +2 -2
- package/dist/node_modules/recharts/es6/container/Surface.js.map +1 -1
- package/dist/node_modules/recharts/es6/context/chartLayoutContext.js +2 -2
- package/dist/node_modules/recharts/es6/context/chartLayoutContext.js.map +1 -1
- package/dist/node_modules/recharts/es6/polar/Pie.js +13 -13
- package/dist/node_modules/recharts/es6/polar/Pie.js.map +1 -1
- package/dist/node_modules/recharts/es6/polar/PolarAngleAxis.js +8 -8
- package/dist/node_modules/recharts/es6/polar/PolarAngleAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/polar/PolarRadiusAxis.js +6 -6
- package/dist/node_modules/recharts/es6/polar/PolarRadiusAxis.js.map +1 -1
- package/dist/node_modules/recharts/es6/polar/RadialBar.js +6 -6
- package/dist/node_modules/recharts/es6/polar/RadialBar.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Cross.js +1 -1
- package/dist/node_modules/recharts/es6/shape/Cross.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Curve.js +1 -1
- package/dist/node_modules/recharts/es6/shape/Curve.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Dot.js +1 -1
- package/dist/node_modules/recharts/es6/shape/Dot.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Polygon.js +4 -4
- package/dist/node_modules/recharts/es6/shape/Polygon.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Rectangle.js +4 -4
- package/dist/node_modules/recharts/es6/shape/Rectangle.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Sector.js +1 -1
- package/dist/node_modules/recharts/es6/shape/Sector.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Symbols.js +1 -1
- package/dist/node_modules/recharts/es6/shape/Symbols.js.map +1 -1
- package/dist/node_modules/recharts/es6/shape/Trapezoid.js +4 -4
- package/dist/node_modules/recharts/es6/shape/Trapezoid.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js +9 -9
- package/dist/node_modules/recharts/es6/util/ActiveShapeUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/BarUtils.js +1 -1
- package/dist/node_modules/recharts/es6/util/BarUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/CartesianUtils.js +1 -1
- package/dist/node_modules/recharts/es6/util/CartesianUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ChartUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/CssPrefixUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/DOMUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/DetectReferenceElementsDomain.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/Events.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/Global.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/IfOverflowMatches.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/LogUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/PolarUtils.js +1 -1
- package/dist/node_modules/recharts/es6/util/PolarUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/RadialBarUtils.js +1 -1
- package/dist/node_modules/recharts/es6/util/RadialBarUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ReactUtils.js +1 -1
- package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js +1 -1
- package/dist/node_modules/recharts/es6/util/ReduceCSSCalc.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ShallowEqual.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/TickUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/calculateViewBox.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/cursor/getCursorPoints.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/cursor/getCursorRectangle.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/cursor/getRadialCursorPoints.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/getEveryNthWithCondition.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/getLegendProps.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/isDomainSpecifiedByUser.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/payload/getUniqPayload.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/tooltip/translate.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/types.js +1 -1
- package/dist/node_modules/recharts/es6/util/types.js.map +1 -1
- package/dist/node_modules/recharts-scale/es6/getNiceTickValues.js.map +1 -1
- package/dist/node_modules/recharts-scale/es6/util/arithmetic.js.map +1 -1
- package/dist/node_modules/recharts-scale/es6/util/utils.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +4 -4
- package/dist/generated/global-operations.d.ts +0 -2
- package/dist/generated/global-operations.d.ts.map +0 -1
- package/dist/generated/global-types.d.ts +0 -23527
- package/dist/generated/global-types.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/assethandler/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from 'react';\nimport { useAssetMoney } from '../providers/AssetHandlerProvider';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport {\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n AlertTriangle,\n Box,\n Clock,\n ClipboardList,\n Receipt,\n Wrench,\n CheckCircle,\n ArrowRight,\n CalendarClock,\n} from 'lucide-react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { useCopy } from '../hooks/useCopy';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n TrendChart,\n formatCompact,\n formatCurrencyCompact,\n seriesColor,\n type ChartDatum,\n type ChartSeries,\n type DonutSlice,\n} from '../components';\n\n// ─── Time-series helpers (all derived from real store timestamps) ─────────────\nfunction monthKey(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n}\nfunction monthLabel(key: string): string {\n const [year, month] = key.split('-').map(Number);\n return new Date(year, month - 1, 1).toLocaleString(undefined, { month: 'short' });\n}\nfunction parseDate(value: string | undefined | null): Date | null {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Group items into ordered monthly buckets, counting by a category accessor. */\nfunction monthlyGrouped<T>(\n items: T[],\n getDate: (item: T) => string | undefined | null,\n getGroup: (item: T) => string,\n maxMonths = 8,\n maxGroups = 6\n): { rows: ChartDatum[]; series: ChartSeries[] } {\n const byMonth = new Map<string, Map<string, number>>();\n const groupTotals = new Map<string, number>();\n for (const item of items) {\n const d = parseDate(getDate(item));\n if (!d) continue;\n const key = monthKey(d);\n const group = getGroup(item) || 'other';\n if (!byMonth.has(key)) byMonth.set(key, new Map());\n const bucket = byMonth.get(key)!;\n bucket.set(group, (bucket.get(group) ?? 0) + 1);\n groupTotals.set(group, (groupTotals.get(group) ?? 0) + 1);\n }\n if (byMonth.size === 0) return { rows: [], series: [] };\n\n const orderedGroups = [...groupTotals.entries()].sort((a, b) => b[1] - a[1]).map(([g]) => g);\n const topGroups = orderedGroups.slice(0, maxGroups);\n const hasOther = orderedGroups.length > maxGroups;\n const keptGroups = new Set(topGroups);\n\n const keys = [...byMonth.keys()].sort().slice(-maxMonths);\n const rows: ChartDatum[] = keys.map((key) => {\n const row: ChartDatum = { month: monthLabel(key) };\n for (const g of topGroups) row[g] = 0;\n if (hasOther) row.other = 0;\n for (const [group, count] of byMonth.get(key)!) {\n if (keptGroups.has(group)) row[group] = ((row[group] as number) ?? 0) + count;\n else if (hasOther) row.other = ((row.other as number) ?? 0) + count;\n }\n return row;\n });\n\n const series: ChartSeries[] = topGroups.map((g, i) => ({\n key: g,\n label: titleCase(g),\n color: seriesColor(i),\n }));\n if (hasOther) {\n series.push({ key: 'other', label: 'Other', color: seriesColor(topGroups.length) });\n }\n return { rows, series };\n}\n\nfunction relativeTime(value: string | undefined | null): string {\n const d = parseDate(value);\n if (!d) return '';\n const diff = Date.now() - d.getTime();\n const mins = Math.round(diff / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n if (days < 30) return `${days}d ago`;\n const months = Math.round(days / 30);\n if (months < 12) return `${months}mo ago`;\n return `${Math.round(months / 12)}y ago`;\n}\n\ntype PillTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\nconst PILL_CLASS: Record<PillTone, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-status-info-bg-subtle text-status-info-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nfunction StatusPill({ tone, children }: { tone: PillTone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium capitalize whitespace-nowrap',\n PILL_CLASS[tone]\n )}\n >\n {children}\n </span>\n );\n}\n\nconst priorityTone = (p: string): PillTone =>\n p === 'critical' ? 'error' : p === 'high' ? 'warning' : p === 'medium' ? 'info' : 'neutral';\nconst maintenanceTone = (status: string): PillTone =>\n status === 'completed' ? 'success' : status === 'in_progress' ? 'info' : 'warning';\n\ninterface FeedItem {\n id: string;\n icon: ComponentType<{ className?: string }>;\n title: string;\n meta: string;\n at: string;\n tone: PillTone;\n statusLabel: string;\n}\n\nexport function DashboardPage() {\n const { currency: moduleCurrency } = useAssetMoney();\n const tc = useCopy();\n const navigate = useAssetHandlerNavigate();\n const {\n hydrated,\n overview,\n assets,\n incidents,\n maintenanceRecords,\n changeRequests,\n workOrders,\n transactions,\n } = useAssetHandlerStore();\n\n const [assetsMode, setAssetsMode] = useState<'status' | 'category'>('status');\n\n const activeIncidents = incidents.filter((i) => i.status !== 'resolved' && i.status !== 'closed');\n const criticalIncidents = incidents.filter(\n (i) => i.priority === 'critical' && i.status !== 'resolved' && i.status !== 'closed'\n );\n const pendingMaintenance = maintenanceRecords.filter(\n (m) => m.status === 'scheduled' || m.status === 'in_progress'\n );\n const openWorkOrders = workOrders.filter(\n (w) => w.status !== 'completed' && w.status !== 'cancelled'\n );\n const pendingChanges = changeRequests.filter(\n (c) => c.status === 'approved' || c.status === 'submitted'\n );\n\n // Cumulative asset-count sparkline — the only KPI with real history.\n const assetSpark = useMemo(() => {\n const byMonth = new Map<string, number>();\n for (const a of assets) {\n const d = parseDate(a.createdAt);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + 1);\n }\n const keys = [...byMonth.keys()].sort();\n return keys.reduce<number[]>((acc, k) => {\n const prev = acc.length ? acc[acc.length - 1] : 0;\n acc.push(prev + (byMonth.get(k) ?? 0));\n return acc;\n }, []);\n }, [assets]);\n\n const metrics = [\n {\n label: tc('assethandler.dashboard.metrics.totalAssets', 'Total {{assets}}'),\n value: overview?.totalAssets ?? assets.length,\n icon: Box,\n spark: assetSpark.length > 1 ? assetSpark : undefined,\n hero: true,\n },\n {\n label: tc('assethandler.dashboard.metrics.openIncidents', 'Open Incidents'),\n value: overview?.openIncidents ?? activeIncidents.length,\n icon: AlertTriangle,\n },\n {\n label: tc('assethandler.dashboard.metrics.openWorkOrders', 'Open Work Orders'),\n value: overview?.openWorkOrders ?? openWorkOrders.length,\n icon: ClipboardList,\n },\n {\n label: tc('assethandler.dashboard.metrics.transactions', 'Transactions'),\n value: overview?.totalTransactions ?? transactions.length,\n icon: Receipt,\n },\n ];\n\n // Portfolio-by-category donut (real asset counts).\n const categoryDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n const key = a.category || 'uncategorized';\n acc[key] = (acc[key] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name: titleCase(name), value }));\n }, [assets]);\n\n // Assets-added-over-time (stacked by status or category via the toggle).\n const assetsSeries = useMemo(\n () =>\n monthlyGrouped(\n assets,\n (a) => a.createdAt,\n (a) => (assetsMode === 'status' ? a.status : a.category),\n 8,\n 6\n ),\n [assets, assetsMode]\n );\n\n // Cumulative transaction value over time (real transaction amounts).\n const valueTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const tx of transactions) {\n const d = parseDate(tx.date);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + tx.amount);\n }\n const keys = [...byMonth.keys()].sort();\n const raw = keys.reduce<number[]>((acc, k) => {\n const prev = acc.length ? acc[acc.length - 1] : 0;\n acc.push(prev + (byMonth.get(k) ?? 0));\n return acc;\n }, []);\n return keys.map((k, i) => ({ month: monthLabel(k), value: Math.round(raw[i]) }));\n }, [transactions]);\n const txCurrency = transactions[0]?.currency ?? moduleCurrency;\n\n // Unified recent-activity feed (merged real records, newest first).\n const feed = useMemo<FeedItem[]>(() => {\n const items: FeedItem[] = [];\n for (const a of assets) {\n items.push({\n id: `asset-${a.id}`,\n icon: Box,\n title: a.name,\n meta: tc('assethandler.dashboard.activity.assetRegistered', '{{asset}} registered'),\n at: a.createdAt,\n tone:\n a.status === 'active' ? 'success' : a.status === 'maintenance' ? 'warning' : 'neutral',\n statusLabel: a.status.replace(/_/g, ' '),\n });\n }\n for (const inc of incidents) {\n items.push({\n id: `inc-${inc.id}`,\n icon: AlertTriangle,\n title: inc.title,\n meta: `Incident · ${inc.priority}`,\n at: inc.createdAt,\n tone:\n inc.status === 'resolved' || inc.status === 'closed'\n ? 'success'\n : inc.priority === 'critical' || inc.priority === 'high'\n ? 'error'\n : 'warning',\n statusLabel: inc.status.replace(/_/g, ' '),\n });\n }\n for (const wo of workOrders) {\n items.push({\n id: `wo-${wo.id}`,\n icon: ClipboardList,\n title: wo.title,\n meta: `Work order · ${wo.type}`,\n at: wo.createdAt,\n tone:\n wo.status === 'completed'\n ? 'success'\n : wo.status === 'in_progress'\n ? 'info'\n : wo.status === 'cancelled'\n ? 'neutral'\n : 'warning',\n statusLabel: wo.status.replace(/_/g, ' '),\n });\n }\n for (const m of maintenanceRecords) {\n const at = m.completedDate ?? m.scheduledDate;\n if (!at) continue;\n items.push({\n id: `mnt-${m.id}`,\n icon: Wrench,\n title: m.title,\n meta: 'Maintenance',\n at,\n tone:\n m.status === 'completed' ? 'success' : m.status === 'in_progress' ? 'info' : 'warning',\n statusLabel: m.status.replace(/_/g, ' '),\n });\n }\n return items\n .filter((i) => parseDate(i.at))\n .sort((a, b) => b.at.localeCompare(a.at))\n .slice(0, 7);\n }, [assets, incidents, workOrders, maintenanceRecords, tc]);\n\n const upcomingMaintenance = [...maintenanceRecords]\n .filter((m) => m.status !== 'completed' && m.status !== 'cancelled')\n .sort((a, b) => (a.scheduledDate ?? '').localeCompare(b.scheduledDate ?? ''))\n .slice(0, 8);\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl border border-border-seam bg-gloss-card p-5 sm:p-6\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n <GradientText>{tc('assethandler.dashboard.title', 'Dashboard')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tc(\n 'assethandler.dashboard.description',\n 'Overview of your IT asset portfolio and operations'\n )}\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: tc('assethandler.dashboard.actions.addAsset', 'Add {{asset}}'),\n onSelect: () => navigate('/assets/asset-create'),\n }}\n actions={[\n {\n label: tc('assethandler.dashboard.actions.viewAnalytics', 'View Analytics'),\n onSelect: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n </div>\n\n <PagePurpose>\n {tc(\n 'assethandler.dashboard.purpose',\n 'Your command center for the whole {{assetLower}} estate — see what needs attention (open incidents, due maintenance, critical alerts) and recent activity at a glance, then jump straight to the area that needs you. Start here every day to know where to act.'\n )}\n </PagePurpose>\n\n {/* KPI Metrics */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n {metrics.map((metric) => (\n <StatTile\n key={metric.label}\n label={metric.label}\n value={hydrated ? metric.value : '—'}\n format={formatCompact}\n icon={metric.icon}\n spark={metric.spark}\n hero={metric.hero}\n />\n ))}\n </div>\n\n {/* Assets-over-time + Recent activity rail */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title={tc('assethandler.dashboard.assetsChart.title', '{{assets}} over time')}\n subtitle={tc(\n 'assethandler.dashboard.assetsChart.subtitle',\n '{{assets}} added each month, from real registration dates'\n )}\n icon={Box}\n actions={\n <div className=\"inline-flex rounded-lg bg-bg-sunken p-0.5\">\n {(['status', 'category'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n onClick={() => setAssetsMode(mode)}\n className={cn(\n 'rounded-md px-3 py-1 text-xs font-medium transition-colors',\n assetsMode === mode\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {mode === 'status' ? 'By status' : 'By category'}\n </button>\n ))}\n </div>\n }\n >\n {assetsSeries.rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.assetsChart.empty.title', 'No history yet')}\n description={tc(\n 'assethandler.dashboard.assetsChart.empty.description',\n 'Register {{assetsLower}} and their monthly trend will appear here.'\n )}\n />\n ) : (\n <BarsChart\n data={assetsSeries.rows}\n xKey=\"month\"\n series={assetsSeries.series}\n stacked\n height={280}\n ariaLabel={tc(\n 'assethandler.dashboard.assetsChart.aria',\n '{{assets}} added per month'\n )}\n />\n )}\n </ChartCard>\n\n {/* Recent Activity rail */}\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" />\n {tc('assethandler.dashboard.activity.title', 'Recent activity')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigate('/analytics')}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline\"\n >\n {tc('assethandler.dashboard.activity.viewAll', 'View all')}\n <ArrowRight className=\"h-3 w-3\" />\n </button>\n </div>\n {feed.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.activity.empty',\n 'Activity across your estate will show up here.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {feed.map((item) => {\n const Icon = item.icon;\n return (\n <li\n key={item.id}\n className=\"flex items-start gap-3 rounded-lg px-2 py-2 hover:bg-bg-sunken/60\"\n >\n <span className=\"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {item.title}\n </p>\n <StatusPill tone={item.tone}>{item.statusLabel}</StatusPill>\n </div>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary capitalize\">\n <span className=\"truncate\">{item.meta}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"shrink-0 normal-case text-text-muted\">\n {relativeTime(item.at)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n\n {/* Transaction value trend (full width) */}\n <ChartCard\n title={tc('assethandler.dashboard.value.title', 'Transaction value')}\n subtitle={tc(\n 'assethandler.dashboard.value.subtitle',\n 'Cumulative transaction amount over time'\n )}\n icon={Receipt}\n >\n {valueTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.value.empty.title', 'No transactions yet')}\n description={tc(\n 'assethandler.dashboard.value.empty.description',\n '{{asset}} purchases, leases and disposals will chart here as they are recorded.'\n )}\n />\n ) : (\n <TrendChart\n data={valueTrend}\n xKey=\"month\"\n series={[{ key: 'value', label: 'Cumulative value' }]}\n valueFormat={formatCurrencyCompact(txCurrency)}\n height={260}\n ariaLabel=\"Cumulative transaction value over time\"\n />\n )}\n </ChartCard>\n\n {/* Portfolio donut + Operations summary */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title={tc('assethandler.dashboard.portfolio.title', 'Portfolio by category')}\n subtitle={tc(\n 'assethandler.dashboard.portfolio.description',\n '{{asset}} distribution by category'\n )}\n icon={Box}\n >\n {categoryDonut.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.portfolio.empty.title', 'No {{assetsLower}} yet')}\n description={tc(\n 'assethandler.dashboard.portfolio.empty.description',\n 'Register your first {{assetLower}} and category distribution will appear here.'\n )}\n />\n ) : (\n <DonutChart\n data={categoryDonut}\n centerLabel={tc('assethandler.dashboard.portfolio.centerLabel', '{{assets}}')}\n centerValue={assets.length}\n height={260}\n ariaLabel={tc('assethandler.dashboard.portfolio.aria', '{{assets}} by category')}\n />\n )}\n </ChartCard>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tc('assethandler.dashboard.operations.title', 'Operations summary')}\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {[\n {\n icon: Wrench,\n value: pendingMaintenance.length,\n label: tc(\n 'assethandler.dashboard.operations.pendingMaintenance',\n 'Pending Maintenance'\n ),\n },\n {\n icon: Activity,\n value: openWorkOrders.length,\n label: tc('assethandler.dashboard.operations.openWorkOrders', 'Open Work Orders'),\n },\n {\n icon: Clock,\n value: pendingChanges.length,\n label: tc('assethandler.dashboard.operations.pendingChanges', 'Pending Changes'),\n },\n {\n icon: CheckCircle,\n value: criticalIncidents.length,\n label: tc('assethandler.dashboard.operations.criticalAlerts', 'Critical Alerts'),\n },\n ].map((op) => {\n const Icon = op.icon;\n return (\n <div\n key={op.label}\n className=\"rounded-lg border border-border-seam bg-gloss-card p-4 text-center\"\n >\n <Icon className=\"mx-auto mb-2 h-5 w-5 text-text-secondary\" />\n <p className=\"text-2xl font-bold tabular-nums\">{op.value}</p>\n <p className=\"text-xs text-text-secondary\">{op.label}</p>\n </div>\n );\n })}\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Active incidents + Open work orders */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n {tc('assethandler.dashboard.incidents.title', 'Active Incidents')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {activeIncidents.length}{' '}\n {tc('assethandler.dashboard.incidents.unresolvedCount', 'unresolved incidents')}\n </p>\n </div>\n {activeIncidents.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.incidents.empty',\n 'No unresolved incidents — all clear.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {activeIncidents.slice(0, 5).map((inc) => (\n <button\n key={inc.id}\n type=\"button\"\n onClick={() => navigate('/itil/incident-management')}\n className=\"flex w-full items-start gap-3 rounded-lg border border-border-seam p-3 text-left hover:bg-bg-sunken/60\"\n >\n <StatusPill tone={priorityTone(inc.priority)}>{inc.priority}</StatusPill>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{inc.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {inc.assignedTo ||\n tc('assethandler.dashboard.incidents.unassigned', 'Unassigned')}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tc('assethandler.dashboard.workOrders.title', 'Open Work Orders')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tc('assethandler.dashboard.workOrders.description', 'Latest work in progress')}\n </p>\n </div>\n {openWorkOrders.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.workOrders.empty',\n 'No open work orders. Create one from the Operations hub.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {[...openWorkOrders]\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n .slice(0, 5)\n .map((item) => (\n <div\n key={item.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-seam p-3\"\n >\n <StatusPill tone={item.status === 'in_progress' ? 'info' : 'warning'}>\n {item.status.replace(/_/g, ' ')}\n </StatusPill>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm\">{item.title}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n {item.type} · {item.priority}\n </p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n\n {/* Upcoming Maintenance — table on md+, cards on mobile */}\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" />\n {tc('assethandler.dashboard.maintenance.title', 'Upcoming Maintenance')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.maintenance.description',\n 'Scheduled maintenance activities'\n )}\n </p>\n </div>\n {upcomingMaintenance.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-tasks\"\n title={tc(\n 'assethandler.dashboard.maintenance.empty.title',\n 'No maintenance scheduled'\n )}\n description={tc(\n 'assethandler.dashboard.maintenance.empty.description',\n 'Plan preventive maintenance from the scheduler and it will show up here.'\n )}\n />\n ) : (\n <>\n {/* Mobile: stacked cards */}\n <div className=\"space-y-2 md:hidden\">\n {upcomingMaintenance.map((rec) => (\n <div key={rec.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 flex-1 truncate text-sm font-medium\">{rec.title}</p>\n <StatusPill tone={maintenanceTone(rec.status)}>\n {rec.status.replace(/_/g, ' ')}\n </StatusPill>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"capitalize\">{rec.type}</span>\n <StatusPill tone={priorityTone(rec.priority)}>{rec.priority}</StatusPill>\n <span>\n {rec.scheduledDate ? new Date(rec.scheduledDate).toLocaleDateString() : '—'}\n </span>\n </div>\n </div>\n ))}\n </div>\n {/* md+: table */}\n <div className=\"hidden overflow-x-auto md:block\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-text-secondary\">\n <th className=\"py-2 text-left font-medium\">Title</th>\n <th className=\"py-2 text-left font-medium\">Type</th>\n <th className=\"py-2 text-left font-medium\">Priority</th>\n <th className=\"py-2 text-left font-medium\">Scheduled</th>\n <th className=\"py-2 text-left font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {upcomingMaintenance.map((rec) => (\n <tr\n key={rec.id}\n className=\"border-b border-border-seam hover:bg-bg-sunken/50\"\n >\n <td className=\"py-2.5 font-medium\">{rec.title}</td>\n <td className=\"py-2.5 capitalize\">{rec.type}</td>\n <td className=\"py-2.5\">\n <StatusPill tone={priorityTone(rec.priority)}>{rec.priority}</StatusPill>\n </td>\n <td className=\"py-2.5\">\n {rec.scheduledDate\n ? new Date(rec.scheduledDate).toLocaleDateString()\n : '—'}\n </td>\n <td className=\"py-2.5\">\n <StatusPill tone={maintenanceTone(rec.status)}>\n {rec.status.replace(/_/g, ' ')}\n </StatusPill>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </>\n )}\n </div>\n </GlassCard>\n\n <NextSteps\n storageKey=\"assethandler-dashboard\"\n steps={[\n {\n id: 'view-assets',\n label: tc(\n 'assethandler.dashboard.nextSteps.viewAssets.label',\n 'Browse your {{assetLower}} inventory'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.viewAssets.description',\n 'See every tracked {{assetLower}}, its status and owner.'\n ),\n onClick: () => navigate('/assets/asset-list'),\n },\n {\n id: 'add-asset',\n label: tc(\n 'assethandler.dashboard.nextSteps.addAsset.label',\n 'Register a new {{assetLower}}'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.addAsset.description',\n 'Add equipment, hardware or software to start tracking it.'\n ),\n onClick: () => navigate('/assets/asset-create'),\n },\n {\n id: 'incidents',\n label: tc('assethandler.dashboard.nextSteps.incidents.label', 'Resolve open incidents'),\n description: tc(\n 'assethandler.dashboard.nextSteps.incidents.description',\n 'Triage service-affecting issues before they breach SLA.'\n ),\n onClick: () => navigate('/itil/incident-management'),\n },\n {\n id: 'maintenance',\n label: tc(\n 'assethandler.dashboard.nextSteps.maintenance.label',\n 'Check the maintenance calendar'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.maintenance.description',\n 'Stay ahead of scheduled and overdue maintenance.'\n ),\n onClick: () => navigate('/maintenance/maintenance-calendar'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/assethandler/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo, useState, type ComponentType } from 'react';\nimport { useAssetMoney } from '../providers/AssetHandlerProvider';\nimport { useAssetHandlerNavigate } from '../hooks/useAssetHandlerNavigate';\nimport {\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n AlertTriangle,\n Box,\n Clock,\n ClipboardList,\n Receipt,\n Wrench,\n CheckCircle,\n ArrowRight,\n CalendarClock,\n} from 'lucide-react';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { useCopy } from '../hooks/useCopy';\nimport { cn } from '../utils/cn';\nimport {\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n TrendChart,\n formatCompact,\n formatCurrencyCompact,\n seriesColor,\n type ChartDatum,\n type ChartSeries,\n type DonutSlice,\n} from '../components';\n\n// ─── Time-series helpers (all derived from real store timestamps) ─────────────\nfunction monthKey(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;\n}\nfunction monthLabel(key: string): string {\n const [year, month] = key.split('-').map(Number);\n return new Date(year, month - 1, 1).toLocaleString(undefined, { month: 'short' });\n}\nfunction parseDate(value: string | undefined | null): Date | null {\n if (!value) return null;\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\nfunction titleCase(value: string): string {\n return value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/** Group items into ordered monthly buckets, counting by a category accessor. */\nfunction monthlyGrouped<T>(\n items: T[],\n getDate: (item: T) => string | undefined | null,\n getGroup: (item: T) => string,\n maxMonths = 8,\n maxGroups = 6\n): { rows: ChartDatum[]; series: ChartSeries[] } {\n const byMonth = new Map<string, Map<string, number>>();\n const groupTotals = new Map<string, number>();\n for (const item of items) {\n const d = parseDate(getDate(item));\n if (!d) continue;\n const key = monthKey(d);\n const group = getGroup(item) || 'other';\n if (!byMonth.has(key)) byMonth.set(key, new Map());\n const bucket = byMonth.get(key)!;\n bucket.set(group, (bucket.get(group) ?? 0) + 1);\n groupTotals.set(group, (groupTotals.get(group) ?? 0) + 1);\n }\n if (byMonth.size === 0) return { rows: [], series: [] };\n\n const orderedGroups = [...groupTotals.entries()].sort((a, b) => b[1] - a[1]).map(([g]) => g);\n const topGroups = orderedGroups.slice(0, maxGroups);\n const hasOther = orderedGroups.length > maxGroups;\n const keptGroups = new Set(topGroups);\n\n const keys = [...byMonth.keys()].sort().slice(-maxMonths);\n const rows: ChartDatum[] = keys.map((key) => {\n const row: ChartDatum = { month: monthLabel(key) };\n for (const g of topGroups) row[g] = 0;\n if (hasOther) row.other = 0;\n for (const [group, count] of byMonth.get(key)!) {\n if (keptGroups.has(group)) row[group] = ((row[group] as number) ?? 0) + count;\n else if (hasOther) row.other = ((row.other as number) ?? 0) + count;\n }\n return row;\n });\n\n const series: ChartSeries[] = topGroups.map((g, i) => ({\n key: g,\n label: titleCase(g),\n color: seriesColor(i),\n }));\n if (hasOther) {\n series.push({ key: 'other', label: 'Other', color: seriesColor(topGroups.length) });\n }\n return { rows, series };\n}\n\nfunction relativeTime(value: string | undefined | null): string {\n const d = parseDate(value);\n if (!d) return '';\n const diff = Date.now() - d.getTime();\n const mins = Math.round(diff / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.round(hours / 24);\n if (days < 30) return `${days}d ago`;\n const months = Math.round(days / 30);\n if (months < 12) return `${months}mo ago`;\n return `${Math.round(months / 12)}y ago`;\n}\n\ntype PillTone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\nconst PILL_CLASS: Record<PillTone, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n error: 'bg-status-error-bg-subtle text-status-error-text',\n info: 'bg-status-info-bg-subtle text-status-info-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nfunction StatusPill({ tone, children }: { tone: PillTone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium capitalize whitespace-nowrap',\n PILL_CLASS[tone]\n )}\n >\n {children}\n </span>\n );\n}\n\nconst priorityTone = (p: string): PillTone =>\n p === 'critical' ? 'error' : p === 'high' ? 'warning' : p === 'medium' ? 'info' : 'neutral';\nconst maintenanceTone = (status: string): PillTone =>\n status === 'completed' ? 'success' : status === 'in_progress' ? 'info' : 'warning';\n\ninterface FeedItem {\n id: string;\n icon: ComponentType<{ className?: string }>;\n title: string;\n meta: string;\n at: string;\n tone: PillTone;\n statusLabel: string;\n}\n\nexport function DashboardPage() {\n const { currency: moduleCurrency } = useAssetMoney();\n const tc = useCopy();\n const navigate = useAssetHandlerNavigate();\n const {\n hydrated,\n overview,\n assets,\n incidents,\n maintenanceRecords,\n changeRequests,\n workOrders,\n transactions,\n } = useAssetHandlerStore();\n\n const [assetsMode, setAssetsMode] = useState<'status' | 'category'>('status');\n\n const activeIncidents = incidents.filter((i) => i.status !== 'resolved' && i.status !== 'closed');\n const criticalIncidents = incidents.filter(\n (i) => i.priority === 'critical' && i.status !== 'resolved' && i.status !== 'closed'\n );\n const pendingMaintenance = maintenanceRecords.filter(\n (m) => m.status === 'scheduled' || m.status === 'in_progress'\n );\n const openWorkOrders = workOrders.filter(\n (w) => w.status !== 'completed' && w.status !== 'cancelled'\n );\n const pendingChanges = changeRequests.filter(\n (c) => c.status === 'approved' || c.status === 'submitted'\n );\n\n // Cumulative asset-count sparkline — the only KPI with real history.\n const assetSpark = useMemo(() => {\n const byMonth = new Map<string, number>();\n for (const a of assets) {\n const d = parseDate(a.createdAt);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + 1);\n }\n const keys = [...byMonth.keys()].sort();\n return keys.reduce<number[]>((acc, k) => {\n const prev = acc.length ? acc[acc.length - 1] : 0;\n acc.push(prev + (byMonth.get(k) ?? 0));\n return acc;\n }, []);\n }, [assets]);\n\n const metrics = [\n {\n label: tc('assethandler.dashboard.metrics.totalAssets', 'Total {{assets}}'),\n value: overview?.totalAssets ?? assets.length,\n icon: Box,\n spark: assetSpark.length > 1 ? assetSpark : undefined,\n hero: true,\n },\n {\n label: tc('assethandler.dashboard.metrics.openIncidents', 'Open Incidents'),\n value: overview?.openIncidents ?? activeIncidents.length,\n icon: AlertTriangle,\n },\n {\n label: tc('assethandler.dashboard.metrics.openWorkOrders', 'Open Work Orders'),\n value: overview?.openWorkOrders ?? openWorkOrders.length,\n icon: ClipboardList,\n },\n {\n label: tc('assethandler.dashboard.metrics.transactions', 'Transactions'),\n value: overview?.totalTransactions ?? transactions.length,\n icon: Receipt,\n },\n ];\n\n // Portfolio-by-category donut (real asset counts).\n const categoryDonut = useMemo<DonutSlice[]>(() => {\n const counts = assets.reduce<Record<string, number>>((acc, a) => {\n const key = a.category || 'uncategorized';\n acc[key] = (acc[key] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts)\n .sort((a, b) => b[1] - a[1])\n .map(([name, value]) => ({ name: titleCase(name), value }));\n }, [assets]);\n\n // Assets-added-over-time (stacked by status or category via the toggle).\n const assetsSeries = useMemo(\n () =>\n monthlyGrouped(\n assets,\n (a) => a.createdAt,\n (a) => (assetsMode === 'status' ? a.status : a.category),\n 8,\n 6\n ),\n [assets, assetsMode]\n );\n\n // Cumulative transaction value over time (real transaction amounts).\n const valueTrend = useMemo<ChartDatum[]>(() => {\n const byMonth = new Map<string, number>();\n for (const tx of transactions) {\n const d = parseDate(tx.date);\n if (!d) continue;\n const key = monthKey(d);\n byMonth.set(key, (byMonth.get(key) ?? 0) + tx.amount);\n }\n const keys = [...byMonth.keys()].sort();\n const raw = keys.reduce<number[]>((acc, k) => {\n const prev = acc.length ? acc[acc.length - 1] : 0;\n acc.push(prev + (byMonth.get(k) ?? 0));\n return acc;\n }, []);\n return keys.map((k, i) => ({ month: monthLabel(k), value: Math.round(raw[i]) }));\n }, [transactions]);\n const txCurrency = transactions[0]?.currency ?? moduleCurrency;\n\n // Unified recent-activity feed (merged real records, newest first).\n const feed = useMemo<FeedItem[]>(() => {\n const items: FeedItem[] = [];\n for (const a of assets) {\n items.push({\n id: `asset-${a.id}`,\n icon: Box,\n title: a.name,\n meta: tc('assethandler.dashboard.activity.assetRegistered', '{{asset}} registered'),\n at: a.createdAt,\n tone:\n a.status === 'active' ? 'success' : a.status === 'maintenance' ? 'warning' : 'neutral',\n statusLabel: a.status.replace(/_/g, ' '),\n });\n }\n for (const inc of incidents) {\n items.push({\n id: `inc-${inc.id}`,\n icon: AlertTriangle,\n title: inc.title,\n meta: `Incident · ${inc.priority}`,\n at: inc.createdAt,\n tone:\n inc.status === 'resolved' || inc.status === 'closed'\n ? 'success'\n : inc.priority === 'critical' || inc.priority === 'high'\n ? 'error'\n : 'warning',\n statusLabel: inc.status.replace(/_/g, ' '),\n });\n }\n for (const wo of workOrders) {\n items.push({\n id: `wo-${wo.id}`,\n icon: ClipboardList,\n title: wo.title,\n meta: `Work order · ${wo.type}`,\n at: wo.createdAt,\n tone:\n wo.status === 'completed'\n ? 'success'\n : wo.status === 'in_progress'\n ? 'info'\n : wo.status === 'cancelled'\n ? 'neutral'\n : 'warning',\n statusLabel: wo.status.replace(/_/g, ' '),\n });\n }\n for (const m of maintenanceRecords) {\n const at = m.completedDate ?? m.scheduledDate;\n if (!at) continue;\n items.push({\n id: `mnt-${m.id}`,\n icon: Wrench,\n title: m.title,\n meta: 'Maintenance',\n at,\n tone:\n m.status === 'completed' ? 'success' : m.status === 'in_progress' ? 'info' : 'warning',\n statusLabel: m.status.replace(/_/g, ' '),\n });\n }\n return items\n .filter((i) => parseDate(i.at))\n .sort((a, b) => b.at.localeCompare(a.at))\n .slice(0, 7);\n }, [assets, incidents, workOrders, maintenanceRecords, tc]);\n\n const upcomingMaintenance = [...maintenanceRecords]\n .filter((m) => m.status !== 'completed' && m.status !== 'cancelled')\n .sort((a, b) => (a.scheduledDate ?? '').localeCompare(b.scheduledDate ?? ''))\n .slice(0, 8);\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-xl border border-border-seam bg-gloss-card p-5 sm:p-6\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"relative flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n <GradientText>{tc('assethandler.dashboard.title', 'Dashboard')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tc(\n 'assethandler.dashboard.description',\n 'Overview of your IT asset portfolio and operations'\n )}\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: tc('assethandler.dashboard.actions.addAsset', 'Add {{asset}}'),\n onSelect: () => navigate('/assets/asset-create'),\n }}\n actions={[\n {\n label: tc('assethandler.dashboard.actions.viewAnalytics', 'View Analytics'),\n onSelect: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n </div>\n\n <PagePurpose>\n {tc(\n 'assethandler.dashboard.purpose',\n 'Your command center for the whole {{assetLower}} estate — see what needs attention (open incidents, due maintenance, critical alerts) and recent activity at a glance, then jump straight to the area that needs you. Start here every day to know where to act.'\n )}\n </PagePurpose>\n\n {/* KPI Metrics */}\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n {metrics.map((metric) => (\n <StatTile\n key={metric.label}\n label={metric.label}\n value={hydrated ? metric.value : '—'}\n format={formatCompact}\n icon={metric.icon}\n spark={metric.spark}\n hero={metric.hero}\n />\n ))}\n </div>\n\n {/* Assets-over-time + Recent activity rail */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title={tc('assethandler.dashboard.assetsChart.title', '{{assets}} over time')}\n subtitle={tc(\n 'assethandler.dashboard.assetsChart.subtitle',\n '{{assets}} added each month, from real registration dates'\n )}\n icon={Box}\n actions={\n <div className=\"inline-flex rounded-lg bg-bg-sunken p-0.5\">\n {(['status', 'category'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n onClick={() => setAssetsMode(mode)}\n className={cn(\n 'rounded-md px-3 py-1 text-xs font-medium transition-colors',\n assetsMode === mode\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n )}\n >\n {mode === 'status' ? 'By status' : 'By category'}\n </button>\n ))}\n </div>\n }\n >\n {assetsSeries.rows.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.assetsChart.empty.title', 'No history yet')}\n description={tc(\n 'assethandler.dashboard.assetsChart.empty.description',\n 'Register {{assetsLower}} and their monthly trend will appear here.'\n )}\n />\n ) : (\n <BarsChart\n data={assetsSeries.rows}\n xKey=\"month\"\n series={assetsSeries.series}\n stacked\n height={280}\n ariaLabel={tc(\n 'assethandler.dashboard.assetsChart.aria',\n '{{assets}} added per month'\n )}\n />\n )}\n </ChartCard>\n\n {/* Recent Activity rail */}\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" />\n {tc('assethandler.dashboard.activity.title', 'Recent activity')}\n </h3>\n <button\n type=\"button\"\n onClick={() => navigate('/analytics')}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline\"\n >\n {tc('assethandler.dashboard.activity.viewAll', 'View all')}\n <ArrowRight className=\"h-3 w-3\" />\n </button>\n </div>\n {feed.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.activity.empty',\n 'Activity across your estate will show up here.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {feed.map((item) => {\n const Icon = item.icon;\n return (\n <li\n key={item.id}\n className=\"flex items-start gap-3 rounded-lg px-2 py-2 hover:bg-bg-sunken/60\"\n >\n <span className=\"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {item.title}\n </p>\n <StatusPill tone={item.tone}>{item.statusLabel}</StatusPill>\n </div>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-secondary capitalize\">\n <span className=\"truncate\">{item.meta}</span>\n <span aria-hidden=\"true\">·</span>\n <span className=\"shrink-0 normal-case text-text-muted\">\n {relativeTime(item.at)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n\n {/* Transaction value trend (full width) */}\n <ChartCard\n title={tc('assethandler.dashboard.value.title', 'Transaction value')}\n subtitle={tc(\n 'assethandler.dashboard.value.subtitle',\n 'Cumulative transaction amount over time'\n )}\n icon={Receipt}\n >\n {valueTrend.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.value.empty.title', 'No transactions yet')}\n description={tc(\n 'assethandler.dashboard.value.empty.description',\n '{{asset}} purchases, leases and disposals will chart here as they are recorded.'\n )}\n />\n ) : (\n <TrendChart\n data={valueTrend}\n xKey=\"month\"\n series={[{ key: 'value', label: 'Cumulative value' }]}\n valueFormat={formatCurrencyCompact(txCurrency)}\n height={260}\n ariaLabel=\"Cumulative transaction value over time\"\n />\n )}\n </ChartCard>\n\n {/* Portfolio donut + Operations summary */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title={tc('assethandler.dashboard.portfolio.title', 'Portfolio by category')}\n subtitle={tc(\n 'assethandler.dashboard.portfolio.description',\n '{{asset}} distribution by category'\n )}\n icon={Box}\n >\n {categoryDonut.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tc('assethandler.dashboard.portfolio.empty.title', 'No {{assetsLower}} yet')}\n description={tc(\n 'assethandler.dashboard.portfolio.empty.description',\n 'Register your first {{assetLower}} and category distribution will appear here.'\n )}\n />\n ) : (\n <DonutChart\n data={categoryDonut}\n centerLabel={tc('assethandler.dashboard.portfolio.centerLabel', '{{assets}}')}\n centerValue={assets.length}\n height={260}\n ariaLabel={tc('assethandler.dashboard.portfolio.aria', '{{assets}} by category')}\n />\n )}\n </ChartCard>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tc('assethandler.dashboard.operations.title', 'Operations summary')}\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {[\n {\n icon: Wrench,\n value: pendingMaintenance.length,\n label: tc(\n 'assethandler.dashboard.operations.pendingMaintenance',\n 'Pending Maintenance'\n ),\n },\n {\n icon: Activity,\n value: openWorkOrders.length,\n label: tc('assethandler.dashboard.operations.openWorkOrders', 'Open Work Orders'),\n },\n {\n icon: Clock,\n value: pendingChanges.length,\n label: tc('assethandler.dashboard.operations.pendingChanges', 'Pending Changes'),\n },\n {\n icon: CheckCircle,\n value: criticalIncidents.length,\n label: tc('assethandler.dashboard.operations.criticalAlerts', 'Critical Alerts'),\n },\n ].map((op) => {\n const Icon = op.icon;\n return (\n <div\n key={op.label}\n className=\"rounded-lg border border-border-seam bg-gloss-card p-4 text-center\"\n >\n <Icon className=\"mx-auto mb-2 h-5 w-5 text-text-secondary\" />\n <p className=\"text-2xl font-bold tabular-nums\">{op.value}</p>\n <p className=\"text-xs text-text-secondary\">{op.label}</p>\n </div>\n );\n })}\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Active incidents + Open work orders */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n {tc('assethandler.dashboard.incidents.title', 'Active Incidents')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {activeIncidents.length}{' '}\n {tc('assethandler.dashboard.incidents.unresolvedCount', 'unresolved incidents')}\n </p>\n </div>\n {activeIncidents.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.incidents.empty',\n 'No unresolved incidents — all clear.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {activeIncidents.slice(0, 5).map((inc) => (\n <button\n key={inc.id}\n type=\"button\"\n onClick={() => navigate('/itil/incident-management')}\n className=\"flex w-full items-start gap-3 rounded-lg border border-border-seam p-3 text-left hover:bg-bg-sunken/60\"\n >\n <StatusPill tone={priorityTone(inc.priority)}>{inc.priority}</StatusPill>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{inc.title}</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {inc.assignedTo ||\n tc('assethandler.dashboard.incidents.unassigned', 'Unassigned')}\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {tc('assethandler.dashboard.workOrders.title', 'Open Work Orders')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tc('assethandler.dashboard.workOrders.description', 'Latest work in progress')}\n </p>\n </div>\n {openWorkOrders.length === 0 ? (\n <p className=\"py-4 text-center text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.workOrders.empty',\n 'No open work orders. Create one from the Operations hub.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {[...openWorkOrders]\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n .slice(0, 5)\n .map((item) => (\n <div\n key={item.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-seam p-3\"\n >\n <StatusPill tone={item.status === 'in_progress' ? 'info' : 'warning'}>\n {item.status.replace(/_/g, ' ')}\n </StatusPill>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm\">{item.title}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n {item.type} · {item.priority}\n </p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n\n {/* Upcoming Maintenance — table on md+, cards on mobile */}\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"flex flex-col gap-4 p-5\">\n <div>\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" />\n {tc('assethandler.dashboard.maintenance.title', 'Upcoming Maintenance')}\n </h3>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tc(\n 'assethandler.dashboard.maintenance.description',\n 'Scheduled maintenance activities'\n )}\n </p>\n </div>\n {upcomingMaintenance.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-tasks\"\n title={tc(\n 'assethandler.dashboard.maintenance.empty.title',\n 'No maintenance scheduled'\n )}\n description={tc(\n 'assethandler.dashboard.maintenance.empty.description',\n 'Plan preventive maintenance from the scheduler and it will show up here.'\n )}\n />\n ) : (\n <>\n {/* Mobile: stacked cards */}\n <div className=\"space-y-2 md:hidden\">\n {upcomingMaintenance.map((rec) => (\n <div key={rec.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 flex-1 truncate text-sm font-medium\">{rec.title}</p>\n <StatusPill tone={maintenanceTone(rec.status)}>\n {rec.status.replace(/_/g, ' ')}\n </StatusPill>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <span className=\"capitalize\">{rec.type}</span>\n <StatusPill tone={priorityTone(rec.priority)}>{rec.priority}</StatusPill>\n <span>\n {rec.scheduledDate ? new Date(rec.scheduledDate).toLocaleDateString() : '—'}\n </span>\n </div>\n </div>\n ))}\n </div>\n {/* md+: table */}\n <div className=\"hidden overflow-x-auto md:block\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-text-secondary\">\n <th className=\"py-2 text-left font-medium\">Title</th>\n <th className=\"py-2 text-left font-medium\">Type</th>\n <th className=\"py-2 text-left font-medium\">Priority</th>\n <th className=\"py-2 text-left font-medium\">Scheduled</th>\n <th className=\"py-2 text-left font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {upcomingMaintenance.map((rec) => (\n <tr\n key={rec.id}\n className=\"border-b border-border-seam hover:bg-bg-sunken/50\"\n >\n <td className=\"py-2.5 font-medium\">{rec.title}</td>\n <td className=\"py-2.5 capitalize\">{rec.type}</td>\n <td className=\"py-2.5\">\n <StatusPill tone={priorityTone(rec.priority)}>{rec.priority}</StatusPill>\n </td>\n <td className=\"py-2.5\">\n {rec.scheduledDate\n ? new Date(rec.scheduledDate).toLocaleDateString()\n : '—'}\n </td>\n <td className=\"py-2.5\">\n <StatusPill tone={maintenanceTone(rec.status)}>\n {rec.status.replace(/_/g, ' ')}\n </StatusPill>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </>\n )}\n </div>\n </GlassCard>\n\n <NextSteps\n storageKey=\"assethandler-dashboard\"\n steps={[\n {\n id: 'view-assets',\n label: tc(\n 'assethandler.dashboard.nextSteps.viewAssets.label',\n 'Browse your {{assetLower}} inventory'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.viewAssets.description',\n 'See every tracked {{assetLower}}, its status and owner.'\n ),\n onClick: () => navigate('/assets/asset-list'),\n },\n {\n id: 'add-asset',\n label: tc(\n 'assethandler.dashboard.nextSteps.addAsset.label',\n 'Register a new {{assetLower}}'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.addAsset.description',\n 'Add equipment, hardware or software to start tracking it.'\n ),\n onClick: () => navigate('/assets/asset-create'),\n },\n {\n id: 'incidents',\n label: tc('assethandler.dashboard.nextSteps.incidents.label', 'Resolve open incidents'),\n description: tc(\n 'assethandler.dashboard.nextSteps.incidents.description',\n 'Triage service-affecting issues before they breach SLA.'\n ),\n onClick: () => navigate('/itil/incident-management'),\n },\n {\n id: 'maintenance',\n label: tc(\n 'assethandler.dashboard.nextSteps.maintenance.label',\n 'Check the maintenance calendar'\n ),\n description: tc(\n 'assethandler.dashboard.nextSteps.maintenance.description',\n 'Stay ahead of scheduled and overdue maintenance.'\n ),\n onClick: () => navigate('/maintenance/maintenance-calendar'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DigitalTwinPage.js","names":[],"sources":["../../../src/assethandler/pages/DigitalTwinPage.tsx"],"sourcesContent":["import { Box, Plus, Boxes, Gauge, Clock, Layers3, LinkIcon, ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport {\n Button,\n GradientText,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\nimport { Pagination, usePagination, StatTile, ChartCard, DonutChart } from '../components';\nimport { notify } from '../utils/toast';\nimport { cn } from '../utils/cn';\n\nconst DIGITAL_TWINS_QUERY = gql`\n query AssetHandlerDigitalTwins {\n digitalTwins {\n id\n assetId\n modelRef\n fidelityLevel\n geometryRef\n properties\n createdAt\n updatedAt\n }\n }\n`;\n\nconst CREATE_DIGITAL_TWIN_MUTATION = gql`\n mutation AssetHandlerCreateDigitalTwin($input: CreateDigitalTwinInput!) {\n createDigitalTwin(input: $input) {\n id\n modelRef\n }\n }\n`;\n\ninterface DigitalTwin {\n id: string;\n assetId: string;\n modelRef: string;\n fidelityLevel: string;\n geometryRef: string | null;\n properties: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CreateDigitalTwinResult {\n createDigitalTwin: { id: string; modelRef: string };\n}\n\nconst FIDELITY_LEVELS = ['LOW', 'MEDIUM', 'HIGH', 'ULTRA'] as const;\n\ntype Tone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\n\nconst TONE_PILL: Record<Tone, string> = {\n success: 'bg-status-success-subtle text-status-success-text',\n warning: 'bg-status-warning-subtle text-status-warning-text',\n error: 'bg-status-error-subtle text-status-error-text',\n info: 'bg-status-info-subtle text-status-info-text',\n neutral: 'bg-status-neutral-bg-subtle text-status-neutral-text',\n};\n\nfunction Pill({ tone, children }: { tone: Tone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium capitalize',\n TONE_PILL[tone]\n )}\n >\n {children}\n </span>\n );\n}\n\nconst fidelityTone = (level: string): Tone => {\n switch (level.toUpperCase()) {\n case 'ULTRA':\n return 'success';\n case 'HIGH':\n return 'warning';\n case 'MEDIUM':\n return 'info';\n default:\n return 'neutral';\n }\n};\n\n// Asset status → operational-style pill.\nconst assetStatusPill = (status: string | undefined): { tone: Tone; label: string } => {\n switch (status) {\n case 'active':\n return { tone: 'success', label: 'Operational' };\n case 'maintenance':\n return { tone: 'warning', label: 'Maintenance' };\n case 'inactive':\n return { tone: 'neutral', label: 'Inactive' };\n case 'retired':\n return { tone: 'neutral', label: 'Retired' };\n case 'disposed':\n return { tone: 'error', label: 'Disposed' };\n default:\n return { tone: 'neutral', label: 'Unknown' };\n }\n};\n\nconst humanize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();\n\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (!Number.isFinite(then)) return '—';\n const diff = Date.now() - then;\n const mins = Math.round(diff / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.round(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.round(hrs / 24);\n if (days < 30) return `${days}d ago`;\n return new Date(iso).toLocaleDateString();\n}\n\nconst isNumeric = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\nfunction NewDigitalTwinDialog({\n open,\n onOpenChange,\n onCreated,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated: () => void;\n}) {\n const assets = useAssetHandlerStore((s) => s.assets);\n const [assetId, setAssetId] = useState('');\n const [modelRef, setModelRef] = useState('');\n const [fidelityLevel, setFidelityLevel] = useState<(typeof FIDELITY_LEVELS)[number]>('MEDIUM');\n const [geometryRef, setGeometryRef] = useState('');\n\n const reset = () => {\n setAssetId('');\n setModelRef('');\n setFidelityLevel('MEDIUM');\n setGeometryRef('');\n };\n\n const [createTwin, { loading }] = useMutation<CreateDigitalTwinResult>(\n CREATE_DIGITAL_TWIN_MUTATION,\n {\n onCompleted: (data) => {\n notify.success('Digital twin created', `${data.createDigitalTwin.modelRef} is now linked.`);\n reset();\n onOpenChange(false);\n onCreated();\n },\n onError: (error) => notify.error('Could not create digital twin', error.message),\n }\n );\n\n const handleSubmit = () => {\n if (!assetId) {\n notify.warning('Asset required', 'Select the asset this twin represents.');\n return;\n }\n if (!modelRef.trim()) {\n notify.warning('Model reference required', 'Enter the model reference for this twin.');\n return;\n }\n void createTwin({\n variables: {\n input: {\n assetId,\n modelRef: modelRef.trim(),\n fidelityLevel,\n geometryRef: geometryRef.trim() || null,\n },\n },\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New Digital Twin</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Create a virtual replica linked to a physical asset.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label>Asset *</Label>\n <Select value={assetId} onValueChange={setAssetId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assets.map((asset) => (\n <SelectItem key={asset.id} value={asset.id}>\n {asset.assetTag} — {asset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Model Reference *</Label>\n <Input\n placeholder=\"e.g., twin-pump-03\"\n value={modelRef}\n onChange={(e) => setModelRef(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Fidelity Level</Label>\n <Select\n value={fidelityLevel}\n onValueChange={(v) => setFidelityLevel(v as typeof fidelityLevel)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {FIDELITY_LEVELS.map((f) => (\n <SelectItem key={f} value={f}>\n {f}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label>Geometry Reference</Label>\n <Input\n placeholder=\"e.g., s3://twins/pump-03.glb\"\n value={geometryRef}\n onChange={(e) => setGeometryRef(e.target.value)}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={loading}>\n {loading ? 'Creating…' : 'Create twin'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── Twin detail dialog: geometry preview + properties + live parameters ─────\nfunction TwinDetailDialog({\n twin,\n asset,\n onClose,\n}: {\n twin: DigitalTwin | null;\n asset: Asset | undefined;\n onClose: () => void;\n}) {\n const propertyEntries = useMemo(\n () => (twin ? Object.entries(twin.properties ?? {}) : []),\n [twin]\n );\n const numericParams = useMemo(\n () => propertyEntries.filter(([, v]) => isNumeric(v)) as [string, number][],\n [propertyEntries]\n );\n\n if (!twin) return null;\n const status = assetStatusPill(asset?.status);\n\n return (\n <ResponsiveDialog open={twin !== null} onOpenChange={(o) => (!o ? onClose() : undefined)}>\n <ResponsiveDialogContent className=\"max-h-[88vh] overflow-y-auto sm:max-w-3xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Digital Twin — {twin.modelRef}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {asset ? `${asset.assetTag} — ${asset.name}` : `Asset ${twin.assetId}`}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n {/* Geometry preview */}\n <div className=\"flex min-h-[220px] flex-col items-center justify-center gap-3 rounded-xl border border-border-seam bg-bg-sunken/40 p-6 text-center\">\n <span className=\"flex h-16 w-16 items-center justify-center rounded-2xl bg-accent-primary/10 text-accent-primary\">\n <Box className=\"h-8 w-8\" />\n </span>\n <div>\n <p className=\"text-sm font-medium\">Geometry model</p>\n {twin.geometryRef ? (\n <p className=\"mt-1 max-w-[240px] truncate font-mono text-xs text-text-secondary\">\n {twin.geometryRef}\n </p>\n ) : (\n <p className=\"mt-1 text-xs text-text-muted\">No geometry linked yet</p>\n )}\n </div>\n <Pill tone={fidelityTone(twin.fidelityLevel)}>\n {humanize(twin.fidelityLevel)} fidelity\n </Pill>\n </div>\n\n {/* Properties + status */}\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-base font-semibold\">Properties</h3>\n <Pill tone={status.tone}>{status.label}</Pill>\n </div>\n {propertyEntries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-4 text-sm text-text-secondary\">\n No properties recorded for this twin yet.\n </p>\n ) : (\n <dl className=\"divide-y divide-border-seam rounded-lg border border-border-seam\">\n {propertyEntries.map(([key, value]) => (\n <div key={key} className=\"flex items-start justify-between gap-3 p-3 text-sm\">\n <dt className=\"text-text-secondary\">{humanize(key)}</dt>\n <dd\n className=\"max-w-[60%] truncate text-right font-medium\"\n title={String(value)}\n >\n {typeof value === 'object' ? JSON.stringify(value) : String(value)}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </div>\n </div>\n\n {/* Live parameters — only from real numeric properties, never fabricated */}\n <div className=\"space-y-2\">\n <h3 className=\"flex items-center gap-2 text-base font-semibold\">\n <Gauge className=\"h-4 w-4\" /> Live parameters\n </h3>\n {numericParams.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border-seam p-4 text-sm text-text-secondary\">\n No live telemetry connected. Numeric values recorded on this twin's properties\n will appear here as live parameters.\n </div>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {numericParams.map(([key, value]) => (\n <StatTile key={key} label={humanize(key)} value={value} />\n ))}\n </div>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={onClose}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function DigitalTwinPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [selectedTwin, setSelectedTwin] = useState<DigitalTwin | null>(null);\n const assets = useAssetHandlerStore((s) => s.assets);\n\n const { data, loading, refetch } = useQuery<{ digitalTwins: DigitalTwin[] }>(\n DIGITAL_TWINS_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n\n const twins = useMemo(() => data?.digitalTwins ?? [], [data]);\n\n const assetById = useMemo(() => {\n const map = new Map<string, Asset>();\n for (const a of assets) map.set(a.id, a);\n return map;\n }, [assets]);\n\n const fidelityDonut = useMemo(() => {\n const counts = new Map<string, number>();\n for (const t of twins)\n counts.set(\n t.fidelityLevel.toUpperCase(),\n (counts.get(t.fidelityLevel.toUpperCase()) ?? 0) + 1\n );\n return FIDELITY_LEVELS.filter((f) => (counts.get(f) ?? 0) > 0).map((f) => ({\n name: humanize(f),\n value: counts.get(f) ?? 0,\n }));\n }, [twins]);\n\n const mostRecent = useMemo(() => {\n if (twins.length === 0) return null;\n return [...twins].sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n )[0];\n }, [twins]);\n\n const highFidelityCount = useMemo(\n () => twins.filter((t) => ['HIGH', 'ULTRA'].includes(t.fidelityLevel.toUpperCase())).length,\n [twins]\n );\n\n const pagination = usePagination(twins, { pageSize: 8 });\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wider text-text-muted\">\n Asset Twins / Digital Twin\n </p>\n <h1 className=\"flex items-center gap-2 text-2xl font-semibold\">\n <Box className=\"h-6 w-6\" /> <GradientText>Digital Twin</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Virtual replicas of your critical assets — geometry, properties and live parameters.\n </p>\n </div>\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Digital Twin\n </Button>\n </div>\n <PagePurpose>\n A live virtual replica of your most critical assets. Use digital twins to simulate what-if\n scenarios, validate maintenance changes, and visualise condition without touching the\n physical equipment — so you can de-risk decisions on high-value or hard-to-access assets.\n </PagePurpose>\n\n <NewDigitalTwinDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onCreated={() => void refetch()}\n />\n <TwinDetailDialog\n twin={selectedTwin}\n asset={selectedTwin ? assetById.get(selectedTwin.assetId) : undefined}\n onClose={() => setSelectedTwin(null)}\n />\n\n {/* ─── Summary ─── */}\n {twins.length > 0 ? (\n <>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-3\">\n <StatTile label=\"Total Twins\" value={twins.length} icon={Boxes} hero />\n <StatTile label=\"High / Ultra Fidelity\" value={highFidelityCount} icon={Layers3} />\n <StatTile\n label=\"Most Recent\"\n value={mostRecent ? relativeTime(mostRecent.updatedAt) : '—'}\n icon={Clock}\n />\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <ChartCard\n title=\"Twins by fidelity\"\n subtitle=\"Model resolution mix\"\n icon={Layers3}\n className=\"lg:col-span-1\"\n >\n <DonutChart\n data={fidelityDonut}\n height={220}\n centerLabel=\"twins\"\n centerValue={twins.length}\n ariaLabel=\"Digital twins by fidelity level\"\n />\n </ChartCard>\n\n <div className=\"grid gap-3 sm:grid-cols-2 lg:col-span-2\">\n {pagination.pageItems.map((twin, i) => {\n const asset = assetById.get(twin.assetId);\n const status = assetStatusPill(asset?.status);\n return (\n <GlassCard\n key={twin.id}\n treatment={i === 0 && pagination.page === 1 ? 'glass' : 'metric'}\n glow={i === 0 && pagination.page === 1}\n className=\"border-border-seam\"\n >\n <button\n type=\"button\"\n onClick={() => setSelectedTwin(twin)}\n className=\"flex h-full w-full flex-col gap-3 p-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-base font-semibold\" title={twin.modelRef}>\n {twin.modelRef}\n </h2>\n <p className=\"truncate text-xs text-text-secondary\">\n {asset ? `${asset.assetTag} — ${asset.name}` : `Asset ${twin.assetId}`}\n </p>\n </div>\n <Pill tone={status.tone}>{status.label}</Pill>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Pill tone={fidelityTone(twin.fidelityLevel)}>\n {humanize(twin.fidelityLevel)} fidelity\n </Pill>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n twin.geometryRef\n ? 'bg-status-info-subtle text-status-info-text'\n : 'bg-status-neutral-bg-subtle text-status-neutral-text'\n )}\n >\n <LinkIcon className=\"h-3 w-3\" />\n {twin.geometryRef ? 'Geometry' : 'No geometry'}\n </span>\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam pt-2 text-xs text-text-secondary\">\n <span>Updated {relativeTime(twin.updatedAt)}</span>\n <span className=\"inline-flex items-center gap-1 text-accent-primary\">\n View <ExternalLink className=\"h-3 w-3\" />\n </span>\n </div>\n </button>\n </GlassCard>\n );\n })}\n </div>\n </div>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"twins\"\n />\n </>\n ) : null}\n\n {loading && twins.length === 0 ? (\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-32 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n\n {!loading && twins.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No digital twins found\"\n description=\"Create a digital twin to model one of your assets.\"\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Digital Twin\n </Button>\n }\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DigitalTwinPage.js","names":[],"sources":["../../../src/assethandler/pages/DigitalTwinPage.tsx"],"sourcesContent":["import { Box, Plus, Boxes, Gauge, Clock, Layers3, LinkIcon, ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport {\n Button,\n GradientText,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { useAssetHandlerStore, type Asset } from '../store/assethandlerStore';\nimport { Pagination, usePagination, StatTile, ChartCard, DonutChart } from '../components';\nimport { notify } from '../utils/toast';\nimport { cn } from '../utils/cn';\n\nconst DIGITAL_TWINS_QUERY = gql`\n query AssetHandlerDigitalTwins {\n digitalTwins {\n id\n assetId\n modelRef\n fidelityLevel\n geometryRef\n properties\n createdAt\n updatedAt\n }\n }\n`;\n\nconst CREATE_DIGITAL_TWIN_MUTATION = gql`\n mutation AssetHandlerCreateDigitalTwin($input: CreateDigitalTwinInput!) {\n createDigitalTwin(input: $input) {\n id\n modelRef\n }\n }\n`;\n\ninterface DigitalTwin {\n id: string;\n assetId: string;\n modelRef: string;\n fidelityLevel: string;\n geometryRef: string | null;\n properties: Record<string, unknown>;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CreateDigitalTwinResult {\n createDigitalTwin: { id: string; modelRef: string };\n}\n\nconst FIDELITY_LEVELS = ['LOW', 'MEDIUM', 'HIGH', 'ULTRA'] as const;\n\ntype Tone = 'success' | 'warning' | 'error' | 'info' | 'neutral';\n\nconst TONE_PILL: Record<Tone, string> = {\n success: 'bg-status-success-subtle text-status-success-text',\n warning: 'bg-status-warning-subtle text-status-warning-text',\n error: 'bg-status-error-subtle text-status-error-text',\n info: 'bg-status-info-subtle text-status-info-text',\n neutral: 'bg-status-neutral-bg-subtle text-status-neutral-text',\n};\n\nfunction Pill({ tone, children }: { tone: Tone; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium capitalize',\n TONE_PILL[tone]\n )}\n >\n {children}\n </span>\n );\n}\n\nconst fidelityTone = (level: string): Tone => {\n switch (level.toUpperCase()) {\n case 'ULTRA':\n return 'success';\n case 'HIGH':\n return 'warning';\n case 'MEDIUM':\n return 'info';\n default:\n return 'neutral';\n }\n};\n\n// Asset status → operational-style pill.\nconst assetStatusPill = (status: string | undefined): { tone: Tone; label: string } => {\n switch (status) {\n case 'active':\n return { tone: 'success', label: 'Operational' };\n case 'maintenance':\n return { tone: 'warning', label: 'Maintenance' };\n case 'inactive':\n return { tone: 'neutral', label: 'Inactive' };\n case 'retired':\n return { tone: 'neutral', label: 'Retired' };\n case 'disposed':\n return { tone: 'error', label: 'Disposed' };\n default:\n return { tone: 'neutral', label: 'Unknown' };\n }\n};\n\nconst humanize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();\n\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (!Number.isFinite(then)) return '—';\n const diff = Date.now() - then;\n const mins = Math.round(diff / 60000);\n if (mins < 1) return 'just now';\n if (mins < 60) return `${mins}m ago`;\n const hrs = Math.round(mins / 60);\n if (hrs < 24) return `${hrs}h ago`;\n const days = Math.round(hrs / 24);\n if (days < 30) return `${days}d ago`;\n return new Date(iso).toLocaleDateString();\n}\n\nconst isNumeric = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\nfunction NewDigitalTwinDialog({\n open,\n onOpenChange,\n onCreated,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onCreated: () => void;\n}) {\n const assets = useAssetHandlerStore((s) => s.assets);\n const [assetId, setAssetId] = useState('');\n const [modelRef, setModelRef] = useState('');\n const [fidelityLevel, setFidelityLevel] = useState<(typeof FIDELITY_LEVELS)[number]>('MEDIUM');\n const [geometryRef, setGeometryRef] = useState('');\n\n const reset = () => {\n setAssetId('');\n setModelRef('');\n setFidelityLevel('MEDIUM');\n setGeometryRef('');\n };\n\n const [createTwin, { loading }] = useMutation<CreateDigitalTwinResult>(\n CREATE_DIGITAL_TWIN_MUTATION,\n {\n onCompleted: (data) => {\n notify.success('Digital twin created', `${data.createDigitalTwin.modelRef} is now linked.`);\n reset();\n onOpenChange(false);\n onCreated();\n },\n onError: (error) => notify.error('Could not create digital twin', error.message),\n }\n );\n\n const handleSubmit = () => {\n if (!assetId) {\n notify.warning('Asset required', 'Select the asset this twin represents.');\n return;\n }\n if (!modelRef.trim()) {\n notify.warning('Model reference required', 'Enter the model reference for this twin.');\n return;\n }\n void createTwin({\n variables: {\n input: {\n assetId,\n modelRef: modelRef.trim(),\n fidelityLevel,\n geometryRef: geometryRef.trim() || null,\n },\n },\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New Digital Twin</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Create a virtual replica linked to a physical asset.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label>Asset *</Label>\n <Select value={assetId} onValueChange={setAssetId}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assets.map((asset) => (\n <SelectItem key={asset.id} value={asset.id}>\n {asset.assetTag} — {asset.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Model Reference *</Label>\n <Input\n placeholder=\"e.g., twin-pump-03\"\n value={modelRef}\n onChange={(e) => setModelRef(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Fidelity Level</Label>\n <Select\n value={fidelityLevel}\n onValueChange={(v) => setFidelityLevel(v as typeof fidelityLevel)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {FIDELITY_LEVELS.map((f) => (\n <SelectItem key={f} value={f}>\n {f}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"space-y-2\">\n <Label>Geometry Reference</Label>\n <Input\n placeholder=\"e.g., s3://twins/pump-03.glb\"\n value={geometryRef}\n onChange={(e) => setGeometryRef(e.target.value)}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => onOpenChange(false)} disabled={loading}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={loading}>\n {loading ? 'Creating…' : 'Create twin'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ─── Twin detail dialog: geometry preview + properties + live parameters ─────\nfunction TwinDetailDialog({\n twin,\n asset,\n onClose,\n}: {\n twin: DigitalTwin | null;\n asset: Asset | undefined;\n onClose: () => void;\n}) {\n const propertyEntries = useMemo(\n () => (twin ? Object.entries(twin.properties ?? {}) : []),\n [twin]\n );\n const numericParams = useMemo(\n () => propertyEntries.filter(([, v]) => isNumeric(v)) as [string, number][],\n [propertyEntries]\n );\n\n if (!twin) return null;\n const status = assetStatusPill(asset?.status);\n\n return (\n <ResponsiveDialog open={twin !== null} onOpenChange={(o) => (!o ? onClose() : undefined)}>\n <ResponsiveDialogContent className=\"max-h-[88vh] overflow-y-auto sm:max-w-3xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Digital Twin — {twin.modelRef}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {asset ? `${asset.assetTag} — ${asset.name}` : `Asset ${twin.assetId}`}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n {/* Geometry preview */}\n <div className=\"flex min-h-[220px] flex-col items-center justify-center gap-3 rounded-xl border border-border-seam bg-bg-sunken/40 p-6 text-center\">\n <span className=\"flex h-16 w-16 items-center justify-center rounded-2xl bg-accent-primary/10 text-accent-primary\">\n <Box className=\"h-8 w-8\" />\n </span>\n <div>\n <p className=\"text-sm font-medium\">Geometry model</p>\n {twin.geometryRef ? (\n <p className=\"mt-1 max-w-[240px] truncate font-mono text-xs text-text-secondary\">\n {twin.geometryRef}\n </p>\n ) : (\n <p className=\"mt-1 text-xs text-text-muted\">No geometry linked yet</p>\n )}\n </div>\n <Pill tone={fidelityTone(twin.fidelityLevel)}>\n {humanize(twin.fidelityLevel)} fidelity\n </Pill>\n </div>\n\n {/* Properties + status */}\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-base font-semibold\">Properties</h3>\n <Pill tone={status.tone}>{status.label}</Pill>\n </div>\n {propertyEntries.length === 0 ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken/40 p-4 text-sm text-text-secondary\">\n No properties recorded for this twin yet.\n </p>\n ) : (\n <dl className=\"divide-y divide-border-seam rounded-lg border border-border-seam\">\n {propertyEntries.map(([key, value]) => (\n <div key={key} className=\"flex items-start justify-between gap-3 p-3 text-sm\">\n <dt className=\"text-text-secondary\">{humanize(key)}</dt>\n <dd\n className=\"max-w-[60%] truncate text-right font-medium\"\n title={String(value)}\n >\n {typeof value === 'object' ? JSON.stringify(value) : String(value)}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </div>\n </div>\n\n {/* Live parameters — only from real numeric properties, never fabricated */}\n <div className=\"space-y-2\">\n <h3 className=\"flex items-center gap-2 text-base font-semibold\">\n <Gauge className=\"h-4 w-4\" /> Live parameters\n </h3>\n {numericParams.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border-seam p-4 text-sm text-text-secondary\">\n No live telemetry connected. Numeric values recorded on this twin's properties\n will appear here as live parameters.\n </div>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3\">\n {numericParams.map(([key, value]) => (\n <StatTile key={key} label={humanize(key)} value={value} />\n ))}\n </div>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={onClose}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function DigitalTwinPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [selectedTwin, setSelectedTwin] = useState<DigitalTwin | null>(null);\n const assets = useAssetHandlerStore((s) => s.assets);\n\n const { data, loading, refetch } = useQuery<{ digitalTwins: DigitalTwin[] }>(\n DIGITAL_TWINS_QUERY,\n { fetchPolicy: 'cache-and-network' }\n );\n\n const twins = useMemo(() => data?.digitalTwins ?? [], [data]);\n\n const assetById = useMemo(() => {\n const map = new Map<string, Asset>();\n for (const a of assets) map.set(a.id, a);\n return map;\n }, [assets]);\n\n const fidelityDonut = useMemo(() => {\n const counts = new Map<string, number>();\n for (const t of twins)\n counts.set(\n t.fidelityLevel.toUpperCase(),\n (counts.get(t.fidelityLevel.toUpperCase()) ?? 0) + 1\n );\n return FIDELITY_LEVELS.filter((f) => (counts.get(f) ?? 0) > 0).map((f) => ({\n name: humanize(f),\n value: counts.get(f) ?? 0,\n }));\n }, [twins]);\n\n const mostRecent = useMemo(() => {\n if (twins.length === 0) return null;\n return [...twins].sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n )[0];\n }, [twins]);\n\n const highFidelityCount = useMemo(\n () => twins.filter((t) => ['HIGH', 'ULTRA'].includes(t.fidelityLevel.toUpperCase())).length,\n [twins]\n );\n\n const pagination = usePagination(twins, { pageSize: 8 });\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-xs font-semibold uppercase tracking-wider text-text-muted\">\n Asset Twins / Digital Twin\n </p>\n <h1 className=\"flex items-center gap-2 text-2xl font-semibold\">\n <Box className=\"h-6 w-6\" /> <GradientText>Digital Twin</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Virtual replicas of your critical assets — geometry, properties and live parameters.\n </p>\n </div>\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Digital Twin\n </Button>\n </div>\n <PagePurpose>\n A live virtual replica of your most critical assets. Use digital twins to simulate what-if\n scenarios, validate maintenance changes, and visualise condition without touching the\n physical equipment — so you can de-risk decisions on high-value or hard-to-access assets.\n </PagePurpose>\n\n <NewDigitalTwinDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onCreated={() => void refetch()}\n />\n <TwinDetailDialog\n twin={selectedTwin}\n asset={selectedTwin ? assetById.get(selectedTwin.assetId) : undefined}\n onClose={() => setSelectedTwin(null)}\n />\n\n {/* ─── Summary ─── */}\n {twins.length > 0 ? (\n <>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-3\">\n <StatTile label=\"Total Twins\" value={twins.length} icon={Boxes} hero />\n <StatTile label=\"High / Ultra Fidelity\" value={highFidelityCount} icon={Layers3} />\n <StatTile\n label=\"Most Recent\"\n value={mostRecent ? relativeTime(mostRecent.updatedAt) : '—'}\n icon={Clock}\n />\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <ChartCard\n title=\"Twins by fidelity\"\n subtitle=\"Model resolution mix\"\n icon={Layers3}\n className=\"lg:col-span-1\"\n >\n <DonutChart\n data={fidelityDonut}\n height={220}\n centerLabel=\"twins\"\n centerValue={twins.length}\n ariaLabel=\"Digital twins by fidelity level\"\n />\n </ChartCard>\n\n <div className=\"grid gap-3 sm:grid-cols-2 lg:col-span-2\">\n {pagination.pageItems.map((twin, i) => {\n const asset = assetById.get(twin.assetId);\n const status = assetStatusPill(asset?.status);\n return (\n <GlassCard\n key={twin.id}\n treatment={i === 0 && pagination.page === 1 ? 'glass' : 'metric'}\n glow={i === 0 && pagination.page === 1}\n className=\"border-border-seam\"\n >\n <button\n type=\"button\"\n onClick={() => setSelectedTwin(twin)}\n className=\"flex h-full w-full flex-col gap-3 p-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-primary\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-base font-semibold\" title={twin.modelRef}>\n {twin.modelRef}\n </h2>\n <p className=\"truncate text-xs text-text-secondary\">\n {asset ? `${asset.assetTag} — ${asset.name}` : `Asset ${twin.assetId}`}\n </p>\n </div>\n <Pill tone={status.tone}>{status.label}</Pill>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Pill tone={fidelityTone(twin.fidelityLevel)}>\n {humanize(twin.fidelityLevel)} fidelity\n </Pill>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n twin.geometryRef\n ? 'bg-status-info-subtle text-status-info-text'\n : 'bg-status-neutral-bg-subtle text-status-neutral-text'\n )}\n >\n <LinkIcon className=\"h-3 w-3\" />\n {twin.geometryRef ? 'Geometry' : 'No geometry'}\n </span>\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-seam pt-2 text-xs text-text-secondary\">\n <span>Updated {relativeTime(twin.updatedAt)}</span>\n <span className=\"inline-flex items-center gap-1 text-accent-primary\">\n View <ExternalLink className=\"h-3 w-3\" />\n </span>\n </div>\n </button>\n </GlassCard>\n );\n })}\n </div>\n </div>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"twins\"\n />\n </>\n ) : null}\n\n {loading && twins.length === 0 ? (\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-32 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n\n {!loading && twins.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No digital twins found\"\n description=\"Create a digital twin to model one of your assets.\"\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Digital Twin\n </Button>\n }\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DiscoveryJobsPage.js","names":[],"sources":["../../../src/assethandler/pages/DiscoveryJobsPage.tsx"],"sourcesContent":["import { Radar, Plus, Loader2, CheckCircle2, XCircle, Clock, Boxes, Activity } from 'lucide-react';\nimport type React from 'react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n type DonutSlice,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\n\nconst DISCOVERY_JOBS_QUERY = gql`\n query AssetHandlerDiscoveryJobs {\n discoveryJobs {\n id\n name\n source\n status\n assetsDiscovered\n errorSummary\n startedAt\n completedAt\n createdAt\n }\n }\n`;\n\nconst START_DISCOVERY_JOB_MUTATION = gql`\n mutation AssetHandlerStartDiscoveryJob($input: StartDiscoveryJobInput!) {\n startDiscoveryJob(input: $input) {\n id\n name\n }\n }\n`;\n\ntype DiscoveryStatus = 'COMPLETED' | 'RUNNING' | 'FAILED' | 'PENDING';\n\ninterface DiscoveryJob {\n id: string;\n name: string;\n source: string;\n status: DiscoveryStatus;\n assetsDiscovered: number;\n errorSummary: string | null;\n startedAt: string | null;\n completedAt: string | null;\n createdAt: string;\n}\n\ninterface StartDiscoveryJobResult {\n startDiscoveryJob: { id: string; name: string };\n}\n\ntype StatusFilter = 'ALL' | 'RUNNING' | 'COMPLETED' | 'FAILED';\n\nconst FILTERS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'FAILED', label: 'Failed' },\n];\n\nconst STATUS_META: Record<\n DiscoveryStatus,\n { label: string; className: string; icon: typeof CheckCircle2; spin?: boolean }\n> = {\n COMPLETED: {\n label: 'Completed',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n icon: CheckCircle2,\n },\n RUNNING: {\n label: 'Running',\n className: 'bg-status-info-bg-subtle text-status-info-text',\n icon: Loader2,\n spin: true,\n },\n FAILED: {\n label: 'Failed',\n className: 'bg-status-error-bg-subtle text-status-error-text',\n icon: XCircle,\n },\n PENDING: {\n label: 'Pending',\n className: 'bg-bg-sunken text-text-secondary',\n icon: Clock,\n },\n};\n\nfunction StatusPill({ status }: { status: DiscoveryStatus }) {\n const meta = STATUS_META[status] ?? STATUS_META.PENDING;\n const Icon = meta.icon;\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',\n meta.className\n )}\n >\n <Icon className={cn('h-3 w-3', meta.spin && 'motion-safe:animate-spin')} aria-hidden=\"true\" />\n {meta.label}\n </span>\n );\n}\n\n/** Human-readable elapsed time between two instants, e.g. \"30m 16s\". */\nfunction formatDuration(startedAt: string | null, completedAt: string | null): string {\n if (!startedAt) return '—';\n const start = new Date(startedAt).getTime();\n const end = completedAt ? new Date(completedAt).getTime() : Date.now();\n const seconds = Math.max(0, Math.round((end - start) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remSec = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remSec}s`;\n const hours = Math.floor(minutes / 60);\n const remMin = minutes % 60;\n return `${hours}h ${remMin}m`;\n}\n\nconst formatStarted = (value: string | null): string =>\n value\n ? new Date(value).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n })\n : '—';\n\nfunction StartDiscoveryDialog({\n open,\n onOpenChange,\n onStarted,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onStarted: () => void;\n}) {\n const [name, setName] = useState('');\n const [source, setSource] = useState('');\n\n const [startDiscoveryJob, { loading: starting }] = useMutation<StartDiscoveryJobResult>(\n START_DISCOVERY_JOB_MUTATION,\n {\n onCompleted: (result) => {\n notify.success(\n 'Discovery job started',\n `${result.startDiscoveryJob.name} is now scanning.`\n );\n setName('');\n setSource('');\n onOpenChange(false);\n onStarted();\n },\n onError: (error) => notify.error('Could not start discovery job', error.message),\n }\n );\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (!name.trim() || !source.trim()) {\n notify.warning('Missing details', 'Job name and discovery source are required.');\n return;\n }\n void startDiscoveryJob({ variables: { input: { name: name.trim(), source: source.trim() } } });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Start Discovery Job</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Point a scan at a source (an AWS region, an IP range or subnet) and it feeds new or\n changed assets into your inventory.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form className=\"space-y-4\" onSubmit={handleSubmit}>\n <div className=\"space-y-2\">\n <Label>Job Name</Label>\n <Input\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder=\"e.g., Nightly datacenter scan\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Target (source / IP range / subnet)</Label>\n <Input\n value={source}\n onChange={(event) => setSource(event.target.value)}\n placeholder=\"e.g., aws-ap-south-1 or 10.10.0.0/24\"\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={starting}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={starting}>\n {starting ? 'Starting…' : 'Start Job'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function DiscoveryJobsPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [filter, setFilter] = useState<StatusFilter>('ALL');\n\n const { data, loading, refetch, startPolling, stopPolling } = useQuery<{\n discoveryJobs: DiscoveryJob[];\n }>(DISCOVERY_JOBS_QUERY, { fetchPolicy: 'cache-and-network' });\n\n const jobs = useMemo(() => data?.discoveryJobs ?? [], [data?.discoveryJobs]);\n\n // Live-refresh while any job is still in flight, then stop to avoid idle polling.\n const hasActive = jobs.some((j) => j.status === 'RUNNING' || j.status === 'PENDING');\n useEffect(() => {\n if (hasActive) {\n startPolling(5000);\n return () => stopPolling();\n }\n stopPolling();\n return undefined;\n }, [hasActive, startPolling, stopPolling]);\n\n const stats = useMemo(() => {\n const running = jobs.filter((j) => j.status === 'RUNNING' || j.status === 'PENDING').length;\n const failed = jobs.filter((j) => j.status === 'FAILED').length;\n const discovered = jobs.reduce((sum, j) => sum + j.assetsDiscovered, 0);\n return { total: jobs.length, running, failed, discovered };\n }, [jobs]);\n\n const statusMix = useMemo<DonutSlice[]>(() => {\n const order: DiscoveryStatus[] = ['COMPLETED', 'RUNNING', 'PENDING', 'FAILED'];\n const counts = jobs.reduce<Record<string, number>>((acc, j) => {\n acc[j.status] = (acc[j.status] ?? 0) + 1;\n return acc;\n }, {});\n return order\n .filter((s) => counts[s])\n .map((s, i) => ({ name: STATUS_META[s].label, value: counts[s], color: seriesColor(i) }));\n }, [jobs]);\n\n const yieldBars = useMemo(\n () =>\n [...jobs]\n .filter((j) => j.assetsDiscovered > 0)\n .sort((a, b) => b.assetsDiscovered - a.assetsDiscovered)\n .slice(0, 8)\n .map((j) => ({ name: j.name, assets: j.assetsDiscovered })),\n [jobs]\n );\n\n const filtered = useMemo(() => {\n if (filter === 'ALL') return jobs;\n if (filter === 'RUNNING')\n return jobs.filter((j) => j.status === 'RUNNING' || j.status === 'PENDING');\n return jobs.filter((j) => j.status === filter);\n }, [jobs, filter]);\n\n const pagination = usePagination(filtered, { pageSize: 12, resetKey: filter });\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={Radar}\n title=\"Discovery Jobs\"\n description=\"Automatically find assets across your cloud and on-prem environments instead of entering them by hand.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Start Discovery Job\n </Button>\n }\n />\n <PagePurpose>\n Point a discovery job at a source (e.g. an AWS region or an IP subnet) and it scans for new\n or changed assets and feeds them into your inventory — keeping the estate complete and up to\n date.\n </PagePurpose>\n\n <StartDiscoveryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onStarted={() => void refetch()}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile label=\"Jobs Run\" value={stats.total} icon={Radar} />\n <StatTile label=\"Assets Discovered\" value={stats.discovered} icon={Boxes} />\n <StatTile\n label=\"Running\"\n value={stats.running}\n icon={Activity}\n delta={stats.running > 0 ? 'live' : undefined}\n deltaDirection=\"neutral\"\n deltaTone={stats.running > 0 ? 'positive' : 'neutral'}\n />\n <StatTile\n label=\"Failed\"\n value={stats.failed}\n icon={XCircle}\n deltaTone={stats.failed > 0 ? 'negative' : 'neutral'}\n />\n </div>\n\n {jobs.length > 0 ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Status mix\" subtitle=\"Jobs by current state\">\n <DonutChart\n data={statusMix}\n height={220}\n centerLabel=\"Jobs\"\n centerValue={stats.total}\n ariaLabel=\"Discovery jobs by status\"\n />\n </ChartCard>\n <ChartCard title=\"Discovery yield\" subtitle=\"Assets found per job (top 8)\">\n <BarsChart\n data={yieldBars}\n xKey=\"name\"\n series={[{ key: 'assets', label: 'Assets found' }]}\n orientation=\"horizontal\"\n height={220}\n categoryWidth={130}\n emptyLabel=\"No assets discovered yet\"\n ariaLabel=\"Assets discovered per job\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n <div\n className=\"flex flex-wrap items-center gap-2\"\n role=\"tablist\"\n aria-label=\"Filter discovery jobs\"\n >\n {FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={filter === f.value}\n onClick={() => setFilter(f.value)}\n className={cn(\n 'rounded-full px-3.5 py-1.5 text-sm font-medium transition-colors',\n filter === f.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {f.label}\n </button>\n ))}\n </div>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n {/* Desktop table */}\n <div className=\"hidden overflow-x-auto md:block\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-4 py-3 text-left font-semibold\">Job Name</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Target</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Status</th>\n <th className=\"px-4 py-3 text-right font-semibold\">Assets Found</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Started</th>\n <th className=\"px-4 py-3 text-right font-semibold\">Duration</th>\n </tr>\n </thead>\n <tbody>\n {pagination.pageItems.map((job) => (\n <tr\n key={job.id}\n className=\"border-b border-border-seam/60 last:border-b-0 hover:bg-bg-sunken/40\"\n >\n <td className=\"px-4 py-3 font-medium text-text-primary\">{job.name}</td>\n <td className=\"max-w-[200px] truncate px-4 py-3 font-mono text-xs text-text-secondary\">\n {job.source}\n </td>\n <td className=\"px-4 py-3\">\n <StatusPill status={job.status} />\n {job.status === 'FAILED' && job.errorSummary ? (\n <p\n className=\"mt-1 max-w-[220px] truncate text-xs text-status-error-text\"\n title={job.errorSummary}\n >\n {job.errorSummary}\n </p>\n ) : null}\n </td>\n <td className=\"px-4 py-3 text-right font-medium tabular-nums\">\n {job.assetsDiscovered.toLocaleString()}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">{formatStarted(job.startedAt)}</td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-secondary\">\n {formatDuration(job.startedAt, job.completedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {/* Mobile cards */}\n <div className=\"divide-y divide-border-seam/60 md:hidden\">\n {pagination.pageItems.map((job) => (\n <div key={job.id} className=\"space-y-2 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">{job.name}</span>\n <StatusPill status={job.status} />\n </div>\n <p className=\"font-mono text-xs text-text-secondary\">{job.source}</p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {job.assetsDiscovered.toLocaleString()}\n </span>{' '}\n assets\n </span>\n <span>Started {formatStarted(job.startedAt)}</span>\n <span>{formatDuration(job.startedAt, job.completedAt)}</span>\n </div>\n {job.status === 'FAILED' && job.errorSummary ? (\n <p className=\"text-xs text-status-error-text\">{job.errorSummary}</p>\n ) : null}\n </div>\n ))}\n </div>\n\n {loading && jobs.length === 0 ? (\n <div className=\"space-y-2 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-8 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n {!loading && filtered.length === 0 ? (\n <IllustratedEmptyState\n illustration={filter === 'ALL' ? 'empty-data' : 'empty-search'}\n title={filter === 'ALL' ? 'No discovery jobs found' : 'No jobs match this filter'}\n description={\n filter === 'ALL'\n ? 'Run a discovery job to populate this list.'\n : 'Try a different status filter.'\n }\n />\n ) : null}\n </GlassCard>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"jobs\"\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,IAAM,IAAuB,CAAG;;;;;;;;;;;;;;GAgB1B,IAA+B,CAAG;;;;;;;GA6BlC,IAAoD;CACxD;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAU,OAAO;EAAU;CACrC,EAEK,IAGF;CACF,WAAW;EACT,OAAO;EACP,WAAW;EACX,MAAM;EACP;CACD,SAAS;EACP,OAAO;EACP,WAAW;EACX,MAAM;EACN,MAAM;EACP;CACD,QAAQ;EACN,OAAO;EACP,WAAW;EACX,MAAM;EACP;CACD,SAAS;EACP,OAAO;EACP,WAAW;EACX,MAAM;EACP;CACF;AAED,SAAS,EAAW,EAAE,aAAuC;CAC3D,IAAM,IAAO,EAAY,MAAW,EAAY,SAC1C,IAAO,EAAK;AAClB,QACE,kBAAC,QAAD;EACE,WAAW,EACT,iFACA,EAAK,UACN;YAJH,CAME,kBAAC,GAAD;GAAM,WAAW,EAAG,WAAW,EAAK,QAAQ,2BAA2B;GAAE,eAAY;GAAS,CAAA,EAC7F,EAAK,MACD;;;AAKX,SAAS,EAAe,GAA0B,GAAoC;AACpF,KAAI,CAAC,EAAW,QAAO;CACvB,IAAM,IAAQ,IAAI,KAAK,EAAU,CAAC,SAAS,EACrC,IAAM,IAAc,IAAI,KAAK,EAAY,CAAC,SAAS,GAAG,KAAK,KAAK,EAChE,IAAU,KAAK,IAAI,GAAG,KAAK,OAAO,IAAM,KAAS,IAAK,CAAC;AAC7D,KAAI,IAAU,GAAI,QAAO,GAAG,EAAQ;CACpC,IAAM,IAAU,KAAK,MAAM,IAAU,GAAG,EAClC,IAAS,IAAU;AAIzB,QAHI,IAAU,KAAW,GAAG,EAAQ,IAAI,EAAO,KAGxC,GAFO,KAAK,MAAM,IAAU,GAAG,CAEtB,IADD,IAAU,GACE;;AAG7B,IAAM,KAAiB,MACrB,IACI,IAAI,KAAK,EAAM,CAAC,eAAe,KAAA,GAAW;CACxC,OAAO;CACP,KAAK;CACL,MAAM;CACN,QAAQ;CACT,CAAC,GACF;AAEN,SAAS,EAAqB,EAC5B,SACA,iBACA,gBAKC;CACD,IAAM,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAElC,CAAC,GAAmB,EAAE,SAAS,OAAc,EACjD,GACA;EACE,cAAc,MAAW;AAQvB,GAPA,EAAO,QACL,yBACA,GAAG,EAAO,kBAAkB,KAAK,mBAClC,EACD,EAAQ,GAAG,EACX,EAAU,GAAG,EACb,EAAa,GAAM,EACnB,GAAW;;EAEb,UAAU,MAAU,EAAO,MAAM,iCAAiC,EAAM,QAAQ;EACjF,CACF;AAWD,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,uBAA2C,CAAA,EAClE,kBAAC,GAAD,EAAA,UAA6B,2HAGC,CAAA,CACP,EAAA,CAAA,EACzB,kBAAC,QAAD;GAAM,WAAU;GAAY,WAnBZ,MAA2B;AAE/C,QADA,EAAM,gBAAgB,EAClB,CAAC,EAAK,MAAM,IAAI,CAAC,EAAO,MAAM,EAAE;AAClC,OAAO,QAAQ,mBAAmB,8CAA8C;AAChF;;AAEG,MAAkB,EAAE,WAAW,EAAE,OAAO;KAAE,MAAM,EAAK,MAAM;KAAE,QAAQ,EAAO,MAAM;KAAE,EAAE,EAAE,CAAC;;aAa1F;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAO,YAAgB,CAAA,EACvB,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAU,EAAQ,EAAM,OAAO,MAAM;MAChD,aAAY;MACZ,CAAA,CACE;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAO,uCAA2C,CAAA,EAClD,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAU,EAAU,EAAM,OAAO,MAAM;MAClD,aAAY;MACZ,CAAA,CACE;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe,EAAa,GAAM;KAClC,UAAU;eACX;KAEQ,CAAA,EACT,kBAAC,GAAD;KAAQ,MAAK;KAAS,UAAU;eAC7B,IAAW,cAAc;KACnB,CAAA,CACc,EAAA,CAAA;IACpB;KACiB,EAAA,CAAA;EACT,CAAA;;AAIvB,SAAgB,IAAoB;CAClC,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAQ,KAAa,EAAuB,MAAM,EAEnD,EAAE,SAAM,YAAS,YAAS,iBAAc,mBAAgB,EAE3D,GAAsB,EAAE,aAAa,qBAAqB,CAAC,EAExD,IAAO,QAAc,GAAM,iBAAiB,EAAE,EAAE,CAAC,GAAM,cAAc,CAAC,EAGtE,IAAY,EAAK,MAAM,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,UAAU;AACpF,SAAgB;AACd,MAAI,EAEF,QADA,EAAa,IAAK,QACL,GAAa;AAE5B,KAAa;IAEZ;EAAC;EAAW;EAAc;EAAY,CAAC;CAE1C,IAAM,IAAQ,QAAc;EAC1B,IAAM,IAAU,EAAK,QAAQ,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,UAAU,CAAC,QAC/E,IAAS,EAAK,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,QACnD,IAAa,EAAK,QAAQ,GAAK,MAAM,IAAM,EAAE,kBAAkB,EAAE;AACvE,SAAO;GAAE,OAAO,EAAK;GAAQ;GAAS;GAAQ;GAAY;IACzD,CAAC,EAAK,CAAC,EAEJ,IAAY,QAA4B;EAC5C,IAAM,IAA2B;GAAC;GAAa;GAAW;GAAW;GAAS,EACxE,IAAS,EAAK,QAAgC,GAAK,OACvD,EAAI,EAAE,WAAW,EAAI,EAAE,WAAW,KAAK,GAChC,IACN,EAAE,CAAC;AACN,SAAO,EACJ,QAAQ,MAAM,EAAO,GAAG,CACxB,KAAK,GAAG,OAAO;GAAE,MAAM,EAAY,GAAG;GAAO,OAAO,EAAO;GAAI,OAAO,EAAY,EAAE;GAAE,EAAE;IAC1F,CAAC,EAAK,CAAC,EAEJ,IAAY,QAEd,CAAC,GAAG,EAAK,CACN,QAAQ,MAAM,EAAE,mBAAmB,EAAE,CACrC,MAAM,GAAG,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,CACvD,MAAM,GAAG,EAAE,CACX,KAAK,OAAO;EAAE,MAAM,EAAE;EAAM,QAAQ,EAAE;EAAkB,EAAE,EAC/D,CAAC,EAAK,CACP,EAEK,IAAW,QACX,MAAW,QAAc,IACzB,MAAW,YACN,EAAK,QAAQ,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,UAAU,GACtE,EAAK,QAAQ,MAAM,EAAE,WAAW,EAAO,EAC7C,CAAC,GAAM,EAAO,CAAC,EAEZ,IAAa,EAAc,GAAU;EAAE,UAAU;EAAI,UAAU;EAAQ,CAAC;AAE9E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAA;IACA,MAAM;IACN,OAAM;IACN,aAAY;IACZ,SACE,kBAAC,GAAD;KAAQ,WAAU;KAAmB,eAAe,EAAc,GAAK;eAAvE,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B;;IAEX,CAAA;GACF,kBAAC,GAAD,EAAA,UAAa,kMAIC,CAAA;GAEd,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,iBAAiB,KAAK,GAAS;IAC/B,CAAA;GAEF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAU,OAAM;MAAW,OAAO,EAAM;MAAO,MAAM;MAAS,CAAA;KAC9D,kBAAC,GAAD;MAAU,OAAM;MAAoB,OAAO,EAAM;MAAY,MAAM;MAAS,CAAA;KAC5E,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAM;MACb,MAAM;MACN,OAAO,EAAM,UAAU,IAAI,SAAS,KAAA;MACpC,gBAAe;MACf,WAAW,EAAM,UAAU,IAAI,aAAa;MAC5C,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAM;MACb,MAAM;MACN,WAAW,EAAM,SAAS,IAAI,aAAa;MAC3C,CAAA;KACE;;GAEL,EAAK,SAAS,IACb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,OAAM;KAAa,UAAS;eACrC,kBAAC,GAAD;MACE,MAAM;MACN,QAAQ;MACR,aAAY;MACZ,aAAa,EAAM;MACnB,WAAU;MACV,CAAA;KACQ,CAAA,EACZ,kBAAC,GAAD;KAAW,OAAM;KAAkB,UAAS;eAC1C,kBAAC,GAAD;MACE,MAAM;MACN,MAAK;MACL,QAAQ,CAAC;OAAE,KAAK;OAAU,OAAO;OAAgB,CAAC;MAClD,aAAY;MACZ,QAAQ;MACR,eAAe;MACf,YAAW;MACX,WAAU;MACV,CAAA;KACQ,CAAA,CACR;QACJ;GAEJ,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,cAAW;cAEV,EAAQ,KAAK,MACZ,kBAAC,UAAD;KAEE,MAAK;KACL,MAAK;KACL,iBAAe,MAAW,EAAE;KAC5B,eAAe,EAAU,EAAE,MAAM;KACjC,WAAW,EACT,oEACA,MAAW,EAAE,QACT,kDACA,2DACL;eAEA,EAAE;KACI,EAbF,EAAE,MAaA,CACT;IACE,CAAA;GAEN,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC;KAEE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,MAAD;UAAI,WAAU;oBAAoC;UAAa,CAAA;SAC/D,kBAAC,MAAD;UAAI,WAAU;oBAAoC;UAAW,CAAA;SAC7D,kBAAC,MAAD;UAAI,WAAU;oBAAoC;UAAW,CAAA;SAC7D,kBAAC,MAAD;UAAI,WAAU;oBAAqC;UAAiB,CAAA;SACpE,kBAAC,MAAD;UAAI,WAAU;oBAAoC;UAAY,CAAA;SAC9D,kBAAC,MAAD;UAAI,WAAU;oBAAqC;UAAa,CAAA;SAC7D;WACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAW,UAAU,KAAK,MACzB,kBAAC,MAAD;QAEE,WAAU;kBAFZ;SAIE,kBAAC,MAAD;UAAI,WAAU;oBAA2C,EAAI;UAAU,CAAA;SACvE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAI;UACF,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBAAd,CACE,kBAAC,GAAD,EAAY,QAAQ,EAAI,QAAU,CAAA,EACjC,EAAI,WAAW,YAAY,EAAI,eAC9B,kBAAC,KAAD;WACE,WAAU;WACV,OAAO,EAAI;qBAEV,EAAI;WACH,CAAA,GACF,KACD;;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAI,iBAAiB,gBAAgB;UACnC,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBAAiC,EAAc,EAAI,UAAU;UAAM,CAAA;SACjF,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAe,EAAI,WAAW,EAAI,YAAY;UAC5C,CAAA;SACF;UAzBE,EAAI,GAyBN,CACL,EACI,CAAA,CACF;;MACJ,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAW,UAAU,KAAK,MACzB,kBAAC,OAAD;OAAkB,WAAU;iBAA5B;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAiC,EAAI;UAAY,CAAA,EACjE,kBAAC,GAAD,EAAY,QAAQ,EAAI,QAAU,CAAA,CAC9B;;QACN,kBAAC,KAAD;SAAG,WAAU;mBAAyC,EAAI;SAAW,CAAA;QACrE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD,EAAA,UAAA;WACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAI,iBAAiB,gBAAgB;YACjC,CAAA;WAAC;WAAI;WAEP,EAAA,CAAA;UACP,kBAAC,QAAD,EAAA,UAAA,CAAM,YAAS,EAAc,EAAI,UAAU,CAAQ,EAAA,CAAA;UACnD,kBAAC,QAAD,EAAA,UAAO,EAAe,EAAI,WAAW,EAAI,YAAY,EAAQ,CAAA;UACzD;;QACL,EAAI,WAAW,YAAY,EAAI,eAC9B,kBAAC,KAAD;SAAG,WAAU;mBAAkC,EAAI;SAAiB,CAAA,GAClE;QACA;SAnBI,EAAI,GAmBR,CACN;MACE,CAAA;KAEL,KAAW,EAAK,WAAW,IAC1B,kBAAC,OAAD;MAAK,WAAU;gBACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD,EAAa,WAAU,8DAA+D,EAA5E,EAA4E,CACtF;MACE,CAAA,GACJ;KACH,CAAC,KAAW,EAAS,WAAW,IAC/B,kBAAC,GAAD;MACE,cAAc,MAAW,QAAQ,eAAe;MAChD,OAAO,MAAW,QAAQ,4BAA4B;MACtD,aACE,MAAW,QACP,+CACA;MAEN,CAAA,GACA;KACM;;GAEZ,kBAAC,GAAD;IACE,MAAM,EAAW;IACjB,WAAW,EAAW;IACtB,YAAY,EAAW;IACvB,UAAU,EAAW;IACrB,YAAY,EAAW;IACvB,SAAS,EAAW;IACpB,SAAS,EAAW;IACpB,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACV,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"DiscoveryJobsPage.js","names":[],"sources":["../../../src/assethandler/pages/DiscoveryJobsPage.tsx"],"sourcesContent":["import { Radar, Plus, Loader2, CheckCircle2, XCircle, Clock, Boxes, Activity } from 'lucide-react';\nimport type React from 'react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n type DonutSlice,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\n\nconst DISCOVERY_JOBS_QUERY = gql`\n query AssetHandlerDiscoveryJobs {\n discoveryJobs {\n id\n name\n source\n status\n assetsDiscovered\n errorSummary\n startedAt\n completedAt\n createdAt\n }\n }\n`;\n\nconst START_DISCOVERY_JOB_MUTATION = gql`\n mutation AssetHandlerStartDiscoveryJob($input: StartDiscoveryJobInput!) {\n startDiscoveryJob(input: $input) {\n id\n name\n }\n }\n`;\n\ntype DiscoveryStatus = 'COMPLETED' | 'RUNNING' | 'FAILED' | 'PENDING';\n\ninterface DiscoveryJob {\n id: string;\n name: string;\n source: string;\n status: DiscoveryStatus;\n assetsDiscovered: number;\n errorSummary: string | null;\n startedAt: string | null;\n completedAt: string | null;\n createdAt: string;\n}\n\ninterface StartDiscoveryJobResult {\n startDiscoveryJob: { id: string; name: string };\n}\n\ntype StatusFilter = 'ALL' | 'RUNNING' | 'COMPLETED' | 'FAILED';\n\nconst FILTERS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'RUNNING', label: 'Running' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'FAILED', label: 'Failed' },\n];\n\nconst STATUS_META: Record<\n DiscoveryStatus,\n { label: string; className: string; icon: typeof CheckCircle2; spin?: boolean }\n> = {\n COMPLETED: {\n label: 'Completed',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n icon: CheckCircle2,\n },\n RUNNING: {\n label: 'Running',\n className: 'bg-status-info-bg-subtle text-status-info-text',\n icon: Loader2,\n spin: true,\n },\n FAILED: {\n label: 'Failed',\n className: 'bg-status-error-bg-subtle text-status-error-text',\n icon: XCircle,\n },\n PENDING: {\n label: 'Pending',\n className: 'bg-bg-sunken text-text-secondary',\n icon: Clock,\n },\n};\n\nfunction StatusPill({ status }: { status: DiscoveryStatus }) {\n const meta = STATUS_META[status] ?? STATUS_META.PENDING;\n const Icon = meta.icon;\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',\n meta.className\n )}\n >\n <Icon className={cn('h-3 w-3', meta.spin && 'motion-safe:animate-spin')} aria-hidden=\"true\" />\n {meta.label}\n </span>\n );\n}\n\n/** Human-readable elapsed time between two instants, e.g. \"30m 16s\". */\nfunction formatDuration(startedAt: string | null, completedAt: string | null): string {\n if (!startedAt) return '—';\n const start = new Date(startedAt).getTime();\n const end = completedAt ? new Date(completedAt).getTime() : Date.now();\n const seconds = Math.max(0, Math.round((end - start) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remSec = seconds % 60;\n if (minutes < 60) return `${minutes}m ${remSec}s`;\n const hours = Math.floor(minutes / 60);\n const remMin = minutes % 60;\n return `${hours}h ${remMin}m`;\n}\n\nconst formatStarted = (value: string | null): string =>\n value\n ? new Date(value).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n })\n : '—';\n\nfunction StartDiscoveryDialog({\n open,\n onOpenChange,\n onStarted,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onStarted: () => void;\n}) {\n const [name, setName] = useState('');\n const [source, setSource] = useState('');\n\n const [startDiscoveryJob, { loading: starting }] = useMutation<StartDiscoveryJobResult>(\n START_DISCOVERY_JOB_MUTATION,\n {\n onCompleted: (result) => {\n notify.success(\n 'Discovery job started',\n `${result.startDiscoveryJob.name} is now scanning.`\n );\n setName('');\n setSource('');\n onOpenChange(false);\n onStarted();\n },\n onError: (error) => notify.error('Could not start discovery job', error.message),\n }\n );\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n if (!name.trim() || !source.trim()) {\n notify.warning('Missing details', 'Job name and discovery source are required.');\n return;\n }\n void startDiscoveryJob({ variables: { input: { name: name.trim(), source: source.trim() } } });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Start Discovery Job</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Point a scan at a source (an AWS region, an IP range or subnet) and it feeds new or\n changed assets into your inventory.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form className=\"space-y-4\" onSubmit={handleSubmit}>\n <div className=\"space-y-2\">\n <Label>Job Name</Label>\n <Input\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder=\"e.g., Nightly datacenter scan\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Target (source / IP range / subnet)</Label>\n <Input\n value={source}\n onChange={(event) => setSource(event.target.value)}\n placeholder=\"e.g., aws-ap-south-1 or 10.10.0.0/24\"\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={starting}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={starting}>\n {starting ? 'Starting…' : 'Start Job'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function DiscoveryJobsPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [filter, setFilter] = useState<StatusFilter>('ALL');\n\n const { data, loading, refetch, startPolling, stopPolling } = useQuery<{\n discoveryJobs: DiscoveryJob[];\n }>(DISCOVERY_JOBS_QUERY, { fetchPolicy: 'cache-and-network' });\n\n const jobs = useMemo(() => data?.discoveryJobs ?? [], [data?.discoveryJobs]);\n\n // Live-refresh while any job is still in flight, then stop to avoid idle polling.\n const hasActive = jobs.some((j) => j.status === 'RUNNING' || j.status === 'PENDING');\n useEffect(() => {\n if (hasActive) {\n startPolling(5000);\n return () => stopPolling();\n }\n stopPolling();\n return undefined;\n }, [hasActive, startPolling, stopPolling]);\n\n const stats = useMemo(() => {\n const running = jobs.filter((j) => j.status === 'RUNNING' || j.status === 'PENDING').length;\n const failed = jobs.filter((j) => j.status === 'FAILED').length;\n const discovered = jobs.reduce((sum, j) => sum + j.assetsDiscovered, 0);\n return { total: jobs.length, running, failed, discovered };\n }, [jobs]);\n\n const statusMix = useMemo<DonutSlice[]>(() => {\n const order: DiscoveryStatus[] = ['COMPLETED', 'RUNNING', 'PENDING', 'FAILED'];\n const counts = jobs.reduce<Record<string, number>>((acc, j) => {\n acc[j.status] = (acc[j.status] ?? 0) + 1;\n return acc;\n }, {});\n return order\n .filter((s) => counts[s])\n .map((s, i) => ({ name: STATUS_META[s].label, value: counts[s], color: seriesColor(i) }));\n }, [jobs]);\n\n const yieldBars = useMemo(\n () =>\n [...jobs]\n .filter((j) => j.assetsDiscovered > 0)\n .sort((a, b) => b.assetsDiscovered - a.assetsDiscovered)\n .slice(0, 8)\n .map((j) => ({ name: j.name, assets: j.assetsDiscovered })),\n [jobs]\n );\n\n const filtered = useMemo(() => {\n if (filter === 'ALL') return jobs;\n if (filter === 'RUNNING')\n return jobs.filter((j) => j.status === 'RUNNING' || j.status === 'PENDING');\n return jobs.filter((j) => j.status === filter);\n }, [jobs, filter]);\n\n const pagination = usePagination(filtered, { pageSize: 12, resetKey: filter });\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={Radar}\n title=\"Discovery Jobs\"\n description=\"Automatically find assets across your cloud and on-prem environments instead of entering them by hand.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Start Discovery Job\n </Button>\n }\n />\n <PagePurpose>\n Point a discovery job at a source (e.g. an AWS region or an IP subnet) and it scans for new\n or changed assets and feeds them into your inventory — keeping the estate complete and up to\n date.\n </PagePurpose>\n\n <StartDiscoveryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onStarted={() => void refetch()}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatTile label=\"Jobs Run\" value={stats.total} icon={Radar} />\n <StatTile label=\"Assets Discovered\" value={stats.discovered} icon={Boxes} />\n <StatTile\n label=\"Running\"\n value={stats.running}\n icon={Activity}\n delta={stats.running > 0 ? 'live' : undefined}\n deltaDirection=\"neutral\"\n deltaTone={stats.running > 0 ? 'positive' : 'neutral'}\n />\n <StatTile\n label=\"Failed\"\n value={stats.failed}\n icon={XCircle}\n deltaTone={stats.failed > 0 ? 'negative' : 'neutral'}\n />\n </div>\n\n {jobs.length > 0 ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Status mix\" subtitle=\"Jobs by current state\">\n <DonutChart\n data={statusMix}\n height={220}\n centerLabel=\"Jobs\"\n centerValue={stats.total}\n ariaLabel=\"Discovery jobs by status\"\n />\n </ChartCard>\n <ChartCard title=\"Discovery yield\" subtitle=\"Assets found per job (top 8)\">\n <BarsChart\n data={yieldBars}\n xKey=\"name\"\n series={[{ key: 'assets', label: 'Assets found' }]}\n orientation=\"horizontal\"\n height={220}\n categoryWidth={130}\n emptyLabel=\"No assets discovered yet\"\n ariaLabel=\"Assets discovered per job\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n <div\n className=\"flex flex-wrap items-center gap-2\"\n role=\"tablist\"\n aria-label=\"Filter discovery jobs\"\n >\n {FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={filter === f.value}\n onClick={() => setFilter(f.value)}\n className={cn(\n 'rounded-full px-3.5 py-1.5 text-sm font-medium transition-colors',\n filter === f.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {f.label}\n </button>\n ))}\n </div>\n\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n {/* Desktop table */}\n <div className=\"hidden overflow-x-auto md:block\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-4 py-3 text-left font-semibold\">Job Name</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Target</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Status</th>\n <th className=\"px-4 py-3 text-right font-semibold\">Assets Found</th>\n <th className=\"px-4 py-3 text-left font-semibold\">Started</th>\n <th className=\"px-4 py-3 text-right font-semibold\">Duration</th>\n </tr>\n </thead>\n <tbody>\n {pagination.pageItems.map((job) => (\n <tr\n key={job.id}\n className=\"border-b border-border-seam/60 last:border-b-0 hover:bg-bg-sunken/40\"\n >\n <td className=\"px-4 py-3 font-medium text-text-primary\">{job.name}</td>\n <td className=\"max-w-[200px] truncate px-4 py-3 font-mono text-xs text-text-secondary\">\n {job.source}\n </td>\n <td className=\"px-4 py-3\">\n <StatusPill status={job.status} />\n {job.status === 'FAILED' && job.errorSummary ? (\n <p\n className=\"mt-1 max-w-[220px] truncate text-xs text-status-error-text\"\n title={job.errorSummary}\n >\n {job.errorSummary}\n </p>\n ) : null}\n </td>\n <td className=\"px-4 py-3 text-right font-medium tabular-nums\">\n {job.assetsDiscovered.toLocaleString()}\n </td>\n <td className=\"px-4 py-3 text-text-secondary\">{formatStarted(job.startedAt)}</td>\n <td className=\"px-4 py-3 text-right tabular-nums text-text-secondary\">\n {formatDuration(job.startedAt, job.completedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {/* Mobile cards */}\n <div className=\"divide-y divide-border-seam/60 md:hidden\">\n {pagination.pageItems.map((job) => (\n <div key={job.id} className=\"space-y-2 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">{job.name}</span>\n <StatusPill status={job.status} />\n </div>\n <p className=\"font-mono text-xs text-text-secondary\">{job.source}</p>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {job.assetsDiscovered.toLocaleString()}\n </span>{' '}\n assets\n </span>\n <span>Started {formatStarted(job.startedAt)}</span>\n <span>{formatDuration(job.startedAt, job.completedAt)}</span>\n </div>\n {job.status === 'FAILED' && job.errorSummary ? (\n <p className=\"text-xs text-status-error-text\">{job.errorSummary}</p>\n ) : null}\n </div>\n ))}\n </div>\n\n {loading && jobs.length === 0 ? (\n <div className=\"space-y-2 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-8 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n {!loading && filtered.length === 0 ? (\n <IllustratedEmptyState\n illustration={filter === 'ALL' ? 'empty-data' : 'empty-search'}\n title={filter === 'ALL' ? 'No discovery jobs found' : 'No jobs match this filter'}\n description={\n filter === 'ALL'\n ? 'Run a discovery job to populate this list.'\n : 'Try a different status filter.'\n }\n />\n ) : null}\n </GlassCard>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"jobs\"\n />\n </div>\n );\n}\n"],"mappings":"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"}
|