mensa 0.9.2 → 0.9.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 651651e83e9d9140b6c5b0f63cc193afd84399ec3af2d511da59be36d56dae44
4
- data.tar.gz: 443c135b4e673f6334db2b99690c60a2b8c7323d81a8c31b2d36f3e132856cff
3
+ metadata.gz: 63364b3ef1004b6f44cb7188209c0924553d7e141f52062df2c4e06bf55cca41
4
+ data.tar.gz: d213f088d9a5f07af9f3502318f23095a0654fb7ea0738a7012c520b0fe5ad21
5
5
  SHA512:
6
- metadata.gz: ec46ba8fcc59ab4c193d4d912de77bdc9ebec4aac87c93983be02c1422d979a1558d9368a6f816d9ba9579b6c56fd928b412d73b39b9924838d62aff8a259467
7
- data.tar.gz: '0491edbee70617b3293afcc8564f5fdb22379563bada952d74bcac0e40212fdf16a76e5e63c8d77258441779348c22147067c28b8fc222c938e45a5f29396dde'
6
+ metadata.gz: 7a0aa6a1675642a482b9d616370755f0cd46b315997d3082ac4ecf4d510116f32d235909cf28a3f7e123b427f88dc3f68c3ccd04a2a09dd5f8515fae7d4e4bca
7
+ data.tar.gz: c89e70746c216146cbaceb589e485dc43a63c37d98f92ad8c420e021eae40f2c04af90130bacd9c90a68b698b0cb57386ff993ce75285638b31361db5dbdb141
data/.shipkit.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- previous_version: 0.9.1
2
+ previous_version: 0.9.3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.3
4
+
5
+ - A Mensa table can be rendered inside a host `<form>`, such as a tab on an
6
+ edit page. Before, the save-view, rename-view and export dialogs (and the
7
+ delete button of each export) rendered their own `<form>`s. HTML has no
8
+ nested forms, so the outer form was closed early, its submit was silently
9
+ blocked by Mensa's empty `required` inputs, and the export options were
10
+ sent along with it. Mensa now renders no `<form>` elements, no `required`
11
+ attributes and no controls that a host form submits.
12
+ - The save-view and rename-view dialogs confirm with their button or Enter in
13
+ the name field. An empty name still shows the browser's validation message,
14
+ now from the translatable `mensa.search.component.view_name_required` and
15
+ `mensa.views.component.view_name_required` keys.
16
+ - **Behaviour change:** the export dialog's controls have a `form` attribute
17
+ that matches no element and names that include the table id (e.g.
18
+ `mensa-export-users-scope`), so they never belong to a host form and don't
19
+ group with radios elsewhere on the page. Select them with
20
+ `data-mensa-export-field="scope"` (or `export_format`, `repeat_mode`,
21
+ `repeat`) instead of `name`.
22
+ - **Behaviour change:** the search input no longer has `name="search_query"`,
23
+ and the `.mensa-table__export-dialog__delete-form` class is gone: an export
24
+ is deleted with a plain `button.mensa-table__export-dialog__delete`.
25
+ - Group header aggregates of `bigint` columns no longer show a trailing `.0`
26
+ (`3,000` instead of `3,000.0`). Postgres returns their SUM as `numeric`;
27
+ whole numbers are now shown without decimals.
28
+ - A selected view survives a page reload when the default view has filters.
29
+ Before, the table showed the selected view's rows, but the views dropdown
30
+ and the filter pills still showed the default view.
31
+ - A view filter that the user changes (another value or operator) becomes a
32
+ user filter: it's always shown, and the eye button disappears when no
33
+ unchanged view filters remain. Before, a pill was a view filter as long as
34
+ the view filtered on its column, so a changed filter could still be hidden.
35
+ - The views dropdown is positioned `fixed`, like the other popovers, so a
36
+ table with few rows (or a host element with `overflow: hidden`) no longer
37
+ clips it.
38
+
3
39
  ## 0.9.2
4
40
 
