oversee 0.1.1 → 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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -14
  3. data/app/assets/config/oversee_manifest.js +2 -1
  4. data/app/components/oversee/{application_component.rb → base.rb} +1 -1
  5. data/app/components/oversee/card.rb +13 -0
  6. data/app/components/oversee/dashboard/filters.rb +58 -0
  7. data/app/components/oversee/dashboard/header.rb +57 -0
  8. data/app/components/oversee/dashboard/index.rb +36 -0
  9. data/app/components/oversee/dashboard/javascript.rb +9 -0
  10. data/app/components/oversee/dashboard/pagination.rb +23 -0
  11. data/app/components/oversee/dashboard/sidebar.rb +90 -0
  12. data/app/components/oversee/dashboard/tailwind.rb +62 -0
  13. data/app/components/oversee/field/display.rb +38 -0
  14. data/app/components/oversee/field/form.rb +30 -0
  15. data/app/components/oversee/field/input/belongs_to.rb +22 -0
  16. data/app/components/oversee/field/input/boolean.rb +13 -0
  17. data/app/components/oversee/field/input/datetime.rb +10 -0
  18. data/app/components/oversee/field/input/integer.rb +10 -0
  19. data/app/components/oversee/field/input/rich_text.rb +18 -0
  20. data/app/components/oversee/field/input/string.rb +10 -0
  21. data/app/components/oversee/field/input.rb +34 -0
  22. data/app/components/oversee/field/label.rb +25 -0
  23. data/app/components/oversee/field/value/belongs_to.rb +21 -0
  24. data/app/components/oversee/field/value/boolean.rb +50 -0
  25. data/app/components/oversee/field/value/datetime.rb +9 -0
  26. data/app/components/oversee/field/value/enum.rb +9 -0
  27. data/app/components/oversee/field/value/integer.rb +9 -0
  28. data/app/components/oversee/field/value/rich_text.rb +9 -0
  29. data/app/components/oversee/field/value/string.rb +20 -0
  30. data/app/components/oversee/field/value/text.rb +9 -0
  31. data/app/components/oversee/field/value.rb +40 -0
  32. data/app/components/oversee/layout/application.rb +46 -0
  33. data/app/components/oversee/resources/base.rb +4 -0
  34. data/app/components/oversee/resources/errors.rb +41 -0
  35. data/app/components/oversee/resources/form.rb +49 -0
  36. data/app/components/oversee/resources/index.rb +45 -0
  37. data/app/components/oversee/resources/new.rb +21 -0
  38. data/app/components/oversee/resources/show.rb +207 -0
  39. data/app/components/oversee/resources/table.rb +115 -0
  40. data/app/components/oversee/table/body.rb +13 -0
  41. data/app/components/oversee/table/data.rb +5 -0
  42. data/app/components/oversee/table/head.rb +9 -0
  43. data/app/components/oversee/table/row.rb +9 -0
  44. data/app/components/oversee/table.rb +21 -0
  45. data/app/controllers/oversee/dashboard_controller.rb +4 -0
  46. data/app/controllers/oversee/resources/fields_controller.rb +20 -36
  47. data/app/controllers/oversee/resources_controller.rb +65 -26
  48. data/app/javascript/oversee/application.js +3 -0
  49. data/app/javascript/oversee/controllers/index.js +8 -0
  50. data/app/javascript/oversee/controllers/reveal_controller.js +7 -0
  51. data/app/javascript/oversee/controllers/sidebar/state_controller.js +21 -0
  52. data/app/models/oversee/resource.rb +62 -0
  53. data/app/service/oversee/filter.rb +39 -0
  54. data/app/service/oversee/search.rb +34 -0
  55. data/app/views/oversee/base.rb +5 -0
  56. data/config/importmap.rb +14 -0
  57. data/config/locales/en.yml +4 -0
  58. data/config/locales/oversee.en.yml +4 -0
  59. data/config/routes.rb +17 -11
  60. data/lib/generators/oversee/install_generator.rb +7 -0
  61. data/lib/oversee/configuration.rb +3 -0
  62. data/lib/oversee/engine.rb +19 -0
  63. data/lib/oversee/version.rb +1 -1
  64. data/lib/oversee.rb +26 -2
  65. data/lib/tasks/oversee_tasks.rake +6 -4
  66. metadata +95 -48
  67. data/app/assets/stylesheets/oversee/application.css +0 -15
  68. data/app/components/oversee/card_component.rb +0 -15
  69. data/app/components/oversee/dashboard/sidebar_component.rb +0 -127
  70. data/app/components/oversee/field_component.rb +0 -39
  71. data/app/components/oversee/field_label_component.rb +0 -155
  72. data/app/components/oversee/fields/display_row_component.rb +0 -58
  73. data/app/components/oversee/fields/input/boolean_component.rb +0 -26
  74. data/app/components/oversee/fields/input/datetime_component.rb +0 -27
  75. data/app/components/oversee/fields/input/integer_component.rb +0 -26
  76. data/app/components/oversee/fields/input/string_component.rb +0 -26
  77. data/app/components/oversee/fields/value/boolean_component.rb +0 -44
  78. data/app/components/oversee/fields/value/datetime_component.rb +0 -16
  79. data/app/components/oversee/fields/value/enum_component.rb +0 -16
  80. data/app/components/oversee/fields/value/integer_component.rb +0 -16
  81. data/app/components/oversee/fields/value/string_component.rb +0 -23
  82. data/app/components/oversee/fields/value/text_component.rb +0 -16
  83. data/app/helpers/oversee/application_helper.rb +0 -5
  84. data/app/jobs/oversee/application_job.rb +0 -4
  85. data/app/mailers/oversee/application_mailer.rb +0 -6
  86. data/app/oversee/cards.rb +0 -2
  87. data/app/oversee/resource.rb +0 -10
  88. data/app/views/layouts/oversee/application.html.erb +0 -26
  89. data/app/views/oversee/application/_javascript.html.erb +0 -17
  90. data/app/views/oversee/dashboard/show.html.erb +0 -18
  91. data/app/views/oversee/resources/_form.html.erb +0 -10
  92. data/app/views/oversee/resources/_input_field.html.erb +0 -20
  93. data/app/views/oversee/resources/edit.html.erb +0 -30
  94. data/app/views/oversee/resources/index.html.erb +0 -59
  95. data/app/views/oversee/resources/input_field.html.erb +0 -1
  96. data/app/views/oversee/resources/new.html.erb +0 -28
  97. data/app/views/oversee/resources/show.html.erb +0 -51
  98. data/app/views/oversee/resources/update.turbo_stream.erb +0 -3
  99. data/app/views/shared/_sidebar.html.erb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eda2b577791e666d84d3b2af260c06604489d84936c8ff907f51573f5441e7e7
