oversee 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -14
  3. data/app/components/oversee/{application_component.rb → base.rb} +1 -1
  4. data/app/components/oversee/card.rb +13 -0
  5. data/app/components/oversee/dashboard/filters.rb +54 -0
  6. data/app/components/oversee/dashboard/header.rb +41 -0
  7. data/app/components/oversee/dashboard/index.rb +24 -0
  8. data/app/components/oversee/dashboard/javascript.rb +9 -0
  9. data/app/components/oversee/dashboard/pagination.rb +23 -0
  10. data/app/components/oversee/dashboard/sidebar.rb +53 -0
  11. data/app/components/oversee/dashboard/tailwind.rb +33 -0
  12. data/app/components/oversee/field/display.rb +29 -0
  13. data/app/components/oversee/field/form.rb +49 -0
  14. data/app/components/oversee/field/input/boolean.rb +23 -0
  15. data/app/components/oversee/field/input/datetime.rb +20 -0
  16. data/app/components/oversee/field/input/integer.rb +20 -0
  17. data/app/components/oversee/field/input/string.rb +20 -0
  18. data/app/components/oversee/field/input.rb +26 -0
  19. data/app/components/oversee/field/label.rb +24 -0
  20. data/app/components/oversee/field/value/boolean.rb +50 -0
  21. data/app/components/oversee/field/value/datetime.rb +9 -0
  22. data/app/components/oversee/field/value/enum.rb +9 -0
  23. data/app/components/oversee/field/value/integer.rb +9 -0
  24. data/app/components/oversee/field/value/string.rb +20 -0
  25. data/app/components/oversee/field/value/text.rb +9 -0
  26. data/app/components/oversee/field/value.rb +34 -0
  27. data/app/components/oversee/resources/base.rb +4 -0
  28. data/app/components/oversee/resources/errors.rb +41 -0
  29. data/app/components/oversee/resources/form.rb +49 -0
  30. data/app/components/oversee/resources/index.rb +26 -0
  31. data/app/components/oversee/resources/new.rb +17 -0
  32. data/app/components/oversee/resources/show.rb +126 -0
  33. data/app/components/oversee/resources/table.rb +109 -0
  34. data/app/components/oversee/table/body.rb +13 -0
  35. data/app/components/oversee/table/data.rb +5 -0
  36. data/app/components/oversee/table/head.rb +9 -0
  37. data/app/components/oversee/table/row.rb +9 -0
  38. data/app/components/oversee/table.rb +21 -0
  39. data/app/controllers/oversee/dashboard_controller.rb +4 -0
  40. data/app/controllers/oversee/resources_controller.rb +62 -23
  41. data/app/models/oversee/resource.rb +36 -0
  42. data/app/oversee/filter.rb +39 -0
  43. data/app/oversee/search.rb +34 -0
  44. data/app/views/layouts/oversee/application.html.erb +5 -5
  45. data/config/locales/en.yml +4 -0
  46. data/config/locales/oversee.en.yml +4 -0
  47. data/config/routes.rb +10 -10
  48. data/lib/oversee/configuration.rb +3 -0
  49. data/lib/oversee/version.rb +1 -1
  50. data/lib/oversee.rb +14 -2
  51. metadata +71 -47
  52. data/app/assets/config/oversee_manifest.js +0 -1
  53. data/app/assets/stylesheets/oversee/application.css +0 -15
  54. data/app/components/oversee/card_component.rb +0 -15
  55. data/app/components/oversee/dashboard/sidebar_component.rb +0 -127
  56. data/app/components/oversee/field_component.rb +0 -39
  57. data/app/components/oversee/field_label_component.rb +0 -155
  58. data/app/components/oversee/fields/display_row_component.rb +0 -58
  59. data/app/components/oversee/fields/input/boolean_component.rb +0 -26
  60. data/app/components/oversee/fields/input/datetime_component.rb +0 -27
  61. data/app/components/oversee/fields/input/integer_component.rb +0 -26
  62. data/app/components/oversee/fields/input/string_component.rb +0 -26
  63. data/app/components/oversee/fields/value/boolean_component.rb +0 -44
  64. data/app/components/oversee/fields/value/datetime_component.rb +0 -16
  65. data/app/components/oversee/fields/value/enum_component.rb +0 -16
  66. data/app/components/oversee/fields/value/integer_component.rb +0 -16
  67. data/app/components/oversee/fields/value/string_component.rb +0 -23
  68. data/app/components/oversee/fields/value/text_component.rb +0 -16
  69. data/app/helpers/oversee/application_helper.rb +0 -5
  70. data/app/mailers/oversee/application_mailer.rb +0 -6
  71. data/app/views/oversee/application/_javascript.html.erb +0 -17
  72. data/app/views/oversee/dashboard/show.html.erb +0 -18
  73. data/app/views/oversee/resources/_form.html.erb +0 -10
  74. data/app/views/oversee/resources/_input_field.html.erb +0 -20
  75. data/app/views/oversee/resources/edit.html.erb +0 -30
  76. data/app/views/oversee/resources/index.html.erb +0 -59
  77. data/app/views/oversee/resources/input_field.html.erb +0 -1
  78. data/app/views/oversee/resources/new.html.erb +0 -28
  79. data/app/views/oversee/resources/show.html.erb +0 -51
  80. data/app/views/oversee/resources/update.turbo_stream.erb +0 -3
  81. data/app/views/shared/_sidebar.html.erb +0 -32
