@hotmeshio/long-tail 0.19.0 → 0.20.0
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/build/api/workflows/config.d.ts +8 -1
- package/build/api/workflows/config.js +24 -1
- package/build/api/workflows/index.d.ts +4 -0
- package/build/api/workflows/index.js +9 -1
- package/build/api/workflows/input-lookups.d.ts +16 -0
- package/build/api/workflows/input-lookups.js +69 -0
- package/build/api/workflows/input-validation.d.ts +14 -0
- package/build/api/workflows/input-validation.js +41 -0
- package/build/api/workflows/invocable.d.ts +19 -0
- package/build/api/workflows/invocable.js +107 -0
- package/build/api/workflows/invocation.js +6 -0
- package/build/lib/db/schemas/034_workflow_input_schema.sql +13 -0
- package/build/lib/db/schemas/035_workflow_input_lookups.sql +9 -0
- package/build/routes/workflows/config.js +12 -0
- package/build/routes/workflows/discovery.js +14 -0
- package/build/services/config/cache.js +3 -0
- package/build/services/config/read.js +6 -0
- package/build/services/config/sql.d.ts +2 -2
- package/build/services/config/sql.js +8 -5
- package/build/services/config/write.js +20 -1
- package/build/services/interceptor/index.js +7 -0
- package/build/services/invocation-access.d.ts +18 -0
- package/build/services/invocation-access.js +32 -0
- package/build/services/knowledge/index.d.ts +1 -0
- package/build/services/knowledge/index.js +1 -0
- package/build/services/knowledge/lookup-refs.d.ts +7 -0
- package/build/services/knowledge/lookup-refs.js +26 -0
- package/build/services/mcp/db-server/schemas.d.ts +2 -2
- package/build/services/workflow-invocation.js +4 -9
- package/build/shared/form-validation/field-validator.d.ts +1 -0
- package/build/shared/form-validation/field-validator.js +94 -6
- package/build/shared/form-validation/index.d.ts +1 -0
- package/build/shared/form-validation/index.js +1 -0
- package/build/shared/form-validation/invoke-context.d.ts +8 -0
- package/build/shared/form-validation/invoke-context.js +12 -0
- package/build/shared/form-validation/validate-resolver-payload.d.ts +1 -1
- package/build/shared/form-validation/validate-resolver-payload.js +1 -0
- package/build/shared/form-validation/x-lt-help.d.ts +2 -2
- package/build/shared/form-validation/x-lt-help.js +2 -2
- package/build/shared/form-validation/x-lt-options.d.ts +3 -3
- package/build/shared/form-validation/x-lt-options.js +19 -5
- package/build/shared/form-validation/x-lt-show-if.d.ts +6 -0
- package/build/shared/form-validation/x-lt-show-if.js +13 -0
- package/build/start/workers.js +12 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +64 -20
- package/build/system/mcp-servers/admin/schemas.js +19 -3
- package/build/system/mcp-servers/admin/workflow-config.js +54 -9
- package/build/system/mcp-servers/admin/workflows.js +18 -3
- package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
- package/build/system/seed/tool-manifests-admin.d.ts +517 -54
- package/build/system/seed/tool-manifests-admin.js +4 -3
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/config.d.ts +10 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +4 -1
- package/build/types/startup.d.ts +16 -0
- package/build/types/validation.d.ts +4 -2
- package/build/types/workflow-icons.d.ts +86 -0
- package/build/types/workflow-icons.js +92 -0
- package/dashboard/dist/assets/{AdminDashboard-DPkWtPc1.js → AdminDashboard-DplvDBti.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-DPkWtPc1.js.map → AdminDashboard-DplvDBti.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-DRfnLiQS.js → AgentConfigPage-D8Bf6YxO.js} +3 -3
- package/dashboard/dist/assets/{AgentConfigPage-DRfnLiQS.js.map → AgentConfigPage-D8Bf6YxO.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-CufRZXnT.js → AgentDetailPage-BN8g0Y2G.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-CufRZXnT.js.map → AgentDetailPage-BN8g0Y2G.js.map} +1 -1
- package/dashboard/dist/assets/AgentsPage-7JJBHku8.js +2 -0
- package/dashboard/dist/assets/{AgentsPage-CoCWGhUd.js.map → AgentsPage-7JJBHku8.js.map} +1 -1
- package/dashboard/dist/assets/{AutoGrowTextarea-DgEQeRQw.js → AutoGrowTextarea-7X3T9La8.js} +2 -2
- package/dashboard/dist/assets/{AutoGrowTextarea-DgEQeRQw.js.map → AutoGrowTextarea-7X3T9La8.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-lz5qwSjg.js +2 -0
- package/dashboard/dist/assets/{AvailableEscalationsPage-BSqOctep.js.map → AvailableEscalationsPage-lz5qwSjg.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-DMojDLlV.js → BotPicker-CGCnIz8I.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DMojDLlV.js.map → BotPicker-CGCnIz8I.js.map} +1 -1
- package/dashboard/dist/assets/BulkAssignModal-NJBcoj-f.js +2 -0
- package/dashboard/dist/assets/{BulkAssignModal-DYbM_6Or.js.map → BulkAssignModal-NJBcoj-f.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-CDQ5JHNP.js → CapabilitiesPage-F8dpNn9B.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-CDQ5JHNP.js.map → CapabilitiesPage-F8dpNn9B.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-D4VlCEbD.js → CollapsibleSection-BZW6VpCU.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D4VlCEbD.js.map → CollapsibleSection-BZW6VpCU.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-DnNHjJtD.js → ConfirmDeleteModal-64adXLdq.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-DnNHjJtD.js.map → ConfirmDeleteModal-64adXLdq.js.map} +1 -1
- package/dashboard/dist/assets/{CopyableId-4Lqlc5cU.js → CopyableId-Bn02zYxt.js} +2 -2
- package/dashboard/dist/assets/{CopyableId-4Lqlc5cU.js.map → CopyableId-Bn02zYxt.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-BxDub9d2.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-BxDub9d2.js.map +1 -0
- package/dashboard/dist/assets/{CredentialsPage-CSYZw-rL.js → CredentialsPage-CnLzopLL.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-CSYZw-rL.js.map → CredentialsPage-CnLzopLL.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-CpmqDrlJ.js → CronLabel-CAD5UYDT.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-CpmqDrlJ.js.map → CronLabel-CAD5UYDT.js.map} +1 -1
- package/dashboard/dist/assets/{DataTable-DFPMeqyQ.js → DataTable-7C7riavf.js} +2 -2
- package/dashboard/dist/assets/{DataTable-DFPMeqyQ.js.map → DataTable-7C7riavf.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-BbXlQnYB.js → DropZone-XlcY8hEH.js} +2 -2
- package/dashboard/dist/assets/{DropZone-BbXlQnYB.js.map → DropZone-XlcY8hEH.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-Cul22qBS.js → ElapsedCell-CwO4ufd0.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-Cul22qBS.js.map → ElapsedCell-CwO4ufd0.js.map} +1 -1
- package/dashboard/dist/assets/{EmptyState-lpUFYfun.js → EmptyState-DgC_6njU.js} +2 -2
- package/dashboard/dist/assets/{EmptyState-lpUFYfun.js.map → EmptyState-DgC_6njU.js.map} +1 -1
- package/dashboard/dist/assets/{EntityTimelinePanel-CtzpJ-j2.js → EntityTimelinePanel-CkF-oW-m.js} +2 -2
- package/dashboard/dist/assets/{EntityTimelinePanel-CtzpJ-j2.js.map → EntityTimelinePanel-CkF-oW-m.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-BZQ8Y_UH.js → EscalationListSchemaPage-h9JO_q5f.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-BZQ8Y_UH.js.map → EscalationListSchemaPage-h9JO_q5f.js.map} +1 -1
- package/dashboard/dist/assets/EscalationListView-AHW2xtlr.js +2 -0
- package/dashboard/dist/assets/EscalationListView-AHW2xtlr.js.map +1 -0
- package/dashboard/dist/assets/{EscalationSchemaPage-CydTtq_A.js → EscalationSchemaPage-mJFL5_Se.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-CydTtq_A.js.map → EscalationSchemaPage-mJFL5_Se.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-DSYC0nI7.js → EscalationsOverview-DlXUFqWJ.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-DSYC0nI7.js.map → EscalationsOverview-DlXUFqWJ.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-s48cTXIw.js → EventTable-Ike2xchn.js} +2 -2
- package/dashboard/dist/assets/{EventTable-s48cTXIw.js.map → EventTable-Ike2xchn.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-B61qKmFD.js → FilterBar-DMOR-O1c.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-B61qKmFD.js.map → FilterBar-DMOR-O1c.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-DxZv_KC2.js → GraphInvokePage-CKtvWowP.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-DxZv_KC2.js.map → GraphInvokePage-CKtvWowP.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-BQ3Q791Q.js +2 -0
- package/dashboard/dist/assets/{HomePage-zK3VP_9g.js.map → HomePage-BQ3Q791Q.js.map} +1 -1
- package/dashboard/dist/assets/ListToolbar-DdJzj472.js +2 -0
- package/dashboard/dist/assets/{ListToolbar-DaxloPQc.js.map → ListToolbar-DdJzj472.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-Ciq8r6-p.js → McpOverview-BbtrduWn.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-Ciq8r6-p.js.map → McpOverview-BbtrduWn.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DIC0FEEb.js → McpQueryDetailPage-C1L4T-Wt.js} +3 -3
- package/dashboard/dist/assets/{McpQueryDetailPage-DIC0FEEb.js.map → McpQueryDetailPage-C1L4T-Wt.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-BBy0NSNg.js → McpQueryPage-CfJy7HZ7.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-BBy0NSNg.js.map → McpQueryPage-CfJy7HZ7.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-BqKNmIyW.js → McpRunDetailPage-CA9yv7he.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-BqKNmIyW.js.map → McpRunDetailPage-CA9yv7he.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-D0RlOpzR.js → McpRunsPage-D7QXjW9z.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-D0RlOpzR.js.map → McpRunsPage-D7QXjW9z.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-Dbtm9R2T.js → NamespacePill-Bb3revqD.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-Dbtm9R2T.js.map → NamespacePill-Bb3revqD.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-BX0SFizr.js → OperationsPage-aLHHAP2X.js} +3 -3
- package/dashboard/dist/assets/{OperationsPage-BX0SFizr.js.map → OperationsPage-aLHHAP2X.js.map} +1 -1
- package/dashboard/dist/assets/OperatorDashboard-sXNDh1Ph.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-vXG0TjAQ.js.map → OperatorDashboard-sXNDh1Ph.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-xB92ELs3.js → PageHeader-Di5Sz3an.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-xB92ELs3.js.map → PageHeader-Di5Sz3an.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-DVo_qqn1.js → PageHeaderWithStats-DutKJ-h2.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-DVo_qqn1.js.map → PageHeaderWithStats-DutKJ-h2.js.map} +1 -1
- package/dashboard/dist/assets/{PersonaDetailPage-w5TXcn4Q.js → PersonaDetailPage-NeHD-Gra.js} +2 -2
- package/dashboard/dist/assets/{PersonaDetailPage-w5TXcn4Q.js.map → PersonaDetailPage-NeHD-Gra.js.map} +1 -1
- package/dashboard/dist/assets/{PersonasPage-Dzcinl1L.js → PersonasPage-DavKXRWX.js} +2 -2
- package/dashboard/dist/assets/{PersonasPage-Dzcinl1L.js.map → PersonasPage-DavKXRWX.js.map} +1 -1
- package/dashboard/dist/assets/{PriorityBadge-BETdj4M-.js → PriorityBadge-r33mpDju.js} +2 -2
- package/dashboard/dist/assets/{PriorityBadge-BETdj4M-.js.map → PriorityBadge-r33mpDju.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-BVXieFX1.js → ProcessDetailPage-DkdKJGe6.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-BVXieFX1.js.map → ProcessDetailPage-DkdKJGe6.js.map} +1 -1
- package/dashboard/dist/assets/ProcessesListPage-CjwVKe32.js +2 -0
- package/dashboard/dist/assets/{ProcessesListPage-CKKNpk4z.js.map → ProcessesListPage-CjwVKe32.js.map} +1 -1
- package/dashboard/dist/assets/ResolverForm-Bk-wDkN-.js +3 -0
- package/dashboard/dist/assets/ResolverForm-Bk-wDkN-.js.map +1 -0
- package/dashboard/dist/assets/{RoleDetailPage-BytSo-YE.js → RoleDetailPage-WQt0y5l7.js} +3 -3
- package/dashboard/dist/assets/{RoleDetailPage-BytSo-YE.js.map → RoleDetailPage-WQt0y5l7.js.map} +1 -1
- package/dashboard/dist/assets/{RolePill-CYpL3a2d.js → RolePill-DhnVPK4q.js} +2 -2
- package/dashboard/dist/assets/{RolePill-CYpL3a2d.js.map → RolePill-DhnVPK4q.js.map} +1 -1
- package/dashboard/dist/assets/RolesPage-OM9KM8Pg.js +2 -0
- package/dashboard/dist/assets/{RolesPage-eayR9H4f.js.map → RolesPage-OM9KM8Pg.js.map} +1 -1
- package/dashboard/dist/assets/{RowActions-eYrzgQSj.js → RowActions-Bdd7z4eB.js} +2 -2
- package/dashboard/dist/assets/{RowActions-eYrzgQSj.js.map → RowActions-Bdd7z4eB.js.map} +1 -1
- package/dashboard/dist/assets/RunAsSelector-fFMt1bGe.js +2 -0
- package/dashboard/dist/assets/{RunAsSelector-D7RPwaAl.js.map → RunAsSelector-fFMt1bGe.js.map} +1 -1
- package/dashboard/dist/assets/{ScanCodesPage-C8-ZBXF5.js → ScanCodesPage-AujH-pPQ.js} +2 -2
- package/dashboard/dist/assets/{ScanCodesPage-C8-ZBXF5.js.map → ScanCodesPage-AujH-pPQ.js.map} +1 -1
- package/dashboard/dist/assets/{ScanSchemeDetailPage-tQhEgFKf.js → ScanSchemeDetailPage-eKQpmsC_.js} +2 -2
- package/dashboard/dist/assets/{ScanSchemeDetailPage-tQhEgFKf.js.map → ScanSchemeDetailPage-eKQpmsC_.js.map} +1 -1
- package/dashboard/dist/assets/ScanStationPage-C1_ppDoZ.js +2 -0
- package/dashboard/dist/assets/{ScanStationPage-CuhqsBKo.js.map → ScanStationPage-C1_ppDoZ.js.map} +1 -1
- package/dashboard/dist/assets/{ScopeBadge-BmNzZlSj.js → ScopeBadge-CXUg0FHo.js} +2 -2
- package/dashboard/dist/assets/{ScopeBadge-BmNzZlSj.js.map → ScopeBadge-CXUg0FHo.js.map} +1 -1
- package/dashboard/dist/assets/{SegmentedTabs-DXNzY05q.js → SegmentedTabs-BpJh4Hwn.js} +2 -2
- package/dashboard/dist/assets/{SegmentedTabs-DXNzY05q.js.map → SegmentedTabs-BpJh4Hwn.js.map} +1 -1
- package/dashboard/dist/assets/{StatCard-BXQ8dD9Y.js → StatCard-nWY4sndl.js} +2 -2
- package/dashboard/dist/assets/{StatCard-BXQ8dD9Y.js.map → StatCard-nWY4sndl.js.map} +1 -1
- package/dashboard/dist/assets/{StatusBadge-BDeUwPtb.js → StatusBadge-DHVwxre2.js} +2 -2
- package/dashboard/dist/assets/{StatusBadge-BDeUwPtb.js.map → StatusBadge-DHVwxre2.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-BIVTAMnP.js → StickyPagination-B91Pk9xh.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-BIVTAMnP.js.map → StickyPagination-B91Pk9xh.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-CYSC2cO1.js → StreamMessageDetail-C7SPqaJt.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-CYSC2cO1.js.map → StreamMessageDetail-C7SPqaJt.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-BMZAs_2K.js → SwimlaneTimeline-BcHR8ylf.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-BMZAs_2K.js.map → SwimlaneTimeline-BcHR8ylf.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-D1uszNd2.js → TagInput-BZ8vb2oW.js} +2 -2
- package/dashboard/dist/assets/{TagInput-D1uszNd2.js.map → TagInput-BZ8vb2oW.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CVUDUU5Q.js → TaskDetailPage-Bqj0I_I0.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CVUDUU5Q.js.map → TaskDetailPage-Bqj0I_I0.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-C2H8oRfn.js → TaskQueuePill-Bxir8WRl.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-C2H8oRfn.js.map → TaskQueuePill-Bxir8WRl.js.map} +1 -1
- package/dashboard/dist/assets/TasksListPage-BB1f79lL.js +2 -0
- package/dashboard/dist/assets/{TasksListPage-DEQymbXC.js.map → TasksListPage-BB1f79lL.js.map} +1 -1
- package/dashboard/dist/assets/TimeAgo-BFnODmNe.js +2 -0
- package/dashboard/dist/assets/{TimeAgo-CCxP67Rb.js.map → TimeAgo-BFnODmNe.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CE5ZUYhV.js → TimestampCell-BXgLOBsr.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CE5ZUYhV.js.map → TimestampCell-BXgLOBsr.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-D-h8AoB8.js → ToolPill-FkgTXIhq.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-D-h8AoB8.js.map → ToolPill-FkgTXIhq.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-BHuQTyBY.js → ToolTestPanel-Bwe3J5lj.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-BHuQTyBY.js.map → ToolTestPanel-Bwe3J5lj.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-SFuiludx.js → TopicDetailPage-DAF07vZ9.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-SFuiludx.js.map → TopicDetailPage-DAF07vZ9.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-DO8pUmng.js → TopicsPage-C1gxBUap.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-DO8pUmng.js.map → TopicsPage-C1gxBUap.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-CsLnT75E.js → UserName-BzXJUE6q.js} +2 -2
- package/dashboard/dist/assets/{UserName-CsLnT75E.js.map → UserName-BzXJUE6q.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-SkRU5cRX.js +2 -0
- package/dashboard/dist/assets/{WorkflowExecutionPage-BttnEvOd.js.map → WorkflowExecutionPage-SkRU5cRX.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-CZvBbkn4.js → WorkflowPill--AYMW7u_.js} +2 -2
- package/dashboard/dist/assets/WorkflowPill--AYMW7u_.js.map +1 -0
- package/dashboard/dist/assets/WorkflowsDashboard-CZAqRk1N.js +2 -0
- package/dashboard/dist/assets/{WorkflowsDashboard-BRboBhlK.js.map → WorkflowsDashboard-CZAqRk1N.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowsOverview-BoN44VTE.js +2 -0
- package/dashboard/dist/assets/{WorkflowsOverview-CDFTYeQo.js.map → WorkflowsOverview-BoN44VTE.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-CmnZFevp.js → YamlWorkflowDetailPage-CqjhtdJf.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-CmnZFevp.js.map → YamlWorkflowDetailPage-CqjhtdJf.js.map} +1 -1
- package/dashboard/dist/assets/YamlWorkflowsPage-CwnJkzyb.js +2 -0
- package/dashboard/dist/assets/{YamlWorkflowsPage-Qq1jfcrH.js.map → YamlWorkflowsPage-CwnJkzyb.js.map} +1 -1
- package/dashboard/dist/assets/{agents-C7vUIO7e.js → agents-gV0Ys-sY.js} +2 -2
- package/dashboard/dist/assets/{agents-C7vUIO7e.js.map → agents-gV0Ys-sY.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BaOblnW4.js → bots-B97itB3I.js} +2 -2
- package/dashboard/dist/assets/{bots-BaOblnW4.js.map → bots-B97itB3I.js.map} +1 -1
- package/dashboard/dist/assets/capabilities-CR9xZgGd.js +2 -0
- package/dashboard/dist/assets/{capabilities-RZpmDWoy.js.map → capabilities-CR9xZgGd.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-BwGdcwXR.js → controlplane-YxvP1KCa.js} +2 -2
- package/dashboard/dist/assets/{controlplane-BwGdcwXR.js.map → controlplane-YxvP1KCa.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-uq-IV2aj.js +2 -0
- package/dashboard/dist/assets/{escalation-columns-B4DG_5v8.js.map → escalation-columns-uq-IV2aj.js.map} +1 -1
- package/dashboard/dist/assets/index-3ezfWLkE.js +6 -0
- package/dashboard/dist/assets/index-3ezfWLkE.js.map +1 -0
- package/dashboard/dist/assets/{index-xNFTmmsv.js → index-B9BBCqLI.js} +2 -2
- package/dashboard/dist/assets/{index-xNFTmmsv.js.map → index-B9BBCqLI.js.map} +1 -1
- package/dashboard/dist/assets/{index-iegzyfMv.js → index-BQpV-DTG.js} +2 -2
- package/dashboard/dist/assets/{index-iegzyfMv.js.map → index-BQpV-DTG.js.map} +1 -1
- package/dashboard/dist/assets/index-Bp9KBLT5.js +2 -0
- package/dashboard/dist/assets/{index-IWufkcH9.js.map → index-Bp9KBLT5.js.map} +1 -1
- package/dashboard/dist/assets/index-Bpvfr4OW.css +1 -0
- package/dashboard/dist/assets/{index-D9NtDRtp.js → index-CFbo9FU9.js} +2 -2
- package/dashboard/dist/assets/{index-D9NtDRtp.js.map → index-CFbo9FU9.js.map} +1 -1
- package/dashboard/dist/assets/{index-BQPUlmmz.js → index-CIpHj4di.js} +2 -2
- package/dashboard/dist/assets/{index-BQPUlmmz.js.map → index-CIpHj4di.js.map} +1 -1
- package/dashboard/dist/assets/index-CIwlVfvV.js +67 -0
- package/dashboard/dist/assets/index-CIwlVfvV.js.map +1 -0
- package/dashboard/dist/assets/{index-BICLHYJJ.js → index-CUZ6LYye.js} +2 -2
- package/dashboard/dist/assets/{index-BICLHYJJ.js.map → index-CUZ6LYye.js.map} +1 -1
- package/dashboard/dist/assets/index-Cfvkl2J0.js +13 -0
- package/dashboard/dist/assets/index-Cfvkl2J0.js.map +1 -0
- package/dashboard/dist/assets/{index-BuqpU6ow.js → index-DMeaiKgf.js} +2 -2
- package/dashboard/dist/assets/{index-BuqpU6ow.js.map → index-DMeaiKgf.js.map} +1 -1
- package/dashboard/dist/assets/{index-G3NmdMjt.js → index-DwOnWeu8.js} +2 -2
- package/dashboard/dist/assets/{index-G3NmdMjt.js.map → index-DwOnWeu8.js.map} +1 -1
- package/dashboard/dist/assets/index-SGyrXGp8.js +3 -0
- package/dashboard/dist/assets/index-SGyrXGp8.js.map +1 -0
- package/dashboard/dist/assets/{index-DU9-flEo.js → index-ipqwPavs.js} +2 -2
- package/dashboard/dist/assets/{index-DU9-flEo.js.map → index-ipqwPavs.js.map} +1 -1
- package/dashboard/dist/assets/invoke-context-DrVyUEfs.js +2 -0
- package/dashboard/dist/assets/invoke-context-DrVyUEfs.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-CjwCbm3E.js → knowledge-XX6cW4Ob.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CjwCbm3E.js.map → knowledge-XX6cW4Ob.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-C-DMktCT.js → mcp-CH13hNH2.js} +2 -2
- package/dashboard/dist/assets/{mcp-C-DMktCT.js.map → mcp-CH13hNH2.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-KBeBNHb4.js → mcp-query-Duydd3JA.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-KBeBNHb4.js.map → mcp-query-Duydd3JA.js.map} +1 -1
- package/dashboard/dist/assets/{personas-B32sSCxm.js → personas-mDk4lLQS.js} +2 -2
- package/dashboard/dist/assets/{personas-B32sSCxm.js.map → personas-mDk4lLQS.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-DNLwDumm.js → pipelines-B5GcgCeF.js} +2 -2
- package/dashboard/dist/assets/{pipelines-DNLwDumm.js.map → pipelines-B5GcgCeF.js.map} +1 -1
- package/dashboard/dist/assets/resolver-payload-m9AGKZ3a.js +2 -0
- package/dashboard/dist/assets/resolver-payload-m9AGKZ3a.js.map +1 -0
- package/dashboard/dist/assets/{tasks-DrWSrjmj.js → tasks-CItmNsRp.js} +2 -2
- package/dashboard/dist/assets/{tasks-DrWSrjmj.js.map → tasks-CItmNsRp.js.map} +1 -1
- package/dashboard/dist/assets/{topics-DgzGU-vR.js → topics-CJYG7BX5.js} +2 -2
- package/dashboard/dist/assets/{topics-DgzGU-vR.js.map → topics-CJYG7BX5.js.map} +1 -1
- package/dashboard/dist/assets/useDebouncedValue-BrwgXcp8.js +2 -0
- package/dashboard/dist/assets/{useDebouncedValue-DG5JOj1F.js.map → useDebouncedValue-BrwgXcp8.js.map} +1 -1
- package/dashboard/dist/assets/{useFilterParams-crmlBw_l.js → useFilterParams-DBRw1Hcv.js} +2 -2
- package/dashboard/dist/assets/{useFilterParams-crmlBw_l.js.map → useFilterParams-DBRw1Hcv.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-CdgF7Jxo.js → useNamespace-CHty0HAS.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-CdgF7Jxo.js.map → useNamespace-CHty0HAS.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-BEaOtgSZ.js → useYamlActivityEvents-CoTsia7t.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-BEaOtgSZ.js.map → useYamlActivityEvents-CoTsia7t.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-BCWsItQ7.js → vendor-icons-DeBdvyKJ.js} +364 -144
- package/dashboard/dist/assets/vendor-icons-DeBdvyKJ.js.map +1 -0
- package/dashboard/dist/assets/vendor-query-DvWWuNIB.js +18 -0
- package/dashboard/dist/assets/vendor-query-DvWWuNIB.js.map +1 -0
- package/dashboard/dist/assets/{vendor-react-CdqEC2zv.js → vendor-react-_9U4mKFr.js} +2 -2
- package/dashboard/dist/assets/{vendor-react-CdqEC2zv.js.map → vendor-react-_9U4mKFr.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-YASUDPo0.js → yaml-workflows-CiWwx5rM.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-YASUDPo0.js.map → yaml-workflows-CiWwx5rM.js.map} +1 -1
- package/dashboard/dist/index.html +5 -5
- package/docs/api/http/workflows.md +52 -1
- package/docs/api/mcp/admin.md +40 -4
- package/docs/code-owned-configuration.md +1 -1
- package/docs/dashboard.md +45 -19
- package/docs/design-principles.md +6 -5
- package/docs/events.md +3 -1
- package/docs/faceted-routing.md +1 -1
- package/docs/hitl/form.md +12 -7
- package/docs/hitl/iframe.md +35 -6
- package/docs/hitl/invoke-form.md +101 -0
- package/docs/hitl/lookups.md +21 -0
- package/docs/hitl/x-lt-show-if.md +11 -0
- package/docs/hitl/x-lt-validation.md +50 -3
- package/docs/hitl/x-lt-widget.md +21 -0
- package/docs/hitl-guide.md +9 -4
- package/docs/iam.md +1 -1
- package/docs/scan-codes.md +13 -4
- package/docs/workflows.md +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/AgentsPage-CoCWGhUd.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-BSqOctep.js +0 -2
- package/dashboard/dist/assets/BulkAssignModal-DYbM_6Or.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-DPHMBmgz.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-DPHMBmgz.js.map +0 -1
- package/dashboard/dist/assets/HomePage-zK3VP_9g.js +0 -2
- package/dashboard/dist/assets/ListToolbar-DaxloPQc.js +0 -2
- package/dashboard/dist/assets/OperatorDashboard-vXG0TjAQ.js +0 -2
- package/dashboard/dist/assets/ProcessesListPage-CKKNpk4z.js +0 -2
- package/dashboard/dist/assets/RolesPage-eayR9H4f.js +0 -2
- package/dashboard/dist/assets/RunAsSelector-D7RPwaAl.js +0 -2
- package/dashboard/dist/assets/ScanStationPage-CuhqsBKo.js +0 -2
- package/dashboard/dist/assets/TasksListPage-DEQymbXC.js +0 -2
- package/dashboard/dist/assets/TimeAgo-CCxP67Rb.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-BttnEvOd.js +0 -2
- package/dashboard/dist/assets/WorkflowPill-CZvBbkn4.js.map +0 -1
- package/dashboard/dist/assets/WorkflowsDashboard-BRboBhlK.js +0 -2
- package/dashboard/dist/assets/WorkflowsOverview-CDFTYeQo.js +0 -2
- package/dashboard/dist/assets/YamlWorkflowsPage-Qq1jfcrH.js +0 -2
- package/dashboard/dist/assets/capabilities-RZpmDWoy.js +0 -2
- package/dashboard/dist/assets/escalation-columns-B4DG_5v8.js +0 -2
- package/dashboard/dist/assets/index-8LIpvj2e.js +0 -3
- package/dashboard/dist/assets/index-8LIpvj2e.js.map +0 -1
- package/dashboard/dist/assets/index-BaC3sbJH.js +0 -67
- package/dashboard/dist/assets/index-BaC3sbJH.js.map +0 -1
- package/dashboard/dist/assets/index-CD6jtND1.css +0 -1
- package/dashboard/dist/assets/index-CsIw-ZLk.js +0 -6
- package/dashboard/dist/assets/index-CsIw-ZLk.js.map +0 -1
- package/dashboard/dist/assets/index-CtJkvX9e.js +0 -5
- package/dashboard/dist/assets/index-CtJkvX9e.js.map +0 -1
- package/dashboard/dist/assets/index-IWufkcH9.js +0 -2
- package/dashboard/dist/assets/useDebouncedValue-DG5JOj1F.js +0 -2
- package/dashboard/dist/assets/vendor-icons-BCWsItQ7.js.map +0 -1
- package/dashboard/dist/assets/vendor-query-RUZdMvN6.js +0 -18
- package/dashboard/dist/assets/vendor-query-RUZdMvN6.js.map +0 -1
- package/dashboard/dist/assets/workflows-CX_oarls.js +0 -2
- package/dashboard/dist/assets/workflows-CX_oarls.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CronLabel-
|
|
1
|
+
{"version":3,"file":"CronLabel-CAD5UYDT.js","sources":["../../src/api/agent-subscriptions.ts","../../src/components/common/display/EventTopicPill.tsx","../../src/components/common/display/CronLabel.tsx"],"sourcesContent":["import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';\nimport { apiFetch } from './client';\n\nexport interface AgentSubscription {\n id: string;\n agent_id: string;\n topic: string;\n filter?: Record<string, any>;\n reaction_type: 'durable' | 'pipeline' | 'mcp_query' | 'capability';\n workflow_type?: string;\n pipeline_id?: string;\n mcp_prompt?: string;\n server_id?: string;\n tool_name?: string;\n input_mapping: Record<string, any>;\n execute_as?: string;\n enabled: boolean;\n created_at: string;\n updated_at: string;\n}\n\ninterface SubscriptionListResponse {\n subscriptions: AgentSubscription[];\n}\n\nexport function useAgentSubscriptions(agentId: string | null) {\n return useQuery<SubscriptionListResponse>({\n queryKey: ['agent-subscriptions', agentId],\n queryFn: () => apiFetch(`/agents/${agentId}/subscriptions`),\n enabled: !!agentId,\n });\n}\n\nexport function useCreateSubscription() {\n const queryClient = useQueryClient();\n return useMutation<AgentSubscription, Error, { agentId: string } & Partial<AgentSubscription>>({\n mutationFn: ({ agentId, ...body }) =>\n apiFetch(`/agents/${agentId}/subscriptions`, {\n method: 'POST',\n body: JSON.stringify(body),\n }),\n onSuccess: (_, vars) => {\n queryClient.invalidateQueries({ queryKey: ['agent-subscriptions', vars.agentId] });\n queryClient.invalidateQueries({ queryKey: ['agents'] });\n },\n });\n}\n\nexport function useUpdateSubscription() {\n const queryClient = useQueryClient();\n return useMutation<AgentSubscription, Error, { agentId: string; subId: string } & Partial<AgentSubscription>>({\n mutationFn: ({ agentId, subId, ...body }) =>\n apiFetch(`/agents/${agentId}/subscriptions/${subId}`, {\n method: 'PUT',\n body: JSON.stringify(body),\n }),\n onSuccess: (_, vars) => {\n queryClient.invalidateQueries({ queryKey: ['agent-subscriptions', vars.agentId] });\n },\n });\n}\n\nexport function useDeleteSubscription() {\n const queryClient = useQueryClient();\n return useMutation<{ deleted: boolean }, Error, { agentId: string; subId: string }>({\n mutationFn: ({ agentId, subId }) =>\n apiFetch(`/agents/${agentId}/subscriptions/${subId}`, { method: 'DELETE' }),\n onSuccess: (_, vars) => {\n queryClient.invalidateQueries({ queryKey: ['agent-subscriptions', vars.agentId] });\n queryClient.invalidateQueries({ queryKey: ['agents'] });\n },\n });\n}\n","import { Radio } from 'lucide-react';\n\n/**\n * Universal event topic display — Radio icon + monotype text.\n * Used everywhere an event subscription topic is rendered.\n * No border, no background — matches CronLabel's simplicity.\n */\n\nconst CATEGORY_COLORS: Record<string, string> = {\n task: 'text-status-active',\n workflow: 'text-accent',\n escalation: 'text-status-warning',\n activity: 'text-status-active',\n knowledge: 'text-accent',\n agent: 'text-status-success',\n app: 'text-status-error',\n milestone: 'text-accent',\n};\n\ninterface EventTopicPillProps {\n topic: string;\n}\n\nexport function EventTopicPill({ topic }: EventTopicPillProps) {\n const parts = topic.split('.');\n const category = parts[0] === 'system' ? parts[1] : parts[0];\n const iconColor = CATEGORY_COLORS[category] ?? 'text-text-quaternary';\n // Show category.action for system events (e.g., workflow.completed)\n const shortTopic = parts[0] === 'system'\n ? `${parts[1]}.${parts[parts.length - 1]}`\n : parts.slice(-2).join('.');\n\n return (\n <span className=\"inline-flex items-center gap-1 text-2xs font-mono text-text-secondary\">\n <Radio className={`w-2.5 h-2.5 shrink-0 ${iconColor}`} strokeWidth={1.5} />\n {shortTopic}\n </span>\n );\n}\n","import { Clock } from 'lucide-react';\n\ninterface CronLabelProps {\n cron: string;\n}\n\n/**\n * Universal cron expression display — Clock icon + monotype text.\n * Used everywhere a cron schedule is rendered.\n */\nexport function CronLabel({ cron }: CronLabelProps) {\n return (\n <span className=\"inline-flex items-center gap-1 text-2xs font-mono text-text-secondary\">\n <Clock className=\"w-2.5 h-2.5 shrink-0 text-text-quaternary\" strokeWidth={1.5} />\n {cron}\n </span>\n );\n}\n"],"names":["useAgentSubscriptions","agentId","useQuery","apiFetch","useCreateSubscription","queryClient","useQueryClient","useMutation","body","_","vars","useUpdateSubscription","subId","useDeleteSubscription","CATEGORY_COLORS","EventTopicPill","topic","parts","category","iconColor","shortTopic","jsxs","jsx","Radio","CronLabel","cron","Clock"],"mappings":"mKAyBO,SAASA,EAAsBC,EAAwB,CAC5D,OAAOC,EAAmC,CACxC,SAAU,CAAC,sBAAuBD,CAAO,EACzC,QAAS,IAAME,EAAS,WAAWF,CAAO,gBAAgB,EAC1D,QAAS,CAAC,CAACA,CAAA,CACZ,CACH,CAEO,SAASG,GAAwB,CACtC,MAAMC,EAAcC,EAAA,EACpB,OAAOC,EAAwF,CAC7F,WAAY,CAAC,CAAE,QAAAN,EAAS,GAAGO,KACzBL,EAAS,WAAWF,CAAO,iBAAkB,CAC3C,OAAQ,OACR,KAAM,KAAK,UAAUO,CAAI,CAAA,CAC1B,EACH,UAAW,CAACC,EAAGC,IAAS,CACtBL,EAAY,kBAAkB,CAAE,SAAU,CAAC,sBAAuBK,EAAK,OAAO,EAAG,EACjFL,EAAY,kBAAkB,CAAE,SAAU,CAAC,QAAQ,EAAG,CACxD,CAAA,CACD,CACH,CAEO,SAASM,GAAwB,CACtC,MAAMN,EAAcC,EAAA,EACpB,OAAOC,EAAuG,CAC5G,WAAY,CAAC,CAAE,QAAAN,EAAS,MAAAW,EAAO,GAAGJ,CAAA,IAChCL,EAAS,WAAWF,CAAO,kBAAkBW,CAAK,GAAI,CACpD,OAAQ,MACR,KAAM,KAAK,UAAUJ,CAAI,CAAA,CAC1B,EACH,UAAW,CAACC,EAAGC,IAAS,CACtBL,EAAY,kBAAkB,CAAE,SAAU,CAAC,sBAAuBK,EAAK,OAAO,EAAG,CACnF,CAAA,CACD,CACH,CAEO,SAASG,GAAwB,CACtC,MAAMR,EAAcC,EAAA,EACpB,OAAOC,EAA6E,CAClF,WAAY,CAAC,CAAE,QAAAN,EAAS,MAAAW,KACtBT,EAAS,WAAWF,CAAO,kBAAkBW,CAAK,GAAI,CAAE,OAAQ,SAAU,EAC5E,UAAW,CAACH,EAAGC,IAAS,CACtBL,EAAY,kBAAkB,CAAE,SAAU,CAAC,sBAAuBK,EAAK,OAAO,EAAG,EACjFL,EAAY,kBAAkB,CAAE,SAAU,CAAC,QAAQ,EAAG,CACxD,CAAA,CACD,CACH,CChEA,MAAMS,EAA0C,CAC9C,KAAY,qBACZ,SAAY,cACZ,WAAY,sBACZ,SAAY,qBACZ,UAAY,cACZ,MAAY,sBACZ,IAAY,oBACZ,UAAY,aACd,EAMO,SAASC,EAAe,CAAE,MAAAC,GAA8B,CAC7D,MAAMC,EAAQD,EAAM,MAAM,GAAG,EACvBE,EAAWD,EAAM,CAAC,IAAM,SAAWA,EAAM,CAAC,EAAIA,EAAM,CAAC,EACrDE,EAAYL,EAAgBI,CAAQ,GAAK,uBAEzCE,EAAaH,EAAM,CAAC,IAAM,SAC5B,GAAGA,EAAM,CAAC,CAAC,IAAIA,EAAMA,EAAM,OAAS,CAAC,CAAC,GACtCA,EAAM,MAAM,EAAE,EAAE,KAAK,GAAG,EAE5B,OACEI,EAAAA,KAAC,OAAA,CAAK,UAAU,wEACd,SAAA,CAAAC,MAACC,GAAM,UAAW,wBAAwBJ,CAAS,GAAI,YAAa,IAAK,EACxEC,CAAA,EACH,CAEJ,CC5BO,SAASI,EAAU,CAAE,KAAAC,GAAwB,CAClD,OACEJ,EAAAA,KAAC,OAAA,CAAK,UAAU,wEACd,SAAA,CAAAC,EAAAA,IAACI,EAAA,CAAM,UAAU,4CAA4C,YAAa,IAAK,EAC9ED,CAAA,EACH,CAEJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as m,j as e}from"./vendor-query-
|
|
2
|
-
//# sourceMappingURL=DataTable-
|
|
1
|
+
import{a as m,j as e}from"./vendor-query-DvWWuNIB.js";import{E}from"./EmptyState-DgC_6njU.js";function F(){const[r,t]=m.useState(null),[d,a]=m.useState(null),s=m.useCallback(i=>t(i),[]);return m.useEffect(()=>{if(!r||typeof ResizeObserver>"u")return;const i=new ResizeObserver(p=>{const o=p[0];o&&a(o.contentRect.width)});return i.observe(r),()=>i.disconnect()},[r]),[s,d]}const v={split:"hidden @split/table:table-cell",wall:"hidden @wall/table:table-cell"};function S(r){const t=r.some(i=>i.priority!==void 0),d=[],a=[],s=[];return r.forEach((i,p)=>{const o=i.priority??(t?2:p===0?1:2);o===1?d.push(i):o===3?s.push(i):a.push(i)}),{identity:d,meta:a,dropped:s}}const A=48;function C(){if(typeof window>"u"||typeof document>"u")return 16;const r=parseFloat(getComputedStyle(document.documentElement).fontSize);return Number.isFinite(r)&&r>0?r:16}function z({active:r,direction:t}){return e.jsx("svg",{className:`w-3 h-3 shrink-0 transition-all duration-150 ${r?"text-accent opacity-100":"opacity-0 group-hover/sorthead:opacity-40 text-text-tertiary"} ${r&&t==="asc"?"rotate-180":""}`,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2.5,children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"})})}function D({columns:r,sort:t,onSort:d}){const a=r.filter(s=>s.sortable);return a.length===0?null:e.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 border-b",children:[e.jsx("span",{className:"text-2xs font-semibold uppercase tracking-wide text-text-tertiary",children:"Sort"}),e.jsxs("select",{className:"select text-2xs py-1",value:(t==null?void 0:t.sort_by)??"",onChange:s=>s.target.value&&d(s.target.value),children:[!(t!=null&&t.sort_by)&&e.jsx("option",{value:"",disabled:!0,children:"Choose…"}),a.map(s=>e.jsx("option",{value:s.key,children:typeof s.label=="string"?s.label:s.key},s.key))]}),(t==null?void 0:t.sort_by)&&e.jsx("button",{onClick:()=>d(t.sort_by),className:"text-2xs text-text-tertiary hover:text-accent transition-colors tabular-nums",title:"Flip sort direction",children:t.order==="asc"?"↑ asc":"↓ desc"})]})}function M({columns:r,data:t,keyFn:d,onRowClick:a,activeRowKey:s,rowClassName:i,isLoading:p,emptyMessage:o="No records found",sort:h,onSort:b,inline:g,layout:N="auto",forceCardMode:k}){const[f,y]=F(),$=k??(y!==null&&y<A*C());if(p)return e.jsx("div",{className:"animate-pulse space-y-0",children:Array.from({length:5}).map((n,c)=>e.jsx("div",{className:"h-14 border-b last:border-b-0 px-6 flex items-center",children:e.jsx("div",{className:"h-3 bg-surface-sunken rounded w-full"})},c))});if(!t.length)return o?e.jsx(E,{title:o}):null;if($){const{identity:n,meta:c}=S(r),x=Math.max(0,n.findIndex(l=>!/(^|\s)w-\d/.test(l.className??"")));return e.jsxs("div",{ref:f,className:"@container/table",children:[b&&e.jsx(D,{columns:r,sort:h,onSort:b}),e.jsx("div",{className:"divide-y divide-surface-border",children:t.map((l,j)=>{const w=s!=null&&d(l)===s;return e.jsxs("div",{onClick:a?()=>a(l):void 0,className:`py-3 px-4 transition-colors duration-100 ${a?"cursor-pointer row-hover":""} ${w?"border-l-2 border-l-accent":""} ${i?i(l):""}`,children:[e.jsx("div",{className:"flex items-center gap-2 min-w-0",children:n.map((u,_)=>e.jsx("div",{className:_===x?"flex-1 min-w-0":"shrink-0",children:u.render(l,j)},u.key))}),c.length>0&&e.jsx("dl",{className:"mt-2 grid grid-cols-1 @dict-inline/table:grid-cols-[max-content_1fr] gap-x-6 gap-y-1 items-baseline",children:c.map(u=>e.jsxs(m.Fragment,{children:[e.jsx("dt",{className:"text-2xs font-semibold uppercase tracking-wide text-text-tertiary",children:u.label}),e.jsx("dd",{className:"text-sm min-w-0 break-words",children:u.render(l,j)})]},u.key))})]},d(l))})})]})}return e.jsx("div",{ref:f,className:"@container/table",children:e.jsxs("table",{className:`w-full ${N==="fixed"?"table-fixed":""}`,children:[e.jsx("thead",{children:e.jsx("tr",{className:"border-b",children:r.map(n=>{const c=n.sortable&&b,x=(h==null?void 0:h.sort_by)===n.key;return e.jsx("th",{onClick:c?()=>b(n.key):void 0,style:n.width?{width:n.width}:void 0,className:`${g?"":"sticky top-[var(--lt-sticky-top,60px)] z-10 "}bg-surface px-6 py-3 text-left text-2xs font-semibold uppercase tracking-widest text-text-tertiary whitespace-nowrap ${n.showFrom?v[n.showFrom]:""} ${n.className??""} ${c?"cursor-pointer select-none group/sorthead hover:text-text-secondary transition-colors":""}`,children:e.jsxs("span",{className:"inline-flex items-center gap-1",children:[n.label,c&&e.jsx(z,{active:x,direction:x?h.order:"desc"})]})},n.key)})})}),e.jsx("tbody",{children:t.map((n,c)=>{const x=s!=null&&d(n)===s;return e.jsx("tr",{onClick:a?()=>a(n):void 0,className:`group/row border-b last:border-b-0 transition-colors duration-100 ${a?"cursor-pointer row-hover":""} ${x?"border-l-2 border-l-accent":""} ${i?i(n):""}`,children:r.map(l=>e.jsx("td",{className:`px-6 py-2.5 text-sm overflow-hidden ${l.showFrom?v[l.showFrom]:""} ${l.className??""}`,children:l.render(n,c)},l.key))},d(n))})})]})})}export{M as D};
|
|
2
|
+
//# sourceMappingURL=DataTable-7C7riavf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable-DFPMeqyQ.js","sources":["../../src/hooks/useContainerWidth.ts","../../src/components/common/data/DataTable.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\n/**\n * Measures an element's content-box width via ResizeObserver — the hook form\n * of \"geometry follows the container\". Returns a CALLBACK ref (the observed\n * element can mount late, e.g. after a loading state) and the width: `null`\n * before the first measure and in environments without ResizeObserver\n * (jsdom), which callers treat as \"wide\".\n */\nexport function useContainerWidth<T extends HTMLElement>(): [(node: T | null) => void, number | null] {\n const [el, setEl] = useState<T | null>(null);\n const [width, setWidth] = useState<number | null>(null);\n\n const ref = useCallback((node: T | null) => setEl(node), []);\n\n useEffect(() => {\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) setWidth(entry.contentRect.width);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [el]);\n\n return [ref, width];\n}\n","import { Fragment, type ReactNode } from 'react';\nimport { EmptyState } from '../display/EmptyState';\nimport { useContainerWidth } from '../../../hooks/useContainerWidth';\nimport type { SortState } from '../../../hooks/useFilterParams';\n\nexport interface Column<T> {\n key: string;\n label: string | ReactNode;\n render: (row: T, index: number) => ReactNode;\n className?: string;\n /** CSS width for the column (e.g. \"40%\", \"12rem\") — set on the header cell,\n * which sizes the whole column under `layout=\"fixed\"`. */\n width?: string;\n /** If true, this column header is clickable and triggers onSort. */\n sortable?: boolean;\n /**\n * Card-fold behavior when the container drops below the @table threshold:\n * 1 = identity — always visible, forms the card's title line;\n * 2 = folds into the card body as a label/value pair (default);\n * 3 = dropped in card mode.\n */\n priority?: 1 | 2 | 3;\n /**\n * Table-mode disclosure: the column renders only when the table's\n * container clears the named threshold. The column budget governs the\n * floor; enrichment columns return with room.\n */\n showFrom?: 'split' | 'wall';\n}\n\n/* Literal class strings so Tailwind's scanner sees the container variants. */\nconst SHOW_FROM_CLASS: Record<NonNullable<Column<unknown>['showFrom']>, string> = {\n split: 'hidden @split/table:table-cell',\n wall: 'hidden @wall/table:table-cell',\n};\n\n/**\n * Split columns by card-fold priority. When NO column declares a priority,\n * the first column is the identity and the rest fold — the sane default for\n * every list page. When any column declares one, undeclared columns fold.\n * Exported for tests.\n */\nexport function partitionColumns<T>(columns: Column<T>[]): {\n identity: Column<T>[];\n meta: Column<T>[];\n dropped: Column<T>[];\n} {\n const anyDeclared = columns.some((c) => c.priority !== undefined);\n const identity: Column<T>[] = [];\n const meta: Column<T>[] = [];\n const dropped: Column<T>[] = [];\n columns.forEach((col, i) => {\n const priority = col.priority ?? (anyDeclared ? 2 : i === 0 ? 1 : 2);\n if (priority === 1) identity.push(col);\n else if (priority === 3) dropped.push(col);\n else meta.push(col);\n });\n return { identity, meta, dropped };\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n data: T[];\n keyFn: (row: T) => string;\n onRowClick?: (row: T) => void;\n /** Highlight the row whose keyFn matches this value. */\n activeRowKey?: string | null;\n /** Optional per-row class name for custom styling (e.g., engine vs worker tint). */\n rowClassName?: (row: T) => string;\n isLoading?: boolean;\n emptyMessage?: string;\n /** Current sort state — pass to show active sort indicator. */\n sort?: SortState;\n /** Called when a sortable column header is clicked. */\n onSort?: (column: string) => void;\n /** Disable the sticky header (useful when nested inside collapsible sections). */\n inline?: boolean;\n /**\n * `fixed` locks the table to its container width: columns with width\n * classes keep them, the rest share the remainder, and long content\n * truncates instead of widening the table. Use beside a slide panel where\n * the table must shrink as the panel expands.\n */\n layout?: 'auto' | 'fixed';\n /** Force console-card rendering regardless of measured width (tests). */\n forceCardMode?: boolean;\n}\n\n/** The @table threshold in rem — below this the table folds into cards. */\nconst CARD_FOLD_REM = 48;\n\nfunction rootFontPx(): number {\n if (typeof window === 'undefined' || typeof document === 'undefined') return 16;\n const size = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return Number.isFinite(size) && size > 0 ? size : 16;\n}\n\nfunction SortIcon({ active, direction }: { active: boolean; direction: 'asc' | 'desc' }) {\n return (\n <svg\n className={`w-3 h-3 shrink-0 transition-all duration-150 ${\n active ? 'text-accent opacity-100' : 'opacity-0 group-hover/sorthead:opacity-40 text-text-tertiary'\n } ${active && direction === 'asc' ? 'rotate-180' : ''}`}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n );\n}\n\n/** Compact sort control for card mode, where there is no header row. */\nfunction CardSortControl<T>({ columns, sort, onSort }: {\n columns: Column<T>[];\n sort?: SortState;\n onSort: (column: string) => void;\n}) {\n const sortable = columns.filter((c) => c.sortable);\n if (sortable.length === 0) return null;\n return (\n <div className=\"flex items-center gap-2 px-4 py-2 border-b\">\n <span className=\"text-2xs font-semibold uppercase tracking-wide text-text-tertiary\">Sort</span>\n <select\n className=\"select text-2xs py-1\"\n value={sort?.sort_by ?? ''}\n onChange={(e) => e.target.value && onSort(e.target.value)}\n >\n {!sort?.sort_by && <option value=\"\" disabled>Choose…</option>}\n {sortable.map((c) => (\n <option key={c.key} value={c.key}>{typeof c.label === 'string' ? c.label : c.key}</option>\n ))}\n </select>\n {sort?.sort_by && (\n <button\n onClick={() => onSort(sort.sort_by)}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors tabular-nums\"\n title=\"Flip sort direction\"\n >\n {sort.order === 'asc' ? '↑ asc' : '↓ desc'}\n </button>\n )}\n </div>\n );\n}\n\nexport function DataTable<T>({\n columns,\n data,\n keyFn,\n onRowClick,\n activeRowKey,\n rowClassName,\n isLoading,\n emptyMessage = 'No records found',\n sort,\n onSort,\n inline,\n layout = 'auto',\n forceCardMode,\n}: DataTableProps<T>) {\n const [wrapRef, width] = useContainerWidth<HTMLDivElement>();\n const cardMode = forceCardMode ?? (width !== null && width < CARD_FOLD_REM * rootFontPx());\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-0\">\n {Array.from({ length: 5 }).map((_, i) => (\n <div key={i} className=\"h-14 border-b last:border-b-0 px-6 flex items-center\">\n <div className=\"h-3 bg-surface-sunken rounded w-full\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (!data.length) {\n return emptyMessage ? <EmptyState title={emptyMessage} /> : null;\n }\n\n if (cardMode) {\n // A table row IS a dictionary: identity columns form the title line, the\n // rest fold into dense console-style pairs. Tables never scroll\n // horizontally — they fold.\n const { identity, meta } = partitionColumns(columns);\n // The title line: glyph/width-classed identity columns (status dots,\n // checkboxes, countdowns) hold their size; the first free-width identity\n // column (the description) flexes and truncates.\n const flexAt = Math.max(0, identity.findIndex((c) => !/(^|\\s)w-\\d/.test(c.className ?? '')));\n return (\n <div ref={wrapRef} className=\"@container/table\">\n {onSort && <CardSortControl columns={columns} sort={sort} onSort={onSort} />}\n <div className=\"divide-y divide-surface-border\">\n {data.map((row, index) => {\n const isActive = activeRowKey != null && keyFn(row) === activeRowKey;\n return (\n <div\n key={keyFn(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`py-3 px-4 transition-colors duration-100 ${\n onRowClick ? 'cursor-pointer row-hover' : ''\n } ${isActive ? 'border-l-2 border-l-accent' : ''} ${rowClassName ? rowClassName(row) : ''}`}\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n {identity.map((col, i) => (\n <div key={col.key} className={i === flexAt ? 'flex-1 min-w-0' : 'shrink-0'}>\n {col.render(row, index)}\n </div>\n ))}\n </div>\n {meta.length > 0 && (\n <dl className=\"mt-2 grid grid-cols-1 @dict-inline/table:grid-cols-[max-content_1fr] gap-x-6 gap-y-1 items-baseline\">\n {meta.map((col) => (\n <Fragment key={col.key}>\n <dt className=\"text-2xs font-semibold uppercase tracking-wide text-text-tertiary\">\n {col.label}\n </dt>\n <dd className=\"text-sm min-w-0 break-words\">{col.render(row, index)}</dd>\n </Fragment>\n ))}\n </dl>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n\n return (\n <div ref={wrapRef} className=\"@container/table\">\n <table className={`w-full ${layout === 'fixed' ? 'table-fixed' : ''}`}>\n <thead>\n <tr className=\"border-b\">\n {columns.map((col) => {\n const isSortable = col.sortable && onSort;\n const isActive = sort?.sort_by === col.key;\n\n return (\n <th\n key={col.key}\n onClick={isSortable ? () => onSort(col.key) : undefined}\n style={col.width ? { width: col.width } : undefined}\n className={`${inline ? '' : 'sticky top-[var(--lt-sticky-top,60px)] z-10 '}bg-surface px-6 py-3 text-left text-2xs font-semibold uppercase tracking-widest text-text-tertiary whitespace-nowrap ${col.showFrom ? SHOW_FROM_CLASS[col.showFrom] : ''} ${col.className ?? ''} ${\n isSortable ? 'cursor-pointer select-none group/sorthead hover:text-text-secondary transition-colors' : ''\n }`}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.label}\n {isSortable && (\n <SortIcon active={isActive} direction={isActive ? sort.order : 'desc'} />\n )}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {data.map((row, index) => {\n const isActive = activeRowKey != null && keyFn(row) === activeRowKey;\n return (\n <tr\n key={keyFn(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`group/row border-b last:border-b-0 transition-colors duration-100 ${\n onRowClick ? 'cursor-pointer row-hover' : ''\n } ${isActive ? 'border-l-2 border-l-accent' : ''} ${rowClassName ? rowClassName(row) : ''}`}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={`px-6 py-2.5 text-sm overflow-hidden ${col.showFrom ? SHOW_FROM_CLASS[col.showFrom] : ''} ${col.className ?? ''}`}\n >\n {col.render(row, index)}\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"names":["useContainerWidth","el","setEl","useState","width","setWidth","ref","useCallback","node","useEffect","observer","entries","entry","SHOW_FROM_CLASS","partitionColumns","columns","anyDeclared","c","identity","meta","dropped","col","i","priority","CARD_FOLD_REM","rootFontPx","size","SortIcon","active","direction","jsx","CardSortControl","sort","onSort","sortable","jsxs","e","DataTable","data","keyFn","onRowClick","activeRowKey","rowClassName","isLoading","emptyMessage","inline","layout","forceCardMode","wrapRef","cardMode","_","EmptyState","flexAt","row","index","isActive","Fragment","isSortable"],"mappings":"8FASO,SAASA,GAAsF,CACpG,KAAM,CAACC,EAAIC,CAAK,EAAIC,EAAAA,SAAmB,IAAI,EACrC,CAACC,EAAOC,CAAQ,EAAIF,EAAAA,SAAwB,IAAI,EAEhDG,EAAMC,EAAAA,YAAaC,GAAmBN,EAAMM,CAAI,EAAG,EAAE,EAE3DC,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACR,GAAM,OAAO,eAAmB,IAAa,OAClD,MAAMS,EAAW,IAAI,eAAgBC,GAAY,CAC/C,MAAMC,EAAQD,EAAQ,CAAC,EACnBC,GAAOP,EAASO,EAAM,YAAY,KAAK,CAC7C,CAAC,EACD,OAAAF,EAAS,QAAQT,CAAE,EACZ,IAAMS,EAAS,WAAA,CACxB,EAAG,CAACT,CAAE,CAAC,EAEA,CAACK,EAAKF,CAAK,CACpB,CCKA,MAAMS,EAA4E,CAChF,MAAO,iCACP,KAAM,+BACR,EAQO,SAASC,EAAoBC,EAIlC,CACA,MAAMC,EAAcD,EAAQ,KAAME,GAAMA,EAAE,WAAa,MAAS,EAC1DC,EAAwB,CAAA,EACxBC,EAAoB,CAAA,EACpBC,EAAuB,CAAA,EAC7B,OAAAL,EAAQ,QAAQ,CAACM,EAAKC,IAAM,CAC1B,MAAMC,EAAWF,EAAI,WAAaL,EAAc,EAAIM,IAAM,EAAI,EAAI,GAC9DC,IAAa,EAAGL,EAAS,KAAKG,CAAG,EAC5BE,IAAa,EAAGH,EAAQ,KAAKC,CAAG,EACpCF,EAAK,KAAKE,CAAG,CACpB,CAAC,EACM,CAAE,SAAAH,EAAU,KAAAC,EAAM,QAAAC,CAAA,CAC3B,CA+BA,MAAMI,EAAgB,GAEtB,SAASC,GAAqB,CAC5B,GAAI,OAAO,OAAW,KAAe,OAAO,SAAa,IAAa,MAAO,IAC7E,MAAMC,EAAO,WAAW,iBAAiB,SAAS,eAAe,EAAE,QAAQ,EAC3E,OAAO,OAAO,SAASA,CAAI,GAAKA,EAAO,EAAIA,EAAO,EACpD,CAEA,SAASC,EAAS,CAAE,OAAAC,EAAQ,UAAAC,GAA6D,CACvF,OACEC,EAAAA,IAAC,MAAA,CACC,UAAW,gDACTF,EAAS,0BAA4B,8DACvC,IAAIA,GAAUC,IAAc,MAAQ,aAAe,EAAE,GACrD,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAa,IAEb,eAAC,OAAA,CAAK,cAAc,QAAQ,eAAe,QAAQ,EAAE,gBAAA,CAAiB,CAAA,CAAA,CAG5E,CAGA,SAASE,EAAmB,CAAE,QAAAhB,EAAS,KAAAiB,EAAM,OAAAC,GAI1C,CACD,MAAMC,EAAWnB,EAAQ,OAAQE,GAAMA,EAAE,QAAQ,EACjD,OAAIiB,EAAS,SAAW,EAAU,KAEhCC,EAAAA,KAAC,MAAA,CAAI,UAAU,6CACb,SAAA,CAAAL,EAAAA,IAAC,OAAA,CAAK,UAAU,oEAAoE,SAAA,OAAI,EACxFK,EAAAA,KAAC,SAAA,CACC,UAAU,uBACV,OAAOH,GAAA,YAAAA,EAAM,UAAW,GACxB,SAAWI,GAAMA,EAAE,OAAO,OAASH,EAAOG,EAAE,OAAO,KAAK,EAEvD,SAAA,CAAA,EAACJ,GAAA,MAAAA,EAAM,UAAWF,EAAAA,IAAC,SAAA,CAAO,MAAM,GAAG,SAAQ,GAAC,SAAA,SAAA,CAAO,EACnDI,EAAS,IAAKjB,GACba,EAAAA,IAAC,SAAA,CAAmB,MAAOb,EAAE,IAAM,gBAAOA,EAAE,OAAU,SAAWA,EAAE,MAAQA,EAAE,GAAA,EAAhEA,EAAE,GAAkE,CAClF,CAAA,CAAA,CAAA,GAEFe,GAAA,YAAAA,EAAM,UACLF,EAAAA,IAAC,SAAA,CACC,QAAS,IAAMG,EAAOD,EAAK,OAAO,EAClC,UAAU,+EACV,MAAM,sBAEL,SAAAA,EAAK,QAAU,MAAQ,QAAU,QAAA,CAAA,CACpC,EAEJ,CAEJ,CAEO,SAASK,EAAa,CAC3B,QAAAtB,EACA,KAAAuB,EACA,MAAAC,EACA,WAAAC,EACA,aAAAC,EACA,aAAAC,EACA,UAAAC,EACA,aAAAC,EAAe,mBACf,KAAAZ,EACA,OAAAC,EACA,OAAAY,EACA,OAAAC,EAAS,OACT,cAAAC,CACF,EAAsB,CACpB,KAAM,CAACC,EAAS5C,CAAK,EAAIJ,EAAA,EACnBiD,EAAWF,IAAkB3C,IAAU,MAAQA,EAAQoB,EAAgBC,KAE7E,GAAIkB,EACF,OACEb,EAAAA,IAAC,MAAA,CAAI,UAAU,0BACZ,SAAA,MAAM,KAAK,CAAE,OAAQ,CAAA,CAAG,EAAE,IAAI,CAACoB,EAAG5B,IACjCQ,EAAAA,IAAC,MAAA,CAAY,UAAU,uDACrB,SAAAA,EAAAA,IAAC,MAAA,CAAI,UAAU,sCAAA,CAAuC,CAAA,EAD9CR,CAEV,CACD,CAAA,CACH,EAIJ,GAAI,CAACgB,EAAK,OACR,OAAOM,EAAed,EAAAA,IAACqB,EAAA,CAAW,MAAOP,EAAc,EAAK,KAG9D,GAAIK,EAAU,CAIZ,KAAM,CAAE,SAAA/B,EAAU,KAAAC,GAASL,EAAiBC,CAAO,EAI7CqC,EAAS,KAAK,IAAI,EAAGlC,EAAS,UAAWD,GAAM,CAAC,aAAa,KAAKA,EAAE,WAAa,EAAE,CAAC,CAAC,EAC3F,OACEkB,EAAAA,KAAC,MAAA,CAAI,IAAKa,EAAS,UAAU,mBAC1B,SAAA,CAAAf,GAAUH,EAAAA,IAACC,EAAA,CAAgB,QAAAhB,EAAkB,KAAAiB,EAAY,OAAAC,EAAgB,EAC1EH,MAAC,OAAI,UAAU,iCACZ,WAAK,IAAI,CAACuB,EAAKC,IAAU,CACxB,MAAMC,EAAWd,GAAgB,MAAQF,EAAMc,CAAG,IAAMZ,EACxD,OACEN,EAAAA,KAAC,MAAA,CAEC,QAASK,EAAa,IAAMA,EAAWa,CAAG,EAAI,OAC9C,UAAW,4CACTb,EAAa,2BAA6B,EAC5C,IAAIe,EAAW,6BAA+B,EAAE,IAAIb,EAAeA,EAAaW,CAAG,EAAI,EAAE,GAEzF,SAAA,CAAAvB,EAAAA,IAAC,MAAA,CAAI,UAAU,kCACZ,SAAAZ,EAAS,IAAI,CAACG,EAAKC,IAClBQ,EAAAA,IAAC,MAAA,CAAkB,UAAWR,IAAM8B,EAAS,iBAAmB,WAC7D,SAAA/B,EAAI,OAAOgC,EAAKC,CAAK,CAAA,EADdjC,EAAI,GAEd,CACD,CAAA,CACH,EACCF,EAAK,OAAS,GACbW,EAAAA,IAAC,KAAA,CAAG,UAAU,sGACX,SAAAX,EAAK,IAAKE,GACTc,EAAAA,KAACqB,EAAAA,SAAA,CACC,SAAA,CAAA1B,EAAAA,IAAC,KAAA,CAAG,UAAU,oEACX,SAAAT,EAAI,MACP,EACAS,MAAC,MAAG,UAAU,8BAA+B,WAAI,OAAOuB,EAAKC,CAAK,CAAA,CAAE,CAAA,GAJvDjC,EAAI,GAKnB,CACD,CAAA,CACH,CAAA,CAAA,EAvBGkB,EAAMc,CAAG,CAAA,CA2BpB,CAAC,CAAA,CACH,CAAA,EACF,CAEJ,CAEA,OACEvB,EAAAA,IAAC,MAAA,CAAI,IAAKkB,EAAS,UAAU,mBAC3B,SAAAb,EAAAA,KAAC,QAAA,CAAM,UAAW,UAAUW,IAAW,QAAU,cAAgB,EAAE,GACjE,SAAA,CAAAhB,EAAAA,IAAC,QAAA,CACC,eAAC,KAAA,CAAG,UAAU,WACX,SAAAf,EAAQ,IAAKM,GAAQ,CACpB,MAAMoC,EAAapC,EAAI,UAAYY,EAC7BsB,GAAWvB,GAAA,YAAAA,EAAM,WAAYX,EAAI,IAEvC,OACES,EAAAA,IAAC,KAAA,CAEC,QAAS2B,EAAa,IAAMxB,EAAOZ,EAAI,GAAG,EAAI,OAC9C,MAAOA,EAAI,MAAQ,CAAE,MAAOA,EAAI,OAAU,OAC1C,UAAW,GAAGwB,EAAS,GAAK,8CAA8C,wHAAwHxB,EAAI,SAAWR,EAAgBQ,EAAI,QAAQ,EAAI,EAAE,IAAIA,EAAI,WAAa,EAAE,IACxQoC,EAAa,wFAA0F,EACzG,GAEA,SAAAtB,EAAAA,KAAC,OAAA,CAAK,UAAU,iCACb,SAAA,CAAAd,EAAI,MACJoC,SACE9B,EAAA,CAAS,OAAQ4B,EAAU,UAAWA,EAAWvB,EAAK,MAAQ,MAAA,CAAQ,CAAA,CAAA,CAE3E,CAAA,EAZKX,EAAI,GAAA,CAef,CAAC,EACH,EACF,QACC,QAAA,CACE,SAAAiB,EAAK,IAAI,CAACe,EAAKC,IAAU,CACxB,MAAMC,EAAWd,GAAgB,MAAQF,EAAMc,CAAG,IAAMZ,EACxD,OACAX,EAAAA,IAAC,KAAA,CAEC,QAASU,EAAa,IAAMA,EAAWa,CAAG,EAAI,OAC9C,UAAW,qEACTb,EAAa,2BAA6B,EAC5C,IAAIe,EAAW,6BAA+B,EAAE,IAAIb,EAAeA,EAAaW,CAAG,EAAI,EAAE,GAExF,SAAAtC,EAAQ,IAAKM,GACZS,EAAAA,IAAC,KAAA,CAEC,UAAW,uCAAuCT,EAAI,SAAWR,EAAgBQ,EAAI,QAAQ,EAAI,EAAE,IAAIA,EAAI,WAAa,EAAE,GAEzH,SAAAA,EAAI,OAAOgC,EAAKC,CAAK,CAAA,EAHjBjC,EAAI,GAAA,CAKZ,CAAA,EAbIkB,EAAMc,CAAG,CAAA,CAgBlB,CAAC,CAAA,CACH,CAAA,CAAA,CACF,CAAA,CACF,CAEJ"}
|
|
1
|
+
{"version":3,"file":"DataTable-7C7riavf.js","sources":["../../src/hooks/useContainerWidth.ts","../../src/components/common/data/DataTable.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\n\n/**\n * Measures an element's content-box width via ResizeObserver — the hook form\n * of \"geometry follows the container\". Returns a CALLBACK ref (the observed\n * element can mount late, e.g. after a loading state) and the width: `null`\n * before the first measure and in environments without ResizeObserver\n * (jsdom), which callers treat as \"wide\".\n */\nexport function useContainerWidth<T extends HTMLElement>(): [(node: T | null) => void, number | null] {\n const [el, setEl] = useState<T | null>(null);\n const [width, setWidth] = useState<number | null>(null);\n\n const ref = useCallback((node: T | null) => setEl(node), []);\n\n useEffect(() => {\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) setWidth(entry.contentRect.width);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [el]);\n\n return [ref, width];\n}\n","import { Fragment, type ReactNode } from 'react';\nimport { EmptyState } from '../display/EmptyState';\nimport { useContainerWidth } from '../../../hooks/useContainerWidth';\nimport type { SortState } from '../../../hooks/useFilterParams';\n\nexport interface Column<T> {\n key: string;\n label: string | ReactNode;\n render: (row: T, index: number) => ReactNode;\n className?: string;\n /** CSS width for the column (e.g. \"40%\", \"12rem\") — set on the header cell,\n * which sizes the whole column under `layout=\"fixed\"`. */\n width?: string;\n /** If true, this column header is clickable and triggers onSort. */\n sortable?: boolean;\n /**\n * Card-fold behavior when the container drops below the @table threshold:\n * 1 = identity — always visible, forms the card's title line;\n * 2 = folds into the card body as a label/value pair (default);\n * 3 = dropped in card mode.\n */\n priority?: 1 | 2 | 3;\n /**\n * Table-mode disclosure: the column renders only when the table's\n * container clears the named threshold. The column budget governs the\n * floor; enrichment columns return with room.\n */\n showFrom?: 'split' | 'wall';\n}\n\n/* Literal class strings so Tailwind's scanner sees the container variants. */\nconst SHOW_FROM_CLASS: Record<NonNullable<Column<unknown>['showFrom']>, string> = {\n split: 'hidden @split/table:table-cell',\n wall: 'hidden @wall/table:table-cell',\n};\n\n/**\n * Split columns by card-fold priority. When NO column declares a priority,\n * the first column is the identity and the rest fold — the sane default for\n * every list page. When any column declares one, undeclared columns fold.\n * Exported for tests.\n */\nexport function partitionColumns<T>(columns: Column<T>[]): {\n identity: Column<T>[];\n meta: Column<T>[];\n dropped: Column<T>[];\n} {\n const anyDeclared = columns.some((c) => c.priority !== undefined);\n const identity: Column<T>[] = [];\n const meta: Column<T>[] = [];\n const dropped: Column<T>[] = [];\n columns.forEach((col, i) => {\n const priority = col.priority ?? (anyDeclared ? 2 : i === 0 ? 1 : 2);\n if (priority === 1) identity.push(col);\n else if (priority === 3) dropped.push(col);\n else meta.push(col);\n });\n return { identity, meta, dropped };\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n data: T[];\n keyFn: (row: T) => string;\n onRowClick?: (row: T) => void;\n /** Highlight the row whose keyFn matches this value. */\n activeRowKey?: string | null;\n /** Optional per-row class name for custom styling (e.g., engine vs worker tint). */\n rowClassName?: (row: T) => string;\n isLoading?: boolean;\n emptyMessage?: string;\n /** Current sort state — pass to show active sort indicator. */\n sort?: SortState;\n /** Called when a sortable column header is clicked. */\n onSort?: (column: string) => void;\n /** Disable the sticky header (useful when nested inside collapsible sections). */\n inline?: boolean;\n /**\n * `fixed` locks the table to its container width: columns with width\n * classes keep them, the rest share the remainder, and long content\n * truncates instead of widening the table. Use beside a slide panel where\n * the table must shrink as the panel expands.\n */\n layout?: 'auto' | 'fixed';\n /** Force console-card rendering regardless of measured width (tests). */\n forceCardMode?: boolean;\n}\n\n/** The @table threshold in rem — below this the table folds into cards. */\nconst CARD_FOLD_REM = 48;\n\nfunction rootFontPx(): number {\n if (typeof window === 'undefined' || typeof document === 'undefined') return 16;\n const size = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return Number.isFinite(size) && size > 0 ? size : 16;\n}\n\nfunction SortIcon({ active, direction }: { active: boolean; direction: 'asc' | 'desc' }) {\n return (\n <svg\n className={`w-3 h-3 shrink-0 transition-all duration-150 ${\n active ? 'text-accent opacity-100' : 'opacity-0 group-hover/sorthead:opacity-40 text-text-tertiary'\n } ${active && direction === 'asc' ? 'rotate-180' : ''}`}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n );\n}\n\n/** Compact sort control for card mode, where there is no header row. */\nfunction CardSortControl<T>({ columns, sort, onSort }: {\n columns: Column<T>[];\n sort?: SortState;\n onSort: (column: string) => void;\n}) {\n const sortable = columns.filter((c) => c.sortable);\n if (sortable.length === 0) return null;\n return (\n <div className=\"flex items-center gap-2 px-4 py-2 border-b\">\n <span className=\"text-2xs font-semibold uppercase tracking-wide text-text-tertiary\">Sort</span>\n <select\n className=\"select text-2xs py-1\"\n value={sort?.sort_by ?? ''}\n onChange={(e) => e.target.value && onSort(e.target.value)}\n >\n {!sort?.sort_by && <option value=\"\" disabled>Choose…</option>}\n {sortable.map((c) => (\n <option key={c.key} value={c.key}>{typeof c.label === 'string' ? c.label : c.key}</option>\n ))}\n </select>\n {sort?.sort_by && (\n <button\n onClick={() => onSort(sort.sort_by)}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors tabular-nums\"\n title=\"Flip sort direction\"\n >\n {sort.order === 'asc' ? '↑ asc' : '↓ desc'}\n </button>\n )}\n </div>\n );\n}\n\nexport function DataTable<T>({\n columns,\n data,\n keyFn,\n onRowClick,\n activeRowKey,\n rowClassName,\n isLoading,\n emptyMessage = 'No records found',\n sort,\n onSort,\n inline,\n layout = 'auto',\n forceCardMode,\n}: DataTableProps<T>) {\n const [wrapRef, width] = useContainerWidth<HTMLDivElement>();\n const cardMode = forceCardMode ?? (width !== null && width < CARD_FOLD_REM * rootFontPx());\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-0\">\n {Array.from({ length: 5 }).map((_, i) => (\n <div key={i} className=\"h-14 border-b last:border-b-0 px-6 flex items-center\">\n <div className=\"h-3 bg-surface-sunken rounded w-full\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (!data.length) {\n return emptyMessage ? <EmptyState title={emptyMessage} /> : null;\n }\n\n if (cardMode) {\n // A table row IS a dictionary: identity columns form the title line, the\n // rest fold into dense console-style pairs. Tables never scroll\n // horizontally — they fold.\n const { identity, meta } = partitionColumns(columns);\n // The title line: glyph/width-classed identity columns (status dots,\n // checkboxes, countdowns) hold their size; the first free-width identity\n // column (the description) flexes and truncates.\n const flexAt = Math.max(0, identity.findIndex((c) => !/(^|\\s)w-\\d/.test(c.className ?? '')));\n return (\n <div ref={wrapRef} className=\"@container/table\">\n {onSort && <CardSortControl columns={columns} sort={sort} onSort={onSort} />}\n <div className=\"divide-y divide-surface-border\">\n {data.map((row, index) => {\n const isActive = activeRowKey != null && keyFn(row) === activeRowKey;\n return (\n <div\n key={keyFn(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`py-3 px-4 transition-colors duration-100 ${\n onRowClick ? 'cursor-pointer row-hover' : ''\n } ${isActive ? 'border-l-2 border-l-accent' : ''} ${rowClassName ? rowClassName(row) : ''}`}\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n {identity.map((col, i) => (\n <div key={col.key} className={i === flexAt ? 'flex-1 min-w-0' : 'shrink-0'}>\n {col.render(row, index)}\n </div>\n ))}\n </div>\n {meta.length > 0 && (\n <dl className=\"mt-2 grid grid-cols-1 @dict-inline/table:grid-cols-[max-content_1fr] gap-x-6 gap-y-1 items-baseline\">\n {meta.map((col) => (\n <Fragment key={col.key}>\n <dt className=\"text-2xs font-semibold uppercase tracking-wide text-text-tertiary\">\n {col.label}\n </dt>\n <dd className=\"text-sm min-w-0 break-words\">{col.render(row, index)}</dd>\n </Fragment>\n ))}\n </dl>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n\n return (\n <div ref={wrapRef} className=\"@container/table\">\n <table className={`w-full ${layout === 'fixed' ? 'table-fixed' : ''}`}>\n <thead>\n <tr className=\"border-b\">\n {columns.map((col) => {\n const isSortable = col.sortable && onSort;\n const isActive = sort?.sort_by === col.key;\n\n return (\n <th\n key={col.key}\n onClick={isSortable ? () => onSort(col.key) : undefined}\n style={col.width ? { width: col.width } : undefined}\n className={`${inline ? '' : 'sticky top-[var(--lt-sticky-top,60px)] z-10 '}bg-surface px-6 py-3 text-left text-2xs font-semibold uppercase tracking-widest text-text-tertiary whitespace-nowrap ${col.showFrom ? SHOW_FROM_CLASS[col.showFrom] : ''} ${col.className ?? ''} ${\n isSortable ? 'cursor-pointer select-none group/sorthead hover:text-text-secondary transition-colors' : ''\n }`}\n >\n <span className=\"inline-flex items-center gap-1\">\n {col.label}\n {isSortable && (\n <SortIcon active={isActive} direction={isActive ? sort.order : 'desc'} />\n )}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {data.map((row, index) => {\n const isActive = activeRowKey != null && keyFn(row) === activeRowKey;\n return (\n <tr\n key={keyFn(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`group/row border-b last:border-b-0 transition-colors duration-100 ${\n onRowClick ? 'cursor-pointer row-hover' : ''\n } ${isActive ? 'border-l-2 border-l-accent' : ''} ${rowClassName ? rowClassName(row) : ''}`}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={`px-6 py-2.5 text-sm overflow-hidden ${col.showFrom ? SHOW_FROM_CLASS[col.showFrom] : ''} ${col.className ?? ''}`}\n >\n {col.render(row, index)}\n </td>\n ))}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"names":["useContainerWidth","el","setEl","useState","width","setWidth","ref","useCallback","node","useEffect","observer","entries","entry","SHOW_FROM_CLASS","partitionColumns","columns","anyDeclared","c","identity","meta","dropped","col","i","priority","CARD_FOLD_REM","rootFontPx","size","SortIcon","active","direction","jsx","CardSortControl","sort","onSort","sortable","jsxs","e","DataTable","data","keyFn","onRowClick","activeRowKey","rowClassName","isLoading","emptyMessage","inline","layout","forceCardMode","wrapRef","cardMode","_","EmptyState","flexAt","row","index","isActive","Fragment","isSortable"],"mappings":"8FASO,SAASA,GAAsF,CACpG,KAAM,CAACC,EAAIC,CAAK,EAAIC,EAAAA,SAAmB,IAAI,EACrC,CAACC,EAAOC,CAAQ,EAAIF,EAAAA,SAAwB,IAAI,EAEhDG,EAAMC,EAAAA,YAAaC,GAAmBN,EAAMM,CAAI,EAAG,EAAE,EAE3DC,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACR,GAAM,OAAO,eAAmB,IAAa,OAClD,MAAMS,EAAW,IAAI,eAAgBC,GAAY,CAC/C,MAAMC,EAAQD,EAAQ,CAAC,EACnBC,GAAOP,EAASO,EAAM,YAAY,KAAK,CAC7C,CAAC,EACD,OAAAF,EAAS,QAAQT,CAAE,EACZ,IAAMS,EAAS,WAAA,CACxB,EAAG,CAACT,CAAE,CAAC,EAEA,CAACK,EAAKF,CAAK,CACpB,CCKA,MAAMS,EAA4E,CAChF,MAAO,iCACP,KAAM,+BACR,EAQO,SAASC,EAAoBC,EAIlC,CACA,MAAMC,EAAcD,EAAQ,KAAME,GAAMA,EAAE,WAAa,MAAS,EAC1DC,EAAwB,CAAA,EACxBC,EAAoB,CAAA,EACpBC,EAAuB,CAAA,EAC7B,OAAAL,EAAQ,QAAQ,CAACM,EAAKC,IAAM,CAC1B,MAAMC,EAAWF,EAAI,WAAaL,EAAc,EAAIM,IAAM,EAAI,EAAI,GAC9DC,IAAa,EAAGL,EAAS,KAAKG,CAAG,EAC5BE,IAAa,EAAGH,EAAQ,KAAKC,CAAG,EACpCF,EAAK,KAAKE,CAAG,CACpB,CAAC,EACM,CAAE,SAAAH,EAAU,KAAAC,EAAM,QAAAC,CAAA,CAC3B,CA+BA,MAAMI,EAAgB,GAEtB,SAASC,GAAqB,CAC5B,GAAI,OAAO,OAAW,KAAe,OAAO,SAAa,IAAa,MAAO,IAC7E,MAAMC,EAAO,WAAW,iBAAiB,SAAS,eAAe,EAAE,QAAQ,EAC3E,OAAO,OAAO,SAASA,CAAI,GAAKA,EAAO,EAAIA,EAAO,EACpD,CAEA,SAASC,EAAS,CAAE,OAAAC,EAAQ,UAAAC,GAA6D,CACvF,OACEC,EAAAA,IAAC,MAAA,CACC,UAAW,gDACTF,EAAS,0BAA4B,8DACvC,IAAIA,GAAUC,IAAc,MAAQ,aAAe,EAAE,GACrD,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAa,IAEb,eAAC,OAAA,CAAK,cAAc,QAAQ,eAAe,QAAQ,EAAE,gBAAA,CAAiB,CAAA,CAAA,CAG5E,CAGA,SAASE,EAAmB,CAAE,QAAAhB,EAAS,KAAAiB,EAAM,OAAAC,GAI1C,CACD,MAAMC,EAAWnB,EAAQ,OAAQE,GAAMA,EAAE,QAAQ,EACjD,OAAIiB,EAAS,SAAW,EAAU,KAEhCC,EAAAA,KAAC,MAAA,CAAI,UAAU,6CACb,SAAA,CAAAL,EAAAA,IAAC,OAAA,CAAK,UAAU,oEAAoE,SAAA,OAAI,EACxFK,EAAAA,KAAC,SAAA,CACC,UAAU,uBACV,OAAOH,GAAA,YAAAA,EAAM,UAAW,GACxB,SAAWI,GAAMA,EAAE,OAAO,OAASH,EAAOG,EAAE,OAAO,KAAK,EAEvD,SAAA,CAAA,EAACJ,GAAA,MAAAA,EAAM,UAAWF,EAAAA,IAAC,SAAA,CAAO,MAAM,GAAG,SAAQ,GAAC,SAAA,SAAA,CAAO,EACnDI,EAAS,IAAKjB,GACba,EAAAA,IAAC,SAAA,CAAmB,MAAOb,EAAE,IAAM,gBAAOA,EAAE,OAAU,SAAWA,EAAE,MAAQA,EAAE,GAAA,EAAhEA,EAAE,GAAkE,CAClF,CAAA,CAAA,CAAA,GAEFe,GAAA,YAAAA,EAAM,UACLF,EAAAA,IAAC,SAAA,CACC,QAAS,IAAMG,EAAOD,EAAK,OAAO,EAClC,UAAU,+EACV,MAAM,sBAEL,SAAAA,EAAK,QAAU,MAAQ,QAAU,QAAA,CAAA,CACpC,EAEJ,CAEJ,CAEO,SAASK,EAAa,CAC3B,QAAAtB,EACA,KAAAuB,EACA,MAAAC,EACA,WAAAC,EACA,aAAAC,EACA,aAAAC,EACA,UAAAC,EACA,aAAAC,EAAe,mBACf,KAAAZ,EACA,OAAAC,EACA,OAAAY,EACA,OAAAC,EAAS,OACT,cAAAC,CACF,EAAsB,CACpB,KAAM,CAACC,EAAS5C,CAAK,EAAIJ,EAAA,EACnBiD,EAAWF,IAAkB3C,IAAU,MAAQA,EAAQoB,EAAgBC,KAE7E,GAAIkB,EACF,OACEb,EAAAA,IAAC,MAAA,CAAI,UAAU,0BACZ,SAAA,MAAM,KAAK,CAAE,OAAQ,CAAA,CAAG,EAAE,IAAI,CAACoB,EAAG5B,IACjCQ,EAAAA,IAAC,MAAA,CAAY,UAAU,uDACrB,SAAAA,EAAAA,IAAC,MAAA,CAAI,UAAU,sCAAA,CAAuC,CAAA,EAD9CR,CAEV,CACD,CAAA,CACH,EAIJ,GAAI,CAACgB,EAAK,OACR,OAAOM,EAAed,EAAAA,IAACqB,EAAA,CAAW,MAAOP,EAAc,EAAK,KAG9D,GAAIK,EAAU,CAIZ,KAAM,CAAE,SAAA/B,EAAU,KAAAC,GAASL,EAAiBC,CAAO,EAI7CqC,EAAS,KAAK,IAAI,EAAGlC,EAAS,UAAWD,GAAM,CAAC,aAAa,KAAKA,EAAE,WAAa,EAAE,CAAC,CAAC,EAC3F,OACEkB,EAAAA,KAAC,MAAA,CAAI,IAAKa,EAAS,UAAU,mBAC1B,SAAA,CAAAf,GAAUH,EAAAA,IAACC,EAAA,CAAgB,QAAAhB,EAAkB,KAAAiB,EAAY,OAAAC,EAAgB,EAC1EH,MAAC,OAAI,UAAU,iCACZ,WAAK,IAAI,CAACuB,EAAKC,IAAU,CACxB,MAAMC,EAAWd,GAAgB,MAAQF,EAAMc,CAAG,IAAMZ,EACxD,OACEN,EAAAA,KAAC,MAAA,CAEC,QAASK,EAAa,IAAMA,EAAWa,CAAG,EAAI,OAC9C,UAAW,4CACTb,EAAa,2BAA6B,EAC5C,IAAIe,EAAW,6BAA+B,EAAE,IAAIb,EAAeA,EAAaW,CAAG,EAAI,EAAE,GAEzF,SAAA,CAAAvB,EAAAA,IAAC,MAAA,CAAI,UAAU,kCACZ,SAAAZ,EAAS,IAAI,CAACG,EAAKC,IAClBQ,EAAAA,IAAC,MAAA,CAAkB,UAAWR,IAAM8B,EAAS,iBAAmB,WAC7D,SAAA/B,EAAI,OAAOgC,EAAKC,CAAK,CAAA,EADdjC,EAAI,GAEd,CACD,CAAA,CACH,EACCF,EAAK,OAAS,GACbW,EAAAA,IAAC,KAAA,CAAG,UAAU,sGACX,SAAAX,EAAK,IAAKE,GACTc,EAAAA,KAACqB,EAAAA,SAAA,CACC,SAAA,CAAA1B,EAAAA,IAAC,KAAA,CAAG,UAAU,oEACX,SAAAT,EAAI,MACP,EACAS,MAAC,MAAG,UAAU,8BAA+B,WAAI,OAAOuB,EAAKC,CAAK,CAAA,CAAE,CAAA,GAJvDjC,EAAI,GAKnB,CACD,CAAA,CACH,CAAA,CAAA,EAvBGkB,EAAMc,CAAG,CAAA,CA2BpB,CAAC,CAAA,CACH,CAAA,EACF,CAEJ,CAEA,OACEvB,EAAAA,IAAC,MAAA,CAAI,IAAKkB,EAAS,UAAU,mBAC3B,SAAAb,EAAAA,KAAC,QAAA,CAAM,UAAW,UAAUW,IAAW,QAAU,cAAgB,EAAE,GACjE,SAAA,CAAAhB,EAAAA,IAAC,QAAA,CACC,eAAC,KAAA,CAAG,UAAU,WACX,SAAAf,EAAQ,IAAKM,GAAQ,CACpB,MAAMoC,EAAapC,EAAI,UAAYY,EAC7BsB,GAAWvB,GAAA,YAAAA,EAAM,WAAYX,EAAI,IAEvC,OACES,EAAAA,IAAC,KAAA,CAEC,QAAS2B,EAAa,IAAMxB,EAAOZ,EAAI,GAAG,EAAI,OAC9C,MAAOA,EAAI,MAAQ,CAAE,MAAOA,EAAI,OAAU,OAC1C,UAAW,GAAGwB,EAAS,GAAK,8CAA8C,wHAAwHxB,EAAI,SAAWR,EAAgBQ,EAAI,QAAQ,EAAI,EAAE,IAAIA,EAAI,WAAa,EAAE,IACxQoC,EAAa,wFAA0F,EACzG,GAEA,SAAAtB,EAAAA,KAAC,OAAA,CAAK,UAAU,iCACb,SAAA,CAAAd,EAAI,MACJoC,SACE9B,EAAA,CAAS,OAAQ4B,EAAU,UAAWA,EAAWvB,EAAK,MAAQ,MAAA,CAAQ,CAAA,CAAA,CAE3E,CAAA,EAZKX,EAAI,GAAA,CAef,CAAC,EACH,EACF,QACC,QAAA,CACE,SAAAiB,EAAK,IAAI,CAACe,EAAKC,IAAU,CACxB,MAAMC,EAAWd,GAAgB,MAAQF,EAAMc,CAAG,IAAMZ,EACxD,OACAX,EAAAA,IAAC,KAAA,CAEC,QAASU,EAAa,IAAMA,EAAWa,CAAG,EAAI,OAC9C,UAAW,qEACTb,EAAa,2BAA6B,EAC5C,IAAIe,EAAW,6BAA+B,EAAE,IAAIb,EAAeA,EAAaW,CAAG,EAAI,EAAE,GAExF,SAAAtC,EAAQ,IAAKM,GACZS,EAAAA,IAAC,KAAA,CAEC,UAAW,uCAAuCT,EAAI,SAAWR,EAAgBQ,EAAI,QAAQ,EAAI,EAAE,IAAIA,EAAI,WAAa,EAAE,GAEzH,SAAAA,EAAI,OAAOgC,EAAKC,CAAK,CAAA,EAHjBjC,EAAI,GAAA,CAKZ,CAAA,EAbIkB,EAAMc,CAAG,CAAA,CAgBlB,CAAC,CAAA,CACH,CAAA,CAAA,CACF,CAAA,CACF,CAEJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as n,j as l}from"./vendor-query-
|
|
2
|
-
//# sourceMappingURL=DropZone-
|
|
1
|
+
import{a as n,j as l}from"./vendor-query-DvWWuNIB.js";import{bS as D}from"./vendor-icons-DeBdvyKJ.js";function v(r,c){const p=c.split(",").map(e=>e.trim());for(const e of p)if(e.startsWith(".")&&r.name.toLowerCase().endsWith(e.toLowerCase())||e===r.type||e.endsWith("/*")&&r.type.startsWith(e.slice(0,-1)))return!0;return!1}function j({onDrop:r,children:c,label:p="Drop files here",accept:e,disabled:i}){const[u,f]=n.useState(!1),s=n.useRef(0),d=n.useCallback(t=>{t.preventDefault(),t.stopPropagation(),s.current++,i||f(!0)},[i]),g=n.useCallback(t=>{t.preventDefault(),t.stopPropagation(),s.current--,s.current<=0&&(s.current=0,f(!1))},[]),x=n.useCallback(t=>{t.preventDefault(),t.stopPropagation()},[]),h=n.useCallback(t=>{if(t.preventDefault(),t.stopPropagation(),s.current=0,f(!1),i)return;const o=Array.from(t.dataTransfer.files);if(console.debug("[DropZone] dropped files:",o.map(a=>({name:a.name,type:a.type,size:a.size}))),e){const a=o.filter(m=>v(m,e));console.debug("[DropZone] after accept filter:",a.length,"of",o.length),a.length?r(a):console.warn("[DropZone] no files matched accept pattern:",e)}else o.length&&r(o)},[r,e,i]);return l.jsxs("div",{className:"relative",onDragEnter:d,onDragLeave:g,onDragOver:x,onDrop:h,children:[c,u&&l.jsx("div",{className:"fixed inset-0 z-50 flex flex-col items-center justify-center transition-opacity pointer-events-none",style:{background:"rgba(var(--color-surface-rgb, 255 255 255) / 0.85)"},children:l.jsxs("div",{className:"flex flex-col items-center gap-2 px-6 py-5 rounded-xl border border-dashed border-text-quaternary/40",children:[l.jsx(D,{className:"w-8 h-8 text-text-tertiary"}),l.jsx("p",{className:"text-xs text-text-secondary",children:p})]})})]})}export{j as D};
|
|
2
|
+
//# sourceMappingURL=DropZone-XlcY8hEH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropZone-
|
|
1
|
+
{"version":3,"file":"DropZone-XlcY8hEH.js","sources":["../../src/components/common/DropZone.tsx"],"sourcesContent":["import { useState, useCallback, useRef, type DragEvent, type ReactNode } from 'react';\nimport { UploadCloud } from 'lucide-react';\n\ninterface DropZoneProps {\n onDrop: (files: File[]) => void;\n children: ReactNode;\n label?: string;\n accept?: string;\n disabled?: boolean;\n}\n\nfunction matchesAccept(file: File, accept: string): boolean {\n const patterns = accept.split(',').map(s => s.trim());\n for (const pattern of patterns) {\n if (pattern.startsWith('.') && file.name.toLowerCase().endsWith(pattern.toLowerCase())) return true;\n if (pattern === file.type) return true;\n if (pattern.endsWith('/*') && file.type.startsWith(pattern.slice(0, -1))) return true;\n }\n return false;\n}\n\nexport function DropZone({ onDrop, children, label = 'Drop files here', accept, disabled }: DropZoneProps) {\n const [dragOver, setDragOver] = useState(false);\n const dragCounter = useRef(0);\n\n const handleDragEnter = useCallback((e: DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounter.current++;\n if (!disabled) setDragOver(true);\n }, [disabled]);\n\n const handleDragLeave = useCallback((e: DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounter.current--;\n if (dragCounter.current <= 0) {\n dragCounter.current = 0;\n setDragOver(false);\n }\n }, []);\n\n const handleDragOver = useCallback((e: DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n }, []);\n\n const handleDrop = useCallback((e: DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounter.current = 0;\n setDragOver(false);\n if (disabled) return;\n\n const files = Array.from(e.dataTransfer.files);\n console.debug('[DropZone] dropped files:', files.map(f => ({ name: f.name, type: f.type, size: f.size })));\n\n if (accept) {\n const filtered = files.filter(f => matchesAccept(f, accept));\n console.debug('[DropZone] after accept filter:', filtered.length, 'of', files.length);\n if (filtered.length) onDrop(filtered);\n else console.warn('[DropZone] no files matched accept pattern:', accept);\n } else {\n if (files.length) onDrop(files);\n }\n }, [onDrop, accept, disabled]);\n\n return (\n <div\n className=\"relative\"\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n >\n {children}\n {dragOver && (\n <div className=\"fixed inset-0 z-50 flex flex-col items-center justify-center transition-opacity pointer-events-none\"\n style={{ background: 'rgba(var(--color-surface-rgb, 255 255 255) / 0.85)' }}\n >\n <div className=\"flex flex-col items-center gap-2 px-6 py-5 rounded-xl border border-dashed border-text-quaternary/40\">\n <UploadCloud className=\"w-8 h-8 text-text-tertiary\" />\n <p className=\"text-xs text-text-secondary\">{label}</p>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"names":["matchesAccept","file","accept","patterns","s","pattern","DropZone","onDrop","children","label","disabled","dragOver","setDragOver","useState","dragCounter","useRef","handleDragEnter","useCallback","e","handleDragLeave","handleDragOver","handleDrop","files","f","filtered","jsxs","jsx","UploadCloud"],"mappings":"sGAWA,SAASA,EAAcC,EAAYC,EAAyB,CAC1D,MAAMC,EAAWD,EAAO,MAAM,GAAG,EAAE,IAAIE,GAAKA,EAAE,MAAM,EACpD,UAAWC,KAAWF,EAGpB,GAFIE,EAAQ,WAAW,GAAG,GAAKJ,EAAK,KAAK,YAAA,EAAc,SAASI,EAAQ,YAAA,CAAa,GACjFA,IAAYJ,EAAK,MACjBI,EAAQ,SAAS,IAAI,GAAKJ,EAAK,KAAK,WAAWI,EAAQ,MAAM,EAAG,EAAE,CAAC,EAAG,MAAO,GAEnF,MAAO,EACT,CAEO,SAASC,EAAS,CAAE,OAAAC,EAAQ,SAAAC,EAAU,MAAAC,EAAQ,kBAAmB,OAAAP,EAAQ,SAAAQ,GAA2B,CACzG,KAAM,CAACC,EAAUC,CAAW,EAAIC,EAAAA,SAAS,EAAK,EACxCC,EAAcC,EAAAA,OAAO,CAAC,EAEtBC,EAAkBC,cAAaC,GAAiB,CACpDA,EAAE,eAAA,EACFA,EAAE,gBAAA,EACFJ,EAAY,UACPJ,GAAUE,EAAY,EAAI,CACjC,EAAG,CAACF,CAAQ,CAAC,EAEPS,EAAkBF,cAAaC,GAAiB,CACpDA,EAAE,eAAA,EACFA,EAAE,gBAAA,EACFJ,EAAY,UACRA,EAAY,SAAW,IACzBA,EAAY,QAAU,EACtBF,EAAY,EAAK,EAErB,EAAG,CAAA,CAAE,EAECQ,EAAiBH,cAAaC,GAAiB,CACnDA,EAAE,eAAA,EACFA,EAAE,gBAAA,CACJ,EAAG,CAAA,CAAE,EAECG,EAAaJ,cAAaC,GAAiB,CAK/C,GAJAA,EAAE,eAAA,EACFA,EAAE,gBAAA,EACFJ,EAAY,QAAU,EACtBF,EAAY,EAAK,EACbF,EAAU,OAEd,MAAMY,EAAQ,MAAM,KAAKJ,EAAE,aAAa,KAAK,EAG7C,GAFA,QAAQ,MAAM,4BAA6BI,EAAM,IAAIC,IAAM,CAAE,KAAMA,EAAE,KAAM,KAAMA,EAAE,KAAM,KAAMA,EAAE,IAAA,EAAO,CAAC,EAErGrB,EAAQ,CACV,MAAMsB,EAAWF,EAAM,UAAYtB,EAAcuB,EAAGrB,CAAM,CAAC,EAC3D,QAAQ,MAAM,kCAAmCsB,EAAS,OAAQ,KAAMF,EAAM,MAAM,EAChFE,EAAS,OAAQjB,EAAOiB,CAAQ,EAC/B,QAAQ,KAAK,8CAA+CtB,CAAM,CACzE,MACMoB,EAAM,QAAQf,EAAOe,CAAK,CAElC,EAAG,CAACf,EAAQL,EAAQQ,CAAQ,CAAC,EAE7B,OACEe,EAAAA,KAAC,MAAA,CACC,UAAU,WACV,YAAaT,EACb,YAAaG,EACb,WAAYC,EACZ,OAAQC,EAEP,SAAA,CAAAb,EACAG,GACCe,EAAAA,IAAC,MAAA,CAAI,UAAU,sGACb,MAAO,CAAE,WAAY,oDAAA,EAErB,SAAAD,EAAAA,KAAC,MAAA,CAAI,UAAU,uGACb,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAY,UAAU,4BAAA,CAA6B,EACpDD,EAAAA,IAAC,IAAA,CAAE,UAAU,8BAA+B,SAAAjB,CAAA,CAAM,CAAA,CAAA,CACpD,CAAA,CAAA,CACF,CAAA,CAAA,CAIR"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as s,j as n}from"./vendor-query-
|
|
2
|
-
//# sourceMappingURL=ElapsedCell-
|
|
1
|
+
import{a as s,j as n}from"./vendor-query-DvWWuNIB.js";import{a6 as p,ar as f}from"./index-CIwlVfvV.js";function D({startDate:r,endDate:o,isLive:e}){const[l,c]=s.useState(Date.now);s.useEffect(()=>{if(!e)return;const u=setInterval(()=>c(Date.now()),1e3);return()=>clearInterval(u)},[e]);const i=new Date(r).getTime(),m=o?new Date(o).getTime():l,t=Math.max(0,m-i),a=p(t),x=s.useMemo(()=>[{label:"ms",value:String(t)},{label:"sec",value:`${(t/1e3).toFixed(3)}s`},{label:"text",value:a}],[t,a]);return n.jsx(f,{options:x,children:n.jsx("span",{className:`text-xs ${e?"text-status-active":"text-text-tertiary"}`,children:a})})}export{D as E};
|
|
2
|
+
//# sourceMappingURL=ElapsedCell-CwO4ufd0.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElapsedCell-
|
|
1
|
+
{"version":3,"file":"ElapsedCell-CwO4ufd0.js","sources":["../../src/components/common/display/ElapsedCell.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { formatDurationCompact } from '../../../lib/format';\nimport { DateTooltip } from './DateTooltip';\n\ninterface ElapsedCellProps {\n startDate: string;\n endDate?: string | null;\n isLive?: boolean;\n}\n\n/**\n * Elapsed duration cell for data tables.\n * Shows compact duration with hover tooltip. Ticks every second when live.\n */\nexport function ElapsedCell({ startDate, endDate, isLive }: ElapsedCellProps) {\n const [now, setNow] = useState(Date.now);\n\n useEffect(() => {\n if (!isLive) return;\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [isLive]);\n\n const start = new Date(startDate).getTime();\n const end = endDate ? new Date(endDate).getTime() : now;\n const ms = Math.max(0, end - start);\n\n const display = formatDurationCompact(ms);\n\n const options = useMemo(() => [\n { label: 'ms', value: String(ms) },\n { label: 'sec', value: `${(ms / 1000).toFixed(3)}s` },\n { label: 'text', value: display },\n ], [ms, display]);\n\n return (\n <DateTooltip options={options}>\n <span className={`text-xs ${isLive ? 'text-status-active' : 'text-text-tertiary'}`}>\n {display}\n </span>\n </DateTooltip>\n );\n}\n"],"names":["ElapsedCell","startDate","endDate","isLive","now","setNow","useState","useEffect","id","start","end","ms","display","formatDurationCompact","options","useMemo","jsx","DateTooltip"],"mappings":"uGAcO,SAASA,EAAY,CAAE,UAAAC,EAAW,QAAAC,EAAS,OAAAC,GAA4B,CAC5E,KAAM,CAACC,EAAKC,CAAM,EAAIC,EAAAA,SAAS,KAAK,GAAG,EAEvCC,EAAAA,UAAU,IAAM,CACd,GAAI,CAACJ,EAAQ,OACb,MAAMK,EAAK,YAAY,IAAMH,EAAO,KAAK,IAAA,CAAK,EAAG,GAAI,EACrD,MAAO,IAAM,cAAcG,CAAE,CAC/B,EAAG,CAACL,CAAM,CAAC,EAEX,MAAMM,EAAQ,IAAI,KAAKR,CAAS,EAAE,QAAA,EAC5BS,EAAMR,EAAU,IAAI,KAAKA,CAAO,EAAE,UAAYE,EAC9CO,EAAK,KAAK,IAAI,EAAGD,EAAMD,CAAK,EAE5BG,EAAUC,EAAsBF,CAAE,EAElCG,EAAUC,EAAAA,QAAQ,IAAM,CAC5B,CAAE,MAAO,KAAM,MAAO,OAAOJ,CAAE,CAAA,EAC/B,CAAE,MAAO,MAAO,MAAO,IAAIA,EAAK,KAAM,QAAQ,CAAC,CAAC,GAAA,EAChD,CAAE,MAAO,OAAQ,MAAOC,CAAA,CAAQ,EAC/B,CAACD,EAAIC,CAAO,CAAC,EAEhB,OACEI,EAAAA,IAACC,EAAA,CAAY,QAAAH,EACX,SAAAE,EAAAA,IAAC,OAAA,CAAK,UAAW,WAAWb,EAAS,qBAAuB,oBAAoB,GAC7E,SAAAS,CAAA,CACH,EACF,CAEJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as e}from"./vendor-query-
|
|
2
|
-
//# sourceMappingURL=EmptyState-
|
|
1
|
+
import{j as e}from"./vendor-query-DvWWuNIB.js";function x({title:a="No data",description:t,icon:s}){return e.jsxs("div",{className:"flex flex-col items-center justify-center py-20",children:[s&&e.jsx("div",{className:"w-12 h-12 rounded-full bg-accent/[0.06] flex items-center justify-center mb-4",children:e.jsx(s,{className:"w-5 h-5 text-accent/50"})}),e.jsx("p",{className:"text-sm text-text-secondary",children:a}),t&&e.jsx("p",{className:"text-xs text-text-tertiary mt-1",children:t})]})}export{x as E};
|
|
2
|
+
//# sourceMappingURL=EmptyState-DgC_6njU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState-
|
|
1
|
+
{"version":3,"file":"EmptyState-DgC_6njU.js","sources":["../../src/components/common/display/EmptyState.tsx"],"sourcesContent":["import type { LucideIcon } from 'lucide-react';\n\nexport function EmptyState({\n title = 'No data',\n description,\n icon: Icon,\n}: {\n title?: string;\n description?: string;\n icon?: LucideIcon;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20\">\n {Icon && (\n <div className=\"w-12 h-12 rounded-full bg-accent/[0.06] flex items-center justify-center mb-4\">\n <Icon className=\"w-5 h-5 text-accent/50\" />\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">{title}</p>\n {description && (\n <p className=\"text-xs text-text-tertiary mt-1\">{description}</p>\n )}\n </div>\n );\n}\n"],"names":["EmptyState","title","description","Icon","jsxs","jsx"],"mappings":"+CAEO,SAASA,EAAW,CACzB,MAAAC,EAAQ,UACR,YAAAC,EACA,KAAMC,CACR,EAIG,CACD,OACEC,EAAAA,KAAC,MAAA,CAAI,UAAU,kDACZ,SAAA,CAAAD,GACCE,EAAAA,IAAC,OAAI,UAAU,gFACb,eAACF,EAAA,CAAK,UAAU,yBAAyB,CAAA,CAC3C,EAEFE,EAAAA,IAAC,IAAA,CAAE,UAAU,8BAA+B,SAAAJ,EAAM,EACjDC,GACCG,EAAAA,IAAC,IAAA,CAAE,UAAU,kCAAmC,SAAAH,CAAA,CAAY,CAAA,EAEhE,CAEJ"}
|
package/dashboard/dist/assets/{EntityTimelinePanel-CtzpJ-j2.js → EntityTimelinePanel-CkF-oW-m.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as c,u as U,j as t}from"./vendor-query-
|
|
2
|
-
//# sourceMappingURL=EntityTimelinePanel-
|
|
1
|
+
import{a as c,u as U,j as t}from"./vendor-query-DvWWuNIB.js";import{ao as Y,m as J,_ as H,aY as ee,I as te,z as se,a6 as W,aZ as B}from"./index-CIwlVfvV.js";import{ai as re,aj as ae,b as ne,X as le,bH as oe}from"./vendor-icons-DeBdvyKJ.js";import{L as ce}from"./vendor-react-_9U4mKFr.js";function z(e){return JSON.stringify(e,(s,r)=>r&&typeof r=="object"&&!Array.isArray(r)?Object.fromEntries(Object.entries(r).sort(([n],[a])=>n<a?-1:1)):r)}function k(e,s=Date.now()){const r=Math.ceil(s/6e4)*6e4;return{from:new Date(r-e*36e5).toISOString(),to:new Date(r).toISOString()}}function ie(e){const[s,r]=c.useState(()=>k(e));return c.useEffect(()=>{r(k(e));let n;const a=()=>{n=setTimeout(()=>{r(k(e)),a()},6e4-Date.now()%6e4+250)};return a(),()=>clearTimeout(n)},[e]),s}function G(e,s){return s instanceof Y&&s.status===403?!1:e<2}function P(e,s){return U({queryKey:["escAggregate",e?z(e):"off"],queryFn:()=>J("/escalations/aggregate-by-facets",{method:"POST",body:JSON.stringify(e)}),staleTime:15e3,enabled:((s==null?void 0:s.enabled)??!0)&&!!e,retry:G})}function X(e){return J("/escalations/timeline-by-facet",{method:"POST",body:JSON.stringify(e)})}function de(e,s){return U({queryKey:["escTimeline",e?z(e):"off"],queryFn:()=>X(e),staleTime:15e3,enabled:!!e,retry:G})}function ue(e){return e instanceof Y&&e.status===403}const D=["rgb(var(--lt-cat-1))","rgb(var(--lt-cat-2))","rgb(var(--lt-cat-3))","rgb(var(--lt-cat-4))","rgb(var(--lt-cat-5))","rgb(var(--lt-cat-6))","rgb(var(--lt-cat-7))","rgb(var(--lt-cat-8))","rgb(var(--lt-cat-9))","rgb(var(--lt-cat-10))"],xe="∅",me="no subtype";function fe(e){return e??xe}function Se(e){return e??me}function A(e){const s=new Map;for(const a of e)s.set(a.label,(s.get(a.label)??0)+a.weight);const r=[...s.entries()].sort(([a,d],[u,l])=>l-d||(a<u?-1:1)),n=new Map;return r.forEach(([a],d)=>n.set(a,D[d%D.length])),n}function Ee(e){return A(e.map(s=>({label:fe(s.subtype),weight:s.dwellSeconds??s.count??0})))}function pe(e,{enabled:s=!0,periodHours:r=720}={}){const n=ie(r),a=e.entity?{entity:e.entity}:e.role?{roles:[e.role]}:null,d=s&&!!a,u=P(d?{query:a,groupBy:{state:!0},measure:{kind:"dwell",window:n},orderBy:[{field:"dwellSeconds",direction:"desc"}]}:null),l=P(d?{query:a,groupBy:{state:!0},measure:{kind:"membership"}}:null),{data:x}=H({enabled:d}),g=c.useMemo(()=>new Set(((x==null?void 0:x.roles)??[]).filter(i=>i.entity_state_source==="subtype").map(i=>i.role)),[x]),j=c.useMemo(()=>{var i,p;return A([...(((i=u.data)==null?void 0:i.groups)??[]).filter(f=>(f.dwellSeconds??0)>0),...((p=l.data)==null?void 0:p.groups)??[]].map(f=>({label:f.state??"—",weight:f.dwellSeconds??f.count??0})))},[u.data,l.data]);return c.useCallback(i=>{const p=g.has(i.role)?i.subtype??i.role:i.role;return j.get(p)},[j,g])}const v=e=>e.subtype??e.role,M=100;function ke({facetKey:e,value:s,role:r,entity:n,intervalColor:a}){var I,_;ee();const{closePanel:d}=te(),u=n?{query:{entity:n}}:r?{query:{roles:[r]}}:{},{data:l,error:x,isLoading:g}=de({facet:{key:e,value:s},...u,order:"desc",limit:M}),j=pe({entity:n,role:r},{enabled:!a}),i=a??j,p=[...(l==null?void 0:l.intervals)??[]].reverse(),[f,T]=c.useState([]),[C,L]=c.useState(!1),[N,O]=c.useState(!1),[q,S]=c.useState(null),E=l?`${s}|${l.intervals.length}|${((I=l.intervals[0])==null?void 0:I.startedAt)??""}|${((_=l.intervals[l.intervals.length-1])==null?void 0:_.startedAt)??""}`:null,$=c.useRef(null);c.useEffect(()=>{E!==$.current&&($.current=E,T([]),L((l==null?void 0:l.overflow)??!1),S(null))},[E,l]);const m=[...f,...p],[h,Z]=c.useState("desc"),y=h==="desc"?[...m].reverse():m,Q=o=>h==="desc"?{prev:y[o],next:y[o-1]}:{prev:y[o-1],next:y[o]},K=A(m.map(o=>({label:v(o),weight:o.durationSeconds}))),F=async()=>{var b;const o=(b=m[0])==null?void 0:b.startedAt;if(!(!o||N)){O(!0),S(null);try{const w=await X({facet:{key:e,value:s},...u,order:"desc",before:o,limit:M});T(V=>[...[...w.intervals].reverse(),...V]),L(w.overflow)}catch(w){S(w.message)}finally{O(!1)}}};return t.jsxs("div",{className:"h-full flex flex-col",children:[t.jsxs("div",{className:"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40",children:[t.jsxs("div",{className:"min-w-0",children:[t.jsxs("p",{className:"text-2xs font-semibold uppercase tracking-widest text-text-tertiary",children:["Timeline · ",t.jsx("span",{className:"normal-case font-mono",children:e})]}),t.jsx("p",{className:"text-sm font-mono text-text-primary truncate mt-0.5",title:s,children:s}),m.length>0&&t.jsxs("p",{className:"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5",children:[t.jsxs("span",{children:[m.length," interval",m.length===1?"":"s"]}),t.jsx("span",{className:"h-2.5 w-px bg-surface-border"}),t.jsx("button",{onClick:()=>Z(o=>o==="desc"?"asc":"desc"),className:"inline-flex items-center gap-0.5 icon-link",title:h==="desc"?"Newest first — click for oldest first":"Oldest first — click for newest first","data-testid":"timeline-sort-toggle",children:h==="desc"?t.jsxs(t.Fragment,{children:[t.jsx(re,{className:"w-2.5 h-2.5"})," newest first"]}):t.jsxs(t.Fragment,{children:[t.jsx(ae,{className:"w-2.5 h-2.5"})," oldest first"]})})]})]}),t.jsxs("span",{className:"flex items-center gap-1 mt-0.5 shrink-0 ml-2",children:[n&&t.jsx(he,{entity:n,value:s}),t.jsx(ce,{to:se(e,s),className:"icon-link",title:`Search every queue for ${e} ${s}`,"data-testid":"timeline-facet-search",children:t.jsx(ne,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:()=>d("entity-timeline"),className:"icon-link",children:t.jsx(le,{className:"w-4 h-4"})})]})]}),t.jsx("div",{className:"flex-1 overflow-y-auto px-5 py-4",children:g?t.jsx("p",{className:"text-2xs text-text-tertiary italic",children:"Loading timeline…"}):ue(x)?t.jsx("p",{className:"text-2xs text-text-tertiary",children:"Timeline requires full read access to this station's queue."}):x?t.jsx("p",{className:"text-2xs text-status-error",children:x.message}):m.length===0?t.jsx("p",{className:"text-2xs text-text-quaternary",children:"No escalation has carried this value."}):t.jsxs("div",{className:"relative",children:[h==="asc"&&t.jsx(R,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"top"}),t.jsx("div",{className:"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border"}),t.jsx("div",{className:"space-y-0",children:y.map((o,b)=>t.jsxs(c.Fragment,{children:[b>0&&t.jsx(ye,{...Q(b)}),t.jsx(be,{interval:o,color:i(o)??K.get(v(o))})]},`${o.startedAt}-${b}`))}),h==="desc"&&t.jsx(R,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"bottom"})]})})]})}function R({hasEarlier:e,loading:s,error:r,onLoad:n,edge:a}){return!e&&!r?null:t.jsxs("div",{className:`${a==="top"?"pb-2":"pt-2"} pl-5`,children:[e&&t.jsx("button",{onClick:n,disabled:s,className:"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40",children:s?"Loading older…":"Load older"}),r&&t.jsx("p",{className:"text-2xs text-status-error mt-1",children:r})]})}function he({entity:e,value:s}){const[r,n]=c.useState(!1),a=c.useRef(void 0);c.useEffect(()=>()=>clearTimeout(a.current),[]);const d=async()=>{const u=`${window.location.origin}/trends?lens=${encodeURIComponent(e)}&entity=${encodeURIComponent(s)}`;await navigator.clipboard.writeText(u),n(!0),clearTimeout(a.current),a.current=setTimeout(()=>n(!1),1500)};return t.jsxs("span",{className:"flex items-center gap-1",children:[r&&t.jsx("span",{className:"text-2xs text-text-quaternary",children:"copied"}),t.jsx("button",{onClick:d,className:"icon-link",title:"Copy link to this timeline",children:t.jsx(oe,{className:"w-3.5 h-3.5"})})]})}function be({interval:e,color:s}){return t.jsxs("div",{className:"relative flex gap-3 py-2",children:[t.jsx("span",{className:"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10",style:{backgroundColor:s??"rgb(var(--lt-surface-border))"},title:v(e)}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsxs("div",{className:"flex items-baseline gap-2",children:[t.jsxs("span",{className:"text-xs font-mono text-text-primary truncate",children:[e.role,e.subtype?t.jsxs("span",{className:"text-text-tertiary",children:[" · ",e.subtype]}):null]}),t.jsx("span",{className:"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0",children:W(e.durationSeconds*1e3)})]}),t.jsxs("p",{className:"text-2xs font-mono text-text-tertiary mt-0.5",children:[B(e.startedAt)," → ",e.endedAt?B(e.endedAt):"now"]})]})]})}function ye({prev:e,next:s}){if(!e.endedAt)return null;const r=Date.parse(s.startedAt)-Date.parse(e.endedAt);return r<=1e3?null:t.jsxs("div",{className:"relative flex items-center gap-3 py-1",children:[t.jsx("span",{className:"w-[9px] shrink-0"}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"}),t.jsxs("span",{className:"text-2xs text-text-quaternary shrink-0",children:["untracked · ",W(r)]}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"})]})}export{ke as E,fe as a,ie as b,A as c,Ee as d,ue as i,Se as s,P as u};
|
|
2
|
+
//# sourceMappingURL=EntityTimelinePanel-CkF-oW-m.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityTimelinePanel-CtzpJ-j2.js","sources":["../../src/api/escalation-analytics.ts","../../src/pages/operations/mix-colors.ts","../../src/pages/operations/useEntityStateColors.ts","../../src/components/escalation/EntityTimelinePanel.tsx"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Escalation analytics hooks — grouped membership/dwell aggregates and\n// per-entity interval timelines (POST /escalations/aggregate-by-facets and\n// /timeline-by-facet). Every escalation is one open interval\n// [created_at, ended_at); these hooks read that time-series.\n//\n// Query-key discipline: inputs are canonicalized (key-sorted stringify) and\n// windows are quantized to the minute — the window end rounds UP to the next\n// minute boundary so (a) keys stay stable across renders within the minute\n// and (b) `to >= now` keeps the query in the server's now-anchored cache\n// (invalidated on escalation writes) instead of the 10-minute past cache.\n// Refresh is push-driven (useEscalationAnalyticsEvents) — never polling.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { apiFetch, ApiError } from './client';\n\n// Wire types — mirrors of types/analytics.ts on the server.\n\nexport interface AnalyticsWindow {\n from: string;\n to: string;\n}\n\nexport type AggregateMeasure =\n | { kind: 'membership'; asOf?: string }\n | { kind: 'dwell'; window: AnalyticsWindow };\n\nexport interface AnalyticsFilter {\n role?: string;\n roles?: string[];\n /** Entity facet key — resolves server-side to every role declaring it (the entity's system). */\n entity?: string;\n facets?: Record<string, unknown>;\n /** Case-insensitive facet-value prefix match, keyed by facet. */\n prefix?: Record<string, string>;\n /**\n * Exact facet-value match via the text projection (metadata->>key = value),\n * keyed by facet. Compares as text, so a categorical value the aggregate\n * returned (bool/enum/number, all surfaced as text) round-trips as a filter.\n */\n equals?: Record<string, string>;\n block?: Record<string, unknown>[];\n /** Row carries ANY of these facet sets (max 200) — the positive mirror of block. */\n anyOf?: Record<string, unknown>[];\n range?: { facet: string; op: '<' | '<=' | '>' | '>=' | '='; value: number }[];\n exists?: string[];\n}\n\nexport interface AggregateByFacetsInput {\n query: AnalyticsFilter;\n groupBy: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[]; state?: boolean };\n measure: AggregateMeasure;\n distinctBy?: string;\n orderBy?: { field: string; direction?: 'asc' | 'desc' }[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateRow {\n role?: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n state?: string;\n count?: number;\n dwellSeconds?: number;\n sampleCount: number;\n}\n\nexport interface AggregateByFacetsResult {\n groups: AggregateRow[];\n overflow: boolean;\n}\n\nexport interface TimelineByFacetInput {\n facet: { key: string; value: string };\n query?: AnalyticsFilter;\n window?: AnalyticsWindow;\n select?: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[] };\n /** Interval order by startedAt (default asc). */\n order?: 'asc' | 'desc';\n /** Strict startedAt upper bound — the \"load earlier\" cursor. */\n before?: string;\n limit?: number;\n}\n\nexport interface TimelineInterval {\n role: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n startedAt: string;\n endedAt: string | null;\n durationSeconds: number;\n}\n\nexport interface TimelineByFacetResult {\n intervals: TimelineInterval[];\n overflow: boolean;\n}\n\n// ── Key + window helpers (exported for tests) ────────────────────────────────\n\n/** Key-order-independent stringify so `{a,b}` and `{b,a}` share one query key. */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(value, (_key, v) =>\n v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => (a < b ? -1 : 1)))\n : v,\n );\n}\n\n/** The trailing window for a period: `to` = next minute boundary, `from` = to − hours. */\nexport function analyticsWindow(periodHours: number, now = Date.now()): AnalyticsWindow {\n const to = Math.ceil(now / 60_000) * 60_000;\n return {\n from: new Date(to - periodHours * 3_600_000).toISOString(),\n to: new Date(to).toISOString(),\n };\n}\n\n/**\n * A live trailing window that rolls forward on the minute. Without the tick a\n * quiet tab's `to` falls into the past and the server reclassifies the query\n * fully-past (10-minute cache) — a \"live\" board frozen for 10 minutes.\n */\nexport function useAnalyticsWindow(periodHours: number): AnalyticsWindow {\n const [window, setWindow] = useState(() => analyticsWindow(periodHours));\n useEffect(() => {\n setWindow(analyticsWindow(periodHours));\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n // Fire just past the minute boundary so ceil() lands on the NEXT one.\n timer = setTimeout(() => {\n setWindow(analyticsWindow(periodHours));\n schedule();\n }, 60_000 - (Date.now() % 60_000) + 250);\n };\n schedule();\n return () => clearTimeout(timer);\n }, [periodHours]);\n return window;\n}\n\n/** 403 is a scope answer, not a transient fault — never retry it. */\nfunction retryUnlessForbidden(failureCount: number, error: unknown): boolean {\n if (error instanceof ApiError && error.status === 403) return false;\n return failureCount < 2;\n}\n\n// ── Hooks ─────────────────────────────────────────────────────────────────────\n\nexport function useAggregateByFacets(\n input: AggregateByFacetsInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<AggregateByFacetsResult>({\n queryKey: ['escAggregate', input ? canonicalStringify(input) : 'off'],\n queryFn: () =>\n apiFetch('/escalations/aggregate-by-facets', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** One-shot timeline fetch — cursor pages (\"load earlier\") outside the query cache. */\nexport function fetchTimelineByFacet(input: TimelineByFacetInput): Promise<TimelineByFacetResult> {\n return apiFetch('/escalations/timeline-by-facet', {\n method: 'POST',\n body: JSON.stringify(input),\n });\n}\n\nexport function useTimelineByFacet(\n input: TimelineByFacetInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<TimelineByFacetResult>({\n queryKey: ['escTimeline', input ? canonicalStringify(input) : 'off'],\n queryFn: () => fetchTimelineByFacet(input!),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** True when the query failed the analytics read gate (render the scope notice). */\nexport function isForbidden(error: unknown): boolean {\n return error instanceof ApiError && error.status === 403;\n}\n","// ---------------------------------------------------------------------------\n// State-mix color assignment — the muted categorical palette (--lt-cat-*)\n// assigned to subtypes by dwell rank. Ten distinct hues so a ten-stage system\n// never repeats a color. The null subtype is a REAL group and gets a color.\n// ---------------------------------------------------------------------------\n\nexport const MIX_PALETTE = [\n 'rgb(var(--lt-cat-1))',\n 'rgb(var(--lt-cat-2))',\n 'rgb(var(--lt-cat-3))',\n 'rgb(var(--lt-cat-4))',\n 'rgb(var(--lt-cat-5))',\n 'rgb(var(--lt-cat-6))',\n 'rgb(var(--lt-cat-7))',\n 'rgb(var(--lt-cat-8))',\n 'rgb(var(--lt-cat-9))',\n 'rgb(var(--lt-cat-10))',\n] as const;\n\nexport const NO_SUBTYPE_KEY = '∅';\nexport const NO_SUBTYPE_LABEL = 'no subtype';\n\n/** Map key for a subtype group (null/undefined collapse to the ∅ group). */\nexport function subtypeKey(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_KEY;\n}\n\nexport function subtypeLabel(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_LABEL;\n}\n\n/**\n * Stable ordered assignment over arbitrary labels: sorted by total weight\n * descending (alphabetical tiebreak so equal ranks never swap), palette\n * wrapping beyond its length. Deterministic for a given entry set.\n */\nexport function assignLabelColors(\n entries: Array<{ label: string; weight: number }>,\n): Map<string, string> {\n const totals = new Map<string, number>();\n for (const e of entries) totals.set(e.label, (totals.get(e.label) ?? 0) + e.weight);\n const ranked = [...totals.entries()].sort(\n ([ak, av], [bk, bv]) => bv - av || (ak < bk ? -1 : 1),\n );\n const colors = new Map<string, string>();\n ranked.forEach(([key], i) => colors.set(key, MIX_PALETTE[i % MIX_PALETTE.length]));\n return colors;\n}\n\n/** The subtype-keyed form used by the station MIX surfaces. */\nexport function assignMixColors(\n groups: { subtype?: string | null; dwellSeconds?: number; count?: number }[],\n): Map<string, string> {\n return assignLabelColors(\n groups.map((g) => ({ label: subtypeKey(g.subtype), weight: g.dwellSeconds ?? g.count ?? 0 })),\n );\n}\n","import { useCallback, useMemo } from 'react';\nimport {\n useAggregateByFacets,\n useAnalyticsWindow,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useRoleDetails } from '../../api/roles';\nimport { assignLabelColors } from './mix-colors';\n\n/**\n * A category-color resolver for an entity system's timeline intervals — the\n * same palette the entity-lens band uses, so a timeline opened anywhere (the\n * lens, the station detail panel, an escalation cell popover) colors its dots\n * by state identically. Each interval's state is derived exactly as the server\n * does (subtype-sourced roles → COALESCE(subtype, role), everyone else → role),\n * then mapped through colors assigned over the system's state dwell.\n *\n * A stable reference window (not the user's selected period) keeps the color\n * assignment consistent across contexts. Pass `enabled: false` when the opener\n * already supplies its own resolver, so this does no redundant fetching.\n */\nexport function useEntityStateColors(\n scope: { entity?: string; role?: string },\n { enabled = true, periodHours = 720 }: { enabled?: boolean; periodHours?: number } = {},\n): (interval: TimelineInterval) => string | undefined {\n const window = useAnalyticsWindow(periodHours);\n const query = scope.entity\n ? { entity: scope.entity }\n : scope.role\n ? { roles: [scope.role] }\n : null;\n const active = enabled && !!query;\n\n const dwell = useAggregateByFacets(\n active\n ? {\n query: query!,\n groupBy: { state: true },\n measure: { kind: 'dwell', window },\n orderBy: [{ field: 'dwellSeconds', direction: 'desc' }],\n }\n : null,\n );\n const now = useAggregateByFacets(\n active ? { query: query!, groupBy: { state: true }, measure: { kind: 'membership' } } : null,\n );\n const { data: roleData } = useRoleDetails({ enabled: active });\n\n const subtypeSourced = useMemo(\n () =>\n new Set(\n (roleData?.roles ?? [])\n .filter((r) => r.entity_state_source === 'subtype')\n .map((r) => r.role),\n ),\n [roleData],\n );\n const colors = useMemo(\n () =>\n assignLabelColors(\n [\n ...(dwell.data?.groups ?? []).filter((g) => (g.dwellSeconds ?? 0) > 0),\n ...(now.data?.groups ?? []),\n ].map((g) => ({ label: g.state ?? '—', weight: g.dwellSeconds ?? g.count ?? 0 })),\n ),\n [dwell.data, now.data],\n );\n\n return useCallback(\n (interval: TimelineInterval) => {\n const state = subtypeSourced.has(interval.role)\n ? (interval.subtype ?? interval.role)\n : interval.role;\n return colors.get(state);\n },\n [colors, subtypeSourced],\n );\n}\n","import { Fragment, useEffect, useRef, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { X, Link as LinkIcon, Search, ArrowDown, ArrowUp } from 'lucide-react';\nimport {\n useTimelineByFacet,\n fetchTimelineByFacet,\n isForbidden,\n type AnalyticsFilter,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useEscalationAnalyticsEvents } from '../../hooks/useEventHooks';\nimport { useShellPanel } from '../../hooks/useShellPanel';\nimport { formatDateTime, formatDurationCompact } from '../../lib/format';\nimport { metadataFacetUrl } from '../../lib/facet-url';\nimport { assignLabelColors } from '../../pages/operations/mix-colors';\nimport { useEntityStateColors } from '../../pages/operations/useEntityStateColors';\n\n/** The category a row belongs to — its state within the process (subtype),\n * falling back to the role. This is the axis the timeline graphic colors by,\n * NOT the escalation lifecycle (resolved/expired) which would be near-uniform\n * across a mostly-completed history. */\nconst categoryOf = (iv: TimelineInterval): string => iv.subtype ?? iv.role;\n\n// Newest page size — the head query and every \"load earlier\" page.\nconst TIMELINE_PAGE_SIZE = 100;\n\n/**\n * One entity's escalation-interval timeline — every station the facet value\n * has moved through, as [started, ended) spans rendered chronologically. Gaps\n * between consecutive intervals are untracked time and render as explicit\n * separators: their size is the digital/physical settle latency, a health\n * signal, never hidden.\n *\n * The head query fetches the NEWEST page (order desc) so push-driven\n * invalidation keeps the recent end fresh; older pages accumulate locally via\n * the \"Load earlier\" cursor (`before` = oldest loaded startedAt) and reset\n * whenever the head page changes.\n *\n * Rendered in the global shell right panel (key 'entity-timeline'). Passing\n * `role` keeps the query inside the pond gate for read_all members; without\n * it the query is cross-role and needs a global principal — a 403 degrades to\n * a quiet scope notice.\n */\nexport function EntityTimelinePanel({\n facetKey,\n value,\n role,\n entity,\n intervalColor,\n}: {\n facetKey: string;\n value: string;\n role?: string;\n /** Scope to the derived entity system (every role declaring this entity facet). */\n entity?: string;\n /**\n * Optional color resolver from the opener (the entity lens): maps an interval\n * to the EXACT color the timeline graphic uses for its category, so the list\n * and the band agree. Absent (standalone opens), the panel colors from its\n * own intervals — consistent, but not cross-component identical.\n */\n intervalColor?: (interval: TimelineInterval) => string | undefined;\n}) {\n useEscalationAnalyticsEvents();\n const { closePanel } = useShellPanel();\n const scope: { query?: AnalyticsFilter } = entity\n ? { query: { entity } }\n : role\n ? { query: { roles: [role] } }\n : {};\n const { data, error, isLoading } = useTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n limit: TIMELINE_PAGE_SIZE,\n });\n\n // Color resolution: the opener's resolver (the lens, period-matched to its\n // band) wins; otherwise derive the entity system's colors here so station\n // detail and cell popovers color by the same scheme. When a resolver is\n // supplied, the fallback does no fetching.\n const systemColor = useEntityStateColors({ entity, role }, { enabled: !intervalColor });\n const resolveColor = intervalColor ?? systemColor;\n\n // Head page, chronological. Older pages prepend before it.\n const headIntervals = [...(data?.intervals ?? [])].reverse();\n const [older, setOlder] = useState<TimelineInterval[]>([]);\n const [hasEarlier, setHasEarlier] = useState(false);\n const [loadingEarlier, setLoadingEarlier] = useState(false);\n const [earlierError, setEarlierError] = useState<string | null>(null);\n\n // Reset accumulated pages when the newest interval set changes — a new facet\n // value, or a push-driven head refetch that shifted the recent end.\n const headSignature = data\n ? `${value}|${data.intervals.length}|${data.intervals[0]?.startedAt ?? ''}|${data.intervals[data.intervals.length - 1]?.startedAt ?? ''}`\n : null;\n const appliedSignature = useRef<string | null>(null);\n useEffect(() => {\n if (headSignature === appliedSignature.current) return;\n appliedSignature.current = headSignature;\n setOlder([]);\n setHasEarlier(data?.overflow ?? false);\n setEarlierError(null);\n }, [headSignature, data]);\n\n const intervals = [...older, ...headIntervals];\n\n // Display order — the dashboard convention is most-recent first, so the\n // default is descending; the toggle is pure presentation over the loaded\n // window. Paging is unchanged either way: the head is the newest page and\n // the strict `before` cursor walks OLDER intervals — sound at thousands of\n // rows (a printer's lifetime runs to ~4500 intervals), no offsets to drift.\n const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc');\n const displayed = sortOrder === 'desc' ? [...intervals].reverse() : intervals;\n // The chronologically earlier/later pair for a rendered neighbor gap.\n const gapPair = (i: number): { prev: TimelineInterval; next: TimelineInterval } =>\n sortOrder === 'desc'\n ? { prev: displayed[i], next: displayed[i - 1] }\n : { prev: displayed[i - 1], next: displayed[i] };\n\n // Category colors — the same palette generator the timeline graphic uses,\n // keyed by each row's category and weighted by dwell so the dominant states\n // take the lead palette slots. The dot reads as \"what this was,\" matching the\n // band segments on the left.\n const categoryColors = assignLabelColors(\n intervals.map((iv) => ({ label: categoryOf(iv), weight: iv.durationSeconds })),\n );\n\n const loadEarlier = async () => {\n const oldest = intervals[0]?.startedAt;\n if (!oldest || loadingEarlier) return;\n setLoadingEarlier(true);\n setEarlierError(null);\n try {\n const page = await fetchTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n before: oldest,\n limit: TIMELINE_PAGE_SIZE,\n });\n setOlder((prev) => [...[...page.intervals].reverse(), ...prev]);\n setHasEarlier(page.overflow);\n } catch (err) {\n setEarlierError((err as Error).message);\n } finally {\n setLoadingEarlier(false);\n }\n };\n\n return (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40\">\n <div className=\"min-w-0\">\n <p className=\"text-2xs font-semibold uppercase tracking-widest text-text-tertiary\">\n Timeline · <span className=\"normal-case font-mono\">{facetKey}</span>\n </p>\n <p className=\"text-sm font-mono text-text-primary truncate mt-0.5\" title={value}>\n {value}\n </p>\n {intervals.length > 0 && (\n <p className=\"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5\">\n <span>{intervals.length} interval{intervals.length === 1 ? '' : 's'}</span>\n <span className=\"h-2.5 w-px bg-surface-border\" />\n <button\n onClick={() => setSortOrder((o) => (o === 'desc' ? 'asc' : 'desc'))}\n className=\"inline-flex items-center gap-0.5 icon-link\"\n title={sortOrder === 'desc' ? 'Newest first — click for oldest first' : 'Oldest first — click for newest first'}\n data-testid=\"timeline-sort-toggle\"\n >\n {sortOrder === 'desc'\n ? <><ArrowDown className=\"w-2.5 h-2.5\" /> newest first</>\n : <><ArrowUp className=\"w-2.5 h-2.5\" /> oldest first</>}\n </button>\n </p>\n )}\n </div>\n <span className=\"flex items-center gap-1 mt-0.5 shrink-0 ml-2\">\n {entity && <CopyLinkButton entity={entity} value={value} />}\n {/* Search every queue for this facet value — the all-roles metadata search. */}\n <Link\n to={metadataFacetUrl(facetKey, value)}\n className=\"icon-link\"\n title={`Search every queue for ${facetKey} ${value}`}\n data-testid=\"timeline-facet-search\"\n >\n <Search className=\"w-3.5 h-3.5\" />\n </Link>\n <button onClick={() => closePanel('entity-timeline')} className=\"icon-link\">\n <X className=\"w-4 h-4\" />\n </button>\n </span>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {isLoading ? (\n <p className=\"text-2xs text-text-tertiary italic\">Loading timeline…</p>\n ) : isForbidden(error) ? (\n <p className=\"text-2xs text-text-tertiary\">\n Timeline requires full read access to this station's queue.\n </p>\n ) : error ? (\n <p className=\"text-2xs text-status-error\">{(error as Error).message}</p>\n ) : intervals.length === 0 ? (\n <p className=\"text-2xs text-text-quaternary\">\n No escalation has carried this value.\n </p>\n ) : (\n <div className=\"relative\">\n {/* Older pages continue in the reading direction: at the TOP when\n oldest-first, at the BOTTOM when newest-first. */}\n {sortOrder === 'asc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"top\"\n />\n )}\n {/* Left rail behind the dots */}\n <div className=\"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border\" />\n <div className=\"space-y-0\">\n {displayed.map((interval, i) => (\n <Fragment key={`${interval.startedAt}-${i}`}>\n {i > 0 && <GapRow {...gapPair(i)} />}\n <IntervalRow\n interval={interval}\n color={resolveColor(interval) ?? categoryColors.get(categoryOf(interval))}\n />\n </Fragment>\n ))}\n </div>\n {sortOrder === 'desc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"bottom\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/** The cursor pager — walks strictly-older intervals a page at a time. */\nfunction LoadOlderControl({\n hasEarlier,\n loading,\n error,\n onLoad,\n edge,\n}: {\n hasEarlier: boolean;\n loading: boolean;\n error: string | null;\n onLoad: () => void;\n edge: 'top' | 'bottom';\n}) {\n if (!hasEarlier && !error) return null;\n return (\n <div className={`${edge === 'top' ? 'pb-2' : 'pt-2'} pl-5`}>\n {hasEarlier && (\n <button\n onClick={onLoad}\n disabled={loading}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40\"\n >\n {loading ? 'Loading older…' : 'Load older'}\n </button>\n )}\n {error && <p className=\"text-2xs text-status-error mt-1\">{error}</p>}\n </div>\n );\n}\n\n/** Copies the entity-lens deep link (?lens= + ?entity=) with a transient note. */\nfunction CopyLinkButton({ entity, value }: { entity: string; value: string }) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n const copy = async () => {\n const url = `${window.location.origin}/trends?lens=${encodeURIComponent(entity)}&entity=${encodeURIComponent(value)}`;\n await navigator.clipboard.writeText(url);\n setCopied(true);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <span className=\"flex items-center gap-1\">\n {copied && <span className=\"text-2xs text-text-quaternary\">copied</span>}\n <button onClick={copy} className=\"icon-link\" title=\"Copy link to this timeline\">\n <LinkIcon className=\"w-3.5 h-3.5\" />\n </button>\n </span>\n );\n}\n\nfunction IntervalRow({ interval, color }: { interval: TimelineInterval; color?: string }) {\n return (\n <div className=\"relative flex gap-3 py-2\">\n {/* Filled dot in the row's CATEGORY color — matches the graphic's segments,\n so the list reads by state the same way the band does. */}\n <span\n className=\"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10\"\n style={{ backgroundColor: color ?? 'rgb(var(--lt-surface-border))' }}\n title={categoryOf(interval)}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-xs font-mono text-text-primary truncate\">\n {interval.role}\n {interval.subtype ? <span className=\"text-text-tertiary\"> · {interval.subtype}</span> : null}\n </span>\n <span className=\"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0\">\n {formatDurationCompact(interval.durationSeconds * 1000)}\n </span>\n </div>\n <p className=\"text-2xs font-mono text-text-tertiary mt-0.5\">\n {formatDateTime(interval.startedAt)} → {interval.endedAt ? formatDateTime(interval.endedAt) : 'now'}\n </p>\n </div>\n </div>\n );\n}\n\n/** Untracked time between two intervals — the settle latency between queues, surfaced. */\nfunction GapRow({ prev, next }: { prev: TimelineInterval; next: TimelineInterval }) {\n if (!prev.endedAt) return null;\n const gapMs = Date.parse(next.startedAt) - Date.parse(prev.endedAt);\n if (gapMs <= 1000) return null; // contiguous handoff\n return (\n <div className=\"relative flex items-center gap-3 py-1\">\n <span className=\"w-[9px] shrink-0\" />\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n untracked · {formatDurationCompact(gapMs)}\n </span>\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n </div>\n );\n}\n"],"names":["canonicalStringify","value","_key","v","a","b","analyticsWindow","periodHours","now","to","useAnalyticsWindow","window","setWindow","useState","useEffect","timer","schedule","retryUnlessForbidden","failureCount","error","ApiError","useAggregateByFacets","input","opts","useQuery","apiFetch","fetchTimelineByFacet","useTimelineByFacet","isForbidden","MIX_PALETTE","NO_SUBTYPE_KEY","NO_SUBTYPE_LABEL","subtypeKey","subtype","subtypeLabel","assignLabelColors","entries","totals","e","ranked","ak","av","bk","bv","colors","key","i","assignMixColors","groups","g","useEntityStateColors","scope","enabled","query","active","dwell","roleData","useRoleDetails","subtypeSourced","useMemo","r","_a","_b","useCallback","interval","state","categoryOf","iv","TIMELINE_PAGE_SIZE","EntityTimelinePanel","facetKey","role","entity","intervalColor","useEscalationAnalyticsEvents","closePanel","useShellPanel","data","isLoading","systemColor","resolveColor","headIntervals","older","setOlder","hasEarlier","setHasEarlier","loadingEarlier","setLoadingEarlier","earlierError","setEarlierError","headSignature","appliedSignature","useRef","intervals","sortOrder","setSortOrder","displayed","gapPair","categoryColors","loadEarlier","oldest","page","prev","err","jsxs","jsx","Fragment","ArrowDown","ArrowUp","CopyLinkButton","Link","metadataFacetUrl","Search","X","LoadOlderControl","GapRow","IntervalRow","loading","onLoad","edge","copied","setCopied","copy","url","LinkIcon","color","formatDateTime","next","gapMs","formatDurationCompact"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EntityTimelinePanel-CkF-oW-m.js","sources":["../../src/api/escalation-analytics.ts","../../src/pages/operations/mix-colors.ts","../../src/pages/operations/useEntityStateColors.ts","../../src/components/escalation/EntityTimelinePanel.tsx"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Escalation analytics hooks — grouped membership/dwell aggregates and\n// per-entity interval timelines (POST /escalations/aggregate-by-facets and\n// /timeline-by-facet). Every escalation is one open interval\n// [created_at, ended_at); these hooks read that time-series.\n//\n// Query-key discipline: inputs are canonicalized (key-sorted stringify) and\n// windows are quantized to the minute — the window end rounds UP to the next\n// minute boundary so (a) keys stay stable across renders within the minute\n// and (b) `to >= now` keeps the query in the server's now-anchored cache\n// (invalidated on escalation writes) instead of the 10-minute past cache.\n// Refresh is push-driven (useEscalationAnalyticsEvents) — never polling.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { apiFetch, ApiError } from './client';\n\n// Wire types — mirrors of types/analytics.ts on the server.\n\nexport interface AnalyticsWindow {\n from: string;\n to: string;\n}\n\nexport type AggregateMeasure =\n | { kind: 'membership'; asOf?: string }\n | { kind: 'dwell'; window: AnalyticsWindow };\n\nexport interface AnalyticsFilter {\n role?: string;\n roles?: string[];\n /** Entity facet key — resolves server-side to every role declaring it (the entity's system). */\n entity?: string;\n facets?: Record<string, unknown>;\n /** Case-insensitive facet-value prefix match, keyed by facet. */\n prefix?: Record<string, string>;\n /**\n * Exact facet-value match via the text projection (metadata->>key = value),\n * keyed by facet. Compares as text, so a categorical value the aggregate\n * returned (bool/enum/number, all surfaced as text) round-trips as a filter.\n */\n equals?: Record<string, string>;\n block?: Record<string, unknown>[];\n /** Row carries ANY of these facet sets (max 200) — the positive mirror of block. */\n anyOf?: Record<string, unknown>[];\n range?: { facet: string; op: '<' | '<=' | '>' | '>=' | '='; value: number }[];\n exists?: string[];\n}\n\nexport interface AggregateByFacetsInput {\n query: AnalyticsFilter;\n groupBy: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[]; state?: boolean };\n measure: AggregateMeasure;\n distinctBy?: string;\n orderBy?: { field: string; direction?: 'asc' | 'desc' }[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateRow {\n role?: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n state?: string;\n count?: number;\n dwellSeconds?: number;\n sampleCount: number;\n}\n\nexport interface AggregateByFacetsResult {\n groups: AggregateRow[];\n overflow: boolean;\n}\n\nexport interface TimelineByFacetInput {\n facet: { key: string; value: string };\n query?: AnalyticsFilter;\n window?: AnalyticsWindow;\n select?: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[] };\n /** Interval order by startedAt (default asc). */\n order?: 'asc' | 'desc';\n /** Strict startedAt upper bound — the \"load earlier\" cursor. */\n before?: string;\n limit?: number;\n}\n\nexport interface TimelineInterval {\n role: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n startedAt: string;\n endedAt: string | null;\n durationSeconds: number;\n}\n\nexport interface TimelineByFacetResult {\n intervals: TimelineInterval[];\n overflow: boolean;\n}\n\n// ── Key + window helpers (exported for tests) ────────────────────────────────\n\n/** Key-order-independent stringify so `{a,b}` and `{b,a}` share one query key. */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(value, (_key, v) =>\n v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => (a < b ? -1 : 1)))\n : v,\n );\n}\n\n/** The trailing window for a period: `to` = next minute boundary, `from` = to − hours. */\nexport function analyticsWindow(periodHours: number, now = Date.now()): AnalyticsWindow {\n const to = Math.ceil(now / 60_000) * 60_000;\n return {\n from: new Date(to - periodHours * 3_600_000).toISOString(),\n to: new Date(to).toISOString(),\n };\n}\n\n/**\n * A live trailing window that rolls forward on the minute. Without the tick a\n * quiet tab's `to` falls into the past and the server reclassifies the query\n * fully-past (10-minute cache) — a \"live\" board frozen for 10 minutes.\n */\nexport function useAnalyticsWindow(periodHours: number): AnalyticsWindow {\n const [window, setWindow] = useState(() => analyticsWindow(periodHours));\n useEffect(() => {\n setWindow(analyticsWindow(periodHours));\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n // Fire just past the minute boundary so ceil() lands on the NEXT one.\n timer = setTimeout(() => {\n setWindow(analyticsWindow(periodHours));\n schedule();\n }, 60_000 - (Date.now() % 60_000) + 250);\n };\n schedule();\n return () => clearTimeout(timer);\n }, [periodHours]);\n return window;\n}\n\n/** 403 is a scope answer, not a transient fault — never retry it. */\nfunction retryUnlessForbidden(failureCount: number, error: unknown): boolean {\n if (error instanceof ApiError && error.status === 403) return false;\n return failureCount < 2;\n}\n\n// ── Hooks ─────────────────────────────────────────────────────────────────────\n\nexport function useAggregateByFacets(\n input: AggregateByFacetsInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<AggregateByFacetsResult>({\n queryKey: ['escAggregate', input ? canonicalStringify(input) : 'off'],\n queryFn: () =>\n apiFetch('/escalations/aggregate-by-facets', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** One-shot timeline fetch — cursor pages (\"load earlier\") outside the query cache. */\nexport function fetchTimelineByFacet(input: TimelineByFacetInput): Promise<TimelineByFacetResult> {\n return apiFetch('/escalations/timeline-by-facet', {\n method: 'POST',\n body: JSON.stringify(input),\n });\n}\n\nexport function useTimelineByFacet(\n input: TimelineByFacetInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<TimelineByFacetResult>({\n queryKey: ['escTimeline', input ? canonicalStringify(input) : 'off'],\n queryFn: () => fetchTimelineByFacet(input!),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** True when the query failed the analytics read gate (render the scope notice). */\nexport function isForbidden(error: unknown): boolean {\n return error instanceof ApiError && error.status === 403;\n}\n","// ---------------------------------------------------------------------------\n// State-mix color assignment — the muted categorical palette (--lt-cat-*)\n// assigned to subtypes by dwell rank. Ten distinct hues so a ten-stage system\n// never repeats a color. The null subtype is a REAL group and gets a color.\n// ---------------------------------------------------------------------------\n\nexport const MIX_PALETTE = [\n 'rgb(var(--lt-cat-1))',\n 'rgb(var(--lt-cat-2))',\n 'rgb(var(--lt-cat-3))',\n 'rgb(var(--lt-cat-4))',\n 'rgb(var(--lt-cat-5))',\n 'rgb(var(--lt-cat-6))',\n 'rgb(var(--lt-cat-7))',\n 'rgb(var(--lt-cat-8))',\n 'rgb(var(--lt-cat-9))',\n 'rgb(var(--lt-cat-10))',\n] as const;\n\nexport const NO_SUBTYPE_KEY = '∅';\nexport const NO_SUBTYPE_LABEL = 'no subtype';\n\n/** Map key for a subtype group (null/undefined collapse to the ∅ group). */\nexport function subtypeKey(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_KEY;\n}\n\nexport function subtypeLabel(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_LABEL;\n}\n\n/**\n * Stable ordered assignment over arbitrary labels: sorted by total weight\n * descending (alphabetical tiebreak so equal ranks never swap), palette\n * wrapping beyond its length. Deterministic for a given entry set.\n */\nexport function assignLabelColors(\n entries: Array<{ label: string; weight: number }>,\n): Map<string, string> {\n const totals = new Map<string, number>();\n for (const e of entries) totals.set(e.label, (totals.get(e.label) ?? 0) + e.weight);\n const ranked = [...totals.entries()].sort(\n ([ak, av], [bk, bv]) => bv - av || (ak < bk ? -1 : 1),\n );\n const colors = new Map<string, string>();\n ranked.forEach(([key], i) => colors.set(key, MIX_PALETTE[i % MIX_PALETTE.length]));\n return colors;\n}\n\n/** The subtype-keyed form used by the station MIX surfaces. */\nexport function assignMixColors(\n groups: { subtype?: string | null; dwellSeconds?: number; count?: number }[],\n): Map<string, string> {\n return assignLabelColors(\n groups.map((g) => ({ label: subtypeKey(g.subtype), weight: g.dwellSeconds ?? g.count ?? 0 })),\n );\n}\n","import { useCallback, useMemo } from 'react';\nimport {\n useAggregateByFacets,\n useAnalyticsWindow,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useRoleDetails } from '../../api/roles';\nimport { assignLabelColors } from './mix-colors';\n\n/**\n * A category-color resolver for an entity system's timeline intervals — the\n * same palette the entity-lens band uses, so a timeline opened anywhere (the\n * lens, the station detail panel, an escalation cell popover) colors its dots\n * by state identically. Each interval's state is derived exactly as the server\n * does (subtype-sourced roles → COALESCE(subtype, role), everyone else → role),\n * then mapped through colors assigned over the system's state dwell.\n *\n * A stable reference window (not the user's selected period) keeps the color\n * assignment consistent across contexts. Pass `enabled: false` when the opener\n * already supplies its own resolver, so this does no redundant fetching.\n */\nexport function useEntityStateColors(\n scope: { entity?: string; role?: string },\n { enabled = true, periodHours = 720 }: { enabled?: boolean; periodHours?: number } = {},\n): (interval: TimelineInterval) => string | undefined {\n const window = useAnalyticsWindow(periodHours);\n const query = scope.entity\n ? { entity: scope.entity }\n : scope.role\n ? { roles: [scope.role] }\n : null;\n const active = enabled && !!query;\n\n const dwell = useAggregateByFacets(\n active\n ? {\n query: query!,\n groupBy: { state: true },\n measure: { kind: 'dwell', window },\n orderBy: [{ field: 'dwellSeconds', direction: 'desc' }],\n }\n : null,\n );\n const now = useAggregateByFacets(\n active ? { query: query!, groupBy: { state: true }, measure: { kind: 'membership' } } : null,\n );\n const { data: roleData } = useRoleDetails({ enabled: active });\n\n const subtypeSourced = useMemo(\n () =>\n new Set(\n (roleData?.roles ?? [])\n .filter((r) => r.entity_state_source === 'subtype')\n .map((r) => r.role),\n ),\n [roleData],\n );\n const colors = useMemo(\n () =>\n assignLabelColors(\n [\n ...(dwell.data?.groups ?? []).filter((g) => (g.dwellSeconds ?? 0) > 0),\n ...(now.data?.groups ?? []),\n ].map((g) => ({ label: g.state ?? '—', weight: g.dwellSeconds ?? g.count ?? 0 })),\n ),\n [dwell.data, now.data],\n );\n\n return useCallback(\n (interval: TimelineInterval) => {\n const state = subtypeSourced.has(interval.role)\n ? (interval.subtype ?? interval.role)\n : interval.role;\n return colors.get(state);\n },\n [colors, subtypeSourced],\n );\n}\n","import { Fragment, useEffect, useRef, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { X, Link as LinkIcon, Search, ArrowDown, ArrowUp } from 'lucide-react';\nimport {\n useTimelineByFacet,\n fetchTimelineByFacet,\n isForbidden,\n type AnalyticsFilter,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useEscalationAnalyticsEvents } from '../../hooks/useEventHooks';\nimport { useShellPanel } from '../../hooks/useShellPanel';\nimport { formatDateTime, formatDurationCompact } from '../../lib/format';\nimport { metadataFacetUrl } from '../../lib/facet-url';\nimport { assignLabelColors } from '../../pages/operations/mix-colors';\nimport { useEntityStateColors } from '../../pages/operations/useEntityStateColors';\n\n/** The category a row belongs to — its state within the process (subtype),\n * falling back to the role. This is the axis the timeline graphic colors by,\n * NOT the escalation lifecycle (resolved/expired) which would be near-uniform\n * across a mostly-completed history. */\nconst categoryOf = (iv: TimelineInterval): string => iv.subtype ?? iv.role;\n\n// Newest page size — the head query and every \"load earlier\" page.\nconst TIMELINE_PAGE_SIZE = 100;\n\n/**\n * One entity's escalation-interval timeline — every station the facet value\n * has moved through, as [started, ended) spans rendered chronologically. Gaps\n * between consecutive intervals are untracked time and render as explicit\n * separators: their size is the digital/physical settle latency, a health\n * signal, never hidden.\n *\n * The head query fetches the NEWEST page (order desc) so push-driven\n * invalidation keeps the recent end fresh; older pages accumulate locally via\n * the \"Load earlier\" cursor (`before` = oldest loaded startedAt) and reset\n * whenever the head page changes.\n *\n * Rendered in the global shell right panel (key 'entity-timeline'). Passing\n * `role` keeps the query inside the pond gate for read_all members; without\n * it the query is cross-role and needs a global principal — a 403 degrades to\n * a quiet scope notice.\n */\nexport function EntityTimelinePanel({\n facetKey,\n value,\n role,\n entity,\n intervalColor,\n}: {\n facetKey: string;\n value: string;\n role?: string;\n /** Scope to the derived entity system (every role declaring this entity facet). */\n entity?: string;\n /**\n * Optional color resolver from the opener (the entity lens): maps an interval\n * to the EXACT color the timeline graphic uses for its category, so the list\n * and the band agree. Absent (standalone opens), the panel colors from its\n * own intervals — consistent, but not cross-component identical.\n */\n intervalColor?: (interval: TimelineInterval) => string | undefined;\n}) {\n useEscalationAnalyticsEvents();\n const { closePanel } = useShellPanel();\n const scope: { query?: AnalyticsFilter } = entity\n ? { query: { entity } }\n : role\n ? { query: { roles: [role] } }\n : {};\n const { data, error, isLoading } = useTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n limit: TIMELINE_PAGE_SIZE,\n });\n\n // Color resolution: the opener's resolver (the lens, period-matched to its\n // band) wins; otherwise derive the entity system's colors here so station\n // detail and cell popovers color by the same scheme. When a resolver is\n // supplied, the fallback does no fetching.\n const systemColor = useEntityStateColors({ entity, role }, { enabled: !intervalColor });\n const resolveColor = intervalColor ?? systemColor;\n\n // Head page, chronological. Older pages prepend before it.\n const headIntervals = [...(data?.intervals ?? [])].reverse();\n const [older, setOlder] = useState<TimelineInterval[]>([]);\n const [hasEarlier, setHasEarlier] = useState(false);\n const [loadingEarlier, setLoadingEarlier] = useState(false);\n const [earlierError, setEarlierError] = useState<string | null>(null);\n\n // Reset accumulated pages when the newest interval set changes — a new facet\n // value, or a push-driven head refetch that shifted the recent end.\n const headSignature = data\n ? `${value}|${data.intervals.length}|${data.intervals[0]?.startedAt ?? ''}|${data.intervals[data.intervals.length - 1]?.startedAt ?? ''}`\n : null;\n const appliedSignature = useRef<string | null>(null);\n useEffect(() => {\n if (headSignature === appliedSignature.current) return;\n appliedSignature.current = headSignature;\n setOlder([]);\n setHasEarlier(data?.overflow ?? false);\n setEarlierError(null);\n }, [headSignature, data]);\n\n const intervals = [...older, ...headIntervals];\n\n // Display order — the dashboard convention is most-recent first, so the\n // default is descending; the toggle is pure presentation over the loaded\n // window. Paging is unchanged either way: the head is the newest page and\n // the strict `before` cursor walks OLDER intervals — sound at thousands of\n // rows (a printer's lifetime runs to ~4500 intervals), no offsets to drift.\n const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc');\n const displayed = sortOrder === 'desc' ? [...intervals].reverse() : intervals;\n // The chronologically earlier/later pair for a rendered neighbor gap.\n const gapPair = (i: number): { prev: TimelineInterval; next: TimelineInterval } =>\n sortOrder === 'desc'\n ? { prev: displayed[i], next: displayed[i - 1] }\n : { prev: displayed[i - 1], next: displayed[i] };\n\n // Category colors — the same palette generator the timeline graphic uses,\n // keyed by each row's category and weighted by dwell so the dominant states\n // take the lead palette slots. The dot reads as \"what this was,\" matching the\n // band segments on the left.\n const categoryColors = assignLabelColors(\n intervals.map((iv) => ({ label: categoryOf(iv), weight: iv.durationSeconds })),\n );\n\n const loadEarlier = async () => {\n const oldest = intervals[0]?.startedAt;\n if (!oldest || loadingEarlier) return;\n setLoadingEarlier(true);\n setEarlierError(null);\n try {\n const page = await fetchTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n before: oldest,\n limit: TIMELINE_PAGE_SIZE,\n });\n setOlder((prev) => [...[...page.intervals].reverse(), ...prev]);\n setHasEarlier(page.overflow);\n } catch (err) {\n setEarlierError((err as Error).message);\n } finally {\n setLoadingEarlier(false);\n }\n };\n\n return (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40\">\n <div className=\"min-w-0\">\n <p className=\"text-2xs font-semibold uppercase tracking-widest text-text-tertiary\">\n Timeline · <span className=\"normal-case font-mono\">{facetKey}</span>\n </p>\n <p className=\"text-sm font-mono text-text-primary truncate mt-0.5\" title={value}>\n {value}\n </p>\n {intervals.length > 0 && (\n <p className=\"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5\">\n <span>{intervals.length} interval{intervals.length === 1 ? '' : 's'}</span>\n <span className=\"h-2.5 w-px bg-surface-border\" />\n <button\n onClick={() => setSortOrder((o) => (o === 'desc' ? 'asc' : 'desc'))}\n className=\"inline-flex items-center gap-0.5 icon-link\"\n title={sortOrder === 'desc' ? 'Newest first — click for oldest first' : 'Oldest first — click for newest first'}\n data-testid=\"timeline-sort-toggle\"\n >\n {sortOrder === 'desc'\n ? <><ArrowDown className=\"w-2.5 h-2.5\" /> newest first</>\n : <><ArrowUp className=\"w-2.5 h-2.5\" /> oldest first</>}\n </button>\n </p>\n )}\n </div>\n <span className=\"flex items-center gap-1 mt-0.5 shrink-0 ml-2\">\n {entity && <CopyLinkButton entity={entity} value={value} />}\n {/* Search every queue for this facet value — the all-roles metadata search. */}\n <Link\n to={metadataFacetUrl(facetKey, value)}\n className=\"icon-link\"\n title={`Search every queue for ${facetKey} ${value}`}\n data-testid=\"timeline-facet-search\"\n >\n <Search className=\"w-3.5 h-3.5\" />\n </Link>\n <button onClick={() => closePanel('entity-timeline')} className=\"icon-link\">\n <X className=\"w-4 h-4\" />\n </button>\n </span>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {isLoading ? (\n <p className=\"text-2xs text-text-tertiary italic\">Loading timeline…</p>\n ) : isForbidden(error) ? (\n <p className=\"text-2xs text-text-tertiary\">\n Timeline requires full read access to this station's queue.\n </p>\n ) : error ? (\n <p className=\"text-2xs text-status-error\">{(error as Error).message}</p>\n ) : intervals.length === 0 ? (\n <p className=\"text-2xs text-text-quaternary\">\n No escalation has carried this value.\n </p>\n ) : (\n <div className=\"relative\">\n {/* Older pages continue in the reading direction: at the TOP when\n oldest-first, at the BOTTOM when newest-first. */}\n {sortOrder === 'asc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"top\"\n />\n )}\n {/* Left rail behind the dots */}\n <div className=\"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border\" />\n <div className=\"space-y-0\">\n {displayed.map((interval, i) => (\n <Fragment key={`${interval.startedAt}-${i}`}>\n {i > 0 && <GapRow {...gapPair(i)} />}\n <IntervalRow\n interval={interval}\n color={resolveColor(interval) ?? categoryColors.get(categoryOf(interval))}\n />\n </Fragment>\n ))}\n </div>\n {sortOrder === 'desc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"bottom\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/** The cursor pager — walks strictly-older intervals a page at a time. */\nfunction LoadOlderControl({\n hasEarlier,\n loading,\n error,\n onLoad,\n edge,\n}: {\n hasEarlier: boolean;\n loading: boolean;\n error: string | null;\n onLoad: () => void;\n edge: 'top' | 'bottom';\n}) {\n if (!hasEarlier && !error) return null;\n return (\n <div className={`${edge === 'top' ? 'pb-2' : 'pt-2'} pl-5`}>\n {hasEarlier && (\n <button\n onClick={onLoad}\n disabled={loading}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40\"\n >\n {loading ? 'Loading older…' : 'Load older'}\n </button>\n )}\n {error && <p className=\"text-2xs text-status-error mt-1\">{error}</p>}\n </div>\n );\n}\n\n/** Copies the entity-lens deep link (?lens= + ?entity=) with a transient note. */\nfunction CopyLinkButton({ entity, value }: { entity: string; value: string }) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n const copy = async () => {\n const url = `${window.location.origin}/trends?lens=${encodeURIComponent(entity)}&entity=${encodeURIComponent(value)}`;\n await navigator.clipboard.writeText(url);\n setCopied(true);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <span className=\"flex items-center gap-1\">\n {copied && <span className=\"text-2xs text-text-quaternary\">copied</span>}\n <button onClick={copy} className=\"icon-link\" title=\"Copy link to this timeline\">\n <LinkIcon className=\"w-3.5 h-3.5\" />\n </button>\n </span>\n );\n}\n\nfunction IntervalRow({ interval, color }: { interval: TimelineInterval; color?: string }) {\n return (\n <div className=\"relative flex gap-3 py-2\">\n {/* Filled dot in the row's CATEGORY color — matches the graphic's segments,\n so the list reads by state the same way the band does. */}\n <span\n className=\"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10\"\n style={{ backgroundColor: color ?? 'rgb(var(--lt-surface-border))' }}\n title={categoryOf(interval)}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-xs font-mono text-text-primary truncate\">\n {interval.role}\n {interval.subtype ? <span className=\"text-text-tertiary\"> · {interval.subtype}</span> : null}\n </span>\n <span className=\"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0\">\n {formatDurationCompact(interval.durationSeconds * 1000)}\n </span>\n </div>\n <p className=\"text-2xs font-mono text-text-tertiary mt-0.5\">\n {formatDateTime(interval.startedAt)} → {interval.endedAt ? formatDateTime(interval.endedAt) : 'now'}\n </p>\n </div>\n </div>\n );\n}\n\n/** Untracked time between two intervals — the settle latency between queues, surfaced. */\nfunction GapRow({ prev, next }: { prev: TimelineInterval; next: TimelineInterval }) {\n if (!prev.endedAt) return null;\n const gapMs = Date.parse(next.startedAt) - Date.parse(prev.endedAt);\n if (gapMs <= 1000) return null; // contiguous handoff\n return (\n <div className=\"relative flex items-center gap-3 py-1\">\n <span className=\"w-[9px] shrink-0\" />\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n untracked · {formatDurationCompact(gapMs)}\n </span>\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n </div>\n );\n}\n"],"names":["canonicalStringify","value","_key","v","a","b","analyticsWindow","periodHours","now","to","useAnalyticsWindow","window","setWindow","useState","useEffect","timer","schedule","retryUnlessForbidden","failureCount","error","ApiError","useAggregateByFacets","input","opts","useQuery","apiFetch","fetchTimelineByFacet","useTimelineByFacet","isForbidden","MIX_PALETTE","NO_SUBTYPE_KEY","NO_SUBTYPE_LABEL","subtypeKey","subtype","subtypeLabel","assignLabelColors","entries","totals","e","ranked","ak","av","bk","bv","colors","key","i","assignMixColors","groups","g","useEntityStateColors","scope","enabled","query","active","dwell","roleData","useRoleDetails","subtypeSourced","useMemo","r","_a","_b","useCallback","interval","state","categoryOf","iv","TIMELINE_PAGE_SIZE","EntityTimelinePanel","facetKey","role","entity","intervalColor","useEscalationAnalyticsEvents","closePanel","useShellPanel","data","isLoading","systemColor","resolveColor","headIntervals","older","setOlder","hasEarlier","setHasEarlier","loadingEarlier","setLoadingEarlier","earlierError","setEarlierError","headSignature","appliedSignature","useRef","intervals","sortOrder","setSortOrder","displayed","gapPair","categoryColors","loadEarlier","oldest","page","prev","err","jsxs","jsx","Fragment","ArrowDown","ArrowUp","CopyLinkButton","Link","metadataFacetUrl","Search","X","LoadOlderControl","GapRow","IntervalRow","loading","onLoad","edge","copied","setCopied","copy","url","LinkIcon","color","formatDateTime","next","gapMs","formatDurationCompact"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as l,j as e}from"./vendor-query-
|
|
1
|
+
import{a as l,j as e}from"./vendor-query-DvWWuNIB.js";import{_ as w,aS as S,a_ as _,a2 as L,J as E}from"./index-CIwlVfvV.js";import{an as v,w as C,aa as O,au as R}from"./vendor-icons-DeBdvyKJ.js";import{e as q}from"./vendor-react-_9U4mKFr.js";const V=`{
|
|
2
2
|
"x-lt-layout": "active-history",
|
|
3
3
|
"x-lt-help": "# {{escalation.type}}\\nThe live item plus its history.",
|
|
4
4
|
"x-lt-active": {
|
|
@@ -16,5 +16,5 @@ import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{Y as w,aB as S,aJ a
|
|
|
16
16
|
},
|
|
17
17
|
"limit": 25
|
|
18
18
|
}
|
|
19
|
-
}`;function j(i){const t=i.trim();if(!t)return{ok:!0,value:null};try{const r=JSON.parse(t);return typeof r!="object"||Array.isArray(r)||r===null?{ok:!1}:{ok:!0,value:r}}catch{return{ok:!1}}}function
|
|
20
|
-
//# sourceMappingURL=EscalationListSchemaPage-
|
|
19
|
+
}`;function j(i){const t=i.trim();if(!t)return{ok:!0,value:null};try{const r=JSON.parse(t);return typeof r!="object"||Array.isArray(r)||r===null?{ok:!1}:{ok:!0,value:r}}catch{return{ok:!1}}}function F(){const{role:i}=q(),{data:t,isLoading:r}=w(),n=S(),s=((t==null?void 0:t.roles)??[]).find(a=>a.role===i),[c,o]=l.useState(""),[h,u]=l.useState(""),[y,d]=l.useState(!1),[N,x]=l.useState(!1),[p,m]=l.useState(!1),f=l.useRef(null);l.useEffect(()=>{!s||f.current===s.role||(f.current=s.role,o(s.list_schema?JSON.stringify(s.list_schema,null,2):""),u(""),d(!1),m(!1))},[s]);const g=a=>{o(a),d(!0),x(!1),m(!j(a).ok)},b=()=>{const a=j(c);if(!a.ok||!s){m(!a.ok);return}n.mutate({role:s.role,list_schema:a.value??null,change_summary:h.trim()||void 0},{onSuccess:()=>{d(!1),x(!0),u(""),window.setTimeout(()=>x(!1),2500)}})},k=a=>{o(a?JSON.stringify(a,null,2):""),d(!0),x(!1),m(!1)};return r?e.jsxs("div",{className:"animate-pulse space-y-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-96 bg-surface-sunken rounded"})]}):s?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-start justify-between mb-10",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-3 mb-1",children:[e.jsx(v,{className:"w-5 h-5 text-accent",strokeWidth:1.5}),e.jsx("h1",{className:"heading-3",children:"Escalations List Schema"}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:dashboard.md:escalations-list-schema"},className:"text-text-quaternary hover:text-accent transition-colors",title:"Open docs for this page",children:e.jsx(C,{className:"w-4 h-4",strokeWidth:1.5})}),e.jsx("span",{className:"font-mono text-sm text-text-tertiary",children:s.role}),s.current_list_schema_version!=null&&e.jsxs("span",{className:"text-2xs font-mono text-text-quaternary",children:["v",s.current_list_schema_version," in use"]})]}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:hitl-guide.md:escalations-list-schema"},className:"text-2xs text-accent hover:underline pl-8",children:"List authoring reference — layout modes, the active card, history, and token binding →"})]}),e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[N&&e.jsxs("span",{className:"flex items-center gap-1 text-xs text-status-success animate-page-enter",children:[e.jsx(O,{className:"w-3 h-3"})," Saved"]}),n.error&&e.jsx("span",{className:"text-xs text-status-error max-w-[220px] truncate",children:n.error.message}),e.jsx("button",{onClick:b,disabled:!y||p||n.isPending,className:"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:n.isPending?"Saving…":"Save Version"})]})]}),e.jsxs("div",{className:"grid grid-cols-3 gap-16 items-start",children:[e.jsxs("div",{className:"col-span-2 space-y-6",children:[e.jsx("textarea",{value:c,onChange:a=>g(a.target.value),rows:28,spellCheck:!1,className:"input text-xs font-mono w-full resize-y leading-relaxed",placeholder:V}),p&&e.jsx("p",{className:"text-2xs text-status-error -mt-4",children:"Invalid JSON"}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5",children:"Change Summary"}),e.jsx("input",{type:"text",value:h,onChange:a=>u(a.target.value),placeholder:"e.g., Added the history column",className:"input text-sm w-full"}),e.jsx("p",{className:"text-2xs text-text-quaternary mt-1.5",children:"Recorded on the version this save creates. Versions independently of the resolve form schema."})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border",children:[e.jsx(R,{className:"w-4 h-4 text-text-quaternary",strokeWidth:1.5}),e.jsx("h2",{className:"section-h2",children:"Versions"})]}),e.jsx(J,{role:s.role,onLoad:k})]})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center py-20 text-center",children:[e.jsx(v,{className:"w-12 h-12 text-text-quaternary mb-4",strokeWidth:1}),e.jsx("h2",{className:"heading-3 mb-2",children:"Role not found"})]})}function J({role:i,onLoad:t}){const{data:r}=_(i),n=(r==null?void 0:r.versions)??[];return n.length===0?e.jsx("p",{className:"text-2xs text-text-tertiary leading-relaxed",children:"Versions appear here after the first save. Each list-schema change adds one; earlier versions stay viewable and loadable as an editing base."}):e.jsx("div",{className:"divide-y divide-surface-border/40",children:n.map(s=>e.jsx(P,{role:i,summary:s,onLoad:t},s.version))})}function P({role:i,summary:t,onLoad:r}){const[n,s]=l.useState(!1),c=L(i,t.version,n);return e.jsxs("div",{className:"py-2",children:[e.jsxs("button",{onClick:()=>s(o=>!o),className:"w-full text-left group",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-xs font-mono text-text-secondary",children:["v",t.version]}),t.is_current&&e.jsx("span",{className:"text-2xs font-bold uppercase tracking-wider text-accent",children:"current"}),e.jsx("span",{className:"flex-1"}),e.jsx("span",{className:"text-2xs text-text-quaternary shrink-0",children:new Date(t.created_at).toLocaleDateString()})]}),t.change_summary&&e.jsx("p",{className:"mt-0.5 text-2xs text-text-tertiary truncate",children:t.change_summary})]}),n&&e.jsx("div",{className:"mt-2 space-y-2",children:c.data?e.jsxs(e.Fragment,{children:[c.data.list_schema?e.jsx(E,{data:c.data.list_schema,defaultCollapsed:!0}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"This version carries no list schema."}),e.jsx("button",{onClick:()=>r(c.data.list_schema??null),className:"text-2xs text-accent hover:underline",children:"Load into editor"})]}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"Loading…"})})]})}export{F as EscalationListSchemaPage};
|
|
20
|
+
//# sourceMappingURL=EscalationListSchemaPage-h9JO_q5f.js.map
|