@hotmeshio/long-tail 0.30.5 → 0.31.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.
Files changed (367) hide show
  1. package/build/api/labels/errors.d.ts +3 -0
  2. package/build/api/labels/errors.js +15 -0
  3. package/build/api/labels/index.d.ts +2 -0
  4. package/build/api/labels/index.js +17 -0
  5. package/build/api/labels/issue.d.ts +54 -0
  6. package/build/api/labels/issue.js +144 -0
  7. package/build/api/labels/templates.d.ts +29 -0
  8. package/build/api/labels/templates.js +98 -0
  9. package/build/api/users.d.ts +8 -4
  10. package/build/api/users.js +51 -13
  11. package/build/index.d.ts +1 -0
  12. package/build/index.js +4 -2
  13. package/build/lib/db/schemas/044_label_sheets.sql +65 -0
  14. package/build/lib/db/schemas/045_label_job_delivery.sql +14 -0
  15. package/build/lib/labels/transports.d.ts +23 -0
  16. package/build/lib/labels/transports.js +39 -0
  17. package/build/modules/auth.d.ts +2 -0
  18. package/build/modules/auth.js +3 -1
  19. package/build/modules/capabilities.d.ts +5 -1
  20. package/build/modules/capabilities.js +38 -8
  21. package/build/modules/config.d.ts +10 -0
  22. package/build/modules/config.js +10 -0
  23. package/build/routes/index.js +2 -0
  24. package/build/routes/labels.d.ts +2 -0
  25. package/build/routes/labels.js +135 -0
  26. package/build/routes/users.js +1 -1
  27. package/build/services/labels/content.d.ts +13 -0
  28. package/build/services/labels/content.js +81 -0
  29. package/build/services/labels/errors.d.ts +5 -0
  30. package/build/services/labels/errors.js +12 -0
  31. package/build/services/labels/index.d.ts +11 -0
  32. package/build/services/labels/index.js +52 -0
  33. package/build/services/labels/issue.d.ts +38 -0
  34. package/build/services/labels/issue.js +181 -0
  35. package/build/services/labels/jobs.d.ts +37 -0
  36. package/build/services/labels/jobs.js +75 -0
  37. package/build/services/labels/media.d.ts +11 -0
  38. package/build/services/labels/media.js +44 -0
  39. package/build/services/labels/reissue.d.ts +13 -0
  40. package/build/services/labels/reissue.js +65 -0
  41. package/build/services/labels/render-pdf.d.ts +11 -0
  42. package/build/services/labels/render-pdf.js +138 -0
  43. package/build/services/labels/targets.d.ts +18 -0
  44. package/build/services/labels/targets.js +216 -0
  45. package/build/services/labels/templates/index.d.ts +3 -0
  46. package/build/services/labels/templates/index.js +18 -0
  47. package/build/services/labels/templates/read.d.ts +4 -0
  48. package/build/services/labels/templates/read.js +36 -0
  49. package/build/services/labels/templates/sql.d.ts +8 -0
  50. package/build/services/labels/templates/sql.js +50 -0
  51. package/build/services/labels/templates/validate.d.ts +19 -0
  52. package/build/services/labels/templates/validate.js +93 -0
  53. package/build/services/labels/templates/write.d.ts +16 -0
  54. package/build/services/labels/templates/write.js +75 -0
  55. package/build/services/labels/token.d.ts +9 -0
  56. package/build/services/labels/token.js +28 -0
  57. package/build/services/labels/verify.d.ts +4 -0
  58. package/build/services/labels/verify.js +38 -0
  59. package/build/services/scan-code/validate.d.ts +3 -0
  60. package/build/services/scan-code/validate.js +10 -0
  61. package/build/services/user/crud.d.ts +13 -1
  62. package/build/services/user/crud.js +42 -11
  63. package/build/services/user/index.d.ts +3 -2
  64. package/build/services/user/index.js +7 -1
  65. package/build/services/user/redact.d.ts +17 -0
  66. package/build/services/user/redact.js +32 -0
  67. package/build/services/user/sql.d.ts +8 -15
  68. package/build/services/user/sql.js +27 -3
  69. package/build/services/user/types.d.ts +21 -1
  70. package/build/shared/labels/compose.d.ts +12 -0
  71. package/build/shared/labels/compose.js +43 -0
  72. package/build/shared/labels/index.d.ts +9 -0
  73. package/build/shared/labels/index.js +54 -0
  74. package/build/shared/labels/label-content.d.ts +17 -0
  75. package/build/shared/labels/label-content.js +137 -0
  76. package/build/shared/labels/layout.d.ts +23 -0
  77. package/build/shared/labels/layout.js +81 -0
  78. package/build/shared/labels/media.d.ts +22 -0
  79. package/build/shared/labels/media.js +116 -0
  80. package/build/shared/labels/render-svg.d.ts +10 -0
  81. package/build/shared/labels/render-svg.js +58 -0
  82. package/build/shared/labels/render-zpl.d.ts +9 -0
  83. package/build/shared/labels/render-zpl.js +63 -0
  84. package/build/shared/labels/symbol.d.ts +18 -0
  85. package/build/shared/labels/symbol.js +74 -0
  86. package/build/shared/labels/tokens.d.ts +18 -0
  87. package/build/shared/labels/tokens.js +43 -0
  88. package/build/shared/labels/types.d.ts +105 -0
  89. package/build/shared/labels/types.js +11 -0
  90. package/build/start/adapters.js +16 -0
  91. package/build/start/workers.js +18 -2
  92. package/build/system/mcp-servers/admin/index.js +2 -0
  93. package/build/system/mcp-servers/admin/labels-schemas.d.ts +192 -0
  94. package/build/system/mcp-servers/admin/labels-schemas.js +46 -0
  95. package/build/system/mcp-servers/admin/labels.d.ts +9 -0
  96. package/build/system/mcp-servers/admin/labels.js +101 -0
  97. package/build/system/mcp-servers/admin/schemas.d.ts +30 -30
  98. package/build/system/mcp-servers/admin/users.js +1 -1
  99. package/build/system/seed/tool-manifests-admin.js +5 -0
  100. package/build/tsconfig.tsbuildinfo +1 -1
  101. package/build/types/capability.d.ts +2 -1
  102. package/build/types/index.d.ts +2 -1
  103. package/build/types/labels/index.d.ts +4 -0
  104. package/build/types/labels/index.js +2 -0
  105. package/build/types/labels/job.d.ts +51 -0
  106. package/build/types/labels/job.js +2 -0
  107. package/build/types/labels/source.d.ts +44 -0
  108. package/build/types/labels/source.js +2 -0
  109. package/build/types/labels/template.d.ts +46 -0
  110. package/build/types/labels/template.js +2 -0
  111. package/build/types/labels/transport.d.ts +24 -0
  112. package/build/types/labels/transport.js +2 -0
  113. package/build/types/scan-code/rule.d.ts +5 -0
  114. package/build/types/startup.d.ts +37 -0
  115. package/build/types/user.d.ts +2 -0
  116. package/dashboard/dist/assets/{AdminDashboard-D12QiMM5.js → AdminDashboard-BafKaq50.js} +2 -2
  117. package/dashboard/dist/assets/{AdminDashboard-D12QiMM5.js.map → AdminDashboard-BafKaq50.js.map} +1 -1
  118. package/dashboard/dist/assets/{AgentConfigPage-BKQd9b8m.js → AgentConfigPage-CFHf31DT.js} +6 -6
  119. package/dashboard/dist/assets/{AgentConfigPage-BKQd9b8m.js.map → AgentConfigPage-CFHf31DT.js.map} +1 -1
  120. package/dashboard/dist/assets/{AgentDetailPage-ie-DxsIj.js → AgentDetailPage-CEvz5rJr.js} +3 -3
  121. package/dashboard/dist/assets/{AgentDetailPage-ie-DxsIj.js.map → AgentDetailPage-CEvz5rJr.js.map} +1 -1
  122. package/dashboard/dist/assets/{AgentsPage-Nqm2DxCP.js → AgentsPage-DjQaio0O.js} +2 -2
  123. package/dashboard/dist/assets/{AgentsPage-Nqm2DxCP.js.map → AgentsPage-DjQaio0O.js.map} +1 -1
  124. package/dashboard/dist/assets/AvailableEscalationsPage-UB03VTJx.js +2 -0
  125. package/dashboard/dist/assets/AvailableEscalationsPage-UB03VTJx.js.map +1 -0
  126. package/dashboard/dist/assets/{BotPicker-BizdmeJp.js → BotPicker-C7MNWOF7.js} +2 -2
  127. package/dashboard/dist/assets/{BotPicker-BizdmeJp.js.map → BotPicker-C7MNWOF7.js.map} +1 -1
  128. package/dashboard/dist/assets/BulkAssignModal-h5fX-_cV.js +2 -0
  129. package/dashboard/dist/assets/{BulkAssignModal-BWfzPrRk.js.map → BulkAssignModal-h5fX-_cV.js.map} +1 -1
  130. package/dashboard/dist/assets/{CapabilitiesPage-DT8q7Zlh.js → CapabilitiesPage-BIzDl-ZT.js} +2 -2
  131. package/dashboard/dist/assets/{CapabilitiesPage-DT8q7Zlh.js.map → CapabilitiesPage-BIzDl-ZT.js.map} +1 -1
  132. package/dashboard/dist/assets/{CollapsibleSection-al5mqR4O.js → CollapsibleSection-B5TivtEg.js} +2 -2
  133. package/dashboard/dist/assets/{CollapsibleSection-al5mqR4O.js.map → CollapsibleSection-B5TivtEg.js.map} +1 -1
  134. package/dashboard/dist/assets/{ConfirmDeleteModal-BrlVktv2.js → ConfirmDeleteModal-CB4nKePb.js} +2 -2
  135. package/dashboard/dist/assets/{ConfirmDeleteModal-BrlVktv2.js.map → ConfirmDeleteModal-CB4nKePb.js.map} +1 -1
  136. package/dashboard/dist/assets/{ConnectedAppsPage-CLAsJ2yQ.js → ConnectedAppsPage-B3S5gVWw.js} +2 -2
  137. package/dashboard/dist/assets/{ConnectedAppsPage-CLAsJ2yQ.js.map → ConnectedAppsPage-B3S5gVWw.js.map} +1 -1
  138. package/dashboard/dist/assets/{CountdownTimer-D2D68exl.js → CountdownTimer-BHVIq3kA.js} +2 -2
  139. package/dashboard/dist/assets/{CountdownTimer-D2D68exl.js.map → CountdownTimer-BHVIq3kA.js.map} +1 -1
  140. package/dashboard/dist/assets/CredentialConfirmModal-w0AGmCIe.js +2 -0
  141. package/dashboard/dist/assets/CredentialConfirmModal-w0AGmCIe.js.map +1 -0
  142. package/dashboard/dist/assets/{CredentialsPage-DWrZbEI0.js → CredentialsPage-BtCGBJrT.js} +2 -2
  143. package/dashboard/dist/assets/{CredentialsPage-DWrZbEI0.js.map → CredentialsPage-BtCGBJrT.js.map} +1 -1
  144. package/dashboard/dist/assets/{CronLabel-DuXR8iWq.js → CronLabel-BG1rZK-5.js} +2 -2
  145. package/dashboard/dist/assets/{CronLabel-DuXR8iWq.js.map → CronLabel-BG1rZK-5.js.map} +1 -1
  146. package/dashboard/dist/assets/{DropZone-DefYTYQy.js → DropZone-D8ZVdEb5.js} +2 -2
  147. package/dashboard/dist/assets/{DropZone-DefYTYQy.js.map → DropZone-D8ZVdEb5.js.map} +1 -1
  148. package/dashboard/dist/assets/{ElapsedCell-ronqG5-E.js → ElapsedCell-l4m8Yvfv.js} +2 -2
  149. package/dashboard/dist/assets/{ElapsedCell-ronqG5-E.js.map → ElapsedCell-l4m8Yvfv.js.map} +1 -1
  150. package/dashboard/dist/assets/{EntityTimelinePanel-CKsktd84.js → EntityTimelinePanel-BOHlIaxF.js} +2 -2
  151. package/dashboard/dist/assets/{EntityTimelinePanel-CKsktd84.js.map → EntityTimelinePanel-BOHlIaxF.js.map} +1 -1
  152. package/dashboard/dist/assets/{EscalationListSchemaPage-C954ngy3.js → EscalationListSchemaPage-BotlwlX3.js} +3 -3
  153. package/dashboard/dist/assets/{EscalationListSchemaPage-C954ngy3.js.map → EscalationListSchemaPage-BotlwlX3.js.map} +1 -1
  154. package/dashboard/dist/assets/{EscalationSchemaPage-Dij9K0ON.js → EscalationSchemaPage-CfZSVOSD.js} +3 -3
  155. package/dashboard/dist/assets/{EscalationSchemaPage-Dij9K0ON.js.map → EscalationSchemaPage-CfZSVOSD.js.map} +1 -1
  156. package/dashboard/dist/assets/{EscalationTimeline-DT2Owdl0.js → EscalationTimeline-CCKpuRBU.js} +2 -2
  157. package/dashboard/dist/assets/{EscalationTimeline-DT2Owdl0.js.map → EscalationTimeline-CCKpuRBU.js.map} +1 -1
  158. package/dashboard/dist/assets/{EscalationTitleSelect-BdbfMA98.js → EscalationTitleSelect-cpTzPHiW.js} +2 -2
  159. package/dashboard/dist/assets/{EscalationTitleSelect-BdbfMA98.js.map → EscalationTitleSelect-cpTzPHiW.js.map} +1 -1
  160. package/dashboard/dist/assets/{EscalationsOverview-niahs84P.js → EscalationsOverview-DS-nK0qV.js} +2 -2
  161. package/dashboard/dist/assets/{EscalationsOverview-niahs84P.js.map → EscalationsOverview-DS-nK0qV.js.map} +1 -1
  162. package/dashboard/dist/assets/{EventTable-DuLMBrSx.js → EventTable-UfebhJf0.js} +2 -2
  163. package/dashboard/dist/assets/{EventTable-DuLMBrSx.js.map → EventTable-UfebhJf0.js.map} +1 -1
  164. package/dashboard/dist/assets/{FilterBar-jtY6KNpL.js → FilterBar-newA5l-X.js} +2 -2
  165. package/dashboard/dist/assets/{FilterBar-jtY6KNpL.js.map → FilterBar-newA5l-X.js.map} +1 -1
  166. package/dashboard/dist/assets/{GraphInvokePage-DSRAI0Zt.js → GraphInvokePage-6SCDyQcK.js} +2 -2
  167. package/dashboard/dist/assets/{GraphInvokePage-DSRAI0Zt.js.map → GraphInvokePage-6SCDyQcK.js.map} +1 -1
  168. package/dashboard/dist/assets/HomePage-BEc-XdKj.js +2 -0
  169. package/dashboard/dist/assets/{HomePage-BUlSnCg8.js.map → HomePage-BEc-XdKj.js.map} +1 -1
  170. package/dashboard/dist/assets/LabelJobsPage-BO50zXT5.js +2 -0
  171. package/dashboard/dist/assets/LabelJobsPage-BO50zXT5.js.map +1 -0
  172. package/dashboard/dist/assets/{LinePage-BJKhVM9Y.js → LinePage-NYxJQ-ex.js} +3 -3
  173. package/dashboard/dist/assets/{LinePage-BJKhVM9Y.js.map → LinePage-NYxJQ-ex.js.map} +1 -1
  174. package/dashboard/dist/assets/{LinesPage-CrpJua3_.js → LinesPage-RbWTaQRh.js} +3 -3
  175. package/dashboard/dist/assets/{LinesPage-CrpJua3_.js.map → LinesPage-RbWTaQRh.js.map} +1 -1
  176. package/dashboard/dist/assets/{ListToolbar-BN0UPt0L.js → ListToolbar-B-E9-toh.js} +2 -2
  177. package/dashboard/dist/assets/{ListToolbar-BN0UPt0L.js.map → ListToolbar-B-E9-toh.js.map} +1 -1
  178. package/dashboard/dist/assets/{McpOverview-BgbJXYPh.js → McpOverview-Shcf9QhZ.js} +2 -2
  179. package/dashboard/dist/assets/{McpOverview-BgbJXYPh.js.map → McpOverview-Shcf9QhZ.js.map} +1 -1
  180. package/dashboard/dist/assets/{McpQueryDetailPage-BNjm2nrc.js → McpQueryDetailPage-D8kmjy2v.js} +3 -3
  181. package/dashboard/dist/assets/{McpQueryDetailPage-BNjm2nrc.js.map → McpQueryDetailPage-D8kmjy2v.js.map} +1 -1
  182. package/dashboard/dist/assets/{McpQueryPage-DplR8wP9.js → McpQueryPage-ck-o5m8A.js} +2 -2
  183. package/dashboard/dist/assets/{McpQueryPage-DplR8wP9.js.map → McpQueryPage-ck-o5m8A.js.map} +1 -1
  184. package/dashboard/dist/assets/McpRunDetailPage-BJKkM9-3.js +2 -0
  185. package/dashboard/dist/assets/{McpRunDetailPage-BHVEcKV5.js.map → McpRunDetailPage-BJKkM9-3.js.map} +1 -1
  186. package/dashboard/dist/assets/{McpRunsPage-CtlLagcs.js → McpRunsPage-DqhHfD5G.js} +2 -2
  187. package/dashboard/dist/assets/{McpRunsPage-CtlLagcs.js.map → McpRunsPage-DqhHfD5G.js.map} +1 -1
  188. package/dashboard/dist/assets/{NamespacePill-CeSfHH-j.js → NamespacePill--uIaGtrN.js} +2 -2
  189. package/dashboard/dist/assets/{NamespacePill-CeSfHH-j.js.map → NamespacePill--uIaGtrN.js.map} +1 -1
  190. package/dashboard/dist/assets/{OperationsPage-DH67bcW9.js → OperationsPage-D6B9Vv__.js} +3 -3
  191. package/dashboard/dist/assets/{OperationsPage-DH67bcW9.js.map → OperationsPage-D6B9Vv__.js.map} +1 -1
  192. package/dashboard/dist/assets/OperatorDashboard-CIH1cxjQ.js +2 -0
  193. package/dashboard/dist/assets/{OperatorDashboard-CMkgMewd.js.map → OperatorDashboard-CIH1cxjQ.js.map} +1 -1
  194. package/dashboard/dist/assets/{PageHeader-DiGWAQlR.js → PageHeader-DClJ33fV.js} +2 -2
  195. package/dashboard/dist/assets/{PageHeader-DiGWAQlR.js.map → PageHeader-DClJ33fV.js.map} +1 -1
  196. package/dashboard/dist/assets/{PageHeaderWithStats-DwnKQ06j.js → PageHeaderWithStats-C-TrbKQL.js} +2 -2
  197. package/dashboard/dist/assets/{PageHeaderWithStats-DwnKQ06j.js.map → PageHeaderWithStats-C-TrbKQL.js.map} +1 -1
  198. package/dashboard/dist/assets/PersonaDetailPage-DjvfyIph.js +2 -0
  199. package/dashboard/dist/assets/{PersonaDetailPage-Cs1FFdNq.js.map → PersonaDetailPage-DjvfyIph.js.map} +1 -1
  200. package/dashboard/dist/assets/{PersonasPage-CHTRdneF.js → PersonasPage-D7ryA8Vc.js} +2 -2
  201. package/dashboard/dist/assets/{PersonasPage-CHTRdneF.js.map → PersonasPage-D7ryA8Vc.js.map} +1 -1
  202. package/dashboard/dist/assets/{PortalPage-ChB5pBTZ.js → PortalPage-D3ybr0mo.js} +2 -2
  203. package/dashboard/dist/assets/{PortalPage-ChB5pBTZ.js.map → PortalPage-D3ybr0mo.js.map} +1 -1
  204. package/dashboard/dist/assets/PrintLabelsPanel-D0HF5x8b.js +2 -0
  205. package/dashboard/dist/assets/PrintLabelsPanel-D0HF5x8b.js.map +1 -0
  206. package/dashboard/dist/assets/{ProcessDetailPage-CzyqSfal.js → ProcessDetailPage-0xS9EdHe.js} +2 -2
  207. package/dashboard/dist/assets/{ProcessDetailPage-CzyqSfal.js.map → ProcessDetailPage-0xS9EdHe.js.map} +1 -1
  208. package/dashboard/dist/assets/{ProcessesListPage-rOwNkaU9.js → ProcessesListPage-93SMj1Fd.js} +2 -2
  209. package/dashboard/dist/assets/{ProcessesListPage-rOwNkaU9.js.map → ProcessesListPage-93SMj1Fd.js.map} +1 -1
  210. package/dashboard/dist/assets/{ResolverForm-DibnlZ-A.js → ResolverForm-DfogHDzl.js} +5 -5
  211. package/dashboard/dist/assets/{ResolverForm-DibnlZ-A.js.map → ResolverForm-DfogHDzl.js.map} +1 -1
  212. package/dashboard/dist/assets/{RoleDetailPage-Bv9R7iQC.js → RoleDetailPage-DlUOJWSB.js} +3 -3
  213. package/dashboard/dist/assets/{RoleDetailPage-Bv9R7iQC.js.map → RoleDetailPage-DlUOJWSB.js.map} +1 -1
  214. package/dashboard/dist/assets/{RolePill-D3hkAdKf.js → RolePill-BjD1Qw5y.js} +2 -2
  215. package/dashboard/dist/assets/{RolePill-D3hkAdKf.js.map → RolePill-BjD1Qw5y.js.map} +1 -1
  216. package/dashboard/dist/assets/RolesPage-DqhHSvaw.js +2 -0
  217. package/dashboard/dist/assets/{RolesPage-CUP7EH5U.js.map → RolesPage-DqhHSvaw.js.map} +1 -1
  218. package/dashboard/dist/assets/{RosterWidget-DZtmQcjU.js → RosterWidget-BrIq61HR.js} +2 -2
  219. package/dashboard/dist/assets/{RosterWidget-DZtmQcjU.js.map → RosterWidget-BrIq61HR.js.map} +1 -1
  220. package/dashboard/dist/assets/RunAsSelector-D6zQrCNr.js +2 -0
  221. package/dashboard/dist/assets/{RunAsSelector-B9-1vgZr.js.map → RunAsSelector-D6zQrCNr.js.map} +1 -1
  222. package/dashboard/dist/assets/ScanCodesPage-B2S9DR2f.js +2 -0
  223. package/dashboard/dist/assets/{ScanCodesPage-KN_e2rJl.js.map → ScanCodesPage-B2S9DR2f.js.map} +1 -1
  224. package/dashboard/dist/assets/ScanSchemeDetailPage-gpxKN-pL.js +5 -0
  225. package/dashboard/dist/assets/ScanSchemeDetailPage-gpxKN-pL.js.map +1 -0
  226. package/dashboard/dist/assets/ScanStationPage-B-UTgXsG.js +2 -0
  227. package/dashboard/dist/assets/{ScanStationPage-B0qbUOwc.js.map → ScanStationPage-B-UTgXsG.js.map} +1 -1
  228. package/dashboard/dist/assets/{SchemeMatchFields-CL7TKnw3.js → SchemeMatchFields-BK24aa7K.js} +2 -2
  229. package/dashboard/dist/assets/{SchemeMatchFields-CL7TKnw3.js.map → SchemeMatchFields-BK24aa7K.js.map} +1 -1
  230. package/dashboard/dist/assets/{ScopeBadge-DGz79rmG.js → ScopeBadge-ofLQ6HW-.js} +2 -2
  231. package/dashboard/dist/assets/{ScopeBadge-DGz79rmG.js.map → ScopeBadge-ofLQ6HW-.js.map} +1 -1
  232. package/dashboard/dist/assets/{StickyPagination-CUwxfBQH.js → StickyPagination-CgVv7vc_.js} +2 -2
  233. package/dashboard/dist/assets/{StickyPagination-CUwxfBQH.js.map → StickyPagination-CgVv7vc_.js.map} +1 -1
  234. package/dashboard/dist/assets/{StreamMessageDetail-DOjcO7d2.js → StreamMessageDetail-B9sxDVw4.js} +2 -2
  235. package/dashboard/dist/assets/{StreamMessageDetail-DOjcO7d2.js.map → StreamMessageDetail-B9sxDVw4.js.map} +1 -1
  236. package/dashboard/dist/assets/{SwimlaneTimeline-SPMpoLHP.js → SwimlaneTimeline-DQc0CYSL.js} +2 -2
  237. package/dashboard/dist/assets/{SwimlaneTimeline-SPMpoLHP.js.map → SwimlaneTimeline-DQc0CYSL.js.map} +1 -1
  238. package/dashboard/dist/assets/{TagInput-DaIdhv3h.js → TagInput-47qVYzke.js} +2 -2
  239. package/dashboard/dist/assets/{TagInput-DaIdhv3h.js.map → TagInput-47qVYzke.js.map} +1 -1
  240. package/dashboard/dist/assets/{TaskDetailPage-DvfzR2IQ.js → TaskDetailPage-D6VOsA1f.js} +2 -2
  241. package/dashboard/dist/assets/{TaskDetailPage-DvfzR2IQ.js.map → TaskDetailPage-D6VOsA1f.js.map} +1 -1
  242. package/dashboard/dist/assets/{TaskQueuePill-sUA1nZe-.js → TaskQueuePill-C5KmdXAl.js} +2 -2
  243. package/dashboard/dist/assets/{TaskQueuePill-sUA1nZe-.js.map → TaskQueuePill-C5KmdXAl.js.map} +1 -1
  244. package/dashboard/dist/assets/{TasksListPage-Jp6wmL7t.js → TasksListPage-DG8hvO8G.js} +2 -2
  245. package/dashboard/dist/assets/{TasksListPage-Jp6wmL7t.js.map → TasksListPage-DG8hvO8G.js.map} +1 -1
  246. package/dashboard/dist/assets/{TimeAgo-BCQA0Gb_.js → TimeAgo-CL02EhFi.js} +2 -2
  247. package/dashboard/dist/assets/{TimeAgo-BCQA0Gb_.js.map → TimeAgo-CL02EhFi.js.map} +1 -1
  248. package/dashboard/dist/assets/{TimestampCell-xhdMHDQt.js → TimestampCell-BE7oKVJg.js} +2 -2
  249. package/dashboard/dist/assets/{TimestampCell-xhdMHDQt.js.map → TimestampCell-BE7oKVJg.js.map} +1 -1
  250. package/dashboard/dist/assets/{ToolPill-CpBAFdEX.js → ToolPill-Cp9Drk1s.js} +2 -2
  251. package/dashboard/dist/assets/{ToolPill-CpBAFdEX.js.map → ToolPill-Cp9Drk1s.js.map} +1 -1
  252. package/dashboard/dist/assets/{ToolTestPanel-D8M3ITrJ.js → ToolTestPanel-CRecLh6S.js} +2 -2
  253. package/dashboard/dist/assets/{ToolTestPanel-D8M3ITrJ.js.map → ToolTestPanel-CRecLh6S.js.map} +1 -1
  254. package/dashboard/dist/assets/{TopicDetailPage-DdBJQp31.js → TopicDetailPage-QzkinTfN.js} +3 -3
  255. package/dashboard/dist/assets/{TopicDetailPage-DdBJQp31.js.map → TopicDetailPage-QzkinTfN.js.map} +1 -1
  256. package/dashboard/dist/assets/{TopicsPage-vpvsynD9.js → TopicsPage-D8dW6J65.js} +2 -2
  257. package/dashboard/dist/assets/{TopicsPage-vpvsynD9.js.map → TopicsPage-D8dW6J65.js.map} +1 -1
  258. package/dashboard/dist/assets/{UserName-BK6Lzsdw.js → UserName-Dw3R_zrl.js} +2 -2
  259. package/dashboard/dist/assets/{UserName-BK6Lzsdw.js.map → UserName-Dw3R_zrl.js.map} +1 -1
  260. package/dashboard/dist/assets/WorkflowExecutionPage-BTocv8or.js +2 -0
  261. package/dashboard/dist/assets/{WorkflowExecutionPage-DkM1TS4I.js.map → WorkflowExecutionPage-BTocv8or.js.map} +1 -1
  262. package/dashboard/dist/assets/{WorkflowPill-CKHip3hH.js → WorkflowPill-BiLYq__w.js} +2 -2
  263. package/dashboard/dist/assets/{WorkflowPill-CKHip3hH.js.map → WorkflowPill-BiLYq__w.js.map} +1 -1
  264. package/dashboard/dist/assets/WorkflowsDashboard-CIPEt3et.js +2 -0
  265. package/dashboard/dist/assets/{WorkflowsDashboard-g_N4y3QS.js.map → WorkflowsDashboard-CIPEt3et.js.map} +1 -1
  266. package/dashboard/dist/assets/{WorkflowsOverview-Drjo-Lu-.js → WorkflowsOverview-Bj4MJIE-.js} +2 -2
  267. package/dashboard/dist/assets/{WorkflowsOverview-Drjo-Lu-.js.map → WorkflowsOverview-Bj4MJIE-.js.map} +1 -1
  268. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CLltNHjZ.js → YamlWorkflowDetailPage-DMEk5k9-.js} +2 -2
  269. package/dashboard/dist/assets/{YamlWorkflowDetailPage-CLltNHjZ.js.map → YamlWorkflowDetailPage-DMEk5k9-.js.map} +1 -1
  270. package/dashboard/dist/assets/{YamlWorkflowsPage-CxQCB-Zi.js → YamlWorkflowsPage-F2yyyoiB.js} +2 -2
  271. package/dashboard/dist/assets/{YamlWorkflowsPage-CxQCB-Zi.js.map → YamlWorkflowsPage-F2yyyoiB.js.map} +1 -1
  272. package/dashboard/dist/assets/{agents-Dk25jjhj.js → agents-mSgxEPpI.js} +2 -2
  273. package/dashboard/dist/assets/{agents-Dk25jjhj.js.map → agents-mSgxEPpI.js.map} +1 -1
  274. package/dashboard/dist/assets/{bots-bRkCqhlq.js → bots-p4Hp0CV6.js} +2 -2
  275. package/dashboard/dist/assets/{bots-bRkCqhlq.js.map → bots-p4Hp0CV6.js.map} +1 -1
  276. package/dashboard/dist/assets/{capabilities-DARawLry.js → capabilities-D_-I_SXR.js} +2 -2
  277. package/dashboard/dist/assets/{capabilities-DARawLry.js.map → capabilities-D_-I_SXR.js.map} +1 -1
  278. package/dashboard/dist/assets/{controlplane-DKccxxs6.js → controlplane-DjdoQnRU.js} +2 -2
  279. package/dashboard/dist/assets/{controlplane-DKccxxs6.js.map → controlplane-DjdoQnRU.js.map} +1 -1
  280. package/dashboard/dist/assets/{escalation-columns-BaYuzMYf.js → escalation-columns-R_IEVQjQ.js} +2 -2
  281. package/dashboard/dist/assets/{escalation-columns-BaYuzMYf.js.map → escalation-columns-R_IEVQjQ.js.map} +1 -1
  282. package/dashboard/dist/assets/{escalation-items-CwbNFnsH.js → escalation-items-BGZlJMpk.js} +2 -2
  283. package/dashboard/dist/assets/{escalation-items-CwbNFnsH.js.map → escalation-items-BGZlJMpk.js.map} +1 -1
  284. package/dashboard/dist/assets/{index-Cf6qCgTf.js → index-BEMdD58x.js} +2 -2
  285. package/dashboard/dist/assets/{index-Cf6qCgTf.js.map → index-BEMdD58x.js.map} +1 -1
  286. package/dashboard/dist/assets/index-BUmz0NMg.css +1 -0
  287. package/dashboard/dist/assets/{index-hKOlahpE.js → index-BuCEhhnl.js} +2 -2
  288. package/dashboard/dist/assets/{index-hKOlahpE.js.map → index-BuCEhhnl.js.map} +1 -1
  289. package/dashboard/dist/assets/{index-D1uliPqS.js → index-BxbQmeqh.js} +2 -2
  290. package/dashboard/dist/assets/{index-D1uliPqS.js.map → index-BxbQmeqh.js.map} +1 -1
  291. package/dashboard/dist/assets/index-C5osluV3.js +2 -0
  292. package/dashboard/dist/assets/{index-CtrSYqmL.js.map → index-C5osluV3.js.map} +1 -1
  293. package/dashboard/dist/assets/{index-CU2UW2y6.js → index-C_dAJUzh.js} +25 -25
  294. package/dashboard/dist/assets/index-C_dAJUzh.js.map +1 -0
  295. package/dashboard/dist/assets/{index-Bx9HVzuT.js → index-D-FYj9jT.js} +2 -2
  296. package/dashboard/dist/assets/{index-Bx9HVzuT.js.map → index-D-FYj9jT.js.map} +1 -1
  297. package/dashboard/dist/assets/{index-BZgtzkkG.js → index-D6K_IgCz.js} +2 -2
  298. package/dashboard/dist/assets/{index-BZgtzkkG.js.map → index-D6K_IgCz.js.map} +1 -1
  299. package/dashboard/dist/assets/index-D7L_VoBD.js +2 -0
  300. package/dashboard/dist/assets/index-D7L_VoBD.js.map +1 -0
  301. package/dashboard/dist/assets/{index-DgpL_Lgc.js → index-D9RAN9sA.js} +2 -2
  302. package/dashboard/dist/assets/{index-DgpL_Lgc.js.map → index-D9RAN9sA.js.map} +1 -1
  303. package/dashboard/dist/assets/{index-dShcvdg3.js → index-DUYAGxwc.js} +2 -2
  304. package/dashboard/dist/assets/{index-dShcvdg3.js.map → index-DUYAGxwc.js.map} +1 -1
  305. package/dashboard/dist/assets/{index-E_hJ7vtX.js → index-Dk2KKo0z.js} +2 -2
  306. package/dashboard/dist/assets/{index-E_hJ7vtX.js.map → index-Dk2KKo0z.js.map} +1 -1
  307. package/dashboard/dist/assets/{index-DU2QCbgH.js → index-DlJkZ12b.js} +2 -2
  308. package/dashboard/dist/assets/{index-DU2QCbgH.js.map → index-DlJkZ12b.js.map} +1 -1
  309. package/dashboard/dist/assets/{index-kzifJWbM.js → index-FiNThtNU.js} +3 -3
  310. package/dashboard/dist/assets/{index-kzifJWbM.js.map → index-FiNThtNU.js.map} +1 -1
  311. package/dashboard/dist/assets/{knowledge-DOe5ks-E.js → knowledge-DSsPTqkK.js} +2 -2
  312. package/dashboard/dist/assets/{knowledge-DOe5ks-E.js.map → knowledge-DSsPTqkK.js.map} +1 -1
  313. package/dashboard/dist/assets/{machines-Zq_00v7k.js → machines-DSgGtqqr.js} +2 -2
  314. package/dashboard/dist/assets/{machines-Zq_00v7k.js.map → machines-DSgGtqqr.js.map} +1 -1
  315. package/dashboard/dist/assets/{mcp-BL8kD0oh.js → mcp-Cg-_uLw8.js} +2 -2
  316. package/dashboard/dist/assets/{mcp-BL8kD0oh.js.map → mcp-Cg-_uLw8.js.map} +1 -1
  317. package/dashboard/dist/assets/{mcp-query-BpokmJWi.js → mcp-query-DQombEK_.js} +2 -2
  318. package/dashboard/dist/assets/{mcp-query-BpokmJWi.js.map → mcp-query-DQombEK_.js.map} +1 -1
  319. package/dashboard/dist/assets/{personas-BubCnQZ8.js → personas-DYrcoVBu.js} +2 -2
  320. package/dashboard/dist/assets/{personas-BubCnQZ8.js.map → personas-DYrcoVBu.js.map} +1 -1
  321. package/dashboard/dist/assets/{pipelines--DUoMx7K.js → pipelines-wdb4xsey.js} +2 -2
  322. package/dashboard/dist/assets/{pipelines--DUoMx7K.js.map → pipelines-wdb4xsey.js.map} +1 -1
  323. package/dashboard/dist/assets/{tasks-CJXN4OB4.js → tasks-Bil7gvm9.js} +2 -2
  324. package/dashboard/dist/assets/{tasks-CJXN4OB4.js.map → tasks-Bil7gvm9.js.map} +1 -1
  325. package/dashboard/dist/assets/{topics-CAG08Ruo.js → topics-wBqaVhRR.js} +2 -2
  326. package/dashboard/dist/assets/{topics-CAG08Ruo.js.map → topics-wBqaVhRR.js.map} +1 -1
  327. package/dashboard/dist/assets/{useDebouncedValue-DQ2D8cm2.js → useDebouncedValue-CwrY8TpV.js} +2 -2
  328. package/dashboard/dist/assets/{useDebouncedValue-DQ2D8cm2.js.map → useDebouncedValue-CwrY8TpV.js.map} +1 -1
  329. package/dashboard/dist/assets/{useNamespace-DjEVAlJQ.js → useNamespace-Sod_xApz.js} +2 -2
  330. package/dashboard/dist/assets/{useNamespace-DjEVAlJQ.js.map → useNamespace-Sod_xApz.js.map} +1 -1
  331. package/dashboard/dist/assets/{useYamlActivityEvents-etcpfIwo.js → useYamlActivityEvents-CKLInV3C.js} +2 -2
  332. package/dashboard/dist/assets/{useYamlActivityEvents-etcpfIwo.js.map → useYamlActivityEvents-CKLInV3C.js.map} +1 -1
  333. package/dashboard/dist/assets/{validate-YJK_6a7m.js → validate-CYhW1xMJ.js} +2 -2
  334. package/dashboard/dist/assets/{validate-YJK_6a7m.js.map → validate-CYhW1xMJ.js.map} +1 -1
  335. package/dashboard/dist/assets/{vendor-icons-en0_uSRD.js → vendor-icons-ed7mYQw4.js} +2 -2
  336. package/dashboard/dist/assets/{vendor-icons-en0_uSRD.js.map → vendor-icons-ed7mYQw4.js.map} +1 -1
  337. package/dashboard/dist/assets/{yaml-workflows-D97Bz6li.js → yaml-workflows-C20qHo3L.js} +2 -2
  338. package/dashboard/dist/assets/{yaml-workflows-D97Bz6li.js.map → yaml-workflows-C20qHo3L.js.map} +1 -1
  339. package/dashboard/dist/index.html +3 -3
  340. package/docs/api/http/labels.md +115 -0
  341. package/docs/api/http/users.md +2 -0
  342. package/docs/api/mcp/admin.md +76 -1
  343. package/docs/code-owned-configuration.md +1 -1
  344. package/docs/iam.md +12 -0
  345. package/docs/label-sheets.md +267 -0
  346. package/docs/scan-codes.md +11 -5
  347. package/package.json +4 -1
  348. package/dashboard/dist/assets/AvailableEscalationsPage-BRKyG5qZ.js +0 -2
  349. package/dashboard/dist/assets/AvailableEscalationsPage-BRKyG5qZ.js.map +0 -1
  350. package/dashboard/dist/assets/BulkAssignModal-BWfzPrRk.js +0 -2
  351. package/dashboard/dist/assets/HomePage-BUlSnCg8.js +0 -2
  352. package/dashboard/dist/assets/McpRunDetailPage-BHVEcKV5.js +0 -2
  353. package/dashboard/dist/assets/OperatorDashboard-CMkgMewd.js +0 -2
  354. package/dashboard/dist/assets/PersonaDetailPage-Cs1FFdNq.js +0 -2
  355. package/dashboard/dist/assets/RolesPage-CUP7EH5U.js +0 -2
  356. package/dashboard/dist/assets/RunAsSelector-B9-1vgZr.js +0 -2
  357. package/dashboard/dist/assets/ScanCodesPage-KN_e2rJl.js +0 -2
  358. package/dashboard/dist/assets/ScanSchemeDetailPage-CqItQ67Y.js +0 -5
  359. package/dashboard/dist/assets/ScanSchemeDetailPage-CqItQ67Y.js.map +0 -1
  360. package/dashboard/dist/assets/ScanStationPage-B0qbUOwc.js +0 -2
  361. package/dashboard/dist/assets/WorkflowExecutionPage-DkM1TS4I.js +0 -2
  362. package/dashboard/dist/assets/WorkflowsDashboard-g_N4y3QS.js +0 -2
  363. package/dashboard/dist/assets/index-BmzscsXK.css +0 -1
  364. package/dashboard/dist/assets/index-CU2UW2y6.js.map +0 -1
  365. package/dashboard/dist/assets/index-CtrSYqmL.js +0 -2
  366. package/dashboard/dist/assets/index-DU-1gMks.js +0 -2
  367. package/dashboard/dist/assets/index-DU-1gMks.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PersonasPage-CHTRdneF.js","sources":["../../src/pages/admin/personas/CreatePersonaModal.tsx","../../src/components/common/form/UserCombobox.tsx","../../src/pages/admin/personas/AssigneesPanel.tsx","../../src/pages/admin/personas/PersonasPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCreatePersona } from '../../../api/personas';\nimport { Modal } from '../../../components/common/modal/Modal';\n\nexport function CreatePersonaModal({\n open,\n onClose,\n}: {\n open: boolean;\n onClose: () => void;\n}) {\n const navigate = useNavigate();\n const createPersona = useCreatePersona();\n const [key, setKey] = useState('');\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (open) {\n setKey('');\n setTitle('');\n setDescription('');\n createPersona.reset();\n }\n }\n\n const handleCreate = () => {\n const trimmed = key.trim().toLowerCase();\n if (!trimmed) return;\n createPersona.mutate(\n {\n key: trimmed,\n title: title.trim() || undefined,\n description: description.trim() || undefined,\n },\n {\n onSuccess: () => {\n onClose();\n // Land on the detail page to link roles right away.\n navigate(`/admin/personas/${encodeURIComponent(trimmed)}`);\n },\n },\n );\n };\n\n return (\n <Modal open={open} onClose={onClose} title=\"Create Persona\">\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Key (required)\n </label>\n <input\n type=\"text\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n placeholder=\"e.g., floor-manager\"\n className=\"input text-xs w-full font-mono\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') handleCreate();\n }}\n />\n <p className=\"text-2xs text-text-tertiary mt-1\">\n Lowercase letters, numbers, hyphens, and underscores only. Stable — it names the persona everywhere.\n </p>\n </div>\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Title\n </label>\n <input\n type=\"text\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g., Floor Manager\"\n className=\"input text-xs w-full\"\n />\n </div>\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Description\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"The day in the life, one paragraph.\"\n rows={3}\n className=\"input text-xs w-full resize-y\"\n />\n </div>\n\n {createPersona.error && (\n <p className=\"text-xs text-status-error\">{(createPersona.error as Error).message}</p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button onClick={onClose} className=\"btn-secondary text-xs\">\n Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={!key.trim() || createPersona.isPending}\n className=\"btn-primary text-xs\"\n >\n {createPersona.isPending ? 'Creating...' : 'Create'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n","import { useState, useRef, useEffect, useMemo } from 'react';\nimport { useUsers } from '../../../api/users';\nimport { useDebouncedValue } from '../../../hooks/useDebouncedValue';\n\nexport interface UserComboboxSelection {\n id: string;\n label: string;\n}\n\n/**\n * Searchable inline combobox for picking a user account. The filter drives the\n * server-side account search (name, email, external_id), so the list stays\n * bounded no matter how many accounts exist; a footer line discloses when more\n * matches remain than the page shows. Styled to match CapabilityCombobox.\n */\nexport function UserCombobox({ selected, onSelect, excludeIds = [], placeholder = 'Search users…' }: {\n selected: UserComboboxSelection | null;\n onSelect: (user: UserComboboxSelection | null) => void;\n /** Accounts to hide from the results (e.g. already assigned). */\n excludeIds?: string[];\n placeholder?: string;\n}) {\n const [open, setOpen] = useState(false);\n const [filter, setFilter] = useState('');\n const containerRef = useRef<HTMLDivElement>(null);\n\n const debounced = useDebouncedValue(filter);\n const PAGE = 20;\n const { data, isFetching } = useUsers({\n search: debounced.trim() || undefined,\n limit: PAGE,\n }, { enabled: open });\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n function handleClick(e: MouseEvent) {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }, [open]);\n\n const excluded = useMemo(() => new Set(excludeIds), [excludeIds]);\n const users = (data?.users ?? []).filter((u) => !excluded.has(u.id));\n const total = data?.total ?? 0;\n const truncated = total > (data?.users.length ?? 0);\n\n const displayValue = selected?.label ?? '';\n const filterText = open ? filter : displayValue;\n\n return (\n <div ref={containerRef} className=\"relative\">\n <input\n type=\"text\"\n value={filterText}\n onFocus={() => { setOpen(true); setFilter(''); }}\n onChange={(e) => {\n setFilter(e.target.value);\n if (!e.target.value) onSelect(null);\n if (!open) setOpen(true);\n }}\n placeholder={placeholder}\n className=\"input text-xs w-full min-w-0\"\n aria-label=\"User\"\n />\n\n {open && (\n <div className=\"absolute z-[100] left-0 right-0 mt-1 max-h-64 overflow-y-auto rounded-md border border-surface-border bg-surface shadow-lg\">\n {users.map((u) => {\n const label = u.display_name || u.external_id;\n return (\n <button\n key={u.id}\n type=\"button\"\n onClick={() => {\n onSelect({ id: u.id, label });\n setFilter('');\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 hover:bg-surface-hover transition-colors flex items-center gap-3 min-w-0 ${\n selected?.id === u.id ? 'bg-accent/5' : ''\n }`}\n >\n <span className=\"text-xs text-text-primary truncate\">{label}</span>\n {u.email && (\n <span className=\"text-2xs text-text-quaternary truncate\">{u.email}</span>\n )}\n </button>\n );\n })}\n\n {users.length === 0 && (\n <p className=\"px-3 py-4 text-center text-2xs text-text-quaternary\">\n {isFetching ? 'Searching…' : filter ? `No users match \"${filter}\"` : 'No users available'}\n </p>\n )}\n\n {truncated && (\n <p className=\"px-3 py-1.5 text-2xs text-text-quaternary border-t border-surface-border/60 tabular-nums\">\n {users.length} of {total} — type to narrow\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n","import { useState } from 'react';\nimport { X } from 'lucide-react';\nimport { usePersona, useAssignPersona, useUnassignPersona } from '../../../api/personas';\nimport { DateValue } from '../../../components/common/display/DateValue';\nimport { UserCombobox, type UserComboboxSelection } from '../../../components/common/form/UserCombobox';\n\nconst SECTION_HDR = 'text-2xs font-semibold uppercase tracking-widest text-text-tertiary';\n\n/**\n * The 90% surface: pick a persona in the list, assign a user, and they get\n * every linked role — pins, list schemas, and forms compose from the roles.\n * Rendered in the global shell right panel (key 'persona-assignees').\n */\nexport function AssigneesPanel({\n personaKey,\n onClose,\n}: {\n personaKey: string | null;\n onClose?: () => void;\n}) {\n const { data: persona } = usePersona(personaKey ?? '');\n const assign = useAssignPersona();\n const unassign = useUnassignPersona();\n const [pendingUser, setPendingUser] = useState<UserComboboxSelection | null>(null);\n\n const assignees = persona?.assignees ?? [];\n\n const handleAssign = () => {\n if (!persona || !pendingUser) return;\n assign.mutate(\n { userId: pendingUser.id, key: persona.key },\n { onSuccess: () => setPendingUser(null) },\n );\n };\n\n return (\n <div className=\"h-full overflow-y-auto px-5 pt-4 pb-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <p className={SECTION_HDR}>Assignees</p>\n {onClose && (\n <button onClick={onClose} className=\"icon-link\" title=\"Close\" aria-label=\"Close\">\n <X className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n\n {!persona ? (\n <p className=\"text-xs text-text-tertiary\">\n Select a persona to assign users. An assignee joins every linked role\n at its scope — sidebar pins and forms compose from the roles.\n </p>\n ) : (\n <div className=\"space-y-4\">\n <div>\n <p className=\"text-sm text-text-primary\">{persona.title || persona.key}</p>\n <p className=\"text-2xs text-text-tertiary mt-0.5\">\n {persona.roles.length} role{persona.roles.length === 1 ? '' : 's'} per assignment\n </p>\n </div>\n\n {assignees.length === 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n Assign a user to compose their whole surface in one step.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {assignees.map((a) => (\n <span\n key={a.id}\n className=\"flex items-center gap-2.5 w-full min-w-0 pl-2.5 pr-2 py-1 text-xs bg-surface-sunken rounded-full text-text-secondary\"\n >\n <span className=\"flex-1 min-w-0 truncate\" title={a.external_id}>\n {a.display_name || a.external_id}\n </span>\n <span className=\"shrink-0 text-2xs text-text-tertiary\">\n <DateValue date={a.assigned_at} />\n </span>\n <button\n onClick={() => unassign.mutate({ userId: a.id, key: persona.key })}\n className=\"shrink-0 text-text-tertiary hover:text-status-error transition-colors ml-1\"\n title={`Unassign ${a.display_name || a.external_id}`}\n >\n &times;\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"pt-3 border-t border-surface-border space-y-2\">\n <p className={SECTION_HDR}>Assign User</p>\n <UserCombobox\n selected={pendingUser}\n onSelect={setPendingUser}\n excludeIds={assignees.map((a) => a.id)}\n />\n <div className=\"flex justify-end pt-1\">\n <button\n onClick={handleAssign}\n disabled={!pendingUser || assign.isPending}\n className=\"btn-primary text-xs\"\n >\n {assign.isPending ? '...' : 'Assign'}\n </button>\n </div>\n {(assign.error || unassign.error) && (\n <p className=\"text-2xs text-status-error\">\n {((assign.error || unassign.error) as Error).message}\n </p>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n","import { useState, useMemo, useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Pencil } from 'lucide-react';\nimport { usePersonas } from '../../../api/personas';\nimport { FilterBar, FilterInput } from '../../../components/common/data/FilterBar';\nimport type { LTPersonaRecord } from '../../../api/types';\nimport { PageHeader } from '../../../components/common/layout/PageHeader';\nimport { RolePill } from '../../../components/common/display/RolePill';\nimport { useShellPanelOptional } from '../../../hooks/useShellPanel';\nimport { CreatePersonaModal } from './CreatePersonaModal';\nimport { AssigneesPanel } from './AssigneesPanel';\n\n// Shell-panel ownership key — the Assignees panel claims/releases this slot.\nconst ASSIGNEES_PANEL_KEY = 'persona-assignees';\n\n// ── Grid columns ──────────────────────────────────────────────────────────────\n// PERSONA(title, 180px) | KEY(140px) | DESCRIPTION(1fr) | ROLES(220px) | USERS(56px) | CONFIGURE(40px)\n// Mirrors the Roles list: display title leads, exact key is the secondary\n// field, the bundle's role pills preview on the right. Clicking a row selects\n// it for the Assignees panel (the 90% path); the pencil opens configuration.\nconst GRID = '180px 140px 1fr 220px 56px 40px';\n\nconst CELL_TEXT = 'text-text-secondary transition-colors group-hover/row:text-text-primary';\nconst HDR = 'text-2xs font-semibold uppercase tracking-widest text-text-quaternary';\nconst CELL_PY = 'py-2';\nconst ROW_PY = 'py-2.5';\n\n/** Title Case fallback derived from the key, same convention as roles. */\nfunction displayPersonaTitle(p: LTPersonaRecord): string {\n if (p.title) return p.title;\n return p.key.replace(/[-_]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction TableHead() {\n return (\n <div\n className=\"grid gap-4 px-3 border-b border-surface-border bg-surface\"\n style={{ gridTemplateColumns: GRID }}\n >\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Persona</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Key</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Description</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Roles</span>\n <span className={`${HDR} ${CELL_PY} flex items-center justify-end`}>Users</span>\n <span className={CELL_PY} />\n </div>\n );\n}\n\nconst ROLE_PREVIEW_LIMIT = 3;\n\nfunction PersonaRow({ persona, active, onClick, onConfigure }: {\n persona: LTPersonaRecord;\n active: boolean;\n onClick: () => void;\n onConfigure: () => void;\n}) {\n const roles = persona.roles ?? [];\n const preview = roles.slice(0, ROLE_PREVIEW_LIMIT);\n const overflow = roles.length - preview.length;\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onClick(); }}\n className={`grid gap-4 px-3 cursor-pointer group/row transition-colors ${active ? 'bg-surface-hover/60' : 'hover:bg-surface-hover/30'}`}\n style={{ gridTemplateColumns: GRID }}\n >\n <span className={`flex items-center min-w-0 text-sm truncate ${ROW_PY} ${CELL_TEXT}`}>\n {displayPersonaTitle(persona)}\n </span>\n\n <span className={`flex items-center text-xs font-mono truncate ${ROW_PY} text-text-tertiary transition-colors group-hover/row:text-text-secondary`}>\n {persona.key}\n </span>\n\n <span className={`flex items-center min-w-0 text-xs text-text-tertiary truncate ${ROW_PY} transition-colors group-hover/row:text-text-secondary`}>\n {persona.description ?? ''}\n </span>\n\n <div className={`flex items-center flex-wrap gap-x-2.5 gap-y-0.5 min-w-0 ${ROW_PY}`}>\n {preview.map((r) => (\n <span key={r.role} className=\"min-w-0 truncate text-text-secondary\">\n <RolePill role={r.role} tone=\"inherit\" />\n </span>\n ))}\n {overflow > 0 && (\n <span className=\"text-2xs text-text-quaternary tabular-nums\">+{overflow}</span>\n )}\n </div>\n\n <span className={`flex items-center justify-end text-sm tabular-nums ${ROW_PY} transition-colors ${CELL_TEXT}`}>\n {persona.user_count > 0 ? persona.user_count : ''}\n </span>\n\n <span className={`flex items-center justify-end ${ROW_PY}`}>\n <button\n onClick={(e) => { e.stopPropagation(); onConfigure(); }}\n className=\"text-text-quaternary/70 hover:text-accent transition-colors\"\n title={`Configure ${displayPersonaTitle(persona)}`}\n aria-label={`Configure ${displayPersonaTitle(persona)}`}\n >\n <Pencil className=\"w-3.5 h-3.5\" strokeWidth={1.5} />\n </button>\n </span>\n </div>\n );\n}\n\n// ── Page ──────────────────────────────────────────────────────────────────────\n\nexport function PersonasPage() {\n const navigate = useNavigate();\n const { data, isLoading } = usePersonas();\n const shell = useShellPanelOptional();\n const [showCreate, setShowCreate] = useState(false);\n const [search, setSearch] = useState('');\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n\n const personas = data?.personas ?? [];\n\n // ── Selection ↔ shell panel sync ────────────────────────────────────────────\n // Clicking a row opens the Assignees panel in the shell's right slot; an\n // external close (the panel's X, slot takeover) clears the selection. The\n // applied-key ref guards against re-setting the panel on unrelated renders.\n const appliedKey = useRef<string | null>(null);\n const panelWasOpen = useRef(false);\n useEffect(() => {\n if (!shell) return;\n if (selectedKey === appliedKey.current) return;\n appliedKey.current = selectedKey;\n if (selectedKey) {\n shell.setPanel(\n <AssigneesPanel personaKey={selectedKey} onClose={() => setSelectedKey(null)} />,\n { key: ASSIGNEES_PANEL_KEY, width: 360 },\n );\n } else {\n panelWasOpen.current = false;\n shell.closePanel(ASSIGNEES_PANEL_KEY);\n }\n }, [selectedKey, shell]);\n useEffect(() => {\n if (!shell || !selectedKey) return;\n if (shell.open && shell.ownerKey === ASSIGNEES_PANEL_KEY) {\n panelWasOpen.current = true;\n return;\n }\n if (panelWasOpen.current) {\n panelWasOpen.current = false;\n setSelectedKey(null);\n }\n }, [shell, selectedKey]);\n // Unmount with the panel open releases the slot (keyed — never yanks\n // another claimant's panel).\n const shellRef = useRef(shell);\n shellRef.current = shell;\n useEffect(\n () => () => {\n if (appliedKey.current) shellRef.current?.closePanel(ASSIGNEES_PANEL_KEY);\n },\n [],\n );\n\n const filtered = useMemo(() => {\n const sorted = [...personas].sort((a, b) =>\n displayPersonaTitle(a).localeCompare(displayPersonaTitle(b)),\n );\n if (!search.trim()) return sorted;\n const q = search.toLowerCase();\n return sorted.filter(\n (p) =>\n p.key.toLowerCase().includes(q) ||\n displayPersonaTitle(p).toLowerCase().includes(q) ||\n p.description?.toLowerCase().includes(q) ||\n p.roles?.some((r) => r.role.toLowerCase().includes(q)),\n );\n }, [personas, search]);\n\n return (\n <div>\n <PageHeader\n title=\"Personas\"\n docsHash=\"#docs:dashboard.md:personas\"\n actions={\n <button\n onClick={() => setShowCreate(true)}\n className=\"btn-primary text-xs flex items-center gap-1.5\"\n >\n + Add Persona\n </button>\n }\n />\n\n {/* The standard full-width sticky filter band — above the split. */}\n {!isLoading && personas.length > 0 && (\n <FilterBar>\n <FilterInput\n label=\"Search\"\n value={search}\n onChange={setSearch}\n placeholder={`${personas.length} personas…`}\n />\n {search && filtered.length !== personas.length && (\n <span className=\"text-2xs text-text-quaternary tabular-nums shrink-0\">\n {filtered.length} of {personas.length}\n </span>\n )}\n </FilterBar>\n )}\n\n {/* The table IS the page — selecting a row opens the Assignees panel in\n the shell's right panel; the row pencil opens the persona's config. */}\n <div className=\"overflow-x-clip\">\n {!isLoading && personas.length > 0 && filtered.length > 0 && (\n <div className=\"sticky top-[60px] z-10 bg-surface\">\n <TableHead />\n </div>\n )}\n\n {isLoading ? (\n <div className=\"animate-pulse space-y-3 mt-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-10 bg-surface-sunken rounded w-full\" />\n ))}\n </div>\n ) : filtered.length === 0 ? (\n <p className=\"text-sm text-text-tertiary mt-8\">\n {search\n ? 'Clear the search to see all personas.'\n : 'Create a persona to bundle roles into a one-step assignment.'}\n </p>\n ) : (\n <div className=\"divide-y divide-surface-border/30\">\n {filtered.map((persona) => (\n <PersonaRow\n key={persona.key}\n persona={persona}\n active={persona.key === selectedKey}\n onClick={() => setSelectedKey(persona.key)}\n onConfigure={() => navigate(`/admin/personas/${encodeURIComponent(persona.key)}`)}\n />\n ))}\n </div>\n )}\n </div>\n\n <CreatePersonaModal open={showCreate} onClose={() => setShowCreate(false)} />\n </div>\n );\n}\n"],"names":["CreatePersonaModal","open","onClose","navigate","useNavigate","createPersona","useCreatePersona","key","setKey","useState","title","setTitle","description","setDescription","prevOpen","setPrevOpen","handleCreate","trimmed","jsx","Modal","jsxs","e","UserCombobox","selected","onSelect","excludeIds","placeholder","setOpen","filter","setFilter","containerRef","useRef","debounced","useDebouncedValue","PAGE","data","isFetching","useUsers","useEffect","handleClick","excluded","useMemo","users","u","total","truncated","displayValue","filterText","label","SECTION_HDR","AssigneesPanel","personaKey","persona","usePersona","assign","useAssignPersona","unassign","useUnassignPersona","pendingUser","setPendingUser","assignees","handleAssign","X","a","DateValue","ASSIGNEES_PANEL_KEY","GRID","CELL_TEXT","HDR","CELL_PY","ROW_PY","displayPersonaTitle","p","c","TableHead","ROLE_PREVIEW_LIMIT","PersonaRow","active","onClick","onConfigure","roles","preview","overflow","r","RolePill","Pencil","PersonasPage","isLoading","usePersonas","shell","useShellPanelOptional","showCreate","setShowCreate","search","setSearch","selectedKey","setSelectedKey","personas","appliedKey","panelWasOpen","shellRef","_a","filtered","sorted","b","q","_b","PageHeader","FilterBar","FilterInput","i"],"mappings":"ieAKO,SAASA,EAAmB,CACjC,KAAAC,EACA,QAAAC,CACF,EAGG,CACD,MAAMC,EAAWC,EAAA,EACXC,EAAgBC,EAAA,EAChB,CAACC,EAAKC,CAAM,EAAIC,EAAAA,SAAS,EAAE,EAC3B,CAACC,EAAOC,CAAQ,EAAIF,EAAAA,SAAS,EAAE,EAC/B,CAACG,EAAaC,CAAc,EAAIJ,EAAAA,SAAS,EAAE,EAE3C,CAACK,EAAUC,CAAW,EAAIN,EAAAA,SAASR,CAAI,EACzCA,IAASa,IACXC,EAAYd,CAAI,EACZA,IACFO,EAAO,EAAE,EACTG,EAAS,EAAE,EACXE,EAAe,EAAE,EACjBR,EAAc,MAAA,IAIlB,MAAMW,EAAe,IAAM,CACzB,MAAMC,EAAUV,EAAI,KAAA,EAAO,YAAA,EACtBU,GACLZ,EAAc,OACZ,CACE,IAAKY,EACL,MAAOP,EAAM,KAAA,GAAU,OACvB,YAAaE,EAAY,QAAU,MAAA,EAErC,CACE,UAAW,IAAM,CACfV,EAAA,EAEAC,EAAS,mBAAmB,mBAAmBc,CAAO,CAAC,EAAE,CAC3D,CAAA,CACF,CAEJ,EAEA,OACEC,EAAAA,IAACC,GAAM,KAAAlB,EAAY,QAAAC,EAAkB,MAAM,iBACzC,SAAAkB,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAA,OAAC,MAAA,CACC,SAAA,CAAAF,EAAAA,IAAC,QAAA,CAAM,UAAU,iFAAiF,SAAA,iBAElG,EACAA,EAAAA,IAAC,QAAA,CACC,KAAK,OACL,MAAOX,EACP,SAAWc,GAAMb,EAAOa,EAAE,OAAO,KAAK,EACtC,YAAY,sBACZ,UAAU,iCACV,UAAYA,GAAM,CACZA,EAAE,MAAQ,SAASL,EAAA,CACzB,CAAA,CAAA,EAEFE,EAAAA,IAAC,IAAA,CAAE,UAAU,mCAAmC,SAAA,sGAAA,CAEhD,CAAA,EACF,SAEC,MAAA,CACC,SAAA,CAAAA,EAAAA,IAAC,QAAA,CAAM,UAAU,iFAAiF,SAAA,QAElG,EACAA,EAAAA,IAAC,QAAA,CACC,KAAK,OACL,MAAOR,EACP,SAAWW,GAAMV,EAASU,EAAE,OAAO,KAAK,EACxC,YAAY,sBACZ,UAAU,sBAAA,CAAA,CACZ,EACF,SAEC,MAAA,CACC,SAAA,CAAAH,EAAAA,IAAC,QAAA,CAAM,UAAU,iFAAiF,SAAA,cAElG,EACAA,EAAAA,IAAC,WAAA,CACC,MAAON,EACP,SAAWS,GAAMR,EAAeQ,EAAE,OAAO,KAAK,EAC9C,YAAY,sCACZ,KAAM,EACN,UAAU,+BAAA,CAAA,CACZ,EACF,EAEChB,EAAc,OACba,MAAC,IAAA,CAAE,UAAU,4BAA8B,SAAAb,EAAc,MAAgB,OAAA,CAAQ,EAGnFe,EAAAA,KAAC,MAAA,CAAI,UAAU,8BACb,SAAA,CAAAF,MAAC,SAAA,CAAO,QAAShB,EAAS,UAAU,wBAAwB,SAAA,SAE5D,EACAgB,EAAAA,IAAC,SAAA,CACC,QAASF,EACT,SAAU,CAACT,EAAI,KAAA,GAAUF,EAAc,UACvC,UAAU,sBAET,SAAAA,EAAc,UAAY,cAAgB,QAAA,CAAA,CAC7C,CAAA,CACF,CAAA,CAAA,CACF,CAAA,CACF,CAEJ,CCpGO,SAASiB,EAAa,CAAE,SAAAC,EAAU,SAAAC,EAAU,WAAAC,EAAa,GAAI,YAAAC,EAAc,iBAM/E,CACD,KAAM,CAACzB,EAAM0B,CAAO,EAAIlB,EAAAA,SAAS,EAAK,EAChC,CAACmB,EAAQC,CAAS,EAAIpB,EAAAA,SAAS,EAAE,EACjCqB,EAAeC,EAAAA,OAAuB,IAAI,EAE1CC,EAAYC,EAAkBL,CAAM,EACpCM,EAAO,GACP,CAAE,KAAAC,EAAM,WAAAC,CAAA,EAAeC,EAAS,CACpC,OAAQL,EAAU,KAAA,GAAU,OAC5B,MAAOE,CAAA,EACN,CAAE,QAASjC,EAAM,EAGpBqC,EAAAA,UAAU,IAAM,CACd,GAAI,CAACrC,EAAM,OACX,SAASsC,EAAYlB,EAAe,CAC9BS,EAAa,SAAW,CAACA,EAAa,QAAQ,SAAST,EAAE,MAAc,GACzEM,EAAQ,EAAK,CAEjB,CACA,gBAAS,iBAAiB,YAAaY,CAAW,EAC3C,IAAM,SAAS,oBAAoB,YAAaA,CAAW,CACpE,EAAG,CAACtC,CAAI,CAAC,EAET,MAAMuC,EAAWC,EAAAA,QAAQ,IAAM,IAAI,IAAIhB,CAAU,EAAG,CAACA,CAAU,CAAC,EAC1DiB,IAASP,GAAA,YAAAA,EAAM,QAAS,CAAA,GAAI,OAAQQ,GAAM,CAACH,EAAS,IAAIG,EAAE,EAAE,CAAC,EAC7DC,GAAQT,GAAA,YAAAA,EAAM,QAAS,EACvBU,EAAYD,IAAST,GAAA,YAAAA,EAAM,MAAM,SAAU,GAE3CW,GAAevB,GAAA,YAAAA,EAAU,QAAS,GAClCwB,EAAa9C,EAAO2B,EAASkB,EAEnC,OACE1B,EAAAA,KAAC,MAAA,CAAI,IAAKU,EAAc,UAAU,WAChC,SAAA,CAAAZ,EAAAA,IAAC,QAAA,CACC,KAAK,OACL,MAAO6B,EACP,QAAS,IAAM,CAAEpB,EAAQ,EAAI,EAAGE,EAAU,EAAE,CAAG,EAC/C,SAAWR,GAAM,CACfQ,EAAUR,EAAE,OAAO,KAAK,EACnBA,EAAE,OAAO,SAAgB,IAAI,EAC7BpB,GAAM0B,EAAQ,EAAI,CACzB,EACA,YAAAD,EACA,UAAU,+BACV,aAAW,MAAA,CAAA,EAGZzB,GACCmB,EAAAA,KAAC,MAAA,CAAI,UAAU,6HACZ,SAAA,CAAAsB,EAAM,IAAKC,GAAM,CAChB,MAAMK,EAAQL,EAAE,cAAgBA,EAAE,YAClC,OACEvB,EAAAA,KAAC,SAAA,CAEC,KAAK,SACL,QAAS,IAAM,CACbI,EAAS,CAAE,GAAImB,EAAE,GAAI,MAAAK,EAAO,EAC5BnB,EAAU,EAAE,EACZF,EAAQ,EAAK,CACf,EACA,UAAW,wGACTJ,GAAA,YAAAA,EAAU,MAAOoB,EAAE,GAAK,cAAgB,EAC1C,GAEA,SAAA,CAAAzB,EAAAA,IAAC,OAAA,CAAK,UAAU,qCAAsC,SAAA8B,EAAM,EAC3DL,EAAE,OACDzB,EAAAA,IAAC,QAAK,UAAU,yCAA0C,WAAE,KAAA,CAAM,CAAA,CAAA,EAb/DyB,EAAE,EAAA,CAiBb,CAAC,EAEAD,EAAM,SAAW,GAChBxB,EAAAA,IAAC,IAAA,CAAE,UAAU,sDACV,SAAAkB,EAAa,aAAeR,EAAS,mBAAmBA,CAAM,IAAM,qBACvE,EAGDiB,GACCzB,EAAAA,KAAC,IAAA,CAAE,UAAU,2FACV,SAAA,CAAAsB,EAAM,OAAO,OAAKE,EAAM,mBAAA,CAAA,CAC3B,CAAA,CAAA,CAEJ,CAAA,EAEJ,CAEJ,CCvGA,MAAMK,EAAc,sEAOb,SAASC,EAAe,CAC7B,WAAAC,EACA,QAAAjD,CACF,EAGG,CACD,KAAM,CAAE,KAAMkD,CAAA,EAAYC,EAAWF,GAAc,EAAE,EAC/CG,EAASC,EAAA,EACTC,EAAWC,EAAA,EACX,CAACC,EAAaC,CAAc,EAAIlD,EAAAA,SAAuC,IAAI,EAE3EmD,GAAYR,GAAA,YAAAA,EAAS,YAAa,CAAA,EAElCS,EAAe,IAAM,CACrB,CAACT,GAAW,CAACM,GACjBJ,EAAO,OACL,CAAE,OAAQI,EAAY,GAAI,IAAKN,EAAQ,GAAA,EACvC,CAAE,UAAW,IAAMO,EAAe,IAAI,CAAA,CAAE,CAE5C,EAEA,OACEvC,EAAAA,KAAC,MAAA,CAAI,UAAU,wCACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,yCACb,SAAA,CAAAF,EAAAA,IAAC,IAAA,CAAE,UAAW+B,EAAa,SAAA,YAAS,EACnC/C,GACCgB,EAAAA,IAAC,SAAA,CAAO,QAAShB,EAAS,UAAU,YAAY,MAAM,QAAQ,aAAW,QACvE,SAAAgB,EAAAA,IAAC4C,EAAA,CAAE,UAAU,UAAU,CAAA,CACzB,CAAA,EAEJ,EAEEV,EAMAhC,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAA,OAAC,MAAA,CACC,SAAA,CAAAF,MAAC,KAAE,UAAU,4BAA6B,SAAAkC,EAAQ,OAASA,EAAQ,IAAI,EACvEhC,EAAAA,KAAC,IAAA,CAAE,UAAU,qCACV,SAAA,CAAAgC,EAAQ,MAAM,OAAO,QAAMA,EAAQ,MAAM,SAAW,EAAI,GAAK,IAAI,iBAAA,CAAA,CACpE,CAAA,EACF,EAECQ,EAAU,SAAW,EACpB1C,EAAAA,IAAC,IAAA,CAAE,UAAU,6BAA6B,SAAA,2DAAA,CAE1C,EAEAA,MAAC,OAAI,UAAU,sBACZ,SAAA0C,EAAU,IAAKG,GACd3C,EAAAA,KAAC,OAAA,CAEC,UAAU,uHAEV,SAAA,CAAAF,EAAAA,IAAC,OAAA,CAAK,UAAU,0BAA0B,MAAO6C,EAAE,YAChD,SAAAA,EAAE,cAAgBA,EAAE,WAAA,CACvB,EACA7C,EAAAA,IAAC,QAAK,UAAU,uCACd,eAAC8C,EAAA,CAAU,KAAMD,EAAE,WAAA,CAAa,CAAA,CAClC,EACA7C,EAAAA,IAAC,SAAA,CACC,QAAS,IAAMsC,EAAS,OAAO,CAAE,OAAQO,EAAE,GAAI,IAAKX,EAAQ,IAAK,EACjE,UAAU,6EACV,MAAO,YAAYW,EAAE,cAAgBA,EAAE,WAAW,GACnD,SAAA,GAAA,CAAA,CAED,CAAA,EAfKA,EAAE,EAAA,CAiBV,EACH,EAGF3C,EAAAA,KAAC,MAAA,CAAI,UAAU,gDACb,SAAA,CAAAF,EAAAA,IAAC,IAAA,CAAE,UAAW+B,EAAa,SAAA,cAAW,EACtC/B,EAAAA,IAACI,EAAA,CACC,SAAUoC,EACV,SAAUC,EACV,WAAYC,EAAU,IAAKG,GAAMA,EAAE,EAAE,CAAA,CAAA,EAEvC7C,EAAAA,IAAC,MAAA,CAAI,UAAU,wBACb,SAAAA,EAAAA,IAAC,SAAA,CACC,QAAS2C,EACT,SAAU,CAACH,GAAeJ,EAAO,UACjC,UAAU,sBAET,SAAAA,EAAO,UAAY,MAAQ,QAAA,CAAA,EAEhC,GACEA,EAAO,OAASE,EAAS,QACzBtC,EAAAA,IAAC,IAAA,CAAE,UAAU,6BACR,UAAAoC,EAAO,OAASE,EAAS,OAAiB,OAAA,CAC/C,CAAA,CAAA,CAEJ,CAAA,CAAA,CACF,EAhEAtC,EAAAA,IAAC,IAAA,CAAE,UAAU,6BAA6B,SAAA,qIAAA,CAG1C,CA6DA,EAEJ,CAEJ,CCtGA,MAAM+C,EAAsB,oBAOtBC,EAAO,kCAEPC,EAAY,0EACZC,EAAM,wEACNC,EAAU,OACVC,EAAS,SAGf,SAASC,EAAoBC,EAA4B,CACvD,OAAIA,EAAE,MAAcA,EAAE,MACfA,EAAE,IAAI,QAAQ,SAAU,GAAG,EAAE,QAAQ,QAAUC,GAAMA,EAAE,YAAA,CAAa,CAC7E,CAEA,SAASC,GAAY,CACnB,OACEtD,EAAAA,KAAC,MAAA,CACC,UAAU,4DACV,MAAO,CAAE,oBAAqB8C,CAAA,EAE9B,SAAA,CAAAhD,EAAAA,IAAC,QAAK,UAAW,GAAGkD,CAAG,IAAIC,CAAO,qBAAsB,SAAA,SAAA,CAAO,EAC/DnD,EAAAA,IAAC,QAAK,UAAW,GAAGkD,CAAG,IAAIC,CAAO,qBAAsB,SAAA,KAAA,CAAG,EAC3DnD,EAAAA,IAAC,QAAK,UAAW,GAAGkD,CAAG,IAAIC,CAAO,qBAAsB,SAAA,aAAA,CAAW,EACnEnD,EAAAA,IAAC,QAAK,UAAW,GAAGkD,CAAG,IAAIC,CAAO,qBAAsB,SAAA,OAAA,CAAK,EAC7DnD,EAAAA,IAAC,QAAK,UAAW,GAAGkD,CAAG,IAAIC,CAAO,iCAAkC,SAAA,OAAA,CAAK,EACzEnD,EAAAA,IAAC,OAAA,CAAK,UAAWmD,CAAA,CAAS,CAAA,CAAA,CAAA,CAGhC,CAEA,MAAMM,EAAqB,EAE3B,SAASC,EAAW,CAAE,QAAAxB,EAAS,OAAAyB,EAAQ,QAAAC,EAAS,YAAAC,GAK7C,CACD,MAAMC,EAAQ5B,EAAQ,OAAS,CAAA,EACzB6B,EAAUD,EAAM,MAAM,EAAGL,CAAkB,EAC3CO,EAAWF,EAAM,OAASC,EAAQ,OAExC,OACE7D,EAAAA,KAAC,MAAA,CACC,KAAK,SACL,SAAU,EACV,QAAA0D,EACA,UAAYzD,GAAM,EAAMA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,MAAKyD,EAAA,CAAW,EACvE,UAAW,8DAA8DD,EAAS,sBAAwB,2BAA2B,GACrI,MAAO,CAAE,oBAAqBX,CAAA,EAE9B,SAAA,CAAAhD,EAAAA,IAAC,OAAA,CAAK,UAAW,8CAA8CoD,CAAM,IAAIH,CAAS,GAC/E,SAAAI,EAAoBnB,CAAO,CAAA,CAC9B,QAEC,OAAA,CAAK,UAAW,gDAAgDkB,CAAM,4EACpE,WAAQ,GAAA,CACX,EAEApD,EAAAA,IAAC,QAAK,UAAW,iEAAiEoD,CAAM,yDACrF,SAAAlB,EAAQ,aAAe,EAAA,CAC1B,EAEAhC,EAAAA,KAAC,MAAA,CAAI,UAAW,2DAA2DkD,CAAM,GAC9E,SAAA,CAAAW,EAAQ,IAAKE,GACZjE,EAAAA,IAAC,OAAA,CAAkB,UAAU,uCAC3B,SAAAA,EAAAA,IAACkE,EAAA,CAAS,KAAMD,EAAE,KAAM,KAAK,UAAU,CAAA,EAD9BA,EAAE,IAEb,CACD,EACAD,EAAW,GACV9D,OAAC,OAAA,CAAK,UAAU,6CAA6C,SAAA,CAAA,IAAE8D,CAAA,CAAA,CAAS,CAAA,EAE5E,EAEAhE,EAAAA,IAAC,OAAA,CAAK,UAAW,sDAAsDoD,CAAM,sBAAsBH,CAAS,GACzG,SAAAf,EAAQ,WAAa,EAAIA,EAAQ,WAAa,GACjD,EAEAlC,MAAC,OAAA,CAAK,UAAW,iCAAiCoD,CAAM,GACtD,SAAApD,EAAAA,IAAC,SAAA,CACC,QAAUG,GAAM,CAAEA,EAAE,gBAAA,EAAmB0D,EAAA,CAAe,EACtD,UAAU,8DACV,MAAO,aAAaR,EAAoBnB,CAAO,CAAC,GAChD,aAAY,aAAamB,EAAoBnB,CAAO,CAAC,GAErD,SAAAlC,EAAAA,IAACmE,EAAA,CAAO,UAAU,cAAc,YAAa,GAAA,CAAK,CAAA,CAAA,CACpD,CACF,CAAA,CAAA,CAAA,CAGN,CAIO,SAASC,IAAe,CAC7B,MAAMnF,EAAWC,EAAA,EACX,CAAE,KAAA+B,EAAM,UAAAoD,CAAA,EAAcC,EAAA,EACtBC,EAAQC,EAAA,EACR,CAACC,EAAYC,CAAa,EAAInF,EAAAA,SAAS,EAAK,EAC5C,CAACoF,EAAQC,CAAS,EAAIrF,EAAAA,SAAS,EAAE,EACjC,CAACsF,EAAaC,CAAc,EAAIvF,EAAAA,SAAwB,IAAI,EAE5DwF,GAAW9D,GAAA,YAAAA,EAAM,WAAY,CAAA,EAM7B+D,EAAanE,EAAAA,OAAsB,IAAI,EACvCoE,EAAepE,EAAAA,OAAO,EAAK,EACjCO,EAAAA,UAAU,IAAM,CACTmD,GACDM,IAAgBG,EAAW,UAC/BA,EAAW,QAAUH,EACjBA,EACFN,EAAM,SACJvE,MAACgC,GAAe,WAAY6C,EAAa,QAAS,IAAMC,EAAe,IAAI,EAAG,EAC9E,CAAE,IAAK/B,EAAqB,MAAO,GAAA,CAAI,GAGzCkC,EAAa,QAAU,GACvBV,EAAM,WAAWxB,CAAmB,GAExC,EAAG,CAAC8B,EAAaN,CAAK,CAAC,EACvBnD,EAAAA,UAAU,IAAM,CACd,GAAI,GAACmD,GAAS,CAACM,GACf,IAAIN,EAAM,MAAQA,EAAM,WAAaxB,EAAqB,CACxDkC,EAAa,QAAU,GACvB,MACF,CACIA,EAAa,UACfA,EAAa,QAAU,GACvBH,EAAe,IAAI,GAEvB,EAAG,CAACP,EAAOM,CAAW,CAAC,EAGvB,MAAMK,EAAWrE,EAAAA,OAAO0D,CAAK,EAC7BW,EAAS,QAAUX,EACnBnD,EAAAA,UACE,IAAM,IAAM,OACN4D,EAAW,WAASG,EAAAD,EAAS,UAAT,MAAAC,EAAkB,WAAWpC,GACvD,EACA,CAAA,CAAC,EAGH,MAAMqC,EAAW7D,EAAAA,QAAQ,IAAM,CAC7B,MAAM8D,EAAS,CAAC,GAAGN,CAAQ,EAAE,KAAK,CAAClC,EAAGyC,IACpCjC,EAAoBR,CAAC,EAAE,cAAcQ,EAAoBiC,CAAC,CAAC,CAAA,EAE7D,GAAI,CAACX,EAAO,KAAA,EAAQ,OAAOU,EAC3B,MAAME,EAAIZ,EAAO,YAAA,EACjB,OAAOU,EAAO,OACX/B,GAAA,SACC,OAAAA,EAAE,IAAI,cAAc,SAASiC,CAAC,GAC9BlC,EAAoBC,CAAC,EAAE,YAAA,EAAc,SAASiC,CAAC,KAC/CJ,EAAA7B,EAAE,cAAF,YAAA6B,EAAe,cAAc,SAASI,OACtCC,EAAAlC,EAAE,QAAF,YAAAkC,EAAS,KAAMvB,GAAMA,EAAE,KAAK,YAAA,EAAc,SAASsB,CAAC,IAAC,CAE3D,EAAG,CAACR,EAAUJ,CAAM,CAAC,EAErB,cACG,MAAA,CACC,SAAA,CAAA3E,EAAAA,IAACyF,EAAA,CACC,MAAM,WACN,SAAS,8BACT,QACEzF,EAAAA,IAAC,SAAA,CACC,QAAS,IAAM0E,EAAc,EAAI,EACjC,UAAU,gDACX,SAAA,eAAA,CAAA,CAED,CAAA,EAKH,CAACL,GAAaU,EAAS,OAAS,UAC9BW,EAAA,CACC,SAAA,CAAA1F,EAAAA,IAAC2F,EAAA,CACC,MAAM,SACN,MAAOhB,EACP,SAAUC,EACV,YAAa,GAAGG,EAAS,MAAM,YAAA,CAAA,EAEhCJ,GAAUS,EAAS,SAAWL,EAAS,QACtC7E,OAAC,OAAA,CAAK,UAAU,sDACb,SAAA,CAAAkF,EAAS,OAAO,OAAKL,EAAS,MAAA,CAAA,CACjC,CAAA,EAEJ,EAKF7E,EAAAA,KAAC,MAAA,CAAI,UAAU,kBACZ,SAAA,CAAA,CAACmE,GAAaU,EAAS,OAAS,GAAKK,EAAS,OAAS,GACtDpF,EAAAA,IAAC,MAAA,CAAI,UAAU,oCACb,SAAAA,EAAAA,IAACwD,IAAU,EACb,EAGDa,EACCrE,EAAAA,IAAC,MAAA,CAAI,UAAU,+BACZ,UAAC,EAAG,EAAG,CAAC,EAAE,IAAK4F,SACb,MAAA,CAAY,UAAU,yCAAbA,CAAqD,CAChE,EACH,EACER,EAAS,SAAW,QACrB,IAAA,CAAE,UAAU,kCACV,SAAAT,EACG,wCACA,8DAAA,CACN,QAEC,MAAA,CAAI,UAAU,oCACZ,SAAAS,EAAS,IAAKlD,GACblC,EAAAA,IAAC0D,EAAA,CAEC,QAAAxB,EACA,OAAQA,EAAQ,MAAQ2C,EACxB,QAAS,IAAMC,EAAe5C,EAAQ,GAAG,EACzC,YAAa,IAAMjD,EAAS,mBAAmB,mBAAmBiD,EAAQ,GAAG,CAAC,EAAE,CAAA,EAJ3EA,EAAQ,GAAA,CAMhB,CAAA,CACH,CAAA,EAEJ,EAEAlC,MAAClB,GAAmB,KAAM2F,EAAY,QAAS,IAAMC,EAAc,EAAK,CAAA,CAAG,CAAA,EAC7E,CAEJ"}
1
+ {"version":3,"file":"PersonasPage-D7ryA8Vc.js","sources":["../../src/pages/admin/personas/CreatePersonaModal.tsx","../../src/components/common/form/UserCombobox.tsx","../../src/pages/admin/personas/AssigneesPanel.tsx","../../src/pages/admin/personas/PersonasPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCreatePersona } from '../../../api/personas';\nimport { Modal } from '../../../components/common/modal/Modal';\n\nexport function CreatePersonaModal({\n open,\n onClose,\n}: {\n open: boolean;\n onClose: () => void;\n}) {\n const navigate = useNavigate();\n const createPersona = useCreatePersona();\n const [key, setKey] = useState('');\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (open) {\n setKey('');\n setTitle('');\n setDescription('');\n createPersona.reset();\n }\n }\n\n const handleCreate = () => {\n const trimmed = key.trim().toLowerCase();\n if (!trimmed) return;\n createPersona.mutate(\n {\n key: trimmed,\n title: title.trim() || undefined,\n description: description.trim() || undefined,\n },\n {\n onSuccess: () => {\n onClose();\n // Land on the detail page to link roles right away.\n navigate(`/admin/personas/${encodeURIComponent(trimmed)}`);\n },\n },\n );\n };\n\n return (\n <Modal open={open} onClose={onClose} title=\"Create Persona\">\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Key (required)\n </label>\n <input\n type=\"text\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n placeholder=\"e.g., floor-manager\"\n className=\"input text-xs w-full font-mono\"\n onKeyDown={(e) => {\n if (e.key === 'Enter') handleCreate();\n }}\n />\n <p className=\"text-2xs text-text-tertiary mt-1\">\n Lowercase letters, numbers, hyphens, and underscores only. Stable — it names the persona everywhere.\n </p>\n </div>\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Title\n </label>\n <input\n type=\"text\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g., Floor Manager\"\n className=\"input text-xs w-full\"\n />\n </div>\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1\">\n Description\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"The day in the life, one paragraph.\"\n rows={3}\n className=\"input text-xs w-full resize-y\"\n />\n </div>\n\n {createPersona.error && (\n <p className=\"text-xs text-status-error\">{(createPersona.error as Error).message}</p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button onClick={onClose} className=\"btn-secondary text-xs\">\n Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={!key.trim() || createPersona.isPending}\n className=\"btn-primary text-xs\"\n >\n {createPersona.isPending ? 'Creating...' : 'Create'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n","import { useState, useRef, useEffect, useMemo } from 'react';\nimport { useUsers } from '../../../api/users';\nimport { useDebouncedValue } from '../../../hooks/useDebouncedValue';\n\nexport interface UserComboboxSelection {\n id: string;\n label: string;\n}\n\n/**\n * Searchable inline combobox for picking a user account. The filter drives the\n * server-side account search (name, email, external_id), so the list stays\n * bounded no matter how many accounts exist; a footer line discloses when more\n * matches remain than the page shows. Styled to match CapabilityCombobox.\n */\nexport function UserCombobox({ selected, onSelect, excludeIds = [], placeholder = 'Search users…' }: {\n selected: UserComboboxSelection | null;\n onSelect: (user: UserComboboxSelection | null) => void;\n /** Accounts to hide from the results (e.g. already assigned). */\n excludeIds?: string[];\n placeholder?: string;\n}) {\n const [open, setOpen] = useState(false);\n const [filter, setFilter] = useState('');\n const containerRef = useRef<HTMLDivElement>(null);\n\n const debounced = useDebouncedValue(filter);\n const PAGE = 20;\n const { data, isFetching } = useUsers({\n search: debounced.trim() || undefined,\n limit: PAGE,\n }, { enabled: open });\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n function handleClick(e: MouseEvent) {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }\n document.addEventListener('mousedown', handleClick);\n return () => document.removeEventListener('mousedown', handleClick);\n }, [open]);\n\n const excluded = useMemo(() => new Set(excludeIds), [excludeIds]);\n const users = (data?.users ?? []).filter((u) => !excluded.has(u.id));\n const total = data?.total ?? 0;\n const truncated = total > (data?.users.length ?? 0);\n\n const displayValue = selected?.label ?? '';\n const filterText = open ? filter : displayValue;\n\n return (\n <div ref={containerRef} className=\"relative\">\n <input\n type=\"text\"\n value={filterText}\n onFocus={() => { setOpen(true); setFilter(''); }}\n onChange={(e) => {\n setFilter(e.target.value);\n if (!e.target.value) onSelect(null);\n if (!open) setOpen(true);\n }}\n placeholder={placeholder}\n className=\"input text-xs w-full min-w-0\"\n aria-label=\"User\"\n />\n\n {open && (\n <div className=\"absolute z-[100] left-0 right-0 mt-1 max-h-64 overflow-y-auto rounded-md border border-surface-border bg-surface shadow-lg\">\n {users.map((u) => {\n const label = u.display_name || u.external_id;\n return (\n <button\n key={u.id}\n type=\"button\"\n onClick={() => {\n onSelect({ id: u.id, label });\n setFilter('');\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 hover:bg-surface-hover transition-colors flex items-center gap-3 min-w-0 ${\n selected?.id === u.id ? 'bg-accent/5' : ''\n }`}\n >\n <span className=\"text-xs text-text-primary truncate\">{label}</span>\n {u.email && (\n <span className=\"text-2xs text-text-quaternary truncate\">{u.email}</span>\n )}\n </button>\n );\n })}\n\n {users.length === 0 && (\n <p className=\"px-3 py-4 text-center text-2xs text-text-quaternary\">\n {isFetching ? 'Searching…' : filter ? `No users match \"${filter}\"` : 'No users available'}\n </p>\n )}\n\n {truncated && (\n <p className=\"px-3 py-1.5 text-2xs text-text-quaternary border-t border-surface-border/60 tabular-nums\">\n {users.length} of {total} — type to narrow\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n","import { useState } from 'react';\nimport { X } from 'lucide-react';\nimport { usePersona, useAssignPersona, useUnassignPersona } from '../../../api/personas';\nimport { DateValue } from '../../../components/common/display/DateValue';\nimport { UserCombobox, type UserComboboxSelection } from '../../../components/common/form/UserCombobox';\n\nconst SECTION_HDR = 'text-2xs font-semibold uppercase tracking-widest text-text-tertiary';\n\n/**\n * The 90% surface: pick a persona in the list, assign a user, and they get\n * every linked role — pins, list schemas, and forms compose from the roles.\n * Rendered in the global shell right panel (key 'persona-assignees').\n */\nexport function AssigneesPanel({\n personaKey,\n onClose,\n}: {\n personaKey: string | null;\n onClose?: () => void;\n}) {\n const { data: persona } = usePersona(personaKey ?? '');\n const assign = useAssignPersona();\n const unassign = useUnassignPersona();\n const [pendingUser, setPendingUser] = useState<UserComboboxSelection | null>(null);\n\n const assignees = persona?.assignees ?? [];\n\n const handleAssign = () => {\n if (!persona || !pendingUser) return;\n assign.mutate(\n { userId: pendingUser.id, key: persona.key },\n { onSuccess: () => setPendingUser(null) },\n );\n };\n\n return (\n <div className=\"h-full overflow-y-auto px-5 pt-4 pb-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <p className={SECTION_HDR}>Assignees</p>\n {onClose && (\n <button onClick={onClose} className=\"icon-link\" title=\"Close\" aria-label=\"Close\">\n <X className=\"w-4 h-4\" />\n </button>\n )}\n </div>\n\n {!persona ? (\n <p className=\"text-xs text-text-tertiary\">\n Select a persona to assign users. An assignee joins every linked role\n at its scope — sidebar pins and forms compose from the roles.\n </p>\n ) : (\n <div className=\"space-y-4\">\n <div>\n <p className=\"text-sm text-text-primary\">{persona.title || persona.key}</p>\n <p className=\"text-2xs text-text-tertiary mt-0.5\">\n {persona.roles.length} role{persona.roles.length === 1 ? '' : 's'} per assignment\n </p>\n </div>\n\n {assignees.length === 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n Assign a user to compose their whole surface in one step.\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\">\n {assignees.map((a) => (\n <span\n key={a.id}\n className=\"flex items-center gap-2.5 w-full min-w-0 pl-2.5 pr-2 py-1 text-xs bg-surface-sunken rounded-full text-text-secondary\"\n >\n <span className=\"flex-1 min-w-0 truncate\" title={a.external_id}>\n {a.display_name || a.external_id}\n </span>\n <span className=\"shrink-0 text-2xs text-text-tertiary\">\n <DateValue date={a.assigned_at} />\n </span>\n <button\n onClick={() => unassign.mutate({ userId: a.id, key: persona.key })}\n className=\"shrink-0 text-text-tertiary hover:text-status-error transition-colors ml-1\"\n title={`Unassign ${a.display_name || a.external_id}`}\n >\n &times;\n </button>\n </span>\n ))}\n </div>\n )}\n\n <div className=\"pt-3 border-t border-surface-border space-y-2\">\n <p className={SECTION_HDR}>Assign User</p>\n <UserCombobox\n selected={pendingUser}\n onSelect={setPendingUser}\n excludeIds={assignees.map((a) => a.id)}\n />\n <div className=\"flex justify-end pt-1\">\n <button\n onClick={handleAssign}\n disabled={!pendingUser || assign.isPending}\n className=\"btn-primary text-xs\"\n >\n {assign.isPending ? '...' : 'Assign'}\n </button>\n </div>\n {(assign.error || unassign.error) && (\n <p className=\"text-2xs text-status-error\">\n {((assign.error || unassign.error) as Error).message}\n </p>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n","import { useState, useMemo, useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Pencil } from 'lucide-react';\nimport { usePersonas } from '../../../api/personas';\nimport { FilterBar, FilterInput } from '../../../components/common/data/FilterBar';\nimport type { LTPersonaRecord } from '../../../api/types';\nimport { PageHeader } from '../../../components/common/layout/PageHeader';\nimport { RolePill } from '../../../components/common/display/RolePill';\nimport { useShellPanelOptional } from '../../../hooks/useShellPanel';\nimport { CreatePersonaModal } from './CreatePersonaModal';\nimport { AssigneesPanel } from './AssigneesPanel';\n\n// Shell-panel ownership key — the Assignees panel claims/releases this slot.\nconst ASSIGNEES_PANEL_KEY = 'persona-assignees';\n\n// ── Grid columns ──────────────────────────────────────────────────────────────\n// PERSONA(title, 180px) | KEY(140px) | DESCRIPTION(1fr) | ROLES(220px) | USERS(56px) | CONFIGURE(40px)\n// Mirrors the Roles list: display title leads, exact key is the secondary\n// field, the bundle's role pills preview on the right. Clicking a row selects\n// it for the Assignees panel (the 90% path); the pencil opens configuration.\nconst GRID = '180px 140px 1fr 220px 56px 40px';\n\nconst CELL_TEXT = 'text-text-secondary transition-colors group-hover/row:text-text-primary';\nconst HDR = 'text-2xs font-semibold uppercase tracking-widest text-text-quaternary';\nconst CELL_PY = 'py-2';\nconst ROW_PY = 'py-2.5';\n\n/** Title Case fallback derived from the key, same convention as roles. */\nfunction displayPersonaTitle(p: LTPersonaRecord): string {\n if (p.title) return p.title;\n return p.key.replace(/[-_]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction TableHead() {\n return (\n <div\n className=\"grid gap-4 px-3 border-b border-surface-border bg-surface\"\n style={{ gridTemplateColumns: GRID }}\n >\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Persona</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Key</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Description</span>\n <span className={`${HDR} ${CELL_PY} flex items-center`}>Roles</span>\n <span className={`${HDR} ${CELL_PY} flex items-center justify-end`}>Users</span>\n <span className={CELL_PY} />\n </div>\n );\n}\n\nconst ROLE_PREVIEW_LIMIT = 3;\n\nfunction PersonaRow({ persona, active, onClick, onConfigure }: {\n persona: LTPersonaRecord;\n active: boolean;\n onClick: () => void;\n onConfigure: () => void;\n}) {\n const roles = persona.roles ?? [];\n const preview = roles.slice(0, ROLE_PREVIEW_LIMIT);\n const overflow = roles.length - preview.length;\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onClick(); }}\n className={`grid gap-4 px-3 cursor-pointer group/row transition-colors ${active ? 'bg-surface-hover/60' : 'hover:bg-surface-hover/30'}`}\n style={{ gridTemplateColumns: GRID }}\n >\n <span className={`flex items-center min-w-0 text-sm truncate ${ROW_PY} ${CELL_TEXT}`}>\n {displayPersonaTitle(persona)}\n </span>\n\n <span className={`flex items-center text-xs font-mono truncate ${ROW_PY} text-text-tertiary transition-colors group-hover/row:text-text-secondary`}>\n {persona.key}\n </span>\n\n <span className={`flex items-center min-w-0 text-xs text-text-tertiary truncate ${ROW_PY} transition-colors group-hover/row:text-text-secondary`}>\n {persona.description ?? ''}\n </span>\n\n <div className={`flex items-center flex-wrap gap-x-2.5 gap-y-0.5 min-w-0 ${ROW_PY}`}>\n {preview.map((r) => (\n <span key={r.role} className=\"min-w-0 truncate text-text-secondary\">\n <RolePill role={r.role} tone=\"inherit\" />\n </span>\n ))}\n {overflow > 0 && (\n <span className=\"text-2xs text-text-quaternary tabular-nums\">+{overflow}</span>\n )}\n </div>\n\n <span className={`flex items-center justify-end text-sm tabular-nums ${ROW_PY} transition-colors ${CELL_TEXT}`}>\n {persona.user_count > 0 ? persona.user_count : ''}\n </span>\n\n <span className={`flex items-center justify-end ${ROW_PY}`}>\n <button\n onClick={(e) => { e.stopPropagation(); onConfigure(); }}\n className=\"text-text-quaternary/70 hover:text-accent transition-colors\"\n title={`Configure ${displayPersonaTitle(persona)}`}\n aria-label={`Configure ${displayPersonaTitle(persona)}`}\n >\n <Pencil className=\"w-3.5 h-3.5\" strokeWidth={1.5} />\n </button>\n </span>\n </div>\n );\n}\n\n// ── Page ──────────────────────────────────────────────────────────────────────\n\nexport function PersonasPage() {\n const navigate = useNavigate();\n const { data, isLoading } = usePersonas();\n const shell = useShellPanelOptional();\n const [showCreate, setShowCreate] = useState(false);\n const [search, setSearch] = useState('');\n const [selectedKey, setSelectedKey] = useState<string | null>(null);\n\n const personas = data?.personas ?? [];\n\n // ── Selection ↔ shell panel sync ────────────────────────────────────────────\n // Clicking a row opens the Assignees panel in the shell's right slot; an\n // external close (the panel's X, slot takeover) clears the selection. The\n // applied-key ref guards against re-setting the panel on unrelated renders.\n const appliedKey = useRef<string | null>(null);\n const panelWasOpen = useRef(false);\n useEffect(() => {\n if (!shell) return;\n if (selectedKey === appliedKey.current) return;\n appliedKey.current = selectedKey;\n if (selectedKey) {\n shell.setPanel(\n <AssigneesPanel personaKey={selectedKey} onClose={() => setSelectedKey(null)} />,\n { key: ASSIGNEES_PANEL_KEY, width: 360 },\n );\n } else {\n panelWasOpen.current = false;\n shell.closePanel(ASSIGNEES_PANEL_KEY);\n }\n }, [selectedKey, shell]);\n useEffect(() => {\n if (!shell || !selectedKey) return;\n if (shell.open && shell.ownerKey === ASSIGNEES_PANEL_KEY) {\n panelWasOpen.current = true;\n return;\n }\n if (panelWasOpen.current) {\n panelWasOpen.current = false;\n setSelectedKey(null);\n }\n }, [shell, selectedKey]);\n // Unmount with the panel open releases the slot (keyed — never yanks\n // another claimant's panel).\n const shellRef = useRef(shell);\n shellRef.current = shell;\n useEffect(\n () => () => {\n if (appliedKey.current) shellRef.current?.closePanel(ASSIGNEES_PANEL_KEY);\n },\n [],\n );\n\n const filtered = useMemo(() => {\n const sorted = [...personas].sort((a, b) =>\n displayPersonaTitle(a).localeCompare(displayPersonaTitle(b)),\n );\n if (!search.trim()) return sorted;\n const q = search.toLowerCase();\n return sorted.filter(\n (p) =>\n p.key.toLowerCase().includes(q) ||\n displayPersonaTitle(p).toLowerCase().includes(q) ||\n p.description?.toLowerCase().includes(q) ||\n p.roles?.some((r) => r.role.toLowerCase().includes(q)),\n );\n }, [personas, search]);\n\n return (\n <div>\n <PageHeader\n title=\"Personas\"\n docsHash=\"#docs:dashboard.md:personas\"\n actions={\n <button\n onClick={() => setShowCreate(true)}\n className=\"btn-primary text-xs flex items-center gap-1.5\"\n >\n + Add Persona\n </button>\n }\n />\n\n {/* The standard full-width sticky filter band — above the split. */}\n {!isLoading && personas.length > 0 && (\n <FilterBar>\n <FilterInput\n label=\"Search\"\n value={search}\n onChange={setSearch}\n placeholder={`${personas.length} personas…`}\n />\n {search && filtered.length !== personas.length && (\n <span className=\"text-2xs text-text-quaternary tabular-nums shrink-0\">\n {filtered.length} of {personas.length}\n </span>\n )}\n </FilterBar>\n )}\n\n {/* The table IS the page — selecting a row opens the Assignees panel in\n the shell's right panel; the row pencil opens the persona's config. */}\n <div className=\"overflow-x-clip\">\n {!isLoading && personas.length > 0 && filtered.length > 0 && (\n <div className=\"sticky top-[60px] z-10 bg-surface\">\n <TableHead />\n </div>\n )}\n\n {isLoading ? (\n <div className=\"animate-pulse space-y-3 mt-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-10 bg-surface-sunken rounded w-full\" />\n ))}\n </div>\n ) : filtered.length === 0 ? (\n <p className=\"text-sm text-text-tertiary mt-8\">\n {search\n ? 'Clear the search to see all personas.'\n : 'Create a persona to bundle roles into a one-step assignment.'}\n </p>\n ) : (\n <div className=\"divide-y divide-surface-border/30\">\n {filtered.map((persona) => (\n <PersonaRow\n key={persona.key}\n persona={persona}\n active={persona.key === selectedKey}\n onClick={() => setSelectedKey(persona.key)}\n onConfigure={() => navigate(`/admin/personas/${encodeURIComponent(persona.key)}`)}\n />\n ))}\n </div>\n )}\n </div>\n\n <CreatePersonaModal open={showCreate} onClose={() => setShowCreate(false)} />\n </div>\n );\n}\n"],"names":["CreatePersonaModal","open","onClose","navigate","useNavigate","createPersona","useCreatePersona","key","setKey","useState","title","setTitle","description","setDescription","prevOpen","setPrevOpen","handleCreate","trimmed","jsx","Modal","jsxs","e","UserCombobox","selected","onSelect","excludeIds","placeholder","setOpen","filter","setFilter","containerRef","useRef","debounced","useDebouncedValue","PAGE","data","isFetching","useUsers","useEffect","handleClick","excluded","useMemo","users","u","total","truncated","displayValue","filterText","label","SECTION_HDR","AssigneesPanel","personaKey","persona","usePersona","assign","useAssignPersona","unassign","useUnassignPersona","pendingUser","setPendingUser","assignees","handleAssign","X","a","DateValue","ASSIGNEES_PANEL_KEY","GRID","CELL_TEXT","HDR","CELL_PY","ROW_PY","displayPersonaTitle","p","c","TableHead","ROLE_PREVIEW_LIMIT","PersonaRow","active","onClick","onConfigure","roles","preview","overflow","r","RolePill","Pencil","PersonasPage","isLoading","usePersonas","shell","useShellPanelOptional","showCreate","setShowCreate","search","setSearch","selectedKey","setSelectedKey","personas","appliedKey","panelWasOpen","shellRef","_a","filtered","sorted","b","q","_b","PageHeader","FilterBar","FilterInput","i"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as f,j as e}from"./vendor-query-DvWWuNIB.js";import{aZ as w,bb as R,bq as N,br as I,a3 as F,bs as v,a6 as j,Z as _,w as O,bt as M,aX as k,j as $,b1 as A,a0 as V}from"./index-CU2UW2y6.js";import{u as W}from"./useContainerWidth-DSLp1gEm.js";import{u as T,E as q,a as z,b as B}from"./EscalationTimeline-DT2Owdl0.js";import{a as K}from"./escalation-columns-BaYuzMYf.js";import{P as G,am as Y,an as H,ao as U,aC as X,L as C}from"./vendor-icons-en0_uSRD.js";import{u as Z,L as y,e as D}from"./vendor-react-_9U4mKFr.js";import"./escalation-items-CwbNFnsH.js";import"./UserName-BK6Lzsdw.js";import"./validate-YJK_6a7m.js";import"./StatusBadge-DHVwxre2.js";import"./DataTable-FjFhx0SE.js";import"./EmptyState-DgC_6njU.js";import"./StickyPagination-CUwxfBQH.js";import"./EntityTimelinePanel-CKsktd84.js";import"./FilterBar-jtY6KNpL.js";import"./PriorityBadge-r33mpDju.js";import"./RolePill-D3hkAdKf.js";import"./WorkflowPill-CKHip3hH.js";import"./CountdownTimer-D2D68exl.js";const E="system.escalation.",L=[["escalations"]];function Q(t,i){const l=new Set;let c=!1;for(const{url:d}of t){const s=w(i(d));if(!s)continue;const a=R(s);a?l.add(a):c=!0}if(c||l.size===0)return{patterns:c?[N({})]:[],keysFor:()=>L};const r=new Map([...l].map(d=>[I(d),d]));return{patterns:[...l].sort().map(d=>N({role:d})),keysFor(d){const s=d.startsWith(E)?d.slice(E.length).split(".")[0]:"",a=r.get(s);return a?[["escalations",{role:a}],["escalations","available",{role:a}],["escalations",{roles:[a]}],["escalations","available",{roles:[a]}],["escalations","related",a],["escalations","related-rows",a]]:L}}}const J=25,ee=15e3,te={statusFilter:"",facets:{},view:null},se={"--lt-sticky-top":"0px"};function ae({pin:t,url:i,from:l}){const c=Z(),r=f.useMemo(()=>w(i),[i]),[p,d]=f.useState(null),[s,a]=f.useState(void 0),m=s??(r==null?void 0:r.facets.orderBy),o=f.useMemo(()=>r?m===r.facets.orderBy?r:{...r,facets:{...r.facets,orderBy:m}}:null,[r,m]),n=T(o??te,{limit:J,offset:0,staleTime:ee,enabled:o!==null}),h=o?F(p??o.view,n.hasRichView):null,b=f.useMemo(()=>Object.keys((o==null?void 0:o.facets.facets)??{}),[o]),g=x=>c(`/escalations/detail/${x.id}`,{state:{from:l}}),u=Math.max(0,n.total-n.escalations.length);return e.jsxs("section",{className:"min-h-0 flex flex-col overflow-hidden border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)]",style:se,"data-testid":"portal-panel",children:[e.jsxs("header",{className:"flex items-center justify-between gap-2 px-4 py-2.5 shrink-0 border-b border-surface-border bg-surface-hover",children:[e.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[e.jsx(G,{className:"w-3.5 h-3.5 shrink-0 text-accent/60",strokeWidth:1.5}),e.jsx("h2",{className:"section-h2 truncate",title:t.label,children:t.label}),o&&n.total>0&&e.jsx("span",{className:"text-2xs tabular-nums text-text-tertiary","data-testid":"portal-panel-count",children:v(n.total)})]}),e.jsxs("div",{className:"flex items-center gap-1.5 shrink-0",children:[o&&h!==j.TIMELINE&&n.hasRichView&&e.jsx("div",{role:"radiogroup","aria-label":"Panel view",className:"flex items-center rounded overflow-hidden border border-surface-border",children:[{key:j.TABLE,title:"Table view",Icon:Y},{key:j.RICH,title:"Rich view",Icon:H}].map(({key:x,title:S,Icon:P})=>e.jsx("button",{type:"button",role:"radio","aria-checked":h===x,onClick:()=>d(x),className:`px-1.5 py-1 transition-colors ${h===x?"bg-accent text-text-inverse":"icon-link hover:bg-surface-raised"}`,title:S,children:e.jsx(P,{className:"w-3.5 h-3.5"})},x))}),o&&e.jsx(q,{orderBy:m,onChange:a}),o&&e.jsx("button",{type:"button",onClick:n.refetch,disabled:n.isFetching,className:"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover disabled:opacity-50",title:"Refresh",children:e.jsx(U,{className:`w-3.5 h-3.5 ${n.isFetching?"animate-spin":""}`})}),e.jsx(y,{to:i,className:"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover",title:"Open the full view","data-testid":"portal-panel-open",children:e.jsx(X,{className:"w-3.5 h-3.5"})})]})]}),e.jsxs("div",{className:"flex-1 min-h-0 overflow-y-auto @container px-4 py-3",children:[o?n.error?e.jsxs("p",{className:"py-6 text-center text-xs text-status-error",role:"alert",children:["Failed to load: ",n.error.message]}):h===j.TIMELINE?e.jsx(z,{escalations:n.escalations,highlightKeys:b,onRowClick:g,total:n.total,page:1,totalPages:1,onPageChange:()=>{}}):h===j.RICH?e.jsx(K,{role:n.singleRole,listSchema:n.listSchema,listSchemaVersion:n.listSchemaVersion,activeEscalations:n.escalations,groups:n.groups,onRowClick:g,onOpenGroup:x=>c(x)}):e.jsx(B,{escalations:n.escalations,highlightKeys:b,onRowClick:g,isLoading:n.isLoading,layout:o.layout}):e.jsxs("p",{className:"py-6 text-center text-xs text-text-quaternary",children:[e.jsx(y,{to:i,className:"text-accent hover:underline",children:t.label})," opens as its own page."]}),u>0&&e.jsxs(y,{to:i,className:"block pt-3 pb-1 text-2xs text-accent hover:underline","data-testid":"portal-panel-more",children:[v(u)," more in the full view"]})]})]})}const ne=54;function re(){if(typeof document>"u")return 16;const t=parseFloat(getComputedStyle(document.documentElement).fontSize);return Number.isFinite(t)&&t>0?t:16}function le({rows:t,resolveUrl:i,from:l}){const[c,r]=W(),p=r!==null&&r<ne*re(),d=(s,a)=>e.jsx(ae,{pin:s,url:i(s.url),from:l},a);return p?e.jsx("div",{ref:c,className:"flex-1 min-h-0 overflow-y-auto grid grid-cols-1 gap-5 auto-rows-[minmax(18rem,auto)]","data-testid":"portal-grid","data-stacked":"true",children:t.flatMap((s,a)=>s.map((m,o)=>d(m,`${a}-${o}`)))}):e.jsx("div",{ref:c,className:"flex-1 min-h-0 grid gap-5",style:{gridTemplateRows:`repeat(${t.length}, minmax(0, 1fr))`},"data-testid":"portal-grid",children:t.map((s,a)=>e.jsx("div",{className:"min-h-0 grid gap-5",style:{gridTemplateColumns:`repeat(${s.length}, minmax(0, 1fr))`},"data-testid":"portal-row",children:s.map((m,o)=>d(m,`${a}-${o}`))},a))})}const oe={statusFilter:"",facets:{},view:null},ie=15e3;function ce({count:t,url:i}){const l=f.useMemo(()=>w(i),[i]),c=T(l??oe,{limit:1,offset:0,staleTime:ie,enabled:l!==null}),r=l?c.isLoading&&c.total===0?null:c.total:null;return e.jsxs(y,{to:i,className:"block min-w-0 border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)] px-4 py-3 hover:bg-surface-hover transition-colors",title:`Open ${t.label}`,"data-testid":"portal-count",children:[e.jsx("p",{className:"text-2xs uppercase tracking-widest text-text-tertiary truncate",children:t.label}),e.jsx("p",{className:"mt-1 text-3xl font-light leading-none text-text-primary","data-testid":"portal-count-value",children:r===null?e.jsx("span",{className:"text-text-quaternary",children:"—"}):v(r)}),t.blurb&&e.jsx("p",{className:"mt-1.5 text-xs text-text-secondary leading-snug",children:t.blurb})]})}function de({counts:t,resolveUrl:i}){return t.length===0?null:e.jsx("div",{className:"grid gap-4 shrink-0 mb-4",style:{gridTemplateColumns:"repeat(auto-fit, minmax(10rem, 1fr))"},"data-testid":"portal-counts",children:t.map((l,c)=>e.jsx(ce,{count:l,url:i(l.url)},`${l.label}-${c}`))})}function me({text:t}){return e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center py-10 text-center",children:[e.jsx(C,{className:"w-6 h-6 text-text-quaternary/50 mb-2",strokeWidth:1}),e.jsx("p",{className:"text-xs text-text-quaternary",children:t})]})}function Ie(){const{role:t="",portal:i}=D(),{data:l,isFetched:c}=_(),{resolveUrl:r}=O(),p=l==null?void 0:l.roles.find(u=>u.role===t),d=M(p),s=i?d.find(u=>u.key===i):d[0],a=(s==null?void 0:s.rows)??null,m=(s==null?void 0:s.counts)??[],o=s?k(t,s.key):k(t),n=f.useMemo(()=>Q(a?[...a.flat(),...m]:[],r),[a,m,r]),h=$("LIST");A(n.patterns,u=>{h(n.keysFor(u.type))});const b=a?a.reduce((u,x)=>u+x.length,0):0,g=c?d.length===0?"This role declares no portal yet. Compose one under Admin, Roles, Portal.":s?null:"This role has no portal by that name.":null;return e.jsxs("div",{className:"flex-1 min-h-0 flex flex-col -mt-[calc(2rem-var(--lt-space-page-x))] -mb-[calc(4rem-var(--lt-space-page-x))]",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3 shrink-0 text-2xs uppercase tracking-widest text-text-tertiary",children:[e.jsx(C,{className:"w-3 h-3 text-accent/60 shrink-0",strokeWidth:1.5}),e.jsx("h1",{className:"truncate font-semibold",title:t,children:V(p??{role:t})}),s&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-text-quaternary",children:"·"}),e.jsx("span",{className:"truncate","data-testid":"portal-title",children:s.label})]}),b>0&&e.jsxs("span",{className:"text-text-quaternary normal-case tracking-normal",children:[b," ",b===1?"view":"views"]})]}),c?g?e.jsx(me,{text:g}):e.jsxs(e.Fragment,{children:[e.jsx(de,{counts:m,resolveUrl:r}),e.jsx(le,{rows:a,resolveUrl:r,from:o})]}):null]})}export{Ie as PortalPage};
2
- //# sourceMappingURL=PortalPage-ChB5pBTZ.js.map
1
+ import{a as f,j as e}from"./vendor-query-DvWWuNIB.js";import{a_ as w,bc as R,bs as N,bt as I,a5 as _,bu as v,a8 as j,$ as F,w as O,bv as $,aY as k,j as M,b2 as A,a2 as V}from"./index-C_dAJUzh.js";import{u as W}from"./useContainerWidth-DSLp1gEm.js";import{u as T,E as q,a as z,b as B}from"./EscalationTimeline-CCKpuRBU.js";import{a as K}from"./escalation-columns-R_IEVQjQ.js";import{P as Y,an as G,ao as D,ap as H,aD as U,L as S}from"./vendor-icons-ed7mYQw4.js";import{u as Q,L as y,e as X}from"./vendor-react-_9U4mKFr.js";import"./escalation-items-BGZlJMpk.js";import"./UserName-Dw3R_zrl.js";import"./validate-CYhW1xMJ.js";import"./StatusBadge-DHVwxre2.js";import"./DataTable-FjFhx0SE.js";import"./EmptyState-DgC_6njU.js";import"./StickyPagination-CgVv7vc_.js";import"./EntityTimelinePanel-BOHlIaxF.js";import"./FilterBar-newA5l-X.js";import"./PriorityBadge-r33mpDju.js";import"./RolePill-BjD1Qw5y.js";import"./WorkflowPill-BiLYq__w.js";import"./CountdownTimer-BHVIq3kA.js";const E="system.escalation.",L=[["escalations"]];function J(t,i){const l=new Set;let c=!1;for(const{url:d}of t){const s=w(i(d));if(!s)continue;const a=R(s);a?l.add(a):c=!0}if(c||l.size===0)return{patterns:c?[N({})]:[],keysFor:()=>L};const r=new Map([...l].map(d=>[I(d),d]));return{patterns:[...l].sort().map(d=>N({role:d})),keysFor(d){const s=d.startsWith(E)?d.slice(E.length).split(".")[0]:"",a=r.get(s);return a?[["escalations",{role:a}],["escalations","available",{role:a}],["escalations",{roles:[a]}],["escalations","available",{roles:[a]}],["escalations","related",a],["escalations","related-rows",a]]:L}}}const Z=25,ee=15e3,te={statusFilter:"",facets:{},view:null},se={"--lt-sticky-top":"0px"};function ae({pin:t,url:i,from:l}){const c=Q(),r=f.useMemo(()=>w(i),[i]),[p,d]=f.useState(null),[s,a]=f.useState(void 0),m=s??(r==null?void 0:r.facets.orderBy),o=f.useMemo(()=>r?m===r.facets.orderBy?r:{...r,facets:{...r.facets,orderBy:m}}:null,[r,m]),n=T(o??te,{limit:Z,offset:0,staleTime:ee,enabled:o!==null}),h=o?_(p??o.view,n.hasRichView):null,b=f.useMemo(()=>Object.keys((o==null?void 0:o.facets.facets)??{}),[o]),g=x=>c(`/escalations/detail/${x.id}`,{state:{from:l}}),u=Math.max(0,n.total-n.escalations.length);return e.jsxs("section",{className:"min-h-0 flex flex-col overflow-hidden border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)]",style:se,"data-testid":"portal-panel",children:[e.jsxs("header",{className:"flex items-center justify-between gap-2 px-4 py-2.5 shrink-0 border-b border-surface-border bg-surface-hover",children:[e.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[e.jsx(Y,{className:"w-3.5 h-3.5 shrink-0 text-accent/60",strokeWidth:1.5}),e.jsx("h2",{className:"section-h2 truncate",title:t.label,children:t.label}),o&&n.total>0&&e.jsx("span",{className:"text-2xs tabular-nums text-text-tertiary","data-testid":"portal-panel-count",children:v(n.total)})]}),e.jsxs("div",{className:"flex items-center gap-1.5 shrink-0",children:[o&&h!==j.TIMELINE&&n.hasRichView&&e.jsx("div",{role:"radiogroup","aria-label":"Panel view",className:"flex items-center rounded overflow-hidden border border-surface-border",children:[{key:j.TABLE,title:"Table view",Icon:G},{key:j.RICH,title:"Rich view",Icon:D}].map(({key:x,title:C,Icon:P})=>e.jsx("button",{type:"button",role:"radio","aria-checked":h===x,onClick:()=>d(x),className:`px-1.5 py-1 transition-colors ${h===x?"bg-accent text-text-inverse":"icon-link hover:bg-surface-raised"}`,title:C,children:e.jsx(P,{className:"w-3.5 h-3.5"})},x))}),o&&e.jsx(q,{orderBy:m,onChange:a}),o&&e.jsx("button",{type:"button",onClick:n.refetch,disabled:n.isFetching,className:"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover disabled:opacity-50",title:"Refresh",children:e.jsx(H,{className:`w-3.5 h-3.5 ${n.isFetching?"animate-spin":""}`})}),e.jsx(y,{to:i,className:"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover",title:"Open the full view","data-testid":"portal-panel-open",children:e.jsx(U,{className:"w-3.5 h-3.5"})})]})]}),e.jsxs("div",{className:"flex-1 min-h-0 overflow-y-auto @container px-4 py-3",children:[o?n.error?e.jsxs("p",{className:"py-6 text-center text-xs text-status-error",role:"alert",children:["Failed to load: ",n.error.message]}):h===j.TIMELINE?e.jsx(z,{escalations:n.escalations,highlightKeys:b,onRowClick:g,total:n.total,page:1,totalPages:1,onPageChange:()=>{}}):h===j.RICH?e.jsx(K,{role:n.singleRole,listSchema:n.listSchema,listSchemaVersion:n.listSchemaVersion,activeEscalations:n.escalations,groups:n.groups,onRowClick:g,onOpenGroup:x=>c(x)}):e.jsx(B,{escalations:n.escalations,highlightKeys:b,onRowClick:g,isLoading:n.isLoading,layout:o.layout}):e.jsxs("p",{className:"py-6 text-center text-xs text-text-quaternary",children:[e.jsx(y,{to:i,className:"text-accent hover:underline",children:t.label})," opens as its own page."]}),u>0&&e.jsxs(y,{to:i,className:"block pt-3 pb-1 text-2xs text-accent hover:underline","data-testid":"portal-panel-more",children:[v(u)," more in the full view"]})]})]})}const ne=54;function re(){if(typeof document>"u")return 16;const t=parseFloat(getComputedStyle(document.documentElement).fontSize);return Number.isFinite(t)&&t>0?t:16}function le({rows:t,resolveUrl:i,from:l}){const[c,r]=W(),p=r!==null&&r<ne*re(),d=(s,a)=>e.jsx(ae,{pin:s,url:i(s.url),from:l},a);return p?e.jsx("div",{ref:c,className:"flex-1 min-h-0 overflow-y-auto grid grid-cols-1 gap-5 auto-rows-[minmax(18rem,auto)]","data-testid":"portal-grid","data-stacked":"true",children:t.flatMap((s,a)=>s.map((m,o)=>d(m,`${a}-${o}`)))}):e.jsx("div",{ref:c,className:"flex-1 min-h-0 grid gap-5",style:{gridTemplateRows:`repeat(${t.length}, minmax(0, 1fr))`},"data-testid":"portal-grid",children:t.map((s,a)=>e.jsx("div",{className:"min-h-0 grid gap-5",style:{gridTemplateColumns:`repeat(${s.length}, minmax(0, 1fr))`},"data-testid":"portal-row",children:s.map((m,o)=>d(m,`${a}-${o}`))},a))})}const oe={statusFilter:"",facets:{},view:null},ie=15e3;function ce({count:t,url:i}){const l=f.useMemo(()=>w(i),[i]),c=T(l??oe,{limit:1,offset:0,staleTime:ie,enabled:l!==null}),r=l?c.isLoading&&c.total===0?null:c.total:null;return e.jsxs(y,{to:i,className:"block min-w-0 border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)] px-4 py-3 hover:bg-surface-hover transition-colors",title:`Open ${t.label}`,"data-testid":"portal-count",children:[e.jsx("p",{className:"text-2xs uppercase tracking-widest text-text-tertiary truncate",children:t.label}),e.jsx("p",{className:"mt-1 text-3xl font-light leading-none text-text-primary","data-testid":"portal-count-value",children:r===null?e.jsx("span",{className:"text-text-quaternary",children:"—"}):v(r)}),t.blurb&&e.jsx("p",{className:"mt-1.5 text-xs text-text-secondary leading-snug",children:t.blurb})]})}function de({counts:t,resolveUrl:i}){return t.length===0?null:e.jsx("div",{className:"grid gap-4 shrink-0 mb-4",style:{gridTemplateColumns:"repeat(auto-fit, minmax(10rem, 1fr))"},"data-testid":"portal-counts",children:t.map((l,c)=>e.jsx(ce,{count:l,url:i(l.url)},`${l.label}-${c}`))})}function me({text:t}){return e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center py-10 text-center",children:[e.jsx(S,{className:"w-6 h-6 text-text-quaternary/50 mb-2",strokeWidth:1}),e.jsx("p",{className:"text-xs text-text-quaternary",children:t})]})}function Ie(){const{role:t="",portal:i}=X(),{data:l,isFetched:c}=F(),{resolveUrl:r}=O(),p=l==null?void 0:l.roles.find(u=>u.role===t),d=$(p),s=i?d.find(u=>u.key===i):d[0],a=(s==null?void 0:s.rows)??null,m=(s==null?void 0:s.counts)??[],o=s?k(t,s.key):k(t),n=f.useMemo(()=>J(a?[...a.flat(),...m]:[],r),[a,m,r]),h=M("LIST");A(n.patterns,u=>{h(n.keysFor(u.type))});const b=a?a.reduce((u,x)=>u+x.length,0):0,g=c?d.length===0?"This role declares no portal yet. Compose one under Admin, Roles, Portal.":s?null:"This role has no portal by that name.":null;return e.jsxs("div",{className:"flex-1 min-h-0 flex flex-col -mt-[calc(2rem-var(--lt-space-page-x))] -mb-[calc(4rem-var(--lt-space-page-x))]",children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3 shrink-0 text-2xs uppercase tracking-widest text-text-tertiary",children:[e.jsx(S,{className:"w-3 h-3 text-accent/60 shrink-0",strokeWidth:1.5}),e.jsx("h1",{className:"truncate font-semibold",title:t,children:V(p??{role:t})}),s&&e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"text-text-quaternary",children:"·"}),e.jsx("span",{className:"truncate","data-testid":"portal-title",children:s.label})]}),b>0&&e.jsxs("span",{className:"text-text-quaternary normal-case tracking-normal",children:[b," ",b===1?"view":"views"]})]}),c?g?e.jsx(me,{text:g}):e.jsxs(e.Fragment,{children:[e.jsx(de,{counts:m,resolveUrl:r}),e.jsx(le,{rows:a,resolveUrl:r,from:o})]}):null]})}export{Ie as PortalPage};
2
+ //# sourceMappingURL=PortalPage-D3ybr0mo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PortalPage-ChB5pBTZ.js","sources":["../../src/lib/portal-refresh.ts","../../src/components/portal/PortalPanel.tsx","../../src/components/portal/PortalGrid.tsx","../../src/components/portal/PortalCounts.tsx","../../src/pages/operator/PortalPage.tsx"],"sourcesContent":["import type { QueryKey } from '@tanstack/react-query';\nimport type { RolePin } from '../api/roles';\nimport { escalationPattern, sanitizeSubjectToken } from './events/subjects';\nimport { parseEscalationListUrl, singleRoleOf } from './escalation-list-url';\n\n/** The subject root every escalation event shares; the role token follows it. */\nconst ESCALATION_TYPE_PREFIX = 'system.escalation.';\nconst WIDE_KEYS: QueryKey[] = [['escalations']];\n\nexport interface PortalRefreshPlan {\n /** One subscription pattern per queue the portal reads; one wide pattern when a URL names no single queue. */\n patterns: string[];\n /** The query keys an event of this type should refresh: the moved queue's lists, or every list when scope is wide. */\n keysFor(eventType: string): QueryKey[];\n}\n\n/**\n * How a portal stays fresh. Every list query key carries its params object,\n * and the query client matches object segments partially, so an event on\n * one queue refreshes only that queue's panels and tiles (pinned by `role`\n * or by a lone `roles` entry) and the queue's related sets. A URL that names\n * several roles or none widens the plan to the whole escalation family.\n */\nexport function portalRefreshPlan(urls: Array<Pick<RolePin, 'url'>>, resolveUrl: (url: string) => string): PortalRefreshPlan {\n const roles = new Set<string>();\n let wide = false;\n for (const { url } of urls) {\n const params = parseEscalationListUrl(resolveUrl(url));\n if (!params) continue;\n const scoped = singleRoleOf(params);\n if (scoped) roles.add(scoped); else wide = true;\n }\n if (wide || roles.size === 0) {\n return { patterns: wide ? [escalationPattern({})] : [], keysFor: () => WIDE_KEYS };\n }\n const byToken = new Map([...roles].map((role) => [sanitizeSubjectToken(role), role]));\n const patterns = [...roles].sort().map((role) => escalationPattern({ role }));\n return {\n patterns,\n keysFor(eventType) {\n const token = eventType.startsWith(ESCALATION_TYPE_PREFIX) ? eventType.slice(ESCALATION_TYPE_PREFIX.length).split('.')[0] : '';\n const role = byToken.get(token);\n return role\n ? [\n ['escalations', { role }],\n ['escalations', 'available', { role }],\n ['escalations', { roles: [role] }],\n ['escalations', 'available', { roles: [role] }],\n ['escalations', 'related', role],\n ['escalations', 'related-rows', role],\n ]\n : WIDE_KEYS;\n },\n };\n}\n","import { useMemo, useState, type CSSProperties } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport { ExternalLink, LayoutList, Pin, RefreshCw, Table } from 'lucide-react';\nimport type { RolePin } from '../../api/roles';\nimport type { FacetOrder } from '../../api/escalations';\nimport type { LTEscalationRecord } from '../../api/types';\nimport { useEscalationListQuery } from '../../hooks/useEscalationListQuery';\nimport { LIST_VIEWS, parseEscalationListUrl, resolveListView, type EscalationListParams, type ListView } from '../../lib/escalation-list-url';\nimport { EscalationSortControl } from '../../pages/operator/EscalationSortControl';\nimport { formatCountCompact } from '../../lib/format';\nimport { EscalationListView } from '../escalation/EscalationListView';\nimport { EscalationTimeline } from '../escalation/EscalationTimeline';\nimport { EscalationTableView } from '../escalation/EscalationTableView';\n\n/** Rows a panel shows before pointing at the full view. */\nexport const PORTAL_PANEL_ROWS = 25;\nconst PANEL_STALE_MS = 15_000;\nconst NO_LIST: EscalationListParams = { statusFilter: '', facets: {}, view: null, layout: null };\n// Table headers stick to the panel's own scroll, not the shell's offset.\nconst PANEL_STYLE = { '--lt-sticky-top': '0px' } as CSSProperties;\n\n/**\n * One portal cell: a pinned list view rendered live. The pin's URL is parsed\n * into the same query the list page runs and drawn with the same components,\n * so the panel and the page it opens onto always agree. No filters, no\n * selection, no pagination: the label, the count, a refresh, and the rows.\n * Each panel is its own bounded sheet, the section recipe (sunken surface,\n * border, section radius), so the portal reads as distinct segments. Two\n * light controls let a person at the screen adapt a panel: the view (table or\n * the role's rich view) and the sort direction. They hold for the visit only;\n * the pin stays as authored.\n */\nexport function PortalPanel({ pin, url, from }: {\n pin: RolePin;\n /** The pin's URL with link variables already resolved. */\n url: string;\n /** Where a detail page opened from here returns to. */\n from: string;\n}) {\n const navigate = useNavigate();\n const pinParams = useMemo(() => parseEscalationListUrl(url), [url]);\n // Visit-local adaptations over the pin: a chosen view, a chosen sort direction.\n const [chosenView, setChosenView] = useState<ListView | null>(null);\n const [chosenOrder, setChosenOrder] = useState<FacetOrder[] | undefined>(undefined);\n const orderBy = chosenOrder ?? pinParams?.facets.orderBy;\n const params = useMemo<EscalationListParams | null>(() => {\n if (!pinParams) return null;\n if (orderBy === pinParams.facets.orderBy) return pinParams;\n return { ...pinParams, facets: { ...pinParams.facets, orderBy } };\n }, [pinParams, orderBy]);\n const list = useEscalationListQuery(params ?? NO_LIST, {\n limit: PORTAL_PANEL_ROWS,\n offset: 0,\n staleTime: PANEL_STALE_MS,\n enabled: params !== null,\n });\n const view = params ? resolveListView(chosenView ?? params.view, list.hasRichView) : null;\n const highlightKeys = useMemo(() => Object.keys(params?.facets.facets ?? {}), [params]);\n const openRow = (row: LTEscalationRecord) => navigate(`/escalations/detail/${row.id}`, { state: { from } });\n const more = Math.max(0, list.total - list.escalations.length);\n\n return (\n <section\n className=\"min-h-0 flex flex-col overflow-hidden border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)]\"\n style={PANEL_STYLE}\n data-testid=\"portal-panel\"\n >\n <header className=\"flex items-center justify-between gap-2 px-4 py-2.5 shrink-0 border-b border-surface-border bg-surface-hover\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <Pin className=\"w-3.5 h-3.5 shrink-0 text-accent/60\" strokeWidth={1.5} />\n <h2 className=\"section-h2 truncate\" title={pin.label}>{pin.label}</h2>\n {params && list.total > 0 && (\n <span className=\"text-2xs tabular-nums text-text-tertiary\" data-testid=\"portal-panel-count\">\n {formatCountCompact(list.total)}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {params && view !== LIST_VIEWS.TIMELINE && list.hasRichView && (\n <div role=\"radiogroup\" aria-label=\"Panel view\" className=\"flex items-center rounded overflow-hidden border border-surface-border\">\n {[\n { key: LIST_VIEWS.TABLE, title: 'Table view', Icon: Table },\n { key: LIST_VIEWS.RICH, title: 'Rich view', Icon: LayoutList },\n ].map(({ key, title, Icon }) => (\n <button\n key={key}\n type=\"button\"\n role=\"radio\"\n aria-checked={view === key}\n onClick={() => setChosenView(key)}\n className={`px-1.5 py-1 transition-colors ${view === key ? 'bg-accent text-text-inverse' : 'icon-link hover:bg-surface-raised'}`}\n title={title}\n >\n <Icon className=\"w-3.5 h-3.5\" />\n </button>\n ))}\n </div>\n )}\n {params && <EscalationSortControl orderBy={orderBy} onChange={setChosenOrder} />}\n {params && (\n <button\n type=\"button\"\n onClick={list.refetch}\n disabled={list.isFetching}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`w-3.5 h-3.5 ${list.isFetching ? 'animate-spin' : ''}`} />\n </button>\n )}\n <Link\n to={url}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover\"\n title=\"Open the full view\"\n data-testid=\"portal-panel-open\"\n >\n <ExternalLink className=\"w-3.5 h-3.5\" />\n </Link>\n </div>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto @container px-4 py-3\">\n {!params ? (\n <p className=\"py-6 text-center text-xs text-text-quaternary\">\n <Link to={url} className=\"text-accent hover:underline\">{pin.label}</Link> opens as its own page.\n </p>\n ) : list.error ? (\n <p className=\"py-6 text-center text-xs text-status-error\" role=\"alert\">\n Failed to load: {list.error.message}\n </p>\n ) : view === LIST_VIEWS.TIMELINE ? (\n <EscalationTimeline\n escalations={list.escalations}\n highlightKeys={highlightKeys}\n onRowClick={openRow}\n total={list.total}\n page={1}\n totalPages={1}\n onPageChange={() => {}}\n />\n ) : view === LIST_VIEWS.RICH ? (\n <EscalationListView\n role={list.singleRole!}\n listSchema={list.listSchema!}\n listSchemaVersion={list.listSchemaVersion}\n activeEscalations={list.escalations}\n groups={list.groups}\n onRowClick={openRow}\n onOpenGroup={(target) => navigate(target)}\n />\n ) : (\n <EscalationTableView\n escalations={list.escalations}\n highlightKeys={highlightKeys}\n onRowClick={openRow}\n isLoading={list.isLoading}\n layout={params.layout}\n />\n )}\n {more > 0 && (\n <Link to={url} className=\"block pt-3 pb-1 text-2xs text-accent hover:underline\" data-testid=\"portal-panel-more\">\n {formatCountCompact(more)} more in the full view\n </Link>\n )}\n </div>\n </section>\n );\n}\n","import type { RolePin } from '../../api/roles';\nimport { useContainerWidth } from '../../hooks/useContainerWidth';\nimport { PortalPanel } from './PortalPanel';\n\n/** Below this container width (the `split` threshold) every cell stacks into one column. */\nexport const PORTAL_STACK_REM = 54;\n\nfunction rootFontPx(): number {\n if (typeof document === 'undefined') return 16;\n const size = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return Number.isFinite(size) && size > 0 ? size : 16;\n}\n\n/**\n * The portal matrix as a page: one grid row per declared row, each dividing\n * its width evenly among its cells, every cell a live panel with its own\n * scroll. A narrow box stacks the cells in declaration order instead.\n */\nexport function PortalGrid({ rows, resolveUrl, from }: {\n rows: RolePin[][];\n /** Link-variable substitution for a pin URL, applied at render time. */\n resolveUrl: (url: string) => string;\n from: string;\n}) {\n const [ref, width] = useContainerWidth<HTMLDivElement>();\n const stacked = width !== null && width < PORTAL_STACK_REM * rootFontPx();\n const cell = (pin: RolePin, key: string) => <PortalPanel key={key} pin={pin} url={resolveUrl(pin.url)} from={from} />;\n\n if (stacked) {\n return (\n <div ref={ref} className=\"flex-1 min-h-0 overflow-y-auto grid grid-cols-1 gap-5 auto-rows-[minmax(18rem,auto)]\" data-testid=\"portal-grid\" data-stacked=\"true\">\n {rows.flatMap((row, r) => row.map((pin, c) => cell(pin, `${r}-${c}`)))}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"flex-1 min-h-0 grid gap-5\"\n style={{ gridTemplateRows: `repeat(${rows.length}, minmax(0, 1fr))` }}\n data-testid=\"portal-grid\"\n >\n {rows.map((row, r) => (\n <div\n key={r}\n className=\"min-h-0 grid gap-5\"\n style={{ gridTemplateColumns: `repeat(${row.length}, minmax(0, 1fr))` }}\n data-testid=\"portal-row\"\n >\n {row.map((pin, c) => cell(pin, `${r}-${c}`))}\n </div>\n ))}\n </div>\n );\n}\n","import { useMemo } from 'react';\nimport { Link } from 'react-router-dom';\nimport type { PortalCount } from '../../api/roles';\nimport { useEscalationListQuery } from '../../hooks/useEscalationListQuery';\nimport { parseEscalationListUrl, type EscalationListParams } from '../../lib/escalation-list-url';\nimport { formatCountCompact } from '../../lib/format';\n\nconst NO_LIST: EscalationListParams = { statusFilter: '', facets: {}, view: null, layout: null };\nconst TILE_STALE_MS = 15_000;\n\n/**\n * One count tile: the live total of a list URL, the same number a pin badge\n * carries (limit 1, read total), under a label and over a blurb. The tile\n * links to the list it counts.\n */\nexport function PortalCountTile({ count, url }: { count: PortalCount; url: string }) {\n const params = useMemo(() => parseEscalationListUrl(url), [url]);\n const list = useEscalationListQuery(params ?? NO_LIST, { limit: 1, offset: 0, staleTime: TILE_STALE_MS, enabled: params !== null });\n const value = !params ? null : list.isLoading && list.total === 0 ? null : list.total;\n return (\n <Link\n to={url}\n className=\"block min-w-0 border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)] px-4 py-3 hover:bg-surface-hover transition-colors\"\n title={`Open ${count.label}`}\n data-testid=\"portal-count\"\n >\n <p className=\"text-2xs uppercase tracking-widest text-text-tertiary truncate\">{count.label}</p>\n <p className=\"mt-1 text-3xl font-light leading-none text-text-primary\" data-testid=\"portal-count-value\">\n {value === null ? <span className=\"text-text-quaternary\">—</span> : formatCountCompact(value)}\n </p>\n {count.blurb && <p className=\"mt-1.5 text-xs text-text-secondary leading-snug\">{count.blurb}</p>}\n </Link>\n );\n}\n\n/** The row of count tiles above a portal's panels; every tile shares one grid so numbers align. */\nexport function PortalCounts({ counts, resolveUrl }: { counts: PortalCount[]; resolveUrl: (url: string) => string }) {\n if (counts.length === 0) return null;\n return (\n <div\n className=\"grid gap-4 shrink-0 mb-4\"\n style={{ gridTemplateColumns: `repeat(auto-fit, minmax(10rem, 1fr))` }}\n data-testid=\"portal-counts\"\n >\n {counts.map((count, i) => <PortalCountTile key={`${count.label}-${i}`} count={count} url={resolveUrl(count.url)} />)}\n </div>\n );\n}\n","import { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { LayoutGrid } from 'lucide-react';\nimport { useRoleDetails } from '../../api/roles';\nimport { useLinkVariables } from '../../hooks/useLinkVariables';\nimport { useEventSubscriptions } from '../../hooks/useEventContext';\nimport { useThrottledInvalidation } from '../../hooks/useEventHooks';\nimport { portalPath, portalsOf } from '../../lib/portal-path';\nimport { portalRefreshPlan } from '../../lib/portal-refresh';\nimport { displayRoleTitle } from '../../lib/role-display';\nimport { PortalGrid } from '../../components/portal/PortalGrid';\nimport { PortalCounts } from '../../components/portal/PortalCounts';\n\nfunction EmptyPortal({ text }: { text: string }) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center py-10 text-center\">\n <LayoutGrid className=\"w-6 h-6 text-text-quaternary/50 mb-2\" strokeWidth={1} />\n <p className=\"text-xs text-text-quaternary\">{text}</p>\n </div>\n );\n}\n\n/**\n * One of a role's portals: a named matrix of pinned views rendered as one\n * page of live panels. Any signed-in user may open it; every panel reads\n * through the same role-scoped list endpoints as the list page, so each\n * viewer sees what their read scope returns. With no portal key the role's\n * first portal shows. One subscription per queue the cells and tiles name\n * keeps them current, each event refreshing only the queue that moved.\n */\nexport function PortalPage() {\n const { role = '', portal: portalKey } = useParams<{ role: string; portal?: string }>();\n const { data: roleData, isFetched } = useRoleDetails();\n const { resolveUrl } = useLinkVariables();\n\n const detail = roleData?.roles.find((r) => r.role === role);\n const portals = portalsOf(detail);\n const portal = portalKey ? portals.find((p) => p.key === portalKey) : portals[0];\n const rows = portal?.rows ?? null;\n const counts = portal?.counts ?? [];\n const from = portal ? portalPath(role, portal.key) : portalPath(role);\n\n // One subscription per queue the cells and tiles name; an event refreshes\n // only that queue's panels and tiles, through the LIST-tier scheduler.\n const plan = useMemo(() => portalRefreshPlan(rows ? [...rows.flat(), ...counts] : [], resolveUrl), [rows, counts, resolveUrl]);\n const invalidate = useThrottledInvalidation('LIST');\n useEventSubscriptions(plan.patterns, (event) => { invalidate(plan.keysFor(event.type)); });\n\n const cellCount = rows ? rows.reduce((n, r) => n + r.length, 0) : 0;\n const empty = !isFetched ? null\n : portals.length === 0 ? 'This role declares no portal yet. Compose one under Admin, Roles, Portal.'\n : !portal ? 'This role has no portal by that name.'\n : null;\n\n // The panels are the page. The shell pads the content box py-8 pb-16; the\n // portal pulls those back to the side gutter so the frame reads evenly on\n // a screen, and the heading demotes to a quiet caption above the grid.\n return (\n <div className=\"flex-1 min-h-0 flex flex-col -mt-[calc(2rem-var(--lt-space-page-x))] -mb-[calc(4rem-var(--lt-space-page-x))]\">\n <div className=\"flex items-center gap-2 mb-3 shrink-0 text-2xs uppercase tracking-widest text-text-tertiary\">\n <LayoutGrid className=\"w-3 h-3 text-accent/60 shrink-0\" strokeWidth={1.5} />\n <h1 className=\"truncate font-semibold\" title={role}>{displayRoleTitle(detail ?? { role })}</h1>\n {portal && (\n <>\n <span className=\"text-text-quaternary\">·</span>\n <span className=\"truncate\" data-testid=\"portal-title\">{portal.label}</span>\n </>\n )}\n {cellCount > 0 && (\n <span className=\"text-text-quaternary normal-case tracking-normal\">\n {cellCount} {cellCount === 1 ? 'view' : 'views'}\n </span>\n )}\n </div>\n {!isFetched ? null\n : empty ? <EmptyPortal text={empty} />\n : (\n <>\n <PortalCounts counts={counts} resolveUrl={resolveUrl} />\n <PortalGrid rows={rows!} resolveUrl={resolveUrl} from={from} />\n </>\n )}\n </div>\n );\n}\n"],"names":["ESCALATION_TYPE_PREFIX","WIDE_KEYS","portalRefreshPlan","urls","resolveUrl","roles","wide","url","params","parseEscalationListUrl","scoped","singleRoleOf","escalationPattern","byToken","role","sanitizeSubjectToken","eventType","token","PORTAL_PANEL_ROWS","PANEL_STALE_MS","NO_LIST","PANEL_STYLE","PortalPanel","pin","from","navigate","useNavigate","pinParams","useMemo","chosenView","setChosenView","useState","chosenOrder","setChosenOrder","orderBy","list","useEscalationListQuery","view","resolveListView","highlightKeys","openRow","row","more","jsxs","jsx","Pin","formatCountCompact","LIST_VIEWS","Table","LayoutList","key","title","Icon","EscalationSortControl","RefreshCw","Link","ExternalLink","EscalationTimeline","EscalationListView","target","EscalationTableView","PORTAL_STACK_REM","rootFontPx","size","PortalGrid","rows","ref","width","useContainerWidth","stacked","cell","r","c","TILE_STALE_MS","PortalCountTile","count","value","PortalCounts","counts","i","EmptyPortal","text","LayoutGrid","PortalPage","portalKey","useParams","roleData","isFetched","useRoleDetails","useLinkVariables","detail","portals","portalsOf","portal","p","portalPath","plan","invalidate","useThrottledInvalidation","useEventSubscriptions","event","cellCount","n","empty","displayRoleTitle","Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"PortalPage-D3ybr0mo.js","sources":["../../src/lib/portal-refresh.ts","../../src/components/portal/PortalPanel.tsx","../../src/components/portal/PortalGrid.tsx","../../src/components/portal/PortalCounts.tsx","../../src/pages/operator/PortalPage.tsx"],"sourcesContent":["import type { QueryKey } from '@tanstack/react-query';\nimport type { RolePin } from '../api/roles';\nimport { escalationPattern, sanitizeSubjectToken } from './events/subjects';\nimport { parseEscalationListUrl, singleRoleOf } from './escalation-list-url';\n\n/** The subject root every escalation event shares; the role token follows it. */\nconst ESCALATION_TYPE_PREFIX = 'system.escalation.';\nconst WIDE_KEYS: QueryKey[] = [['escalations']];\n\nexport interface PortalRefreshPlan {\n /** One subscription pattern per queue the portal reads; one wide pattern when a URL names no single queue. */\n patterns: string[];\n /** The query keys an event of this type should refresh: the moved queue's lists, or every list when scope is wide. */\n keysFor(eventType: string): QueryKey[];\n}\n\n/**\n * How a portal stays fresh. Every list query key carries its params object,\n * and the query client matches object segments partially, so an event on\n * one queue refreshes only that queue's panels and tiles (pinned by `role`\n * or by a lone `roles` entry) and the queue's related sets. A URL that names\n * several roles or none widens the plan to the whole escalation family.\n */\nexport function portalRefreshPlan(urls: Array<Pick<RolePin, 'url'>>, resolveUrl: (url: string) => string): PortalRefreshPlan {\n const roles = new Set<string>();\n let wide = false;\n for (const { url } of urls) {\n const params = parseEscalationListUrl(resolveUrl(url));\n if (!params) continue;\n const scoped = singleRoleOf(params);\n if (scoped) roles.add(scoped); else wide = true;\n }\n if (wide || roles.size === 0) {\n return { patterns: wide ? [escalationPattern({})] : [], keysFor: () => WIDE_KEYS };\n }\n const byToken = new Map([...roles].map((role) => [sanitizeSubjectToken(role), role]));\n const patterns = [...roles].sort().map((role) => escalationPattern({ role }));\n return {\n patterns,\n keysFor(eventType) {\n const token = eventType.startsWith(ESCALATION_TYPE_PREFIX) ? eventType.slice(ESCALATION_TYPE_PREFIX.length).split('.')[0] : '';\n const role = byToken.get(token);\n return role\n ? [\n ['escalations', { role }],\n ['escalations', 'available', { role }],\n ['escalations', { roles: [role] }],\n ['escalations', 'available', { roles: [role] }],\n ['escalations', 'related', role],\n ['escalations', 'related-rows', role],\n ]\n : WIDE_KEYS;\n },\n };\n}\n","import { useMemo, useState, type CSSProperties } from 'react';\nimport { Link, useNavigate } from 'react-router-dom';\nimport { ExternalLink, LayoutList, Pin, RefreshCw, Table } from 'lucide-react';\nimport type { RolePin } from '../../api/roles';\nimport type { FacetOrder } from '../../api/escalations';\nimport type { LTEscalationRecord } from '../../api/types';\nimport { useEscalationListQuery } from '../../hooks/useEscalationListQuery';\nimport { LIST_VIEWS, parseEscalationListUrl, resolveListView, type EscalationListParams, type ListView } from '../../lib/escalation-list-url';\nimport { EscalationSortControl } from '../../pages/operator/EscalationSortControl';\nimport { formatCountCompact } from '../../lib/format';\nimport { EscalationListView } from '../escalation/EscalationListView';\nimport { EscalationTimeline } from '../escalation/EscalationTimeline';\nimport { EscalationTableView } from '../escalation/EscalationTableView';\n\n/** Rows a panel shows before pointing at the full view. */\nexport const PORTAL_PANEL_ROWS = 25;\nconst PANEL_STALE_MS = 15_000;\nconst NO_LIST: EscalationListParams = { statusFilter: '', facets: {}, view: null, layout: null };\n// Table headers stick to the panel's own scroll, not the shell's offset.\nconst PANEL_STYLE = { '--lt-sticky-top': '0px' } as CSSProperties;\n\n/**\n * One portal cell: a pinned list view rendered live. The pin's URL is parsed\n * into the same query the list page runs and drawn with the same components,\n * so the panel and the page it opens onto always agree. No filters, no\n * selection, no pagination: the label, the count, a refresh, and the rows.\n * Each panel is its own bounded sheet, the section recipe (sunken surface,\n * border, section radius), so the portal reads as distinct segments. Two\n * light controls let a person at the screen adapt a panel: the view (table or\n * the role's rich view) and the sort direction. They hold for the visit only;\n * the pin stays as authored.\n */\nexport function PortalPanel({ pin, url, from }: {\n pin: RolePin;\n /** The pin's URL with link variables already resolved. */\n url: string;\n /** Where a detail page opened from here returns to. */\n from: string;\n}) {\n const navigate = useNavigate();\n const pinParams = useMemo(() => parseEscalationListUrl(url), [url]);\n // Visit-local adaptations over the pin: a chosen view, a chosen sort direction.\n const [chosenView, setChosenView] = useState<ListView | null>(null);\n const [chosenOrder, setChosenOrder] = useState<FacetOrder[] | undefined>(undefined);\n const orderBy = chosenOrder ?? pinParams?.facets.orderBy;\n const params = useMemo<EscalationListParams | null>(() => {\n if (!pinParams) return null;\n if (orderBy === pinParams.facets.orderBy) return pinParams;\n return { ...pinParams, facets: { ...pinParams.facets, orderBy } };\n }, [pinParams, orderBy]);\n const list = useEscalationListQuery(params ?? NO_LIST, {\n limit: PORTAL_PANEL_ROWS,\n offset: 0,\n staleTime: PANEL_STALE_MS,\n enabled: params !== null,\n });\n const view = params ? resolveListView(chosenView ?? params.view, list.hasRichView) : null;\n const highlightKeys = useMemo(() => Object.keys(params?.facets.facets ?? {}), [params]);\n const openRow = (row: LTEscalationRecord) => navigate(`/escalations/detail/${row.id}`, { state: { from } });\n const more = Math.max(0, list.total - list.escalations.length);\n\n return (\n <section\n className=\"min-h-0 flex flex-col overflow-hidden border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)]\"\n style={PANEL_STYLE}\n data-testid=\"portal-panel\"\n >\n <header className=\"flex items-center justify-between gap-2 px-4 py-2.5 shrink-0 border-b border-surface-border bg-surface-hover\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <Pin className=\"w-3.5 h-3.5 shrink-0 text-accent/60\" strokeWidth={1.5} />\n <h2 className=\"section-h2 truncate\" title={pin.label}>{pin.label}</h2>\n {params && list.total > 0 && (\n <span className=\"text-2xs tabular-nums text-text-tertiary\" data-testid=\"portal-panel-count\">\n {formatCountCompact(list.total)}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {params && view !== LIST_VIEWS.TIMELINE && list.hasRichView && (\n <div role=\"radiogroup\" aria-label=\"Panel view\" className=\"flex items-center rounded overflow-hidden border border-surface-border\">\n {[\n { key: LIST_VIEWS.TABLE, title: 'Table view', Icon: Table },\n { key: LIST_VIEWS.RICH, title: 'Rich view', Icon: LayoutList },\n ].map(({ key, title, Icon }) => (\n <button\n key={key}\n type=\"button\"\n role=\"radio\"\n aria-checked={view === key}\n onClick={() => setChosenView(key)}\n className={`px-1.5 py-1 transition-colors ${view === key ? 'bg-accent text-text-inverse' : 'icon-link hover:bg-surface-raised'}`}\n title={title}\n >\n <Icon className=\"w-3.5 h-3.5\" />\n </button>\n ))}\n </div>\n )}\n {params && <EscalationSortControl orderBy={orderBy} onChange={setChosenOrder} />}\n {params && (\n <button\n type=\"button\"\n onClick={list.refetch}\n disabled={list.isFetching}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover disabled:opacity-50\"\n title=\"Refresh\"\n >\n <RefreshCw className={`w-3.5 h-3.5 ${list.isFetching ? 'animate-spin' : ''}`} />\n </button>\n )}\n <Link\n to={url}\n className=\"inline-flex h-7 w-7 items-center justify-center rounded icon-link hover:bg-surface-hover\"\n title=\"Open the full view\"\n data-testid=\"portal-panel-open\"\n >\n <ExternalLink className=\"w-3.5 h-3.5\" />\n </Link>\n </div>\n </header>\n\n <div className=\"flex-1 min-h-0 overflow-y-auto @container px-4 py-3\">\n {!params ? (\n <p className=\"py-6 text-center text-xs text-text-quaternary\">\n <Link to={url} className=\"text-accent hover:underline\">{pin.label}</Link> opens as its own page.\n </p>\n ) : list.error ? (\n <p className=\"py-6 text-center text-xs text-status-error\" role=\"alert\">\n Failed to load: {list.error.message}\n </p>\n ) : view === LIST_VIEWS.TIMELINE ? (\n <EscalationTimeline\n escalations={list.escalations}\n highlightKeys={highlightKeys}\n onRowClick={openRow}\n total={list.total}\n page={1}\n totalPages={1}\n onPageChange={() => {}}\n />\n ) : view === LIST_VIEWS.RICH ? (\n <EscalationListView\n role={list.singleRole!}\n listSchema={list.listSchema!}\n listSchemaVersion={list.listSchemaVersion}\n activeEscalations={list.escalations}\n groups={list.groups}\n onRowClick={openRow}\n onOpenGroup={(target) => navigate(target)}\n />\n ) : (\n <EscalationTableView\n escalations={list.escalations}\n highlightKeys={highlightKeys}\n onRowClick={openRow}\n isLoading={list.isLoading}\n layout={params.layout}\n />\n )}\n {more > 0 && (\n <Link to={url} className=\"block pt-3 pb-1 text-2xs text-accent hover:underline\" data-testid=\"portal-panel-more\">\n {formatCountCompact(more)} more in the full view\n </Link>\n )}\n </div>\n </section>\n );\n}\n","import type { RolePin } from '../../api/roles';\nimport { useContainerWidth } from '../../hooks/useContainerWidth';\nimport { PortalPanel } from './PortalPanel';\n\n/** Below this container width (the `split` threshold) every cell stacks into one column. */\nexport const PORTAL_STACK_REM = 54;\n\nfunction rootFontPx(): number {\n if (typeof document === 'undefined') return 16;\n const size = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return Number.isFinite(size) && size > 0 ? size : 16;\n}\n\n/**\n * The portal matrix as a page: one grid row per declared row, each dividing\n * its width evenly among its cells, every cell a live panel with its own\n * scroll. A narrow box stacks the cells in declaration order instead.\n */\nexport function PortalGrid({ rows, resolveUrl, from }: {\n rows: RolePin[][];\n /** Link-variable substitution for a pin URL, applied at render time. */\n resolveUrl: (url: string) => string;\n from: string;\n}) {\n const [ref, width] = useContainerWidth<HTMLDivElement>();\n const stacked = width !== null && width < PORTAL_STACK_REM * rootFontPx();\n const cell = (pin: RolePin, key: string) => <PortalPanel key={key} pin={pin} url={resolveUrl(pin.url)} from={from} />;\n\n if (stacked) {\n return (\n <div ref={ref} className=\"flex-1 min-h-0 overflow-y-auto grid grid-cols-1 gap-5 auto-rows-[minmax(18rem,auto)]\" data-testid=\"portal-grid\" data-stacked=\"true\">\n {rows.flatMap((row, r) => row.map((pin, c) => cell(pin, `${r}-${c}`)))}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"flex-1 min-h-0 grid gap-5\"\n style={{ gridTemplateRows: `repeat(${rows.length}, minmax(0, 1fr))` }}\n data-testid=\"portal-grid\"\n >\n {rows.map((row, r) => (\n <div\n key={r}\n className=\"min-h-0 grid gap-5\"\n style={{ gridTemplateColumns: `repeat(${row.length}, minmax(0, 1fr))` }}\n data-testid=\"portal-row\"\n >\n {row.map((pin, c) => cell(pin, `${r}-${c}`))}\n </div>\n ))}\n </div>\n );\n}\n","import { useMemo } from 'react';\nimport { Link } from 'react-router-dom';\nimport type { PortalCount } from '../../api/roles';\nimport { useEscalationListQuery } from '../../hooks/useEscalationListQuery';\nimport { parseEscalationListUrl, type EscalationListParams } from '../../lib/escalation-list-url';\nimport { formatCountCompact } from '../../lib/format';\n\nconst NO_LIST: EscalationListParams = { statusFilter: '', facets: {}, view: null, layout: null };\nconst TILE_STALE_MS = 15_000;\n\n/**\n * One count tile: the live total of a list URL, the same number a pin badge\n * carries (limit 1, read total), under a label and over a blurb. The tile\n * links to the list it counts.\n */\nexport function PortalCountTile({ count, url }: { count: PortalCount; url: string }) {\n const params = useMemo(() => parseEscalationListUrl(url), [url]);\n const list = useEscalationListQuery(params ?? NO_LIST, { limit: 1, offset: 0, staleTime: TILE_STALE_MS, enabled: params !== null });\n const value = !params ? null : list.isLoading && list.total === 0 ? null : list.total;\n return (\n <Link\n to={url}\n className=\"block min-w-0 border border-surface-border bg-surface-sunken rounded-[var(--lt-radius-section)] px-4 py-3 hover:bg-surface-hover transition-colors\"\n title={`Open ${count.label}`}\n data-testid=\"portal-count\"\n >\n <p className=\"text-2xs uppercase tracking-widest text-text-tertiary truncate\">{count.label}</p>\n <p className=\"mt-1 text-3xl font-light leading-none text-text-primary\" data-testid=\"portal-count-value\">\n {value === null ? <span className=\"text-text-quaternary\">—</span> : formatCountCompact(value)}\n </p>\n {count.blurb && <p className=\"mt-1.5 text-xs text-text-secondary leading-snug\">{count.blurb}</p>}\n </Link>\n );\n}\n\n/** The row of count tiles above a portal's panels; every tile shares one grid so numbers align. */\nexport function PortalCounts({ counts, resolveUrl }: { counts: PortalCount[]; resolveUrl: (url: string) => string }) {\n if (counts.length === 0) return null;\n return (\n <div\n className=\"grid gap-4 shrink-0 mb-4\"\n style={{ gridTemplateColumns: `repeat(auto-fit, minmax(10rem, 1fr))` }}\n data-testid=\"portal-counts\"\n >\n {counts.map((count, i) => <PortalCountTile key={`${count.label}-${i}`} count={count} url={resolveUrl(count.url)} />)}\n </div>\n );\n}\n","import { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { LayoutGrid } from 'lucide-react';\nimport { useRoleDetails } from '../../api/roles';\nimport { useLinkVariables } from '../../hooks/useLinkVariables';\nimport { useEventSubscriptions } from '../../hooks/useEventContext';\nimport { useThrottledInvalidation } from '../../hooks/useEventHooks';\nimport { portalPath, portalsOf } from '../../lib/portal-path';\nimport { portalRefreshPlan } from '../../lib/portal-refresh';\nimport { displayRoleTitle } from '../../lib/role-display';\nimport { PortalGrid } from '../../components/portal/PortalGrid';\nimport { PortalCounts } from '../../components/portal/PortalCounts';\n\nfunction EmptyPortal({ text }: { text: string }) {\n return (\n <div className=\"flex-1 flex flex-col items-center justify-center py-10 text-center\">\n <LayoutGrid className=\"w-6 h-6 text-text-quaternary/50 mb-2\" strokeWidth={1} />\n <p className=\"text-xs text-text-quaternary\">{text}</p>\n </div>\n );\n}\n\n/**\n * One of a role's portals: a named matrix of pinned views rendered as one\n * page of live panels. Any signed-in user may open it; every panel reads\n * through the same role-scoped list endpoints as the list page, so each\n * viewer sees what their read scope returns. With no portal key the role's\n * first portal shows. One subscription per queue the cells and tiles name\n * keeps them current, each event refreshing only the queue that moved.\n */\nexport function PortalPage() {\n const { role = '', portal: portalKey } = useParams<{ role: string; portal?: string }>();\n const { data: roleData, isFetched } = useRoleDetails();\n const { resolveUrl } = useLinkVariables();\n\n const detail = roleData?.roles.find((r) => r.role === role);\n const portals = portalsOf(detail);\n const portal = portalKey ? portals.find((p) => p.key === portalKey) : portals[0];\n const rows = portal?.rows ?? null;\n const counts = portal?.counts ?? [];\n const from = portal ? portalPath(role, portal.key) : portalPath(role);\n\n // One subscription per queue the cells and tiles name; an event refreshes\n // only that queue's panels and tiles, through the LIST-tier scheduler.\n const plan = useMemo(() => portalRefreshPlan(rows ? [...rows.flat(), ...counts] : [], resolveUrl), [rows, counts, resolveUrl]);\n const invalidate = useThrottledInvalidation('LIST');\n useEventSubscriptions(plan.patterns, (event) => { invalidate(plan.keysFor(event.type)); });\n\n const cellCount = rows ? rows.reduce((n, r) => n + r.length, 0) : 0;\n const empty = !isFetched ? null\n : portals.length === 0 ? 'This role declares no portal yet. Compose one under Admin, Roles, Portal.'\n : !portal ? 'This role has no portal by that name.'\n : null;\n\n // The panels are the page. The shell pads the content box py-8 pb-16; the\n // portal pulls those back to the side gutter so the frame reads evenly on\n // a screen, and the heading demotes to a quiet caption above the grid.\n return (\n <div className=\"flex-1 min-h-0 flex flex-col -mt-[calc(2rem-var(--lt-space-page-x))] -mb-[calc(4rem-var(--lt-space-page-x))]\">\n <div className=\"flex items-center gap-2 mb-3 shrink-0 text-2xs uppercase tracking-widest text-text-tertiary\">\n <LayoutGrid className=\"w-3 h-3 text-accent/60 shrink-0\" strokeWidth={1.5} />\n <h1 className=\"truncate font-semibold\" title={role}>{displayRoleTitle(detail ?? { role })}</h1>\n {portal && (\n <>\n <span className=\"text-text-quaternary\">·</span>\n <span className=\"truncate\" data-testid=\"portal-title\">{portal.label}</span>\n </>\n )}\n {cellCount > 0 && (\n <span className=\"text-text-quaternary normal-case tracking-normal\">\n {cellCount} {cellCount === 1 ? 'view' : 'views'}\n </span>\n )}\n </div>\n {!isFetched ? null\n : empty ? <EmptyPortal text={empty} />\n : (\n <>\n <PortalCounts counts={counts} resolveUrl={resolveUrl} />\n <PortalGrid rows={rows!} resolveUrl={resolveUrl} from={from} />\n </>\n )}\n </div>\n );\n}\n"],"names":["ESCALATION_TYPE_PREFIX","WIDE_KEYS","portalRefreshPlan","urls","resolveUrl","roles","wide","url","params","parseEscalationListUrl","scoped","singleRoleOf","escalationPattern","byToken","role","sanitizeSubjectToken","eventType","token","PORTAL_PANEL_ROWS","PANEL_STALE_MS","NO_LIST","PANEL_STYLE","PortalPanel","pin","from","navigate","useNavigate","pinParams","useMemo","chosenView","setChosenView","useState","chosenOrder","setChosenOrder","orderBy","list","useEscalationListQuery","view","resolveListView","highlightKeys","openRow","row","more","jsxs","jsx","Pin","formatCountCompact","LIST_VIEWS","Table","LayoutList","key","title","Icon","EscalationSortControl","RefreshCw","Link","ExternalLink","EscalationTimeline","EscalationListView","target","EscalationTableView","PORTAL_STACK_REM","rootFontPx","size","PortalGrid","rows","ref","width","useContainerWidth","stacked","cell","r","c","TILE_STALE_MS","PortalCountTile","count","value","PortalCounts","counts","i","EmptyPortal","text","LayoutGrid","PortalPage","portalKey","useParams","roleData","isFetched","useRoleDetails","useLinkVariables","detail","portals","portalsOf","portal","p","portalPath","plan","invalidate","useThrottledInvalidation","useEventSubscriptions","event","cellCount","n","empty","displayRoleTitle","Fragment"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{a as u,j as e}from"./vendor-query-DvWWuNIB.js";import{_ as se,M as te}from"./index-C_dAJUzh.js";import{r as ae,b as ne,c as le,d as re,e as ie,f as oe,p as ce}from"./CredentialConfirmModal-w0AGmCIe.js";function A({label:n,required:t,hint:s,error:a,htmlFor:r,className:l="",children:d}){const i=r?`${r}-msg`:void 0;return e.jsxs("div",{className:l,children:[n&&e.jsxs("label",{htmlFor:r,className:"label",children:[n,t&&e.jsx("span",{className:"text-status-error ml-0.5",children:"*"})]}),d,a?e.jsx("p",{id:i,className:"text-2xs text-status-error mt-1",role:"alert",children:a}):s?e.jsx("p",{id:i,className:"hint",children:s}):null]})}function I(n,t,s){return s||t?`${n}-msg`:void 0}function f({label:n,hint:t,error:s,required:a,fieldClassName:r,id:l,className:d="",...i}){const x=u.useId(),o=l??x;return e.jsx(A,{label:n,required:a,hint:t,error:s,htmlFor:o,className:r,children:e.jsx("input",{id:o,className:`input ${d}`,"aria-required":a||void 0,"aria-invalid":s?!0:void 0,"aria-describedby":I(o,t,s),...i})})}function p({label:n,hint:t,error:s,required:a,fieldClassName:r,id:l,className:d="",options:i,placeholder:x,...o}){const h=u.useId(),g=l??h;return e.jsx(A,{label:n,required:a,hint:t,error:s,htmlFor:g,className:r,children:e.jsxs("select",{id:g,className:`select ${d}`,"aria-required":a||void 0,"aria-invalid":s?!0:void 0,"aria-describedby":I(g,t,s),...o,children:[x!==void 0&&e.jsx("option",{value:"",children:x}),i.map(v=>e.jsx("option",{value:v.value,children:v.label},v.value))]})})}function de({label:n,hint:t,error:s,required:a,fieldClassName:r,id:l,className:d="",...i}){const x=u.useId(),o=l??x;return e.jsx(A,{label:n,required:a,hint:t,error:s,htmlFor:o,className:r,children:e.jsx("textarea",{id:o,className:`textarea ${d}`,"aria-required":a||void 0,"aria-invalid":s?!0:void 0,"aria-describedby":I(o,t,s),...i})})}function ue({media:n,value:t,onChange:s}){const a=n.find(l=>l.id===t.media),r=a?a.columns*a.rows:1;return e.jsxs("div",{className:"space-y-3",children:[e.jsx(p,{label:"Media",hint:"The label stock or thermal roll in the printer.",value:t.media??"",options:n.map(l=>({value:l.id,label:l.name})),onChange:l=>s({...t,media:l.target.value,startPosition:1})}),e.jsxs("div",{className:"grid gap-3 grid-cols-2",children:[e.jsx(f,{label:"Start at label",hint:r>1?`Skips used labels on the first sheet (1 to ${r}).`:"One label per page on this media.",type:"number",min:1,max:r,disabled:r===1,value:t.startPosition??1,onChange:l=>s({...t,startPosition:Number(l.target.value)})}),e.jsx(f,{label:"Copies",hint:"Of each label, printed together.",type:"number",min:1,max:50,value:t.copies??1,onChange:l=>s({...t,copies:Number(l.target.value)})})]}),(a==null?void 0:a.family)==="sheet"&&e.jsxs("div",{className:"grid gap-3 grid-cols-2",children:[e.jsx(p,{label:"Per page",value:t.onePerPage?"one":"fill",options:[{value:"fill",label:"Fill each sheet"},{value:"one",label:"One label per page"}],onChange:l=>s({...t,onePerPage:l.target.value==="one"})}),e.jsx(p,{label:"Cut marks",hint:"For card stock you trim by hand.",value:t.cutMarks?"yes":"no",options:[{value:"no",label:"None"},{value:"yes",label:"Print cut marks"}],onChange:l=>s({...t,cutMarks:l.target.value==="yes"})})]})]})}const xe=["code_128","qr_code","data_matrix"],K=8;function U(){return typeof window<"u"&&"BarcodeDetector"in window}async function me(n){const t=n.viewBox.baseVal,s=document.createElement("canvas");s.width=Math.round(t.width*K),s.height=Math.round(t.height*K);const a=new Image,r=new XMLSerializer().serializeToString(n);a.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(r)}`,await a.decode();const l=s.getContext("2d");return l.fillStyle="#fff",l.fillRect(0,0,s.width,s.height),l.drawImage(a,0,0,s.width,s.height),s}async function he(n){if(!U())return null;const t=window.BarcodeDetector,s=new t({formats:xe}),a=new Set,r=new Set;for(const d of Array.from(n.querySelectorAll("svg.lt-print-page"))){d.querySelectorAll("[data-code]").forEach(x=>a.add(x.getAttribute("data-code")));const i=await s.detect(await me(d));for(const x of i)r.add(x.rawValue)}const l=[...a].filter(d=>!r.has(d));return{expected:a.size,decoded:a.size-l.length,missing:l}}const T=8;function ge({sheet:n,warnings:t}){const s=u.useMemo(()=>ae(n,{outlines:!0}),[n]),a=u.useRef(null),[r,l]=u.useState(null),[d,i]=u.useState(!1),x=n.pages.reduce((h,g)=>h+g.labels.length,0);async function o(){if(a.current){i(!0);try{l(await he(a.current))}finally{i(!1)}}}return e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-3 text-xs text-text-secondary",children:[e.jsxs("span",{children:[x," labels on ",s.length," ",s.length===1?"page":"pages"," · ",n.media.name]}),U()&&e.jsx("button",{onClick:o,disabled:d,className:"ml-auto text-accent hover:text-accent-hover",children:d?"Checking…":"Check codes on screen"})]}),r&&e.jsxs("p",{role:"status",className:`text-xs ${r.missing.length?"text-status-error":"text-status-success"}`,children:[r.decoded," of ",r.expected," codes read back on screen",r.missing.length>0&&`; not read: ${r.missing.slice(0,3).join(", ")}`]}),t.length>0&&e.jsxs("ul",{"aria-label":"Label warnings",className:"space-y-1 border-l-2 border-l-status-error pl-3",children:[t.slice(0,T).map((h,g)=>e.jsxs("li",{className:"text-xs text-text-secondary",children:[e.jsxs("span",{className:"font-medium text-status-error",children:["Label ",h.index+1]})," · ",h.detail]},g)),t.length>T&&e.jsxs("li",{className:"text-xs text-text-tertiary",children:["and ",t.length-T," more"]})]}),e.jsx("div",{ref:a,className:"grid gap-4 @split:grid-cols-2",children:s.map((h,g)=>e.jsx("div",{"data-page":g+1,className:"bg-white shadow-sm [&>svg]:w-full [&>svg]:h-auto",dangerouslySetInnerHTML:{__html:h}},g))})]})}const V={kind:"range",prefix:"",from:1,to:10,pad:0};function pe({value:n,onChange:t}){return e.jsxs("div",{className:"space-y-3",children:[e.jsx(p,{label:"Targets",hint:"A range numbers targets in sequence; a list can carry a column per text token.",value:n.kind,options:[{value:"range",label:"A numbered range"},{value:"list",label:"A pasted list"}],onChange:s=>t(s.target.value==="list"?{kind:"list",text:""}:V)}),n.kind==="range"?e.jsxs("div",{className:"grid gap-3 grid-cols-2 @form-cols:grid-cols-4",children:[e.jsx(f,{label:"Prefix",value:n.prefix??"",onChange:s=>t({...n,prefix:s.target.value}),placeholder:"A-"}),e.jsx(f,{label:"From",type:"number",min:0,value:n.from,onChange:s=>t({...n,from:Number(s.target.value)})}),e.jsx(f,{label:"To",type:"number",min:0,value:n.to,onChange:s=>t({...n,to:Number(s.target.value)})}),e.jsx(f,{label:"Digits",hint:"Pads with zeros: 2 prints 01.",type:"number",min:0,max:12,value:n.pad??0,onChange:s=>t({...n,pad:Number(s.target.value)})})]}):e.jsx(de,{label:"List",hint:'One target per line. Start with a header such as "target,subtitle" to fill {item.subtitle}.',rows:6,value:n.text,onChange:s=>t({kind:"list",text:s.target.value})})]})}function be({value:n,onChange:t}){const{data:s}=se();return e.jsxs("div",{className:"space-y-3",children:[e.jsx(p,{label:"Badges for",hint:"Users without a badge are left out; up to 200 badges print per job.",value:n.role===void 0?"all":"role",options:[{value:"all",label:"Everyone with a badge"},{value:"role",label:"Everyone in a role"}],onChange:a=>t(a.target.value==="role"?{kind:"users",role:""}:{kind:"users"})}),n.role!==void 0&&e.jsx(p,{label:"Role",value:n.role,placeholder:"Choose…",options:((s==null?void 0:s.roles)??[]).map(a=>({value:a,label:a})),onChange:a=>t({kind:"users",role:a.target.value})})]})}function ye({open:n,onClose:t,title:s,templates:a,source:r,sourceCount:l,identity:d=!1}){var W,H;const{data:i}=ne(),{data:x}=le(),o=re(),h=ie(),[g,v]=u.useState(((W=a[0])==null?void 0:W.name)??""),[F,G]=u.useState(V),[M,J]=u.useState({kind:"users"}),[y,$]=u.useState({}),[k,X]=u.useState(""),[R,P]=u.useState(null),[E,_]=u.useState(null),m=a.find(c=>c.name===g)??null,z=(i==null?void 0:i.media)??[],C=z.find(c=>c.id===y.media),N=(C==null?void 0:C.family)==="thermal",w=r??(d?M:F),B=!(w.kind==="users"&&w.role==="");u.useEffect(()=>{m&&i&&$(c=>({...c,media:m.media??i.defaultMedia,startPosition:1}))},[m==null?void 0:m.name,i==null?void 0:i.defaultMedia]);const L=u.useMemo(()=>({template:g,source:w,layout:y}),[g,w,y]),O=JSON.stringify(L),[Z,Q]=u.useState(null),q=!!o.data&&Z===O,Y=l??((H=o.data)==null?void 0:H.labelCount)??0,S=!!m&&q&&B&&!h.isPending;function j(c,ee=!1){if(_(null),m!=null&&m.sensitive&&!ee){P(c);return}h.mutate({...L,output:c,confirm:m!=null&&m.sensitive?!0:void 0},{onSuccess:b=>{P(null),b.sheet&&ce(b.sheet),_(b.downloaded?`Saved ${b.downloaded}.`:b.receipt?`Sent to ${c.slice(10)} (job ${b.jobId}).`:`Printing job ${b.jobId}.`)}})}const D=((x==null?void 0:x.transports)??[]).filter(c=>c.formats.includes(N?"zpl":"pdf"));return e.jsx(te,{open:n,onClose:t,title:s,maxWidth:"max-w-5xl",closeOnBackdrop:!1,children:e.jsxs("div",{className:"@container space-y-6",children:[e.jsxs("div",{className:"grid gap-6 @split:grid-cols-[22rem_1fr]",children:[e.jsxs("div",{className:"space-y-4",children:[e.jsx(p,{label:"Template",hint:(m==null?void 0:m.description)??"What each label shows.",value:g,placeholder:a.length?void 0:"No templates for this choice",options:a.map(c=>({value:c.name,label:c.title})),onChange:c=>v(c.target.value)}),r?e.jsxs("p",{className:"text-xs text-text-secondary",children:[l??0," selected"]}):d?e.jsx(be,{value:M,onChange:J}):e.jsx(pe,{value:F,onChange:G}),e.jsx(ue,{media:z,value:y,onChange:$}),e.jsx("button",{onClick:()=>o.mutate(L,{onSuccess:()=>Q(O)}),disabled:!m||!B||o.isPending,className:"btn-secondary text-xs",children:o.isPending?"Laying out…":"Preview"}),o.error&&e.jsx("p",{role:"alert",className:"text-xs text-status-error",children:o.error.message})]}),e.jsx("div",{className:"min-w-0",children:o.data?e.jsx(ge,{sheet:o.data.sheet,warnings:o.data.warnings}):e.jsx("p",{className:"text-xs text-text-tertiary",children:"Preview the pages before printing."})})]}),R?e.jsx(oe,{count:Y,identity:d,isPending:h.isPending,error:h.error,onConfirm:()=>j(R,!0),onClose:()=>P(null)}):e.jsxs("div",{className:"flex flex-wrap items-end gap-3 border-t border-surface-border pt-4",children:[!q&&e.jsx("p",{className:"text-xs text-text-tertiary",children:"Preview this choice to print it."}),!N&&e.jsx("button",{onClick:()=>j("sheet"),disabled:!S,className:"btn-primary text-xs",children:"Print"}),!N&&e.jsx("button",{onClick:()=>j("pdf"),disabled:!S,className:"btn-secondary text-xs",children:"Download PDF"}),N&&e.jsx("button",{onClick:()=>j("zpl"),disabled:!S,className:"btn-primary text-xs",children:"Download ZPL"}),D.length>0&&e.jsxs(e.Fragment,{children:[e.jsx(p,{label:"Printer",value:k,placeholder:"Choose…",options:D.map(c=>({value:c.name,label:c.name})),onChange:c=>X(c.target.value)}),e.jsx("button",{onClick:()=>j(`transport:${k}`),disabled:!S||!k,className:"btn-secondary text-xs",children:"Send"})]}),h.error&&e.jsx("p",{role:"alert",className:"text-xs text-status-error",children:h.error.message}),E&&e.jsx("p",{role:"status",className:"text-xs text-status-success ml-auto",children:E})]})]})})}export{ye as P,p as S,f as T};
2
+ //# sourceMappingURL=PrintLabelsPanel-D0HF5x8b.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrintLabelsPanel-D0HF5x8b.js","sources":["../../src/components/common/form/Field.tsx","../../src/components/labels/LabelLayoutFields.tsx","../../src/lib/verify-symbols.ts","../../src/components/labels/LabelSheetPreview.tsx","../../src/components/labels/LabelSourceFields.tsx","../../src/components/labels/BadgeSourceFields.tsx","../../src/components/labels/PrintLabelsPanel.tsx"],"sourcesContent":["import { useId, type ReactNode, type InputHTMLAttributes, type SelectHTMLAttributes, type TextareaHTMLAttributes } from 'react';\n\n/**\n * Canonical form primitives. Every authored page composes its forms from these\n * so labels, fields, hints, and errors look identical everywhere — and so a\n * single override of the `.input` / `.select` / `.textarea` classes (or the\n * theme tokens they read) restyles the whole product at once. Do not hand-roll\n * `<input className=\"border ...\">`; reach for these.\n */\n\ninterface FieldShellProps {\n label?: ReactNode;\n required?: boolean;\n hint?: ReactNode;\n error?: ReactNode;\n htmlFor?: string;\n className?: string;\n children: ReactNode;\n}\n\n/** Label + control + hint/error, wired for accessibility. The message renders\n * at `${htmlFor}-msg` so controls can point aria-describedby at it. */\nexport function Field({ label, required, hint, error, htmlFor, className = '', children }: FieldShellProps) {\n const msgId = htmlFor ? `${htmlFor}-msg` : undefined;\n return (\n <div className={className}>\n {label && (\n <label htmlFor={htmlFor} className=\"label\">\n {label}\n {required && <span className=\"text-status-error ml-0.5\">*</span>}\n </label>\n )}\n {children}\n {error ? (\n <p id={msgId} className=\"text-2xs text-status-error mt-1\" role=\"alert\">{error}</p>\n ) : hint ? (\n <p id={msgId} className=\"hint\">{hint}</p>\n ) : null}\n </div>\n );\n}\n\ntype BaseProps = {\n label?: ReactNode;\n hint?: ReactNode;\n error?: ReactNode;\n required?: boolean;\n /** Extra classes on the wrapping Field, not the control. */\n fieldClassName?: string;\n};\n\nfunction describedBy(id: string, hint: ReactNode, error: ReactNode): string | undefined {\n return error || hint ? `${id}-msg` : undefined;\n}\n\nexport function TextField({\n label, hint, error, required, fieldClassName, id, className = '', ...rest\n}: BaseProps & InputHTMLAttributes<HTMLInputElement>) {\n const auto = useId();\n const fieldId = id ?? auto;\n return (\n <Field label={label} required={required} hint={hint} error={error} htmlFor={fieldId} className={fieldClassName}>\n <input\n id={fieldId}\n className={`input ${className}`}\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={describedBy(fieldId, hint, error)}\n {...rest}\n />\n </Field>\n );\n}\n\ninterface SelectFieldProps extends BaseProps, Omit<SelectHTMLAttributes<HTMLSelectElement>, 'children'> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nexport function SelectField({\n label, hint, error, required, fieldClassName, id, className = '', options, placeholder, ...rest\n}: SelectFieldProps) {\n const auto = useId();\n const fieldId = id ?? auto;\n return (\n <Field label={label} required={required} hint={hint} error={error} htmlFor={fieldId} className={fieldClassName}>\n <select\n id={fieldId}\n className={`select ${className}`}\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={describedBy(fieldId, hint, error)}\n {...rest}\n >\n {placeholder !== undefined && <option value=\"\">{placeholder}</option>}\n {options.map((o) => (\n <option key={o.value} value={o.value}>{o.label}</option>\n ))}\n </select>\n </Field>\n );\n}\n\nexport function TextArea({\n label, hint, error, required, fieldClassName, id, className = '', ...rest\n}: BaseProps & TextareaHTMLAttributes<HTMLTextAreaElement>) {\n const auto = useId();\n const fieldId = id ?? auto;\n return (\n <Field label={label} required={required} hint={hint} error={error} htmlFor={fieldId} className={fieldClassName}>\n <textarea\n id={fieldId}\n className={`textarea ${className}`}\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={describedBy(fieldId, hint, error)}\n {...rest}\n />\n </Field>\n );\n}\n","import { SelectField, TextField } from '../common/form/Field';\nimport type { LabelLayout } from '../../api/labels';\nimport type { LabelMedia } from '../../../../shared/labels/types';\n\n/** Media, start position on a partly used sheet, copies, and page options. */\nexport function LabelLayoutFields({ media, value, onChange }: {\n media: LabelMedia[];\n value: LabelLayout;\n onChange: (next: LabelLayout) => void;\n}) {\n const chosen = media.find((m) => m.id === value.media);\n const slots = chosen ? chosen.columns * chosen.rows : 1;\n return (\n <div className=\"space-y-3\">\n <SelectField\n label=\"Media\"\n hint=\"The label stock or thermal roll in the printer.\"\n value={value.media ?? ''}\n options={media.map((m) => ({ value: m.id, label: m.name }))}\n onChange={(e) => onChange({ ...value, media: e.target.value, startPosition: 1 })}\n />\n <div className=\"grid gap-3 grid-cols-2\">\n <TextField\n label=\"Start at label\"\n hint={slots > 1 ? `Skips used labels on the first sheet (1 to ${slots}).` : 'One label per page on this media.'}\n type=\"number\"\n min={1}\n max={slots}\n disabled={slots === 1}\n value={value.startPosition ?? 1}\n onChange={(e) => onChange({ ...value, startPosition: Number(e.target.value) })}\n />\n <TextField\n label=\"Copies\"\n hint=\"Of each label, printed together.\"\n type=\"number\"\n min={1}\n max={50}\n value={value.copies ?? 1}\n onChange={(e) => onChange({ ...value, copies: Number(e.target.value) })}\n />\n </div>\n {chosen?.family === 'sheet' && (\n <div className=\"grid gap-3 grid-cols-2\">\n <SelectField\n label=\"Per page\"\n value={value.onePerPage ? 'one' : 'fill'}\n options={[{ value: 'fill', label: 'Fill each sheet' }, { value: 'one', label: 'One label per page' }]}\n onChange={(e) => onChange({ ...value, onePerPage: e.target.value === 'one' })}\n />\n <SelectField\n label=\"Cut marks\"\n hint=\"For card stock you trim by hand.\"\n value={value.cutMarks ? 'yes' : 'no'}\n options={[{ value: 'no', label: 'None' }, { value: 'yes', label: 'Print cut marks' }]}\n onChange={(e) => onChange({ ...value, cutMarks: e.target.value === 'yes' })}\n />\n </div>\n )}\n </div>\n );\n}\n","/**\n * On-screen code check: rasterize each preview page and decode it with the\n * browser's BarcodeDetector (Chrome), then compare with the codes each\n * symbol was drawn from. Browsers without the detector skip the check.\n */\n\ninterface DetectedBarcode { rawValue: string }\ninterface BarcodeDetectorLike { detect(source: CanvasImageSource): Promise<DetectedBarcode[]> }\ntype DetectorCtor = new (opts: { formats: string[] }) => BarcodeDetectorLike;\n\nexport interface SymbolCheck {\n expected: number;\n decoded: number;\n missing: string[];\n}\n\nconst FORMATS = ['code_128', 'qr_code', 'data_matrix'];\nconst RASTER_SCALE = 8;\n\nexport function canVerifySymbols(): boolean {\n return typeof window !== 'undefined' && 'BarcodeDetector' in window;\n}\n\nasync function rasterize(svg: SVGSVGElement): Promise<HTMLCanvasElement> {\n const viewBox = svg.viewBox.baseVal;\n const canvas = document.createElement('canvas');\n canvas.width = Math.round(viewBox.width * RASTER_SCALE);\n canvas.height = Math.round(viewBox.height * RASTER_SCALE);\n const image = new Image();\n const markup = new XMLSerializer().serializeToString(svg);\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;\n await image.decode();\n const ctx = canvas.getContext('2d')!;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(image, 0, 0, canvas.width, canvas.height);\n return canvas;\n}\n\n/** Decode every page under `container` and report codes the browser could not read back. */\nexport async function verifySymbols(container: HTMLElement): Promise<SymbolCheck | null> {\n if (!canVerifySymbols()) return null;\n const Detector = (window as unknown as { BarcodeDetector: DetectorCtor }).BarcodeDetector;\n const detector = new Detector({ formats: FORMATS });\n const expected = new Set<string>();\n const decoded = new Set<string>();\n for (const svg of Array.from(container.querySelectorAll<SVGSVGElement>('svg.lt-print-page'))) {\n svg.querySelectorAll('[data-code]').forEach((el) => expected.add(el.getAttribute('data-code')!));\n const found = await detector.detect(await rasterize(svg));\n for (const code of found) decoded.add(code.rawValue);\n }\n const missing = [...expected].filter((code) => !decoded.has(code));\n return { expected: expected.size, decoded: expected.size - missing.length, missing };\n}\n","import { useMemo, useRef, useState } from 'react';\n\nimport { renderSheetSvg } from '../../../../shared/labels/render-svg';\nimport type { LabelSheet, LabelWarning } from '../../../../shared/labels/types';\nimport { canVerifySymbols, verifySymbols, type SymbolCheck } from '../../lib/verify-symbols';\n\nconst WARNINGS_SHOWN = 8;\n\n/**\n * Pages as they will print, label outlines dashed, with every warning that\n * would make a code fail on the floor listed under them. Markup comes from\n * the shared renderer, which escapes all label text.\n */\nexport function LabelSheetPreview({ sheet, warnings }: { sheet: LabelSheet; warnings: LabelWarning[] }) {\n const pages = useMemo(() => renderSheetSvg(sheet, { outlines: true }), [sheet]);\n const pagesRef = useRef<HTMLDivElement>(null);\n const [check, setCheck] = useState<SymbolCheck | null>(null);\n const [checking, setChecking] = useState(false);\n const labelCount = sheet.pages.reduce((n, p) => n + p.labels.length, 0);\n\n async function runCheck() {\n if (!pagesRef.current) return;\n setChecking(true);\n try {\n setCheck(await verifySymbols(pagesRef.current));\n } finally {\n setChecking(false);\n }\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3 text-xs text-text-secondary\">\n <span>{labelCount} labels on {pages.length} {pages.length === 1 ? 'page' : 'pages'} · {sheet.media.name}</span>\n {canVerifySymbols() && (\n <button onClick={runCheck} disabled={checking} className=\"ml-auto text-accent hover:text-accent-hover\">\n {checking ? 'Checking…' : 'Check codes on screen'}\n </button>\n )}\n </div>\n {check && (\n <p role=\"status\" className={`text-xs ${check.missing.length ? 'text-status-error' : 'text-status-success'}`}>\n {check.decoded} of {check.expected} codes read back on screen\n {check.missing.length > 0 && `; not read: ${check.missing.slice(0, 3).join(', ')}`}\n </p>\n )}\n {warnings.length > 0 && (\n <ul aria-label=\"Label warnings\" className=\"space-y-1 border-l-2 border-l-status-error pl-3\">\n {warnings.slice(0, WARNINGS_SHOWN).map((w, i) => (\n <li key={i} className=\"text-xs text-text-secondary\">\n <span className=\"font-medium text-status-error\">Label {w.index + 1}</span> · {w.detail}\n </li>\n ))}\n {warnings.length > WARNINGS_SHOWN && (\n <li className=\"text-xs text-text-tertiary\">and {warnings.length - WARNINGS_SHOWN} more</li>\n )}\n </ul>\n )}\n <div ref={pagesRef} className=\"grid gap-4 @split:grid-cols-2\">\n {pages.map((svg, i) => (\n <div\n key={i}\n data-page={i + 1}\n className=\"bg-white shadow-sm [&>svg]:w-full [&>svg]:h-auto\"\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n ))}\n </div>\n </div>\n );\n}\n","import { SelectField, TextArea, TextField } from '../common/form/Field';\nimport type { LabelSource } from '../../api/labels';\n\nexport type EditableSource = Extract<LabelSource, { kind: 'list' | 'range' }>;\n\nexport const DEFAULT_RANGE: EditableSource = { kind: 'range', prefix: '', from: 1, to: 10, pad: 0 };\n\n/** A pasted list or a numeric range: the targets an admin types. */\nexport function LabelSourceFields({ value, onChange }: { value: EditableSource; onChange: (next: EditableSource) => void }) {\n return (\n <div className=\"space-y-3\">\n <SelectField\n label=\"Targets\"\n hint=\"A range numbers targets in sequence; a list can carry a column per text token.\"\n value={value.kind}\n options={[{ value: 'range', label: 'A numbered range' }, { value: 'list', label: 'A pasted list' }]}\n onChange={(e) => onChange(e.target.value === 'list' ? { kind: 'list', text: '' } : DEFAULT_RANGE)}\n />\n {value.kind === 'range' ? (\n <div className=\"grid gap-3 grid-cols-2 @form-cols:grid-cols-4\">\n <TextField label=\"Prefix\" value={value.prefix ?? ''} onChange={(e) => onChange({ ...value, prefix: e.target.value })} placeholder=\"A-\" />\n <TextField label=\"From\" type=\"number\" min={0} value={value.from} onChange={(e) => onChange({ ...value, from: Number(e.target.value) })} />\n <TextField label=\"To\" type=\"number\" min={0} value={value.to} onChange={(e) => onChange({ ...value, to: Number(e.target.value) })} />\n <TextField label=\"Digits\" hint=\"Pads with zeros: 2 prints 01.\" type=\"number\" min={0} max={12} value={value.pad ?? 0} onChange={(e) => onChange({ ...value, pad: Number(e.target.value) })} />\n </div>\n ) : (\n <TextArea\n label=\"List\"\n hint='One target per line. Start with a header such as \"target,subtitle\" to fill {item.subtitle}.'\n rows={6}\n value={value.text}\n onChange={(e) => onChange({ kind: 'list', text: e.target.value })}\n />\n )}\n </div>\n );\n}\n","import { SelectField } from '../common/form/Field';\nimport { useRoles } from '../../api/roles';\nimport type { LabelSource } from '../../api/labels';\n\nexport type UserSource = Extract<LabelSource, { kind: 'users' }>;\n\n/** Whose badges print when no one is selected: every badge holder, or every holder in one role. */\nexport function BadgeSourceFields({ value, onChange }: { value: UserSource; onChange: (next: UserSource) => void }) {\n const { data } = useRoles();\n return (\n <div className=\"space-y-3\">\n <SelectField\n label=\"Badges for\"\n hint=\"Users without a badge are left out; up to 200 badges print per job.\"\n value={value.role === undefined ? 'all' : 'role'}\n options={[{ value: 'all', label: 'Everyone with a badge' }, { value: 'role', label: 'Everyone in a role' }]}\n onChange={(e) => onChange(e.target.value === 'role' ? { kind: 'users', role: '' } : { kind: 'users' })}\n />\n {value.role !== undefined && (\n <SelectField\n label=\"Role\"\n value={value.role}\n placeholder=\"Choose…\"\n options={(data?.roles ?? []).map((r) => ({ value: r, label: r }))}\n onChange={(e) => onChange({ kind: 'users', role: e.target.value })}\n />\n )}\n </div>\n );\n}\n","import { useEffect, useMemo, useState } from 'react';\n\nimport { Modal } from '../common/modal/Modal';\nimport { SelectField } from '../common/form/Field';\nimport {\n useIssueLabels, useLabelMedia, useLabelTransports, usePreviewLabels,\n type LabelLayout, type LabelOutput, type LabelSource, type LabelTemplate,\n} from '../../api/labels';\nimport { printSheet } from '../../lib/print-sheet';\nimport { CredentialConfirm } from './CredentialConfirmModal';\nimport { LabelLayoutFields } from './LabelLayoutFields';\nimport { LabelSheetPreview } from './LabelSheetPreview';\nimport { DEFAULT_RANGE, LabelSourceFields, type EditableSource } from './LabelSourceFields';\nimport { BadgeSourceFields, type UserSource } from './BadgeSourceFields';\n\ninterface PrintLabelsPanelProps {\n open: boolean;\n onClose: () => void;\n title: string;\n /** The templates this entry point offers. */\n templates: LabelTemplate[];\n /** Targets chosen elsewhere (selected rows or users); without it, a range or list, or for badges, everyone or a role. */\n source?: LabelSource;\n /** How many targets `source` names, for the confirmation. */\n sourceCount?: number;\n /** True when the templates print badges (an identity scheme). */\n identity?: boolean;\n}\n\n/**\n * The one print flow every entry point opens: choose a template, targets and\n * stock, preview the pages with their warnings, then print in the browser,\n * download a PDF or ZPL, or send to a registered printer. A sensitive\n * template (every badge) confirms first; the server records the requester\n * before it composes a single code.\n */\nexport function PrintLabelsPanel({ open, onClose, title, templates, source, sourceCount, identity = false }: PrintLabelsPanelProps) {\n const { data: mediaData } = useLabelMedia();\n const { data: transportData } = useLabelTransports();\n const preview = usePreviewLabels();\n const issue = useIssueLabels();\n\n const [templateName, setTemplateName] = useState(templates[0]?.name ?? '');\n const [typed, setTyped] = useState<EditableSource>(DEFAULT_RANGE);\n const [holders, setHolders] = useState<UserSource>({ kind: 'users' });\n const [layout, setLayout] = useState<LabelLayout>({});\n const [transport, setTransport] = useState('');\n const [pending, setPending] = useState<LabelOutput | null>(null);\n const [done, setDone] = useState<string | null>(null);\n\n const template = templates.find((t) => t.name === templateName) ?? null;\n const media = mediaData?.media ?? [];\n const chosenMedia = media.find((m) => m.id === layout.media);\n const thermal = chosenMedia?.family === 'thermal';\n const jobSource: LabelSource = source ?? (identity ? holders : typed);\n const sourceReady = !(jobSource.kind === 'users' && jobSource.role === '');\n\n useEffect(() => {\n if (template && mediaData) setLayout((l) => ({ ...l, media: template.media ?? mediaData.defaultMedia, startPosition: 1 }));\n }, [template?.name, mediaData?.defaultMedia]);\n\n const request = useMemo(() => ({ template: templateName, source: jobSource, layout }), [templateName, jobSource, layout]);\n const requestKey = JSON.stringify(request);\n const [previewedKey, setPreviewedKey] = useState<string | null>(null);\n // Printing follows a preview of exactly this request, so its warnings are seen first.\n const previewed = !!preview.data && previewedKey === requestKey;\n const count = sourceCount ?? preview.data?.labelCount ?? 0;\n const ready = !!template && previewed && sourceReady && !issue.isPending;\n\n function run(output: LabelOutput, confirm = false) {\n setDone(null);\n if (template?.sensitive && !confirm) {\n setPending(output);\n return;\n }\n issue.mutate({ ...request, output, confirm: template?.sensitive ? true : undefined }, {\n onSuccess: (result) => {\n setPending(null);\n if (result.sheet) printSheet(result.sheet);\n setDone(result.downloaded\n ? `Saved ${result.downloaded}.`\n : result.receipt ? `Sent to ${output.slice('transport:'.length)} (job ${result.jobId}).` : `Printing job ${result.jobId}.`);\n },\n });\n }\n\n const sendable = (transportData?.transports ?? []).filter((t) => t.formats.includes(thermal ? 'zpl' : 'pdf'));\n\n return (\n <Modal open={open} onClose={onClose} title={title} maxWidth=\"max-w-5xl\" closeOnBackdrop={false}>\n <div className=\"@container space-y-6\">\n <div className=\"grid gap-6 @split:grid-cols-[22rem_1fr]\">\n <div className=\"space-y-4\">\n <SelectField\n label=\"Template\"\n hint={template?.description ?? 'What each label shows.'}\n value={templateName}\n placeholder={templates.length ? undefined : 'No templates for this choice'}\n options={templates.map((t) => ({ value: t.name, label: t.title }))}\n onChange={(e) => setTemplateName(e.target.value)}\n />\n {source\n ? <p className=\"text-xs text-text-secondary\">{sourceCount ?? 0} selected</p>\n : identity\n ? <BadgeSourceFields value={holders} onChange={setHolders} />\n : <LabelSourceFields value={typed} onChange={setTyped} />}\n <LabelLayoutFields media={media} value={layout} onChange={setLayout} />\n <button\n onClick={() => preview.mutate(request, { onSuccess: () => setPreviewedKey(requestKey) })}\n disabled={!template || !sourceReady || preview.isPending}\n className=\"btn-secondary text-xs\"\n >\n {preview.isPending ? 'Laying out…' : 'Preview'}\n </button>\n {preview.error && <p role=\"alert\" className=\"text-xs text-status-error\">{(preview.error as Error).message}</p>}\n </div>\n <div className=\"min-w-0\">\n {preview.data\n ? <LabelSheetPreview sheet={preview.data.sheet} warnings={preview.data.warnings} />\n : <p className=\"text-xs text-text-tertiary\">Preview the pages before printing.</p>}\n </div>\n </div>\n\n {pending ? (\n <CredentialConfirm\n count={count}\n identity={identity}\n isPending={issue.isPending}\n error={issue.error as Error | null}\n onConfirm={() => run(pending, true)}\n onClose={() => setPending(null)}\n />\n ) : (\n <div className=\"flex flex-wrap items-end gap-3 border-t border-surface-border pt-4\">\n {!previewed && <p className=\"text-xs text-text-tertiary\">Preview this choice to print it.</p>}\n {!thermal && <button onClick={() => run('sheet')} disabled={!ready} className=\"btn-primary text-xs\">Print</button>}\n {!thermal && <button onClick={() => run('pdf')} disabled={!ready} className=\"btn-secondary text-xs\">Download PDF</button>}\n {thermal && <button onClick={() => run('zpl')} disabled={!ready} className=\"btn-primary text-xs\">Download ZPL</button>}\n {sendable.length > 0 && (\n <>\n <SelectField\n label=\"Printer\"\n value={transport}\n placeholder=\"Choose…\"\n options={sendable.map((t) => ({ value: t.name, label: t.name }))}\n onChange={(e) => setTransport(e.target.value)}\n />\n <button onClick={() => run(`transport:${transport}`)} disabled={!ready || !transport} className=\"btn-secondary text-xs\">\n Send\n </button>\n </>\n )}\n {issue.error && <p role=\"alert\" className=\"text-xs text-status-error\">{(issue.error as Error).message}</p>}\n {done && <p role=\"status\" className=\"text-xs text-status-success ml-auto\">{done}</p>}\n </div>\n )}\n </div>\n </Modal>\n );\n}\n"],"names":["Field","label","required","hint","error","htmlFor","className","children","msgId","jsxs","jsx","describedBy","id","TextField","fieldClassName","rest","auto","useId","fieldId","SelectField","options","placeholder","o","TextArea","LabelLayoutFields","media","value","onChange","chosen","m","slots","e","FORMATS","RASTER_SCALE","canVerifySymbols","rasterize","svg","viewBox","canvas","image","markup","ctx","verifySymbols","container","Detector","detector","expected","decoded","el","found","code","missing","WARNINGS_SHOWN","LabelSheetPreview","sheet","warnings","pages","useMemo","renderSheetSvg","pagesRef","useRef","check","setCheck","useState","checking","setChecking","labelCount","n","p","runCheck","w","i","DEFAULT_RANGE","LabelSourceFields","BadgeSourceFields","data","useRoles","r","PrintLabelsPanel","open","onClose","title","templates","source","sourceCount","identity","mediaData","useLabelMedia","transportData","useLabelTransports","preview","usePreviewLabels","issue","useIssueLabels","templateName","setTemplateName","_a","typed","setTyped","holders","setHolders","layout","setLayout","transport","setTransport","pending","setPending","done","setDone","template","t","chosenMedia","thermal","jobSource","sourceReady","useEffect","l","request","requestKey","previewedKey","setPreviewedKey","previewed","count","_b","ready","run","output","confirm","result","printSheet","sendable","Modal","CredentialConfirm","Fragment"],"mappings":"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"}