mensa 0.2.5 → 0.3.0
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/.devcontainer/Dockerfile +6 -2
- data/.devcontainer/compose.yaml +1 -1
- data/.devcontainer/devcontainer.json +31 -29
- data/.devcontainer/postCreate.sh +8 -0
- data/.devcontainer/postStart.sh +9 -0
- data/.gitignore +3 -1
- data/.zed/tasks.json +12 -0
- data/Gemfile.lock +155 -153
- data/Procfile +1 -1
- data/README.md +85 -60
- data/app/assets/stylesheets/mensa/application.css +14 -11
- data/app/components/mensa/add_filter/component.css +110 -5
- data/app/components/mensa/add_filter/component.html.slim +10 -12
- data/app/components/mensa/add_filter/component.rb +7 -1
- data/app/components/mensa/add_filter/component_controller.js +697 -83
- data/app/components/mensa/cell/component.css +9 -0
- data/app/components/mensa/column_customizer/component.css +40 -0
- data/app/components/mensa/column_customizer/component.html.slim +14 -0
- data/app/components/mensa/column_customizer/component.rb +13 -0
- data/app/components/mensa/column_customizer/component_controller.js +383 -0
- data/app/components/mensa/control_bar/component.css +127 -4
- data/app/components/mensa/control_bar/component.html.slim +41 -14
- data/app/components/mensa/control_bar/component.rb +0 -4
- data/app/components/mensa/empty_state/component.css +20 -0
- data/app/components/mensa/empty_state/component.html.slim +7 -0
- data/app/components/mensa/empty_state/component.rb +18 -0
- data/app/components/mensa/filter_pill/component.css +23 -0
- data/app/components/mensa/filter_pill/component.html.slim +9 -6
- data/app/components/mensa/filter_pill/component.rb +9 -0
- data/app/components/mensa/filter_pill/component_controller.js +50 -10
- data/app/components/mensa/filter_pill_list/component.css +58 -9
- data/app/components/mensa/filter_pill_list/component.html.slim +11 -8
- data/app/components/mensa/filter_pill_list/component_controller.js +747 -48
- data/app/components/mensa/header/component.css +41 -43
- data/app/components/mensa/header/component.html.slim +7 -7
- data/app/components/mensa/row_action/component.html.slim +2 -2
- data/app/components/mensa/search/component.css +68 -9
- data/app/components/mensa/search/component.html.slim +19 -15
- data/app/components/mensa/search/component_controller.js +39 -49
- data/app/components/mensa/selection/component_controller.js +147 -0
- data/app/components/mensa/table/component.css +28 -0
- data/app/components/mensa/table/component.html.slim +9 -6
- data/app/components/mensa/table/component.rb +1 -0
- data/app/components/mensa/table/component_controller.js +524 -88
- data/app/components/mensa/table_row/component.css +6 -0
- data/app/components/mensa/table_row/component.html.slim +8 -3
- data/app/components/mensa/view/component.css +97 -29
- data/app/components/mensa/view/component.html.slim +23 -10
- data/app/components/mensa/view/component.rb +5 -0
- data/app/components/mensa/views/component.css +106 -13
- data/app/components/mensa/views/component.html.slim +51 -17
- data/app/components/mensa/views/component_controller.js +245 -20
- data/app/controllers/mensa/tables/batch_actions_controller.rb +24 -0
- data/app/controllers/mensa/tables/exports_controller.rb +96 -0
- data/app/controllers/mensa/tables/filters_controller.rb +4 -1
- data/app/controllers/mensa/tables/views_controller.rb +108 -0
- data/app/controllers/mensa/tables_controller.rb +3 -6
- data/app/helpers/mensa/application_helper.rb +4 -0
- data/app/javascript/mensa/application.js +2 -2
- data/app/javascript/mensa/controllers/index.js +13 -4
- data/app/jobs/mensa/export_job.rb +77 -84
- data/app/models/mensa/export.rb +93 -0
- data/app/tables/mensa/base.rb +103 -12
- data/app/tables/mensa/batch_action.rb +27 -0
- data/app/tables/mensa/cell.rb +15 -0
- data/app/tables/mensa/column.rb +15 -2
- data/app/tables/mensa/config/batch_dsl.rb +13 -0
- data/app/tables/mensa/config/column_dsl.rb +1 -0
- data/app/tables/mensa/config/filter_dsl.rb +4 -1
- data/app/tables/mensa/config/render_dsl.rb +1 -1
- data/app/tables/mensa/config/table_dsl.rb +12 -5
- data/app/tables/mensa/config/view_dsl.rb +2 -0
- data/app/tables/mensa/config_readers.rb +20 -1
- data/app/tables/mensa/filter.rb +86 -3
- data/app/tables/mensa/scope.rb +24 -12
- data/app/views/mensa/exports/_badge.html.slim +5 -0
- data/app/views/mensa/exports/_dialog.html.slim +42 -0
- data/app/views/mensa/exports/_list.html.slim +29 -0
- data/app/views/mensa/tables/filters/show.turbo_stream.slim +35 -8
- data/app/views/mensa/tables/views/create.turbo_stream.slim +11 -0
- data/app/views/mensa/tables/views/destroy.turbo_stream.slim +11 -0
- data/app/views/mensa/tables/views/update.turbo_stream.slim +11 -0
- data/config/locales/en.yml +44 -0
- data/config/locales/nl.yml +45 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20260604120000_create_mensa_exports.rb +25 -0
- data/docs/columns.png +0 -0
- data/docs/export.png +0 -0
- data/docs/filters.png +0 -0
- data/docs/table.png +0 -0
- data/lib/mensa/configuration.rb +33 -12
- data/lib/mensa/engine.rb +7 -2
- data/lib/mensa/version.rb +1 -1
- data/mensa.gemspec +2 -1
- data/mise.toml +8 -0
- data/package-lock.json +0 -7
- metadata +50 -8
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
.mensa-empty-state
|
|
2
|
+
.mensa-empty-state__icon
|
|
3
|
+
i class=Mensa.config.icons[:search]
|
|
4
|
+
h3.mensa-empty-state__title = t("mensa.empty_state.title", model: model_name_plural)
|
|
5
|
+
p.mensa-empty-state__subtitle = t("mensa.empty_state.subtitle")
|
|
6
|
+
button.mensa-empty-state__button type="button" data-action="click->mensa-table#cancelFiltersAndSearch"
|
|
7
|
+
= t("mensa.empty_state.clear_button")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mensa
|
|
4
|
+
module EmptyState
|
|
5
|
+
class Component < ::Mensa::ApplicationComponent
|
|
6
|
+
attr_reader :table
|
|
7
|
+
|
|
8
|
+
def initialize(table:)
|
|
9
|
+
@table = table
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# "orders", "users", etc. — used inside the translated heading.
|
|
13
|
+
def model_name_plural
|
|
14
|
+
table.model.model_name.human.pluralize.downcase
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.mensa-filter-pill {
|
|
2
|
+
@apply inline-flex items-stretch text-xs;
|
|
3
|
+
|
|
4
|
+
&__chip {
|
|
5
|
+
@apply flex items-center gap-1 pl-2 pr-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded-l-md cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
&__column {
|
|
9
|
+
@apply text-gray-600 dark:text-gray-300;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&__operator {
|
|
13
|
+
@apply rounded px-1 bg-violet-100 dark:bg-violet-900/40 text-violet-700 dark:text-violet-300;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&__value {
|
|
17
|
+
@apply rounded pl-1 bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-400 font-medium;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__remove {
|
|
21
|
+
@apply inline-flex items-center justify-center px-1.5 text-gray-400 bg-gray-100 dark:bg-gray-700 rounded-r-md hover:text-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 dark:hover:text-gray-200 cursor-pointer transition-colors;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
.
|
|
2
|
-
button.
|
|
3
|
-
span.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
.mensa-filter-pill data-controller="mensa-filter-pill" data-mensa-filter-pill-mensa-filter-pill-list-outlet="#mensa-filter-pill-list-#{filter.table.table_id}" data-mensa-filter-pill-column-name-value=filter.column.name data-mensa-filter-pill-value-value=(filter.operator_with_value? ? (filter.value.is_a?(Array) ? filter.value.to_json : filter.value) : nil ) data-mensa-filter-pill-operator-value=filter.operator data-mensa-filter-pill-operator-without-value-value=("true" unless filter.operator_with_value?) data-view-filter=("true" if view_filter?)
|
|
2
|
+
button.mensa-filter-pill__chip[type="button" data-action="click->mensa-filter-pill#edit"]
|
|
3
|
+
span.mensa-filter-pill__column = filter.column.human_name
|
|
4
|
+
span.mensa-filter-pill__operator = filter.operator_label
|
|
5
|
+
- formatted_value = filter.value.is_a?(Array) ? filter.value.join(", ") : filter.value
|
|
6
|
+
- if formatted_value.present? && filter.operator_with_value?
|
|
7
|
+
span.mensa-filter-pill__value = formatted_value
|
|
8
|
+
button.mensa-filter-pill__remove[type="button" title="Remove filter" data-action="click->mensa-filter-pill#remove"]
|
|
9
|
+
i.fa-solid.fa-xmark
|
|
@@ -10,6 +10,15 @@ module Mensa
|
|
|
10
10
|
def initialize(filter:)
|
|
11
11
|
@filter = filter
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def view_filter?
|
|
15
|
+
view = filter.table.table_view
|
|
16
|
+
return false unless view
|
|
17
|
+
|
|
18
|
+
view_filters = view.config&.dig(:filters) || view.config&.dig("filters") || {}
|
|
19
|
+
col = filter.column.name.to_s
|
|
20
|
+
view_filters.key?(col) || view_filters.key?(col.to_sym)
|
|
21
|
+
end
|
|
13
22
|
end
|
|
14
23
|
end
|
|
15
24
|
end
|
|
@@ -1,12 +1,52 @@
|
|
|
1
|
-
import ApplicationController from
|
|
1
|
+
import ApplicationController from "mensa/controllers/application_controller";
|
|
2
2
|
|
|
3
3
|
export default class FilterPillComponentController extends ApplicationController {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
4
|
+
static outlets = ["mensa-filter-pill-list"];
|
|
5
|
+
|
|
6
|
+
static values = {
|
|
7
|
+
columnName: String,
|
|
8
|
+
operator: String,
|
|
9
|
+
value: String,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
connect() {}
|
|
13
|
+
|
|
14
|
+
// Re-opens the value selector for this filter's column (reusing the
|
|
15
|
+
// add-filter popover), pre-selected to the current value. Choosing a new
|
|
16
|
+
// value re-requests the table via the add-filter flow.
|
|
17
|
+
edit(event) {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
|
|
20
|
+
if (!this.hasMensaFilterPillListOutlet) return;
|
|
21
|
+
|
|
22
|
+
let value = this.hasValueValue ? this.valueValue : null;
|
|
23
|
+
if (this.hasValueValue) {
|
|
24
|
+
try {
|
|
25
|
+
value = JSON.parse(value);
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.mensaFilterPillListOutlet.editFilter(
|
|
30
|
+
this.columnNameValue,
|
|
31
|
+
value,
|
|
32
|
+
this.operatorValue,
|
|
33
|
+
this.element,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Removes this filter pill and re-requests the table with the remaining
|
|
38
|
+
// filters. The list controller reads the active filters straight from the
|
|
39
|
+
// DOM, so we drop our element first, then ask it to refresh.
|
|
40
|
+
remove(event) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
|
|
44
|
+
const list = this.hasMensaFilterPillListOutlet
|
|
45
|
+
? this.mensaFilterPillListOutlet
|
|
46
|
+
: null;
|
|
47
|
+
|
|
48
|
+
this.element.remove();
|
|
49
|
+
|
|
50
|
+
if (list) list.refreshFilters();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,14 +1,63 @@
|
|
|
1
1
|
.mensa-table {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
&__search-bar {
|
|
3
|
+
@apply flex items-center gap-1.5 flex-1 min-w-0 h-full px-2;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
&__pills-area {
|
|
6
|
+
@apply flex flex-1 flex-wrap items-center gap-1 min-w-0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&__input-wrapper {
|
|
10
|
+
@apply relative flex items-center flex-1 min-w-[4rem];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__search-icon {
|
|
14
|
+
@apply absolute left-2 text-gray-400 pointer-events-none -ml-4;
|
|
15
|
+
font-size: 0.9rem;
|
|
16
|
+
/* icon is after input in DOM but floats left via absolute positioning */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__input {
|
|
20
|
+
@apply w-full bg-transparent text-xs text-gray-700 dark:text-gray-200 placeholder:text-gray-400;
|
|
21
|
+
/* Override @tailwindcss/forms — direct CSS beats attribute selector specificity */
|
|
22
|
+
border: none !important;
|
|
23
|
+
outline: none !important;
|
|
24
|
+
box-shadow: none !important;
|
|
25
|
+
padding: 0 0.25rem 0 1.5rem;
|
|
8
26
|
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
&:focus {
|
|
28
|
+
border: none !important;
|
|
29
|
+
outline: none !important;
|
|
30
|
+
box-shadow: none !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Hide the icon that follows when input has content (placeholder not shown) */
|
|
34
|
+
&:not(:placeholder-shown) ~ .mensa-table__search-bar__search-icon {
|
|
35
|
+
display: none;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&__clear {
|
|
40
|
+
@apply flex-none p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer;
|
|
41
|
+
}
|
|
11
42
|
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Hide placeholder when visible filter pills exist.
|
|
46
|
+
User-added pills (no data-view-filter) are always visible.
|
|
47
|
+
View-filter pills are only visible when the eye toggle is showing them. */
|
|
48
|
+
.mensa-table__search-bar__pills-area:has(.mensa-filter-pill:not([data-view-filter="true"]))
|
|
49
|
+
.mensa-table__search-bar__input::placeholder,
|
|
50
|
+
.mensa-table:not(.mensa-table--view-filters-hidden)
|
|
51
|
+
.mensa-table__search-bar__pills-area:has(.mensa-filter-pill[data-view-filter="true"])
|
|
52
|
+
.mensa-table__search-bar__input::placeholder {
|
|
53
|
+
opacity: 0;
|
|
54
|
+
}
|
|
12
55
|
|
|
13
|
-
|
|
14
|
-
|
|
56
|
+
/* Hide search icon under the same conditions */
|
|
57
|
+
.mensa-table__search-bar__pills-area:has(.mensa-filter-pill:not([data-view-filter="true"]))
|
|
58
|
+
.mensa-table__search-bar__search-icon,
|
|
59
|
+
.mensa-table:not(.mensa-table--view-filters-hidden)
|
|
60
|
+
.mensa-table__search-bar__pills-area:has(.mensa-filter-pill[data-view-filter="true"])
|
|
61
|
+
.mensa-table__search-bar__search-icon {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
- has_filterable_columns = table.columns.any?(&:filter?)
|
|
2
|
+
.mensa-table__search-bar id="mensa-filter-pill-list-#{table.table_id}" data-controller="mensa-filter-pill-list" data-mensa-table-target="filterList" data-mensa-filter-pill-list-table-name-value=table.name data-mensa-filter-pill-list-mensa-table-outlet=".mensa-table#table-#{table.table_id}" data-mensa-filter-pill-list-mensa-filter-pill-outlet=".mensa-filter-pill" data-mensa-filter-pill-list-mensa-add-filter-outlet="[data-controller=mensa-add-filter]"
|
|
3
|
+
.mensa-table__search-bar__pills-area
|
|
4
|
+
= render Mensa::FilterPill::Component.with_collection(table.active_filters)
|
|
5
|
+
- if has_filterable_columns
|
|
6
|
+
= render Mensa::AddFilter::Component.new(table: table)
|
|
7
|
+
.mensa-table__search-bar__input-wrapper
|
|
8
|
+
input.mensa-table__search-bar__input[type="text" name="search_query" autocomplete="off" placeholder=t(has_filterable_columns ? ".search" : ".search_only") data-mensa-filter-pill-list-target="searchInput" data-action="input->mensa-filter-pill-list#monitorSearch keydown.enter->mensa-filter-pill-list#search keydown.esc->mensa-filter-pill-list#resetSearch focus->mensa-filter-pill-list#searchFocused keydown.down->mensa-filter-pill-list#navigateDown keydown.up->mensa-filter-pill-list#navigateUp" value=params[:query]]
|
|
9
|
+
i.mensa-table__search-bar__search-icon class=Mensa.config.icons[:search]
|
|
10
|
+
button.mensa-table__search-bar__clear.hidden[type="button" data-mensa-filter-pill-list-target="resetSearchButton" data-action="mensa-filter-pill-list#resetSearch"]
|
|
11
|
+
i.fas.fa-xmark
|