mensa 0.9.2 → 0.9.3
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 +4 -4
- data/.shipkit.yml +1 -1
- data/CHANGELOG.md +36 -0
- data/app/components/mensa/control_bar/component.css +1 -5
- data/app/components/mensa/filter_pill/component.rb +15 -1
- data/app/components/mensa/filter_pill_list/component.html.erb +0 -1
- data/app/components/mensa/filter_pill_list/component_controller.js +16 -1
- data/app/components/mensa/search/component.html.erb +4 -4
- data/app/components/mensa/table/component_controller.js +46 -19
- data/app/components/mensa/views/component.css +1 -1
- data/app/components/mensa/views/component.html.erb +4 -4
- data/app/components/mensa/views/component_controller.js +22 -1
- data/app/tables/mensa/group.rb +3 -0
- data/app/views/mensa/exports/_dialog.html.erb +15 -10
- data/app/views/mensa/exports/_list.html.erb +6 -6
- data/config/locales/en.yml +3 -0
- data/config/locales/nl.yml +3 -0
- data/lib/mensa/version.rb +1 -1
- metadata +3 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4dc7c940f4eff95458542c2ac2d54a71df5735105019fb09d2fc4b70f588124
|
|
4
|
+
data.tar.gz: 6f43f89a33e27295b268b6c45c4a018599e313418b1aeb4b3ae758de63180211
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a038582389be24ec077d6234974d30700bcef87ca6d3524abdecfed97bcb09280fcbbb46095af18fac2339243ebcf3f39e3cc54adad0e96519183e6e5435a67
|
|
7
|
+
data.tar.gz: 2803b2f1fa2e42b19ca2ed2aff842e7bdd2bc1315f2fef13b590010105725746f832113fec55861e8ea911eeaf18e2454b9798822d0330ad73068b472f6b7dce
|
data/.shipkit.yml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
---
|
|
2
|
-
previous_version: 0.9.
|
|
2
|
+
previous_version: 0.9.2
|
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
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
-
<
|
|
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="
|
|
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
|
-
</
|
|
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
|
|
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
|
-
|
|
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
|
|
574
|
-
const
|
|
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
|
-
|
|
584
|
-
?
|
|
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)
|
|
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
|
|
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 {
|
|
@@ -55,21 +55,21 @@
|
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
57
|
<dialog class="mensa-table__views__rename-dialog" data-mensa-views-target="renameDialog" data-action="click->mensa-views#renameDialogBackdrop">
|
|
58
|
-
<
|
|
58
|
+
<div class="mensa-table__views__rename-form">
|
|
59
59
|
<input type="hidden" data-mensa-views-target="renameViewId">
|
|
60
60
|
<h3 class="mensa-table__views__rename-title"><%= t(".rename_view", default: "Rename view") %></h3>
|
|
61
61
|
<label class="mensa-table__views__rename-label">
|
|
62
62
|
<%= 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">
|
|
63
|
+
<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
64
|
</label>
|
|
65
65
|
<div class="mensa-table__views__rename-actions">
|
|
66
66
|
<button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--secondary" type="button" data-action="mensa-views#cancelRename">
|
|
67
67
|
<%= t(".cancel", default: "Cancel") %>
|
|
68
68
|
</button>
|
|
69
|
-
<button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--primary" type="
|
|
69
|
+
<button class="mensa-table__views__rename-btn mensa-table__views__rename-btn--primary" type="button" data-action="mensa-views#confirmRename">
|
|
70
70
|
<%= t(".rename", default: "Rename") %>
|
|
71
71
|
</button>
|
|
72
72
|
</div>
|
|
73
|
-
</
|
|
73
|
+
</div>
|
|
74
74
|
</dialog>
|
|
75
75
|
</div>
|
|
@@ -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
|
-
|
|
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();
|
data/app/tables/mensa/group.rb
CHANGED
|
@@ -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
|
-
|
|
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="
|
|
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
|
-
</
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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>
|
data/config/locales/en.yml
CHANGED
|
@@ -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.
|
data/config/locales/nl.yml
CHANGED
|
@@ -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
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.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom de Grunt
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
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.
|
|
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: []
|