infotable 1.0.0 → 1.2.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/README.md +14 -2
- data/app/assets/javascripts/infotable/controllers/drawer_controller.js +68 -42
- data/app/assets/javascripts/infotable/controllers/table_controller.js +127 -23
- data/app/assets/tailwind/infotable/engine.css +5 -0
- data/app/controllers/infotable/tables_controller.rb +17 -7
- data/app/helpers/infotable/tables_helper.rb +64 -2
- data/app/views/infotable/tables/_active_filters.html.erb +24 -0
- data/app/views/infotable/tables/_column_visibility.html.erb +11 -11
- data/app/views/infotable/tables/_filter_drawer.html.erb +4 -4
- data/app/views/infotable/tables/_next_page_frame.html.erb +5 -5
- data/app/views/infotable/tables/_pagination.html.erb +12 -12
- data/app/views/infotable/tables/_rail.html.erb +56 -0
- data/app/views/infotable/tables/_side_panel.html.erb +22 -10
- data/app/views/infotable/tables/_table.html.erb +43 -26
- data/app/views/infotable/tables/_table_rows.html.erb +3 -3
- data/app/views/infotable/tables/filters/_boolean.html.erb +2 -2
- data/app/views/infotable/tables/filters/_date.html.erb +2 -30
- data/app/views/infotable/tables/filters/_number.html.erb +2 -32
- data/app/views/infotable/tables/filters/_range.html.erb +66 -0
- data/app/views/infotable/tables/filters/_select.html.erb +4 -4
- data/app/views/infotable/tables/filters/_text.html.erb +3 -3
- data/app/views/infotable/tables/show.html.erb +22 -13
- data/config/locales/en.yml +23 -0
- data/config/locales/pl.yml +23 -0
- data/config/routes.rb +1 -0
- data/lib/generators/infotable/install/install_generator.rb +10 -64
- data/lib/infotable/base.rb +66 -0
- data/lib/infotable/configuration.rb +3 -1
- data/lib/infotable/engine.rb +0 -8
- data/lib/infotable/formatters/action_formatter.rb +7 -7
- data/lib/infotable/formatters/avatar_formatter.rb +2 -2
- data/lib/infotable/formatters/badge_formatter.rb +9 -9
- data/lib/infotable/formatters/boolean_formatter.rb +2 -2
- data/lib/infotable/formatters/link_formatter.rb +1 -1
- data/lib/infotable/formatters/percent_formatter.rb +1 -1
- data/lib/infotable/version.rb +1 -1
- data/lib/infotable.rb +0 -12
- metadata +5 -3
- data/lib/generators/infotable/install/templates/TAILWIND_CONFIG.md +0 -131
- data/lib/tasks/infotable.rake +0 -55
|
@@ -1,32 +1,2 @@
|
|
|
1
|
-
<%# Number filter
|
|
2
|
-
|
|
3
|
-
<%# From input %>
|
|
4
|
-
<div class="relative">
|
|
5
|
-
<input type="number"
|
|
6
|
-
name="filters[<%= column[:name] %>][from]"
|
|
7
|
-
id="<%= infotable_filter_id(table_name, column, 'from') %>"
|
|
8
|
-
placeholder="<%= t('infotable.filters.min') %>"
|
|
9
|
-
step="any"
|
|
10
|
-
class="block w-full rounded-lg border-0 bg-white pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
11
|
-
<button type="button"
|
|
12
|
-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
13
|
-
data-action="click->infotable--drawer#clearSingleFilter"
|
|
14
|
-
data-filter-name="<%= column[:name] %>"
|
|
15
|
-
data-filter-type="number"
|
|
16
|
-
title="<%= t('infotable.filters.clear_filter') %>">
|
|
17
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
18
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
19
|
-
</svg>
|
|
20
|
-
</button>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<%# To input %>
|
|
24
|
-
<div class="relative">
|
|
25
|
-
<input type="number"
|
|
26
|
-
name="filters[<%= column[:name] %>][to]"
|
|
27
|
-
id="<%= infotable_filter_id(table_name, column, 'to') %>"
|
|
28
|
-
placeholder="<%= t('infotable.filters.max') %>"
|
|
29
|
-
step="any"
|
|
30
|
-
class="block w-full rounded-lg border-0 bg-white pr-3 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
1
|
+
<%# Number filter: range or comparison, see _range %>
|
|
2
|
+
<%= render "infotable/tables/filters/range", column: column, table_name: table_name, kind: :number %>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<%# Shared by the number and date filters.
|
|
2
|
+
Locals: column, table_name, kind (:number or :date)
|
|
3
|
+
The operator select picks the shape of the filter:
|
|
4
|
+
"" from/to inputs (either may stay blank)
|
|
5
|
+
single ops one :value input
|
|
6
|
+
empty ops no input %>
|
|
7
|
+
<%
|
|
8
|
+
kind = kind.to_sym
|
|
9
|
+
input_type = kind == :date ? "date" : "number"
|
|
10
|
+
ops = kind == :date ? %w[on before after] : %w[eq neq gt gte lt lte]
|
|
11
|
+
input_class = "block w-full rounded-lg border-0 bg-white dark:bg-gray-900 pr-3 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] disabled:cursor-not-allowed disabled:bg-neutral-100"
|
|
12
|
+
select_class = "#{input_class} appearance-none pr-10 cursor-pointer bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]"
|
|
13
|
+
step = kind == :number ? "any" : nil
|
|
14
|
+
%>
|
|
15
|
+
<div class="space-y-2" data-range-container id="<%= infotable_filter_id(table_name, column, 'container') %>">
|
|
16
|
+
<div class="flex items-center gap-2">
|
|
17
|
+
<select name="filters[<%= column[:name] %>][op]"
|
|
18
|
+
data-range-op
|
|
19
|
+
data-action="change->infotable--drawer#syncFilterOps"
|
|
20
|
+
id="<%= infotable_filter_id(table_name, column, 'op') %>"
|
|
21
|
+
class="<%= select_class %>">
|
|
22
|
+
<option value=""><%= t("infotable.filters.range_ops.between") %></option>
|
|
23
|
+
<% ops.each do |op| %>
|
|
24
|
+
<option value="<%= op %>"><%= t("infotable.filters.#{kind}_ops.#{op}") %></option>
|
|
25
|
+
<% end %>
|
|
26
|
+
<option value="is_empty"><%= t("infotable.filters.text_ops.is_empty") %></option>
|
|
27
|
+
<option value="is_not_empty"><%= t("infotable.filters.text_ops.is_not_empty") %></option>
|
|
28
|
+
</select>
|
|
29
|
+
<button type="button"
|
|
30
|
+
class="shrink-0 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
31
|
+
data-action="click->infotable--drawer#clearSingleFilter"
|
|
32
|
+
data-filter-name="<%= column[:name] %>"
|
|
33
|
+
data-filter-type="<%= kind %>"
|
|
34
|
+
title="<%= t('infotable.filters.clear_filter') %>">
|
|
35
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
36
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
37
|
+
</svg>
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="grid grid-cols-2 gap-2" data-range-fields="range">
|
|
42
|
+
<input type="<%= input_type %>"
|
|
43
|
+
name="filters[<%= column[:name] %>][from]"
|
|
44
|
+
id="<%= infotable_filter_id(table_name, column, 'from') %>"
|
|
45
|
+
placeholder="<%= t(kind == :date ? 'infotable.filters.from' : 'infotable.filters.min') %>"
|
|
46
|
+
aria-label="<%= t('infotable.filters.from') %>"
|
|
47
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
48
|
+
class="<%= input_class %>">
|
|
49
|
+
<input type="<%= input_type %>"
|
|
50
|
+
name="filters[<%= column[:name] %>][to]"
|
|
51
|
+
id="<%= infotable_filter_id(table_name, column, 'to') %>"
|
|
52
|
+
placeholder="<%= t(kind == :date ? 'infotable.filters.to' : 'infotable.filters.max') %>"
|
|
53
|
+
aria-label="<%= t('infotable.filters.to') %>"
|
|
54
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
55
|
+
class="<%= input_class %>">
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div data-range-fields="single" style="display: none">
|
|
59
|
+
<input type="<%= input_type %>"
|
|
60
|
+
name="filters[<%= column[:name] %>][value]"
|
|
61
|
+
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
62
|
+
placeholder="<%= t('infotable.filters.enter_value') %>"
|
|
63
|
+
<% if step %>step="<%= step %>"<% end %>
|
|
64
|
+
class="<%= input_class %>">
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
15
15
|
multiple
|
|
16
16
|
size="6"
|
|
17
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pl-3 py-2 pr-8 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75"
|
|
17
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pl-3 py-2 pr-8 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75"
|
|
18
18
|
style="min-height: 120px;">
|
|
19
19
|
<% options.each do |option| %>
|
|
20
20
|
<% if option.is_a?(Array) %>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<% end %>
|
|
26
26
|
</select>
|
|
27
27
|
<button type="button"
|
|
28
|
-
class="absolute right-2 top-2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
28
|
+
class="absolute right-2 top-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
29
29
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
30
30
|
data-filter-name="<%= column[:name] %>"
|
|
31
31
|
data-filter-type="select"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<div class="relative">
|
|
42
42
|
<select name="filters[<%= column[:name] %>][value]"
|
|
43
43
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
44
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
44
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
45
45
|
<option value=""><%= t('infotable.filters.all') %></option>
|
|
46
46
|
<% options.each do |option| %>
|
|
47
47
|
<% if option.is_a?(Array) %>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<% end %>
|
|
53
53
|
</select>
|
|
54
54
|
<button type="button"
|
|
55
|
-
class="absolute right-8 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
55
|
+
class="absolute right-8 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
56
56
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
57
57
|
data-filter-name="<%= column[:name] %>"
|
|
58
58
|
data-filter-type="select"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
data-filter-op
|
|
7
7
|
data-action="change->infotable--drawer#syncFilterOps"
|
|
8
8
|
id="<%= infotable_filter_id(table_name, column, 'op') %>"
|
|
9
|
-
class="block w-full appearance-none rounded-lg border-0 bg-white pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
9
|
+
class="block w-full appearance-none rounded-lg border-0 bg-white dark:bg-gray-900 pr-10 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] cursor-pointer disabled:cursor-not-allowed disabled:bg-neutral-100 disabled:opacity-75 bg-[url('data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20fill=%27none%27%20viewBox=%270%200%2020%2020%27%3e%3cpath%20stroke=%27%23737373%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20stroke-width=%271.5%27%20d=%27M6%208l4%204%204-4%27/%3e%3c/svg%3e')] bg-[right_0.75rem_center] bg-no-repeat bg-[length:1.25em_1.25em]">
|
|
10
10
|
<option value="contains"><%= t('infotable.filters.text_ops.contains') %></option>
|
|
11
11
|
<option value="exact"><%= t('infotable.filters.text_ops.exact') %></option>
|
|
12
12
|
<option value="not_contain"><%= t('infotable.filters.text_ops.not_contain') %></option>
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
name="filters[<%= column[:name] %>][value]"
|
|
24
24
|
id="<%= infotable_filter_id(table_name, column, 'value') %>"
|
|
25
25
|
placeholder="<%= t('infotable.filters.enter_value') %>"
|
|
26
|
-
class="block w-full rounded-lg border-0 bg-white pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
26
|
+
class="block w-full rounded-lg border-0 bg-white dark:bg-gray-900 pr-8 pl-3 py-2 text-base sm:text-sm text-neutral-900 dark:text-neutral-100 placeholder:text-neutral-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-neutral-600 focus:ring-offset-0 transition-all [box-shadow:inset_0_0_0_1px_rgb(212_212_212)] dark:[box-shadow:inset_0_0_0_1px_rgb(82_82_82)] focus:[box-shadow:inset_0_0_0_2px_rgb(82_82_82)] dark:focus:[box-shadow:inset_0_0_0_2px_rgb(163_163_163)] disabled:cursor-not-allowed disabled:bg-neutral-100">
|
|
27
27
|
<button type="button"
|
|
28
|
-
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
|
|
28
|
+
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
|
29
29
|
data-action="click->infotable--drawer#clearSingleFilter"
|
|
30
30
|
data-filter-name="<%= column[:name] %>"
|
|
31
31
|
data-filter-type="text"
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<%# This view renders inside the turbo frame %>
|
|
2
2
|
<%# Turbo requires the frame element to be present in the response %>
|
|
3
3
|
<turbo-frame id="<%= params[:table_name] %>_content">
|
|
4
|
-
|
|
4
|
+
<% active_filters = infotable_active_filters(@table_class, @filters, @search) %>
|
|
5
|
+
<%= render "infotable/tables/active_filters", chips: active_filters, table_name: params[:table_name] %>
|
|
6
|
+
<div<% if @pagy && !@table_class.countless? %> data-total-count="<%= @pagy.count %>"<% end %> class="relative overflow-y-auto overflow-x-auto scroll-smooth [scrollbar-width:thin] [scrollbar-color:#cbd5e1_#f1f5f9] [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar-track]:bg-slate-100 [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-thumb:hover]:bg-slate-400 dark:[scrollbar-color:#475569_#1e293b] dark:[&::-webkit-scrollbar-track]:bg-slate-800 dark:[&::-webkit-scrollbar-thumb]:bg-slate-600"
|
|
5
7
|
style="min-height: <%= @table_class.min_height %>; max-height: <%= @table_class.max_height %>;">
|
|
6
8
|
|
|
7
9
|
<% if @records.any? %>
|
|
8
10
|
<%# Table %>
|
|
9
|
-
<table class="min-w-full divide-y divide-gray-200">
|
|
10
|
-
<thead class="bg-gray-50 border-b-2 border-gray-200">
|
|
11
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
12
|
+
<thead class="bg-gray-50 dark:bg-gray-800 border-b-2 border-gray-200 dark:border-gray-700">
|
|
11
13
|
<tr>
|
|
12
14
|
<%
|
|
13
15
|
row_size = @table_class.row_size_value
|
|
@@ -18,12 +20,12 @@
|
|
|
18
20
|
end
|
|
19
21
|
%>
|
|
20
22
|
<% if @table_class.checkbox_enabled? %>
|
|
21
|
-
<th scope="col" class="<%= header_padding %> w-10 bg-gray-50 sticky top-0 z-20">
|
|
23
|
+
<th scope="col" class="<%= header_padding %> w-10 bg-gray-50 dark:bg-gray-800 sticky top-0 z-20">
|
|
22
24
|
<input type="checkbox"
|
|
23
25
|
aria-label="<%= t('infotable.batch.select_all') %>"
|
|
24
26
|
data-infotable--table-target="selectAll"
|
|
25
27
|
data-action="change->infotable--table#toggleAll"
|
|
26
|
-
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
|
28
|
+
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-indigo-600 dark:text-indigo-400 focus:ring-indigo-500">
|
|
27
29
|
</th>
|
|
28
30
|
<% end %>
|
|
29
31
|
<% @table_class.columns.each do |column| %>
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
data-default-visible="<%= column[:visible] %>"
|
|
33
35
|
<%= tag.attributes(data: { default_pinned: column[:pinned] }) if column[:pinned] %>
|
|
34
36
|
style="width: <%= column[:width] %>; max-width: <%= column[:width] %>; overflow: hidden;"
|
|
35
|
-
class="<%= header_padding %> text-left text-xs font-semibold text-gray-900 tracking-wider select-none bg-gray-50 sticky top-0 z-20">
|
|
37
|
+
class="<%= header_padding %> text-left text-xs font-semibold text-gray-900 dark:text-gray-100 tracking-wider select-none bg-gray-50 dark:bg-gray-800 sticky top-0 z-20">
|
|
36
38
|
<% if column[:sortable] %>
|
|
37
39
|
<% sort_index = current_sorts.index { |name, _| name == column[:name].to_s } %>
|
|
38
40
|
<% sort_dir = sort_index ? current_sorts[sort_index].last : nil %>
|
|
@@ -56,16 +58,16 @@
|
|
|
56
58
|
<span class="flex-none rounded">
|
|
57
59
|
<% if sort_index %>
|
|
58
60
|
<% if sort_dir == 'asc' %>
|
|
59
|
-
<svg class="h-4 w-4 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
|
61
|
+
<svg class="h-4 w-4 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20">
|
|
60
62
|
<path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"/>
|
|
61
63
|
</svg>
|
|
62
64
|
<% else %>
|
|
63
|
-
<svg class="h-4 w-4 text-indigo-600" fill="currentColor" viewBox="0 0 20 20">
|
|
65
|
+
<svg class="h-4 w-4 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20">
|
|
64
66
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
65
67
|
</svg>
|
|
66
68
|
<% end %>
|
|
67
69
|
<% if current_sorts.size > 1 %>
|
|
68
|
-
<span class="text-[10px] font-bold text-indigo-600"><%= sort_index + 1 %></span>
|
|
70
|
+
<span class="text-[10px] font-bold text-indigo-600 dark:text-indigo-400"><%= sort_index + 1 %></span>
|
|
69
71
|
<% end %>
|
|
70
72
|
<% else %>
|
|
71
73
|
<svg class="h-4 w-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -81,7 +83,7 @@
|
|
|
81
83
|
<% end %>
|
|
82
84
|
</tr>
|
|
83
85
|
</thead>
|
|
84
|
-
<tbody id="<%= params[:table_name] %>_tbody" class="bg-white">
|
|
86
|
+
<tbody id="<%= params[:table_name] %>_tbody" class="bg-white dark:bg-gray-900">
|
|
85
87
|
<%= render partial: "infotable/tables/table_rows", locals: { records: @records, table_class: @table_class } %>
|
|
86
88
|
</tbody>
|
|
87
89
|
</table>
|
|
@@ -96,8 +98,15 @@
|
|
|
96
98
|
<svg class="mx-auto h-16 w-16 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
97
99
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
|
|
98
100
|
</svg>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
<% if active_filters.any? %>
|
|
102
|
+
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100"><%= t('infotable.table.empty_state.filtered_title') %></h3>
|
|
103
|
+
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"><%= t('infotable.table.empty_state.filtered_description') %></p>
|
|
104
|
+
<button type="button" data-action="click->infotable--table#clearAllFilters" data-empty-clear-filters
|
|
105
|
+
class="mt-4 inline-flex items-center rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500"><%= t('infotable.filters.active.clear_all') %></button>
|
|
106
|
+
<% else %>
|
|
107
|
+
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100"><%= t('infotable.table.empty_state.title') %></h3>
|
|
108
|
+
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400"><%= t('infotable.table.empty_state.description') %></p>
|
|
109
|
+
<% end %>
|
|
101
110
|
</div>
|
|
102
111
|
<% end %>
|
|
103
112
|
|
|
@@ -105,7 +114,7 @@
|
|
|
105
114
|
|
|
106
115
|
<%# Pagination - Only visible when infinite scroll is disabled %>
|
|
107
116
|
<% unless @infinite_scroll_enabled %>
|
|
108
|
-
<div class="border-t border-gray-200 bg-white px-6 py-4">
|
|
117
|
+
<div class="border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-6 py-4">
|
|
109
118
|
<%= render "infotable/tables/pagination", pagy: @pagy, table_name: params[:table_name] %>
|
|
110
119
|
</div>
|
|
111
120
|
<% end %>
|
data/config/locales/en.yml
CHANGED
|
@@ -5,6 +5,8 @@ en:
|
|
|
5
5
|
export_csv: "Export CSV"
|
|
6
6
|
filters: "Filters"
|
|
7
7
|
exporting: "Exporting..."
|
|
8
|
+
refresh: "Refresh"
|
|
9
|
+
columns: "Columns"
|
|
8
10
|
sort_hint: "Click to sort, Shift+click to add a column to the sort"
|
|
9
11
|
loading:
|
|
10
12
|
initial: "Loading data..."
|
|
@@ -13,6 +15,8 @@ en:
|
|
|
13
15
|
empty_state:
|
|
14
16
|
title: "No data found"
|
|
15
17
|
description: "Get started by adding some records to see them here."
|
|
18
|
+
filtered_title: "No rows match your filters"
|
|
19
|
+
filtered_description: "Remove a filter or search for something else."
|
|
16
20
|
|
|
17
21
|
drawer:
|
|
18
22
|
label: "Filters and columns"
|
|
@@ -39,6 +43,10 @@ en:
|
|
|
39
43
|
clear_all: "Clear All"
|
|
40
44
|
apply_filters: "Apply Filters"
|
|
41
45
|
clear_filter: "Clear this filter"
|
|
46
|
+
active:
|
|
47
|
+
search: "Search: \u201c%{query}\u201d"
|
|
48
|
+
remove: "Remove filter %{filter}"
|
|
49
|
+
clear_all: "Clear all filters"
|
|
42
50
|
from: "From"
|
|
43
51
|
to: "To"
|
|
44
52
|
min: "Min"
|
|
@@ -60,6 +68,19 @@ en:
|
|
|
60
68
|
ends_with: "Ends with"
|
|
61
69
|
is_empty: "Is empty"
|
|
62
70
|
is_not_empty: "Is not empty"
|
|
71
|
+
range_ops:
|
|
72
|
+
between: "Between"
|
|
73
|
+
number_ops:
|
|
74
|
+
eq: "Equals"
|
|
75
|
+
neq: "Does not equal"
|
|
76
|
+
gt: "Greater than"
|
|
77
|
+
gte: "Greater than or equal"
|
|
78
|
+
lt: "Less than"
|
|
79
|
+
lte: "Less than or equal"
|
|
80
|
+
date_ops:
|
|
81
|
+
"on": "On"
|
|
82
|
+
before: "Before"
|
|
83
|
+
after: "After"
|
|
63
84
|
|
|
64
85
|
formatters:
|
|
65
86
|
ago: "%{time} ago"
|
|
@@ -73,6 +94,8 @@ en:
|
|
|
73
94
|
clear: "Clear selection"
|
|
74
95
|
select_all: "Select all rows"
|
|
75
96
|
select_row: "Select row"
|
|
97
|
+
select_all_matching: "Select all %{count} matching"
|
|
98
|
+
truncated: "Only the first %{max} rows are selected"
|
|
76
99
|
|
|
77
100
|
search:
|
|
78
101
|
placeholder: "Search..."
|
data/config/locales/pl.yml
CHANGED
|
@@ -5,6 +5,8 @@ pl:
|
|
|
5
5
|
export_csv: "Eksportuj CSV"
|
|
6
6
|
filters: "Filtry"
|
|
7
7
|
exporting: "Eksportowanie..."
|
|
8
|
+
refresh: "Odśwież"
|
|
9
|
+
columns: "Kolumny"
|
|
8
10
|
sort_hint: "Kliknij, aby sortować; Shift+klik dodaje kolumnę do sortowania"
|
|
9
11
|
loading:
|
|
10
12
|
initial: "Ładowanie danych..."
|
|
@@ -13,6 +15,8 @@ pl:
|
|
|
13
15
|
empty_state:
|
|
14
16
|
title: "Nie znaleziono danych"
|
|
15
17
|
description: "Rozpocznij od dodania rekordów, aby zobaczyć je tutaj."
|
|
18
|
+
filtered_title: "Żaden wiersz nie pasuje do filtrów"
|
|
19
|
+
filtered_description: "Usuń filtr lub wyszukaj coś innego."
|
|
16
20
|
|
|
17
21
|
drawer:
|
|
18
22
|
label: "Filtry i kolumny"
|
|
@@ -39,6 +43,10 @@ pl:
|
|
|
39
43
|
clear_all: "Wyczyść wszystkie"
|
|
40
44
|
apply_filters: "Zastosuj filtry"
|
|
41
45
|
clear_filter: "Wyczyść ten filtr"
|
|
46
|
+
active:
|
|
47
|
+
search: "Szukaj: \u201e%{query}\u201d"
|
|
48
|
+
remove: "Usuń filtr %{filter}"
|
|
49
|
+
clear_all: "Wyczyść wszystkie filtry"
|
|
42
50
|
from: "Od"
|
|
43
51
|
to: "Do"
|
|
44
52
|
min: "Min"
|
|
@@ -60,6 +68,19 @@ pl:
|
|
|
60
68
|
ends_with: "Kończy się na"
|
|
61
69
|
is_empty: "Jest puste"
|
|
62
70
|
is_not_empty: "Nie jest puste"
|
|
71
|
+
range_ops:
|
|
72
|
+
between: "Pomiędzy"
|
|
73
|
+
number_ops:
|
|
74
|
+
eq: "Równa się"
|
|
75
|
+
neq: "Różne od"
|
|
76
|
+
gt: "Większe niż"
|
|
77
|
+
gte: "Większe lub równe"
|
|
78
|
+
lt: "Mniejsze niż"
|
|
79
|
+
lte: "Mniejsze lub równe"
|
|
80
|
+
date_ops:
|
|
81
|
+
"on": "W dniu"
|
|
82
|
+
before: "Przed"
|
|
83
|
+
after: "Po"
|
|
63
84
|
|
|
64
85
|
formatters:
|
|
65
86
|
ago: "%{time} temu"
|
|
@@ -72,6 +93,8 @@ pl:
|
|
|
72
93
|
selected: "Zaznaczono: %{count}"
|
|
73
94
|
clear: "Wyczyść zaznaczenie"
|
|
74
95
|
select_all: "Zaznacz wszystkie wiersze"
|
|
96
|
+
select_all_matching: "Zaznacz wszystkie pasujące (%{count})"
|
|
97
|
+
truncated: "Zaznaczono tylko pierwsze %{max} wierszy"
|
|
75
98
|
select_row: "Zaznacz wiersz"
|
|
76
99
|
|
|
77
100
|
search:
|
data/config/routes.rb
CHANGED
|
@@ -3,4 +3,5 @@ Infotable::Engine.routes.draw do
|
|
|
3
3
|
get "tables/:table_name", to: "tables#show", as: :table
|
|
4
4
|
get "tables/:table_name/data", to: "tables#data", as: :table_data
|
|
5
5
|
get "tables/:table_name/export", to: "tables#export", as: :table_export
|
|
6
|
+
get "tables/:table_name/ids", to: "tables#ids", as: :table_ids
|
|
6
7
|
end
|
|
@@ -96,25 +96,6 @@ module Infotable
|
|
|
96
96
|
template "initializer.rb", "config/initializers/infotable.rb"
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def add_to_gitignore
|
|
100
|
-
gitignore_entry = "# InfoTable - Generated from ERB template, do not commit\napp/assets/stylesheets/infotable.css\napp/assets/tailwind/infotable.css\n"
|
|
101
|
-
|
|
102
|
-
if File.exist?(".gitignore")
|
|
103
|
-
content = File.read(".gitignore")
|
|
104
|
-
unless content.include?("infotable.css")
|
|
105
|
-
append_to_file ".gitignore", "\n#{gitignore_entry}"
|
|
106
|
-
say " ✅ Added generated infotable.css to .gitignore", :green
|
|
107
|
-
else
|
|
108
|
-
say " ℹ️ infotable.css already in .gitignore", :blue
|
|
109
|
-
end
|
|
110
|
-
else
|
|
111
|
-
create_file ".gitignore", gitignore_entry
|
|
112
|
-
say " ✅ Created .gitignore with infotable.css", :green
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
say " 📝 Commit infotable.css.erb (the template), not infotable.css (generated)", :cyan
|
|
116
|
-
end
|
|
117
|
-
|
|
118
99
|
def show_post_install_message
|
|
119
100
|
say "\n" + "=" * 60, :green
|
|
120
101
|
say "✅ InfoTable installation complete!", :green
|
|
@@ -144,9 +125,7 @@ module Infotable
|
|
|
144
125
|
say "💡 Important Notes:", :cyan
|
|
145
126
|
say "=" * 60, :cyan
|
|
146
127
|
say "• InfoTable requires the tailwindcss-rails gem", :green
|
|
147
|
-
say "•
|
|
148
|
-
say "• Gem paths resolve dynamically for each environment (dev/production)", :green
|
|
149
|
-
say "• Commit infotable.css.erb (template), gitignore infotable.css (generated)", :cyan
|
|
128
|
+
say "• Tailwind scans the gem through @import \"../builds/tailwind/infotable\" in your Tailwind entry file", :green
|
|
150
129
|
|
|
151
130
|
say "\n📖 Documentation: https://github.com/jcieslar/infotable", :cyan
|
|
152
131
|
end
|
|
@@ -496,61 +475,28 @@ import InfoTableDrawerController from "infotable/controllers/drawer_controller"
|
|
|
496
475
|
return
|
|
497
476
|
end
|
|
498
477
|
|
|
499
|
-
#
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
478
|
+
# tailwindcss-rails writes app/assets/builds/tailwind/infotable.css (it imports the gem's
|
|
479
|
+
# app/assets/tailwind/infotable/engine.css) on every build; Tailwind only scans the gem's
|
|
480
|
+
# views if the main CSS imports it.
|
|
481
|
+
import = %(@import "../builds/tailwind/infotable";)
|
|
503
482
|
content = File.read(tailwind_file)
|
|
504
|
-
if content.
|
|
483
|
+
if content.include?(import)
|
|
505
484
|
say " ℹ️ InfoTable import already exists in #{File.basename(tailwind_file)}", :blue
|
|
506
485
|
else
|
|
507
|
-
if content.
|
|
486
|
+
if content.match?(/@import\s+["']tailwindcss["'];?\s*\n/)
|
|
508
487
|
inject_into_file tailwind_file, after: /@import\s+["']tailwindcss["'];?\s*\n/ do
|
|
509
|
-
"\n
|
|
488
|
+
"#{import}\n"
|
|
510
489
|
end
|
|
511
490
|
else
|
|
512
|
-
prepend_to_file tailwind_file, "
|
|
491
|
+
prepend_to_file tailwind_file, "#{import}\n"
|
|
513
492
|
end
|
|
514
493
|
|
|
515
|
-
say " ✅ Added
|
|
494
|
+
say " ✅ Added #{import} to #{File.basename(tailwind_file)}", :green
|
|
516
495
|
end
|
|
517
496
|
|
|
518
497
|
say " 📝 Run: rails tailwindcss:build", :cyan
|
|
519
498
|
end
|
|
520
499
|
|
|
521
|
-
def create_infotable_css_file
|
|
522
|
-
# Determine the stylesheets directory
|
|
523
|
-
stylesheets_dir = if File.exist?("app/assets/stylesheets")
|
|
524
|
-
"app/assets/stylesheets"
|
|
525
|
-
elsif File.exist?("app/assets/tailwind")
|
|
526
|
-
"app/assets/tailwind"
|
|
527
|
-
else
|
|
528
|
-
"app/assets/stylesheets"
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
infotable_css_path = File.join(stylesheets_dir, "infotable.css.erb")
|
|
532
|
-
|
|
533
|
-
# Create ERB file that dynamically resolves gem paths
|
|
534
|
-
css_content = <<~CSS
|
|
535
|
-
/* InfoTable Tailwind Sources - ERB Template */
|
|
536
|
-
/* Requires: tailwindcss-rails gem (https://github.com/rails/tailwindcss-rails) */
|
|
537
|
-
/* Paths are resolved dynamically via ERB from Infotable module */
|
|
538
|
-
|
|
539
|
-
/* Local table definitions in your Rails app */
|
|
540
|
-
@source "../../app/infotable";
|
|
541
|
-
|
|
542
|
-
/* InfoTable gem paths (resolved via ERB at build time) */
|
|
543
|
-
@source "<%= Infotable.views_path %>";
|
|
544
|
-
@source "<%= Infotable.formatters_path %>";
|
|
545
|
-
CSS
|
|
546
|
-
|
|
547
|
-
create_file infotable_css_path, css_content
|
|
548
|
-
|
|
549
|
-
say " ✅ Created #{infotable_css_path} with ERB templates", :green
|
|
550
|
-
say " 📝 Gem paths resolve dynamically via Infotable module", :cyan
|
|
551
|
-
say " 📝 Works in all environments automatically", :green
|
|
552
|
-
end
|
|
553
|
-
|
|
554
500
|
def package_json_has?(package)
|
|
555
501
|
return false unless File.exist?("package.json")
|
|
556
502
|
|
data/lib/infotable/base.rb
CHANGED
|
@@ -116,6 +116,21 @@ module Infotable
|
|
|
116
116
|
record.public_send(@checkbox_settings[:id_method])
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
# Ids of every row the query matches (the "select all matching" button), at most `limit`.
|
|
120
|
+
# Plucks when the id method is a plain column of an ungrouped query, loads records otherwise.
|
|
121
|
+
def checkbox_ids(query, limit = nil)
|
|
122
|
+
method = @checkbox_settings[:id_method]
|
|
123
|
+
scope = query.reorder(nil)
|
|
124
|
+
scope = scope.limit(limit) if limit
|
|
125
|
+
model = query.model
|
|
126
|
+
|
|
127
|
+
if model.column_names.include?(method.to_s) && scope.group_values.empty? && scope.having_clause.empty?
|
|
128
|
+
scope.pluck(model.arel_table[method])
|
|
129
|
+
else
|
|
130
|
+
scope.map { |record| checkbox_value(record) }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
119
134
|
# Per-table access check. Block receives the controller; falsy => 403.
|
|
120
135
|
# authorize { |controller| controller.current_user&.admin? }
|
|
121
136
|
def authorize(&block)
|
|
@@ -157,6 +172,7 @@ module Infotable
|
|
|
157
172
|
searchable: options.fetch(:searchable, false),
|
|
158
173
|
filterable: options.fetch(:filterable, false),
|
|
159
174
|
visible: options.fetch(:visible, true),
|
|
175
|
+
exportable: options.fetch(:exportable, !%i[action actions].include?(type)), # false keeps the column out of CSV exports
|
|
160
176
|
pinned: options[:pinned], # :left, :right, or nil
|
|
161
177
|
virtual: options.fetch(:virtual, false), # Virtual/computed columns (aggregates, subqueries)
|
|
162
178
|
virtual_sql: options[:virtual_sql], # SQL expression for aggregate columns (used in HAVING clause)
|
|
@@ -322,6 +338,26 @@ module Infotable
|
|
|
322
338
|
@filters_button_settings[:visible] = visible
|
|
323
339
|
end
|
|
324
340
|
|
|
341
|
+
# Where the controls live. :header (default) is the bar above the table with title, search,
|
|
342
|
+
# export and filters buttons. :rail hides that bar and puts filters, columns, export and
|
|
343
|
+
# refresh on a slim vertical rail at the table's right edge; search moves into the drawer.
|
|
344
|
+
# @example
|
|
345
|
+
# toolbar :rail
|
|
346
|
+
def toolbar(mode)
|
|
347
|
+
unless [ :header, :rail ].include?(mode)
|
|
348
|
+
raise ArgumentError, "Invalid toolbar: #{mode}. Must be one of header, rail"
|
|
349
|
+
end
|
|
350
|
+
@toolbar_setting = mode
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def toolbar_mode
|
|
354
|
+
@toolbar_setting || :header
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def rail_toolbar?
|
|
358
|
+
toolbar_mode == :rail
|
|
359
|
+
end
|
|
360
|
+
|
|
325
361
|
# Check if filters button is visible
|
|
326
362
|
def filters_button_visible?
|
|
327
363
|
@filters_button_settings ||= { visible: true }
|
|
@@ -444,6 +480,36 @@ module Infotable
|
|
|
444
480
|
columns.select { |col| col[:visible] }
|
|
445
481
|
end
|
|
446
482
|
|
|
483
|
+
# Columns a CSV export may contain (action columns and `exportable: false` are left out)
|
|
484
|
+
def exportable_columns
|
|
485
|
+
columns.select { |col| col[:exportable] }
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Which columns the CSV export contains and in which order, when the user has not chosen:
|
|
489
|
+
# export_columns :name, :email, :created_at
|
|
490
|
+
# Without it the export uses the visible, exportable columns in table order. With it, the
|
|
491
|
+
# list is also the pool the user can pick from (visible_columns can only narrow and reorder it).
|
|
492
|
+
def export_columns(*names)
|
|
493
|
+
@export_column_names = names.flatten.map(&:to_sym)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def export_column_names
|
|
497
|
+
@export_column_names
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# The columns to export, as column definitions in export order. `requested` is the list of
|
|
501
|
+
# names the browser sends (the user's visible columns in their on-screen order).
|
|
502
|
+
def columns_for_export(requested = nil)
|
|
503
|
+
pool = exportable_columns
|
|
504
|
+
pool = export_column_names.map { |name| pool.find { |col| col[:name] == name } || raise(ArgumentError, "export_columns: unknown or non-exportable column #{name.inspect} in #{self.name}") } if export_column_names
|
|
505
|
+
|
|
506
|
+
by_name = pool.index_by { |col| col[:name].to_s }
|
|
507
|
+
picked = Array(requested).map { |name| by_name[name.to_s] }.compact.uniq
|
|
508
|
+
return picked if picked.any?
|
|
509
|
+
|
|
510
|
+
export_column_names ? pool : pool.select { |col| col[:visible] }
|
|
511
|
+
end
|
|
512
|
+
|
|
447
513
|
# Get sortable columns
|
|
448
514
|
def sortable_columns
|
|
449
515
|
columns.select { |col| col[:sortable] }
|
|
@@ -14,7 +14,8 @@ module Infotable
|
|
|
14
14
|
:empty_value,
|
|
15
15
|
:parent_controller,
|
|
16
16
|
:max_filter_options,
|
|
17
|
-
:url_state
|
|
17
|
+
:url_state,
|
|
18
|
+
:max_select_all
|
|
18
19
|
|
|
19
20
|
def initialize
|
|
20
21
|
@default_per_page = 25
|
|
@@ -30,6 +31,7 @@ module Infotable
|
|
|
30
31
|
@nil_value = "-" # Default placeholder for nil values
|
|
31
32
|
@empty_value = "-" # Default placeholder for empty strings
|
|
32
33
|
@url_state = true # mirror table state in the address bar
|
|
34
|
+
@max_select_all = 10_000 # "select all matching" collects at most this many ids (nil = no limit)
|
|
33
35
|
@max_filter_options = 500 # select filters render at most this many <option>s (nil = no limit)
|
|
34
36
|
@parent_controller = "ActionController::Base" # Set to "ApplicationController" to inherit host auth/filters
|
|
35
37
|
end
|
data/lib/infotable/engine.rb
CHANGED
|
@@ -39,13 +39,5 @@ module Infotable
|
|
|
39
39
|
initializer "infotable.i18n" do |app|
|
|
40
40
|
config.i18n.load_path += Dir[root.join("config/locales/**/*.yml")]
|
|
41
41
|
end
|
|
42
|
-
|
|
43
|
-
# Expose gem paths via ENV variables for Tailwind CSS v4 @source directives
|
|
44
|
-
# This allows Tailwind to scan the gem's views/formatters at build time
|
|
45
|
-
# Works in development, production, Docker, CI - everywhere!
|
|
46
|
-
initializer "infotable.tailwind", before: :load_config_initializers do
|
|
47
|
-
ENV["INFOTABLE_VIEWS_PATH"] = root.join("app/views/infotable").to_s
|
|
48
|
-
ENV["INFOTABLE_FORMATTERS_PATH"] = root.join("lib/infotable/formatters").to_s
|
|
49
|
-
end
|
|
50
42
|
end
|
|
51
43
|
end
|