web-mojo 2.4.7 → 2.4.10

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 (125) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/admin-models.cjs.js +1 -1
  3. package/dist/admin-models.cjs.js.map +1 -1
  4. package/dist/admin-models.es.js +1 -1
  5. package/dist/admin-models.es.js.map +1 -1
  6. package/dist/admin.cjs.js +1 -1
  7. package/dist/admin.cjs.js.map +1 -1
  8. package/dist/admin.es.js +1 -1
  9. package/dist/admin.es.js.map +1 -1
  10. package/dist/auth.cjs.js +1 -1
  11. package/dist/auth.es.js +1 -1
  12. package/dist/charts.cjs.js +1 -1
  13. package/dist/charts.es.js +1 -1
  14. package/dist/chunks/{ChatView-D2WOSxPu.js → ChatView-C_2dRLAY.js} +2 -2
  15. package/dist/chunks/{ChatView-D2WOSxPu.js.map → ChatView-C_2dRLAY.js.map} +1 -1
  16. package/dist/chunks/{ChatView-kWguc444.js → ChatView-DVb2Ufq_.js} +2 -2
  17. package/dist/chunks/{ChatView-kWguc444.js.map → ChatView-DVb2Ufq_.js.map} +1 -1
  18. package/dist/chunks/{Collection-DNmr743A.js → Collection-Bq_EMAqK.js} +2 -2
  19. package/dist/chunks/{Collection-DNmr743A.js.map → Collection-Bq_EMAqK.js.map} +1 -1
  20. package/dist/chunks/{Collection-C0pHSKDH.js → Collection-tgvDhyz_.js} +2 -2
  21. package/dist/chunks/{Collection-C0pHSKDH.js.map → Collection-tgvDhyz_.js.map} +1 -1
  22. package/dist/chunks/{ContextMenu-T3yDdsIe.js → ContextMenu-BL_sdgIw.js} +2 -2
  23. package/dist/chunks/{ContextMenu-T3yDdsIe.js.map → ContextMenu-BL_sdgIw.js.map} +1 -1
  24. package/dist/chunks/{ContextMenu-BeveGkJr.js → ContextMenu-fIy3upEy.js} +2 -2
  25. package/dist/chunks/{ContextMenu-BeveGkJr.js.map → ContextMenu-fIy3upEy.js.map} +1 -1
  26. package/dist/chunks/{DataView-VZIXSsZa.js → DataView-DRQ2yFRz.js} +2 -2
  27. package/dist/chunks/{DataView-VZIXSsZa.js.map → DataView-DRQ2yFRz.js.map} +1 -1
  28. package/dist/chunks/{DataView-z2rxXk4L.js → DataView-DW_ufpQo.js} +2 -2
  29. package/dist/chunks/{DataView-z2rxXk4L.js.map → DataView-DW_ufpQo.js.map} +1 -1
  30. package/dist/chunks/{FormView-COIPtbrd.js → FormView-eWUWw8oL.js} +2 -2
  31. package/dist/chunks/{FormView-COIPtbrd.js.map → FormView-eWUWw8oL.js.map} +1 -1
  32. package/dist/chunks/{FormView-DUXQruUZ.js → FormView-xl_w506t.js} +2 -2
  33. package/dist/chunks/{FormView-DUXQruUZ.js.map → FormView-xl_w506t.js.map} +1 -1
  34. package/dist/chunks/{ListView-BjsNHuZ1.js → ListView-Bj4ONQFG.js} +2 -2
  35. package/dist/chunks/{ListView-BjsNHuZ1.js.map → ListView-Bj4ONQFG.js.map} +1 -1
  36. package/dist/chunks/{ListView-BxcxIwC3.js → ListView-jyIVhCN3.js} +2 -2
  37. package/dist/chunks/{ListView-BxcxIwC3.js.map → ListView-jyIVhCN3.js.map} +1 -1
  38. package/dist/chunks/MetricsCountryMapView-D_666qlW.js +2 -0
  39. package/dist/chunks/MetricsCountryMapView-D_666qlW.js.map +1 -0
  40. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js +2 -0
  41. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js.map +1 -0
  42. package/dist/chunks/Modal-CCVJefEX.js +3 -0
  43. package/dist/chunks/{Modal-Bm1OQ8Ou.js.map → Modal-CCVJefEX.js.map} +1 -1
  44. package/dist/chunks/Modal-Uwt-GBgJ.js +3 -0
  45. package/dist/chunks/{Modal-KnJhNZ1E.js.map → Modal-Uwt-GBgJ.js.map} +1 -1
  46. package/dist/chunks/Passkeys-DScGUF6p.js +2 -0
  47. package/dist/chunks/{Passkeys-BlHx11-5.js.map → Passkeys-DScGUF6p.js.map} +1 -1
  48. package/dist/chunks/{Passkeys-B1-Z4-16.js → Passkeys-c4amXO03.js} +2 -2
  49. package/dist/chunks/{Passkeys-B1-Z4-16.js.map → Passkeys-c4amXO03.js.map} +1 -1
  50. package/dist/chunks/{TokenManager-CiNtJclo.js → TokenManager-BRx2U5w4.js} +2 -2
  51. package/dist/chunks/{TokenManager-CiNtJclo.js.map → TokenManager-BRx2U5w4.js.map} +1 -1
  52. package/dist/chunks/{TokenManager-6atX9uKB.js → TokenManager-DZP2Lcnz.js} +2 -2
  53. package/dist/chunks/{TokenManager-6atX9uKB.js.map → TokenManager-DZP2Lcnz.js.map} +1 -1
  54. package/dist/chunks/{User-9qvKV7G6.js → User-DR-2VJcv.js} +2 -2
  55. package/dist/chunks/{User-9qvKV7G6.js.map → User-DR-2VJcv.js.map} +1 -1
  56. package/dist/chunks/{User-BM76Ughk.js → User-e9aOjdHv.js} +2 -2
  57. package/dist/chunks/{User-BM76Ughk.js.map → User-e9aOjdHv.js.map} +1 -1
  58. package/dist/chunks/{UserProfileView-DDflzpTa.js → UserProfileView-CCNcNT-M.js} +2 -2
  59. package/dist/chunks/{UserProfileView-DDflzpTa.js.map → UserProfileView-CCNcNT-M.js.map} +1 -1
  60. package/dist/chunks/{UserProfileView-cUF8ED9n.js → UserProfileView-PmerV2tC.js} +2 -2
  61. package/dist/chunks/{UserProfileView-cUF8ED9n.js.map → UserProfileView-PmerV2tC.js.map} +1 -1
  62. package/dist/chunks/{View-CPWwS19u.js → View-CLWMWXbO.js} +2 -2
  63. package/dist/chunks/{View-CPWwS19u.js.map → View-CLWMWXbO.js.map} +1 -1
  64. package/dist/chunks/{View-BxlKR1IW.js → View-DADtcBcb.js} +2 -2
  65. package/dist/chunks/{View-BxlKR1IW.js.map → View-DADtcBcb.js.map} +1 -1
  66. package/dist/chunks/{WebApp-BdxhRnnT.js → WebApp-BBrvjnnH.js} +2 -2
  67. package/dist/chunks/{WebApp-BdxhRnnT.js.map → WebApp-BBrvjnnH.js.map} +1 -1
  68. package/dist/chunks/{WebApp-Bo_egO0c.js → WebApp-wMWf0qkm.js} +2 -2
  69. package/dist/chunks/{WebApp-Bo_egO0c.js.map → WebApp-wMWf0qkm.js.map} +1 -1
  70. package/dist/chunks/exportChart-DVnTgKP0.js +2 -0
  71. package/dist/chunks/exportChart-DVnTgKP0.js.map +1 -0
  72. package/dist/chunks/exportChart-DYWi1WFJ.js +2 -0
  73. package/dist/chunks/exportChart-DYWi1WFJ.js.map +1 -0
  74. package/dist/chunks/{index-DsID1QpB.js → index-B312g4f-.js} +2 -2
  75. package/dist/chunks/{index-DsID1QpB.js.map → index-B312g4f-.js.map} +1 -1
  76. package/dist/chunks/{index-Cxffar1o.js → index-D7ITRVln.js} +2 -2
  77. package/dist/chunks/{index-Cxffar1o.js.map → index-D7ITRVln.js.map} +1 -1
  78. package/dist/chunks/version-B1_7ymBC.js +2 -0
  79. package/dist/chunks/version-B1_7ymBC.js.map +1 -0
  80. package/dist/chunks/version-CQGpN0qh.js +2 -0
  81. package/dist/chunks/version-CQGpN0qh.js.map +1 -0
  82. package/dist/docit.cjs.js +1 -1
  83. package/dist/docit.es.js +1 -1
  84. package/dist/index.cjs.js +1 -1
  85. package/dist/index.es.js +1 -1
  86. package/dist/lightbox.cjs.js +1 -1
  87. package/dist/lightbox.es.js +1 -1
  88. package/dist/map.cjs.js +1 -1
  89. package/dist/map.es.js +1 -1
  90. package/dist/timeline.cjs.js +1 -1
  91. package/dist/timeline.es.js +1 -1
  92. package/dist/user-profile.cjs.js +1 -1
  93. package/dist/user-profile.es.js +1 -1
  94. package/package.json +1 -1
  95. package/dist/chunks/AssistantPanelView-DmlaVMvK.js +0 -2
  96. package/dist/chunks/AssistantPanelView-DmlaVMvK.js.map +0 -1
  97. package/dist/chunks/AssistantPanelView-rkVX6wky.js +0 -2
  98. package/dist/chunks/AssistantPanelView-rkVX6wky.js.map +0 -1
  99. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js +0 -2
  100. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js.map +0 -1
  101. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js +0 -2
  102. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js.map +0 -1
  103. package/dist/chunks/Modal-Bm1OQ8Ou.js +0 -3
  104. package/dist/chunks/Modal-KnJhNZ1E.js +0 -3
  105. package/dist/chunks/Passkeys-BlHx11-5.js +0 -2
  106. package/dist/chunks/TicketPanelView-BGQZ6q2B.js +0 -2
  107. package/dist/chunks/TicketPanelView-BGQZ6q2B.js.map +0 -1
  108. package/dist/chunks/TicketPanelView-BZ8e66o1.js +0 -2
  109. package/dist/chunks/TicketPanelView-BZ8e66o1.js.map +0 -1
  110. package/dist/chunks/admin-CmyeTstQ.js +0 -2
  111. package/dist/chunks/admin-CmyeTstQ.js.map +0 -1
  112. package/dist/chunks/admin-D6gHOojO.js +0 -2
  113. package/dist/chunks/admin-D6gHOojO.js.map +0 -1
  114. package/dist/chunks/admin-models-CkHjtMHf.js +0 -2
  115. package/dist/chunks/admin-models-CkHjtMHf.js.map +0 -1
  116. package/dist/chunks/admin-models-DLtFboxy.js +0 -2
  117. package/dist/chunks/admin-models-DLtFboxy.js.map +0 -1
  118. package/dist/chunks/exportChart-BTrEOM9j.js +0 -2
  119. package/dist/chunks/exportChart-BTrEOM9j.js.map +0 -1
  120. package/dist/chunks/exportChart-BfzZUb1j.js +0 -2
  121. package/dist/chunks/exportChart-BfzZUb1j.js.map +0 -1
  122. package/dist/chunks/version-DdqQDDOW.js +0 -2
  123. package/dist/chunks/version-DdqQDDOW.js.map +0 -1
  124. package/dist/chunks/version-NEBZhkhG.js +0 -2
  125. package/dist/chunks/version-NEBZhkhG.js.map +0 -1