@@ -0,0 +1,20 @@
1
+ class Oversee::Field::Value::String < Phlex::HTML
2
+ def initialize(key: nil, value: nil, kind: :value)
3
+ @key = key
4
+ @value = value
5
+ @kind = kind
6
+ end
7
+
8
+ def view_template
9
+ return p(class: "text-gray-400 text-xs uppercase") { "Empty" } if @value == ""
10
+ return p(class: "text-gray-400 text-xs uppercase") { "Redacted" } if sensitive?
11
+
12
+ p(class: "truncate") { @value }
13
+ end
14
+
15
+ private
16
+
17
+ def sensitive?
18
+ @key&.downcase&.include?("password") || @key&.downcase&.include?("token")
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ class Oversee::Field::Value::Text < Phlex::HTML
2
+ def initialize(datatype: :string, key: nil, value: nil, kind: :value)
3
+ @value = value
4
+ end
5
+
6
+ def view_template
7
+ p(class: "truncate") { @value }
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ class Oversee::Field::Value < Oversee::Base
2
+ MAP = {
3
+ string: Oversee::Field::Value::String,
4
+ boolean: Oversee::Field::Value::Boolean,
5
+ integer: Oversee::Field::Value::Integer,
6
+ datetime: Oversee::Field::Value::Datetime,
7
+ text: Oversee::Field::Value::Text,
8
+ enum: Oversee::Field::Value::Enum
9
+ }
10
+
11
+ def initialize(key: nil, value: nil, datatype: :string, **options)
12
+ @key = key
13
+ @value = value
14
+ @datatype = datatype
15
+ @options = options
16
+
17
+ @class = @options[:class]
18
+ end
19
+
20
+ def view_template
21
+ return p(class: "text-gray-400 text-xs"){ "—" } if @value.nil?
22
+ render component_class.new(key: @key, value: @value)
23
+ end
24
+
25
+ private
26
+
27
+ def component_class
28
+ MAP[@datatype.to_sym] || Oversee::Field::Value::String
29
+ end
30
+
31
+ def for_table?
32
+ @options[:for_table] || false
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Base < Oversee::Base
4
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Errors < Oversee::Base
4
+ def initialize(resource: nil, errors: nil, content: nil, **options)
5
+ @resource = resource
6
+ @errors = errors
7
+ @content = content
8
+ @options = options
9
+ end
10
+
11
+ def view_template
12
+ if @content || @resource&.errors&.any?
13
+ div(class: container_class) do
14
+ div(class: "inline-flex items-center gap-1.5 bg-red-600 py-1 px-2 text-xs text-white") do
15
+ # render Phlex::TablerIcons::ExclamationCircle.new(class: "size-5", stroke_width: 1.75)
16
+ h3(class: "text-sm font-medium") { plain @content || "Something went wrong!" }
17
+ end
18
+ if displayed_errors.present?
19
+ div(class: "mt-4 border-l-2 border-red-600 p-2") do
20
+ ul(class: "space-y-1 text-sm text-gray-700") do
21
+ displayed_errors.each do |message|
22
+ li { "— #{message}" }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def container_class
34
+ @options[:container_class] || "bg-white mb-4"
35
+ end
36
+
37
+ def displayed_errors
38
+ @errors || @resource&.errors&.full_messages
39
+ end
40
+
41
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Form < Oversee::Base
4
+ include Phlex::Rails::Helpers::FormWith
5
+ def initialize(resource:)
6
+ @resource = resource
7
+ end
8
+
9
+ def view_template
10
+ div(id: dom_id(@resource)) do
11
+ render Oversee::Resources::Errors.new(resource: @resource)
12
+ form_with model: @resource,
13
+ scope: :resource,
14
+ url: helpers.create_resource_path(resource_class_name:),
15
+ scope: :resource do |f|
16
+ @resource.class.columns_hash.each do |key, metadata|
17
+ if [@resource.class.primary_key, "created_at", "updated_at"].include?(key)
18
+ next
19
+ end
20
+ div(class: "py-2") do
21
+ render Oversee::Field::Label.new(
22
+ key: key,
23
+ datatype: metadata.sql_type_metadata.type
24
+ )
25
+ div(class: "mt-2") do
26
+ render Oversee::Field::Input.new(
27
+ datatype: metadata.sql_type_metadata.type,
28
+ key: key
29
+ )
30
+ end
31
+ end
32
+ end
33
+ hr(class: "-mx-8 my-8")
34
+ div(class: "flex justify-end mt-8") do
35
+ plain f.submit "Save",
36
+ class:
37
+ "bg-gray-900 px-6 py-2 rounded-full text-white font-medium text-sm hover:bg-gray-700 cursor-pointer"
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ private
45
+
46
+ def resource_class_name
47
+ @resource.class.to_s
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Index < Oversee::Base
4
+ def initialize(resources:, resource_class:, pagy:, params:)
5
+ @resources = resources
6
+ @resource_class = resource_class
7
+ @pagy = pagy
8
+ @params = params
9
+ end
10
+
11
+
12
+ def view_template
13
+ render Oversee::Dashboard::Header.new(title: @resource_class.to_s, subtitle: "Index") do
14
+ a(href: helpers.new_resource_path(@params[:resource_class_name]), class: "inline-flex items-center justify-center size-8 rounded-full bg-emerald-100 text-emerald-500 hover:bg-emerald-200 text-sm font-medium") { render Phlex::Icons::Iconoir::Plus.new(class: "size-4 text-emerald-500") }
15
+ end
16
+ render Oversee::Dashboard::Filters.new(params: @params)
17
+ render Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: @params)
18
+ render Oversee::Dashboard::Pagination.new(pagy: @pagy, params: @params)
19
+ end
20
+
21
+ private
22
+
23
+ def resource_associations
24
+ @resource_class.reflect_on_all_associations.select { |association| association.macro == :belongs_to }
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::New < Oversee::Base
4
+ def initialize(resource:, resource_class:, params:)
5
+ @resource = resource
6
+ @resource_class = resource_class
7
+ @params = params
8
+ end
9
+
10
+ def view_template
11
+ render Oversee::Dashboard::Header.new(title: @resource_class.to_s, subtitle: "Creating new record")
12
+
13
+ div(class: "p-8") do
14
+ render Oversee::Resources::Form.new(resource: @resource)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Show < Oversee::Base
4
+ include Phlex::Rails::Helpers::ButtonTo
5
+ include Phlex::Rails::Helpers::TurboFrameTag
6
+
7
+ def initialize(resource:, resource_class:, resource_associations:, params:)
8
+ @resource = resource
9
+ @resource_class = resource_class
10
+ @resource_associations = resource_associations
11
+ @params = params
12
+ @oversee_resource = Oversee::Resource.new(resource_class: @resource_class, resource: @resource)
13
+ end
14
+
15
+
16
+ def view_template
17
+ render Oversee::Dashboard::Header.new(title: @resource_class.to_s, subtitle: "##{@resource.id}", return_path: helpers.resources_path(resource_class_name: @params[:resource_class_name])) do
18
+ button_to(helpers.resource_path(resource_class_name: @params[:resource_class_name]), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "size-8 inline-flex items-center justify-center rounded-full text-rose-500 bg-rose-50 hover:bg-rose-100 text-sm font-medium transition-colors") { render Phlex::Icons::Iconoir::Trash.new(class: "size-4") }
19
+ end
20
+
21
+ div(class: "p-8") do
22
+ @resource_class.columns_hash.each do |key, metadata|
23
+ div(class: "py-4") do
24
+ div(class: "space-y-2") do
25
+ render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
26
+ div(id: dom_id(@resource, :"#{key}_row"), class: "flex items-center gap-2 mt-4") do
27
+ render Oversee::Field::Display.new(resource: @resource, key: key, datatype: metadata.sql_type_metadata.type)
28
+ # div(id: dom_id(@resource, :"#{key}_actions")) do
29
+ # div(class: "bg-white text-gray-400 size-9 aspect-square inline-flex items-center justify-center"){ render Phlex::Icons::Iconoir::Copy.new(class: "size-5", stroke_width: 1.75) }
30
+ # end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ # BELONGS_TO Associations
37
+ if !!belongs_to_associations.length
38
+ belongs_to_associations.each do |association|
39
+ div(class: "py-6") do
40
+ div(class: "space-y-4") do
41
+ div(class:"flex items-center gap-2") do
42
+ render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
43
+ a(href: helpers.resources_path(resource_class_name: association[:class_name].to_s), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
44
+ end
45
+
46
+ div(class: "flex items-center gap-2 flex-wrap") do
47
+ foreign_key_value = @resource[association[:foreign_key]]
48
+ path = !!foreign_key_value ? helpers.resource_path(id: foreign_key_value, resource_class_name: association[:class_name]) : helpers.resources_path(resource_class_name: association[:class_name])
49
+
50
+ a(
51
+ href: path,
52
+ class:
53
+ "inline-flex items-center gap-2 text-xs border border-transparent bg-gray-100 hover:bg-gray-200 px-3 py-1.5"
54
+ ) do
55
+ plain "#{association[:class_name]} | #{foreign_key_value}"
56
+ span { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # HAS_MANY Associations
65
+ if !!has_many_associations.length
66
+ has_many_associations.each do |association|
67
+ associated_resources = @resource.send(association[:name])
68
+ associated_resource_class = association[:class_name].constantize
69
+
70
+ div(class: "py-6") do
71
+ div(class: "space-y-4") do
72
+ div(class:"flex items-center gap-2") do
73
+ render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
74
+ a(href: helpers.resources_path(resource_class_name: association[:class_name]), class: "hover:text-blue-500") { render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-3") }
75
+ end
76
+
77
+ div(class: "bg-gray-50 p-2") do
78
+ if associated_resources.present?
79
+ div(class: "bg-white") do
80
+ # turbo_frame_tag(
81
+ # dom_id(associated_resource_class, :table),
82
+ # src: helpers.resources_table_path(
83
+ # resource_class_name: association[:class_name],
84
+ # filters: { eq: { association[:foreign_key] => [@resource.id] } }
85
+ # ),
86
+ # loading: :lazy,
87
+ # data: { turbo_stream: true }
88
+ # ) do
89
+ # div(class: "h-20 flex items-center justify-center") { render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "animate-pulse size-6 text-gray-600") }
90
+ # end
91
+
92
+ #
93
+ render Oversee::Resources::Table.new(
94
+ resources: associated_resources,
95
+ resource_class: associated_resource_class,
96
+ params: @params
97
+ )
98
+ end
99
+ else
100
+ p(class: "bg-gray-50 p-2 pr-4 flex gap-2 items-center text-xs") {
101
+ render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "size-3")
102
+ plain "No #{association[:name].to_s.titleize.downcase} found"
103
+ }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def belongs_to_associations
116
+ @oversee_resource.associations[:belongs_to]
117
+ end
118
+
119
+ def has_many_associations
120
+ @oversee_resource.associations[:has_many]
121
+ end
122
+
123
+ def has_associations?
124
+ @resource_associations.present?
125
+ end
126
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Oversee::Resources::Table < Oversee::Base
4
+ include Phlex::Rails::Helpers::TurboFrameTag
5
+
6
+ def initialize(resources:, resource_class:, params:, **options)
7
+ @resources = resources
8
+ @resource_class = resource_class || resources.first.class
9
+ @params = params
10
+ @options = options
11
+ end
12
+
13
+ def view_template
14
+ turbo_frame_tag dom_id(@resource_class, :table), target: :_top do
15
+ render Oversee::Table.new do |table|
16
+ table.head do |head|
17
+ head.row do
18
+ th(scope: "col", class: "px-4 py-3 text-left text-xs text-gray-900 uppercase")
19
+ # Attributes
20
+ @resource_class.columns_hash.each do |key, metadata|
21
+ th(scope: "col", class: "text-left text-xs text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition relative") do
22
+ a(
23
+ href:
24
+ (
25
+ helpers.resources_path(
26
+ resource: @params[:resource],
27
+ sort_attribute: key,
28
+ sort_direction:
29
+ @params[:sort_direction] == "asc" ? :desc : :asc
30
+ )
31
+ ),
32
+ class: "px-4 py-3 flex items-center justify-between gap-2 w-full h-full hover:text-gray-900 transition-colors"
33
+ ) do
34
+ render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
35
+ render Phlex::Icons::Iconoir::ArrowSeparateVertical.new(class: "size-3 text-gray-500", stroke_width: 2.5)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Associations
41
+ resource_associations.each do |association|
42
+ th(scope: "col", class: "text-left text-xs text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition relative") do
43
+ span(
44
+ class: "px-4 py-3 flex items-center justify-between gap-2 w-full h-full hover:text-gray-900 transition-colors"
45
+ ) do
46
+ render Oversee::Field::Label.new(key: association.name, datatype: nil)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ table.body do |body|
54
+ @resources.each do |resource|
55
+ body.row do |row|
56
+ td do
57
+ div(class: "flex space-x-2 mx-4") do
58
+ a(
59
+ href:
60
+ (
61
+ helpers.resource_path(
62
+ resource.id,
63
+ resource_class_name: @params[:resource_class_name]
64
+ )
65
+ ),
66
+ data: { turbo_stream: true },
67
+ class: "size-6 bg-gray-100 inline-flex items-center justify-center hover:bg-gray-200 group"
68
+ ) { render Phlex::Icons::Iconoir::Eye.new(class: "size-4 text-gray-500") }
69
+ end
70
+ end
71
+
72
+ @resource_class.columns_hash.each do |key, metadata|
73
+ row.data do
74
+ div(class: "max-w-96") do
75
+ render Oversee::Field::Value.new(datatype: metadata.sql_type_metadata.type, value: resource.send(key), key: key)
76
+ end
77
+ end
78
+ end
79
+
80
+ resource_associations.each do |association|
81
+ foreign_id = resource.send(association.foreign_key)
82
+ resource_class_name = association.class_name
83
+
84
+ path = !!foreign_id ? helpers.resource_path(id: foreign_id, resource_class_name:) : helpers.resources_path(resource_class_name: resource_class_name)
85
+
86
+ row.data do
87
+ div(class: "max-w-96") do
88
+ a(
89
+ href: path,
90
+ class:"px-2 py-1 bg-gray-100 text-gray-500 text-sm flex items-center justify-between gap-2 hover:bg-gray-200 hover:text-gray-900 min-w-20") do
91
+ span { foreign_id.presence || "N/A" }
92
+ render Phlex::Icons::Iconoir::ArrowRight.new(class: "size-3")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def resource_associations
107
+ @resource_class.reflect_on_all_associations.select { |association| association.macro == :belongs_to }
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ class Oversee::Table::Body < Phlex::HTML
2
+ def view_template(&)
3
+ tbody(class: "divide-y divide-gray-200", &)
4
+ end
5
+
6
+ def row(&)
7
+ render Oversee::Table::Row.new(&)
8
+ end
9
+
10
+ def data(&)
11
+ render Oversee::Table::Data.new(&)
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class Oversee::Table::Data < Phlex::HTML
2
+ def view_template(&)
3
+ td(class: "whitespace-nowrap p-4 text-sm text-gray-500", &)
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Oversee::Table::Head < Phlex::HTML
2
+ def view_template(&)
3
+ thead(class: "bg-white", &)
4
+ end
5
+
6
+ def row(&)
7
+ render Oversee::Table::Row.new(&)
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Oversee::Table::Row < Phlex::HTML
2
+ def view_template(&)
3
+ tr(class: "divide-x divide-gray-200") { yield }
4
+ end
5
+
6
+ def data(&)
7
+ render Oversee::Table::Data.new(&)
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ class Oversee::Table < Phlex::HTML
2
+ def view_template(&)
3
+ div(class: "overflow-x-hidden") do
4
+ div(class: "-mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8") do
5
+ div(class: "inline-block min-w-full align-middle sm:px-6 lg:px-8") do
6
+ table(class: "min-w-full divide-y divide-gray-200") do
7
+ yield
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ def head(&)
15
+ render Oversee::Table::Head.new(&)
16
+ end
17
+
18
+ def body(&)
19
+ render Oversee::Table::Body.new(&)
20
+ end
21
+ end
@@ -1,5 +1,9 @@
1
1
  module Oversee
2
2
  class DashboardController < BaseController
3
+ def index
4
+ render Oversee::Dashboard::Index.new
5
+ end
6
+
3
7
  def show
4
8
  fetch_metrics
5
9
 
@@ -2,51 +2,67 @@ module Oversee
2
2
  class ResourcesController < BaseController
3
3
  include ActionView::RecordIdentifier
4
4
 
5
- before_action :set_resource_class, except: [:create, :update]
6
- before_action :set_resource, only: %i[show edit destroy]
5
+ before_action :set_resource_class
6
+ before_action :set_resource, only: %i[show edit update destroy input_field]
7
+ before_action :set_resources, only: %i[index table]
7
8
 
8
9
  def index
9
- set_sorting_rules
10
-
11
- @resources = @resource_class.order(@sort_attribute.to_sym => sort_direction)
12
- @pagy, @resources = pagy(@resources)
10
+ @pagy, @resources = pagy(@resources, limit: params[:per_page] || Oversee.configuration.per_page)
11
+
12
+ render Oversee::Resources::Index.new(
13
+ resources: @resources,
14
+ resource_class: @resource_class,
15
+ pagy: @pagy,
16
+ params: params
17
+ )
13
18
  end
14
19
 
15
20
  def new
16
21
  @resource = @resource_class.new
22
+ render Oversee::Resources::New.new(
23
+ resource: @resource,
24
+ resource_class: @resource_class,
25
+ params: params
26
+ )
17
27
  end
18
28
 
19
29
  def create
20
- @resource_class = params[:resource_class].constantize
21
30
  @resource = @resource_class.new(resource_params)
22
31
 
23
32
  respond_to do |format|
24
33
  if @resource.update(resource_params)
25
- format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
26
- format.turbo_stream
34
+ format.html { redirect_to resource_path(@resource.id, resource_class_name: @resource_class) }
35
+ format.turbo_stream { redirect_to resource_path(@resource.id, resource_class_name: @resource_class), status: :see_other }
27
36
  else
37
+ format.html { render :new }
38
+ format.turbo_stream { render turbo_stream: turbo_stream.replace(dom_id(@resource), Oversee::Resources::Form.new(resource: @resource)) }
28
39
  end
29
40
  end
30
41
  end
31
42
 
32
43
  def show
33
- resource_associations
44
+ render Oversee::Resources::Show.new(
45
+ resource: @resource,
46
+ resource_class: @resource_class,
47
+ resource_associations: resource_associations,
48
+ params: params
49
+ )
34
50
  end
35
51
 
36
52
  def edit
37
53
  end
38
54
 
39
55
  def update
40
- @resource_class = params[:resource_class].constantize
41
- set_resource
42
-
43
- @key = params[:resource][:oversee_key]
44
- @datatype = params[:resource][:oversee_datatype]
56
+ key = params[:oversee_key]
57
+ datatype = params[:oversee_datatype]
45
58
 
46
59
  respond_to do |format|
47
60
  if @resource.update(resource_params)
48
61
  format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
49
- format.turbo_stream
62
+ format.turbo_stream do
63
+ component = Oversee::Field::Display.new(resource: @resource, datatype:, key:, value: @resource.send(key))
64
+ render turbo_stream: turbo_stream.replace(dom_id(@resource, key), component)
65
+ end
50
66
  else
51
67
  end
52
68
  end
@@ -61,15 +77,30 @@ module Oversee
61
77
  def input_field
62
78
  set_resource
63
79
 
64
- @key = params[:key].to_sym
65
- @value = @resource.send(@key)
66
- @datatype = @resource.class.columns_hash[@key.to_s].type
80
+ key = params[:key].to_sym
81
+ value = @resource.send(key)
82
+ datatype = @resource.class.columns_hash[key.to_s].type
83
+
84
+ field_dom_id = dom_id(@resource, key)
85
+ field = Oversee::Field::Form.new(resource: @resource, datatype:, key:, value:)
86
+
87
+ respond_to do |format|
88
+ format.turbo_stream do
89
+ render turbo_stream: turbo_stream.replace(field_dom_id, field)
90
+ end
91
+ end
92
+ end
93
+
94
+ def table
95
+ component_id = dom_id(@resource_class, :table)
96
+ component = Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: params)
67
97
 
68
98
  respond_to do |format|
69
99
  format.turbo_stream do
70
- render turbo_stream: [
71
- turbo_stream.replace(dom_id(@resource, @key), partial: "oversee/resources/input_field", locals: { datatype: @datatype, key: @key, value: @value })
72
- ]
100
+ render turbo_stream: turbo_stream.replace(component_id, component)
101
+ end
102
+ format.html do
103
+ render component, layout: false
73
104
  end
74
105
  end
75
106
  end
@@ -77,13 +108,21 @@ module Oversee
77
108
  private
78
109
 
79
110
  def set_resource_class
80
- @resource_class = params[:resource].constantize
111
+ @resource_class = params[:resource_class_name].constantize
81
112
  end
82
113
 
83
114
  def set_resource
84
115
  @resource = @resource_class.find(params[:id])
85
116
  end
86
117
 
118
+ def set_resources
119
+ set_sorting_rules
120
+
121
+ @resources = @resource_class.order(@sort_attribute.to_sym => sort_direction)
122
+ @resources = Filter.new(collection: @resources, params:).apply
123
+ @resources = Search.new(collection: @resources, resource_class: @resource_class, query: params[:query]).call
124
+ end
125
+
87
126
  def resource_associations
88
127
  @resource_associations ||= @resource_class.reflect_on_all_associations
89
128
  end