4
- data.tar.gz: 83ca5ac7da8e9b5f66bc19523e2b78875634e669b0acd7e34bad2b2dcb76887d
3
+ metadata.gz: c31a8c277449dfaec238f66cfc8e404a64c51dff4beef1a944fee85c5935991c
4
+ data.tar.gz: f3fcd62d4ff3184f5b9f97273c2269e986b5f03736a9961440aca242b38a1292
5
5
  SHA512:
6
- metadata.gz: 0a7fc59d7b8f317e15927ec883017b374f5216aeaa5208bdb29d67f3cbf03dd944e57b7a90e5eac161cde60257276def41ebfa5e34399f3f41db43e47c3e63d8
7
- data.tar.gz: a441505a216cf3df44dc9a58be3e135a5741564b3b8a51e1e486de08a53dfdfe0f640dcc2126813c1dd3d32d21deb806480f52a03b8deab54a6713af3e33b6ad
6
+ metadata.gz: ad42840ab6b72667e5e16853cea4d9028fc1356fd4ff2ec7d3909b6102c2fc80dca9287245ad4ab62021181c238beae371f77b473f1f9464bf0b1162a29946c5
7
+ data.tar.gz: 229f928f44cfe6d6535cd8ef6682472a9f5ecf59e29bc65714ed71e1c6147ab48b582e5c065fc1e9233d5e536155e16d8750a1fc92f3e74bd97e3720cbc58e42
data/README.md CHANGED
@@ -1,40 +1,79 @@
1
- [![Gem Version](https://badge.fury.io/rb/oversee.svg)](https://badge.fury.io/rb/oversee)
2
1
 
3
- # 👓 Oversee
2
+ # Oversee
3
+
4
4
  Plug & play admin dashboard for Rails applications.
5
5
 
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/oversee.svg)](https://badge.fury.io/rb/oversee)
8
+
9
+ ---
10
+
11
+ ### Features
12
+
13
+ - Minimal, if any, configuration needed
14
+ - Tailored and pleasant user interface
15
+ - Geared for performance
16
+
17
+ ---
18
+
19
+ ![Oversee Screenshot](docs/images/screenshot.png)
20
+
6
21
  Developed by [Primevise](https://primevise.com)
7
22
 
8
- > [!NOTE]
23
+ > [!NOTE]
9
24
  > Oversee is still rather incomplete and only has the very basic features. It might significantly change and break things before a stable release.
10
25
 
11
26
  ## Installation
12
- Add it to your application by running
27
+
28
+ #### Add gem
29
+
30
+ Simply add the gem to your Gemfile by running the following command
13
31
 
14
32
  ```bash
15
33
  $ bundle add oversee
16
34
  ```
17
35
 
36
+ > [!TIP]
37
+ > Currently, we don't release new gem versions too often due to the fragile nature of the gem. However, you can use the edge version of the gem by pointing directly to the git repository.
38
+ > ```ruby
39
+ > gem "oversee", git: "https://github.com/primevise/oversee", branch: :main
40
+ > ```
18
41
 
19
- or add this line to your application's Gemfile:
42
+ ---
20
43
 
21
- ```ruby
22
- gem "oversee"
23
- ```
44
+ #### Mount it on your application
24
45
 
25
- And then run:
26
- ```bash
27
- $ bundle
46
+ After you have the gem installed, you can then mount the engine in your `config/routes.rb`:
47
+
48
+ ```ruby
49
+ Rails.application.routes.draw do
50
+ mount Oversee::Engine => "/oversee"
51
+ end
28
52
  ```
29
53
 
30
- ## Contributing
31
- TBA
54
+ Ideally, you'd want to limit access to the dashboard to only authorized users.
32
55
 
33
56
  ## Notable mentions
34
- This gem is inspired by other open source admin dashboards. A big thank you goes to these projects:
57
+
58
+ This gem would not be possible without the work of others. A big thank you goes to these projects:
35
59
 
36
60
  - [madmin](https://github.com/excid3/madmin)
37
61
  - [Avo](https://github.com/avo-hq/avo)
62
+ - [Iconoir](https://github.com/iconoir-icons/iconoir)
63
+
64
+ ## Who uses Oversee?
65
+
66
+ - [Mintis](https://mintis.app)
67
+ - [No Logo X](https://nologox.com)
68
+ - [Release Server](https://releaseserver.com)
69
+ - [College Life Work](https://work.collegelife.co)
70
+
71
+ Do you use Oversee in your project? Let us know!
72
+
73
+ ## Contributing
74
+
75
+ TBA
38
76
 
39
77
  ## License
78
+
40
79
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1 +1,2 @@
1
- //= link_directory ../stylesheets/oversee .css
1
+ //= link oversee/application.js
2
+ //= link_tree ../../javascript/oversee/controllers .js
@@ -1,4 +1,4 @@
1
- class Oversee::ApplicationComponent < Phlex::HTML
1
+ class Oversee::Base < Phlex::HTML
2
2
  include Phlex::Rails::Helpers::DOMID
3
3
  include Phlex::Rails::Helpers::Routes
4
4
  include Phlex::Rails::Helpers::LinkTo
@@ -0,0 +1,13 @@
1
+ class Oversee::Card < Phlex::HTML
2
+ def initialize(card_name:)
3
+ @card_name = card_name
4
+ @card = card_name.constantize.new
5
+ end
6
+
7
+ def view_template
8
+ div(class: "bg-gray-100 rounded-md p-4") do
9
+ p(class: "text-xs uppercase text-gray-600") { @card.label }
10
+ h4(class: "mt-2 text-gray-900 text-2xl") { @card.value }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,58 @@
1
+ class Oversee::Dashboard::Filters < Oversee::Base
2
+ def initialize(params: nil)
3
+
4
+ @params = params
5
+
6
+ puts "params: #{params}"
7
+ puts "sortless_path: #{sortless_path}"
8
+ end
9
+
10
+ def view_template(&)
11
+ div(class: "flex items-center justify-between") do
12
+ div(class: "flex items-center gap-2") do
13
+ if show_action_section?
14
+ button(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
15
+ render Phlex::Icons::Iconoir::FilterAlt.new(class: "size-3")
16
+ plain "Filters"
17
+ end
18
+ end
19
+ if false
20
+ a(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
21
+ render Phlex::Icons::Iconoir::XMark.new(class: "size-4 text-gray-500")
22
+ plain "Clear sorting"
23
+ end
24
+ end
25
+ end
26
+ div(class: "flex items-center gap-4") do
27
+ form(action: "", class: "flex items-center gap-2") do
28
+ input(
29
+ type: :search,
30
+ name: :query,
31
+ value: @params[:query],
32
+ placeholder: search_placeholder,
33
+ class: "flex bg-gray-100 min-w-64 w-64 focus:w-96 transition-all h-10 items-center pl-4 py-2 placeholder:text-gray-500 rounded-sm text-sm"
34
+ )
35
+ button(class: "size-10 inline-flex items-center justify-center bg-gray-100 hover:bg-gray-200 transition-colors") { render Phlex::Icons::Iconoir::Search.new(class: "size-4 text-gray-600") }
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def show_action_section?
44
+ Rails.env.development? || @params[:experimental] == "true"
45
+ end
46
+
47
+ def sortless_path
48
+ sortless_query_params = @params.except(:sort_attribute, :sort_direction, :controller, :action)
49
+ # helpers.resources_path(sortless_query_params)
50
+ end
51
+
52
+ def search_placeholder
53
+ context = Oversee::Search.new(collection: nil, resource_class: @params[:resource_class_name].constantize)
54
+ attr = context.default_searchable_attribute
55
+
56
+ "Search by #{attr}"
57
+ end
58
+ end
@@ -0,0 +1,57 @@
1
+ class Oversee::Dashboard::Header < Oversee::Base
2
+ attr_reader :title
3
+ attr_reader :subtitle
4
+ attr_reader :return_path
5
+ attr_reader :show_back_button
6
+
7
+ def initialize(title: nil, subtitle: nil, return_path: nil, show_back_button: true)
8
+ @title = title || "Dashboard"
9
+ @subtitle = subtitle || "Manage your account"
10
+ @return_path = return_path
11
+ @show_back_button = show_back_button
12
+ end
13
+
14
+ def view_template(&)
15
+
16
+ div(class: "min-h-10 flex items-center justify-between") do
17
+ if block_given?
18
+ yield(self)
19
+ else
20
+ left
21
+ right
22
+ end
23
+ end
24
+ end
25
+
26
+ def left(&)
27
+ div(class: "flex items-center gap-2") do
28
+ back_button if show_back_button
29
+ h3(class: "text-lg font-medium text-gray-900") { title } if title.present?
30
+ yield if block_given?
31
+ end
32
+ end
33
+
34
+ def right(&)
35
+ div(class: "flex items-center gap-4", &)
36
+ end
37
+
38
+ def separator
39
+ render Phlex::Icons::Iconoir::Slash.new(class: "size-4 text-gray-300", stroke_width: 1.75)
40
+ end
41
+
42
+ private
43
+
44
+ def back_button
45
+ a(
46
+ href: return_button_path,
47
+ class: "mr-2 size-8 inline-flex items-center justify-center bg-gray-50 text-gray-500 hover:text-gray-900 hover:bg-gray-200 transition-colors",
48
+ data: { controller: "back", action: "back#navigate", turbo_action: "replace" }
49
+ ) do
50
+ render Phlex::Icons::Iconoir::ArrowLeft.new(class: "size-3", stroke_width: 2.5)
51
+ end
52
+ end
53
+
54
+ def return_button_path
55
+ @return_path || root_path
56
+ end
57
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Dashboard::Index < Oversee::Base
4
+
5
+ def around_template
6
+ render Oversee::Layout::Application.new { super }
7
+ end
8
+
9
+ def view_template
10
+ div(class: "flex items-center justify-between") do
11
+ h1(class: "text-lg font-medium text-gray-900") { "Dashboard" }
12
+ h1(class: "text-sm font-medium text-gray-500") { Date.current.to_fs(:long) }
13
+ end
14
+
15
+ if Oversee.card_class_names.present?
16
+ div(class: "grid grid-cols-4 gap-4") do
17
+ Oversee.card_class_names.each do |card_name|
18
+ render Oversee::Card.new(card_name: card_name)
19
+ end
20
+ end
21
+ end
22
+
23
+ hr(class: "my-4")
24
+
25
+ div(class: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4") do
26
+ Oversee.application_resource_names.sort.each do |resource_class_name|
27
+ a(href: helpers.resources_path(resource_class_name:), class: "w-full bg-gray-100/75 block hover:bg-gray-50 p-4 truncate") do
28
+ div(class: "flex items-center justify-center size-8 bg-white") do
29
+ render Phlex::Icons::Iconoir::Folder.new(class: "size-4 text-gray-400")
30
+ end
31
+ p(class: "mt-4 font-medium text-gray-700") { resource_class_name }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ class Oversee::Dashboard::Javascript < Phlex::HTML
2
+ include Phlex::Rails::Helpers::JavascriptImportmapTags
3
+
4
+ def view_template
5
+ script(async: true, src:"https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js")
6
+ javascript_importmap_tags("application", importmap: Oversee.importmap)
7
+ script(type: "module") { raw %(import * as Turbo from "@hotwired/turbo-rails").html_safe }
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ class Oversee::Dashboard::Pagination < Oversee::Base
3
+ include Pagy::Frontend
4
+ include Phlex::Rails::Helpers::Request
5
+
6
+ def initialize(pagy:, params:)
7
+ @pagy = pagy
8
+ @params = params
9
+ end
10
+
11
+ def view_template
12
+ div(class:"mt-4 flex items-center justify-between") do
13
+
14
+ div(class: "font-regular text-xs") do
15
+ raw pagy_info(@pagy).html_safe
16
+ end
17
+
18
+ div(class: "flex items-center gap-4") do
19
+ raw pagy_nav(@pagy).html_safe
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Dashboard::Sidebar < Oversee::Base
4
+ def view_template
5
+ div(class: "bg-white p-4 rounded-lg") do
6
+ button(class: "mb-4 ") { collapse_icon } unless true
7
+
8
+ p(class: "text-[0.7rem] uppercase text-gray-400 font-medium mb-2") { "Menu" }
9
+ ul(class: "text-sm text-gray-700") do
10
+ li do
11
+ a(href: root_path, class:"flex items-center gap-2 hover:bg-gray-50 p-2") do
12
+ render Phlex::Icons::Iconoir::LayoutLeft.new(class: "size-4 text-gray-400")
13
+ span { "Dashboard" }
14
+ end
15
+ end
16
+ li do
17
+ a(href: helpers.main_app.root_path, class: "flex items-center gap-2 hover:bg-gray-50 p-2 rounded-lg") do
18
+ render Phlex::Icons::Iconoir::HomeAltSlimHoriz.new(class: "size-4 text-gray-400")
19
+ span { "Return to app" }
20
+ end
21
+ end
22
+ end
23
+
24
+ if Rails.env.development?
25
+ hr(class: "my-4 -mx-4")
26
+ details(
27
+ id: "links-menu",
28
+ class: "group",
29
+ # open: true,
30
+ data: {
31
+ controller: "sidebar--state",
32
+ action: "sidebar--state#persist",
33
+ sidebar__state_target: "expandable"
34
+ }
35
+ ) do
36
+ summary(class: "flex items-center justify-between cursor-pointer") do
37
+ p(class: "text-[0.7rem] uppercase text-gray-400 font-medium") { "Links" }
38
+ render Phlex::Icons::Iconoir::NavArrowDown.new(class: "size-4 text-gray-400 transform transition-transform group-open:rotate-180 group-hover:text-blue-500")
39
+ end
40
+ ul(class: "mt-2 text-sm text-gray-700 overflow-x-hidden") do
41
+ li do
42
+ a(href: "https://github.com/primevise/oversee", target: "_blank", class: "flex items-center gap-2 hover:bg-gray-50 p-2 truncate") do
43
+ render Phlex::Icons::Iconoir::ArrowUpRightSquare.new(class: "size-4 text-gray-400")
44
+ span { "Repository" }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ hr(class: "my-4 -mx-4")
52
+
53
+ details(
54
+ id: "resources-menu",
55
+ class: "group",
56
+ # open: true,
57
+ data: {
58
+ controller: "sidebar--state",
59
+ action: "sidebar--state#persist",
60
+ sidebar__state_target: "expandable"
61
+ }
62
+ ) do
63
+ summary(class: "flex items-center justify-between cursor-pointer") do
64
+ p(class: "text-[0.7rem] uppercase text-gray-400 font-medium") { "Resources" }
65
+ render Phlex::Icons::Iconoir::NavArrowDown.new(class: "size-4 text-gray-400 transform transition-transform group-open:rotate-180 group-hover:text-blue-500")
66
+ end
67
+ ul(class: "mt-2 text-sm text-gray-700 overflow-x-hidden") do
68
+ Oversee.application_resource_names.sort.each do |resource_class_name|
69
+ li do
70
+ a(href: helpers.resources_path(resource_class_name:), class: "flex items-center gap-2 hover:bg-gray-50 p-2 truncate") do
71
+ render Phlex::Icons::Iconoir::Folder.new(class: "size-4 text-gray-400")
72
+ span { resource_class_name }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ hr(class: "my-4 -mx-4")
80
+ p(class: "text-xs text-gray-500") do
81
+ plain("Powered by ")
82
+ a(
83
+ href: "https://github.com/primevise/oversee",
84
+ class: "text-blue-500 hover:underline",
85
+ target: "_blank"
86
+ ) { "Oversee" }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,62 @@
1
+ class Oversee::Dashboard::Tailwind < Phlex::HTML
2
+ def view_template
3
+ script(src: "https://cdn.tailwindcss.com/3.4.15?plugins=typography@0.5.15")
4
+ style(type:"text/tailwindcss") do
5
+ raw pagy_css.html_safe
6
+ raw trix_css.html_safe
7
+ end
8
+ end
9
+
10
+ def trix_css
11
+ <<~CSS
12
+ trix-editor {
13
+ @apply rounded-b rounded-t-none border border-t-0 border-gray-200 !ring-0;
14
+ }
15
+
16
+ .trix-active {
17
+ @apply !bg-gray-100 !text-blue-500;
18
+ }
19
+
20
+ .trix-button-row {
21
+ @apply overflow-hidden rounded-t border;
22
+ }
23
+
24
+ .trix-button {
25
+ @apply !relative !inline-flex !h-7 !items-center !justify-center border-0 !border-b-0 !border-l-0 !px-7 !py-4;
26
+ }
27
+
28
+ .trix-button::before {
29
+ @apply !top-1.5 !h-5;
30
+ }
31
+
32
+ .trix-button-group {
33
+ @apply !mb-0 rounded-b-none !border-0 border-gray-200;
34
+ }
35
+ CSS
36
+ end
37
+
38
+ def pagy_css
39
+ <<~CSS
40
+ .pagy {
41
+ @apply flex gap-2 items-center text-sm text-gray-500;
42
+ a:not(.gap) {
43
+ @apply inline-flex items-center justify-center h-8 min-w-8 font-normal;
44
+ &:hover { @apply bg-gray-100; }
45
+ &:not([href]) { /* disabled links */
46
+ @apply text-gray-300 cursor-not-allowed;
47
+ }
48
+ &.current {
49
+ @apply font-semibold bg-gray-100 text-gray-900;
50
+ }
51
+ }
52
+
53
+ label {
54
+ @apply inline-block whitespace-nowrap bg-gray-200 px-3 py-0.5;
55
+ input {
56
+ @apply bg-gray-100 border-none rounded-md;
57
+ }
58
+ }
59
+ }
60
+ CSS
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ class Oversee::Field::Display < Oversee::Base
2
+
3
+ attr_reader :resource
4
+ attr_reader :key
5
+ attr_reader :value
6
+ attr_reader :datatype
7
+
8
+ def initialize(resource:, key: nil, value: nil, datatype: :string, **options)
9
+ @resource = resource
10
+ @key = key
11
+ @value = value
12
+ @datatype = datatype
13
+ @options = options
14
+ end
15
+
16
+ def view_template
17
+ html_tag(
18
+ id: dom_id(resource, key),
19
+ href: helpers.resource_input_path(resource_class_name:, key:, datatype:),
20
+ class: "bg-gray-100 h-10 flex items-center px-4 py-2 hover:bg-gray-200 transition-colors w-full cursor-pointer",
21
+ data: { turbo_stream: true }
22
+ ) do
23
+ render Oversee::Field::Value.new(key:, value:, datatype:, **@options)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def resource_class_name
30
+ @resource_class_name ||= @resource.class.name
31
+ end
32
+
33
+ def html_tag(...)
34
+ edittable? ? a(...) : div(...)
35
+ end
36
+
37
+ def edittable? = @options[:edittable] || true
38
+ end
@@ -0,0 +1,30 @@
1
+ class Oversee::Field::Form < Oversee::Base
2
+ include Phlex::Rails::Helpers::FormWith
3
+
4
+ def initialize(resource:, key: nil, value: nil, datatype: :string, method: :patch, **options)
5
+ @resource = resource
6
+ @key = key
7
+ @value = value
8
+ @datatype = datatype
9
+ @method = method
10
+ @options = options
11
+ end
12
+
13
+ def view_template
14
+ form_with(id: dom_id(@resource, @key), model: @resource, url: helpers.update_resource_path(resource_class_name: resource_class_name, id: @resource.id), method: @method, class: "flex items-center w-full gap-4") do |form|
15
+ input type: :hidden, id: "oversee_key", name: "oversee_key", value: @key.to_s
16
+ input type: :hidden, id: "oversee_datatype", name: "oversee_datatype", value: @datatype
17
+ render Oversee::Field::Input.new(key: @key, value: @value, datatype: @datatype, **@options)
18
+ button(class:"h-9 bg-gray-900 hover:bg-gray-700 text-white inline-flex items-center cursor-pointer gap-2 px-4 rounded-full text-xs font-medium") {
19
+ render Phlex::Icons::Iconoir::Check.new(class: "size-4")
20
+ plain "Save"
21
+ }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def resource_class_name
28
+ @resource.class.name
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ class Oversee::Field::Input::BelongsTo < Oversee::Field::Input
2
+ def initialize(key:, value:, **options)
3
+ @key = key
4
+ @value = value
5
+ end
6
+
7
+ def view_template
8
+ input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
9
+
10
+ # select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
11
+
12
+ # option(value: 1, selected: @value) { "True" }
13
+ # option(value: 0, selected: !@value) { "False" }
14
+ # end
15
+ end
16
+
17
+ private
18
+
19
+ def select_values
20
+
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ class Oversee::Field::Input::Boolean < Oversee::Field::Input
2
+ def initialize(key:, value:)
3
+ @key = key
4
+ @value = value
5
+ end
6
+
7
+ def view_template
8
+ select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
9
+ option(value: 1, selected: @value) { "True" }
10
+ option(value: 0, selected: !@value) { "False" }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class Oversee::Field::Input::Datetime < Oversee::Field::Input
2
+ def initialize(key:, value:)
3
+ @key = key
4
+ @value = value
5
+ end
6
+
7
+ def view_template
8
+ input type: "datetime-local", id: field_id, name: field_name, value: @value&.strftime("%Y-%m-%dT%T"), class: "flex w-full border rounded-sm px-4 py-2 text-sm"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class Oversee::Field::Input::Integer < Oversee::Field::Input
2
+ def initialize(key:, value:)
3
+ @key = key
4
+ @value = value
5
+ end
6
+
7
+ def view_template
8
+ input type: "number", id: field_id, name: field_name, value: @value, class: "flex w-full border rounded-sm px-4 py-2 text-sm"
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ class Oversee::Field::Input::RichText < Oversee::Field::Input
2
+ register_element :trix_editor
3
+
4
+ attr_reader :key
5
+ attr_reader :value
6
+
7
+ def initialize(key:, value:)
8
+ @key = key
9
+ @value = value
10
+ end
11
+
12
+ def view_template
13
+ div do
14
+ input(type: "hidden", id: field_id, name: field_name, value:)
15
+ trix_editor(id: "#{field_id}_trix_editor", input: field_id)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ class Oversee::Field::Input::String < Oversee::Field::Input
2
+ def initialize(key:, value:)
3
+ @key = key
4
+ @value = value
5
+ end
6
+
7
+ def view_template
8
+ input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ class Oversee::Field::Input < Oversee::Base
2
+ MAP = {
3
+ belongs_to: Oversee::Field::Input::BelongsTo,
4
+ boolean: Oversee::Field::Input::Boolean,
5
+ datetime: Oversee::Field::Input::Datetime,
6
+ enum: Oversee::Field::Input::String,
7
+ integer: Oversee::Field::Input::Integer,
8
+ rich_text: Oversee::Field::Input::RichText,
9
+ string: Oversee::Field::Input::String,
10
+ text: Oversee::Field::Input::String,
11
+ }
12
+
13
+ def initialize(key: nil, value: nil, datatype: :string, **options)
14
+ @key = key
15
+ @value = value
16
+ @datatype = datatype
17
+ @options = options
18
+ end
19
+
20
+ def view_template
21
+ render component_class.new(key: @key, value: @value, **@options)
22
+ end
23
+
24
+ private
25
+
26
+ def component_class
27
+ MAP[@datatype.to_sym] || Oversee::Field::Input::String
28
+ end
29
+
30
+ private
31
+
32
+ def field_id = "resource_#{@key.to_s}"
33
+ def field_name = "resource[#{@key.to_s}]"
34
+ end