web-mojo 2.1.175 → 2.1.219

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 (73) hide show
  1. package/README.md +25 -41
  2. package/dist/admin.cjs.js +1 -1
  3. package/dist/admin.cjs.js.map +1 -1
  4. package/dist/admin.es.js +27 -13
  5. package/dist/admin.es.js.map +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.cjs.js.map +1 -1
  8. package/dist/auth.es.js +3 -3
  9. package/dist/auth.es.js.map +1 -1
  10. package/dist/charts.cjs.js +1 -1
  11. package/dist/charts.es.js +2 -2
  12. package/dist/chunks/{ContextMenu-CvFe_-pA.js → ContextMenu-BGstYX-N.js} +2 -2
  13. package/dist/chunks/{ContextMenu-CvFe_-pA.js.map → ContextMenu-BGstYX-N.js.map} +1 -1
  14. package/dist/chunks/{ContextMenu-CuTbtePk.js → ContextMenu-DjS0WgXA.js} +2 -2
  15. package/dist/chunks/{ContextMenu-CuTbtePk.js.map → ContextMenu-DjS0WgXA.js.map} +1 -1
  16. package/dist/chunks/{DataView-UjG66gmW.js → DataView-Dmobu7T8.js} +2 -2
  17. package/dist/chunks/{DataView-UjG66gmW.js.map → DataView-Dmobu7T8.js.map} +1 -1
  18. package/dist/chunks/{DataView-BwZajXhu.js → DataView-LCk00Xma.js} +2 -2
  19. package/dist/chunks/{DataView-BwZajXhu.js.map → DataView-LCk00Xma.js.map} +1 -1
  20. package/dist/chunks/{Dialog-eoLNdg-d.js → Dialog-Bu9EGh-z.js} +62 -16
  21. package/dist/chunks/Dialog-Bu9EGh-z.js.map +1 -0
  22. package/dist/chunks/Dialog-CxJjXDJ-.js +2 -0
  23. package/dist/chunks/Dialog-CxJjXDJ-.js.map +1 -0
  24. package/dist/chunks/{FilePreviewView-Crpalaos.js → FilePreviewView-C-VswdQl.js} +578 -20
  25. package/dist/chunks/FilePreviewView-C-VswdQl.js.map +1 -0
  26. package/dist/chunks/FilePreviewView-CxT-vXFS.js +2 -0
  27. package/dist/chunks/FilePreviewView-CxT-vXFS.js.map +1 -0
  28. package/dist/chunks/FormView-DWV5Lhc3.js +2 -0
  29. package/dist/chunks/FormView-DWV5Lhc3.js.map +1 -0
  30. package/dist/chunks/{FormView-fUbbKQQU.js → FormView-DhZi_-D_.js} +4 -2
  31. package/dist/chunks/FormView-DhZi_-D_.js.map +1 -0
  32. package/dist/chunks/{MetricsChart-DY1w4wC-.js → MetricsChart-BOSgjGoW.js} +3 -3
  33. package/dist/chunks/{MetricsChart-DY1w4wC-.js.map → MetricsChart-BOSgjGoW.js.map} +1 -1
  34. package/dist/chunks/{MetricsChart-B_KuNFD-.js → MetricsChart-CHQmuqGa.js} +2 -2
  35. package/dist/chunks/{MetricsChart-B_KuNFD-.js.map → MetricsChart-CHQmuqGa.js.map} +1 -1
  36. package/dist/chunks/{PDFViewer-CYaI8u8I.js → PDFViewer-C7_03lr6.js} +3 -3
  37. package/dist/chunks/{PDFViewer-CYaI8u8I.js.map → PDFViewer-C7_03lr6.js.map} +1 -1
  38. package/dist/chunks/{PDFViewer-D5SmAaQD.js → PDFViewer-CTQ_WArl.js} +2 -2
  39. package/dist/chunks/{PDFViewer-D5SmAaQD.js.map → PDFViewer-CTQ_WArl.js.map} +1 -1
  40. package/dist/chunks/{Page-tbcVc_Wl.js → Page-7mRcLFeD.js} +2 -2
  41. package/dist/chunks/{Page-tbcVc_Wl.js.map → Page-7mRcLFeD.js.map} +1 -1
  42. package/dist/chunks/{Page-D5kSAjt8.js → Page-CG1u27-d.js} +2 -2
  43. package/dist/chunks/{Page-D5kSAjt8.js.map → Page-CG1u27-d.js.map} +1 -1
  44. package/dist/chunks/{TopNav-DlHcfQbu.js → TopNav-CF1Ic6Ty.js} +2 -2
  45. package/dist/chunks/{TopNav-DlHcfQbu.js.map → TopNav-CF1Ic6Ty.js.map} +1 -1
  46. package/dist/chunks/{TopNav-mQvYJp04.js → TopNav-CprY7p9c.js} +2 -2
  47. package/dist/chunks/{TopNav-mQvYJp04.js.map → TopNav-CprY7p9c.js.map} +1 -1
  48. package/dist/chunks/{User-DI3U4oRV.js → User-CgSxo-iW.js} +2 -2
  49. package/dist/chunks/{User-DI3U4oRV.js.map → User-CgSxo-iW.js.map} +1 -1
  50. package/dist/chunks/{User-CvWFN0ul.js → User-DPWffTVQ.js} +2 -2
  51. package/dist/chunks/{User-CvWFN0ul.js.map → User-DPWffTVQ.js.map} +1 -1
  52. package/dist/chunks/{WebApp-sKJf8j1s.js → WebApp-D35UJNMm.js} +2 -2
  53. package/dist/chunks/{WebApp-sKJf8j1s.js.map → WebApp-D35UJNMm.js.map} +1 -1
  54. package/dist/chunks/{WebApp-B2r2EDj7.js → WebApp-DDt4Ihy7.js} +12 -12
  55. package/dist/chunks/{WebApp-B2r2EDj7.js.map → WebApp-DDt4Ihy7.js.map} +1 -1
  56. package/dist/css/web-mojo.css +1 -1
  57. package/dist/docit.cjs.js +1 -1
  58. package/dist/docit.es.js +6 -6
  59. package/dist/index.cjs.js +1 -1
  60. package/dist/index.es.js +11 -11
  61. package/dist/lightbox.cjs.js +1 -1
  62. package/dist/lightbox.es.js +4 -4
  63. package/dist/table.css +267 -0
  64. package/package.json +1 -1
  65. package/dist/chunks/Dialog-BmvpkgLD.js +0 -2
  66. package/dist/chunks/Dialog-BmvpkgLD.js.map +0 -1
  67. package/dist/chunks/Dialog-eoLNdg-d.js.map +0 -1
  68. package/dist/chunks/FilePreviewView-Crpalaos.js.map +0 -1
  69. package/dist/chunks/FilePreviewView-CxplM_0z.js +0 -2
  70. package/dist/chunks/FilePreviewView-CxplM_0z.js.map +0 -1
  71. package/dist/chunks/FormView-BDBRWOlR.js +0 -2
  72. package/dist/chunks/FormView-BDBRWOlR.js.map +0 -1
  73. package/dist/chunks/FormView-fUbbKQQU.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { M as Model, C as Collection, T as ToastService, G as GroupList, c as UserList } from "./User-DI3U4oRV.js";
