web-mojo 2.1.219 → 2.1.265

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 (75) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +261 -59
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +3 -3
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/{ContextMenu-DjS0WgXA.js → ContextMenu-BqYO9pWG.js} +2 -2
  12. package/dist/chunks/{ContextMenu-DjS0WgXA.js.map → ContextMenu-BqYO9pWG.js.map} +1 -1
  13. package/dist/chunks/{ContextMenu-BGstYX-N.js → ContextMenu-CsMXWcoQ.js} +2 -2
  14. package/dist/chunks/{ContextMenu-BGstYX-N.js.map → ContextMenu-CsMXWcoQ.js.map} +1 -1
  15. package/dist/chunks/{DataView-Dmobu7T8.js → DataView-Cy9OMdVA.js} +2 -2
  16. package/dist/chunks/{DataView-Dmobu7T8.js.map → DataView-Cy9OMdVA.js.map} +1 -1
  17. package/dist/chunks/{DataView-LCk00Xma.js → DataView-Di2F5dQR.js} +2 -2
  18. package/dist/chunks/{DataView-LCk00Xma.js.map → DataView-Di2F5dQR.js.map} +1 -1
  19. package/dist/chunks/Dialog-BIOrX51Q.js +2 -0
  20. package/dist/chunks/Dialog-BIOrX51Q.js.map +1 -0
  21. package/dist/chunks/{Dialog-Bu9EGh-z.js → Dialog-ia-mdhjA.js} +41 -34
  22. package/dist/chunks/Dialog-ia-mdhjA.js.map +1 -0
  23. package/dist/chunks/{FilePreviewView-C-VswdQl.js → FilePreviewView-BsV1ur-F.js} +9 -6
  24. package/dist/chunks/FilePreviewView-BsV1ur-F.js.map +1 -0
  25. package/dist/chunks/FilePreviewView-C550ZqA1.js +2 -0
  26. package/dist/chunks/FilePreviewView-C550ZqA1.js.map +1 -0
  27. package/dist/chunks/{FormView-DWV5Lhc3.js → FormView-BxKY1Pua.js} +2 -2
  28. package/dist/chunks/{FormView-DWV5Lhc3.js.map → FormView-BxKY1Pua.js.map} +1 -1
  29. package/dist/chunks/{FormView-DhZi_-D_.js → FormView-Ieig2Ikt.js} +2 -2
  30. package/dist/chunks/{FormView-DhZi_-D_.js.map → FormView-Ieig2Ikt.js.map} +1 -1
  31. package/dist/chunks/{MetricsChart-CHQmuqGa.js → MetricsChart-BDhDGz4E.js} +2 -2
  32. package/dist/chunks/{MetricsChart-CHQmuqGa.js.map → MetricsChart-BDhDGz4E.js.map} +1 -1
  33. package/dist/chunks/{MetricsChart-BOSgjGoW.js → MetricsChart-Ch8QKUkf.js} +14 -8
  34. package/dist/chunks/{MetricsChart-BOSgjGoW.js.map → MetricsChart-Ch8QKUkf.js.map} +1 -1
  35. package/dist/chunks/{PDFViewer-C7_03lr6.js → PDFViewer-CzjtNiNr.js} +3 -3
  36. package/dist/chunks/{PDFViewer-C7_03lr6.js.map → PDFViewer-CzjtNiNr.js.map} +1 -1
  37. package/dist/chunks/{PDFViewer-CTQ_WArl.js → PDFViewer-DANtnv28.js} +2 -2
  38. package/dist/chunks/{PDFViewer-CTQ_WArl.js.map → PDFViewer-DANtnv28.js.map} +1 -1
  39. package/dist/chunks/{Page-7mRcLFeD.js → Page-BXT4XGsW.js} +2 -2
  40. package/dist/chunks/{Page-7mRcLFeD.js.map → Page-BXT4XGsW.js.map} +1 -1
  41. package/dist/chunks/{Page-CG1u27-d.js → Page-D-s2pJ4R.js} +2 -2
  42. package/dist/chunks/{Page-CG1u27-d.js.map → Page-D-s2pJ4R.js.map} +1 -1
  43. package/dist/chunks/{TopNav-CF1Ic6Ty.js → TopNav-E_ZvzFaL.js} +2 -2
  44. package/dist/chunks/{TopNav-CF1Ic6Ty.js.map → TopNav-E_ZvzFaL.js.map} +1 -1
  45. package/dist/chunks/{TopNav-CprY7p9c.js → TopNav-WEv4D1Jr.js} +2 -2
  46. package/dist/chunks/{TopNav-CprY7p9c.js.map → TopNav-WEv4D1Jr.js.map} +1 -1
  47. package/dist/chunks/{User-CgSxo-iW.js → User-BH8gosuT.js} +4 -2
  48. package/dist/chunks/{User-CgSxo-iW.js.map → User-BH8gosuT.js.map} +1 -1
  49. package/dist/chunks/{User-DPWffTVQ.js → User-CHIu2dzX.js} +2 -2
  50. package/dist/chunks/User-CHIu2dzX.js.map +1 -0
  51. package/dist/chunks/WebApp-AQ6Px5yi.js +2 -0
  52. package/dist/chunks/WebApp-AQ6Px5yi.js.map +1 -0
  53. package/dist/chunks/{WebApp-DDt4Ihy7.js → WebApp-Dr-gVl9H.js} +23 -12
  54. package/dist/chunks/WebApp-Dr-gVl9H.js.map +1 -0
  55. package/dist/css/web-mojo.css +1 -1
  56. package/dist/docit.cjs.js +1 -1
  57. package/dist/docit.es.js +6 -6
  58. package/dist/index.cjs.js +1 -1
  59. package/dist/index.cjs.js.map +1 -1
  60. package/dist/index.es.js +11 -11
  61. package/dist/index.es.js.map +1 -1
  62. package/dist/lightbox.cjs.js +1 -1
  63. package/dist/lightbox.es.js +4 -4
  64. package/dist/table.css +1 -8
  65. package/package.json +1 -1
  66. package/dist/chunks/Dialog-Bu9EGh-z.js.map +0 -1
  67. package/dist/chunks/Dialog-CxJjXDJ-.js +0 -2
  68. package/dist/chunks/Dialog-CxJjXDJ-.js.map +0 -1
  69. package/dist/chunks/FilePreviewView-C-VswdQl.js.map +0 -1
  70. package/dist/chunks/FilePreviewView-CxT-vXFS.js +0 -2
  71. package/dist/chunks/FilePreviewView-CxT-vXFS.js.map +0 -1
  72. package/dist/chunks/User-DPWffTVQ.js.map +0 -1
  73. package/dist/chunks/WebApp-D35UJNMm.js +0 -2
  74. package/dist/chunks/WebApp-D35UJNMm.js.map +0 -1
  75. package/dist/chunks/WebApp-DDt4Ihy7.js.map +0 -1
