activeadmin_calendar 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 12858a761cb81a1bed452aedd1786fce50144c17426bf7255f109eafd0e19939
4
+ data.tar.gz: ed69d6fff3f544f19d53f25948fc620a3c3a0ee68988e69f0d7148d301596892
5
+ SHA512:
6
+ metadata.gz: 87b4773b56d4d7e1bb2a90181ca845353749d7c94e4530a765c9186db7d45524af1930b8bba71e7da3a2435564f0686d4a77f4809588c94adeaf206ea4f482a8
7
+ data.tar.gz: 9654babc7504f7b4db6355e255f3eb5fb718cdb6e1580e7695da656595de32fb937cfc2d86e948ab6f911b2d56ede42aa0bd9a513b49b1e04429c83d55e6e079
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Fedoronchuk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # activeadmin_calendar
2
+
3
+ ![CI](https://github.com/activeadmin-plugins/activeadmin_calendar/workflows/CI/badge.svg)
4
+ ![Coverage](https://img.shields.io/endpoint?url=https://activeadmin-plugins.github.io/activeadmin_calendar/badge.json)
5
+ ![Ruby](https://img.shields.io/badge/ruby-3.3%2B-blue)
6
+
7
+ Adds `index as: :calendar` to ActiveAdmin — renders the resource list as a
8
+ month grid with one cell per day. The index block is yielded
9
+ `(date, records_for_that_day)`.
10
+
11
+ Works with **ActiveAdmin 3.5+ and 4.x**.
12
+
13
+ ### ActiveAdmin 4
14
+
15
+ ![Payments calendar on AA 4](https://github.com/activeadmin-plugins/activeadmin_calendar/releases/download/assets-v1/payments_calendar_4_0.png)
16
+
17
+ ### ActiveAdmin 3
18
+
19
+ ![Payments calendar on AA 3.5](https://github.com/activeadmin-plugins/activeadmin_calendar/releases/download/assets-v1/payments_calendar_3_5.png)
20
+
21
+ ## Install
22
+
23
+ ```ruby
24
+ # Gemfile
25
+ gem "activeadmin_calendar"
26
+ ```
27
+
28
+ For Sprockets (AA 3):
29
+
30
+ ```scss
31
+ /* app/assets/stylesheets/active_admin.scss */
32
+ @import "activeadmin_calendar";
33
+ ```
34
+
35
+ Propshaft / Tailwind (AA 4) picks the bundled CSS up automatically.
36
+
37
+ ## Usage
38
+
39
+ ### Single-date events — `group_by:`
40
+
41
+ One SQL query per visible month, rows bucketed in Ruby.
42
+
43
+ ```ruby
44
+ ActiveAdmin.register Payment do
45
+ config.paginate = false
46
+ filter :card_type, as: :select, collection: Payment::CARD_TYPES
47
+
48
+ index as: :calendar, group_by: :paid_at do |date, payments|
49
+ by_card = payments.group_by(&:card_type)
50
+ .transform_values { |ps| ps.sum(&:amount) }
51
+ ul do
52
+ by_card.each { |c, sum| li "#{c.upcase}: #{number_to_currency(sum)}" }
53
+ total = by_card.values.sum
54
+ li { strong "Total: #{number_to_currency(total)}" } unless total.zero?
55
+ end
56
+ end
57
+ end
58
+ ```
59
+
60
+ ### Range / fan-out events — `group_by_scope:`
61
+
62
+ One row appears in **every** day in its active range. Use a custom scope:
63
+
64
+ #### ActiveAdmin 4
65
+
66
+ ![Bookings calendar — fan-out on AA 4](https://github.com/activeadmin-plugins/activeadmin_calendar/releases/download/assets-v1/bookings_calendar_4_0.png)
67
+
68
+ #### ActiveAdmin 3
69
+
70
+ ![Bookings calendar — fan-out on AA 3.5](https://github.com/activeadmin-plugins/activeadmin_calendar/releases/download/assets-v1/bookings_calendar_3_5.png)
71
+
72
+ ```ruby
73
+ class Booking < ApplicationRecord
74
+ scope :active_on, ->(date) { where("check_in <= ? AND check_out > ?", date, date) }
75
+ end
76
+
77
+ ActiveAdmin.register Booking do
78
+ filter :room_number
79
+ filter :guest_name_cont, label: "Guest name contains"
80
+
81
+ index as: :calendar, group_by_scope: :active_on do |date, bookings|
82
+ ul do
83
+ bookings.sort_by(&:room_number).each do |b|
84
+ first, last = b.check_in == date, b.check_out - 1.day == date
85
+ marker = first && last ? "•" : first ? "→" : last ? "←" : "·"
86
+ li { text_node "#{marker} ##{b.room_number} #{b.guest_name}" }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ ```
92
+
93
+ May 18 → 21 booking shows on 18 (`→`), 19 (`·`), 20 (`←`).
94
+
95
+ ## Options
96
+
97
+ | Option | Effect |
98
+ |---|---|
99
+ | `group_by:` (Symbol) | Bucket by a date/datetime column. **1 SQL** per month (prefetched). |
100
+ | `group_by_scope:` (Symbol) | Call `Model.scope(date)` per cell — for ranges, joins, or any bucket logic that simple `where(col = date)` can't express. **N SQL** per month (one per visible day). |
101
+
102
+ URL params `?year=2026&month=5` drive the visible month. Today / Previous /
103
+ Next links are rendered automatically and preserve current filter params.
104
+
105
+ Ransack filters apply transparently — the gem buckets the already-scoped
106
+ collection. `?q[card_type_eq]=visa` narrows what's shown in each cell.
107
+
108
+ ## Compatibility
109
+
110
+ | AA | Status |
111
+ |---|---|
112
+ | 4.0.0.beta22 | ✅ |
113
+ | 3.5 | ✅ |
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,104 @@
1
+ /*
2
+ * AA 4 wraps every index_as_* component in `.paginated-collection` /
3
+ * `.paginated-collection-contents`, which Tailwind plugin styles as a panel
4
+ * with border + background. The calendar has its own visual structure and
5
+ * doesn't need that wrapper — reset it.
6
+ */
7
+ .paginated-collection:has(#index_calendar),
8
+ .paginated-collection:has(#index_calendar) .paginated-collection-contents {
9
+ background: transparent;
10
+ border: 0;
11
+ box-shadow: none;
12
+ padding: 0;
13
+ }
14
+
15
+ div#index_calendar_header {
16
+ display: flex;
17
+ align-items: baseline;
18
+ gap: 1.5em;
19
+ margin-bottom: 0.5em;
20
+ }
21
+
22
+ div#index_calendar_header h2 {
23
+ margin: 0;
24
+ }
25
+
26
+ ul#index_calendar_nav {
27
+ list-style-type: none;
28
+ padding: 0;
29
+ margin: 0;
30
+ display: flex;
31
+ gap: 0.75em;
32
+ }
33
+
34
+ ul#index_calendar_nav li {
35
+ margin: 0;
36
+ }
37
+
38
+ ul#index_calendar_nav li a {
39
+ display: block;
40
+ padding: 2px 8px;
41
+ border: 1px solid #ddd;
42
+ text-decoration: none;
43
+ color: #222;
44
+ background-color: #eee;
45
+ }
46
+
47
+ ul#index_calendar_nav li a:hover {
48
+ border-color: #333;
49
+ }
50
+
51
+ table#index_calendar {
52
+ border-collapse: collapse;
53
+ }
54
+
55
+ table#index_calendar th,
56
+ table#index_calendar td {
57
+ line-height: 1.5em;
58
+ width: 14.286%;
59
+ }
60
+
61
+ table#index_calendar thead th {
62
+ color: #555;
63
+ background-color: transparent;
64
+ font-weight: normal;
65
+ text-align: left;
66
+ }
67
+
68
+ table#index_calendar tbody td {
69
+ height: 120px;
70
+ vertical-align: top;
71
+ border: 1px solid #ddd;
72
+ font-size: 90%;
73
+ padding: 6px 8px;
74
+ }
75
+
76
+ table#index_calendar thead th {
77
+ padding: 4px 8px;
78
+ }
79
+
80
+ table#index_calendar tbody td.current_month {
81
+ color: #333;
82
+ background-color: transparent;
83
+ }
84
+
85
+ table#index_calendar tbody td.not_current_month {
86
+ color: #aaa;
87
+ background-color: transparent;
88
+ }
89
+
90
+ table#index_calendar tbody td.today {
91
+ color: #222;
92
+ background-color: #eee;
93
+ }
94
+
95
+ table#index_calendar tbody td.today .day {
96
+ font-weight: bold;
97
+ }
98
+
99
+ table#index_calendar ul,
100
+ table#index_calendar ol {
101
+ list-style-type: none;
102
+ padding: 0;
103
+ margin: 0;
104
+ }
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/engine"
4
+
5
+ module ActiveadminCalendar
6
+ class Engine < ::Rails::Engine
7
+ config.to_prepare do
8
+ require "activeadmin_calendar/index_as_calendar"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Views
5
+ # = Index as a Calendar
6
+ #
7
+ # Render the index page as a month calendar grid. Resources are bucketed
8
+ # into day cells by a configurable attribute (default: `updated_at`) or via
9
+ # a custom scope.
10
+ #
11
+ # index as: :calendar, group_by: :paid_at do |date, payments|
12
+ # ul { payments.each { |p| li p.amount } }
13
+ # end
14
+ #
15
+ # For pre-bucketed scopes (when the group-by column is not on the table or
16
+ # needs custom SQL) pass `:group_by_scope`:
17
+ #
18
+ # scope :on_date, ->(date) { where(paid_at: date.all_day) }
19
+ # index as: :calendar, group_by_scope: :on_date do |date, items|
20
+ # # ...
21
+ # end
22
+ class IndexAsCalendar < ActiveAdmin::Component
23
+ def self.index_name
24
+ "calendar"
25
+ end
26
+
27
+ def build(page_presenter, collection)
28
+ @page_presenter = page_presenter
29
+ @collection = collection
30
+ prefetch_month_records!
31
+ build_calendar
32
+ end
33
+
34
+ def group_by
35
+ @page_presenter[:group_by] || :updated_at
36
+ end
37
+
38
+ def group_by_scope
39
+ @page_presenter[:group_by_scope]
40
+ end
41
+
42
+ private
43
+
44
+ def build_calendar
45
+ build_navigation
46
+ build_table
47
+ end
48
+
49
+ def build_navigation
50
+ prev_month = current_month.at_beginning_of_month - 1
51
+ next_month = current_month.at_end_of_month + 1
52
+
53
+ div id: "index_calendar_header" do
54
+ h2 current_month.strftime("%B %Y")
55
+
56
+ ul id: "index_calendar_nav" do
57
+ li link_to("Today", params.to_unsafe_h.except(:year, :month)), class: "today"
58
+ li link_to("Previous", params.to_unsafe_h.merge(year: prev_month.year, month: prev_month.month)), class: "prev"
59
+ li link_to("Next", params.to_unsafe_h.merge(year: next_month.year, month: next_month.month)), class: "next"
60
+ end
61
+ end
62
+ end
63
+
64
+ def build_table
65
+ table id: "index_calendar" do
66
+ build_table_headers
67
+ build_table_body
68
+ end
69
+ end
70
+
71
+ def build_table_headers
72
+ thead do
73
+ tr do
74
+ 7.times do |i|
75
+ th I18n.t("date.abbr_day_names").rotate[i].to_s.capitalize
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def build_table_body
82
+ tbody do
83
+ (grid_start_date..grid_end_date).to_a.in_groups_of(7).each { |week| build_week(week) }
84
+ end
85
+ end
86
+
87
+ def build_week(dates)
88
+ tr { dates.each { |d| build_day(d) } }
89
+ end
90
+
91
+ def build_day(date)
92
+ active = date.month == current_month.month && date.year == current_month.year
93
+ classes = [active ? "current_month" : "not_current_month"]
94
+ classes << "today" if date == Time.zone.now.to_date
95
+
96
+ td class: classes.join(" ") do
97
+ div class: "day" do
98
+ date.day == 1 ? date.strftime("%b #{date.day}") : date.day.to_s
99
+ end
100
+ scope = day_scope(date)
101
+ instance_exec(date, scope, &@page_presenter.block) if @page_presenter.block
102
+ end
103
+ end
104
+
105
+ # Pre-loads the entire visible month range in one SQL query and groups
106
+ # the rows in Ruby by date. Called once in #build. Skipped when the user
107
+ # opts into `group_by_scope:` — that mode keeps per-cell semantics since
108
+ # we can't infer how a custom scope buckets rows.
109
+ def prefetch_month_records!
110
+ return if group_by_scope
111
+ return unless @collection.respond_to?(:where)
112
+
113
+ col = group_by.to_s
114
+ records = @collection.where("#{col} >= ? AND #{col} < ?", grid_start_date, grid_end_date.tomorrow).to_a
115
+ @prefetched_by_date = records.group_by do |r|
116
+ val = r.public_send(group_by)
117
+ val.respond_to?(:to_date) ? val.to_date : val
118
+ end
119
+ @prefetched_by_date.default = [].freeze
120
+ end
121
+
122
+ def day_scope(date)
123
+ if group_by_scope
124
+ @collection.public_send(group_by_scope, date)
125
+ else
126
+ @prefetched_by_date[date]
127
+ end
128
+ end
129
+
130
+ def grid_start_date
131
+ current_month.at_beginning_of_month.beginning_of_week
132
+ end
133
+
134
+ def grid_end_date
135
+ current_month.at_end_of_month.end_of_week
136
+ end
137
+
138
+ def current_month
139
+ @current_month ||= begin
140
+ now = Time.zone.now
141
+ year = params[:year].presence&.to_i || now.year
142
+ month = params[:month].presence&.to_i || now.month
143
+ Date.new(year, month, 1)
144
+ rescue Date::Error, ArgumentError, TypeError
145
+ now.to_date.beginning_of_month
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveadminCalendar
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activeadmin_calendar/version"
4
+
5
+ module ActiveadminCalendar
6
+ end
7
+
8
+ require "activeadmin_calendar/engine" if defined?(Rails)
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activeadmin_calendar
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Igor Fedoronchuk
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activeadmin
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '3.5'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '3.5'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: arbre
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '1.4'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '1.4'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: railties
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '7.0'
59
+ type: :runtime
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '7.0'
66
+ description: 'Adds `index as: :calendar` — a month-grid index style that buckets resources
67
+ by a date attribute or custom scope and yields each day cell.'
68
+ email:
69
+ - fedoronchuk@gmail.com
70
+ executables: []
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - LICENSE.txt
75
+ - README.md
76
+ - app/assets/stylesheets/activeadmin_calendar.css
77
+ - lib/activeadmin_calendar.rb
78
+ - lib/activeadmin_calendar/engine.rb
79
+ - lib/activeadmin_calendar/index_as_calendar.rb
80
+ - lib/activeadmin_calendar/version.rb
81
+ homepage: https://github.com/activeadmin-plugins/activeadmin_calendar
82
+ licenses:
83
+ - MIT
84
+ metadata:
85
+ homepage_uri: https://github.com/activeadmin-plugins/activeadmin_calendar
86
+ source_code_uri: https://github.com/activeadmin-plugins/activeadmin_calendar
87
+ changelog_uri: https://github.com/activeadmin-plugins/activeadmin_calendar/releases
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '3.1'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.7.1
103
+ specification_version: 4
104
+ summary: Calendar index style for ActiveAdmin
105
+ test_files: []