2
- import { r as rest, V as View, d as dataFormatter, M as Mustache } from "./WebApp-B2r2EDj7.js";
3
- import { P as Page } from "./Page-tbcVc_Wl.js";
4
- import Dialog from "./Dialog-eoLNdg-d.js";
5
- import { F as FormView } from "./FormView-fUbbKQQU.js";
1
+ import { M as Model, C as Collection, T as ToastService, G as GroupList, c as UserList } from "./User-CgSxo-iW.js";
2
+ import { r as rest, V as View, d as dataFormatter, M as Mustache } from "./WebApp-DDt4Ihy7.js";
3
+ import { P as Page } from "./Page-7mRcLFeD.js";
4
+ import Dialog from "./Dialog-Bu9EGh-z.js";
5
+ import { F as FormView } from "./FormView-DhZi_-D_.js";
6
6
  class S3Bucket extends Model {
7
7
  constructor(data = {}) {
8
8
  super(data, {
@@ -3079,6 +3079,7 @@ class TableRow extends ListViewItem {
3079
3079
  this.contextMenu = options.contextMenu || null;
3080
3080
  this.batchActions = options.batchActions || null;
3081
3081
  this.tableView = options.tableView || options.listView || null;
3082
+ this.editingCells = /* @__PURE__ */ new Set();
3082
3083
  this.template = this.buildRowTemplate();
3083
3084
  }
3084
3085
  /**
@@ -3115,13 +3116,17 @@ class TableRow extends ListViewItem {
3115
3116
  this.columns.forEach((column) => {
3116
3117
  const cellClass = column.class || column.className || "";
3117
3118
  const responsiveClasses = this.getResponsiveClasses(column.visibility);
3118
- const combinedClasses = [cellClass, responsiveClasses].filter((c) => c).join(" ");
3119
+ const editableClass = column.editable ? "editable-cell" : "";
3120
+ const combinedClasses = [cellClass, responsiveClasses, editableClass].filter((c) => c).join(" ");
3119
3121
  const cellContent = this.buildCellTemplate(column);
3120
- if (!column.action && this.tableView.rowAction) {
3121
- column.action = this.tableView.rowAction;
3122
+ let cellAction = column.action;
3123
+ if (!cellAction && column.editable) {
3124
+ cellAction = "edit-cell";
3125
+ } else if (!cellAction && this.tableView.rowAction) {
3126
+ cellAction = this.tableView.rowAction;
3122
3127
  }
3123
- if (column.action) {
3124
- template += `<td class="${combinedClasses}" data-action="${column.action}" data-column="${column.key}">${cellContent}</td>`;
3128
+ if (cellAction) {
3129
+ template += `<td class="${combinedClasses}" data-action="${cellAction}" data-column="${column.key}">${cellContent}</td>`;
3125
3130
  } else {
3126
3131
  template += `<td class="${combinedClasses}" data-column="${column.key}">${cellContent}</td>`;
3127
3132
  }
@@ -3151,6 +3156,9 @@ class TableRow extends ListViewItem {
3151
3156
  if (column.template) {
3152
3157
  return column.template;
3153
3158
  }
3159
+ if (column.editable) {
3160
+ return `<span class="cell-content" data-field="${column.key}">{{{${path}}}}</span>`;
3161
+ }
3154
3162
  return `{{{${path}}}}`;
3155
3163
  }
3156
3164
  /**
@@ -3281,11 +3289,22 @@ class TableRow extends ListViewItem {
3281
3289
  this.element.setAttribute("data-id", id);
3282
3290
  }
3283
3291
  }
3292
+ /**
3293
+ * Handle edit cell action
3294
+ */
3295
+ async onActionEditCell(event, element) {
3296
+ event.stopPropagation();
3297
+ const columnKey = element.getAttribute("data-column");
3298
+ const column = this.columns.find((col) => col.key === columnKey);
3299
+ if (!column || !column.editable) return;
3300
+ if (this.editingCells.has(columnKey)) return;
3301
+ await this.enterEditMode(columnKey, column, element);
3302
+ }
3284
3303
  /**
3285
3304
  * Handle row click action
3286
3305
  */
3287
3306
  async onActionRowClick(event, element) {
3288
- if (event.target.closest(".btn-group") || event.target.closest(".dropdown")) {
3307
+ if (event.target.closest(".btn-group") || event.target.closest(".dropdown") || event.target.closest(".cell-editor")) {
3289
3308
  return;
3290
3309
  }
3291
3310
  this.emit("row:click", {
@@ -3358,6 +3377,265 @@ class TableRow extends ListViewItem {
3358
3377
  });
3359
3378
  }
3360
3379
  }
3380
+ /**
3381
+ * Enter edit mode for a cell
3382
+ */
3383
+ async enterEditMode(columnKey, column, cellElement) {
3384
+ const contentSpan = cellElement.querySelector(".cell-content");
3385
+ if (!contentSpan) return;
3386
+ this.editingCells.add(columnKey);
3387
+ const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];
3388
+ const editor = this.createCellEditor(column, currentValue);
3389
+ const originalContent = contentSpan.innerHTML;
3390
+ contentSpan.style.display = "none";
3391
+ const editorContainer = document.createElement("div");
3392
+ editorContainer.className = "cell-editor";
3393
+ editorContainer.innerHTML = editor;
3394
+ cellElement.appendChild(editorContainer);
3395
+ const input = editorContainer.querySelector("input, select, .form-check-input");
3396
+ if (input) {
3397
+ input.focus();
3398
+ if (input.type === "text" || input.type === "textarea") {
3399
+ input.select();
3400
+ }
3401
+ }
3402
+ editorContainer.dataset.originalContent = originalContent;
3403
+ editorContainer.dataset.columnKey = columnKey;
3404
+ this.setupEditorEvents(editorContainer, columnKey, column);
3405
+ this.emit("cell:edit", {
3406
+ row: this,
3407
+ model: this.model,
3408
+ column: columnKey,
3409
+ originalValue: currentValue
3410
+ });
3411
+ }
3412
+ /**
3413
+ * Create cell editor HTML based on column configuration
3414
+ */
3415
+ createCellEditor(column, currentValue) {
3416
+ const options = column.editableOptions || {};
3417
+ switch (options.type) {
3418
+ case "select":
3419
+ return this.createSelectEditor(options, currentValue);
3420
+ case "switch":
3421
+ case "checkbox":
3422
+ return this.createSwitchEditor(options, currentValue);
3423
+ case "textarea":
3424
+ return this.createTextareaEditor(options, currentValue);
3425
+ default:
3426
+ return this.createTextEditor(options, currentValue);
3427
+ }
3428
+ }
3429
+ /**
3430
+ * Create text input editor
3431
+ */
3432
+ createTextEditor(options, currentValue) {
3433
+ const placeholder = options.placeholder || "";
3434
+ const inputType = options.inputType || "text";
3435
+ return `
3436
+ <div class="d-flex gap-1 align-items-center">
3437
+ <input type="${inputType}"
3438
+ class="form-control form-control-sm cell-input"
3439
+ value="${this.escapeHtml(currentValue || "")}"
3440
+ placeholder="${placeholder}">
3441
+ <button type="button" class="btn btn-sm btn-success cell-save" title="Save">
3442
+ <i class="bi bi-check"></i>
3443
+ </button>
3444
+ <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
3445
+ <i class="bi bi-x"></i>
3446
+ </button>
3447
+ </div>
3448
+ `;
3449
+ }
3450
+ /**
3451
+ * Create textarea editor
3452
+ */
3453
+ createTextareaEditor(options, currentValue) {
3454
+ const placeholder = options.placeholder || "";
3455
+ const rows = options.rows || 2;
3456
+ return `
3457
+ <div class="d-flex gap-1">
3458
+ <textarea class="form-control form-control-sm cell-input"
3459
+ rows="${rows}"
3460
+ placeholder="${placeholder}">${this.escapeHtml(currentValue || "")}</textarea>
3461
+ <div class="d-flex flex-column gap-1">
3462
+ <button type="button" class="btn btn-sm btn-success cell-save" title="Save">
3463
+ <i class="bi bi-check"></i>
3464
+ </button>
3465
+ <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
3466
+ <i class="bi bi-x"></i>
3467
+ </button>
3468
+ </div>
3469
+ </div>
3470
+ `;
3471
+ }
3472
+ /**
3473
+ * Create select dropdown editor
3474
+ */
3475
+ createSelectEditor(options, currentValue) {
3476
+ const optionsArray = options.options || [];
3477
+ let optionsHtml = "";
3478
+ optionsArray.forEach((option) => {
3479
+ if (typeof option === "string") {
3480
+ const selected = option === currentValue ? "selected" : "";
3481
+ optionsHtml += `<option value="${option}" ${selected}>${option}</option>`;
3482
+ } else if (typeof option === "object" && option.value !== void 0) {
3483
+ const selected = option.value === currentValue ? "selected" : "";
3484
+ optionsHtml += `<option value="${option.value}" ${selected}>${option.label || option.value}</option>`;
3485
+ }
3486
+ });
3487
+ return `
3488
+ <div class="d-flex gap-1 align-items-center">
3489
+ <select class="form-select form-select-sm cell-input">
3490
+ ${optionsHtml}
3491
+ </select>
3492
+ <button type="button" class="btn btn-sm btn-success cell-save" title="Save">
3493
+ <i class="bi bi-check"></i>
3494
+ </button>
3495
+ <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
3496
+ <i class="bi bi-x"></i>
3497
+ </button>
3498
+ </div>
3499
+ `;
3500
+ }
3501
+ /**
3502
+ * Create switch/checkbox editor
3503
+ */
3504
+ createSwitchEditor(options, currentValue) {
3505
+ const checked = currentValue ? "checked" : "";
3506
+ const switchType = options.type === "switch" ? "form-switch" : "";
3507
+ return `
3508
+ <div class="d-flex gap-2 align-items-center">
3509
+ <div class="form-check ${switchType}">
3510
+ <input class="form-check-input cell-input" type="checkbox" ${checked}>
3511
+ </div>
3512
+ <div class="d-flex gap-1">
3513
+ <button type="button" class="btn btn-sm btn-success cell-save" title="Save">
3514
+ <i class="bi bi-check"></i>
3515
+ </button>
3516
+ <button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
3517
+ <i class="bi bi-x"></i>
3518
+ </button>
3519
+ </div>
3520
+ </div>
3521
+ `;
3522
+ }
3523
+ /**
3524
+ * Setup event listeners for cell editor
3525
+ */
3526
+ setupEditorEvents(editorContainer, columnKey, column) {
3527
+ const input = editorContainer.querySelector(".cell-input");
3528
+ const saveBtn = editorContainer.querySelector(".cell-save");
3529
+ const cancelBtn = editorContainer.querySelector(".cell-cancel");
3530
+ if (input && (input.type === "text" || input.type === "email" || input.type === "number")) {
3531
+ input.addEventListener("keydown", (e) => {
3532
+ if (e.key === "Enter") {
3533
+ e.preventDefault();
3534
+ this.saveCellEdit(editorContainer, columnKey, column);
3535
+ } else if (e.key === "Escape") {
3536
+ e.preventDefault();
3537
+ this.cancelCellEdit(editorContainer, columnKey);
3538
+ }
3539
+ });
3540
+ }
3541
+ if (input && (input.type === "checkbox" || input.tagName === "SELECT") && column.autoSave !== false) {
3542
+ input.addEventListener("change", () => {
3543
+ this.saveCellEdit(editorContainer, columnKey, column);
3544
+ });
3545
+ }
3546
+ saveBtn?.addEventListener("click", () => {
3547
+ this.saveCellEdit(editorContainer, columnKey, column);
3548
+ });
3549
+ cancelBtn?.addEventListener("click", () => {
3550
+ this.cancelCellEdit(editorContainer, columnKey);
3551
+ });
3552
+ }
3553
+ /**
3554
+ * Save cell edit
3555
+ */
3556
+ async saveCellEdit(editorContainer, columnKey, column) {
3557
+ const input = editorContainer.querySelector(".cell-input");
3558
+ if (!input) return;
3559
+ let newValue;
3560
+ if (input.type === "checkbox") {
3561
+ newValue = input.checked;
3562
+ } else if (input.tagName === "SELECT") {
3563
+ newValue = input.value;
3564
+ } else {
3565
+ newValue = input.value;
3566
+ }
3567
+ const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];
3568
+ try {
3569
+ if (this.model.save) {
3570
+ await this.model.save({ [columnKey]: newValue });
3571
+ } else {
3572
+ this.model[columnKey] = newValue;
3573
+ }
3574
+ this.exitEditMode(editorContainer, columnKey, newValue);
3575
+ this.emit("cell:save", {
3576
+ row: this,
3577
+ model: this.model,
3578
+ column: columnKey,
3579
+ oldValue,
3580
+ newValue
3581
+ });
3582
+ } catch (error) {
3583
+ console.error("Failed to save cell edit:", error);
3584
+ this.emit("cell:save:error", {
3585
+ row: this,
3586
+ model: this.model,
3587
+ column: columnKey,
3588
+ oldValue,
3589
+ newValue,
3590
+ error
3591
+ });
3592
+ editorContainer.classList.add("saving-error");
3593
+ setTimeout(() => editorContainer.classList.remove("saving-error"), 3e3);
3594
+ }
3595
+ }
3596
+ /**
3597
+ * Cancel cell edit
3598
+ */
3599
+ cancelCellEdit(editorContainer, columnKey) {
3600
+ const originalContent = editorContainer.dataset.originalContent;
3601
+ this.exitEditMode(editorContainer, columnKey, null, originalContent);
3602
+ this.emit("cell:cancel", {
3603
+ row: this,
3604
+ model: this.model,
3605
+ column: columnKey
3606
+ });
3607
+ }
3608
+ /**
3609
+ * Exit edit mode and restore content
3610
+ */
3611
+ exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {
3612
+ const cellElement = editorContainer.closest("td");
3613
+ const contentSpan = cellElement.querySelector(".cell-content");
3614
+ if (contentSpan) {
3615
+ if (newValue !== null) {
3616
+ const column = this.columns.find((col) => col.key === columnKey);
3617
+ let displayValue = newValue;
3618
+ if (column && column.formatter && typeof column.formatter === "string") {
3619
+ displayValue = dataFormatter.pipe(newValue, column.formatter);
3620
+ }
3621
+ contentSpan.innerHTML = this.escapeHtml(displayValue);
3622
+ } else if (originalContent) {
3623
+ contentSpan.innerHTML = originalContent;
3624
+ }
3625
+ contentSpan.style.display = "";
3626
+ }
3627
+ editorContainer.remove();
3628
+ this.editingCells.delete(columnKey);
3629
+ }
3630
+ /**
3631
+ * Escape HTML for safe display
3632
+ */
3633
+ escapeHtml(text) {
3634
+ if (text === null || text === void 0) return "";
3635
+ const div = document.createElement("div");
3636
+ div.textContent = text;
3637
+ return div.innerHTML;
3638
+ }
3361
3639
  /**
3362
3640
  * Override select to handle table-specific selection UI
3363
3641
  */
@@ -3391,6 +3669,7 @@ class TableView extends ListView {
3391
3669
  ...options
3392
3670
  };
3393
3671
  super(tableOptions);
3672
+ this.isFullscreen = false;
3394
3673
  this.columns = options.columns || [];
3395
3674
  this.actions = options.actions || null;
3396
3675
  this.contextMenu = options.contextMenu || null;
@@ -3434,7 +3713,20 @@ class TableView extends ListView {
3434
3713
  this.searchPlaceholder = options.searchPlaceholder || "Search...";
3435
3714
  this.initializeColumns();
3436
3715
  this.extractColumnFilters();
3716
+ this.footerTotalColumns = this.columns.filter((col) => col.footer_total === true);
3717
+ this.hasFooterTotals = this.footerTotalColumns.length > 0;
3437
3718
  this.template = this.buildTableTemplate();
3719
+ this.setupCollectionListeners();
3720
+ }
3721
+ /**
3722
+ * Setup collection event listeners for totals updates
3723
+ */
3724
+ setupCollectionListeners() {
3725
+ if (this.hasFooterTotals && this.collection) {
3726
+ this.collection.on("reset add remove change", () => {
3727
+ this.updateFooterTotals();
3728
+ });
3729
+ }
3438
3730
  }
3439
3731
  /**
3440
3732
  * Initialize column configuration
@@ -3463,6 +3755,80 @@ class TableView extends ListView {
3463
3755
  }
3464
3756
  return `d-none d-${visibility}-table-cell`;
3465
3757
  }
3758
+ /**
3759
+ * Extract column key and formatter from combined key (e.g., "sales_amount|currency")
3760
+ */
3761
+ parseColumnKey(key) {
3762
+ const parts = key.split("|");
3763
+ return {
3764
+ fieldKey: parts[0],
3765
+ formatter: parts[1] || null
3766
+ };
3767
+ }
3768
+ /**
3769
+ * Update footer totals in the DOM without full re-render
3770
+ */
3771
+ updateFooterTotals() {
3772
+ if (!this.hasFooterTotals || !this.element) return;
3773
+ const totals = this.calculateFooterTotals();
3774
+ console.log("Updating footer totals in DOM:", totals);
3775
+ let totalColumnIndex = 0;
3776
+ this.columns.forEach((column) => {
3777
+ if (column.footer_total) {
3778
+ const safeKey = `col_${totalColumnIndex}`;
3779
+ const cell = this.element.querySelector(`[data-total-column="${safeKey}"]`);
3780
+ if (cell && totals[safeKey]) {
3781
+ const formatter = this.parseColumnKey(column.key).formatter || column.formatter;
3782
+ let displayValue;
3783
+ if (formatter && typeof formatter === "string") {
3784
+ displayValue = this.formatValue(totals[safeKey].value, formatter);
3785
+ } else {
3786
+ displayValue = totals[safeKey].value;
3787
+ }
3788
+ cell.textContent = displayValue;
3789
+ }
3790
+ totalColumnIndex++;
3791
+ }
3792
+ });
3793
+ }
3794
+ /**
3795
+ * Format a value using DataFormatter
3796
+ */
3797
+ formatValue(value, formatter) {
3798
+ try {
3799
+ return dataFormatter.pipe(value, formatter);
3800
+ } catch (e) {
3801
+ console.warn("Error formatting value:", e);
3802
+ return value;
3803
+ }
3804
+ }
3805
+ /**
3806
+ * Calculate totals for footer columns
3807
+ */
3808
+ calculateFooterTotals() {
3809
+ if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {
3810
+ return {};
3811
+ }
3812
+ const totals = {};
3813
+ this.footerTotalColumns.forEach((column, totalColumnIndex) => {
3814
+ const { fieldKey, formatter } = this.parseColumnKey(column.key);
3815
+ let sum = 0;
3816
+ this.collection.forEach((model) => {
3817
+ const value = model.get ? model.get(fieldKey) : model[fieldKey];
3818
+ const numValue = parseFloat(value) || 0;
3819
+ sum += numValue;
3820
+ });
3821
+ console.log(`Footer total for ${column.key}: ${sum} (from ${this.collection.length} items)`);
3822
+ const safeKey = `col_${totalColumnIndex}`;
3823
+ totals[safeKey] = {
3824
+ value: sum,
3825
+ formatter: formatter || column.formatter,
3826
+ fieldKey,
3827
+ originalKey: column.key
3828
+ };
3829
+ });
3830
+ return totals;
3831
+ }
3466
3832
  /**
3467
3833
  * Extract filters from column configuration
3468
3834
  */
@@ -3474,14 +3840,6 @@ class TableView extends ListView {
3474
3840
  }
3475
3841
  });
3476
3842
  }