package/dist/admin.es.js CHANGED
@@ -1,14 +1,14 @@
1
- import { P as Page } from "./chunks/Page-7mRcLFeD.js";
2
- import { V as View, h as MOJOUtils } from "./chunks/WebApp-DDt4Ihy7.js";
3
- import { B, b, a, c, e, f, W } from "./chunks/WebApp-DDt4Ihy7.js";
4
- import Dialog$1 from "./chunks/Dialog-Bu9EGh-z.js";
5
- import { M as MetricsChart, P as PieChart } from "./chunks/MetricsChart-BOSgjGoW.js";
6
- import { b as TablePage, i as EmailDomainForms, h as EmailDomainList, E as EmailDomain, l as MailboxForms, k as MailboxList, j as Mailbox, p as EmailTemplate, d as TabView, r as EmailTemplateForms, q as EmailTemplateList, I as IncidentEvent, z as IncidentEventForms, y as IncidentEventList, u as FileManagerForms, t as FileManagerList, v as File, T as TableView, x as FileForms, w as FileList, am as GeoLocatedIP, an as GeoLocatedIPList, a7 as MemberList, a6 as LogList, ap as TicketList, B as IncidentList, V as IncidentStats, N as IncidentHistoryList, K as IncidentHistory, F as FilePreviewView, A as Incident, C as IncidentForms, W as Job, a0 as JobEventList, _ as JobLogList, Y as JobForms, X as JobList, a3 as JobRunnerList, a1 as JobsEngineStats, a4 as JobRunnerForms, a2 as JobRunner, a5 as Log, M as Member, a8 as MemberForms, a9 as MetricsPermission, ab as MetricsForms, aa as MetricsPermissionList, ak as PushConfigForms, ah as PushConfigList, aj as PushDeliveryList, ad as PushDeviceList, al as PushTemplateForms, af as PushTemplateList, R as RuleSet, U as RuleList, O as RuleSetList, g as S3BucketForms, f as S3BucketList, m as SentMessage, n as SentMessageList, ar as TicketNoteList, aq as TicketNote, ao as Ticket, as as TicketForms } from "./chunks/FilePreviewView-C-VswdQl.js";
7
- import DataView from "./chunks/DataView-Dmobu7T8.js";
8
- import { C as ContextMenu } from "./chunks/ContextMenu-DjS0WgXA.js";
9
- import { C as Collection, a as Group, G as GroupList, b as GroupForms, f as UserDevice, i as UserDeviceLocationList, g as UserDeviceList, U as User, e as UserDataView, d as UserForms, c as UserList } from "./chunks/User-CgSxo-iW.js";
10
- import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-C7_03lr6.js";
11
- import { a as applyFileDropMixin } from "./chunks/FormView-DhZi_-D_.js";
1
+ import { P as Page } from "./chunks/Page-BXT4XGsW.js";
2
+ import { V as View, h as MOJOUtils } from "./chunks/WebApp-Dr-gVl9H.js";
3
+ import { B, b, a, c, e, f, W } from "./chunks/WebApp-Dr-gVl9H.js";
4
+ import Dialog$1 from "./chunks/Dialog-ia-mdhjA.js";
5
+ import { M as MetricsChart, P as PieChart } from "./chunks/MetricsChart-Ch8QKUkf.js";
6
+ import { b as TablePage, i as EmailDomainForms, h as EmailDomainList, E as EmailDomain, l as MailboxForms, k as MailboxList, j as Mailbox, p as EmailTemplate, d as TabView, r as EmailTemplateForms, q as EmailTemplateList, I as IncidentEvent, z as IncidentEventForms, y as IncidentEventList, u as FileManagerForms, t as FileManagerList, v as File, T as TableView, x as FileForms, w as FileList, am as GeoLocatedIP, an as GeoLocatedIPList, a7 as MemberList, a6 as LogList, ap as TicketList, B as IncidentList, V as IncidentStats, N as IncidentHistoryList, K as IncidentHistory, F as FilePreviewView, A as Incident, C as IncidentForms, W as Job, a0 as JobEventList, _ as JobLogList, Y as JobForms, X as JobList, a3 as JobRunnerList, a1 as JobsEngineStats, a4 as JobRunnerForms, a2 as JobRunner, a5 as Log, M as Member, a8 as MemberForms, a9 as MetricsPermission, ab as MetricsForms, aa as MetricsPermissionList, ak as PushConfigForms, ah as PushConfigList, aj as PushDeliveryList, ad as PushDeviceList, al as PushTemplateForms, af as PushTemplateList, R as RuleSet, U as RuleList, O as RuleSetList, g as S3BucketForms, f as S3BucketList, m as SentMessage, n as SentMessageList, ar as TicketNoteList, aq as TicketNote, ao as Ticket, as as TicketForms } from "./chunks/FilePreviewView-BsV1ur-F.js";
7
+ import DataView from "./chunks/DataView-Cy9OMdVA.js";
8
+ import { C as ContextMenu } from "./chunks/ContextMenu-BqYO9pWG.js";
9
+ import { C as Collection, a as Group, G as GroupList, b as GroupForms, f as UserDevice, i as UserDeviceLocationList, g as UserDeviceList, U as User, e as UserDataView, d as UserForms, c as UserList } from "./chunks/User-BH8gosuT.js";
10
+ import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer-CzjtNiNr.js";
11
+ import { a as applyFileDropMixin } from "./chunks/FormView-Ieig2Ikt.js";
12
12
  class AdminHeaderView extends View {
13
13
  constructor(options = {}) {
14
14
  super({
@@ -2169,6 +2169,7 @@ class ChatInputView extends View {
2169
2169
  ...options
2170
2170
  });
2171
2171
  this.uploads = [];
2172
+ this.buttonText = options.buttonText || "Send";
2172
2173
  }
2173
2174
  getTemplate() {
2174
2175
  return `
@@ -2176,7 +2177,7 @@ class ChatInputView extends View {
2176
2177
  <textarea class="form-control" placeholder="Type a message..." rows="3"></textarea>
2177
2178
  <div class="d-flex justify-content-between align-items-center mt-2">
2178
2179
  <small class="text-muted">Drag & drop files to attach.</small>
2179
- <button class="btn btn-primary" data-action="send-message">Send</button>
2180
+ <button class="btn btn-primary" data-action="send-message">${this.buttonText}</button>
2180
2181
  </div>
2181
2182
  <div class="uploads-container mt-2"></div>
2182
2183
  </div>
@@ -2218,6 +2219,7 @@ class ChatView extends View {
2218
2219
  });
2219
2220
  this.adapter = options.adapter;
2220
2221
  this.items = [];
2222
+ this.inputButtonText = options.inputButtonText || "Send";
2221
2223
  }
2222
2224
  getTemplate() {
2223
2225
  return `
@@ -2236,14 +2238,16 @@ class ChatView extends View {
2236
2238
  messageView.render(true, historyContainer);
2237
2239
  });
2238
2240
  const inputContainer = this.element.querySelector(".chat-input-container");
2239
- this.inputView = new ChatInputView();
2240
- this.addChild(this.inputView);
2241
+ if (!this.inputView) {
2242
+ this.inputView = new ChatInputView({ buttonText: this.inputButtonText });
2243
+ this.addChild(this.inputView);
2244
+ this.inputView.on("note:submit", async (data) => {
2245
+ await this.adapter.addNote(data);
2246
+ this.items = await this.adapter.fetch();
2247
+ this.render();
2248
+ });
2249
+ }
2241
2250
  this.inputView.render(true, inputContainer);
2242
- this.inputView.on("note:submit", async (data) => {
2243
- await this.adapter.addNote(data);
2244
- this.items = await this.adapter.fetch();
2245
- this.render();
2246
- });
2247
2251
  }
2248
2252
  }