@@ -1,2 +0,0 @@
1
- import{C as e,M as t,d as i,r as s}from"./Collection-DNmr743A.js";import{L as n,a as l}from"./ListView-BxcxIwC3.js";import{M as a}from"./View-BxlKR1IW.js";import{M as o}from"./Modal-KnJhNZ1E.js";import{F as r}from"./FormView-COIPtbrd.js";class Log extends t{constructor(e={}){super(e,{endpoint:"/api/logs"})}}class LogList extends e{constructor(e={}){super({ModelClass:Log,endpoint:"/api/logs",size:10,...e})}}class Member extends t{constructor(e={}){super(e,{endpoint:"/api/group/member"})}hasPermission(e){if(Array.isArray(e))return e.some(e=>this.hasPermission(e));const t=this.get("permissions");return!!t&&1==t[e]}async fetchForGroup(e){return await this.fetch({url:`/api/group/${e}/member`})}}class MemberList extends e{constructor(e={}){super({ModelClass:Member,endpoint:"/api/group/member",size:10,...e})}}const c={edit:{title:"Edit Membership",fields:[{name:"user.display_name",type:"text",label:"Display Name",placeholder:"Enter Display Name"},{name:"metadata.role",type:"text",label:"Role",placeholder:"Enter role"},{name:"is_active",type:"switch",label:"Is Enabled",columns:12}]}};Member.BASE_PERMISSIONS=[{name:"manage_group",label:"Group Admin"},{name:"view_metrics",label:"View Metrics"},{name:"view_logs",label:"View Logs"},{name:"view_tickets",label:"View Tickets"},{name:"view_members",label:"View Members"},{name:"manage_members",label:"Manage Members"},{name:"view_billing",label:"View Billing"}],Member.APP_PERMISSIONS=[],Member.PERMISSIONS=[],Member.PERMISSION_FIELDS=[];const d=e=>({name:`permissions.${e.name}`,type:"switch",label:e.label,columns:6,...e.tooltip?{tooltip:e.tooltip}:{}});Member.rebuildPermissions=function(){Member.PERMISSIONS.length=0,Member.PERMISSIONS.push(...Member.BASE_PERMISSIONS,...Member.APP_PERMISSIONS),Member.PERMISSION_FIELDS.length=0,Member.PERMISSION_FIELDS.push(...Member.PERMISSIONS.map(d))},Member.rebuildPermissions(),Member.EDIT_FORM=c.edit,Member.ADD_FORM=c.create;class TableRow extends n{constructor(e={}){super({tagName:"tr",className:"table-row",enableTooltips:!0,...e}),this.columns=e.columns||[],this.actions=e.actions||null,this.contextMenu=e.contextMenu||null,this.batchActions=e.batchActions||null,this.tableView=e.tableView||e.listView||null,this.editingCells=/* @__PURE__ */new Set,this.template=this.buildRowTemplate()}getResponsiveClasses(e){if(!e)return"";const t=["sm","md","lg","xl","xxl"];if("string"==typeof e)return t.includes(e)?`d-none d-${e}-table-cell`:(console.warn(`Invalid visibility breakpoint: ${e}. Valid options are: ${t.join(", ")}`),"");if("object"==typeof e){const i=[];if(e.hide){if(!t.includes(e.hide))return console.warn(`Invalid hide breakpoint: ${e.hide}. Valid options are: ${t.join(", ")}`),"";i.push(`d-table-cell d-${e.hide}-none`)}if(e.show){if(!t.includes(e.show))return console.warn(`Invalid show breakpoint: ${e.show}. Valid options are: ${t.join(", ")}`),"";e.hide?i.push(`d-${e.show}-table-cell`):i.push(`d-none d-${e.show}-table-cell`)}return i.join(" ")}return""}buildRowTemplate(){let e="";return this.tableView&&this.tableView.isSelectable()&&(e+='\n <td style="padding: 0;">\n <div class="mojo-select-cell {{#selected}}selected{{/selected}}"\n data-action="select">\n <div class="mojo-checkbox">\n <i class="bi bi-check"></i>\n </div>\n </div>\n </td>\n '),this.columns.forEach((t,i)=>{const s=[t.class||t.className||"",this.getResponsiveClasses(t.visibility),t.editable?"editable-cell":"",this.tableView&&this.tableView.getAlignClass?this.tableView.getAlignClass(t.align):""].filter(e=>e).join(" "),n=this.buildCellTemplate(t,i);let l=t.action;!l&&t.editable?l="edit-cell":!l&&this.tableView.rowAction&&(l=this.tableView.rowAction),e+=l?`<td class="${s}" data-action="${l}" data-column="${t.key}">${n}</td>`:`<td class="${s}" data-column="${t.key}">${n}</td>`}),this.actions?e+=this.buildActionsTemplate():this.contextMenu&&(e+=this.buildContextMenuTemplate()),e}buildCellTemplate(e,t=0){const i=`model.${e.key}`,s=e.formatter||e.format,n=e.editable?` class="cell-content" data-field="${e.key}"`:"";if(s){if("string"==typeof s)return e.editable?`<span${n}>{{{${i}|${s}}}}</span>`:`{{{${i}|${s}}}}`;if("function"==typeof s){const s=e.editable?"cell-content":"",n=e.editable?` data-field="${e.key}"`:"";return`<span class="${s}" data-formatter="${e.key}" data-formatter-id="${t}"${n}>{{${i}}}</span>`}}return e.template?e.editable?`<span${n}>${e.template}</span>`:e.template:e.editable?`<span${n}>{{{${i}}}}</span>`:`{{{${i}}}}`}buildActionsTemplate(){return this.actions&&0!==this.actions.length?`<td><div class="btn-group btn-group-sm">${this.actions.map(e=>{if("string"==typeof e)switch(e){case"view":return'\n <button class="btn btn-sm btn-outline-primary"\n data-action="view"\n title="View">\n <i class="bi bi-eye"></i>\n </button>\n ';case"edit":return'\n <button class="btn btn-sm btn-outline-secondary"\n data-action="edit"\n title="Edit">\n <i class="bi bi-pencil"></i>\n </button>\n ';case"delete":return'\n <button class="btn btn-sm btn-outline-danger"\n data-action="delete"\n title="Delete">\n <i class="bi bi-trash"></i>\n </button>\n ';default:return""}else if("object"==typeof e)return`\n <button class="btn btn-sm ${e.class||"btn-outline-primary"}"\n data-id="${this.model.id}"\n data-action="${e.action}"\n title="${e.label||""}">\n ${e.icon?`<i class="${e.icon}"></i>`:""}\n ${e.label&&!e.icon?e.label:""}\n </button>\n `;return""}).join("")}</div></td>`:""}buildContextMenuTemplate(){return this.contextMenu&&0!==this.contextMenu.length?`\n <td class="text-end" style="width: 1px;">\n <div class="dropdown">\n <button class="btn btn-sm btn-link border-0"\n type="button"\n data-bs-toggle="dropdown"\n aria-expanded="false"\n style="color: #6c757d;">\n <i class="bi bi-three-dots-vertical"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end shadow-sm">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `:""}buildContextMenuItems(){return this.contextMenu.map(e=>{if(e.separator||e.divider)return'<li><hr class="dropdown-divider"></li>';let t="dropdown-item";return("delete"===e.action||e.danger)&&(t+=" text-danger"),e.disabled&&(t+=" disabled"),`\n <li>\n <a class="${t}" href="#"\n data-id="{{model.id}}"\n data-action="${e.action}"\n ${e.disabled?'aria-disabled="true" tabindex="-1"':""}>\n ${e.icon?`<i class="${e.icon} me-2"></i>`:""}\n ${e.label}\n </a>\n </li>\n `}).join("")}async onAfterRender(){await super.onAfterRender(),this.columns.forEach((e,t)=>{if(e.formatter&&"function"==typeof e.formatter){let s=this.element.querySelector(`[data-formatter-id="${t}"]`);if(s||(s=this.element.querySelector(`[data-formatter="${e.key}"]`)),s){const t=this.model.get?this.model.get(e.key):this.model[e.key],n={value:t,row:this.model,model:this.model,column:e,table:this.tableView,index:this.index};try{s.innerHTML=e.formatter(t,n)}catch(i){console.error(`Error formatting cell for column ${e.key}:`,i)}}}}),this.selected&&this.element.classList.add("selected");const e=this.model.get?this.model.get("id"):this.model.id;e&&this.element.setAttribute("data-id",e)}async onActionEditCell(e,t){e.stopPropagation();const i=t.getAttribute("data-column"),s=this.columns.find(e=>e.key===i);s&&s.editable&&(this.editingCells.has(i)||await this.enterEditMode(i,s,t))}async onActionRowClick(e,t){e.target.closest(".btn-group")||e.target.closest(".dropdown")||e.target.closest(".cell-editor")||(this.emit("row:click",{row:this,model:this.model,column:t.getAttribute("data-column"),event:e}),this.tableView&&this.tableView.emit("row:click",{row:this,model:this.model,column:t.getAttribute("data-column"),event:e}))}async onActionView(e,t){e.stopPropagation(),this.emit("row:view",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:view",{row:this,model:this.model,event:e})}async onActionEdit(e,t){return e.stopPropagation(),this.emit("row:edit",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:edit",{row:this,model:this.model,event:e}),!0}async onActionDelete(e,t){e.stopPropagation(),this.emit("row:delete",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:delete",{row:this,model:this.model,event:e})}async enterEditMode(e,t,i){const s=i.querySelector(".cell-content");if(!s)return;this.editingCells.add(e);const n=this.model.get?this.model.get(e):this.model[e],l=this.createCellEditor(t,n),a=s.innerHTML;s.style.display="none";const o=document.createElement("div");o.className="cell-editor",o.innerHTML=l,i.appendChild(o);const r=o.querySelector("input, select, .form-check-input");r&&(r.focus(),"text"!==r.type&&"textarea"!==r.type||r.select()),o.dataset.originalContent=a,o.dataset.columnKey=e,this.setupEditorEvents(o,e,t),this.emit("cell:edit",{row:this,model:this.model,column:e,originalValue:n})}createCellEditor(e,t){const i=e.editableOptions||{};switch(i.type){case"select":return this.createSelectEditor(i,t);case"switch":case"checkbox":return this.createSwitchEditor(i,t);case"textarea":return this.createTextareaEditor(i,t);default:return this.createTextEditor(i,t)}}createTextEditor(e,t){const i=e.placeholder||"";return`\n <div class="d-flex gap-1 align-items-center">\n <input type="${e.inputType||"text"}"\n class="form-control form-control-sm cell-input"\n value="${this.escapeHtml(t||"")}"\n placeholder="${i}">\n <button type="button" class="btn btn-sm btn-success cell-save" title="Save">\n <i class="bi bi-check"></i>\n </button>\n <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">\n <i class="bi bi-x"></i>\n </button>\n </div>\n `}createTextareaEditor(e,t){const i=e.placeholder||"";return`\n <div class="d-flex gap-1">\n <textarea class="form-control form-control-sm cell-input"\n rows="${e.rows||2}"\n placeholder="${i}">${this.escapeHtml(t||"")}</textarea>\n <div class="d-flex flex-column gap-1">\n <button type="button" class="btn btn-sm btn-success cell-save" title="Save">\n <i class="bi bi-check"></i>\n </button>\n <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">\n <i class="bi bi-x"></i>\n </button>\n </div>\n </div>\n `}createSelectEditor(e,t){const i=e.options||[];let s="";return i.forEach(e=>{if("string"==typeof e)s+=`<option value="${e}" ${e===t?"selected":""}>${e}</option>`;else if("object"==typeof e&&void 0!==e.value){const i=e.value===t?"selected":"";s+=`<option value="${e.value}" ${i}>${e.label||e.value}</option>`}}),`\n <div class="d-flex gap-1 align-items-center">\n <select class="form-select form-select-sm cell-input">\n ${s}\n </select>\n <button type="button" class="btn btn-sm btn-success cell-save" title="Save">\n <i class="bi bi-check"></i>\n </button>\n <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">\n <i class="bi bi-x"></i>\n </button>\n </div>\n `}createSwitchEditor(e,t){const i=t?"checked":"";return`\n <div class="d-flex gap-2 align-items-center">\n <div class="form-check ${"switch"===e.type?"form-switch":""}">\n <input class="form-check-input cell-input" type="checkbox" ${i}>\n </div>\n <div class="d-flex gap-1">\n <button type="button" class="btn btn-sm btn-success cell-save" title="Save">\n <i class="bi bi-check"></i>\n </button>\n <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">\n <i class="bi bi-x"></i>\n </button>\n </div>\n </div>\n `}setupEditorEvents(e,t,i){const s=e.querySelector(".cell-input"),n=e.querySelector(".cell-save"),l=e.querySelector(".cell-cancel");!s||"text"!==s.type&&"email"!==s.type&&"number"!==s.type||s.addEventListener("keydown",s=>{"Enter"===s.key?(s.preventDefault(),this.saveCellEdit(e,t,i)):"Escape"===s.key&&(s.preventDefault(),this.cancelCellEdit(e,t))}),!s||"checkbox"!==s.type&&"SELECT"!==s.tagName||!1===i.autoSave||s.addEventListener("change",()=>{this.saveCellEdit(e,t,i)}),n?.addEventListener("click",()=>{this.saveCellEdit(e,t,i)}),l?.addEventListener("click",()=>{this.cancelCellEdit(e,t)})}async saveCellEdit(e,t,i){const s=e.querySelector(".cell-input");if(!s)return;let n;n="checkbox"===s.type?s.checked:(s.tagName,s.value);const l=this.model.get?this.model.get(t):this.model[t];try{this.model.save?await this.model.save({[t]:n}):this.model[t]=n,this.exitEditMode(e,t,n),this.emit("cell:save",{row:this,model:this.model,column:t,oldValue:l,newValue:n})}catch(a){console.error("Failed to save cell edit:",a),this.emit("cell:save:error",{row:this,model:this.model,column:t,oldValue:l,newValue:n,error:a}),e.classList.add("saving-error"),setTimeout(()=>e.classList.remove("saving-error"),3e3)}}cancelCellEdit(e,t){const i=e.dataset.originalContent;this.exitEditMode(e,t,null,i),this.emit("cell:cancel",{row:this,model:this.model,column:t})}exitEditMode(e,t,s=null,n=null){const l=e.closest("td").querySelector(".cell-content");if(l){if(null!==s){const e=this.columns.find(e=>e.key===t);let n=s;e&&e.formatter&&"string"==typeof e.formatter&&(n=i.pipe(s,e.formatter)),l.innerHTML=this.escapeHtml(n)}else n&&(l.innerHTML=n);l.style.display=""}e.remove(),this.editingCells.delete(t)}escapeHtml(e){if(null==e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}select(){super.select(),this.addClass("selected");const e=this.element?.querySelector(".mojo-select-cell");e&&e.classList.add("selected")}deselect(){super.deselect(),this.removeClass("selected");const e=this.element?.querySelector(".mojo-select-cell");e&&e.classList.remove("selected")}}const h={exact:{display:"is",description:"Exact match"},in:{display:"in",description:"Match any of the values (comma-separated)"},not:{display:"is not",description:"Does not match"},not_in:{display:"not in",description:"Does not match any of the values"},gt:{display:">",description:"Greater than"},gte:{display:">=",description:"Greater than or equal to"},lt:{display:"<",description:"Less than"},lte:{display:"<=",description:"Less than or equal to"},contains:{display:"contains",description:"Contains substring (case-sensitive)"},icontains:{display:"contains",description:"Contains substring (case-insensitive)"},startswith:{display:"starts with",description:"Starts with substring (case-sensitive)"},istartswith:{display:"starts with",description:"Starts with substring (case-insensitive)"},endswith:{display:"ends with",description:"Ends with substring (case-sensitive)"},iendswith:{display:"ends with",description:"Ends with substring (case-insensitive)"},isnull:{display:e=>"true"===e||!0===e?"is null":"is not null",description:"Check if value is null or not"},range:{display:"between",description:"Between two values (comma-separated)"}};function u(e){if(!e||"string"!=typeof e)return{field:e,lookup:null};const t=e.split("__");if(1===t.length)return{field:e,lookup:null};const i=t[t.length-1];return h[i]?{field:t.slice(0,-1).join("__"),lookup:i}:{field:e,lookup:null}}function m(e,t,i){if(!e||null==t)return"";const{field:s,lookup:n}=u(e),l=h[n];if(t&&"object"==typeof t&&!Array.isArray(t)){const e=void 0!==t.start&&null!==t.start&&""!==t.start,s=void 0!==t.end&&null!==t.end&&""!==t.end;return e||s?e&&s?`${i} between '${t.start}' and '${t.end}'`:e?`${i} from '${t.start}'`:`${i} until '${t.end}'`:`${i} is '${JSON.stringify(t)}'`}const a=Array.isArray(t)?t.join(","):String(t);if(!n||"exact"===n)return`${i} is '${a}'`;if("in"===n||"not_in"===n){const e=a.split(",").map(e=>e.trim()).filter(e=>e);if(0===e.length)return`${i} ${l.display}`;const t=e.map(e=>`'${e}'`).join(", ");return`${i} ${l.display} ${t}`}if("range"===n){const e=a.split(",").map(e=>e.trim()).filter(e=>e);return 2===e.length?`${i} between '${e[0]}' and '${e[1]}'`:`${i} ${l.display} '${a}'`}return"isnull"===n?`${i} ${"function"==typeof l.display?l.display(a):l.display}`:l?`${i} ${l.display} '${a}'`:`${i} is '${a}'`}const p={LOOKUPS:h,parseFilterKey:u,formatFilterDisplay:m,getLookupDescription:function(e){const t=h[e];return t?t.description:"Exact match"},isValidLookup:function(e){return e&&h.hasOwnProperty(e)},getAvailableLookups:function(){return Object.keys(h)},buildFilterKey:function(e,t=null){return e?t?`${e}__${t}`:e:""}};class TableView extends l{constructor(e={}){super({className:"table-view-component",itemClass:e.itemClass||TableRow,selectionMode:e.selectable?"multiple":"none",emptyMessage:e.emptyMessage||"No data available",addButtonIcon:e.addButtonIcon||"bi bi-plus-circle",...e}),this.isFullscreen=!1,this.columns=e.columns||[],this.actions=e.actions||null,this.contextMenu=e.contextMenu||null,this.batchActions=e.batchActions||null,this.searchable=!1!==e.searchable,this.sortable=!1!==e.sortable,this.filterable=!1!==e.filterable,this.paginated=!1!==e.paginated,this.clickAction=e.clickAction||"view",this.fetchOnView=!1!==e.fetchOnView,this.itemView=e.itemView,this.addForm=e.addForm,this.editForm=e.editForm,this.deleteTemplate=e.deleteTemplate,this.formDialogConfig=e.formDialogConfig||{},this.viewDialogOptions=e.viewDialogOptions||{},this.exportOptions=e.exportOptions||null,this.options.showExport&&!this.exportOptions&&(this.exportOptions=[{format:"csv",label:"Export as CSV",icon:"bi bi-file-earmark-spreadsheet"},{format:"json",label:"Export as JSON",icon:"bi bi-file-earmark-code"}]),this.exportSource=e.exportSource||"remote",this.filters={},this.additionalFilters=e.filters||[],this.hideActivePills=!0===e.hideActivePills,this.hideActivePillNames=e.hideActivePillNames||[],this.rowAction=e.rowAction||"row-click",this.batchBarLocation=e.batchBarLocation||"bottom",this.options.addButtonLabel=e.addButtonLabel||"Add",this.toolbarButtons=e.toolbarButtons||[],this.tableOptions={striped:!0,bordered:!1,hover:!0,responsive:!1,size:null,...e.tableOptions},this.searchPlacement=e.searchPlacement||"toolbar",this.searchPlaceholder=e.searchPlaceholder||"Search...",this.initializeColumns(),this.extractColumnFilters(),this.footerTotalColumns=this.columns.filter(e=>!0===e.footer_total),this.hasFooterTotals=this.footerTotalColumns.length>0,this.template=this.buildTableTemplate(),this.setupCollectionListeners()}setupCollectionListeners(){this.hasFooterTotals&&this.collection&&this.collection.on("reset add remove change",()=>{this.updateFooterTotals()})}initializeColumns(){this.columns.forEach(e=>{!e.key&&e.name&&(e.key=e.name),e.label||e.title||(e.label=e.key.charAt(0).toUpperCase()+e.key.slice(1))})}getResponsiveClasses(e){if(!e)return"";const t=["sm","md","lg","xl","xxl"];if("string"==typeof e)return t.includes(e)?`d-none d-${e}-table-cell`:(console.warn(`Invalid visibility breakpoint: ${e}. Valid options are: ${t.join(", ")}`),"");if("object"==typeof e){const i=[];if(e.hide){if(!t.includes(e.hide))return console.warn(`Invalid hide breakpoint: ${e.hide}. Valid options are: ${t.join(", ")}`),"";i.push(`d-table-cell d-${e.hide}-none`)}if(e.show){if(!t.includes(e.show))return console.warn(`Invalid show breakpoint: ${e.show}. Valid options are: ${t.join(", ")}`),"";e.hide?i.push(`d-${e.show}-table-cell`):i.push(`d-none d-${e.show}-table-cell`)}return i.join(" ")}return""}getAlignClass(e){if(!e)return"";return{left:"text-start",start:"text-start",center:"text-center",right:"text-end",end:"text-end"}[String(e).toLowerCase()]||(console.warn(`Invalid column align: ${e}. Valid options are: left, center, right`),"")}parseColumnKey(e){const t=e.split("|");return{fieldKey:t[0],formatter:t[1]||null}}updateFooterTotals(){if(!this.hasFooterTotals||!this.element)return;const e=this.calculateFooterTotals();let t=0;this.columns.forEach(i=>{if(i.footer_total){const s=`col_${t}`,n=this.element.querySelector(`[data-total-column="${s}"]`);if(n&&e[s]){const t=this.parseColumnKey(i.key).formatter||i.formatter;let l;l=t&&"string"==typeof t?this.formatValue(e[s].value,t):e[s].value,n.textContent=l}t++}})}formatValue(e,t){try{return i.pipe(e,t)}catch(s){return console.warn("Error formatting value:",s),e}}calculateFooterTotals(){if(!this.hasFooterTotals||!this.collection||0===this.collection.length)return{};const e={};return this.footerTotalColumns.forEach((t,i)=>{const{fieldKey:s,formatter:n}=this.parseColumnKey(t.key);let l=0;this.collection.forEach(e=>{const t=e.get?e.get(s):e[s],i=parseFloat(t)||0;l+=i}),t.key,this.collection.length,e[`col_${i}`]={value:l,formatter:n||t.formatter,fieldKey:s,originalKey:t.key}}),e}extractColumnFilters(){this.filters={},this.columns.forEach(e=>{if(e.filter){const{fieldKey:t}=this.parseColumnKey(e.key);this.filters[t]=e.filter}})}isSelectable(){return this.batchActions&&this.batchActions.length>0&&"multiple"==this.selectionMode}buildTableTemplate(){const e="top"===this.batchBarLocation?this.buildBatchActionsPanel():"",t="bottom"===this.batchBarLocation?this.buildBatchActionsPanel():"";return`\n <div class="mojo-table-wrapper">\n ${this.buildToolbarTemplate()}\n ${e}\n <div class="table-container"${(()=>{const e=this.tableOptions&&null!=this.tableOptions.fontSize?this.tableOptions.fontSize:this.options&&this.options.fontSize,t="sm"===e?"0.9rem":"xs"===e?"0.8rem":e?String(e):null;return t?` style="font-size: ${t};"`:""})()}>\n {{#loading}}\n <div class="mojo-table-loading d-flex justify-content-center align-items-center py-5">\n <div class="spinner-border" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class="table-empty text-center py-5">\n <i class="bi bi-inbox fa-2x mb-2 text-muted"></i>\n <p class="text-muted">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class="${this.buildTableClasses()}">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container="items"></tbody>\n ${this.hasFooterTotals?this.buildTableFooterTemplate():""}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${t}\n ${this.buildPaginationTemplate()}\n </div>\n `}buildTableClasses(){let e=["table"];return this.tableOptions.striped&&e.push("table-striped"),this.tableOptions.bordered&&e.push("table-bordered"),this.tableOptions.hover&&e.push("table-hover"),this.tableOptions.responsive&&e.push("table-responsive"),this.tableOptions.background&&e.push(`table-${this.tableOptions.background}`),"sm"===this.tableOptions.size&&e.push("table-sm"),"lg"===this.tableOptions.size&&e.push("table-lg"),e.join(" ")}buildToolbarTemplate(){return this.searchable||this.filterable?`\n <div class="table-action-buttons mb-3">\n <div class="d-flex align-items-center gap-2">\n ${this.buildActionButtonsTemplate()}\n ${this.filterable?this.buildFilterDropdownTemplate():""}\n ${this.searchable&&"toolbar"===this.searchPlacement?this.buildSearchTemplate():""}\n\n </div>\n <div data-container="filter-pills"></div>\n </div>\n `:""}buildActionButtonsTemplate(){let e=[];if(e.push('\n <button class="btn btn-sm btn-outline-secondary btn-refresh"\n data-action="refresh"\n title="Refresh">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n '),this.isFullscreenSupported()&&e.push('\n <button class="btn btn-sm btn-outline-secondary btn-fullscreen"\n data-action="toggle-fullscreen"\n title="Toggle Fullscreen">\n <i class="bi bi-fullscreen"></i>\n </button>\n '),this.options.showAdd&&e.push(`\n <button class="btn btn-sm btn-success btn-add"\n data-action="add"\n title="${this.options.addButtonLabel}">\n <i class="${this.options.addButtonIcon} me-1"></i>\n <span class="d-none d-lg-inline">${this.options.addButtonLabel}</span>\n </button>\n `),this.options.showExport)if(this.exportOptions&&this.exportOptions.length>1){const t=this.exportOptions.map(e=>`\n <li>\n <a class="dropdown-item" href="#" data-action="export" data-format="${e.format}">\n <i class="${e.icon||"bi bi-file-earmark-arrow-down"} me-2"></i>${e.label}\n </a>\n </li>\n `).join("");e.push(`\n <div class="dropdown">\n <button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"\n data-bs-toggle="dropdown" aria-expanded="false" title="Export">\n <i class="bi bi-download me-1"></i>\n <span class="d-none d-lg-inline">Export</span>\n </button>\n <ul class="dropdown-menu">\n ${t}\n </ul>\n </div>\n `)}else{const t=this.exportOptions&&1===this.exportOptions.length?this.exportOptions[0].format:"json";e.push(`\n <button class="btn btn-sm btn-outline-secondary btn-export"\n data-action="export"\n data-format="${t}"\n title="Export">\n <i class="bi bi-download me-1"></i>\n <span class="d-none d-lg-inline">Export</span>\n </button>\n `)}return this.toolbarButtons&&this.toolbarButtons.length>0&&this.toolbarButtons.forEach((t,i)=>{const{label:s="Button",icon:n="",action:l="",handler:a=null,variant:o="outline-secondary",title:r=s,className:c="",permissions:d=null}=t;if(d&&!this.checkPermissions(d))return;const h=n?`<i class="${n} me-1"></i>`:"",u=`<span class="d-none d-lg-inline">${s}</span>`;let m="";a?m=`data-action="custom-toolbar-button" data-button-index="${i}"`:l&&(m=`data-action="${l}"`);const p=`btn btn-sm btn-${o} ${c}`.trim();e.push(`\n <button class="${p}"\n ${m}\n title="${r}">\n ${h}${u}\n </button>\n `)}),e.join("")}buildSearchTemplate(){return'\n <div class="flex-grow-1" style="max-width: 400px;">\n <div class="input-group input-group-sm">\n <span class="input-group-text">\n <i class="bi bi-search"></i>\n </span>\n <input type="search"\n class="form-control"\n placeholder="{{searchPlaceholder}}"\n data-filter="search"\n data-change-action="apply-search"\n value="{{collection.params.search}}"\n aria-label="Search">\n {{#searchValue}}\n <button class="btn btn-outline-secondary" type="button"\n data-action="clear-search"\n title="Clear search">\n <i class="bi bi-x"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n '}buildFilterDropdownTemplate(){return this.filters&&Object.keys(this.filters).length>0||this.additionalFilters&&this.additionalFilters.length>0?`\n <div class="dropdown">\n <button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"\n data-bs-toggle="dropdown" aria-expanded="false">\n <i class="bi bi-filter me-1"></i>\n <span class="d-none d-lg-inline">Add Filter</span>\n </button>\n <div class="dropdown-menu" style="min-width: 250px;">\n ${this.buildFilterList()}\n </div>\n </div>\n `:""}buildFilterList(){const e=this.getAllAvailableFilters(),t=this.getActiveFilters();return 0===e.length?'<div class="dropdown-item-text text-muted">No filters available</div>':`\n ${e.map(e=>{const i=t.hasOwnProperty(e.key),s=i?"active":"",n=this.getFilterIcon(e.type||e.config?.type);return`\n <button class="dropdown-item ${s}"\n data-action="add-filter"\n data-filter-key="${e.key}">\n <i class="bi bi-${n} me-2"></i>\n ${e.label}\n ${i?'<i class="bi bi-check-circle ms-auto"></i>':""}\n </button>\n `}).join("")}\n ${Object.keys(t).length>0?'\n <div class="dropdown-divider"></div>\n <button class="dropdown-item text-danger" data-action="clear-all-filters">\n <i class="bi bi-x-circle me-2"></i>Clear All Filters\n </button>\n ':""}\n `}updateFilterPills(){const e=this.element?.querySelector('[data-container="filter-pills"]');if(!e)return;this.getActiveFilters();const t=this.buildActivePills();e.innerHTML=t}updateSearchInputs(e){const t=this.element?.querySelectorAll('[data-filter="search"]');t&&t.forEach(t=>{t.value=e||""})}buildActivePills(){if(this.hideActivePills)return"";const e=this.getActiveFilters(),t=e.search&&""!==e.search.toString().trim();let i=Object.entries(e).filter(([e,t])=>t&&""!==t.toString().trim()&&"search"!==e);return this.hideActivePillNames&&this.hideActivePillNames.length>0&&(i=i.filter(([e])=>!this.hideActivePillNames.includes(e))),0!==i.length||t?`\n <div class="row mt-2">\n <div class="col-12">\n <div class="d-flex flex-wrap align-items-center">\n ${i.map(([e,t])=>{const{field:i}=u(e);return`\n <span class="badge bg-primary me-1 mb-1 py-1 px-2 position-relative" style="font-size: 0.75rem;">\n <i class="bi bi-filter me-1" style="font-size: 0.65rem;"></i>\n\n <button type="button" class="btn btn-link text-white p-0 ms-1"\n style="font-size: 0.65rem; line-height: 1;"\n data-action="edit-filter"\n data-filter="${e}"\n title="Edit filter">\n ${m(e,t,this.getFilterLabel(i))}\n </button>\n\n <button type="button" class="btn-close btn-close-white ms-1"\n style="font-size: 0.6rem; width: 0.5rem; height: 0.5rem;"\n data-action="remove-filter"\n data-filter="${e}"\n title="Remove filter">\n </button>\n </span>\n `}).join("")}\n ${i.length>1||i.length>0&&t||0===i.length&&t?'\n <button class="btn btn-sm btn-outline-secondary mb-1 py-0 px-2" style="font-size: 0.75rem;" data-action="clear-all-filters">\n <i class="bi bi-x-circle me-1" style="font-size: 0.7rem;"></i>\n <small>Clear All</small>\n </button>\n ':""}\n </div>\n </div>\n </div>\n `:""}buildTableHeaderTemplate(){let e="";return this.isSelectable()&&(e+='\n <th style="width: 40px; padding: 0;">\n <div class="mojo-select-all-cell" data-action="select-all">\n <div class="mojo-checkbox">\n <i class="bi bi-check"></i>\n </div>\n </div>\n </th>\n '),this.columns.forEach(t=>{const{fieldKey:i}=this.parseColumnKey(t.key),s=this.sortable&&!1!==t.sortable,n=this.getSortBy()===i?this.getSortDirection():null,l=this.getSortIcon(n),a=t.label||t.title||i,o=this.getResponsiveClasses(t.visibility),r=s?`\n <div class="dropdown d-inline-block ms-2">\n <button class="btn btn-sm btn-link p-0 text-decoration-none" type="button"\n data-bs-toggle="dropdown" aria-expanded="false"\n data-column="${i}">\n ${l}\n </button>\n <ul class="dropdown-menu dropdown-menu-end">\n <li><a class="dropdown-item ${"asc"===n?"active":""}"\n data-action="sort" data-field="${i}" data-direction="asc">\n <i class="bi bi-sort-alpha-down me-2"></i>Sort A-Z\n </a></li>\n <li><a class="dropdown-item ${"desc"===n?"active":""}"\n data-action="sort" data-field="${i}" data-direction="desc">\n <i class="bi bi-sort-alpha-down-alt me-2"></i>Sort Z-A\n </a></li>\n <li><a class="dropdown-item ${null===n?"active":""}"\n data-action="sort" data-field="${i}" data-direction="none">\n <i class="bi bi-x-circle me-2"></i>No Sort\n </a></li>\n </ul>\n </div>\n `:"",c=this.getAlignClass(t.align);e+=`\n <th class="${s?"sortable":""} ${o} ${c}">\n <div class="d-flex align-items-center ${"text-center"===c?"justify-content-center":"text-end"===c?"justify-content-end":""}">\n <span>${a}</span>\n ${r}\n </div>\n </th>\n `}),this.actions?e+="<th>Actions</th>":this.contextMenu&&(e+='<th style="width: 1px;"></th>'),`\n <thead>\n <tr>\n ${e}\n </tr>\n </thead>\n `}buildTableFooterTemplate(){let e="";this.isSelectable()&&(e+="<td></td>");let t=0;return this.columns.forEach((i,s)=>{const n=this.getResponsiveClasses(i.visibility),l=this.getAlignClass(i.align);if(i.footer_total){const s=`col_${t}`,a=this.parseColumnKey(i.key).formatter||i.formatter;let o;o=a&&"string"==typeof a?`{{{footerTotals.${s}.value|${a}}}}`:`{{footerTotals.${s}.value}}`,e+=`<td class="table-footer-total ${n} ${l}" data-total-column="${s}">${o}</td>`,t++}else e+=0===s?`<td class="table-footer-label ${n} ${l}"><strong>Totals</strong></td>`:`<td class="${n} ${l}"></td>`}),(this.actions||this.contextMenu)&&(e+="<td></td>"),`\n <tfoot>\n <tr class="table-totals-row">\n ${e}\n </tr>\n </tfoot>\n `}buildBatchActionsPanel(){if(!this.batchActions||0===this.batchActions.length)return"";if("top"===this.batchBarLocation){let e="";return this.batchActions.forEach(t=>{e+=`\n <button class="btn btn-sm btn-outline-secondary" data-action="batch-${t.action}" title="${t.label}">\n <i class="${t.icon} me-1"></i>\n <span class="d-none d-lg-inline">${t.label}</span>\n </button>\n `}),`\n <div class="batch-actions-panel-top alert alert-info d-none mb-3" role="alert">\n <div class="d-flex justify-content-between align-items-center">\n <div class="d-flex align-items-center">\n <strong class="me-2">\n <span class="batch-select-count">0</span> ${this.options.batchPanelTitle||"items"} selected\n </strong>\n </div>\n <div class="d-flex gap-2 align-items-center">\n ${e}\n <button class="btn btn-sm btn-outline-secondary" data-action="clear-selection" title="Clear Selection">\n <i class="bi bi-x-circle me-1"></i>\n <span class="d-none d-lg-inline">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `}{let e="";return this.batchActions.forEach(t=>{e+=`\n <div class="batch-select-action text-center px-2" data-action="batch-${t.action}">\n <div class="batch-action-icon fs-3">\n <i class="${t.icon}"></i>\n </div>\n <div class="batch-action-title small">${t.label}</div>\n </div>\n `}),`\n <div class="batch-actions-panel rounded-start rounded-end" style="display: none;">\n <div class="batch-select-panel rounded-start rounded-end">\n <div class="row g-0">\n <div class="col-auto">\n <div class="batch-select-count rounded-start">0</div>\n </div>\n <div class="col">\n <div class="ps-2 batch-select-title">${this.options.batchPanelTitle||"Rows"}</div>\n </div>\n <div class="col">\n <div class="batch-select-actions d-flex justify-content-end">\n ${e}\n </div>\n </div>\n <div class="col-auto">\n <div class="batch-select-end rounded-end"></div>\n </div>\n </div>\n </div>\n </div>\n `}}buildPaginationTemplate(){return this.paginated?'\n <div class="table-status-bar mt-3">\n <div class="d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3">\n <div class="d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start">\n <span class="text-muted">\n Showing <span data-value="start">0</span> to <span data-value="end">0</span>\n of <span data-value="total">0</span> entries\n </span>\n <div class="d-flex align-items-center">\n <label class="form-label me-2 mb-0">Show:</label>\n <select class="form-select form-select-sm" style="width: auto;" data-change-action="page-size">\n <option value="5">5</option>\n <option value="10">10</option>\n <option value="25">25</option>\n <option value="50">50</option>\n <option value="100">100</option>\n </select>\n </div>\n </div>\n <nav aria-label="Table pagination">\n <ul class="pagination pagination-sm mb-0 justify-content-center" data-container="pagination">\n \x3c!-- Pagination will be rendered here --\x3e\n </ul>\n </nav>\n </div>\n </div>\n ':""}_createItemView(e,t){const i=new this.itemClass({model:e,index:t,listView:this,tableView:this,template:this.itemTemplate,columns:this.columns,actions:this.actions,contextMenu:this.contextMenu,batchActions:this.batchActions,containerId:"items"});return this.itemViews.set(e.id,i),i.on("item:select",e=>{this._onItemSelect(e),this.updateBatchActionsPanel()}),i.on("item:deselect",e=>{this._onItemDeselect(e),this.updateBatchActionsPanel()}),i.on("row:click",this._onRowClick.bind(this)),i.on("row:view",this._onRowView.bind(this)),i.on("row:edit",this._onRowEdit.bind(this)),i.on("row:delete",this._onRowDelete.bind(this)),i.on("cell:edit",this._onCellEdit.bind(this)),i.on("cell:save",this._onCellSave.bind(this)),i.on("cell:cancel",this._onCellCancel.bind(this)),i}async onMounted(){await super.onMounted();const e=this.getActiveFilters();this.collection&&Object.keys(e).length>0&&this.updateFilterPills(),this.setupSearchClearListener()}setupSearchClearListener(){this.element&&this.element.querySelectorAll('input[type="search"][data-filter="search"]').forEach(e=>{e.addEventListener("input",e=>{""===e.target.value&&this.getActiveFilters().search&&this.onActionClearSearch(e,e.target)})})}_onRowClick(e){if(this.emit("row:click",e),this.options.onRowClick)return this.options.onRowClick(e.model,e.event);"view"===this.clickAction?this._onRowView(e):"edit"===this.clickAction&&this._onRowEdit(e)}getModelClass(e){return this.collection?.ModelClass?this.collection.ModelClass:this.collection?.model?this.collection.model:e?.constructor?e.constructor:null}getModelName(e){const t=this.getModelClass(e);return t&&(t.MODEL_NAME||t.name.replace(/Model$/,""))||"Item"}getItemViewClass(e){if(this.itemView)return this.itemView;const t=this.getModelClass(e);return t?.VIEW_CLASS?t.VIEW_CLASS:null}getAddFormConfig(e){return this.addForm||e?.ADD_FORM||this.editForm||e?.EDIT_FORM}getEditFormConfig(e){return this.editForm||e?.EDIT_FORM||this.addForm||e?.ADD_FORM}getFormDialogConfig(e){return{...e?.FORM_DIALOG_CONFIG,...this.formDialogConfig}}renderTemplateString(e,t){return e?a.render(e,t):""}async _onRowView(e){if(this.emit("row:view",e),this.options.onItemView)return void(await this.options.onItemView(e.model,e.event));if(this.fetchOnView)try{o.loading(),await e.model.fetch()}catch(i){return o.hideLoading(!0),void o.showError(i?.data?.error||i?.message||"Failed to load item details")}finally{o.hideLoading(!0)}const t=this.getItemViewClass(e.model);if(t){const i=new t({model:e.model,collection:this.collection});await o.dialog({header:!1,body:i,size:"lg",centered:!1,...this.getFormDialogConfig(this.getModelClass(e.model)),...this.viewDialogOptions})}else await o.data({title:`View ${this.getModelName(e.model)} #${e.model.id}`,model:e.model})}async _onRowEdit(e){if(this.emit("row:edit",e),this.options.onItemEdit)return void(await this.options.onItemEdit(e.model,e.event));const t=this.getModelClass(e.model);let i=this.getEditFormConfig(t);if(i){i.fields||(i={title:`Edit ${this.getModelName(e.model)}`,fields:i});const s=await o.modelForm({model:e.model,...i,...this.getFormDialogConfig(t)});if(!s)return;if(!s.success||!s?.result?.data.status)return void o.showError(s?.result?.data?.error||s?.result?.message||"An error occurred")}else{const t=await o.dialog({title:`Edit ${this.getModelName(e.model)} #${e.model.id}`,body:new r({model:e.model,fields:this.options.formFields||[]})});if(t){const i=await e.model.save(t);if(!i.data?.status)return void o.showError(i.data.error||"An error occurred");await this.refresh()}}}async _onRowDelete(e){if(this.emit("row:delete",e),this.options.onItemDelete)return void(await this.options.onItemDelete(e.model,e.event));const t=this.getModelClass(e.model),i=this.deleteTemplate||t?.DELETE_TEMPLATE||'Are you sure you want to delete this {{name||"item"}}?',s=this.renderTemplateString(i,e.model);await o.confirm({message:s||"Are you sure you want to delete this item?",title:"Confirm Delete",confirmText:"Delete",confirmClass:"btn-danger"})&&(await e.model.destroy(),this.collection.fetch())}_onCellEdit(e){this.emit("cell:edit",e)}async _onCellSave(e){this.emit("cell:save",e)}_onCellCancel(e){this.emit("cell:cancel",e)}isFullscreenSupported(){return!!(document.fullscreenEnabled||document.mozFullScreenEnabled||document.webkitFullscreenEnabled||document.msFullscreenEnabled)}async onActionToggleFullscreen(e,t){this.isFullscreen?await this.exitFullscreen():await this.enterFullscreen()}async enterFullscreen(){try{this.element.requestFullscreen?await this.element.requestFullscreen():this.element.mozRequestFullScreen?await this.element.mozRequestFullScreen():this.element.webkitRequestFullscreen?await this.element.webkitRequestFullscreen():this.element.msRequestFullscreen&&await this.element.msRequestFullscreen(),this.isFullscreen=!0,this.element.classList.add("table-fullscreen"),this.updateFullscreenButton(),this.setupFullscreenListeners(),this.emit("table:fullscreen:enter")}catch(e){console.warn("Could not enter fullscreen:",e)}}async exitFullscreen(){try{document.exitFullscreen?await document.exitFullscreen():document.mozCancelFullScreen?await document.mozCancelFullScreen():document.webkitExitFullscreen?await document.webkitExitFullscreen():document.msExitFullscreen&&await document.msExitFullscreen(),this.isFullscreen=!1,this.element.classList.remove("table-fullscreen"),this.updateFullscreenButton(),this.emit("table:fullscreen:exit")}catch(e){console.warn("Could not exit fullscreen:",e)}}updateFullscreenButton(){const e=this.element?.querySelector(".btn-fullscreen"),t=e?.querySelector("i");e&&t&&(this.isFullscreen?(t.className="bi bi-fullscreen-exit",e.title="Exit Fullscreen"):(t.className="bi bi-fullscreen",e.title="Enter Fullscreen"))}setupFullscreenListeners(){if(this._fullscreenHandler)return;const e=()=>{!(document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement)&&this.isFullscreen&&(this.isFullscreen=!1,this.element.classList.remove("table-fullscreen"),this.updateFullscreenButton(),this.emit("table:fullscreen:exit"))};document.addEventListener("fullscreenchange",e),document.addEventListener("mozfullscreenchange",e),document.addEventListener("webkitfullscreenchange",e),document.addEventListener("msfullscreenchange",e),this._fullscreenHandler=e}cleanupFullscreenListeners(){this._fullscreenHandler&&(document.removeEventListener("fullscreenchange",this._fullscreenHandler),document.removeEventListener("mozfullscreenchange",this._fullscreenHandler),document.removeEventListener("webkitfullscreenchange",this._fullscreenHandler),document.removeEventListener("msfullscreenchange",this._fullscreenHandler),this._fullscreenHandler=null)}destroy(){this.cleanupFullscreenListeners(),super.destroy()}async onActionRefresh(e,t){await this.refresh()}async onActionAdd(e,t){if(this.options.onAdd)return this.emit("table:add",{event:e}),void(await this.options.onAdd(e));this.emit("table:add",{event:e});const i=this.getModelClass();if(!i)return void console.warn("Cannot determine Model class for add operation");let s=this.getAddFormConfig(i);if(s){const e=new i;s.fields||(s={title:`Add ${this.getModelName()}`,fields:s});const t=await o.form({model:e,...s,...this.getFormDialogConfig(i)});if(t){this.options.addRequiresActiveGroup&&(t.group=this.getApp().activeGroup.id),this.options.addRequiresActiveUser&&(t.user=this.getApp().activeUser.id),this.options.addFormDefaults&&Object.assign(t,this.options.addFormDefaults);const i=await e.save(t);if(!i?.data.status)return void o.showError(i?.data.error||"An error occurred");this.collection&&this.collection.add(e),await this.refresh()}}else{const e=new i,t=await o.dialog({title:`Add ${this.getModelName()}`,body:new r({model:e,fields:this.options.formFields||[]})});if(t){const i=await e.save(t);if(!i?.data.status)return void o.showError(i.data.error||"An error occurred");this.collection&&this.collection.add(e),await this.refresh()}}}async onActionExport(e,t){const i=t.getAttribute("data-format")||"json";this.emit("table:export",{format:i,source:this.exportSource,event:e}),"remote"===this.exportSource?this.collection?await this.collection.download(i):console.warn("TableView: Cannot export from remote without a collection."):this.options.onExport?await this.options.onExport(this.collection?.toJSON()||[],i):console.warn("TableView: onExport handler not implemented for local export.")}async onActionApplySearch(e,t){const i=t.value.trim();this.collection&&(this.setFilter("search",i),this.collection.params.start=0,this.collection.restEnabled?await this.collection.fetch():await this.render()),this.updateFilterPills(),this.emit("table:search",{searchTerm:i,event:e}),this.emit("params-changed")}async onActionClearSearch(e,t){this.setFilter("search",null),this.collection&&(this.collection.params.start=0,this.collection.restEnabled&&await this.collection.fetch()),await this.render(),this.updateFilterPills(),this.emit("table:search",{searchTerm:"",event:e}),this.emit("params-changed")}getSortBy(){const e=this.collection?.params?.sort;return e?e.startsWith("-")?e.slice(1):e:null}getSortDirection(){const e=this.collection?.params?.sort;return e&&e.startsWith("-")?"desc":"asc"}getSortIcon(e){return"asc"===e?'<i class="bi bi-sort-alpha-down text-primary"></i>':"desc"===e?'<i class="bi bi-sort-alpha-down-alt text-primary"></i>':'<i class="bi bi-three-dots-vertical text-muted"></i>'}async onActionSort(e,t){e.preventDefault();const i=t.getAttribute("data-field"),s=t.getAttribute("data-direction");if(this.collection){let e;if(e="none"===s?void 0:"desc"===s?`-${i}`:i,this.collection.setParams({...this.collection.params,sort:e,start:0}),this.collection.restEnabled)await this.collection.fetch();else{if(e){const t=e.startsWith("-"),i=t?e.slice(1):e;this.collection.sort((e,s)=>{const n=e.get(i),l=s.get(i);return n<l?t?1:-1:n>l?t?-1:1:0})}this.render()}}this.updateSortIcons(),this.emit("table:sort",{field:i,event:e}),this.emit("params-changed")}updateSortIcons(){if(!this.element)return;const e=this.getSortBy(),t=this.getSortDirection();this.columns.forEach(i=>{if(this.sortable&&!1!==i.sortable){const{fieldKey:s}=this.parseColumnKey(i.key),n=this.element.querySelector(`[data-bs-toggle="dropdown"][data-column="${s}"]`);if(n){const i=e===s,l=this.getSortIcon(i?t:null);n.innerHTML=l;const a=n.nextElementSibling;if(a){const n=a.querySelector(`[data-field="${s}"][data-direction="asc"]`),l=a.querySelector(`[data-field="${s}"][data-direction="desc"]`),o=a.querySelector(`[data-field="${s}"][data-direction="none"]`);n&&n.classList.toggle("active",i&&"asc"===t),l&&l.classList.toggle("active",i&&"desc"===t),o&&o.classList.toggle("active",!i||e!==s)}}}})}async onActionSelectAll(e,t){e.stopPropagation();const i=this.itemViews.size>0&&Array.from(this.itemViews.values()).every(e=>e.selected);i?this.clearSelection():this.forEachItem(e=>{e.selected||e.select()});const s=this.element?.querySelector(".mojo-select-all-cell");s&&s.classList.toggle("selected",!i),this.updateBatchActionsPanel()}async onBeforeRender(){this.searchValue=this.getActiveFilters().search||"",this.footerTotals=this.calculateFooterTotals()}async onAfterRender(){if(await super.onAfterRender(),this.hasFooterTotals&&this.updateFooterTotals(),this.paginated&&this.collection){const e=this.collection.meta?.count||this.collection.length(),t=this.collection.params?.start||0,i=this.collection.params?.size||10,s=Math.min(t+i,e),n=this.element.querySelector('[data-value="start"]'),l=this.element.querySelector('[data-value="end"]'),a=this.element.querySelector('[data-value="total"]');n&&(n.textContent=t+1),l&&(l.textContent=s),a&&(a.textContent=e);const o=this.element.querySelector('[data-change-action="page-size"]');o&&(o.value=i),this.renderPagination()}this.updateSortIcons(),this.updateFilterPills(),this.setupSearchClearListener()}renderPagination(){const e=this.element.querySelector('[data-container="pagination"]');if(!e||!this.collection)return;const t=this.collection.meta?.count||this.collection.length(),i=this.collection.params?.size||10,s=this.collection.params?.start||0,n=Math.floor(s/i)+1,l=Math.ceil(t/i);if(l<=1)return void(e.innerHTML="");const a=n>1?n-1:l,o=n<l?n+1:1,r=[];r.push(`\n <li class="page-item">\n <a class="page-link" href="#" data-action="page" data-page="${a}">\n <i class="bi bi-chevron-left"></i>\n </a>\n </li>\n `);const c=/* @__PURE__ */new Set([1,l]);for(let u=n-1;u<=n+1;u++)u>=1&&u<=l&&c.add(u);const d=Array.from(c).sort((e,t)=>e-t);let h=0;for(const u of d)h&&u-h>1&&r.push('\n <li class="page-item disabled"><span class="page-link">…</span></li>\n '),r.push(`\n <li class="page-item ${u===n?"active":""}">\n <a class="page-link" href="#" data-action="page" data-page="${u}">${u}</a>\n </li>\n `),h=u;r.push(`\n <li class="page-item">\n <a class="page-link" href="#" data-action="page" data-page="${o}">\n <i class="bi bi-chevron-right"></i>\n </a>\n </li>\n `),e.innerHTML=r.join("")}async onActionPage(e,t){e.preventDefault();const i=parseInt(t.getAttribute("data-page"),10),s=this.collection.params?.size||10,n=this.collection.meta?.count||this.collection.length(),l=Math.max(1,Math.ceil(n/s));let a=isNaN(i)?1:i;a<1&&(a=l),a>l&&(a=1),this.collection.setParams({...this.collection.params,start:(a-1)*s}),this.collection.restEnabled?await this.collection.fetch():this.render(),this.emit("table:page",{page:a,event:e}),this.emit("params-changed")}async onChangePageSize(e,t){const i=parseInt(t.value);this.collection&&(this.collection.setParams({...this.collection.params,start:0,size:i}),this.collection.restEnabled&&await this.collection.fetch(),this.render()),this.emit("table:pagesize",{size:i,event:e}),this.emit("params-changed")}getActiveFilters(){if(!this.collection?.params)return{};const{start:e,size:t,sort:i,...s}=this.collection.params,n={},l=/* @__PURE__ */new Set;return this.getAllAvailableFilters().forEach(e=>{if("daterange"===e.config.type){const t=e.key,i=e.config.startName||"dr_start",a=e.config.endName||"dr_end",o=e.config.fieldName||"dr_field";s[o]===t&&(s[i]||s[a])&&(n[t]={start:s[i]||"",end:s[a]||""},l.add(i),l.add(a),l.add(o))}}),Object.keys(s).forEach(e=>{l.has(e)||(n[e]=s[e])}),Object.keys(n).forEach(e=>{if(n.hasOwnProperty(e)){const t=`${e}__in`;n.hasOwnProperty(t)&&(delete n[e],n[t]=n[t])}}),n}setFilter(e,t){if(!this.collection)return;const i=this.getFilterConfig(e);if(i&&"daterange"===i.type){const s=i.startName||"dr_start",n=i.endName||"dr_end",l=i.fieldName||"dr_field";delete this.collection.params[s],delete this.collection.params[n],delete this.collection.params[l],t&&"object"==typeof t&&(t.start||t.end)&&(t.start&&(this.collection.params[s]=t.start),t.end&&(this.collection.params[n]=t.end),this.collection.params[l]=e)}else{const{field:i,lookup:s}=u(e);if(delete this.collection.params[e],delete this.collection.params[i],delete this.collection.params[`${i}__in`],!t||Array.isArray(t)&&0===t.length)return;Array.isArray(t)?1===t.length?this.collection.params[i]=t[0]:this.collection.params[`${i}__in`]=t.join(","):this.collection.params[e]=t}}getAllAvailableFilters(){const e=[];return this.columns.forEach(t=>{if(t.filter){const{fieldKey:i}=this.parseColumnKey(t.key);e.push({key:i,label:t.filter.label||t.label||i,type:t.filter.type,config:t.filter})}}),this.additionalFilters&&Array.isArray(this.additionalFilters)&&this.additionalFilters.forEach(t=>{e.push({key:t.name||t.key,label:t.label,type:t.type,config:t})}),e}getFilterConfig(e){const t=this.columns.find(t=>{const{fieldKey:i}=this.parseColumnKey(t.key);return i===e});if(t&&t.filter)return t.filter;if(this.additionalFilters&&Array.isArray(this.additionalFilters)){const t=this.additionalFilters.find(t=>(t.name||t.key)===e);if(t)return t}return null}getFilterLabel(e){if("search"===e)return"Search";const t=this.filters[e];if(t&&t.label)return t.label;const i=this.additionalFilters.find(t=>(t.name||t.key)===e);return i&&i.label?i.label:e.charAt(0).toUpperCase()+e.slice(1)}getFilterDisplayValue(e,t){if("search"===e)return`"${t}"`;const i=this.filters[e]||this.additionalFilters.find(t=>(t.name||t.key)===e);if(i&&"daterange"===i.type&&"object"==typeof t)return`${t.start||""} to ${t.end||""}`;if(i&&"select"===i.type&&i.options){if("object"==typeof i.options[0]){const e=i.options.find(e=>e.value===t);return e?e.label:t}return t}return t}getFilterIcon(e){return{text:"search",select:"funnel",date:"calendar",daterange:"calendar-range",number:"123",boolean:"toggle-on"}[e]||"filter"}async onActionAddFilter(e,t){const i=t.getAttribute("data-filter-key"),s=this.getFilterConfig(i),n=this.getActiveFilters()[i];if(!s)return void console.warn("No filter config found for key:",i);const l=await o.form({title:`${void 0!==n&&""!==n?"Edit":"Add"} ${this.getFilterLabel(i)} Filter`,size:"md",fields:[this.buildFilterDialogField(s,n,i)]});if(l){const e=this.extractFilterValue(s,l);this.setFilter(i,e),await this.applyFilters()}}buildFilterDialogField(e,t,i){const{name:s,value:n,...l}=e,a={...l,name:"filter_value",label:l.label,value:t,placeholder:l.placeholder||l.placeHolder};if("daterange"===e.type){if(a.startName=a.startName||"dr_start",a.endName=a.endName||"dr_end",a.fieldName=a.fieldName||"dr_field",a.format=a.format||"YYYY-MM-DD",a.displayFormat=a.displayFormat||"MMM DD, YYYY",a.separator=a.separator||" to ",a.label=a.label||"Date Range",t&&"object"==typeof t){const e=e=>{if(!e&&0!==e)return"";if(e instanceof Date&&!isNaN(e))return e.toISOString().slice(0,10);const t=String(e).trim();if(!t)return"";if(/^-?\d+$/.test(t)){const e=Number(t),i=t.length<=10?1e3*e:e,s=new Date(i);if(!isNaN(s))return s.toISOString().slice(0,10)}const i=new Date(t);return isNaN(i)?t:i.toISOString().slice(0,10)};a.startDate=e(t.start||t.from||t.begin||""),a.endDate=e(t.end||t.to||t.finish||"")}}else if("multiselect"===e.type){let i=[];t&&(Array.isArray(t)?i=t:"string"==typeof t&&(i=t.split(",").map(e=>e.trim()).filter(e=>e))),a.value=i,a.placeholder||a.placeHolder||(e.placeholder||e.placeHolder?a.placeholder=e.placeholder||e.placeHolder:e.label&&(a.placeholder=`Select ${e.label}...`))}return a}extractFilterValue(e,t){if("daterange"===e.type){const i=e.startName||"dr_start",s=e.endName||"dr_end";return{start:t[i],end:t[s]}}return e.type,t.filter_value}async applyFilters(){if(this.collection&&(this.collection.params.start=0),this.collection?.restEnabled)try{await this.collection.fetch(),await this.render()}catch(e){console.error("Failed to fetch filtered data:",e),await this.render()}else await this.render();this.updateFilterPills(),this.emit("params-changed")}async onActionEditFilter(e,t){const i=t.getAttribute("data-filter"),{field:s}=u(i);let n=this.getFilterConfig(s)||this.getFilterConfig(i);const l=this.getActiveFilters(),a=l[i]||l[s];if(!n)return void console.warn("No filter config found for key:",i,"or field:",s);const r={filter_value:a};if("daterange"===n.type&&a&&"object"==typeof a){const e=n.startName||"dr_start",t=n.endName||"dr_end";r[e]=a.start||"",r[t]=a.end||""}const c=await o.form({title:`Edit ${this.getFilterLabel(s)} Filter`,size:"md",data:r,fields:[this.buildFilterDialogField(n,a,s)]});if(c){const e=this.extractFilterValue(n,c);this.setFilter(i,e),await this.applyFilters()}}async onActionRemoveFilter(e,t){const i=t.getAttribute("data-filter"),{field:s}=u(i);this.setFilter(i,null),"search"===i&&this.updateSearchInputs(""),this.collection.restEnabled&&await this.collection.fetch(),this.render(),this.updateFilterPills(),this.emit("filter:remove",{key:i,field:s}),this.emit("params-changed")}async onActionClearAllFilters(e,t){if(!this.collection)return;const{start:i,size:s,sort:n}=this.collection.params,l={start:i,size:s};n&&(l.sort=n),Array.isArray(this.hideActivePillNames)&&this.hideActivePillNames.length>0&&this.hideActivePillNames.forEach(e=>{void 0!==this.collection.params[e]&&(l[e]=this.collection.params[e]);const t=this.getFilterConfig(e);if(t&&"daterange"===t.type){const e=t.startName||"dr_start",i=t.endName||"dr_end",s=t.fieldName||"dr_field";void 0!==this.collection.params[e]&&(l[e]=this.collection.params[e]),void 0!==this.collection.params[i]&&(l[i]=this.collection.params[i]),void 0!==this.collection.params[s]&&(l[s]=this.collection.params[s])}}),this.collection.params=l,this.updateSearchInputs(""),this.collection.restEnabled&&await this.collection.fetch(),this.render(),this.updateFilterPills(),this.emit("filters:clear"),this.emit("params-changed")}updateBatchActionsPanel(){if(!this.batchActions||0===this.batchActions.length)return;const e=this.getSelectedItems().length;if("top"===this.batchBarLocation){const t=this.element?.querySelector(".batch-actions-panel-top"),i=this.element?.querySelector(".batch-select-count");t&&i&&(i.textContent=e,e>0?t.classList.remove("d-none"):t.classList.add("d-none"))}else{const t=this.element?.querySelector(".batch-actions-panel"),i=this.element?.querySelector(".batch-select-count");t&&i&&(i.textContent=e,t.style.display=e>0?"block":"none")}const t=this.element?.querySelector(".mojo-select-all-cell");if(t){const e=this.itemViews.size>0&&Array.from(this.itemViews.values()).every(e=>e.selected),i=Array.from(this.itemViews.values()).some(e=>e.selected);t.classList.toggle("selected",e),t.classList.toggle("indeterminate",!e&&i);const s=t.querySelector("i");s&&(s.className=!e&&i?"bi bi-dash":"bi bi-check")}}async onActionBatch(e,t){const i=t.getAttribute("data-action").replace("batch-",""),s=this.getSelectedItems();this.emit("batch:action",{action:i,items:s,event:e})}async onActionClearSelection(e,t){this.clearSelection(),this.updateBatchActionsPanel()}async onActionCustomToolbarButton(e,t){const i=parseInt(t.getAttribute("data-button-index"),10),s=this.toolbarButtons[i];s&&"function"==typeof s.handler&&await s.handler.call(this,e,t)}}const b=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,default:TableView},Symbol.toStringTag,{value:"Module"}));function f(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),i=t+"=".repeat((4-t.length%4)%4);return Uint8Array.from(atob(i),e=>e.charCodeAt(0))}function g(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}class Passkey extends t{constructor(e={},t={}){super(e,{endpoint:"/api/account/passkeys",...t})}static suggestName(){const e=navigator.userAgent;let t="Device";/iPad/.test(e)?t="iPad":/iPhone/.test(e)?t="iPhone":/Macintosh|MacIntel/.test(e)?t="Mac":/Android/.test(e)?t="Android":/Windows/.test(e)?t="Windows PC":/Linux/.test(e)&&(t="Linux");let i="";return/Edg\//.test(e)?i="Edge":/Chrome\//.test(e)&&!/Chromium/.test(e)?i="Chrome":/Safari\//.test(e)&&!/Chrome/.test(e)?i="Safari":/Firefox\//.test(e)&&(i="Firefox"),i?`${t} — ${i}`:t}static async register(e){const t=await Passkey.registerBegin();if(!t?.data?.challenge_id||!t?.data?.publicKey)return{success:!1,error:t?.error||"Could not start registration."};const{challenge_id:i,publicKey:s}=t.data;"string"==typeof s.challenge&&(s.challenge=f(s.challenge)),"string"==typeof s.user?.id&&(s.user.id=f(s.user.id)),s.excludeCredentials&&(s.excludeCredentials=s.excludeCredentials.map(e=>({...e,id:"string"==typeof e.id?f(e.id):e.id})));const n=await navigator.credentials.create({publicKey:s});if(!n)return{success:!1,error:"Passkey creation was cancelled."};const l={id:n.id,rawId:g(n.rawId),type:n.type,response:{clientDataJSON:g(n.response.clientDataJSON),attestationObject:g(n.response.attestationObject)}};n.response.getTransports&&(l.transports=n.response.getTransports());const a=await Passkey.registerComplete({challenge_id:i,credential:l,friendly_name:e||"My Passkey"});return a?.data?.id?{success:!0,passkey:a.data}:{success:!1,error:a?.error||"Registration could not be completed."}}static async registerBegin(e={}){try{return await s.POST("/api/account/passkeys/register/begin",{},e.params,{dataOnly:!0})}catch(t){return{success:!1,error:t?.message||"Failed to begin passkey registration"}}}static async registerComplete(e={},t={}){if(!e.challenge_id||!e.credential)return{success:!1,error:"Missing challenge_id or credential data"};try{return await s.POST("/api/account/passkeys/register/complete",e,t.params,{dataOnly:!0})}catch(i){return{success:!1,error:i?.message||"Failed to complete passkey registration"}}}}class PasskeyList extends e{constructor(e={}){super({ModelClass:Passkey,endpoint:"/api/account/passkeys",size:10,...e})}}const y={edit:{fields:[{name:"friendly_name",type:"text",label:"Name",placeholder:"My iPhone",required:!0,columns:12,help:"A friendly name to identify this passkey"},{name:"is_enabled",type:"switch",label:"Enabled",columns:12,help:"Disable to prevent this passkey from being used for authentication"}]}};export{p as D,h as L,MemberList as M,Passkey as P,TableView as T,PasskeyList as a,y as b,TableRow as c,Member as d,Log as e,LogList as f,c as g,m as h,b as i,u as p};
2
- //# sourceMappingURL=Passkeys-BlHx11-5.js.map
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./View-CPWwS19u.js"),t=require("./ChatView-kWguc444.js"),n=require("./ContextMenu-BeveGkJr.js"),i=require("./Modal-Bm1OQ8Ou.js"),a=require("./admin-models-CkHjtMHf.js"),s=require("./User-9qvKV7G6.js"),o=require("./admin-CmyeTstQ.js"),r=require("./Collection-C0pHSKDH.js");function l(e){return e?String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):""}const c=/* @__PURE__ */new Set(["incident.RuleSet","incident.Incident","incident.Event","incident.Ticket","account.GeoLocatedIP"]),d={"incident.rule_approval":{dot:"accent",label:"Rule"},"incident.block_confirm":{dot:"red",label:"Block"},"incident.rule_update":{dot:"green",label:"Update"},"incident.escalate":{dot:"amber",label:"Escalate"}};class ActionCardView extends e.View{constructor(e={}){super({className:"action-card-view",...e}),this.action=e.action,this.noteId=e.noteId,this.ticketStatus=e.ticketStatus}get isResolved(){return!!this.action?.resolved}get isContext(){return"context"===this.action?.type}get isClosed(){return"closed"===this.ticketStatus||"resolved"===this.ticketStatus}get handlerConfig(){return d[this.action?.handler]||{dot:"accent",label:"Action"}}onBeforeRender(){const e=this.handlerConfig;this.dotClass=e.dot,this.isContext?this._buildContextTemplate():this.isResolved?this._buildResolvedTemplate():this._buildPendingTemplate()}onActionToggleCompact(){const e=this.element?.querySelector(".ac.resolved");e&&e.classList.toggle("compact")}_buildContextTemplate(){const e=(this.action.references||[]).filter(e=>c.has(e.model)).map(e=>{const t=l(e.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;return`<span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${t}</span>`}).join("");this.template=`\n <div class="ac ac-context">\n <div class="ac-top">\n <span class="ac-dot context"></span>\n <span class="ac-label">Referenced models</span>\n </div>\n <div class="ac-detail">${e}</div>\n </div>\n `}_buildResolvedTemplate(){const e=this.action.resolution||"approved",t="approved"===e?"approved":"denied",n="approved"===e?"Approved":"Denied",i=this.action.context?.target;let a="";if(i&&c.has(i.model)){const e=l(this.action.context.label)||`${l(i.model.split(".").pop())} #${l(i.pk)}`;a=`<div class="ac-detail"><span class="ac-ref" data-action="open-ref" data-model="${l(i.model)}" data-pk="${l(i.pk)}"><i class="bi bi-box-arrow-up-right"></i>${e}</span></div>`}this.template=`\n <div class="ac resolved compact">\n <div class="ac-top" data-action="toggle-compact" title="Click to toggle">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n <span class="ac-badge ${t}">${n}</span>\n <i class="bi bi-chevron-down ac-chevron"></i>\n </div>\n ${a}\n </div>\n `}_buildPendingTemplate(){const e=this.action.context?.target;let t="";if(e&&c.has(e.model)){const n=l(this.action.context.label)||`${l(e.model.split(".").pop())} #${l(e.pk)}`;t=`<br><span class="ac-ref" data-action="open-ref" data-model="${l(e.model)}" data-pk="${l(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${n}</span>`}const n=l(this.action.context?.detail),i=this.isClosed?" disabled":"";this.template=`\n <div class="ac">\n <div class="ac-top">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${l(this.action.label)||"Action"}</span>\n </div>\n <div class="ac-detail">${n}${t}</div>\n <div class="ac-foot">\n <button class="btn-approve" data-action="approve"${i}>Approve</button>\n <button class="btn-deny" data-action="deny"${i}>Deny</button>\n </div>\n </div>\n `}async onActionOpenRef(e,t){const n=t.dataset.model,a=t.dataset.pk;if(!c.has(n)||!/^\d+$/.test(a))return;const s=this.getApp(),o=s?.getModelByRef(n);o?.VIEW_CLASS&&i.Modal.showModel(new o({id:a}))}onActionApprove(){this.emit("action:respond",{noteId:this.noteId,action:"approve",handler:this.action.handler,context:this.action.context})}onActionDeny(){this.emit("action:respond",{noteId:this.noteId,action:"deny",handler:this.action.handler,context:this.action.context})}}const p=["new","open","in_progress","pending","resolved","qa","closed","ignored"],h={new:"pill-new",open:"pill-open",in_progress:"pill-prog",pending:"pill-prog",resolved:"pill-resolved",qa:"pill-open",closed:"pill-closed",ignored:"pill-closed"},g=[{value:10,label:"P10 — Critical"},{value:9,label:"P9 — Severe"},{value:8,label:"P8 — High"},{value:7,label:"P7 — Elevated"},{value:5,label:"P5 — Normal"},{value:3,label:"P3 — Low"},{value:1,label:"P1 — Info"}];class TicketPanelView extends e.View{constructor(e={}){super({className:"ticket-panel-view",...e}),this.model=e.model||new a.Ticket(e.data||{})}onBeforeRender(){const e=this.model.get("status")||"new";this.statusPill=h[e]||"pill-closed",this.statusLabel=e.replace(/_/g," "),this.priorityLabel=`P${this.model.get("priority")||5}`,this.assigneeName=this.model.get("assignee.display_name")||this.model.get("assignee")||"Unassigned",this.categoryLabel=this.model.get("category")||"ticket",this.groupName=this.model.get("group.name")||this.model.get("group")||"None",this.hasDescription=!!this.model.get("description"),this.hasIncident=!(!this.model.get("incident")||"object"!=typeof this.model.get("incident")||!this.model.get("incident").id),this.priorityColor=(this.model.get("priority")||5)>=7?"var(--bs-danger)":"var(--bs-secondary-color)",this.template=`\n <style>\n .ticket-panel-view { height: 100%; display: flex; flex-direction: column; }\n .tp-header { padding: 10px 16px 6px; border-bottom: 1px solid var(--bs-border-color-translucent); flex-shrink: 0; }\n .tp-title-row { display: flex; align-items: flex-start; gap: 6px; }\n .tp-title { font-size: 0.88rem; font-weight: 600; color: var(--bs-emphasis-color); line-height: 1.3; flex: 1; min-width: 0; cursor: ${this.hasDescription?"pointer":"default"}; transition: color 0.12s; }\n ${this.hasDescription?".tp-title:hover { color: var(--bs-primary); }":""}\n .tp-title i { font-size: 0.6rem; vertical-align: middle; margin-left: 3px; opacity: 0; transition: opacity 0.12s; }\n .tp-title:hover i { opacity: 0.6; }\n .tp-btns { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }\n .tp-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: var(--bs-secondary-color); border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.12s; }\n .tp-btn:hover { background: var(--bs-tertiary-bg); color: var(--bs-body-color); }\n .tp-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }\n .tp-id { font-family: var(--bs-font-monospace); font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; font-weight: 500; cursor: pointer; transition: filter 0.12s; }\n .tp-pill:hover { filter: brightness(0.9); }\n .tp-pill-new { background: rgba(var(--bs-info-rgb), 0.1); color: var(--bs-info); }\n .tp-pill-open { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-pill-prog { background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); }\n .tp-pill-closed { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }\n .tp-pill-resolved { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-time { font-size: 0.66rem; color: var(--bs-secondary-color); }\n .tp-desc-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--bs-primary); cursor: pointer; padding: 2px 8px; border-radius: 5px; background: rgba(var(--bs-primary-rgb), 0.08); transition: all 0.12s; }\n .tp-desc-chip:hover { background: rgba(var(--bs-primary-rgb), 0.16); }\n .tp-desc-chip i { font-size: 0.7rem; }\n .tp-meta { display: flex; align-items: center; gap: 3px; margin-top: 5px; }\n .tp-fields { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; }\n .tp-field { display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--bs-secondary-color); padding: 2px 7px; border-radius: 5px; cursor: pointer; transition: all 0.12s; border: 1px solid transparent; }\n .tp-field:hover { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }\n .tp-field i { font-size: 0.68rem; }\n .tp-field .caret { font-size: 0.55rem; opacity: 0; transition: opacity 0.12s; margin-left: -1px; }\n .tp-field:hover .caret { opacity: 0.6; }\n .tp-sep { color: var(--bs-secondary-color); font-size: 0.6rem; margin: 0 1px; user-select: none; }\n\n .tp-linked { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--bs-border-color-translucent); font-size: 0.75rem; color: var(--bs-secondary-color); flex-shrink: 0; }\n .tp-linked i { color: var(--bs-warning); font-size: 0.72rem; }\n .tp-linked a { color: var(--bs-primary); text-decoration: none; font-weight: 500; }\n .tp-linked a:hover { text-decoration: underline; }\n .tp-linked .lpill { font-size: 0.62rem; padding: 0 5px; border-radius: 3px; background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); font-weight: 500; }\n\n .tp-conv { flex: 1; overflow-y: auto; min-height: 0; }\n .tp-conv .chat-container { border: none; border-radius: 0; }\n .tp-conv .chat-messages { padding: 6px 0; }\n .tp-conv .chat-input-wrapper { display: none; }\n .tp-conv .message-item { position: relative; }\n .tp-conv .tp-edit-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; border: none; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); border-radius: 5px; cursor: pointer; font-size: 0.72rem; transition: all 0.12s; }\n .tp-conv .tp-edit-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }\n .tp-conv .message-item:hover .tp-edit-btn { display: flex; }\n .tp-conv .message-text { white-space: normal; }\n .tp-conv .message-text p { margin-bottom: 6px; }\n .tp-conv .message-text p:last-child { margin-bottom: 0; }\n .tp-conv .message-text h1,\n .tp-conv .message-text h2,\n .tp-conv .message-text h3,\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-weight: 600; margin-top: 10px; margin-bottom: 4px; line-height: 1.3; }\n .tp-conv .message-text h1 { font-size: 1.05rem; }\n .tp-conv .message-text h2 { font-size: 1rem; }\n .tp-conv .message-text h3 { font-size: 0.95rem; }\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-size: 0.88rem; }\n .tp-conv .message-text h1:first-child,\n .tp-conv .message-text h2:first-child,\n .tp-conv .message-text h3:first-child { margin-top: 0; }\n .tp-conv .message-text hr { margin: 4px 0; opacity: 0.15; }\n .tp-conv .message-text ul,\n .tp-conv .message-text ol { padding-left: 20px; margin-top: 2px; margin-bottom: 6px; }\n .tp-conv .message-text li { margin-bottom: 2px; }\n .tp-conv .message-text pre { background: var(--bs-tertiary-bg); border-radius: 6px; padding: 10px 14px; margin: 8px 0; font-size: 0.8rem; overflow-x: auto; }\n .tp-conv .message-text code { font-size: 0.85em; padding: 1px 5px; background: var(--bs-tertiary-bg); border-radius: 4px; }\n .tp-conv .message-text pre code { padding: 0; background: none; }\n .tp-conv .message-text table { width: 100%; margin: 8px 0; border-collapse: collapse; font-size: 0.82rem; }\n .tp-conv .message-text th,\n .tp-conv .message-text td { padding: 5px 8px; border: 1px solid var(--bs-border-color); text-align: left; }\n .tp-conv .message-text th { background: var(--bs-tertiary-bg); font-weight: 600; }\n .tp-conv .message-text blockquote { margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--bs-border-color); color: var(--bs-secondary-color); }\n\n .tp-action-area { padding: 0; }\n\n .tp-input { border-top: 1px solid var(--bs-border-color-translucent); padding: 10px 16px; flex-shrink: 0; }\n .tp-input-wrap { display: flex; align-items: flex-end; gap: 8px; }\n .tp-input textarea { flex: 1; font-size: 0.8rem; border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 7px 10px; resize: none; background: var(--bs-body-bg); color: var(--bs-body-color); outline: none; transition: border-color 0.15s; font-family: inherit; max-height: 160px; overflow-y: auto; }\n .tp-input textarea:focus { border-color: var(--bs-primary); }\n .tp-input textarea::placeholder { color: var(--bs-secondary-color); }\n .tp-send-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: var(--bs-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 0.85rem; transition: filter 0.12s; }\n .tp-send-btn:hover { filter: brightness(1.1); }\n .tp-input-hint { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-input-hint i { font-size: 0.75rem; }\n .tp-input.tp-dragover { background: rgba(var(--bs-primary-rgb), 0.04); }\n .tp-input.tp-dragover textarea { border-color: var(--bs-primary); border-style: dashed; }\n .tp-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0; }\n .tp-attach-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.72rem; color: var(--bs-body-color); }\n .tp-attach-chip i { font-size: 0.68rem; }\n .tp-attach-chip .remove { cursor: pointer; color: var(--bs-secondary-color); margin-left: 2px; }\n .tp-attach-chip .remove:hover { color: var(--bs-danger); }\n .tp-attach-chip.uploading { opacity: 0.6; }\n .tp-conv .message-content.tp-collapsed { max-height: var(--tp-collapse-h, 52px); overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }\n .tp-conv .message-item:has(.tp-show-more) { flex-wrap: wrap; }\n .tp-show-more { background: none; border: none; padding: 2px 0; margin: -2px 0 0 48px; font-size: 0.7rem; color: var(--bs-secondary-color); cursor: pointer; text-align: left; width: calc(100% - 48px); }\n .tp-show-more:hover { color: var(--bs-body-color); }\n </style>\n\n <div class="tp-header">\n <div class="tp-title-row">\n <div class="tp-title" ${this.hasDescription?'data-action="show-description"':""} ${this.hasDescription?'title="View full description"':""}>\n {{model.title}}${this.hasDescription?' <i class="bi bi-arrow-up-right-square"></i>':""}\n </div>\n <div class="tp-btns">\n <div data-container="panel-menu"></div>\n </div>\n </div>\n <div class="tp-sub">\n <span class="tp-id">#{{model.id}}</span>\n <span class="tp-pill tp-${this.statusPill}" data-action="change-status" title="Change status">{{statusLabel}} <i class="bi bi-chevron-down" style="font-size:0.5rem;"></i></span>\n <span class="tp-time"><i class="bi bi-clock"></i> {{model.created|relative}}</span>\n <span class="tp-desc-chip" data-action="show-description" title="${this.hasDescription?"View / edit description":"Add description"}"><i class="bi bi-file-text"></i> ${this.hasDescription?"Description":"Add description"}</span>\n </div>\n <div class="tp-meta">\n <div class="tp-fields">\n <span class="tp-field" data-action="change-priority" title="Change priority">\n <i class="bi bi-flag-fill" style="color:${this.priorityColor};"></i>{{priorityLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-assignee" title="Assign">\n <i class="bi bi-person"></i>{{assigneeName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-category" title="Change category">\n <i class="bi bi-tag"></i>{{categoryLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-group" title="Change group">\n <i class="bi bi-people"></i>{{groupName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n </div>\n </div>\n </div>\n\n {{#hasIncident|bool}}\n <div class="tp-linked">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <a href="#" data-action="view-incident">Incident #{{model.incident.id}}</a>\n {{#model.incident.status}}<span class="lpill">{{model.incident.status}}</span>{{/model.incident.status}}\n {{#model.incident.event_count}}<span>&middot; {{model.incident.event_count}} events</span>{{/model.incident.event_count}}\n </div>\n {{/hasIncident|bool}}\n\n <div class="tp-conv">\n <div data-container="chat-area"></div>\n </div>\n\n <div class="tp-input" data-ref="drop-zone">\n <div class="tp-attachments" data-ref="attachments"></div>\n <div class="tp-input-wrap">\n <textarea rows="2" placeholder="Add a note..." data-ref="note-textarea"></textarea>\n <button class="tp-send-btn" data-action="send-note" title="Send"><i class="bi bi-arrow-up"></i></button>\n </div>\n <div class="tp-input-hint">\n <i class="bi bi-paperclip"></i> Drag &amp; drop files to attach\n </div>\n </div>\n `}async onInit(){this.adapter=new o.TicketNoteAdapter(this.model.get("id")),this.chatView=new t.ChatView({containerId:"chat-area",adapter:this.adapter,theme:"compact",currentUserId:this._getCurrentUserId(),showInput:!1}),this.addChild(this.chatView);const e=new n.ContextMenu({containerId:"panel-menu",className:"context-menu-view header-menu-absolute",context:this.model,config:{icon:"bi-three-dots",btnClass:"tp-btn",items:[{label:"Ask AI",action:"ask-ai",icon:"bi-robot"},{type:"divider"},{label:"Edit Ticket",action:"edit-ticket",icon:"bi-pencil"},{label:"Refresh Notes",action:"refresh-notes",icon:"bi-arrow-clockwise"},{type:"divider"},{label:"Close Window",action:"close",icon:"bi-x-lg"}]}});this.addChild(e)}async onAfterRender(){this._setupTextarea(),this._setupDragDrop(),await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async _loadActionCards(){this._cleanupActionCards();const e=this.chatView.messages||[];if(e.length)for(const t of e){if(!t.action||"object"!=typeof t.action)continue;const e=this.chatView.messageViews.get(t.id);if(!e?.element)continue;const n=new ActionCardView({action:t.action,noteId:t.id,ticketStatus:this.model.get("status")});"context"===t.action.type||t.action.resolved||n.on("action:respond",e=>this._handleActionResponse(e)),this.addChild(n),await n.render(),e.element.after(n.element)}}_cleanupActionCards(){for(const e in this.children){const t=this.children[e];t instanceof ActionCardView&&this.removeChild(t)}}async _handleActionResponse(e){const t={action:{handler:e.handler,context:e.context}},n=this.getApp();n?.showLoading();try{await this.adapter.addActionResponse(t,e.action),await this.model.fetch(),await this.chatView.refresh(),this.render()}finally{n?.hideLoading()}}_getCurrentUserId(){const e=this.getApp();return e?.activeUser?.id||e?.getActiveUser?.()?.id||null}onActionClose(){this.emit("panel:close")}async onActionSendNote(){const e=this.element?.querySelector('[data-ref="note-textarea"]'),t=e?.value?.trim(),n=this._stagedFiles||[];(t||n.length)&&(e.value="",e.style.height="",this._stagedFiles=[],this._renderAttachments(),await this.adapter.addNote({text:t||"",files:n}),await this.chatView.refresh(),await this._afterChatRefresh())}_setupTextarea(){const e=this.element?.querySelector('[data-ref="note-textarea"]');if(!e)return;const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input")),e.scrollTop=e.scrollHeight},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&!s.shiftKey&&!o)return s.preventDefault(),void this.onActionSendNote();if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)}),e.addEventListener("input",()=>{e.style.height="",e.style.height=Math.min(e.scrollHeight,160)+"px"})}_setupDragDrop(){const e=this.element?.querySelector('[data-ref="drop-zone"]');if(!e)return;this._stagedFiles=this._stagedFiles||[];let t=0;e.addEventListener("dragenter",n=>{n.preventDefault(),t++,e.classList.add("tp-dragover")}),e.addEventListener("dragleave",()=>{t--,t<=0&&(t=0,e.classList.remove("tp-dragover"))}),e.addEventListener("dragover",e=>e.preventDefault()),e.addEventListener("drop",async n=>{n.preventDefault(),t=0,e.classList.remove("tp-dragover");const i=Array.from(n.dataTransfer?.files||[]);if(!i.length)return;const{File:a}=await Promise.resolve().then(()=>require("./Modal-Bm1OQ8Ou.js")).then(e=>e.Files);for(const e of i){const t=Date.now()+Math.random();this._addAttachChip(t,e.name,!0);try{const n=new a;await n.upload({file:e,showToast:!1}),this._stagedFiles.push(n),this._updateAttachChip(t,n.get("name")||e.name,n)}catch(s){console.error("File upload failed:",s),this._removeAttachChip(t),this.getApp()?.toast?.error?.("Upload failed: "+e.name)}}})}_addAttachChip(e,t,n){const i=this.element?.querySelector('[data-ref="attachments"]');if(!i)return;const a=document.createElement("span");a.className="tp-attach-chip"+(n?" uploading":""),a.dataset.chipId=e,a.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}`+(n?"":'<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>'),n||a.querySelector(".remove").addEventListener("click",()=>{this._removeAttachChip(e)}),i.appendChild(a)}_updateAttachChip(e,t,n){const i=this.element?.querySelector(`[data-chip-id="${e}"]`);i&&(i.classList.remove("uploading"),i.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>`,i.querySelector(".remove").addEventListener("click",()=>{this._stagedFiles=(this._stagedFiles||[]).filter(e=>e!==n),i.remove()}))}_removeAttachChip(e){const t=this.element?.querySelector(`[data-chip-id="${e}"]`);t&&t.remove()}_renderAttachments(){const e=this.element?.querySelector('[data-ref="attachments"]');e&&(e.innerHTML="")}_escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}_addEditButtons(){const e=this._getCurrentUserId();if(!e||!this.chatView?.messageViews)return;const t=new Map((this.chatView.messages||[]).map(e=>[e.id,e]));this.chatView.messageViews.forEach((n,i)=>{const a=t.get(i),s=n?.element?.querySelector(".message-item");if(!s)return;if(s.querySelectorAll(".tp-edit-btn").forEach(e=>e.remove()),!a||a.author?.id!==e)return;const o=document.createElement("button");o.className="tp-edit-btn",o.title="Edit note",o.innerHTML='<i class="bi bi-pencil"></i>',o.addEventListener("click",e=>{e.stopPropagation(),this._editNote(a)}),s.appendChild(o)})}_setupCollapsible(){const e=this.element?.querySelector('[data-container="chat-area"]');e&&(e.querySelectorAll(".tp-show-more").forEach(e=>e.remove()),e.querySelectorAll(".tp-collapsed").forEach(e=>e.classList.remove("tp-collapsed")),setTimeout(()=>{e.querySelectorAll(".message-content").forEach(e=>{if(e.scrollHeight<=52)return;e.classList.add("tp-collapsed"),e.style.setProperty("--tp-collapse-h","52px");const t=document.createElement("button");t.className="tp-show-more",t.textContent="Show more",t.addEventListener("click",()=>{const n=e.classList.toggle("tp-collapsed");t.textContent=n?"Show more":"Show less"}),e.after(t)})},150))}async _editNote(e){const t=Object.keys(e._metadata||{}).length?JSON.stringify(e._metadata,null,2):"",n=await i.Modal.form({title:"Edit Note",icon:"bi-pencil",size:"lg",fields:[{type:"tabset",tabs:[{label:"Note",fields:[{name:"note",type:"textarea",label:"Note",required:!0,cols:12,rows:8,value:e._rawContent||e.content}]},{label:"Metadata",fields:[{name:"metadata_json",type:"json",label:"Metadata (JSON)",cols:12,rows:10,value:t,help:'Action metadata — e.g. { "action": { "handler": "incident.rule_approval", "label": "...", "context": { ... } } }'}]}]}]});if(!n)return;const{TicketNote:a}=await Promise.resolve().then(()=>require("./admin-models-CkHjtMHf.js")).then(e=>e.Tickets),s=new a({id:e.id}),o={note:n.note};n.metadata_json&&(o.metadata="string"==typeof n.metadata_json?JSON.parse(n.metadata_json):n.metadata_json),await s.save(o),await this.chatView.refresh(),await this._afterChatRefresh()}async _saveAndSync(e){await this.model.save(e),await this.model.fetch(),this.chatView&&(await this.chatView.refresh(),await this._afterChatRefresh())}async onActionChangeStatus(e){const t=p.map(e=>({label:e.replace(/_/g," "),value:e,active:e===this.model.get("status")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({status:n}),this.render())}async onActionChangePriority(e){const t=g.map(e=>({label:e.label,value:e.value,active:e.value===this.model.get("priority")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({priority:parseInt(n)}),this.render())}async onActionChangeAssignee(){const e=await i.Modal.form({title:"Assign User",icon:"bi-person-plus",size:"sm",fields:[{name:"assignee",type:"collection",label:"User",Collection:s.UserList,labelField:"display_name",valueField:"id",required:!0,cols:12,value:this.model.get("assignee")}]});e&&(await this._saveAndSync({assignee:e.assignee}),this.render())}async onActionChangeCategory(e){const t=Object.entries(a.TicketCategories).map(([e,t])=>({label:t,value:e,active:e===this.model.get("category")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({category:n}),this.render())}async onActionChangeGroup(){const e=await i.Modal.form({title:"Change Group",icon:"bi-people",size:"sm",fields:[{name:"group",type:"collection",label:"Group",Collection:s.GroupList,labelField:"name",valueField:"id",required:!1,cols:12,value:this.model.get("group")}]});e&&(await this._saveAndSync({group:e.group}),this.render())}async onActionShowDescription(){const e=this.model.get("description")||"";if(!e)return this._editDescription();let t=!1,n="";try{const i=await r.rest.post("/api/docit/render",{markdown:e});n=i?.data?.data?.html||i?.data?.html||"",t=!!n}catch(a){}if(!t){const t=document.createElement("div");t.textContent=e,n=`<pre style="white-space:pre-wrap;">${t.innerHTML}</pre>`}"edit"===await i.Modal.dialog({title:`Ticket #${this.model.get("id")} — Description`,body:`<div style="font-size:0.85rem; line-height:1.65;">${n}</div>`,size:"lg",buttons:[{text:"Edit",class:"btn-primary",value:"edit"},{text:"Close",class:"btn-secondary",value:"close"}]})&&await this._editDescription()}async _editDescription(){const e=this.model.get("id"),t=`\n <div class="tp-desc-edit">\n <textarea data-ref="desc-textarea" rows="16" placeholder="Description (markdown supported)..."\n style="width:100%; font-family: var(--bs-font-monospace); font-size: 0.85rem; padding: 10px 12px; border: 1px solid var(--bs-border-color); border-radius: 8px; background: var(--bs-body-bg); color: var(--bs-body-color); resize: vertical; outline: none;">${(this.model.get("description")||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</textarea>\n <div class="text-muted small mt-1">\n Markdown supported. Cmd/Ctrl+B = bold · Cmd/Ctrl+I = italic · Shift+Enter continues lists · \`\`\` opens a code block\n </div>\n </div>\n `,n=(async()=>{for(let e=0;e<20;e++){await new Promise(e=>setTimeout(e,50));const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');if(e)return this._wireMarkdownTextarea(e),e}return null})(),a=await i.Modal.dialog({title:`Ticket #${e} — Edit Description`,body:t,size:"lg",buttons:[{text:"Cancel",class:"btn-secondary",value:null},{text:"Save",class:"btn-primary",handler:()=>{const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');return e?e.value:null}}]});await n,null!=a&&(await this._saveAndSync({description:a}),this.render())}_wireMarkdownTextarea(e){const t=(t,n=t.length)=>{const i=e.selectionStart,a=e.selectionEnd;e.setRangeText(t,i,a,"end"),e.selectionStart=e.selectionEnd=i+n,e.dispatchEvent(new Event("input"))},n=t=>{const n=e.selectionStart,i=e.selectionEnd,a=e.value.substring(n,i);a.startsWith(t)&&a.endsWith(t)?(e.setRangeText(a.slice(t.length,-t.length),n,i,"end"),e.selectionStart=n,e.selectionEnd=i-2*t.length):(e.setRangeText(t+a+t,n,i,"end"),e.selectionStart=n+t.length,e.selectionEnd=i+t.length)},i=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},a=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&s.shiftKey){const{start:n,text:a}=i(e.selectionStart),o=a.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const i=o[1],r=o[2];if(a.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${i}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!a(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!a(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=i(e.selectionStart),a=n.replace(/^ {1,2}/,"");e.setRangeText(a,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+a.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const i=s[n.key];if(!i)return;const a=e.selectionStart,o=e.selectionEnd;if(a!==o){n.preventDefault();const t=e.value.substring(a,o);e.setRangeText(n.key+t+i,a,o,"end"),e.selectionStart=a+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+i,1)})}async onActionViewIncident(){const e=this.model.get("incident");e?.id&&i.Modal.showModel(new a.Incident({id:e.id}))}async onActionEditTicket(){await i.Modal.modelForm({title:`Edit Ticket #${this.model.get("id")}`,model:this.model,size:"lg",fields:a.TicketForms.edit.fields})&&this.render()}async onActionRefreshNotes(){await this.chatView.refresh(),await this._afterChatRefresh(),this.getApp()?.toast?.success("Notes refreshed")}async onActionAskAi(){await o.openAssistantChat(this,"incident.Ticket")}async _showInlineSelect(e,t){return new Promise(i=>{let a=!1;const s=new n.ContextMenu({config:{items:e.map((e,t)=>({label:e.label,action:`pick-${t}`,class:e.active?"fw-bold":"",handler:()=>{a=!0,this.removeChild(s),i(e.value)}}))}}),o=s.closeDropdown.bind(s);s.closeDropdown=()=>{o(),a||(this.removeChild(s),i(null))},this.addChild(s),s.openAt(t.clientX,t.clientY)})}async _afterChatRefresh(){await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async setTicket(e){this.model=e,this.adapter=new o.TicketNoteAdapter(e.get("id")),this.chatView.adapter=this.adapter,this.chatView.clearMessages(),await this.render(),await this.chatView.refresh(),await this._afterChatRefresh()}}exports.default=TicketPanelView;
2
- //# sourceMappingURL=TicketPanelView-BGQZ6q2B.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TicketPanelView-BGQZ6q2B.js","sources":["../../src/extensions/admin/incidents/ActionCardView.js","../../src/extensions/admin/incidents/TicketPanelView.js"],"sourcesContent":["import View from '@core/View.js';\nimport Modal from '@core/views/feedback/Modal.js';\n\nfunction esc(s) {\n if (!s) return '';\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\nconst ALLOWED_REFS = new Set([\n 'incident.RuleSet', 'incident.Incident', 'incident.Event',\n 'incident.Ticket', 'account.GeoLocatedIP'\n]);\n\nconst HANDLER_COLORS = {\n 'incident.rule_approval': { dot: 'accent', label: 'Rule' },\n 'incident.block_confirm': { dot: 'red', label: 'Block' },\n 'incident.rule_update': { dot: 'green', label: 'Update' },\n 'incident.escalate': { dot: 'amber', label: 'Escalate' }\n};\n\nclass ActionCardView extends View {\n constructor(options = {}) {\n super({\n className: 'action-card-view',\n ...options\n });\n this.action = options.action;\n this.noteId = options.noteId;\n this.ticketStatus = options.ticketStatus;\n }\n\n get isResolved() {\n return !!this.action?.resolved;\n }\n\n get isContext() {\n return this.action?.type === 'context';\n }\n\n get isClosed() {\n return this.ticketStatus === 'closed' || this.ticketStatus === 'resolved';\n }\n\n get handlerConfig() {\n return HANDLER_COLORS[this.action?.handler] || { dot: 'accent', label: 'Action' };\n }\n\n onBeforeRender() {\n const cfg = this.handlerConfig;\n this.dotClass = cfg.dot;\n\n if (this.isContext) {\n this._buildContextTemplate();\n } else if (this.isResolved) {\n this._buildResolvedTemplate();\n } else {\n this._buildPendingTemplate();\n }\n }\n\n onActionToggleCompact() {\n const acEl = this.element?.querySelector('.ac.resolved');\n if (acEl) acEl.classList.toggle('compact');\n }\n\n _buildContextTemplate() {\n const refs = (this.action.references || []).filter(ref => ALLOWED_REFS.has(ref.model));\n const refItems = refs.map(ref => {\n const label = esc(ref.label) || `${esc(ref.model.split('.').pop())} #${esc(ref.pk)}`;\n return `<span class=\"ac-ref\" data-action=\"open-ref\" data-model=\"${esc(ref.model)}\" data-pk=\"${esc(ref.pk)}\"><i class=\"bi bi-box-arrow-up-right\"></i>${label}</span>`;\n }).join('');\n\n this.template = `\n <div class=\"ac ac-context\">\n <div class=\"ac-top\">\n <span class=\"ac-dot context\"></span>\n <span class=\"ac-label\">Referenced models</span>\n </div>\n <div class=\"ac-detail\">${refItems}</div>\n </div>\n `;\n }\n\n _buildResolvedTemplate() {\n const resolution = this.action.resolution || 'approved';\n const badgeClass = resolution === 'approved' ? 'approved' : 'denied';\n const badgeText = resolution === 'approved' ? 'Approved' : 'Denied';\n const target = this.action.context?.target;\n let refHtml = '';\n if (target && ALLOWED_REFS.has(target.model)) {\n const label = esc(this.action.context.label) || `${esc(target.model.split('.').pop())} #${esc(target.pk)}`;\n refHtml = `<div class=\"ac-detail\"><span class=\"ac-ref\" data-action=\"open-ref\" data-model=\"${esc(target.model)}\" data-pk=\"${esc(target.pk)}\"><i class=\"bi bi-box-arrow-up-right\"></i>${label}</span></div>`;\n }\n this.template = `\n <div class=\"ac resolved compact\">\n <div class=\"ac-top\" data-action=\"toggle-compact\" title=\"Click to toggle\">\n <span class=\"ac-dot ${this.dotClass}\"></span>\n <span class=\"ac-label\">${esc(this.action.label) || 'Action'}</span>\n <span class=\"ac-badge ${badgeClass}\">${badgeText}</span>\n <i class=\"bi bi-chevron-down ac-chevron\"></i>\n </div>\n ${refHtml}\n </div>\n `;\n }\n\n _buildPendingTemplate() {\n const target = this.action.context?.target;\n let refHtml = '';\n if (target && ALLOWED_REFS.has(target.model)) {\n const label = esc(this.action.context.label) || `${esc(target.model.split('.').pop())} #${esc(target.pk)}`;\n refHtml = `<br><span class=\"ac-ref\" data-action=\"open-ref\" data-model=\"${esc(target.model)}\" data-pk=\"${esc(target.pk)}\"><i class=\"bi bi-box-arrow-up-right\"></i>${label}</span>`;\n }\n const detail = esc(this.action.context?.detail);\n const disabledAttr = this.isClosed ? ' disabled' : '';\n this.template = `\n <div class=\"ac\">\n <div class=\"ac-top\">\n <span class=\"ac-dot ${this.dotClass}\"></span>\n <span class=\"ac-label\">${esc(this.action.label) || 'Action'}</span>\n </div>\n <div class=\"ac-detail\">${detail}${refHtml}</div>\n <div class=\"ac-foot\">\n <button class=\"btn-approve\" data-action=\"approve\"${disabledAttr}>Approve</button>\n <button class=\"btn-deny\" data-action=\"deny\"${disabledAttr}>Deny</button>\n </div>\n </div>\n `;\n }\n\n async onActionOpenRef(_event, el) {\n const modelRef = el.dataset.model;\n const pk = el.dataset.pk;\n if (!ALLOWED_REFS.has(modelRef) || !/^\\d+$/.test(pk)) return;\n const app = this.getApp();\n const ModelClass = app?.getModelByRef(modelRef);\n if (ModelClass?.VIEW_CLASS) {\n Modal.showModel(new ModelClass({ id: pk }));\n }\n }\n\n onActionApprove() {\n this.emit('action:respond', { noteId: this.noteId, action: 'approve', handler: this.action.handler, context: this.action.context });\n }\n\n onActionDeny() {\n this.emit('action:respond', { noteId: this.noteId, action: 'deny', handler: this.action.handler, context: this.action.context });\n }\n}\n\nexport { HANDLER_COLORS };\nexport default ActionCardView;\n","import View from '@core/View.js';\nimport ChatView from '@core/views/chat/ChatView.js';\nimport ContextMenu from '@core/views/feedback/ContextMenu.js';\nimport Modal from '@core/views/feedback/Modal.js';\nimport { Ticket, TicketForms, TicketCategories } from '@ext/admin/models/Tickets.js';\nimport { Incident } from '@ext/admin/models/Incident.js';\nimport { GroupList } from '@core/models/Group.js';\nimport { UserList } from '@core/models/User.js';\nimport TicketNoteAdapter from './adapters/TicketNoteAdapter.js';\nimport ActionCardView from './ActionCardView.js';\nimport { openAssistantChat } from '../assistant/AssistantContextChat.js';\nimport rest from '@core/Rest.js';\n\n\nconst STATUS_OPTIONS = ['new', 'open', 'in_progress', 'pending', 'resolved', 'qa', 'closed', 'ignored'];\nconst STATUS_PILL = {\n new: 'pill-new',\n open: 'pill-open',\n in_progress: 'pill-prog',\n pending: 'pill-prog',\n resolved: 'pill-resolved',\n qa: 'pill-open',\n closed: 'pill-closed',\n ignored: 'pill-closed'\n};\nconst PRIORITY_OPTIONS = [\n { value: 10, label: 'P10 — Critical' },\n { value: 9, label: 'P9 — Severe' },\n { value: 8, label: 'P8 — High' },\n { value: 7, label: 'P7 — Elevated' },\n { value: 5, label: 'P5 — Normal' },\n { value: 3, label: 'P3 — Low' },\n { value: 1, label: 'P1 — Info' }\n];\n\nclass TicketPanelView extends View {\n constructor(options = {}) {\n super({\n className: 'ticket-panel-view',\n ...options\n });\n this.model = options.model || new Ticket(options.data || {});\n }\n\n onBeforeRender() {\n const status = this.model.get('status') || 'new';\n this.statusPill = STATUS_PILL[status] || 'pill-closed';\n this.statusLabel = (status || '').replace(/_/g, ' ');\n this.priorityLabel = `P${this.model.get('priority') || 5}`;\n this.assigneeName = this.model.get('assignee.display_name') || this.model.get('assignee') || 'Unassigned';\n this.categoryLabel = this.model.get('category') || 'ticket';\n this.groupName = this.model.get('group.name') || this.model.get('group') || 'None';\n this.hasDescription = !!this.model.get('description');\n this.hasIncident = !!(this.model.get('incident') && typeof this.model.get('incident') === 'object' && this.model.get('incident').id);\n this.priorityColor = (this.model.get('priority') || 5) >= 7 ? 'var(--bs-danger)' : 'var(--bs-secondary-color)';\n\n this.template = `\n <style>\n .ticket-panel-view { height: 100%; display: flex; flex-direction: column; }\n .tp-header { padding: 10px 16px 6px; border-bottom: 1px solid var(--bs-border-color-translucent); flex-shrink: 0; }\n .tp-title-row { display: flex; align-items: flex-start; gap: 6px; }\n .tp-title { font-size: 0.88rem; font-weight: 600; color: var(--bs-emphasis-color); line-height: 1.3; flex: 1; min-width: 0; cursor: ${this.hasDescription ? 'pointer' : 'default'}; transition: color 0.12s; }\n ${this.hasDescription ? '.tp-title:hover { color: var(--bs-primary); }' : ''}\n .tp-title i { font-size: 0.6rem; vertical-align: middle; margin-left: 3px; opacity: 0; transition: opacity 0.12s; }\n .tp-title:hover i { opacity: 0.6; }\n .tp-btns { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }\n .tp-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: var(--bs-secondary-color); border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.12s; }\n .tp-btn:hover { background: var(--bs-tertiary-bg); color: var(--bs-body-color); }\n .tp-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }\n .tp-id { font-family: var(--bs-font-monospace); font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; font-weight: 500; cursor: pointer; transition: filter 0.12s; }\n .tp-pill:hover { filter: brightness(0.9); }\n .tp-pill-new { background: rgba(var(--bs-info-rgb), 0.1); color: var(--bs-info); }\n .tp-pill-open { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-pill-prog { background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); }\n .tp-pill-closed { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }\n .tp-pill-resolved { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-time { font-size: 0.66rem; color: var(--bs-secondary-color); }\n .tp-desc-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--bs-primary); cursor: pointer; padding: 2px 8px; border-radius: 5px; background: rgba(var(--bs-primary-rgb), 0.08); transition: all 0.12s; }\n .tp-desc-chip:hover { background: rgba(var(--bs-primary-rgb), 0.16); }\n .tp-desc-chip i { font-size: 0.7rem; }\n .tp-meta { display: flex; align-items: center; gap: 3px; margin-top: 5px; }\n .tp-fields { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; }\n .tp-field { display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--bs-secondary-color); padding: 2px 7px; border-radius: 5px; cursor: pointer; transition: all 0.12s; border: 1px solid transparent; }\n .tp-field:hover { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }\n .tp-field i { font-size: 0.68rem; }\n .tp-field .caret { font-size: 0.55rem; opacity: 0; transition: opacity 0.12s; margin-left: -1px; }\n .tp-field:hover .caret { opacity: 0.6; }\n .tp-sep { color: var(--bs-secondary-color); font-size: 0.6rem; margin: 0 1px; user-select: none; }\n\n .tp-linked { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--bs-border-color-translucent); font-size: 0.75rem; color: var(--bs-secondary-color); flex-shrink: 0; }\n .tp-linked i { color: var(--bs-warning); font-size: 0.72rem; }\n .tp-linked a { color: var(--bs-primary); text-decoration: none; font-weight: 500; }\n .tp-linked a:hover { text-decoration: underline; }\n .tp-linked .lpill { font-size: 0.62rem; padding: 0 5px; border-radius: 3px; background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); font-weight: 500; }\n\n .tp-conv { flex: 1; overflow-y: auto; min-height: 0; }\n .tp-conv .chat-container { border: none; border-radius: 0; }\n .tp-conv .chat-messages { padding: 6px 0; }\n .tp-conv .chat-input-wrapper { display: none; }\n .tp-conv .message-item { position: relative; }\n .tp-conv .tp-edit-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; border: none; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); border-radius: 5px; cursor: pointer; font-size: 0.72rem; transition: all 0.12s; }\n .tp-conv .tp-edit-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }\n .tp-conv .message-item:hover .tp-edit-btn { display: flex; }\n .tp-conv .message-text { white-space: normal; }\n .tp-conv .message-text p { margin-bottom: 6px; }\n .tp-conv .message-text p:last-child { margin-bottom: 0; }\n .tp-conv .message-text h1,\n .tp-conv .message-text h2,\n .tp-conv .message-text h3,\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-weight: 600; margin-top: 10px; margin-bottom: 4px; line-height: 1.3; }\n .tp-conv .message-text h1 { font-size: 1.05rem; }\n .tp-conv .message-text h2 { font-size: 1rem; }\n .tp-conv .message-text h3 { font-size: 0.95rem; }\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-size: 0.88rem; }\n .tp-conv .message-text h1:first-child,\n .tp-conv .message-text h2:first-child,\n .tp-conv .message-text h3:first-child { margin-top: 0; }\n .tp-conv .message-text hr { margin: 4px 0; opacity: 0.15; }\n .tp-conv .message-text ul,\n .tp-conv .message-text ol { padding-left: 20px; margin-top: 2px; margin-bottom: 6px; }\n .tp-conv .message-text li { margin-bottom: 2px; }\n .tp-conv .message-text pre { background: var(--bs-tertiary-bg); border-radius: 6px; padding: 10px 14px; margin: 8px 0; font-size: 0.8rem; overflow-x: auto; }\n .tp-conv .message-text code { font-size: 0.85em; padding: 1px 5px; background: var(--bs-tertiary-bg); border-radius: 4px; }\n .tp-conv .message-text pre code { padding: 0; background: none; }\n .tp-conv .message-text table { width: 100%; margin: 8px 0; border-collapse: collapse; font-size: 0.82rem; }\n .tp-conv .message-text th,\n .tp-conv .message-text td { padding: 5px 8px; border: 1px solid var(--bs-border-color); text-align: left; }\n .tp-conv .message-text th { background: var(--bs-tertiary-bg); font-weight: 600; }\n .tp-conv .message-text blockquote { margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--bs-border-color); color: var(--bs-secondary-color); }\n\n .tp-action-area { padding: 0; }\n\n .tp-input { border-top: 1px solid var(--bs-border-color-translucent); padding: 10px 16px; flex-shrink: 0; }\n .tp-input-wrap { display: flex; align-items: flex-end; gap: 8px; }\n .tp-input textarea { flex: 1; font-size: 0.8rem; border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 7px 10px; resize: none; background: var(--bs-body-bg); color: var(--bs-body-color); outline: none; transition: border-color 0.15s; font-family: inherit; max-height: 160px; overflow-y: auto; }\n .tp-input textarea:focus { border-color: var(--bs-primary); }\n .tp-input textarea::placeholder { color: var(--bs-secondary-color); }\n .tp-send-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: var(--bs-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 0.85rem; transition: filter 0.12s; }\n .tp-send-btn:hover { filter: brightness(1.1); }\n .tp-input-hint { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-input-hint i { font-size: 0.75rem; }\n .tp-input.tp-dragover { background: rgba(var(--bs-primary-rgb), 0.04); }\n .tp-input.tp-dragover textarea { border-color: var(--bs-primary); border-style: dashed; }\n .tp-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0; }\n .tp-attach-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.72rem; color: var(--bs-body-color); }\n .tp-attach-chip i { font-size: 0.68rem; }\n .tp-attach-chip .remove { cursor: pointer; color: var(--bs-secondary-color); margin-left: 2px; }\n .tp-attach-chip .remove:hover { color: var(--bs-danger); }\n .tp-attach-chip.uploading { opacity: 0.6; }\n .tp-conv .message-content.tp-collapsed { max-height: var(--tp-collapse-h, 52px); overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }\n .tp-conv .message-item:has(.tp-show-more) { flex-wrap: wrap; }\n .tp-show-more { background: none; border: none; padding: 2px 0; margin: -2px 0 0 48px; font-size: 0.7rem; color: var(--bs-secondary-color); cursor: pointer; text-align: left; width: calc(100% - 48px); }\n .tp-show-more:hover { color: var(--bs-body-color); }\n </style>\n\n <div class=\"tp-header\">\n <div class=\"tp-title-row\">\n <div class=\"tp-title\" ${this.hasDescription ? 'data-action=\"show-description\"' : ''} ${this.hasDescription ? 'title=\"View full description\"' : ''}>\n {{model.title}}${this.hasDescription ? ' <i class=\"bi bi-arrow-up-right-square\"></i>' : ''}\n </div>\n <div class=\"tp-btns\">\n <div data-container=\"panel-menu\"></div>\n </div>\n </div>\n <div class=\"tp-sub\">\n <span class=\"tp-id\">#{{model.id}}</span>\n <span class=\"tp-pill tp-${this.statusPill}\" data-action=\"change-status\" title=\"Change status\">{{statusLabel}} <i class=\"bi bi-chevron-down\" style=\"font-size:0.5rem;\"></i></span>\n <span class=\"tp-time\"><i class=\"bi bi-clock\"></i> {{model.created|relative}}</span>\n <span class=\"tp-desc-chip\" data-action=\"show-description\" title=\"${this.hasDescription ? 'View / edit description' : 'Add description'}\"><i class=\"bi bi-file-text\"></i> ${this.hasDescription ? 'Description' : 'Add description'}</span>\n </div>\n <div class=\"tp-meta\">\n <div class=\"tp-fields\">\n <span class=\"tp-field\" data-action=\"change-priority\" title=\"Change priority\">\n <i class=\"bi bi-flag-fill\" style=\"color:${this.priorityColor};\"></i>{{priorityLabel}}\n <i class=\"bi bi-chevron-down caret\"></i>\n </span>\n <span class=\"tp-sep\">&middot;</span>\n <span class=\"tp-field\" data-action=\"change-assignee\" title=\"Assign\">\n <i class=\"bi bi-person\"></i>{{assigneeName}}\n <i class=\"bi bi-chevron-down caret\"></i>\n </span>\n <span class=\"tp-sep\">&middot;</span>\n <span class=\"tp-field\" data-action=\"change-category\" title=\"Change category\">\n <i class=\"bi bi-tag\"></i>{{categoryLabel}}\n <i class=\"bi bi-chevron-down caret\"></i>\n </span>\n <span class=\"tp-sep\">&middot;</span>\n <span class=\"tp-field\" data-action=\"change-group\" title=\"Change group\">\n <i class=\"bi bi-people\"></i>{{groupName}}\n <i class=\"bi bi-chevron-down caret\"></i>\n </span>\n </div>\n </div>\n </div>\n\n {{#hasIncident|bool}}\n <div class=\"tp-linked\">\n <i class=\"bi bi-exclamation-triangle-fill\"></i>\n <a href=\"#\" data-action=\"view-incident\">Incident #{{model.incident.id}}</a>\n {{#model.incident.status}}<span class=\"lpill\">{{model.incident.status}}</span>{{/model.incident.status}}\n {{#model.incident.event_count}}<span>&middot; {{model.incident.event_count}} events</span>{{/model.incident.event_count}}\n </div>\n {{/hasIncident|bool}}\n\n <div class=\"tp-conv\">\n <div data-container=\"chat-area\"></div>\n </div>\n\n <div class=\"tp-input\" data-ref=\"drop-zone\">\n <div class=\"tp-attachments\" data-ref=\"attachments\"></div>\n <div class=\"tp-input-wrap\">\n <textarea rows=\"2\" placeholder=\"Add a note...\" data-ref=\"note-textarea\"></textarea>\n <button class=\"tp-send-btn\" data-action=\"send-note\" title=\"Send\"><i class=\"bi bi-arrow-up\"></i></button>\n </div>\n <div class=\"tp-input-hint\">\n <i class=\"bi bi-paperclip\"></i> Drag &amp; drop files to attach\n </div>\n </div>\n `;\n }\n\n async onInit() {\n this.adapter = new TicketNoteAdapter(this.model.get('id'));\n\n this.chatView = new ChatView({\n containerId: 'chat-area',\n adapter: this.adapter,\n theme: 'compact',\n currentUserId: this._getCurrentUserId(),\n showInput: false\n });\n this.addChild(this.chatView);\n\n const menu = new ContextMenu({\n containerId: 'panel-menu',\n className: 'context-menu-view header-menu-absolute',\n context: this.model,\n config: {\n icon: 'bi-three-dots',\n btnClass: 'tp-btn',\n items: [\n { label: 'Ask AI', action: 'ask-ai', icon: 'bi-robot' },\n { type: 'divider' },\n { label: 'Edit Ticket', action: 'edit-ticket', icon: 'bi-pencil' },\n { label: 'Refresh Notes', action: 'refresh-notes', icon: 'bi-arrow-clockwise' },\n { type: 'divider' },\n { label: 'Close Window', action: 'close', icon: 'bi-x-lg' }\n ]\n }\n });\n this.addChild(menu);\n }\n\n async onAfterRender() {\n this._setupTextarea();\n this._setupDragDrop();\n // Wait one microtask so child message-view DOM is settled before we\n // place inline action cards and edit buttons.\n await new Promise(r => setTimeout(r, 0));\n await this._loadActionCards();\n this._addEditButtons();\n this._setupCollapsible();\n }\n\n async _loadActionCards() {\n this._cleanupActionCards();\n\n const messages = this.chatView.messages || [];\n if (!messages.length) return;\n\n for (const msg of messages) {\n if (!msg.action || typeof msg.action !== 'object') continue;\n\n const msgView = this.chatView.messageViews.get(msg.id);\n if (!msgView?.element) continue;\n\n const card = new ActionCardView({\n action: msg.action,\n noteId: msg.id,\n ticketStatus: this.model.get('status')\n });\n // Pending approvals get the respond handler\n if (msg.action.type !== 'context' && !msg.action.resolved) {\n card.on('action:respond', (data) => this._handleActionResponse(data));\n }\n this.addChild(card);\n await card.render();\n msgView.element.after(card.element);\n }\n }\n\n _cleanupActionCards() {\n for (const id in this.children) {\n const child = this.children[id];\n if (child instanceof ActionCardView) {\n this.removeChild(child);\n }\n }\n }\n\n async _handleActionResponse(data) {\n const actionNote = { action: { handler: data.handler, context: data.context } };\n const app = this.getApp();\n app?.showLoading();\n try {\n await this.adapter.addActionResponse(actionNote, data.action);\n await this.model.fetch();\n await this.chatView.refresh();\n this.render();\n } finally {\n app?.hideLoading();\n }\n }\n\n _getCurrentUserId() {\n const app = this.getApp();\n return app?.activeUser?.id || app?.getActiveUser?.()?.id || null;\n }\n\n // ── Actions ──\n\n onActionClose() {\n this.emit('panel:close');\n }\n\n async onActionSendNote() {\n const textarea = this.element?.querySelector('[data-ref=\"note-textarea\"]');\n const text = textarea?.value?.trim();\n const files = this._stagedFiles || [];\n if (!text && !files.length) return;\n textarea.value = '';\n textarea.style.height = '';\n this._stagedFiles = [];\n this._renderAttachments();\n await this.adapter.addNote({ text: text || '', files });\n await this.chatView.refresh();\n await this._afterChatRefresh();\n }\n\n _setupTextarea() {\n const textarea = this.element?.querySelector('[data-ref=\"note-textarea\"]');\n if (!textarea) return;\n\n const _insert = (text, cursorOffset = text.length) => {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n textarea.setRangeText(text, start, end, 'end');\n textarea.selectionStart = textarea.selectionEnd = start + cursorOffset;\n textarea.dispatchEvent(new Event('input'));\n textarea.scrollTop = textarea.scrollHeight;\n };\n\n const _wrapSelection = (wrapper) => {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const sel = textarea.value.substring(start, end);\n if (sel.startsWith(wrapper) && sel.endsWith(wrapper)) {\n textarea.setRangeText(sel.slice(wrapper.length, -wrapper.length), start, end, 'end');\n textarea.selectionStart = start;\n textarea.selectionEnd = end - wrapper.length * 2;\n } else {\n textarea.setRangeText(wrapper + sel + wrapper, start, end, 'end');\n textarea.selectionStart = start + wrapper.length;\n textarea.selectionEnd = end + wrapper.length;\n }\n };\n\n const _lineAt = (pos) => {\n const before = textarea.value.substring(0, pos);\n const lineStart = before.lastIndexOf('\\n') + 1;\n return { start: lineStart, text: textarea.value.substring(lineStart, pos) };\n };\n\n const _inCodeFence = (pos) => {\n const before = textarea.value.substring(0, pos);\n const fences = (before.match(/^```/gm) || []).length;\n return fences % 2 === 1;\n };\n\n textarea.addEventListener('keydown', (e) => {\n const mod = e.ctrlKey || e.metaKey;\n\n // Enter → submit (unless Shift held)\n if (e.key === 'Enter' && !e.shiftKey && !mod) {\n e.preventDefault();\n this.onActionSendNote();\n return;\n }\n\n // Shift+Enter → auto-continue list bullets\n if (e.key === 'Enter' && e.shiftKey) {\n const { start, text } = _lineAt(textarea.selectionStart);\n const match = text.match(/^(\\s*)([-*]|\\d+\\.)\\s/);\n if (match) {\n e.preventDefault();\n const indent = match[1];\n const bullet = match[2];\n if (text.trim() === bullet) {\n textarea.setRangeText('', start, textarea.selectionStart, 'end');\n } else {\n const next = /^\\d+\\./.test(bullet) ? `${parseInt(bullet) + 1}.` : bullet;\n _insert(`\\n${indent}${next} `);\n }\n return;\n }\n }\n\n // ``` auto-close: after typing third backtick, insert fence pair\n if (e.key === '`' && !mod) {\n const pos = textarea.selectionStart;\n const before = textarea.value.substring(0, pos);\n if (before.endsWith('``') && !_inCodeFence(pos - 2)) {\n e.preventDefault();\n _insert('`\\n\\n```', 2);\n return;\n }\n }\n\n // Tab indent/dedent inside code fences\n if (e.key === 'Tab' && _inCodeFence(textarea.selectionStart)) {\n e.preventDefault();\n if (e.shiftKey) {\n const { start, text } = _lineAt(textarea.selectionStart);\n const stripped = text.replace(/^ {1,2}/, '');\n textarea.setRangeText(stripped, start, start + text.length, 'end');\n textarea.selectionStart = textarea.selectionEnd = start + stripped.length;\n } else {\n _insert(' ');\n }\n return;\n }\n\n // Ctrl/Cmd+B → bold\n if (mod && e.key === 'b') {\n e.preventDefault();\n _wrapSelection('**');\n return;\n }\n\n // Ctrl/Cmd+I → italic\n if (mod && e.key === 'i') {\n e.preventDefault();\n _wrapSelection('*');\n return;\n }\n });\n\n // Auto-close pairs: ( [ \"\n const PAIRS = { '(': ')', '[': ']', '\"': '\"' };\n textarea.addEventListener('keydown', (e) => {\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n const close = PAIRS[e.key];\n if (!close) return;\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n if (start !== end) {\n e.preventDefault();\n const sel = textarea.value.substring(start, end);\n textarea.setRangeText(e.key + sel + close, start, end, 'end');\n textarea.selectionStart = start + 1;\n textarea.selectionEnd = end + 1;\n } else {\n e.preventDefault();\n _insert(e.key + close, 1);\n }\n });\n\n textarea.addEventListener('input', () => {\n textarea.style.height = '';\n textarea.style.height = Math.min(textarea.scrollHeight, 160) + 'px';\n });\n }\n\n _setupDragDrop() {\n const zone = this.element?.querySelector('[data-ref=\"drop-zone\"]');\n if (!zone) return;\n this._stagedFiles = this._stagedFiles || [];\n let counter = 0;\n zone.addEventListener('dragenter', (e) => { e.preventDefault(); counter++; zone.classList.add('tp-dragover'); });\n zone.addEventListener('dragleave', () => { counter--; if (counter <= 0) { counter = 0; zone.classList.remove('tp-dragover'); } });\n zone.addEventListener('dragover', (e) => e.preventDefault());\n zone.addEventListener('drop', async (e) => {\n e.preventDefault();\n counter = 0;\n zone.classList.remove('tp-dragover');\n const files = Array.from(e.dataTransfer?.files || []);\n if (!files.length) return;\n const { File: FileModel } = await import('@core/models/Files.js');\n for (const file of files) {\n const chipId = Date.now() + Math.random();\n this._addAttachChip(chipId, file.name, true);\n try {\n const fileModel = new FileModel();\n await fileModel.upload({ file, showToast: false });\n this._stagedFiles.push(fileModel);\n this._updateAttachChip(chipId, fileModel.get('name') || file.name, fileModel);\n } catch (err) {\n console.error('File upload failed:', err);\n this._removeAttachChip(chipId);\n this.getApp()?.toast?.error?.('Upload failed: ' + file.name);\n }\n }\n });\n }\n\n _addAttachChip(id, name, uploading) {\n const container = this.element?.querySelector('[data-ref=\"attachments\"]');\n if (!container) return;\n const chip = document.createElement('span');\n chip.className = 'tp-attach-chip' + (uploading ? ' uploading' : '');\n chip.dataset.chipId = id;\n chip.innerHTML = `<i class=\"bi bi-paperclip\"></i>${this._escapeHtml(name)}` +\n (uploading ? '' : '<span class=\"remove\" data-remove=\"1\"><i class=\"bi bi-x\"></i></span>');\n if (!uploading) {\n chip.querySelector('.remove').addEventListener('click', () => {\n this._removeAttachChip(id);\n });\n }\n container.appendChild(chip);\n }\n\n _updateAttachChip(id, name, fileModel) {\n const chip = this.element?.querySelector(`[data-chip-id=\"${id}\"]`);\n if (!chip) return;\n chip.classList.remove('uploading');\n chip.innerHTML = `<i class=\"bi bi-paperclip\"></i>${this._escapeHtml(name)}<span class=\"remove\" data-remove=\"1\"><i class=\"bi bi-x\"></i></span>`;\n chip.querySelector('.remove').addEventListener('click', () => {\n this._stagedFiles = (this._stagedFiles || []).filter(f => f !== fileModel);\n chip.remove();\n });\n }\n\n _removeAttachChip(id) {\n const chip = this.element?.querySelector(`[data-chip-id=\"${id}\"]`);\n if (chip) chip.remove();\n }\n\n _renderAttachments() {\n const container = this.element?.querySelector('[data-ref=\"attachments\"]');\n if (container) container.innerHTML = '';\n }\n\n _escapeHtml(str) {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n _addEditButtons() {\n const userId = this._getCurrentUserId();\n if (!userId || !this.chatView?.messageViews) return;\n const msgById = new Map((this.chatView.messages || []).map(m => [m.id, m]));\n this.chatView.messageViews.forEach((view, msgId) => {\n const msg = msgById.get(msgId);\n const item = view?.element?.querySelector('.message-item');\n if (!item) return;\n item.querySelectorAll('.tp-edit-btn').forEach(b => b.remove());\n if (!msg || msg.author?.id !== userId) return;\n const btn = document.createElement('button');\n btn.className = 'tp-edit-btn';\n btn.title = 'Edit note';\n btn.innerHTML = '<i class=\"bi bi-pencil\"></i>';\n btn.addEventListener('click', (e) => { e.stopPropagation(); this._editNote(msg); });\n item.appendChild(btn);\n });\n }\n\n _setupCollapsible() {\n const container = this.element?.querySelector('[data-container=\"chat-area\"]');\n if (!container) return;\n container.querySelectorAll('.tp-show-more').forEach(b => b.remove());\n container.querySelectorAll('.tp-collapsed').forEach(el => el.classList.remove('tp-collapsed'));\n setTimeout(() => {\n const MAX = 52;\n container.querySelectorAll('.message-content').forEach(body => {\n if (body.scrollHeight <= MAX) return;\n body.classList.add('tp-collapsed');\n body.style.setProperty('--tp-collapse-h', MAX + 'px');\n const btn = document.createElement('button');\n btn.className = 'tp-show-more';\n btn.textContent = 'Show more';\n btn.addEventListener('click', () => {\n const collapsed = body.classList.toggle('tp-collapsed');\n btn.textContent = collapsed ? 'Show more' : 'Show less';\n });\n body.after(btn);\n });\n }, 150);\n }\n\n async _editNote(msg) {\n const metaJson = Object.keys(msg._metadata || {}).length\n ? JSON.stringify(msg._metadata, null, 2) : '';\n const data = await Modal.form({\n title: 'Edit Note',\n icon: 'bi-pencil',\n size: 'lg',\n fields: [{\n type: 'tabset',\n tabs: [\n {\n label: 'Note',\n fields: [{\n name: 'note', type: 'textarea', label: 'Note',\n required: true, cols: 12, rows: 8,\n value: msg._rawContent || msg.content\n }]\n },\n {\n label: 'Metadata',\n fields: [{\n name: 'metadata_json', type: 'json', label: 'Metadata (JSON)',\n cols: 12, rows: 10,\n value: metaJson,\n help: 'Action metadata — e.g. { \"action\": { \"handler\": \"incident.rule_approval\", \"label\": \"...\", \"context\": { ... } } }'\n }]\n }\n ]\n }]\n });\n if (!data) return;\n const { TicketNote } = await import('@ext/admin/models/Tickets.js');\n const note = new TicketNote({ id: msg.id });\n const payload = { note: data.note };\n if (data.metadata_json) {\n payload.metadata = typeof data.metadata_json === 'string'\n ? JSON.parse(data.metadata_json) : data.metadata_json;\n }\n await note.save(payload);\n await this.chatView.refresh();\n await this._afterChatRefresh();\n }\n\n async _saveAndSync(patch) {\n // Some endpoints don't echo the updated record on PUT — Model.save() only\n // merges response.data.data into local state. Re-fetch so the panel and\n // the shared list collection both see the new values. Also refresh notes\n // since the backend may create a status_change note as a side effect.\n await this.model.save(patch);\n await this.model.fetch();\n if (this.chatView) {\n await this.chatView.refresh();\n await this._afterChatRefresh();\n }\n }\n\n async onActionChangeStatus(event) {\n const items = STATUS_OPTIONS.map(s => ({\n label: s.replace(/_/g, ' '),\n value: s,\n active: s === this.model.get('status')\n }));\n const result = await this._showInlineSelect(items, event);\n if (!result) return;\n await this._saveAndSync({ status: result });\n this.render();\n }\n\n async onActionChangePriority(event) {\n const items = PRIORITY_OPTIONS.map(p => ({\n label: p.label,\n value: p.value,\n active: p.value === this.model.get('priority')\n }));\n const result = await this._showInlineSelect(items, event);\n if (!result) return;\n await this._saveAndSync({ priority: parseInt(result) });\n this.render();\n }\n\n async onActionChangeAssignee() {\n const data = await Modal.form({\n title: 'Assign User',\n icon: 'bi-person-plus',\n size: 'sm',\n fields: [{\n name: 'assignee', type: 'collection', label: 'User',\n Collection: UserList, labelField: 'display_name', valueField: 'id',\n required: true, cols: 12,\n value: this.model.get('assignee')\n }]\n });\n if (!data) return;\n await this._saveAndSync({ assignee: data.assignee });\n this.render();\n }\n\n async onActionChangeCategory(event) {\n const items = Object.entries(TicketCategories).map(([key, label]) => ({\n label,\n value: key,\n active: key === this.model.get('category')\n }));\n const result = await this._showInlineSelect(items, event);\n if (!result) return;\n await this._saveAndSync({ category: result });\n this.render();\n }\n\n async onActionChangeGroup() {\n const data = await Modal.form({\n title: 'Change Group',\n icon: 'bi-people',\n size: 'sm',\n fields: [{\n name: 'group', type: 'collection', label: 'Group',\n Collection: GroupList, labelField: 'name', valueField: 'id',\n required: false, cols: 12,\n value: this.model.get('group')\n }]\n });\n if (!data) return;\n await this._saveAndSync({ group: data.group });\n this.render();\n }\n\n async onActionShowDescription() {\n const raw = this.model.get('description') || '';\n if (!raw) {\n // No description yet → jump straight to edit\n return this._editDescription();\n }\n let rendered = false;\n let html = '';\n try {\n const resp = await rest.post('/api/docit/render', { markdown: raw });\n html = resp?.data?.data?.html || resp?.data?.html || '';\n rendered = !!html;\n } catch (_e) { /* fallback to escaped text */ }\n if (!rendered) {\n const div = document.createElement('div');\n div.textContent = raw;\n html = `<pre style=\"white-space:pre-wrap;\">${div.innerHTML}</pre>`;\n }\n const choice = await Modal.dialog({\n title: `Ticket #${this.model.get('id')} — Description`,\n body: `<div style=\"font-size:0.85rem; line-height:1.65;\">${html}</div>`,\n size: 'lg',\n buttons: [\n { text: 'Edit', class: 'btn-primary', value: 'edit' },\n { text: 'Close', class: 'btn-secondary', value: 'close' }\n ]\n });\n if (choice === 'edit') await this._editDescription();\n }\n\n async _editDescription() {\n const id = this.model.get('id');\n const current = this.model.get('description') || '';\n const escaped = current\n .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n const body = `\n <div class=\"tp-desc-edit\">\n <textarea data-ref=\"desc-textarea\" rows=\"16\" placeholder=\"Description (markdown supported)...\"\n style=\"width:100%; font-family: var(--bs-font-monospace); font-size: 0.85rem; padding: 10px 12px; border: 1px solid var(--bs-border-color); border-radius: 8px; background: var(--bs-body-bg); color: var(--bs-body-color); resize: vertical; outline: none;\">${escaped}</textarea>\n <div class=\"text-muted small mt-1\">\n Markdown supported. Cmd/Ctrl+B = bold · Cmd/Ctrl+I = italic · Shift+Enter continues lists · \\`\\`\\` opens a code block\n </div>\n </div>\n `;\n // Modal.dialog only mounts strings or Views — pass HTML string and find\n // the textarea after the modal renders, then wire shortcuts.\n const wireUp = async () => {\n for (let i = 0; i < 20; i++) {\n await new Promise(r => setTimeout(r, 50));\n const ta = document.querySelector('.modal.show [data-ref=\"desc-textarea\"]');\n if (ta) { this._wireMarkdownTextarea(ta); return ta; }\n }\n return null;\n };\n const wirePromise = wireUp();\n const choice = await Modal.dialog({\n title: `Ticket #${id} — Edit Description`,\n body,\n size: 'lg',\n buttons: [\n { text: 'Cancel', class: 'btn-secondary', value: null },\n {\n text: 'Save', class: 'btn-primary',\n handler: () => {\n const ta = document.querySelector('.modal.show [data-ref=\"desc-textarea\"]');\n return ta ? ta.value : null;\n }\n }\n ]\n });\n await wirePromise; // ensure cleanup of the polling\n if (choice === null || choice === undefined) return;\n await this._saveAndSync({ description: choice });\n this.render();\n }\n\n _wireMarkdownTextarea(textarea) {\n const _insert = (text, cursorOffset = text.length) => {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n textarea.setRangeText(text, start, end, 'end');\n textarea.selectionStart = textarea.selectionEnd = start + cursorOffset;\n textarea.dispatchEvent(new Event('input'));\n };\n const _wrapSelection = (wrapper) => {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const sel = textarea.value.substring(start, end);\n if (sel.startsWith(wrapper) && sel.endsWith(wrapper)) {\n textarea.setRangeText(sel.slice(wrapper.length, -wrapper.length), start, end, 'end');\n textarea.selectionStart = start;\n textarea.selectionEnd = end - wrapper.length * 2;\n } else {\n textarea.setRangeText(wrapper + sel + wrapper, start, end, 'end');\n textarea.selectionStart = start + wrapper.length;\n textarea.selectionEnd = end + wrapper.length;\n }\n };\n const _lineAt = (pos) => {\n const before = textarea.value.substring(0, pos);\n const lineStart = before.lastIndexOf('\\n') + 1;\n return { start: lineStart, text: textarea.value.substring(lineStart, pos) };\n };\n const _inCodeFence = (pos) => {\n const before = textarea.value.substring(0, pos);\n const fences = (before.match(/^```/gm) || []).length;\n return fences % 2 === 1;\n };\n textarea.addEventListener('keydown', (e) => {\n const mod = e.ctrlKey || e.metaKey;\n // Shift+Enter → auto-continue list bullets (plain Enter saves the form,\n // so list continuation moves to Shift+Enter here too)\n if (e.key === 'Enter' && e.shiftKey) {\n const { start, text } = _lineAt(textarea.selectionStart);\n const match = text.match(/^(\\s*)([-*]|\\d+\\.)\\s/);\n if (match) {\n e.preventDefault();\n const indent = match[1];\n const bullet = match[2];\n if (text.trim() === bullet) {\n textarea.setRangeText('', start, textarea.selectionStart, 'end');\n } else {\n const next = /^\\d+\\./.test(bullet) ? `${parseInt(bullet) + 1}.` : bullet;\n _insert(`\\n${indent}${next} `);\n }\n return;\n }\n }\n if (e.key === '`' && !mod) {\n const pos = textarea.selectionStart;\n const before = textarea.value.substring(0, pos);\n if (before.endsWith('``') && !_inCodeFence(pos - 2)) {\n e.preventDefault();\n _insert('`\\n\\n```', 2);\n return;\n }\n }\n if (e.key === 'Tab' && _inCodeFence(textarea.selectionStart)) {\n e.preventDefault();\n if (e.shiftKey) {\n const { start, text } = _lineAt(textarea.selectionStart);\n const stripped = text.replace(/^ {1,2}/, '');\n textarea.setRangeText(stripped, start, start + text.length, 'end');\n textarea.selectionStart = textarea.selectionEnd = start + stripped.length;\n } else {\n _insert(' ');\n }\n return;\n }\n if (mod && e.key === 'b') { e.preventDefault(); _wrapSelection('**'); return; }\n if (mod && e.key === 'i') { e.preventDefault(); _wrapSelection('*'); return; }\n });\n const PAIRS = { '(': ')', '[': ']', '\"': '\"' };\n textarea.addEventListener('keydown', (e) => {\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n const close = PAIRS[e.key];\n if (!close) return;\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n if (start !== end) {\n e.preventDefault();\n const sel = textarea.value.substring(start, end);\n textarea.setRangeText(e.key + sel + close, start, end, 'end');\n textarea.selectionStart = start + 1;\n textarea.selectionEnd = end + 1;\n } else {\n e.preventDefault();\n _insert(e.key + close, 1);\n }\n });\n }\n\n async onActionViewIncident() {\n const incident = this.model.get('incident');\n if (incident?.id) {\n Modal.showModel(new Incident({ id: incident.id }));\n }\n }\n\n async onActionEditTicket() {\n const resp = await Modal.modelForm({\n title: `Edit Ticket #${this.model.get('id')}`,\n model: this.model,\n size: 'lg',\n fields: TicketForms.edit.fields\n });\n if (resp) this.render();\n }\n\n async onActionRefreshNotes() {\n await this.chatView.refresh();\n await this._afterChatRefresh();\n this.getApp()?.toast?.success('Notes refreshed');\n }\n\n async onActionAskAi() {\n await openAssistantChat(this, 'incident.Ticket');\n }\n\n async _showInlineSelect(items, event) {\n return new Promise((resolve) => {\n let picked = false;\n const menu = new ContextMenu({\n config: {\n // Each item needs a unique action — ContextMenu identifies the\n // clicked item by data-item-action, and find() returns the FIRST\n // match, so reusing one action name maps every click to item[0].\n items: items.map((item, i) => ({\n label: item.label,\n action: `pick-${i}`,\n class: item.active ? 'fw-bold' : '',\n handler: () => {\n picked = true;\n this.removeChild(menu);\n resolve(item.value);\n }\n }))\n }\n });\n const origClose = menu.closeDropdown.bind(menu);\n menu.closeDropdown = () => {\n origClose();\n if (!picked) {\n this.removeChild(menu);\n resolve(null);\n }\n };\n this.addChild(menu);\n menu.openAt(event.clientX, event.clientY);\n });\n }\n\n async _afterChatRefresh() {\n // ChatView.refresh() doesn't await each message view's render — its inner\n // _renderChildren calls messageView.render(false) without await. Wait one\n // microtask so the message-item DOM is in place before we touch it.\n await new Promise(r => setTimeout(r, 0));\n await this._loadActionCards();\n this._addEditButtons();\n this._setupCollapsible();\n }\n\n async setTicket(ticket) {\n this.model = ticket;\n this.adapter = new TicketNoteAdapter(ticket.get('id'));\n this.chatView.adapter = this.adapter;\n this.chatView.clearMessages();\n await this.render();\n await this.chatView.refresh();\n await this._afterChatRefresh();\n }\n}\n\nexport default TicketPanelView;\n"],"names":["esc","s","String","replace","ALLOWED_REFS","Set","HANDLER_COLORS","dot","label","ActionCardView","View","constructor","options","super","className","this","action","noteId","ticketStatus","isResolved","resolved","isContext","type","isClosed","handlerConfig","handler","onBeforeRender","cfg","dotClass","_buildContextTemplate","_buildResolvedTemplate","_buildPendingTemplate","onActionToggleCompact","acEl","element","querySelector","classList","toggle","refItems","references","filter","ref","has","model","map","split","pop","pk","join","template","resolution","badgeClass","badgeText","target","context","refHtml","detail","disabledAttr","onActionOpenRef","_event","el","modelRef","dataset","test","app","getApp","ModelClass","getModelByRef","VIEW_CLASS","Modal","showModel","id","onActionApprove","emit","onActionDeny","STATUS_OPTIONS","STATUS_PILL","new","open","in_progress","pending","qa","closed","ignored","PRIORITY_OPTIONS","value","TicketPanelView","Ticket","data","status","get","statusPill","statusLabel","priorityLabel","assigneeName","categoryLabel","groupName","hasDescription","hasIncident","priorityColor","onInit","adapter","TicketNoteAdapter","chatView","ChatView","containerId","theme","currentUserId","_getCurrentUserId","showInput","addChild","menu","ContextMenu","config","icon","btnClass","items","onAfterRender","_setupTextarea","_setupDragDrop","Promise","r","setTimeout","_loadActionCards","_addEditButtons","_setupCollapsible","_cleanupActionCards","messages","length","msg","msgView","messageViews","card","on","_handleActionResponse","render","after","children","child","removeChild","actionNote","showLoading","addActionResponse","fetch","refresh","hideLoading","activeUser","getActiveUser","onActionClose","onActionSendNote","textarea","text","trim","files","_stagedFiles","style","height","_renderAttachments","addNote","_afterChatRefresh","_insert","cursorOffset","start","selectionStart","end","selectionEnd","setRangeText","dispatchEvent","Event","scrollTop","scrollHeight","_wrapSelection","wrapper","sel","substring","startsWith","endsWith","slice","_lineAt","pos","lineStart","lastIndexOf","_inCodeFence","match","addEventListener","e","mod","ctrlKey","metaKey","key","shiftKey","preventDefault","indent","bullet","next","parseInt","stripped","PAIRS","altKey","close","Math","min","zone","counter","add","remove","async","Array","from","dataTransfer","File","FileModel","resolve","then","require","n","Files","file","chipId","Date","now","random","_addAttachChip","name","fileModel","upload","showToast","push","_updateAttachChip","err","console","error","_removeAttachChip","toast","uploading","container","chip","document","createElement","innerHTML","_escapeHtml","appendChild","f","str","div","textContent","userId","msgById","Map","m","forEach","view","msgId","item","querySelectorAll","b","author","btn","title","stopPropagation","_editNote","body","setProperty","MAX","collapsed","metaJson","Object","keys","_metadata","JSON","stringify","form","size","fields","tabs","required","cols","rows","_rawContent","content","help","TicketNote","Tickets","note","payload","metadata_json","metadata","parse","save","_saveAndSync","patch","onActionChangeStatus","event","active","result","_showInlineSelect","onActionChangePriority","p","priority","onActionChangeAssignee","Collection","UserList","labelField","valueField","assignee","onActionChangeCategory","entries","TicketCategories","category","onActionChangeGroup","GroupList","group","onActionShowDescription","raw","_editDescription","rendered","html","resp","rest","post","markdown","_e","dialog","buttons","class","wirePromise","i","ta","_wireMarkdownTextarea","wireUp","choice","description","onActionViewIncident","incident","Incident","onActionEditTicket","modelForm","TicketForms","edit","onActionRefreshNotes","success","onActionAskAi","openAssistantChat","picked","origClose","closeDropdown","bind","openAt","clientX","clientY","setTicket","ticket","clearMessages"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{V as e}from"./View-BxlKR1IW.js";import{C as t}from"./ChatView-D2WOSxPu.js";import{C as n}from"./ContextMenu-T3yDdsIe.js";import{M as a}from"./Modal-KnJhNZ1E.js";import{T as i,b as s,I as o,c as r}from"./admin-models-DLtFboxy.js";import{U as l,G as c}from"./User-BM76Ughk.js";import{T as d,o as p}from"./admin-D6gHOojO.js";import{r as h}from"./Collection-DNmr743A.js";function g(e){return e?String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):""}const m=/* @__PURE__ */new Set(["incident.RuleSet","incident.Incident","incident.Event","incident.Ticket","account.GeoLocatedIP"]),b={"incident.rule_approval":{dot:"accent",label:"Rule"},"incident.block_confirm":{dot:"red",label:"Block"},"incident.rule_update":{dot:"green",label:"Update"},"incident.escalate":{dot:"amber",label:"Escalate"}};class ActionCardView extends e{constructor(e={}){super({className:"action-card-view",...e}),this.action=e.action,this.noteId=e.noteId,this.ticketStatus=e.ticketStatus}get isResolved(){return!!this.action?.resolved}get isContext(){return"context"===this.action?.type}get isClosed(){return"closed"===this.ticketStatus||"resolved"===this.ticketStatus}get handlerConfig(){return b[this.action?.handler]||{dot:"accent",label:"Action"}}onBeforeRender(){const e=this.handlerConfig;this.dotClass=e.dot,this.isContext?this._buildContextTemplate():this.isResolved?this._buildResolvedTemplate():this._buildPendingTemplate()}onActionToggleCompact(){const e=this.element?.querySelector(".ac.resolved");e&&e.classList.toggle("compact")}_buildContextTemplate(){const e=(this.action.references||[]).filter(e=>m.has(e.model)).map(e=>{const t=g(e.label)||`${g(e.model.split(".").pop())} #${g(e.pk)}`;return`<span class="ac-ref" data-action="open-ref" data-model="${g(e.model)}" data-pk="${g(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${t}</span>`}).join("");this.template=`\n <div class="ac ac-context">\n <div class="ac-top">\n <span class="ac-dot context"></span>\n <span class="ac-label">Referenced models</span>\n </div>\n <div class="ac-detail">${e}</div>\n </div>\n `}_buildResolvedTemplate(){const e=this.action.resolution||"approved",t="approved"===e?"approved":"denied",n="approved"===e?"Approved":"Denied",a=this.action.context?.target;let i="";if(a&&m.has(a.model)){const e=g(this.action.context.label)||`${g(a.model.split(".").pop())} #${g(a.pk)}`;i=`<div class="ac-detail"><span class="ac-ref" data-action="open-ref" data-model="${g(a.model)}" data-pk="${g(a.pk)}"><i class="bi bi-box-arrow-up-right"></i>${e}</span></div>`}this.template=`\n <div class="ac resolved compact">\n <div class="ac-top" data-action="toggle-compact" title="Click to toggle">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${g(this.action.label)||"Action"}</span>\n <span class="ac-badge ${t}">${n}</span>\n <i class="bi bi-chevron-down ac-chevron"></i>\n </div>\n ${i}\n </div>\n `}_buildPendingTemplate(){const e=this.action.context?.target;let t="";if(e&&m.has(e.model)){const n=g(this.action.context.label)||`${g(e.model.split(".").pop())} #${g(e.pk)}`;t=`<br><span class="ac-ref" data-action="open-ref" data-model="${g(e.model)}" data-pk="${g(e.pk)}"><i class="bi bi-box-arrow-up-right"></i>${n}</span>`}const n=g(this.action.context?.detail),a=this.isClosed?" disabled":"";this.template=`\n <div class="ac">\n <div class="ac-top">\n <span class="ac-dot ${this.dotClass}"></span>\n <span class="ac-label">${g(this.action.label)||"Action"}</span>\n </div>\n <div class="ac-detail">${n}${t}</div>\n <div class="ac-foot">\n <button class="btn-approve" data-action="approve"${a}>Approve</button>\n <button class="btn-deny" data-action="deny"${a}>Deny</button>\n </div>\n </div>\n `}async onActionOpenRef(e,t){const n=t.dataset.model,i=t.dataset.pk;if(!m.has(n)||!/^\d+$/.test(i))return;const s=this.getApp(),o=s?.getModelByRef(n);o?.VIEW_CLASS&&a.showModel(new o({id:i}))}onActionApprove(){this.emit("action:respond",{noteId:this.noteId,action:"approve",handler:this.action.handler,context:this.action.context})}onActionDeny(){this.emit("action:respond",{noteId:this.noteId,action:"deny",handler:this.action.handler,context:this.action.context})}}const v=["new","open","in_progress","pending","resolved","qa","closed","ignored"],u={new:"pill-new",open:"pill-open",in_progress:"pill-prog",pending:"pill-prog",resolved:"pill-resolved",qa:"pill-open",closed:"pill-closed",ignored:"pill-closed"},f=[{value:10,label:"P10 — Critical"},{value:9,label:"P9 — Severe"},{value:8,label:"P8 — High"},{value:7,label:"P7 — Elevated"},{value:5,label:"P5 — Normal"},{value:3,label:"P3 — Low"},{value:1,label:"P1 — Info"}];class TicketPanelView extends e{constructor(e={}){super({className:"ticket-panel-view",...e}),this.model=e.model||new i(e.data||{})}onBeforeRender(){const e=this.model.get("status")||"new";this.statusPill=u[e]||"pill-closed",this.statusLabel=e.replace(/_/g," "),this.priorityLabel=`P${this.model.get("priority")||5}`,this.assigneeName=this.model.get("assignee.display_name")||this.model.get("assignee")||"Unassigned",this.categoryLabel=this.model.get("category")||"ticket",this.groupName=this.model.get("group.name")||this.model.get("group")||"None",this.hasDescription=!!this.model.get("description"),this.hasIncident=!(!this.model.get("incident")||"object"!=typeof this.model.get("incident")||!this.model.get("incident").id),this.priorityColor=(this.model.get("priority")||5)>=7?"var(--bs-danger)":"var(--bs-secondary-color)",this.template=`\n <style>\n .ticket-panel-view { height: 100%; display: flex; flex-direction: column; }\n .tp-header { padding: 10px 16px 6px; border-bottom: 1px solid var(--bs-border-color-translucent); flex-shrink: 0; }\n .tp-title-row { display: flex; align-items: flex-start; gap: 6px; }\n .tp-title { font-size: 0.88rem; font-weight: 600; color: var(--bs-emphasis-color); line-height: 1.3; flex: 1; min-width: 0; cursor: ${this.hasDescription?"pointer":"default"}; transition: color 0.12s; }\n ${this.hasDescription?".tp-title:hover { color: var(--bs-primary); }":""}\n .tp-title i { font-size: 0.6rem; vertical-align: middle; margin-left: 3px; opacity: 0; transition: opacity 0.12s; }\n .tp-title:hover i { opacity: 0.6; }\n .tp-btns { display: flex; gap: 2px; align-items: center; flex-shrink: 0; }\n .tp-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: var(--bs-secondary-color); border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.12s; }\n .tp-btn:hover { background: var(--bs-tertiary-bg); color: var(--bs-body-color); }\n .tp-sub { display: flex; align-items: center; gap: 6px; margin-top: 3px; }\n .tp-id { font-family: var(--bs-font-monospace); font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 0.66rem; font-weight: 500; cursor: pointer; transition: filter 0.12s; }\n .tp-pill:hover { filter: brightness(0.9); }\n .tp-pill-new { background: rgba(var(--bs-info-rgb), 0.1); color: var(--bs-info); }\n .tp-pill-open { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-pill-prog { background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); }\n .tp-pill-closed { background: var(--bs-secondary-bg); color: var(--bs-secondary-color); }\n .tp-pill-resolved { background: rgba(var(--bs-success-rgb), 0.1); color: var(--bs-success); }\n .tp-time { font-size: 0.66rem; color: var(--bs-secondary-color); }\n .tp-desc-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.7rem; color: var(--bs-primary); cursor: pointer; padding: 2px 8px; border-radius: 5px; background: rgba(var(--bs-primary-rgb), 0.08); transition: all 0.12s; }\n .tp-desc-chip:hover { background: rgba(var(--bs-primary-rgb), 0.16); }\n .tp-desc-chip i { font-size: 0.7rem; }\n .tp-meta { display: flex; align-items: center; gap: 3px; margin-top: 5px; }\n .tp-fields { display: inline-flex; align-items: center; gap: 2px; flex-wrap: wrap; }\n .tp-field { display: inline-flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--bs-secondary-color); padding: 2px 7px; border-radius: 5px; cursor: pointer; transition: all 0.12s; border: 1px solid transparent; }\n .tp-field:hover { background: var(--bs-tertiary-bg); border-color: var(--bs-border-color); color: var(--bs-body-color); }\n .tp-field i { font-size: 0.68rem; }\n .tp-field .caret { font-size: 0.55rem; opacity: 0; transition: opacity 0.12s; margin-left: -1px; }\n .tp-field:hover .caret { opacity: 0.6; }\n .tp-sep { color: var(--bs-secondary-color); font-size: 0.6rem; margin: 0 1px; user-select: none; }\n\n .tp-linked { display: flex; align-items: center; gap: 6px; padding: 7px 16px; border-bottom: 1px solid var(--bs-border-color-translucent); font-size: 0.75rem; color: var(--bs-secondary-color); flex-shrink: 0; }\n .tp-linked i { color: var(--bs-warning); font-size: 0.72rem; }\n .tp-linked a { color: var(--bs-primary); text-decoration: none; font-weight: 500; }\n .tp-linked a:hover { text-decoration: underline; }\n .tp-linked .lpill { font-size: 0.62rem; padding: 0 5px; border-radius: 3px; background: rgba(var(--bs-warning-rgb), 0.1); color: var(--bs-warning); font-weight: 500; }\n\n .tp-conv { flex: 1; overflow-y: auto; min-height: 0; }\n .tp-conv .chat-container { border: none; border-radius: 0; }\n .tp-conv .chat-messages { padding: 6px 0; }\n .tp-conv .chat-input-wrapper { display: none; }\n .tp-conv .message-item { position: relative; }\n .tp-conv .tp-edit-btn { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; display: none; align-items: center; justify-content: center; border: none; background: var(--bs-tertiary-bg); color: var(--bs-secondary-color); border-radius: 5px; cursor: pointer; font-size: 0.72rem; transition: all 0.12s; }\n .tp-conv .tp-edit-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }\n .tp-conv .message-item:hover .tp-edit-btn { display: flex; }\n .tp-conv .message-text { white-space: normal; }\n .tp-conv .message-text p { margin-bottom: 6px; }\n .tp-conv .message-text p:last-child { margin-bottom: 0; }\n .tp-conv .message-text h1,\n .tp-conv .message-text h2,\n .tp-conv .message-text h3,\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-weight: 600; margin-top: 10px; margin-bottom: 4px; line-height: 1.3; }\n .tp-conv .message-text h1 { font-size: 1.05rem; }\n .tp-conv .message-text h2 { font-size: 1rem; }\n .tp-conv .message-text h3 { font-size: 0.95rem; }\n .tp-conv .message-text h4,\n .tp-conv .message-text h5,\n .tp-conv .message-text h6 { font-size: 0.88rem; }\n .tp-conv .message-text h1:first-child,\n .tp-conv .message-text h2:first-child,\n .tp-conv .message-text h3:first-child { margin-top: 0; }\n .tp-conv .message-text hr { margin: 4px 0; opacity: 0.15; }\n .tp-conv .message-text ul,\n .tp-conv .message-text ol { padding-left: 20px; margin-top: 2px; margin-bottom: 6px; }\n .tp-conv .message-text li { margin-bottom: 2px; }\n .tp-conv .message-text pre { background: var(--bs-tertiary-bg); border-radius: 6px; padding: 10px 14px; margin: 8px 0; font-size: 0.8rem; overflow-x: auto; }\n .tp-conv .message-text code { font-size: 0.85em; padding: 1px 5px; background: var(--bs-tertiary-bg); border-radius: 4px; }\n .tp-conv .message-text pre code { padding: 0; background: none; }\n .tp-conv .message-text table { width: 100%; margin: 8px 0; border-collapse: collapse; font-size: 0.82rem; }\n .tp-conv .message-text th,\n .tp-conv .message-text td { padding: 5px 8px; border: 1px solid var(--bs-border-color); text-align: left; }\n .tp-conv .message-text th { background: var(--bs-tertiary-bg); font-weight: 600; }\n .tp-conv .message-text blockquote { margin: 6px 0; padding: 4px 12px; border-left: 3px solid var(--bs-border-color); color: var(--bs-secondary-color); }\n\n .tp-action-area { padding: 0; }\n\n .tp-input { border-top: 1px solid var(--bs-border-color-translucent); padding: 10px 16px; flex-shrink: 0; }\n .tp-input-wrap { display: flex; align-items: flex-end; gap: 8px; }\n .tp-input textarea { flex: 1; font-size: 0.8rem; border: 1px solid var(--bs-border-color); border-radius: 8px; padding: 7px 10px; resize: none; background: var(--bs-body-bg); color: var(--bs-body-color); outline: none; transition: border-color 0.15s; font-family: inherit; max-height: 160px; overflow-y: auto; }\n .tp-input textarea:focus { border-color: var(--bs-primary); }\n .tp-input textarea::placeholder { color: var(--bs-secondary-color); }\n .tp-send-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: var(--bs-primary); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 0.85rem; transition: filter 0.12s; }\n .tp-send-btn:hover { filter: brightness(1.1); }\n .tp-input-hint { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 0.7rem; color: var(--bs-secondary-color); }\n .tp-input-hint i { font-size: 0.75rem; }\n .tp-input.tp-dragover { background: rgba(var(--bs-primary-rgb), 0.04); }\n .tp-input.tp-dragover textarea { border-color: var(--bs-primary); border-style: dashed; }\n .tp-attachments { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0; }\n .tp-attach-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: var(--bs-tertiary-bg); border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.72rem; color: var(--bs-body-color); }\n .tp-attach-chip i { font-size: 0.68rem; }\n .tp-attach-chip .remove { cursor: pointer; color: var(--bs-secondary-color); margin-left: 2px; }\n .tp-attach-chip .remove:hover { color: var(--bs-danger); }\n .tp-attach-chip.uploading { opacity: 0.6; }\n .tp-conv .message-content.tp-collapsed { max-height: var(--tp-collapse-h, 52px); overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }\n .tp-conv .message-item:has(.tp-show-more) { flex-wrap: wrap; }\n .tp-show-more { background: none; border: none; padding: 2px 0; margin: -2px 0 0 48px; font-size: 0.7rem; color: var(--bs-secondary-color); cursor: pointer; text-align: left; width: calc(100% - 48px); }\n .tp-show-more:hover { color: var(--bs-body-color); }\n </style>\n\n <div class="tp-header">\n <div class="tp-title-row">\n <div class="tp-title" ${this.hasDescription?'data-action="show-description"':""} ${this.hasDescription?'title="View full description"':""}>\n {{model.title}}${this.hasDescription?' <i class="bi bi-arrow-up-right-square"></i>':""}\n </div>\n <div class="tp-btns">\n <div data-container="panel-menu"></div>\n </div>\n </div>\n <div class="tp-sub">\n <span class="tp-id">#{{model.id}}</span>\n <span class="tp-pill tp-${this.statusPill}" data-action="change-status" title="Change status">{{statusLabel}} <i class="bi bi-chevron-down" style="font-size:0.5rem;"></i></span>\n <span class="tp-time"><i class="bi bi-clock"></i> {{model.created|relative}}</span>\n <span class="tp-desc-chip" data-action="show-description" title="${this.hasDescription?"View / edit description":"Add description"}"><i class="bi bi-file-text"></i> ${this.hasDescription?"Description":"Add description"}</span>\n </div>\n <div class="tp-meta">\n <div class="tp-fields">\n <span class="tp-field" data-action="change-priority" title="Change priority">\n <i class="bi bi-flag-fill" style="color:${this.priorityColor};"></i>{{priorityLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-assignee" title="Assign">\n <i class="bi bi-person"></i>{{assigneeName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-category" title="Change category">\n <i class="bi bi-tag"></i>{{categoryLabel}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n <span class="tp-sep">&middot;</span>\n <span class="tp-field" data-action="change-group" title="Change group">\n <i class="bi bi-people"></i>{{groupName}}\n <i class="bi bi-chevron-down caret"></i>\n </span>\n </div>\n </div>\n </div>\n\n {{#hasIncident|bool}}\n <div class="tp-linked">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <a href="#" data-action="view-incident">Incident #{{model.incident.id}}</a>\n {{#model.incident.status}}<span class="lpill">{{model.incident.status}}</span>{{/model.incident.status}}\n {{#model.incident.event_count}}<span>&middot; {{model.incident.event_count}} events</span>{{/model.incident.event_count}}\n </div>\n {{/hasIncident|bool}}\n\n <div class="tp-conv">\n <div data-container="chat-area"></div>\n </div>\n\n <div class="tp-input" data-ref="drop-zone">\n <div class="tp-attachments" data-ref="attachments"></div>\n <div class="tp-input-wrap">\n <textarea rows="2" placeholder="Add a note..." data-ref="note-textarea"></textarea>\n <button class="tp-send-btn" data-action="send-note" title="Send"><i class="bi bi-arrow-up"></i></button>\n </div>\n <div class="tp-input-hint">\n <i class="bi bi-paperclip"></i> Drag &amp; drop files to attach\n </div>\n </div>\n `}async onInit(){this.adapter=new d(this.model.get("id")),this.chatView=new t({containerId:"chat-area",adapter:this.adapter,theme:"compact",currentUserId:this._getCurrentUserId(),showInput:!1}),this.addChild(this.chatView);const e=new n({containerId:"panel-menu",className:"context-menu-view header-menu-absolute",context:this.model,config:{icon:"bi-three-dots",btnClass:"tp-btn",items:[{label:"Ask AI",action:"ask-ai",icon:"bi-robot"},{type:"divider"},{label:"Edit Ticket",action:"edit-ticket",icon:"bi-pencil"},{label:"Refresh Notes",action:"refresh-notes",icon:"bi-arrow-clockwise"},{type:"divider"},{label:"Close Window",action:"close",icon:"bi-x-lg"}]}});this.addChild(e)}async onAfterRender(){this._setupTextarea(),this._setupDragDrop(),await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async _loadActionCards(){this._cleanupActionCards();const e=this.chatView.messages||[];if(e.length)for(const t of e){if(!t.action||"object"!=typeof t.action)continue;const e=this.chatView.messageViews.get(t.id);if(!e?.element)continue;const n=new ActionCardView({action:t.action,noteId:t.id,ticketStatus:this.model.get("status")});"context"===t.action.type||t.action.resolved||n.on("action:respond",e=>this._handleActionResponse(e)),this.addChild(n),await n.render(),e.element.after(n.element)}}_cleanupActionCards(){for(const e in this.children){const t=this.children[e];t instanceof ActionCardView&&this.removeChild(t)}}async _handleActionResponse(e){const t={action:{handler:e.handler,context:e.context}},n=this.getApp();n?.showLoading();try{await this.adapter.addActionResponse(t,e.action),await this.model.fetch(),await this.chatView.refresh(),this.render()}finally{n?.hideLoading()}}_getCurrentUserId(){const e=this.getApp();return e?.activeUser?.id||e?.getActiveUser?.()?.id||null}onActionClose(){this.emit("panel:close")}async onActionSendNote(){const e=this.element?.querySelector('[data-ref="note-textarea"]'),t=e?.value?.trim(),n=this._stagedFiles||[];(t||n.length)&&(e.value="",e.style.height="",this._stagedFiles=[],this._renderAttachments(),await this.adapter.addNote({text:t||"",files:n}),await this.chatView.refresh(),await this._afterChatRefresh())}_setupTextarea(){const e=this.element?.querySelector('[data-ref="note-textarea"]');if(!e)return;const t=(t,n=t.length)=>{const a=e.selectionStart,i=e.selectionEnd;e.setRangeText(t,a,i,"end"),e.selectionStart=e.selectionEnd=a+n,e.dispatchEvent(new Event("input")),e.scrollTop=e.scrollHeight},n=t=>{const n=e.selectionStart,a=e.selectionEnd,i=e.value.substring(n,a);i.startsWith(t)&&i.endsWith(t)?(e.setRangeText(i.slice(t.length,-t.length),n,a,"end"),e.selectionStart=n,e.selectionEnd=a-2*t.length):(e.setRangeText(t+i+t,n,a,"end"),e.selectionStart=n+t.length,e.selectionEnd=a+t.length)},a=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},i=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&!s.shiftKey&&!o)return s.preventDefault(),void this.onActionSendNote();if("Enter"===s.key&&s.shiftKey){const{start:n,text:i}=a(e.selectionStart),o=i.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const a=o[1],r=o[2];if(i.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${a}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!i(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!i(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=a(e.selectionStart),i=n.replace(/^ {1,2}/,"");e.setRangeText(i,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+i.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const a=s[n.key];if(!a)return;const i=e.selectionStart,o=e.selectionEnd;if(i!==o){n.preventDefault();const t=e.value.substring(i,o);e.setRangeText(n.key+t+a,i,o,"end"),e.selectionStart=i+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+a,1)}),e.addEventListener("input",()=>{e.style.height="",e.style.height=Math.min(e.scrollHeight,160)+"px"})}_setupDragDrop(){const e=this.element?.querySelector('[data-ref="drop-zone"]');if(!e)return;this._stagedFiles=this._stagedFiles||[];let t=0;e.addEventListener("dragenter",n=>{n.preventDefault(),t++,e.classList.add("tp-dragover")}),e.addEventListener("dragleave",()=>{t--,t<=0&&(t=0,e.classList.remove("tp-dragover"))}),e.addEventListener("dragover",e=>e.preventDefault()),e.addEventListener("drop",async n=>{n.preventDefault(),t=0,e.classList.remove("tp-dragover");const a=Array.from(n.dataTransfer?.files||[]);if(!a.length)return;const{File:i}=await import("./Modal-KnJhNZ1E.js").then(e=>e.h);for(const e of a){const t=Date.now()+Math.random();this._addAttachChip(t,e.name,!0);try{const n=new i;await n.upload({file:e,showToast:!1}),this._stagedFiles.push(n),this._updateAttachChip(t,n.get("name")||e.name,n)}catch(s){console.error("File upload failed:",s),this._removeAttachChip(t),this.getApp()?.toast?.error?.("Upload failed: "+e.name)}}})}_addAttachChip(e,t,n){const a=this.element?.querySelector('[data-ref="attachments"]');if(!a)return;const i=document.createElement("span");i.className="tp-attach-chip"+(n?" uploading":""),i.dataset.chipId=e,i.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}`+(n?"":'<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>'),n||i.querySelector(".remove").addEventListener("click",()=>{this._removeAttachChip(e)}),a.appendChild(i)}_updateAttachChip(e,t,n){const a=this.element?.querySelector(`[data-chip-id="${e}"]`);a&&(a.classList.remove("uploading"),a.innerHTML=`<i class="bi bi-paperclip"></i>${this._escapeHtml(t)}<span class="remove" data-remove="1"><i class="bi bi-x"></i></span>`,a.querySelector(".remove").addEventListener("click",()=>{this._stagedFiles=(this._stagedFiles||[]).filter(e=>e!==n),a.remove()}))}_removeAttachChip(e){const t=this.element?.querySelector(`[data-chip-id="${e}"]`);t&&t.remove()}_renderAttachments(){const e=this.element?.querySelector('[data-ref="attachments"]');e&&(e.innerHTML="")}_escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}_addEditButtons(){const e=this._getCurrentUserId();if(!e||!this.chatView?.messageViews)return;const t=new Map((this.chatView.messages||[]).map(e=>[e.id,e]));this.chatView.messageViews.forEach((n,a)=>{const i=t.get(a),s=n?.element?.querySelector(".message-item");if(!s)return;if(s.querySelectorAll(".tp-edit-btn").forEach(e=>e.remove()),!i||i.author?.id!==e)return;const o=document.createElement("button");o.className="tp-edit-btn",o.title="Edit note",o.innerHTML='<i class="bi bi-pencil"></i>',o.addEventListener("click",e=>{e.stopPropagation(),this._editNote(i)}),s.appendChild(o)})}_setupCollapsible(){const e=this.element?.querySelector('[data-container="chat-area"]');e&&(e.querySelectorAll(".tp-show-more").forEach(e=>e.remove()),e.querySelectorAll(".tp-collapsed").forEach(e=>e.classList.remove("tp-collapsed")),setTimeout(()=>{e.querySelectorAll(".message-content").forEach(e=>{if(e.scrollHeight<=52)return;e.classList.add("tp-collapsed"),e.style.setProperty("--tp-collapse-h","52px");const t=document.createElement("button");t.className="tp-show-more",t.textContent="Show more",t.addEventListener("click",()=>{const n=e.classList.toggle("tp-collapsed");t.textContent=n?"Show more":"Show less"}),e.after(t)})},150))}async _editNote(e){const t=Object.keys(e._metadata||{}).length?JSON.stringify(e._metadata,null,2):"",n=await a.form({title:"Edit Note",icon:"bi-pencil",size:"lg",fields:[{type:"tabset",tabs:[{label:"Note",fields:[{name:"note",type:"textarea",label:"Note",required:!0,cols:12,rows:8,value:e._rawContent||e.content}]},{label:"Metadata",fields:[{name:"metadata_json",type:"json",label:"Metadata (JSON)",cols:12,rows:10,value:t,help:'Action metadata — e.g. { "action": { "handler": "incident.rule_approval", "label": "...", "context": { ... } } }'}]}]}]});if(!n)return;const{TicketNote:i}=await import("./admin-models-DLtFboxy.js").then(e=>e.aQ),s=new i({id:e.id}),o={note:n.note};n.metadata_json&&(o.metadata="string"==typeof n.metadata_json?JSON.parse(n.metadata_json):n.metadata_json),await s.save(o),await this.chatView.refresh(),await this._afterChatRefresh()}async _saveAndSync(e){await this.model.save(e),await this.model.fetch(),this.chatView&&(await this.chatView.refresh(),await this._afterChatRefresh())}async onActionChangeStatus(e){const t=v.map(e=>({label:e.replace(/_/g," "),value:e,active:e===this.model.get("status")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({status:n}),this.render())}async onActionChangePriority(e){const t=f.map(e=>({label:e.label,value:e.value,active:e.value===this.model.get("priority")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({priority:parseInt(n)}),this.render())}async onActionChangeAssignee(){const e=await a.form({title:"Assign User",icon:"bi-person-plus",size:"sm",fields:[{name:"assignee",type:"collection",label:"User",Collection:l,labelField:"display_name",valueField:"id",required:!0,cols:12,value:this.model.get("assignee")}]});e&&(await this._saveAndSync({assignee:e.assignee}),this.render())}async onActionChangeCategory(e){const t=Object.entries(s).map(([e,t])=>({label:t,value:e,active:e===this.model.get("category")})),n=await this._showInlineSelect(t,e);n&&(await this._saveAndSync({category:n}),this.render())}async onActionChangeGroup(){const e=await a.form({title:"Change Group",icon:"bi-people",size:"sm",fields:[{name:"group",type:"collection",label:"Group",Collection:c,labelField:"name",valueField:"id",required:!1,cols:12,value:this.model.get("group")}]});e&&(await this._saveAndSync({group:e.group}),this.render())}async onActionShowDescription(){const e=this.model.get("description")||"";if(!e)return this._editDescription();let t=!1,n="";try{const a=await h.post("/api/docit/render",{markdown:e});n=a?.data?.data?.html||a?.data?.html||"",t=!!n}catch(i){}if(!t){const t=document.createElement("div");t.textContent=e,n=`<pre style="white-space:pre-wrap;">${t.innerHTML}</pre>`}"edit"===await a.dialog({title:`Ticket #${this.model.get("id")} — Description`,body:`<div style="font-size:0.85rem; line-height:1.65;">${n}</div>`,size:"lg",buttons:[{text:"Edit",class:"btn-primary",value:"edit"},{text:"Close",class:"btn-secondary",value:"close"}]})&&await this._editDescription()}async _editDescription(){const e=this.model.get("id"),t=`\n <div class="tp-desc-edit">\n <textarea data-ref="desc-textarea" rows="16" placeholder="Description (markdown supported)..."\n style="width:100%; font-family: var(--bs-font-monospace); font-size: 0.85rem; padding: 10px 12px; border: 1px solid var(--bs-border-color); border-radius: 8px; background: var(--bs-body-bg); color: var(--bs-body-color); resize: vertical; outline: none;">${(this.model.get("description")||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}</textarea>\n <div class="text-muted small mt-1">\n Markdown supported. Cmd/Ctrl+B = bold · Cmd/Ctrl+I = italic · Shift+Enter continues lists · \`\`\` opens a code block\n </div>\n </div>\n `,n=(async()=>{for(let e=0;e<20;e++){await new Promise(e=>setTimeout(e,50));const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');if(e)return this._wireMarkdownTextarea(e),e}return null})(),i=await a.dialog({title:`Ticket #${e} — Edit Description`,body:t,size:"lg",buttons:[{text:"Cancel",class:"btn-secondary",value:null},{text:"Save",class:"btn-primary",handler:()=>{const e=document.querySelector('.modal.show [data-ref="desc-textarea"]');return e?e.value:null}}]});await n,null!=i&&(await this._saveAndSync({description:i}),this.render())}_wireMarkdownTextarea(e){const t=(t,n=t.length)=>{const a=e.selectionStart,i=e.selectionEnd;e.setRangeText(t,a,i,"end"),e.selectionStart=e.selectionEnd=a+n,e.dispatchEvent(new Event("input"))},n=t=>{const n=e.selectionStart,a=e.selectionEnd,i=e.value.substring(n,a);i.startsWith(t)&&i.endsWith(t)?(e.setRangeText(i.slice(t.length,-t.length),n,a,"end"),e.selectionStart=n,e.selectionEnd=a-2*t.length):(e.setRangeText(t+i+t,n,a,"end"),e.selectionStart=n+t.length,e.selectionEnd=a+t.length)},a=t=>{const n=e.value.substring(0,t).lastIndexOf("\n")+1;return{start:n,text:e.value.substring(n,t)}},i=t=>(e.value.substring(0,t).match(/^```/gm)||[]).length%2==1;e.addEventListener("keydown",s=>{const o=s.ctrlKey||s.metaKey;if("Enter"===s.key&&s.shiftKey){const{start:n,text:i}=a(e.selectionStart),o=i.match(/^(\s*)([-*]|\d+\.)\s/);if(o){s.preventDefault();const a=o[1],r=o[2];if(i.trim()===r)e.setRangeText("",n,e.selectionStart,"end");else{const e=/^\d+\./.test(r)?`${parseInt(r)+1}.`:r;t(`\n${a}${e} `)}return}}if("`"===s.key&&!o){const n=e.selectionStart;if(e.value.substring(0,n).endsWith("``")&&!i(n-2))return s.preventDefault(),void t("`\n\n```",2)}if("Tab"!==s.key||!i(e.selectionStart))return o&&"b"===s.key?(s.preventDefault(),void n("**")):o&&"i"===s.key?(s.preventDefault(),void n("*")):void 0;if(s.preventDefault(),s.shiftKey){const{start:t,text:n}=a(e.selectionStart),i=n.replace(/^ {1,2}/,"");e.setRangeText(i,t,t+n.length,"end"),e.selectionStart=e.selectionEnd=t+i.length}else t(" ")});const s={"(":")","[":"]",'"':'"'};e.addEventListener("keydown",n=>{if(n.ctrlKey||n.metaKey||n.altKey)return;const a=s[n.key];if(!a)return;const i=e.selectionStart,o=e.selectionEnd;if(i!==o){n.preventDefault();const t=e.value.substring(i,o);e.setRangeText(n.key+t+a,i,o,"end"),e.selectionStart=i+1,e.selectionEnd=o+1}else n.preventDefault(),t(n.key+a,1)})}async onActionViewIncident(){const e=this.model.get("incident");e?.id&&a.showModel(new o({id:e.id}))}async onActionEditTicket(){await a.modelForm({title:`Edit Ticket #${this.model.get("id")}`,model:this.model,size:"lg",fields:r.edit.fields})&&this.render()}async onActionRefreshNotes(){await this.chatView.refresh(),await this._afterChatRefresh(),this.getApp()?.toast?.success("Notes refreshed")}async onActionAskAi(){await p(this,"incident.Ticket")}async _showInlineSelect(e,t){return new Promise(a=>{let i=!1;const s=new n({config:{items:e.map((e,t)=>({label:e.label,action:`pick-${t}`,class:e.active?"fw-bold":"",handler:()=>{i=!0,this.removeChild(s),a(e.value)}}))}}),o=s.closeDropdown.bind(s);s.closeDropdown=()=>{o(),i||(this.removeChild(s),a(null))},this.addChild(s),s.openAt(t.clientX,t.clientY)})}async _afterChatRefresh(){await new Promise(e=>setTimeout(e,0)),await this._loadActionCards(),this._addEditButtons(),this._setupCollapsible()}async setTicket(e){this.model=e,this.adapter=new d(e.get("id")),this.chatView.adapter=this.adapter,this.chatView.clearMessages(),await this.render(),await this.chatView.refresh(),await this._afterChatRefresh()}}export{TicketPanelView as default};
2
- //# sourceMappingURL=TicketPanelView-BZ8e66o1.js.map