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.
- checksums.yaml +4 -4
- data/README.md +53 -14
- data/app/assets/config/oversee_manifest.js +2 -1
- data/app/components/oversee/{application_component.rb → base.rb} +1 -1
- data/app/components/oversee/card.rb +13 -0
- data/app/components/oversee/dashboard/filters.rb +58 -0
- data/app/components/oversee/dashboard/header.rb +57 -0
- data/app/components/oversee/dashboard/index.rb +36 -0
- data/app/components/oversee/dashboard/javascript.rb +9 -0
- data/app/components/oversee/dashboard/pagination.rb +23 -0
- data/app/components/oversee/dashboard/sidebar.rb +90 -0
- data/app/components/oversee/dashboard/tailwind.rb +62 -0
- data/app/components/oversee/field/display.rb +38 -0
- data/app/components/oversee/field/form.rb +30 -0
- data/app/components/oversee/field/input/belongs_to.rb +22 -0
- data/app/components/oversee/field/input/boolean.rb +13 -0
- data/app/components/oversee/field/input/datetime.rb +10 -0
- data/app/components/oversee/field/input/integer.rb +10 -0
- data/app/components/oversee/field/input/rich_text.rb +18 -0
- data/app/components/oversee/field/input/string.rb +10 -0
- data/app/components/oversee/field/input.rb +34 -0
- data/app/components/oversee/field/label.rb +25 -0
- data/app/components/oversee/field/value/belongs_to.rb +21 -0
- data/app/components/oversee/field/value/boolean.rb +50 -0
- data/app/components/oversee/field/value/datetime.rb +9 -0
- data/app/components/oversee/field/value/enum.rb +9 -0
- data/app/components/oversee/field/value/integer.rb +9 -0
- data/app/components/oversee/field/value/rich_text.rb +9 -0
- data/app/components/oversee/field/value/string.rb +20 -0
- data/app/components/oversee/field/value/text.rb +9 -0
- data/app/components/oversee/field/value.rb +40 -0
- data/app/components/oversee/layout/application.rb +46 -0
- data/app/components/oversee/resources/base.rb +4 -0
- data/app/components/oversee/resources/errors.rb +41 -0
- data/app/components/oversee/resources/form.rb +49 -0
- data/app/components/oversee/resources/index.rb +45 -0
- data/app/components/oversee/resources/new.rb +21 -0
- data/app/components/oversee/resources/show.rb +207 -0
- data/app/components/oversee/resources/table.rb +115 -0
- data/app/components/oversee/table/body.rb +13 -0
- data/app/components/oversee/table/data.rb +5 -0
- data/app/components/oversee/table/head.rb +9 -0
- data/app/components/oversee/table/row.rb +9 -0
- data/app/components/oversee/table.rb +21 -0
- data/app/controllers/oversee/dashboard_controller.rb +4 -0
- data/app/controllers/oversee/resources/fields_controller.rb +20 -36
- data/app/controllers/oversee/resources_controller.rb +65 -26
- data/app/javascript/oversee/application.js +3 -0
- data/app/javascript/oversee/controllers/index.js +8 -0
- data/app/javascript/oversee/controllers/reveal_controller.js +7 -0
- data/app/javascript/oversee/controllers/sidebar/state_controller.js +21 -0
- data/app/models/oversee/resource.rb +62 -0
- data/app/service/oversee/filter.rb +39 -0
- data/app/service/oversee/search.rb +34 -0
- data/app/views/oversee/base.rb +5 -0
- data/config/importmap.rb +14 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/oversee.en.yml +4 -0
- data/config/routes.rb +17 -11
- data/lib/generators/oversee/install_generator.rb +7 -0
- data/lib/oversee/configuration.rb +3 -0
- data/lib/oversee/engine.rb +19 -0
- data/lib/oversee/version.rb +1 -1
- data/lib/oversee.rb +26 -2
- data/lib/tasks/oversee_tasks.rake +6 -4
- metadata +95 -48
- data/app/assets/stylesheets/oversee/application.css +0 -15
- data/app/components/oversee/card_component.rb +0 -15
- data/app/components/oversee/dashboard/sidebar_component.rb +0 -127
- data/app/components/oversee/field_component.rb +0 -39
- data/app/components/oversee/field_label_component.rb +0 -155
- data/app/components/oversee/fields/display_row_component.rb +0 -58
- data/app/components/oversee/fields/input/boolean_component.rb +0 -26
- data/app/components/oversee/fields/input/datetime_component.rb +0 -27
- data/app/components/oversee/fields/input/integer_component.rb +0 -26
- data/app/components/oversee/fields/input/string_component.rb +0 -26
- data/app/components/oversee/fields/value/boolean_component.rb +0 -44
- data/app/components/oversee/fields/value/datetime_component.rb +0 -16
- data/app/components/oversee/fields/value/enum_component.rb +0 -16
- data/app/components/oversee/fields/value/integer_component.rb +0 -16
- data/app/components/oversee/fields/value/string_component.rb +0 -23
- data/app/components/oversee/fields/value/text_component.rb +0 -16
- data/app/helpers/oversee/application_helper.rb +0 -5
- data/app/jobs/oversee/application_job.rb +0 -4
- data/app/mailers/oversee/application_mailer.rb +0 -6
- data/app/oversee/cards.rb +0 -2
- data/app/oversee/resource.rb +0 -10
- data/app/views/layouts/oversee/application.html.erb +0 -26
- data/app/views/oversee/application/_javascript.html.erb +0 -17
- data/app/views/oversee/dashboard/show.html.erb +0 -18
- data/app/views/oversee/resources/_form.html.erb +0 -10
- data/app/views/oversee/resources/_input_field.html.erb +0 -20
- data/app/views/oversee/resources/edit.html.erb +0 -30
- data/app/views/oversee/resources/index.html.erb +0 -59
- data/app/views/oversee/resources/input_field.html.erb +0 -1
- data/app/views/oversee/resources/new.html.erb +0 -28
- data/app/views/oversee/resources/show.html.erb +0 -51
- data/app/views/oversee/resources/update.turbo_stream.erb +0 -3
- data/app/views/shared/_sidebar.html.erb +0 -32
@@ -0,0 +1,25 @@
|
|
1
|
+
class Oversee::Field::Label < Oversee::Base
|
2
|
+
ICON_MAP = {
|
3
|
+
string: Phlex::Icons::Iconoir::Text,
|
4
|
+
text: Phlex::Icons::Iconoir::TextSquare,
|
5
|
+
rich_text: Phlex::Icons::Iconoir::TextSquare,
|
6
|
+
integer: Phlex::Icons::Iconoir::Number0Square,
|
7
|
+
datetime: Phlex::Icons::Iconoir::Calendar,
|
8
|
+
boolean: Phlex::Icons::Iconoir::SwitchOn,
|
9
|
+
data: Phlex::Icons::Iconoir::Page,
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(datatype: :string, key: nil)
|
13
|
+
@datatype = datatype
|
14
|
+
@key = key
|
15
|
+
end
|
16
|
+
|
17
|
+
def view_template
|
18
|
+
div(id: "#{@key}_label", class:"inline-flex items-center space-x-2") do
|
19
|
+
div(class: "size-5 bg-gray-100 inline-flex items-center justify-center") do
|
20
|
+
render ICON_MAP[@datatype] ? ICON_MAP[@datatype].new(class: "size-3") : ICON_MAP[:data].new(class: "size-3")
|
21
|
+
end
|
22
|
+
label(class: "uppercase text-xs text-gray-700 font-medium block cursor-auto") { @key.to_s.humanize(keep_id_suffix: true) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Oversee::Field::Value::BelongsTo < Phlex::HTML
|
2
|
+
def initialize(key: nil, value: nil, **options)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def view_template
|
9
|
+
p(title: @value, class:"inline-flex items-center gap-1") do
|
10
|
+
if display_key?
|
11
|
+
span { @key.humanize }
|
12
|
+
span {"|"}
|
13
|
+
end
|
14
|
+
span { @value }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def display_key? = !!@options[:display_key]
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Oversee::Field::Value::Boolean < Phlex::HTML
|
2
|
+
def initialize(key: nil, value: nil, kind: :value)
|
3
|
+
@value = value
|
4
|
+
end
|
5
|
+
|
6
|
+
def view_template
|
7
|
+
@value ? check_icon : x_icon
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def check_icon
|
13
|
+
svg(
|
14
|
+
stroke_width: "2",
|
15
|
+
viewbox: "0 0 24 24",
|
16
|
+
fill: "none",
|
17
|
+
xmlns: "http://www.w3.org/2000/svg",
|
18
|
+
color: "currentColor",
|
19
|
+
class: "size-4 text-emerald-500"
|
20
|
+
) do |s|
|
21
|
+
s.path(
|
22
|
+
d: "M5 13L9 17L19 7",
|
23
|
+
stroke: "currentColor",
|
24
|
+
stroke_width: "2",
|
25
|
+
stroke_linecap: "round",
|
26
|
+
stroke_linejoin: "round"
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def x_icon
|
32
|
+
svg(
|
33
|
+
stroke_width: "1.5",
|
34
|
+
viewbox: "0 0 24 24",
|
35
|
+
fill: "none",
|
36
|
+
xmlns: "http://www.w3.org/2000/svg",
|
37
|
+
color: "currentColor",
|
38
|
+
class: "size-4 text-rose-500"
|
39
|
+
) do |s|
|
40
|
+
s.path(
|
41
|
+
d:
|
42
|
+
"M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426",
|
43
|
+
stroke: "currentColor",
|
44
|
+
stroke_width: "1.5",
|
45
|
+
stroke_linecap: "round",
|
46
|
+
stroke_linejoin: "round"
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -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,40 @@
|
|
1
|
+
class Oversee::Field::Value < Oversee::Base
|
2
|
+
MAP = {
|
3
|
+
string: Oversee::Field::Value::String,
|
4
|
+
belongs_to: Oversee::Field::Value::BelongsTo,
|
5
|
+
boolean: Oversee::Field::Value::Boolean,
|
6
|
+
datetime: Oversee::Field::Value::Datetime,
|
7
|
+
enum: Oversee::Field::Value::Enum,
|
8
|
+
integer: Oversee::Field::Value::Integer,
|
9
|
+
rich_text: Oversee::Field::Value::RichText,
|
10
|
+
text: Oversee::Field::Value::Text,
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_reader :key
|
14
|
+
attr_reader :value
|
15
|
+
attr_reader :datatype
|
16
|
+
|
17
|
+
def initialize(key: nil, value: nil, datatype: :string, **options)
|
18
|
+
@key = key
|
19
|
+
@value = value
|
20
|
+
@datatype = datatype
|
21
|
+
@options = options
|
22
|
+
|
23
|
+
@class = @options[:class]
|
24
|
+
end
|
25
|
+
|
26
|
+
def view_template
|
27
|
+
return p(class: "text-gray-400 text-xs"){ "—" } if value.nil?
|
28
|
+
render component_class.new(key:, value:, **@options)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def component_class
|
34
|
+
MAP[datatype.to_sym] || Oversee::Field::Value::String
|
35
|
+
end
|
36
|
+
|
37
|
+
def for_table?
|
38
|
+
@options[:for_table] || false
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Oversee::Layout::Application < Oversee::Base
|
4
|
+
extend Phlex::Kit
|
5
|
+
|
6
|
+
include Phlex::Rails::Helpers::CSPMetaTag
|
7
|
+
include Phlex::Rails::Helpers::CSRFMetaTags
|
8
|
+
include Phlex::Rails::Helpers::StylesheetLinkTag
|
9
|
+
include Phlex::Rails::Helpers::JavascriptImportmapTags
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
doctype
|
13
|
+
html do
|
14
|
+
# render Head
|
15
|
+
head do
|
16
|
+
csrf_meta_tags
|
17
|
+
csp_meta_tag
|
18
|
+
|
19
|
+
title { "Oversee | #{Oversee.application_name}" }
|
20
|
+
|
21
|
+
meta(name: "viewport", content: "width=device-width, initial-scale=1.0")
|
22
|
+
meta(name: "ROBOTS", content: "NOODP")
|
23
|
+
|
24
|
+
link(rel: "stylesheet", type: "text/css", href: "https://unpkg.com/trix@2.1.8/dist/trix.css")
|
25
|
+
|
26
|
+
render Oversee::Dashboard::Javascript.new
|
27
|
+
render Oversee::Dashboard::Tailwind.new
|
28
|
+
end
|
29
|
+
|
30
|
+
body(class: "min-h-screen bg-gray-100 p-4") do
|
31
|
+
# render Flash.new
|
32
|
+
|
33
|
+
div(class: "flex gap-4 w-full") do
|
34
|
+
div(class: "w-72 shrink-0") { render Oversee::Dashboard::Sidebar.new }
|
35
|
+
div(class: "w-full overflow-scroll") do
|
36
|
+
div(class: "bg-white rounded-lg overflow-clip p-4") do
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
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,45 @@
|
|
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 around_template
|
13
|
+
render Oversee::Layout::Application.new { super }
|
14
|
+
end
|
15
|
+
|
16
|
+
def view_template
|
17
|
+
render Oversee::Dashboard::Header.new(title: @resource_class.to_s.pluralize) do |h|
|
18
|
+
h.left
|
19
|
+
h.right do
|
20
|
+
a(
|
21
|
+
href: helpers.new_resource_path(@params[:resource_class_name]),
|
22
|
+
target: "_top",
|
23
|
+
class: "inline-flex items-center justify-center gap-1.5 h-8 px-4 rounded-full bg-transparent text-gray-900 hover:bg-gray-100 text-sm font-medium transition group"
|
24
|
+
) do
|
25
|
+
render Phlex::Icons::Iconoir::Plus.new(class: "size-4 text-gray-500 group-hover:text-blue-500", stroke_width: 2.5)
|
26
|
+
plain "Add new"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
hr(class: "my-4")
|
31
|
+
|
32
|
+
render Oversee::Dashboard::Filters.new(params: @params)
|
33
|
+
|
34
|
+
hr(class: "mt-4")
|
35
|
+
render Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: @params)
|
36
|
+
hr()
|
37
|
+
render Oversee::Dashboard::Pagination.new(pagy: @pagy, params: @params)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def resource_associations
|
43
|
+
@resource_class.reflect_on_all_associations.select { |association| association.macro == :belongs_to }
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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 around_template
|
11
|
+
render Oversee::Layout::Application.new { super }
|
12
|
+
end
|
13
|
+
|
14
|
+
def view_template
|
15
|
+
render Oversee::Dashboard::Header.new(title: @resource_class.to_s, subtitle: "Creating new record")
|
16
|
+
|
17
|
+
hr(class: "my-4")
|
18
|
+
|
19
|
+
render Oversee::Resources::Form.new(resource: @resource)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,207 @@
|
|
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
|
+
|
8
|
+
attr_reader :resource
|
9
|
+
attr_reader :resource_class
|
10
|
+
attr_reader :resource_associations
|
11
|
+
|
12
|
+
def initialize(resource:, resource_class:, resource_associations:, params:)
|
13
|
+
@resource = resource
|
14
|
+
@resource_class = resource_class
|
15
|
+
@resource_associations = resource_associations
|
16
|
+
@params = params
|
17
|
+
|
18
|
+
@oversee_resource = Oversee::Resource.new(resource_class: @resource_class, instance: @resource)
|
19
|
+
end
|
20
|
+
|
21
|
+
def around_template
|
22
|
+
render Oversee::Layout::Application.new { super }
|
23
|
+
end
|
24
|
+
|
25
|
+
def view_template
|
26
|
+
render Oversee::Dashboard::Header.new(title: @resource_class.to_s.pluralize) do |h|
|
27
|
+
h.left do
|
28
|
+
h.separator
|
29
|
+
div(class: "inline-flex items-center gap-1 text-sm bg-gray-100 text-gray-600 h-6 px-2") do
|
30
|
+
render Phlex::Icons::Iconoir::Hashtag.new(class: "size-2.5 text-gray-500", stroke_width: 2)
|
31
|
+
span { @resource.to_param }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
h.right do
|
35
|
+
details(class: "relative inline-block") do
|
36
|
+
summary(class: "cursor-pointer list-none") do
|
37
|
+
div(class: "inline-flex items-center justify-center size-8 hover:bg-indigo-50 transition") do
|
38
|
+
render Phlex::Icons::Iconoir::MoreHorizCircle.new(class: "size-4 text-gray-500")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
ul(
|
42
|
+
class:
|
43
|
+
"absolute p-2 mt-2 right-0 top-full min-w-40 overflow-hidden rounded-lg bg-white border border-b-2 border-gray-200/75 divide-y text-xs text-gray-500 font-medium"
|
44
|
+
) do
|
45
|
+
li(class: "w-full") do
|
46
|
+
button_to(helpers.resource_path(resource_class_name: @params[:resource_class_name]), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "p-1 hover:bg-gray-50 w-full flex items-center gap-2 transition duration-100") do
|
47
|
+
div(class: "inline-flex items-center justify-center size-6 bg-gray-100") do
|
48
|
+
render Phlex::Icons::Iconoir::Trash.new(class: "size-3 text-gray-500")
|
49
|
+
end
|
50
|
+
plain "Delete"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
hr(class: "my-4")
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
# COLUMNS
|
64
|
+
@resource_class.columns_hash.each do |key, metadata|
|
65
|
+
next if @oversee_resource.foreign_keys.include?(key.to_s)
|
66
|
+
|
67
|
+
div(class: "py-4") do
|
68
|
+
div(class: "space-y-2") do
|
69
|
+
render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
|
70
|
+
div(id: dom_id(@resource, :"#{key}_row"), class: "flex items-center gap-2 mt-4") do
|
71
|
+
render Oversee::Field::Display.new(resource:, key:, value: @resource.send(key), datatype: metadata.sql_type_metadata.type)
|
72
|
+
# div(id: dom_id(@resource, :"#{key}_actions")) do
|
73
|
+
# button(class: "bg-gray-100 hover:bg-gray-200 text-gray-400 hover:text-blue-500 size-10 aspect-square inline-flex items-center justify-center transition-colors") { render Phlex::Icons::Iconoir::Copy.new(class: "size-4") }
|
74
|
+
# end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
hr(class: "my-4")
|
81
|
+
|
82
|
+
# RICH TEXT Associations
|
83
|
+
if !!rich_text_associations.length
|
84
|
+
rich_text_associations.each do |association|
|
85
|
+
|
86
|
+
# Remove the "rich_text_" prefix from the association name
|
87
|
+
key = association[:name].to_s[10..].to_sym
|
88
|
+
|
89
|
+
div do
|
90
|
+
div(class: "space-y-4") do
|
91
|
+
div(class:"flex items-center gap-2") do
|
92
|
+
render Oversee::Field::Label.new(key: key.to_s.titleize, datatype: :rich_text)
|
93
|
+
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") }
|
94
|
+
end
|
95
|
+
|
96
|
+
div(id: dom_id(@resource, :"#{key.to_s}_row"), class: "flex items-center gap-2 mt-4") do
|
97
|
+
render Oversee::Field::Display.new(resource:, key:, value: @resource.send(key).to_plain_text[..196], datatype: :rich_text)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# BELONGS_TO Associations
|
105
|
+
if !!belongs_to_associations.length
|
106
|
+
belongs_to_associations.each do |association|
|
107
|
+
div(class: "py-6") do
|
108
|
+
div(class: "space-y-4") do
|
109
|
+
div(class:"flex items-center gap-2") do
|
110
|
+
render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
|
111
|
+
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") }
|
112
|
+
end
|
113
|
+
|
114
|
+
foreign_key = association[:foreign_key]
|
115
|
+
foreign_key_value = @resource[association[:foreign_key]]
|
116
|
+
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])
|
117
|
+
|
118
|
+
div(id: dom_id(@resource, :"#{foreign_key}_row"), class: "flex items-center gap-2 mt-4") do
|
119
|
+
render Oversee::Field::Display.new(resource:, key: foreign_key, value: foreign_key_value, datatype: :belongs_to, display_key: true)
|
120
|
+
div(id: dom_id(@resource, :"#{foreign_key}_actions")) do
|
121
|
+
a(href: path, class: "bg-gray-100 hover:bg-gray-200 text-gray-400 hover:text-blue-500 size-10 aspect-square inline-flex items-center justify-center transition-colors"){ render Phlex::Icons::Iconoir::ArrowUpRight.new(class: "size-4") }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
hr(class: "my-4")
|
130
|
+
|
131
|
+
# HAS_MANY Associations
|
132
|
+
if !!has_many_associations.length
|
133
|
+
div(class: "flex flex-col gap-8") do
|
134
|
+
has_many_associations.each do |association|
|
135
|
+
associated_resources = @resource.send(association[:name])
|
136
|
+
associated_resource_class = association[:class_name].constantize
|
137
|
+
|
138
|
+
div(class: "space-y-4") do
|
139
|
+
div(class:"flex items-center gap-2") do
|
140
|
+
render Oversee::Field::Label.new(key: association[:name].to_s.titleize, datatype: :data)
|
141
|
+
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") }
|
142
|
+
end
|
143
|
+
|
144
|
+
div(class: "bg-gray-50 p-2") do
|
145
|
+
# turbo_frame_tag(
|
146
|
+
# dom_id(associated_resource_class, :table),
|
147
|
+
# src: helpers.resources_table_path(resources_table_params(association)),
|
148
|
+
# loading: :lazy,
|
149
|
+
# data: { turbo_stream: true }
|
150
|
+
# ) do
|
151
|
+
# div(class: "h-20 flex items-center justify-center") { render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "animate-pulse size-6 text-gray-600") }
|
152
|
+
# end
|
153
|
+
|
154
|
+
if associated_resources.present?
|
155
|
+
div(class: "bg-white") do
|
156
|
+
render Oversee::Resources::Table.new(
|
157
|
+
resources: associated_resources,
|
158
|
+
resource_class: associated_resource_class,
|
159
|
+
params: @params
|
160
|
+
)
|
161
|
+
end
|
162
|
+
else
|
163
|
+
p(class: "bg-gray-50 p-2 pr-4 flex gap-2 items-center text-xs") {
|
164
|
+
render Phlex::Icons::Iconoir::DatabaseSearch.new(class: "size-3")
|
165
|
+
plain "No #{association[:name].to_s.titleize.downcase} found"
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def rich_text_associations
|
178
|
+
@oversee_resource.rich_text_associations
|
179
|
+
end
|
180
|
+
|
181
|
+
def belongs_to_associations
|
182
|
+
@oversee_resource.associations[:belongs_to]
|
183
|
+
end
|
184
|
+
|
185
|
+
def has_many_associations
|
186
|
+
@oversee_resource.associations[:has_many]
|
187
|
+
end
|
188
|
+
|
189
|
+
def has_associations?
|
190
|
+
@resource_associations.present?
|
191
|
+
end
|
192
|
+
|
193
|
+
def resources_table_params(association)
|
194
|
+
# if association[:through].nil?
|
195
|
+
# return { association[:foreign_key] => { eq: [@resource.id] } }
|
196
|
+
# else
|
197
|
+
# keys = @resource.send(association[:through]).pluck(association[:foreign_key])
|
198
|
+
# return { @resource_class.primary_key => { eq: keys } }
|
199
|
+
# end
|
200
|
+
|
201
|
+
{
|
202
|
+
resource_class_name: association[:class_name],
|
203
|
+
association_name: association[:through],
|
204
|
+
filters: { association[:foreign_key] => { eq: [@resource.id] } }
|
205
|
+
}
|
206
|
+
end
|
207
|
+
end
|