2249
2253
  class IncidentView extends View {
@@ -2472,7 +2476,7 @@ class JobStatsView extends View {
2472
2476
  <div class="job-stats-header mb-4">
2473
2477
  <div class="row">
2474
2478
  <div class="col-xl-2 col-lg-4 col-md-6 col-12 mb-3">
2475
- <div class="card h-100 border-0 shadow-sm">
2479
+ <div class="card h-100 border-0 shadow">
2476
2480
  <div class="card-body">
2477
2481
  <div class="d-flex justify-content-between align-items-start">
2478
2482
  <div>
@@ -2491,7 +2495,7 @@ class JobStatsView extends View {
2491
2495
  </div>
2492
2496
 
2493
2497
  <div class="col-xl-2 col-lg-4 col-md-6 col-12 mb-3">
2494
- <div class="card h-100 border-0 shadow-sm">
2498
+ <div class="card h-100 border-0 shadow">
2495
2499
  <div class="card-body">
2496
2500
  <div class="d-flex justify-content-between align-items-start">
2497
2501
  <div>
@@ -2510,7 +2514,7 @@ class JobStatsView extends View {
2510
2514
  </div>
2511
2515
 
2512
2516
  <div class="col-xl-2 col-lg-4 col-md-6 col-12 mb-3">
2513
- <div class="card h-100 border-0 shadow-sm">
2517
+ <div class="card h-100 border-0 shadow">
2514
2518
  <div class="card-body">
2515
2519
  <div class="d-flex justify-content-between align-items-start">
2516
2520
  <div>
@@ -2529,7 +2533,7 @@ class JobStatsView extends View {
2529
2533
  </div>
2530
2534
 
2531
2535
  <div class="col-xl-3 col-lg-6 col-md-6 col-12 mb-3">
2532
- <div class="card h-100 border-0 shadow-sm">
2536
+ <div class="card h-100 border-0 shadow">
2533
2537
  <div class="card-body">
2534
2538
  <div class="d-flex justify-content-between align-items-start">
2535
2539
  <div>
@@ -2548,7 +2552,7 @@ class JobStatsView extends View {
2548
2552
  </div>
2549
2553
 
2550
2554
  <div class="col-xl-3 col-lg-6 col-md-6 col-12 mb-3">
2551
- <div class="card h-100 border-0 shadow-sm">
2555
+ <div class="card h-100 border-0 shadow">
2552
2556
  <div class="card-body">
2553
2557
  <div class="d-flex justify-content-between align-items-start">
2554
2558
  <div>
@@ -2592,7 +2596,7 @@ class JobHealthView extends View {
2592
2596
  };
2593
2597
  this.template = `
2594
2598
  <div class="job-health-header mb-4">
2595
- <div class="card border-0 shadow-sm">
2599
+ <div class="card border-0 shadow">
2596
2600
  <div class="card-body">
2597
2601
  <div class="row align-items-center">
2598
2602
  <div class="col-md-6">
@@ -3082,7 +3086,6 @@ class JobsTable extends TableView {
3082
3086
  super({
3083
3087
  Collection: JobList,
3084
3088
  collectionParams: {
3085
- start: 0,
3086
3089
  size: 15,
3087
3090
  sort: "-created"
3088
3091
  },
@@ -3581,10 +3584,17 @@ class JobsAdminPage extends Page {
3581
3584
  <div data-container="job-stats"></div>
3582
3585
 
3583
3586
  <!-- Job Health -->
3584
- <div data-container="job-health"></div>
3587
+ <div class="row">
3588
+ <div class="col-12">
3589
+ <div data-container="job-health"></div>
3590
+ </div>
3591
+ <div class="col-12">
3592
+ <div class="mb-3" data-container="job-metrics"></div>
3593
+ </div>
3594
+ </div>
3585
3595
 
3586
3596
  <!-- Job Tables -->
3587
- <div class="card border shadow-sm">
3597
+ <div class="card border shadow">
3588
3598
  <div class="card-header">
3589
3599
  <h5 class="card-title mb-0">
3590
3600
  <i class="bi bi-list-task me-2"></i>Job Management
@@ -3619,6 +3629,25 @@ class JobsAdminPage extends Page {
3619
3629
  activeTab: "Jobs"
3620
3630
  });
3621
3631
  this.addChild(this.jobTablesView);
3632
+ this.jobMetricsChart = new MetricsChart({
3633
+ title: `<i class="bi bi-graph-up me-2"></i> Job Metrics`,
3634
+ endpoint: "/api/metrics/fetch",
3635
+ height: 100,
3636
+ granularity: "hours",
3637
+ category: "jobs_channels",
3638
+ account: "global",
3639
+ chartType: "bar",
3640
+ showDateRange: false,
3641
+ yAxis: {
3642
+ label: "Count",
3643
+ beginAtZero: true
3644
+ },
3645
+ tooltip: {
3646
+ y: "number"
3647
+ },
3648
+ containerId: "job-metrics"
3649
+ });
3650
+ this.addChild(this.jobMetricsChart);
3622
3651
  await this.jobStats.fetch();
3623
3652
  }
3624
3653
  // Auto-refresh management
@@ -6887,8 +6916,8 @@ class TicketNoteAdapter {
6887
6916
  type: "user_comment",
6888
6917
  // Ticket notes are always user comments
6889
6918
  author: {
6890
- name: note.get("author.display_name") || "System",
6891
- avatarUrl: note.get("author.avatar.url")
6919
+ name: note.get("user.display_name") || "System",
6920
+ avatarUrl: note.get("user.avatar.url")
6892
6921
  },
6893
6922
  timestamp: note.get("created"),
6894
6923
  content: note.get("note"),
@@ -6896,12 +6925,12 @@ class TicketNoteAdapter {
6896
6925
  };
6897
6926
  }
6898
6927
  async addNote(data) {
6899
- const note = new TicketNote({
6900
- ticket: this.ticketId,
6928
+ const note = new TicketNote();
6929
+ const resp = await note.save({
6930
+ parent: this.ticketId,
6901
6931
  note: data.text,
6902
6932
  media: data.files && data.files.length > 0 ? data.files[0].id : null
6903
6933
  });
6904
- const resp = await note.save();
6905
6934
  if (resp.success) {
6906
6935
  await this.collection.fetch();
6907
6936
  }
@@ -6916,31 +6945,79 @@ class TicketView extends View {
6916
6945
  });
6917
6946
  this.model = options.model || new Ticket(options.data || {});
6918
6947
  this.template = `
6919
- <div class="ticket-view-container p-3">
6920
- <!-- Header -->
6921
- <div class="d-flex justify-content-between align-items-center mb-4">
6922
- <div>
6923
- <h3 class="mb-1">Ticket #{{model.id}}: {{model.title}}</h3>
6924
- <span class="badge {{model.status|badge}}">{{model.status|capitalize}}</span>
6925
- <span class="ms-2">Priority: {{model.priority}}</span>
6948
+ <div class="ticket-view-container">
6949
+ <!-- Ticket Header -->
6950
+ <div class="d-flex justify-content-between align-items-start mb-4">
6951
+ <!-- Left Side: Primary Identity -->
6952
+ <div class="d-flex align-items-center gap-3">
6953
+ <div class="avatar-placeholder rounded-circle bg-light d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
6954
+ <i class="bi bi-ticket-perforated text-secondary" style="font-size: 40px;"></i>
6955
+ </div>
6956
+ <div>
6957
+ <h3 class="mb-1">{{model.title|truncate(50)|default('Untitled Ticket')}}</h3>
6958
+ <div class="text-muted small">
6959
+ <span>Ticket #{{model.id}}</span>
6960
+ <span class="mx-2">|</span>
6961
+ <span>Priority: {{model.priority|capitalize}}</span>
6962
+ {{#model.assignee}}
6963
+ <span class="mx-2">|</span>
6964
+ <span>Assigned to: {{model.assignee.display_name}}</span>
6965
+ {{/model.assignee}}
6966
+ </div>
6967
+ {{#model.incident}}
6968
+ <div class="text-muted small mt-1">
6969
+ <i class="bi bi-exclamation-triangle"></i> Related to incident: {{model.incident}}
6970
+ </div>
6971
+ {{/model.incident}}
6972
+ </div>
6926
6973
  </div>
6927
- <div data-container="ticket-context-menu"></div>
6928
- </div>
6929
6974
 
6930
- <div class="row">
6931
- <div class="col-md-8">
6932
- <h5>Description</h5>
6933
- <div class="border p-3 rounded bg-light mb-4">{{{model.description}}}</div>
6934
- </div>
6935
- <div class="col-md-4">
6936
- <h5>Details</h5>
6937
- <div data-container="details-view"></div>
6975
+ <!-- Right Side: Status & Actions -->
6976
+ <div class="d-flex align-items-start gap-4">
6977
+ <div class="text-end">
6978
+ <div class="d-flex align-items-center gap-2">
6979
+ <span class="badge {{model.status|badgeClass}}">{{model.status|capitalize}}</span>
6980
+ </div>
6981
+ {{#model.created}}
6982
+ <div class="text-muted small mt-1">Created {{model.created|relative}}</div>
6983
+ {{/model.created}}
6984
+ {{#model.modified}}
6985
+ <div class="text-muted small">Updated {{model.modified|relative}}</div>
6986
+ {{/model.modified}}
6987
+ </div>
6988
+ <div data-container="ticket-context-menu"></div>
6938
6989
  </div>
6939
6990
  </div>
6991
+
6992
+ <!-- Content Area -->
6940
6993
  <div class="row">
6941
- <div class="col-12">
6942
- <h5>Activity & Notes</h5>
6943
- <div data-container="chat-view"></div>
6994
+ <div class="col-lg-8">
6995
+ <!-- Description Section -->
6996
+ <div class="mb-4">
6997
+ <h5 class="border-bottom pb-2 mb-3">Description</h5>
6998
+ <div class="border rounded p-3 bg-light">
6999
+ {{#model.description}}
7000
+ {{{model.description}}}
7001
+ {{/model.description}}
7002
+ {{^model.description}}
7003
+ <em class="text-muted">No description provided</em>
7004
+ {{/model.description}}
7005
+ </div>
7006
+ </div>
7007
+
7008
+ <!-- Activity & Notes Section -->
7009
+ <div>
7010
+ <h5 class="border-bottom pb-2 mb-3">Activity & Notes</h5>
7011
+ <div data-container="chat-view"></div>
7012
+ </div>
7013
+ </div>
7014
+
7015
+ <div class="col-lg-4">
7016
+ <!-- Details Sidebar -->
7017
+ <div class="border rounded p-3 bg-light">
7018
+ <h5 class="mb-3">Ticket Details</h5>
7019
+ <div data-container="details-view"></div>
7020
+ </div>
6944
7021
  </div>
6945
7022
  </div>
6946
7023
  </div>
@@ -6961,29 +7038,153 @@ class TicketView extends View {
6961
7038
  const adapter = new TicketNoteAdapter(this.model.get("id"));
6962
7039
  this.chatView = new ChatView({
6963
7040
  containerId: "chat-view",
6964
- adapter
7041
+ adapter,
7042
+ inputButtonText: "Add Note"
6965
7043
  });
6966
7044
  this.addChild(this.chatView);
6967
- const contextMenu = new ContextMenu({
7045
+ const ticketMenu = new ContextMenu({
6968
7046
  containerId: "ticket-context-menu",
7047
+ className: "context-menu-view header-menu-absolute",
6969
7048
  context: this.model,
6970
7049
  config: {
6971
7050
  icon: "bi-three-dots-vertical",
6972
7051
  items: [
7052
+ { label: "Edit Ticket", action: "edit-ticket", icon: "bi-pencil" },
6973
7053
  { label: "Change Status", action: "change-status", icon: "bi-tag" },
6974
7054
  { label: "Set Priority", action: "set-priority", icon: "bi-flag" },
6975
- { label: "Assign User", action: "assign-user", icon: "bi-person" }
7055
+ { label: "Assign User", action: "assign-user", icon: "bi-person" },
7056
+ { type: "divider" },
7057
+ { label: "Close Ticket", action: "close-ticket", icon: "bi-x-circle" }
6976
7058
  ]
6977
7059
  }
6978
7060
  });
6979
- this.addChild(contextMenu);
7061
+ this.addChild(ticketMenu);
7062
+ }
7063
+ // Context Menu Action Handlers
7064
+ async onActionEditTicket() {
7065
+ const resp = await Dialog$1.showModelForm({
7066
+ title: `Edit Ticket #${this.model.get("id")} - ${this.model.get("title")}`,
7067
+ model: this.model,
7068
+ size: "lg",
7069
+ fields: [
7070
+ { name: "title", label: "Title", type: "text", required: true },
7071
+ { name: "description", label: "Description", type: "textarea", rows: 4 },
7072
+ {
7073
+ name: "status",
7074
+ label: "Status",
7075
+ type: "select",
7076
+ options: ["open", "in_progress", "pending", "resolved", "closed"]
7077
+ },
7078
+ {
7079
+ name: "priority",
7080
+ label: "Priority",
7081
+ type: "select",
7082
+ options: ["low", "normal", "high", "urgent"]
7083
+ },
7084
+ { name: "assignee_id", label: "Assignee", type: "user_select" }
7085
+ ]
7086
+ });
7087
+ if (resp) {
7088
+ this.render();
7089
+ }
7090
+ }
7091
+ async onActionChangeStatus() {
7092
+ const statuses = ["new", "open", "in_progress", "pending", "resolved", "closed", "ignored"];
7093
+ const currentStatus = this.model.get("status");
7094
+ const result = await Dialog$1.showForm({
7095
+ title: "Change Ticket Status",
7096
+ size: "sm",
7097
+ fields: [
7098
+ {
7099
+ name: "status",
7100
+ label: "New Status",
7101
+ type: "select",
7102
+ options: statuses.map((s) => ({ value: s, label: s.replace("_", " ").toUpperCase() })),
7103
+ value: currentStatus,
7104
+ required: true
7105
+ }
7106
+ ]
7107
+ });
7108
+ if (result) {
7109
+ try {
7110
+ await this.model.save({ status: result.status });
7111
+ this.render();
7112
+ } catch (error) {
7113
+ Dialog$1.alert({
7114
+ type: "error",
7115
+ title: "Error",
7116
+ message: "Failed to update ticket status: " + error.message
7117
+ });
7118
+ }
7119
+ }
7120
+ }
7121
+ async onActionSetPriority() {
7122
+ const priorities = ["low", "normal", "high", "urgent"];
7123
+ const currentPriority = this.model.get("priority");
7124
+ const result = await Dialog$1.showForm({
7125
+ title: "Set Ticket Priority",
7126
+ size: "sm",
7127
+ fields: [
7128
+ {
7129
+ name: "priority",
7130
+ label: "Priority Level",
7131
+ type: "select",
7132
+ options: priorities.map((p) => ({ value: p, label: p.toUpperCase() })),
7133
+ value: currentPriority,
7134
+ required: true
7135
+ }
7136
+ ]
7137
+ });
7138
+ if (result) {
7139
+ try {
7140
+ await this.model.save({ priority: result.priority });
7141
+ this.render();
7142
+ } catch (error) {
7143
+ Dialog$1.alert({
7144
+ type: "error",
7145
+ title: "Error",
7146
+ message: "Failed to update ticket priority: " + error.message
7147
+ });
7148
+ }
7149
+ }
7150
+ }
7151
+ async onActionAssignUser() {
7152
+ console.log("TODO: Implement assign user dialog with user selector");
7153
+ Dialog$1.alert({
7154
+ title: "Coming Soon",
7155
+ message: "User assignment feature will be implemented soon."
7156
+ });
7157
+ }
7158
+ async onActionCloseTicket() {
7159
+ const confirmed = await Dialog$1.confirm({
7160
+ title: "Close Ticket",
7161
+ message: `Are you sure you want to close ticket #${this.model.get("id")}?`,
7162
+ confirmText: "Close Ticket",
7163
+ confirmClass: "btn-warning"
7164
+ });
7165
+ if (confirmed) {
7166
+ try {
7167
+ await this.model.save({ status: "closed" });
7168
+ this.render();
7169
+ Dialog$1.alert({
7170
+ type: "success",
7171
+ title: "Success",
7172
+ message: "Ticket has been closed successfully."
7173
+ });
7174
+ } catch (error) {
7175
+ Dialog$1.alert({
7176
+ type: "error",
7177
+ title: "Error",
7178
+ message: "Failed to close ticket: " + error.message
7179
+ });
7180
+ }
7181
+ }
6980
7182
  }
6981
7183
  }
6982
7184
  Ticket.VIEW_CLASS = TicketView;
6983
7185
  class TicketTablePage extends TablePage {
6984
7186
  constructor(options = {}) {
6985
7187
  super({
6986
- ...options,
6987
7188
  name: "admin_tickets",
6988
7189
  pageName: "Tickets",
6989
7190
  router: "admin/tickets",
@@ -7037,7 +7238,8 @@ class TicketTablePage extends TablePage {
7037
7238
  bordered: false,
7038
7239
  hover: true,
7039
7240
  responsive: false
7040
- }
7241
+ },
7242
+ ...options
7041
7243
  });
7042
7244
  }
7043
7245
  }