3477
- /**
3478
- * Override getTemplateData to provide dynamic values for Mustache
3479
- */
3480
- getTemplateData() {
3481
- const data = super.getTemplateData();
3482
- data.searchValue = this.getActiveFilters().search || "";
3483
- return data;
3484
- }
3485
3843
  isSelectable() {
3486
3844
  return this.batchActions && this.batchActions.length > 0 && this.selectionMode == "multiple";
3487
3845
  }
@@ -3514,6 +3872,7 @@ class TableView extends ListView {
3514
3872
  <table class="${this.buildTableClasses()}">
3515
3873
  ${this.buildTableHeaderTemplate()}
3516
3874
  <tbody data-container="items"></tbody>
3875
+ ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ""}
3517
3876
  </table>
3518
3877
  {{/isEmpty}}
3519
3878
  {{/loading}}
@@ -3568,6 +3927,15 @@ class TableView extends ListView {
3568
3927
  <i class="bi bi-arrow-clockwise"></i>
3569
3928
  </button>
3570
3929
  `);
3930
+ if (this.isFullscreenSupported()) {
3931
+ buttons.push(`
3932
+ <button class="btn btn-sm btn-outline-secondary btn-fullscreen"
3933
+ data-action="toggle-fullscreen"
3934
+ title="Toggle Fullscreen">
3935
+ <i class="bi bi-fullscreen"></i>
3936
+ </button>
3937
+ `);
3938
+ }
3571
3939
  if (this.options.showAdd) {
3572
3940
  buttons.push(`
3573
3941
  <button class="btn btn-sm btn-success btn-add"
@@ -3850,6 +4218,47 @@ class TableView extends ListView {
3850
4218
  </thead>
3851
4219
  `;
3852
4220
  }
4221
+ /**
4222
+ * Build table footer template with totals
4223
+ */
4224
+ buildTableFooterTemplate() {
4225
+ let footerCells = "";
4226
+ if (this.isSelectable()) {
4227
+ footerCells += "<td></td>";
4228
+ }
4229
+ let totalColumnIndex = 0;
4230
+ this.columns.forEach((column, index) => {
4231
+ const responsiveClasses = this.getResponsiveClasses(column.visibility);
4232
+ if (column.footer_total) {
4233
+ const safeKey = `col_${totalColumnIndex}`;
4234
+ const formatter = this.parseColumnKey(column.key).formatter || column.formatter;
4235
+ let cellContent;
4236
+ if (formatter && typeof formatter === "string") {
4237
+ cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;
4238
+ } else {
4239
+ cellContent = `{{footerTotals.${safeKey}.value}}`;
4240
+ }
4241
+ footerCells += `<td class="table-footer-total ${responsiveClasses}" data-total-column="${safeKey}">${cellContent}</td>`;
4242
+ totalColumnIndex++;
4243
+ } else if (index === 0) {
4244
+ footerCells += `<td class="table-footer-label ${responsiveClasses}"><strong>Totals</strong></td>`;
4245
+ } else {
4246
+ footerCells += `<td class="${responsiveClasses}"></td>`;
4247
+ }
4248
+ });
4249
+ if (this.actions) {
4250
+ footerCells += "<td></td>";
4251
+ } else if (this.contextMenu) {
4252
+ footerCells += "<td></td>";
4253
+ }
4254
+ return `
4255
+ <tfoot>
4256
+ <tr class="table-totals-row">
4257
+ ${footerCells}
4258
+ </tr>
4259
+ </tfoot>
4260
+ `;
4261
+ }
3853
4262
  /**
3854
4263
  * Build batch actions panel
3855
4264
  */
@@ -3986,6 +4395,9 @@ class TableView extends ListView {
3986
4395
  itemView.on("row:view", this._onRowView.bind(this));
3987
4396
  itemView.on("row:edit", this._onRowEdit.bind(this));
3988
4397
  itemView.on("row:delete", this._onRowDelete.bind(this));
4398
+ itemView.on("cell:edit", this._onCellEdit.bind(this));
4399
+ itemView.on("cell:save", this._onCellSave.bind(this));
4400
+ itemView.on("cell:cancel", this._onCellCancel.bind(this));
3989
4401
  return itemView;
3990
4402
  }
3991
4403
  /**
@@ -4165,6 +4577,140 @@ class TableView extends ListView {
4165
4577
  this.collection.remove(event.model);
4166
4578
  }
4167
4579
  }
4580
+ /**
4581
+ * Handle cell edit event
4582
+ */
4583
+ _onCellEdit(event) {
4584
+ this.emit("cell:edit", event);
4585
+ }
4586
+ /**
4587
+ * Handle cell save event
4588
+ */
4589
+ async _onCellSave(event) {
4590
+ this.emit("cell:save", event);
4591
+ }
4592
+ /**
4593
+ * Handle cell cancel event
4594
+ */
4595
+ _onCellCancel(event) {
4596
+ this.emit("cell:cancel", event);
4597
+ }
4598
+ /**
4599
+ * Check if fullscreen is supported by the browser
4600
+ */
4601
+ isFullscreenSupported() {
4602
+ return !!(document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled);
4603
+ }
4604
+ /**
4605
+ * Handle toggle fullscreen action
4606
+ */
4607
+ async onActionToggleFullscreen(event, element) {
4608
+ if (this.isFullscreen) {
4609
+ await this.exitFullscreen();
4610
+ } else {
4611
+ await this.enterFullscreen();
4612
+ }
4613
+ }
4614
+ /**
4615
+ * Enter fullscreen mode
4616
+ */
4617
+ async enterFullscreen() {
4618
+ try {
4619
+ if (this.element.requestFullscreen) {
4620
+ await this.element.requestFullscreen();
4621
+ } else if (this.element.mozRequestFullScreen) {
4622
+ await this.element.mozRequestFullScreen();
4623
+ } else if (this.element.webkitRequestFullscreen) {
4624
+ await this.element.webkitRequestFullscreen();
4625
+ } else if (this.element.msRequestFullscreen) {
4626
+ await this.element.msRequestFullscreen();
4627
+ }
4628
+ this.isFullscreen = true;
4629
+ this.element.classList.add("table-fullscreen");
4630
+ this.updateFullscreenButton();
4631
+ this.setupFullscreenListeners();
4632
+ this.emit("table:fullscreen:enter");
4633
+ } catch (error) {
4634
+ console.warn("Could not enter fullscreen:", error);
4635
+ }
4636
+ }
4637
+ /**
4638
+ * Exit fullscreen mode
4639
+ */
4640
+ async exitFullscreen() {
4641
+ try {
4642
+ if (document.exitFullscreen) {
4643
+ await document.exitFullscreen();
4644
+ } else if (document.mozCancelFullScreen) {
4645
+ await document.mozCancelFullScreen();
4646
+ } else if (document.webkitExitFullscreen) {
4647
+ await document.webkitExitFullscreen();
4648
+ } else if (document.msExitFullscreen) {
4649
+ await document.msExitFullscreen();
4650
+ }
4651
+ this.isFullscreen = false;
4652
+ this.element.classList.remove("table-fullscreen");
4653
+ this.updateFullscreenButton();
4654
+ this.emit("table:fullscreen:exit");
4655
+ } catch (error) {
4656
+ console.warn("Could not exit fullscreen:", error);
4657
+ }
4658
+ }
4659
+ /**
4660
+ * Update fullscreen button icon and title
4661
+ */
4662
+ updateFullscreenButton() {
4663
+ const button = this.element?.querySelector(".btn-fullscreen");
4664
+ const icon = button?.querySelector("i");
4665
+ if (button && icon) {
4666
+ if (this.isFullscreen) {
4667
+ icon.className = "bi bi-fullscreen-exit";
4668
+ button.title = "Exit Fullscreen";
4669
+ } else {
4670
+ icon.className = "bi bi-fullscreen";
4671
+ button.title = "Enter Fullscreen";
4672
+ }
4673
+ }
4674
+ }
4675
+ /**
4676
+ * Setup fullscreen event listeners
4677
+ */
4678
+ setupFullscreenListeners() {
4679
+ if (this._fullscreenHandler) return;
4680
+ const handleFullscreenChange = () => {
4681
+ const isCurrentlyFullscreen = !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
4682
+ if (!isCurrentlyFullscreen && this.isFullscreen) {
4683
+ this.isFullscreen = false;
4684
+ this.element.classList.remove("table-fullscreen");
4685
+ this.updateFullscreenButton();
4686
+ this.emit("table:fullscreen:exit");
4687
+ }
4688
+ };
4689
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
4690
+ document.addEventListener("mozfullscreenchange", handleFullscreenChange);
4691
+ document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
4692
+ document.addEventListener("msfullscreenchange", handleFullscreenChange);
4693
+ this._fullscreenHandler = handleFullscreenChange;
4694
+ }
4695
+ /**
4696
+ * Cleanup fullscreen listeners
4697
+ */
4698
+ cleanupFullscreenListeners() {
4699
+ if (this._fullscreenHandler) {
4700
+ document.removeEventListener("fullscreenchange", this._fullscreenHandler);
4701
+ document.removeEventListener("mozfullscreenchange", this._fullscreenHandler);
4702
+ document.removeEventListener("webkitfullscreenchange", this._fullscreenHandler);
4703
+ document.removeEventListener("msfullscreenchange", this._fullscreenHandler);
4704
+ this._fullscreenHandler = null;
4705
+ }
4706
+ }
4707
+ /**
4708
+ * Override destroy to cleanup fullscreen listeners
4709
+ */
4710
+ destroy() {
4711
+ this.cleanupFullscreenListeners();
4712
+ super.destroy();
4713
+ }
4168
4714
  /**
4169
4715
  * Handle refresh action
4170
4716
  */
@@ -4410,11 +4956,23 @@ class TableView extends ListView {
4410
4956
  }
4411
4957
  this.updateBatchActionsPanel();
4412
4958
  }
4959
+ /**
4960
+ * Override render to set data properties before rendering
4961
+ */
4962
+ async render(force, container) {
4963
+ this.searchValue = this.getActiveFilters().search || "";
4964
+ this.footerTotals = this.calculateFooterTotals();
4965
+ console.log("Setting footerTotals before render:", this.footerTotals);
4966
+ return super.render(force, container);
4967
+ }
4413
4968
  /**
4414
4969
  * Override onAfterRender to update pagination info
4415
4970
  */
4416
4971
  async onAfterRender() {
4417
4972
  await super.onAfterRender();
4973
+ if (this.hasFooterTotals) {
4974
+ this.updateFooterTotals();
4975
+ }
4418
4976
  if (this.paginated && this.collection) {
4419
4977
  const total = this.collection.meta?.count || this.collection.length();
4420
4978
  const start = this.collection.params?.start || 0;
@@ -5962,4 +6520,4 @@ export {
5962
6520
  IncidentEventList as y,
5963
6521
  IncidentEventForms as z
5964
6522
  };
5965
- //# sourceMappingURL=FilePreviewView-Crpalaos.js.map
6523
+ //# sourceMappingURL=FilePreviewView-C-VswdQl.js.map