5
41
  - The table tour no longer needs the `driver.js` npm package. Mensa now ships
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mensa (0.9.2)
4
+ mensa (0.9.3)
5
5
  csv
6
6
  importmap-rails
7
7
  pagy (>= 43)
@@ -63,12 +63,8 @@
63
63
  @apply flex shrink-0 items-center gap-3 px-3 py-2.5;
64
64
  }
65
65
 
66
- .mensa-table__export-dialog__delete-form {
67
- @apply shrink-0;
68
- }
69
-
70
66
  .mensa-table__export-dialog__delete {
71
- @apply inline-flex h-9 w-9 items-center justify-center rounded-md text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-red-600 dark:hover:text-red-400 focus:outline-none focus:ring-2 focus:ring-red-500/30 transition-colors cursor-pointer;
67
+ @apply shrink-0 inline-flex h-9 w-9 items-center justify-center rounded-md text-gray-400 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-red-600 dark:hover:text-red-400 focus:outline-none focus:ring-2 focus:ring-red-500/30 transition-colors cursor-pointer;
72
68
  }
73
69
 
74
70
  .mensa-table__export-dialog__item-info {
@@ -11,13 +11,27 @@ module Mensa
11
11
  @filter = filter
12
12
  end
13
13
 
14
+ # A pill is a view filter only while it still filters like the view does,
15
+ # once the user changes it, it's their own filter.
14
16
  def view_filter?
15
17
  view = filter.table.table_view
16
18
  return false unless view
17
19
 
18
20
  view_filters = view.config&.dig(:filters) || view.config&.dig("filters") || {}
19
21
  col = filter.column.name.to_s
20
- view_filters.key?(col) || view_filters.key?(col.to_sym)
22
+ view_filter = view_filters[col] || view_filters[col.to_sym]
23
+ return false unless view_filter
24
+
25
+ view_filter = view_filter.to_h.with_indifferent_access
26
+ return false if view_filter[:operator].present? && view_filter[:operator].to_sym != filter.operator
27
+
28
+ comparable(view_filter[:value]) == comparable(filter.value)
29
+ end
30
+
31
+ private
32
+
33
+ def comparable(value)
34
+ Array(value).map(&:to_s).reject(&:blank?).sort
21
35
  end
22
36
  end
23
37
  end
@@ -18,7 +18,6 @@
18
18
  <div class="mensa-table__search-bar__input-wrapper">
19
19
  <input class="mensa-table__search-bar__input"
20
20
  type="text"
21
- name="search_query"
22
21
  autocomplete="off"
23
22
  placeholder="<%= t(has_filterable_columns ? ".search" : ".search_only") %>"
24
23
  data-mensa-filter-pill-list-target="searchInput"
@@ -319,8 +319,11 @@ export default class FilterPillListComponentController extends ApplicationContro
319
319
  const filtersDiffer =
320
320
  filtersProvided &&
321
321
  JSON.stringify(rendered) !== JSON.stringify(filters);
322
+ // The pills belong to the view the server rendered, so they only count
323
+ // when the stored view is that same view.
324
+ const viewDiffers = view.length > 0 && view !== this.renderedView();
322
325
  const alreadyRendered =
323
- Object.keys(rendered).length > 0 && !filtersDiffer;
326
+ Object.keys(rendered).length > 0 && !filtersDiffer && !viewDiffers;
324
327
 
325
328
  if (!hasState || alreadyRendered || table.frameLoadHandled) {
326
329
  if (typeof table.loadFrame === "function") {
@@ -546,6 +549,18 @@ export default class FilterPillListComponentController extends ApplicationContro
546
549
  this._monitorResetButton();
547
550
  }
548
551
 
552
+ // The id of the view checked in the views dropdown, "" without views.
553
+ renderedView() {
554
+ const root = this.element.closest(".mensa-table");
555
+ const check = root?.querySelector(
556
+ '[data-mensa-views-target="view"] .mensa-table__views__option-check:not(.invisible)',
557
+ );
558
+ return (
559
+ check?.closest('[data-mensa-views-target="view"]')?.dataset
560
+ .viewId || ""
561
+ );
562
+ }
563
+
549
564
  setViewHighlight(view) {
550
565
  if (!view) return;
551
566
 
@@ -577,9 +592,7 @@ export default class FilterPillListComponentController extends ApplicationContro
577
592
  `[data-mensa-views-target="view"][data-view-id="${view}"]`,
578
593
  );
579
594
  if (viewEl) {
580
- const name =
581
- viewEl.dataset.viewName ||
582
- viewEl.querySelector("span")?.textContent?.trim();
595
+ const name = viewEl.dataset.viewName;
583
596
  if (name) triggerLabel.textContent = name;
584
597
  }
585
598
  }
@@ -1,13 +1,13 @@
1
1
  <% if table.current_user %>
2
2
  <dialog class="mensa-table__save-view-dialog" data-mensa-table-target="saveViewDialog" data-action="click->mensa-table#saveViewDialogBackdrop">
3
- <form class="mensa-table__save-view-dialog__form" data-action="submit->mensa-table#confirmSaveView">
3
+ <div class="mensa-table__save-view-dialog__form">
4
4
  <header class="mensa-table__save-view-dialog__header">
5
5
  <h2 class="mensa-table__save-view-dialog__title"><%= t(".save_view_title", default: "Save view") %></h2>
6
6
  <p class="mensa-table__save-view-dialog__subtitle"><%= t(".save_view_subtitle", default: "Save the current filters, ordering and search as a reusable view.") %></p>
7
7
  </header>
8
8
  <label class="mensa-table__save-view-dialog__field">
9
9
  <span class="mensa-table__save-view-dialog__label"><%= t(".view_name", default: "Name") %></span>
10
- <input class="mensa-table__save-view-dialog__input" type="text" required placeholder="<%= t(".view_name_placeholder", default: "e.g. Active customers") %>" data-mensa-table-target="saveViewName">
10
+ <input class="mensa-table__save-view-dialog__input" type="text" aria-required="true" placeholder="<%= t(".view_name_placeholder", default: "e.g. Active customers") %>" data-required-message="<%= t(".view_name_required", default: "Please enter a name for this view.") %>" data-mensa-table-target="saveViewName" data-action="keydown.enter->mensa-table#confirmSaveView input->mensa-table#clearValidity">
11
11
  </label>
12
12
  <label class="mensa-table__save-view-dialog__field">
13
13
  <span class="mensa-table__save-view-dialog__label"><%= t(".view_description", default: "Description") %></span>
@@ -17,10 +17,10 @@
17
17
  <button class="mensa-table__save-view-dialog__button mensa-table__save-view-dialog__button--secondary" type="button" data-action="mensa-table#cancelSaveView">
18
18
  <%= t(".cancel") %>
19
19
  </button>
20
- <button class="mensa-table__save-view-dialog__button mensa-table__save-view-dialog__button--primary" type="submit">
20
+ <button class="mensa-table__save-view-dialog__button mensa-table__save-view-dialog__button--primary" type="button" data-action="mensa-table#confirmSaveView">
21
21
  <%= t(".save") %>
22
22
  </button>
23
23
  </div>
24
- </form>
24
+ </div>
25
25
  </dialog>
26
26
  <% end %>
@@ -1,5 +1,5 @@
1
1
  import ApplicationController from "mensa/controllers/application_controller";
2
- import { get, post, patch } from "@rails/request.js";
2
+ import { get, post, patch, destroy } from "@rails/request.js";
3
3
 
4
4
  export default class TableComponentController extends ApplicationController {
5
5
  static targets = [
@@ -300,6 +300,8 @@ export default class TableComponentController extends ApplicationController {
300
300
  }
301
301
 
302
302
  async confirmSaveView(event) {
303
+ // Called from a button click or Enter in the name input. The table may
304
+ // sit inside a host <form>; this stops Enter from submitting it.
303
305
  event.preventDefault();
304
306
 
305
307
  const name = this.hasSaveViewNameTarget
@@ -307,7 +309,7 @@ export default class TableComponentController extends ApplicationController {
307
309
  : "";
308
310
  if (!name) {
309
311
  if (this.hasSaveViewNameTarget)
310
- this.saveViewNameTarget.reportValidity();
312
+ this.reportMissing(this.saveViewNameTarget);
311
313
  return;
312
314
  }
313
315
 
@@ -560,28 +562,18 @@ export default class TableComponentController extends ApplicationController {
560
562
  );
561
563
  if (!options) return;
562
564
 
563
- const mode = dialog.querySelector(
564
- 'input[name="repeat_mode"]:checked',
565
- )?.value;
566
- options.hidden = mode !== "repeating";
565
+ options.hidden = this._exportField("repeat_mode") !== "repeating";
567
566
  }
568
567
 
569
568
  confirmExport(event) {
570
- event.preventDefault();
569
+ if (event) event.preventDefault();
571
570
  if (!this.hasExportsUrlValue) return;
572
571
 
573
- const dialog = this.exportDialogTarget;
574
- const scope =
575
- dialog.querySelector('input[name="scope"]:checked')?.value || "all";
576
- const exportFormat =
577
- dialog.querySelector('input[name="export_format"]:checked')
578
- ?.value || "csv_excel";
579
- const repeatMode = dialog.querySelector(
580
- 'input[name="repeat_mode"]:checked',
581
- )?.value;
572
+ const scope = this._exportField("scope") || "all";
573
+ const exportFormat = this._exportField("export_format") || "csv_excel";
582
574
  const repeat =
583
- repeatMode === "repeating"
584
- ? dialog.querySelector('select[name="repeat"]')?.value || ""
575
+ this._exportField("repeat_mode") === "repeating"
576
+ ? this._exportField("repeat") || ""
585
577
  : "";
586
578
 
587
579
  const state = this.currentViewState();
@@ -611,6 +603,27 @@ export default class TableComponentController extends ApplicationController {
611
603
  });
612
604
  }
613
605
 
606
+ deleteExport(event) {
607
+ if (event) event.preventDefault();
608
+ const url = event.currentTarget.dataset.url;
609
+ if (!url) return;
610
+
611
+ destroy(url, { responseKind: "turbo-stream" });
612
+ }
613
+
614
+ // Shows the browser's validation bubble for an empty text input. The
615
+ // inputs aren't `required` (that would block a host <form> around the
616
+ // table), so the message is set by hand and cleared again on input.
617
+ reportMissing(input) {
618
+ input.setCustomValidity(input.dataset.requiredMessage || "");
619
+ input.reportValidity();
620
+ input.focus();
621
+ }
622
+
623
+ clearValidity(event) {
624
+ event.currentTarget.setCustomValidity("");
625
+ }
626
+
614
627
  get ourUrl() {
615
628
  if (
616
629
  this.hasTurboFrameTarget &&
@@ -629,6 +642,17 @@ export default class TableComponentController extends ApplicationController {
629
642
 
630
643
  // --- Private ---
631
644
 
645
+ // Export controls are found by data attribute: their names carry the
646
+ // table id so they don't group with radios elsewhere on the page.
647
+ _exportField(field) {
648
+ const dialog = this.exportDialogTarget;
649
+ const selector = `[data-mensa-export-field="${field}"]`;
650
+ const control =
651
+ dialog.querySelector(`input${selector}:checked`) ||
652
+ dialog.querySelector(`select${selector}`);
653
+ return control?.value;
654
+ }
655
+
632
656
  // Reads the currently-selected view ID from the DOM (after Turbo re-renders
633
657
  // the views component) and persists it to localStorage.
634
658
  _persistSelectedViewId() {
@@ -674,7 +698,10 @@ export default class TableComponentController extends ApplicationController {
674
698
 
675
699
  _openSaveDialog() {
676
700
  if (!this.hasSaveViewDialogTarget) return;
677
- if (this.hasSaveViewNameTarget) this.saveViewNameTarget.value = "";
701
+ if (this.hasSaveViewNameTarget) {
702
+ this.saveViewNameTarget.value = "";
703
+ this.saveViewNameTarget.setCustomValidity("");
704
+ }
678
705
  if (this.hasSaveViewDescriptionTarget)
679
706
  this.saveViewDescriptionTarget.value = "";
680
707
  if (typeof this.saveViewDialogTarget.showModal === "function") {
@@ -11,7 +11,7 @@
11
11
  }
12
12
 
13
13
  .mensa-table__views__dropdown {
14
- @apply absolute left-0 top-full z-20 mt-1 min-w-[12rem] rounded-lg bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10 py-1;
14
+ @apply fixed z-50 min-w-[12rem] rounded-lg bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10 py-1;
15
15
  }
16
16
 
17
17
  .mensa-table__views__option {
@@ -23,7 +23,7 @@
23
23
  }
24
24
 
25
25
  .mensa-table__views__option-check {
26
- @apply text-primary-600 text-xs w-4;
26
+ @apply inline-flex justify-center shrink-0 text-primary-600 text-xs w-4;
27
27
  }
28
28
 
29
29
  .mensa-table__views__option-menu {
@@ -22,7 +22,8 @@
22
22
  <% is_user_view = view.is_a?(Mensa::TableView) %>
23
23
  <li class="mensa-table__views__option" role="option" data-view-id="<%= view.id %>" data-view-name="<%= view.name %>" data-mensa-views-target="view">
24
24
  <button class="mensa-table__views__option-btn" type="button" data-action="mensa-views#select" data-view-id="<%= view.id %>">
25
- <i class="mensa-table__views__option-check <%= Mensa.config.icons[:views_option_selected] %><%= " invisible" unless is_selected %>"></i>
25
+ <%# The state is on the span: FontAwesome swaps the <i> for an <svg> asynchronously, copying classes that may be stale by then %>
26
+ <span class="mensa-table__views__option-check<%= " invisible" unless is_selected %>"><i class="<%= Mensa.config.icons[:views_option_selected] %>"></i></span>
26
27
  <span><%= view.name %></span>
27
28
  </button>
28
29
  <% if is_user_view %>
@@ -55,21 +56,21 @@
55
56
  </div>
56
57
 
57
58
  <dialog class="mensa-table__views__rename-dialog" data-mensa-views-target="renameDialog" data-action="click->mensa-views#renameDialogBackdrop">
58
- <form class="mensa-table__views__rename-form" data-action="submit->mensa-views#confirmRename">
59
+ <div class="mensa-table__views__rename-form">
59
60
  <input type="hidden" data-mensa-views-target="renameViewId">
60
61
  <h3 class="mensa-table__views__rename-title"><%= t(".rename_view", default: "Rename view") %></h3>
61
62
  <label class="mensa-table__views__rename-label">
62
63
  <%= t(".view_name", default: "Name") %>
63
- <input class="mensa-table__views__rename-input" type="text" required placeholder="<%= t(".view_name_placeholder", default: "View name") %>" data-mensa-views-target="renameInput">
64
+ <input class="mensa-table__views__rename-input" type="text" aria-required="true" placeholder="<%= t(".view_name_placeholder", default: "View name") %>" data-required-message="<%= t(".view_name_required", default: "Please enter a name for this view.") %>" data-mensa-views-target="renameInput" data-action="keydown.enter->mensa-views#confirmRename input->mensa-views#clearRenameValidity">
64
65
  </label>
65
66
  <div class="mensa-table__views__rename-actions">
66
67
  <button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--secondary" type="button" data-action="mensa-views#cancelRename">
67
68
  <%= t(".cancel", default: "Cancel") %>
68
69
  </button>
69
- <button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--primary" type="submit">
70
+ <button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--primary" type="button" data-action="mensa-views#confirmRename">
70
71
  <%= t(".rename", default: "Rename") %>
71
72
  </button>
72
73
  </div>
73
- </form>
74
+ </div>
74
75
  </dialog>
75
76
  </div>
@@ -42,7 +42,7 @@ export default class ViewsComponentController extends ApplicationController {
42
42
  check?.classList.toggle("invisible", !isThis);
43
43
  });
44
44
 
45
- const viewName = event.currentTarget.querySelector("span")?.textContent?.trim() || "";
45
+ const viewName = selected.closest("[data-mensa-views-target='view']")?.dataset.viewName || "";
46
46
  if (this.hasTriggerLabelTarget) {
47
47
  this.triggerLabelTarget.textContent = viewName;
48
48
  }
@@ -90,6 +90,7 @@ export default class ViewsComponentController extends ApplicationController {
90
90
  if (this.hasRenameDialogTarget) {
91
91
  this.renameViewIdTarget.value = viewId;
92
92
  this.renameInputTarget.value = viewName;
93
+ this.renameInputTarget.setCustomValidity("");
93
94
  if (typeof this.renameDialogTarget.showModal === "function") {
94
95
  this.renameDialogTarget.showModal();
95
96
  } else {
@@ -111,11 +112,18 @@ export default class ViewsComponentController extends ApplicationController {
111
112
  }
112
113
 
113
114
  async confirmRename(event) {
115
+ // Called from a button click or Enter in the name input. The table may
116
+ // sit inside a host <form>; this stops Enter from submitting it.
114
117
  event.preventDefault();
115
118
  const viewId = this.renameViewIdTarget.value;
116
119
  const newName = this.renameInputTarget.value.trim();
117
120
  if (!newName) {
118
- this.renameInputTarget.reportValidity();
121
+ // The input isn't `required` (that would block a host <form>), so
122
+ // the browser's message is set by hand and cleared on input.
123
+ const input = this.renameInputTarget;
124
+ input.setCustomValidity(input.dataset.requiredMessage || "");
125
+ input.reportValidity();
126
+ input.focus();
119
127
  return;
120
128
  }
121
129
 
@@ -131,6 +139,10 @@ export default class ViewsComponentController extends ApplicationController {
131
139
  }
132
140
  }
133
141
 
142
+ clearRenameValidity() {
143
+ this.renameInputTarget.setCustomValidity("");
144
+ }
145
+
134
146
  async duplicateView(event) {
135
147
  event.preventDefault();
136
148
  const viewId = this.submenuTarget.dataset.viewId;
@@ -198,10 +210,19 @@ export default class ViewsComponentController extends ApplicationController {
198
210
  // --- private ---
199
211
 
200
212
  _openDropdown() {
213
+ this._positionDropdown();
201
214
  this.dropdownTarget.classList.remove("hidden");
202
215
  this._bindOutsideClick();
203
216
  }
204
217
 
218
+ // Fixed positioning with viewport coordinates, so a small table (or a host
219
+ // element with overflow hidden) doesn't clip the dropdown.
220
+ _positionDropdown() {
221
+ const rect = this.triggerTarget.getBoundingClientRect();
222
+ this.dropdownTarget.style.top = `${rect.bottom + 4}px`;
223
+ this.dropdownTarget.style.left = `${rect.left}px`;
224
+ }
225
+
205
226
  _closeDropdown() {
206
227
  this.dropdownTarget.classList.add("hidden");
207
228
  this._closeSubmenu();
@@ -34,6 +34,9 @@ module Mensa
34
34
  return unless aggregates.key?(column.name)
35
35
 
36
36
  value = aggregates[column.name]
37
+ # SUM of a bigint column comes back as a BigDecimal, which would show as
38
+ # "3,000.0". Whole numbers are shown without decimals.
39
+ value = value.to_i if value.is_a?(BigDecimal) && value.frac.zero?
37
40
  return ActiveSupport::NumberHelper.number_to_delimited(value) if value.is_a?(Numeric)
38
41
 
39
42
  Mensa::Cell.new(row: nil, column: column, value: value).formatted
@@ -14,15 +14,20 @@
14
14
  <%= render partial: "mensa/exports/list", locals: {table_name: table.name, user: user, table_params: table.params, exports: exports} %>
15
15
  </div>
16
16
 
17
- <form class="mensa-table__export-dialog__form" data-action="submit->mensa-table#confirmExport">
17
+ <%# These controls belong to no form: form= names an element that doesn't
18
+ exist, so a host <form> around the table neither submits nor
19
+ validates them. Radios without a form owner group by name across
20
+ the whole document, so the names carry the table id. %>
21
+ <% form_id = "mensa-export-#{table.table_id}" %>
22
+ <div class="mensa-table__export-dialog__form">
18
23
  <fieldset class="mensa-table__export-dialog__fieldset" data-mensa-tour="export_scope">
19
24
  <legend class="mensa-table__export-dialog__section-title"><%= t("mensa.exports.new_export", default: "New export") %></legend>
20
25
  <label class="mensa-table__export-dialog__option">
21
- <input type="radio" name="scope" value="all" checked>
26
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-scope" data-mensa-export-field="scope" value="all" checked>
22
27
  <span><%= t("mensa.exports.scope_all", default: "All records (matching current filters)") %></span>
23
28
  </label>
24
29
  <label class="mensa-table__export-dialog__option">
25
- <input type="radio" name="scope" value="current_page">
30
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-scope" data-mensa-export-field="scope" value="current_page">
26
31
  <span><%= t("mensa.exports.scope_current_page", default: "Current page") %></span>
27
32
  </label>
28
33
  </fieldset>
@@ -30,11 +35,11 @@
30
35
  <fieldset class="mensa-table__export-dialog__fieldset" data-mensa-tour="export_format">
31
36
  <legend class="mensa-table__export-dialog__section-title"><%= t("mensa.exports.export_as", default: "Export as") %></legend>
32
37
  <label class="mensa-table__export-dialog__option">
33
- <input type="radio" name="export_format" value="csv_excel" checked>
38
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-export_format" data-mensa-export-field="export_format" value="csv_excel" checked>
34
39
  <span><%= t("mensa.exports.format_excel", default: "CSV for Excel, Numbers, or other spreadsheet programs") %></span>
35
40
  </label>
36
41
  <label class="mensa-table__export-dialog__option">
37
- <input type="radio" name="export_format" value="plain_csv">
42
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-export_format" data-mensa-export-field="export_format" value="plain_csv">
38
43
  <span><%= t("mensa.exports.format_plain", default: "Plain CSV file") %></span>
39
44
  </label>
40
45
  </fieldset>
@@ -42,16 +47,16 @@
42
47
  <fieldset class="mensa-table__export-dialog__fieldset" data-mensa-tour="export_repeat">
43
48
  <legend class="mensa-table__export-dialog__section-title"><%= t("mensa.exports.repeat_section", default: "Repeat") %></legend>
44
49
  <label class="mensa-table__export-dialog__option">
45
- <input type="radio" name="repeat_mode" value="once" checked data-action="change->mensa-table#toggleExportRepeat">
50
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-repeat_mode" data-mensa-export-field="repeat_mode" value="once" checked data-action="change->mensa-table#toggleExportRepeat">
46
51
  <span><%= t("mensa.exports.repeat_once", default: "Only once") %></span>
47
52
  </label>
48
53
  <label class="mensa-table__export-dialog__option">
49
- <input type="radio" name="repeat_mode" value="repeating" data-action="change->mensa-table#toggleExportRepeat">
54
+ <input type="radio" form="<%= form_id %>" name="<%= form_id %>-repeat_mode" data-mensa-export-field="repeat_mode" value="repeating" data-action="change->mensa-table#toggleExportRepeat">
50
55
  <span><%= t("mensa.exports.repeat_repeating", default: "Repeating") %></span>
51
56
  </label>
52
57
 
53
58
  <div class="mensa-table__export-dialog__repeat-options" hidden data-mensa-table-repeat-options>
54
- <select name="repeat" class="mensa-table__export-dialog__select">
59
+ <select form="<%= form_id %>" name="<%= form_id %>-repeat" data-mensa-export-field="repeat" class="mensa-table__export-dialog__select">
55
60
  <option value="daily"><%= t("mensa.exports.repeat_intervals.daily", default: "daily") %></option>
56
61
  <option value="weekly"><%= t("mensa.exports.repeat_intervals.weekly", default: "weekly") %></option>
57
62
  <option value="monthly"><%= t("mensa.exports.repeat_intervals.monthly", default: "monthly") %></option>
@@ -66,12 +71,12 @@
66
71
  <button class="mensa-table__export-dialog__button mensa-table__export-dialog__button--secondary" type="button" data-action="mensa-table#cancelExport">
67
72
  <%= t("mensa.exports.cancel", default: "Cancel") %>
68
73
  </button>
69
- <button class="mensa-table__export-dialog__button mensa-table__export-dialog__button--primary" type="submit">
74
+ <button class="mensa-table__export-dialog__button mensa-table__export-dialog__button--primary" type="button" data-action="mensa-table#confirmExport">
70
75
  <i class="<%= Mensa.config.icons[:exports_dialog_submit] %>"></i>
71
76
  <%= t("mensa.exports.submit", default: "Export") %>
72
77
  </button>
73
78
  </div>
74
- </form>
79
+ </div>
75
80
  </div>
76
81
  </div>
77
82
  </dialog>
@@ -6,13 +6,13 @@
6
6
  <ul class="mensa-table__export-dialog__list">
7
7
  <% exports.each do |export| %>
8
8
  <li class="mensa-table__export-dialog__item">
9
- <%= button_to mensa.table_export_path(export.table_name, export),
10
- method: :delete,
11
- class: "mensa-table__export-dialog__delete",
12
- form_class: "mensa-table__export-dialog__delete-form",
13
- aria: {label: t("mensa.exports.delete", default: "Delete export") } do %>
9
+ <button class="mensa-table__export-dialog__delete"
10
+ type="button"
11
+ data-action="mensa-table#deleteExport"
12
+ data-url="<%= mensa.table_export_path(export.table_name, export) %>"
13
+ aria-label="<%= t("mensa.exports.delete", default: "Delete export") %>">
14
14
  <i class="<%= Mensa.config.icons[:exports_list_delete] %>"></i>
15
- <% end %>
15
+ </button>
16
16
 
17
17
  <div class="mensa-table__export-dialog__item-info">
18
18
  <span class="mensa-table__export-dialog__item-name"><%= export.filename.presence || t("mensa.exports.item_name", default: "%{table} export", table: export.table_name.to_s.humanize) %></span>
@@ -93,6 +93,7 @@ en:
93
93
  view_name_placeholder: e.g. Active customers
94
94
  view_description: Description
95
95
  view_description_placeholder: Optional notes about this view
96
+ view_name_required: Please enter a name for this view.
96
97
  exports:
97
98
  title: "Export %{table}"
98
99
  close: Close
@@ -132,3 +133,5 @@ en:
132
133
  clear_button: Clear search and filters
133
134
  views:
134
135
  default: All
136
+ component:
137
+ view_name_required: Please enter a name for this view.
@@ -93,6 +93,7 @@ nl:
93
93
  view_name_placeholder: bijv. Actieve klanten
94
94
  view_description: Omschrijving
95
95
  view_description_placeholder: Optionele notities over deze weergave
96
+ view_name_required: Vul een naam in voor deze weergave.
96
97
  exports:
97
98
  title: "%{table} exporteren"
98
99
  close: Sluiten
@@ -132,3 +133,5 @@ nl:
132
133
  clear_button: Zoekopdracht en filters wissen
133
134
  views:
134
135
  default: Alles
136
+ component:
137
+ view_name_required: Vul een naam in voor deze weergave.
data/lib/mensa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mensa
2
- VERSION = "0.9.2"
2
+ VERSION = "0.9.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mensa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom de Grunt
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -374,8 +373,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
374
373
  - !ruby/object:Gem::Version
375
374
  version: '0'
376
375
  requirements: []
377
- rubygems_version: 3.4.10
378
- signing_key:
376
+ rubygems_version: 3.6.9
379
377
  specification_version: 4
380
378
  summary: Fast and awesome tables
381
379
  test_files: []