databasium 0.1.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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +109 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/builds/application.js +9045 -0
  7. data/app/assets/builds/application.js.map +7 -0
  8. data/app/assets/builds/databasium.css +2 -0
  9. data/app/assets/config/databasium_manifest.js +1 -0
  10. data/app/assets/javascript/databasium/application.js +2 -0
  11. data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
  12. data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
  13. data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
  14. data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
  15. data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
  16. data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
  17. data/app/assets/javascript/databasium/controllers/index.js +7 -0
  18. data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
  19. data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
  20. data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
  21. data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
  22. data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
  23. data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
  24. data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
  25. data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
  26. data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
  27. data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
  28. data/app/assets/stylesheets/databasium/application.css +15 -0
  29. data/app/assets/stylesheets/databasium/colors.css +55 -0
  30. data/app/assets/stylesheets/databasium/custom.css +36 -0
  31. data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
  32. data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
  33. data/app/components/base.rb +50 -0
  34. data/app/components/databasium/collapsable.rb +62 -0
  35. data/app/components/databasium/forms/model.rb +147 -0
  36. data/app/components/databasium/forms/search.rb +31 -0
  37. data/app/components/databasium/global/error.rb +60 -0
  38. data/app/components/databasium/global/flash.rb +73 -0
  39. data/app/components/databasium/global/header_actions.rb +36 -0
  40. data/app/components/databasium/global/sidebar.rb +45 -0
  41. data/app/components/databasium/global/suggestion.rb +25 -0
  42. data/app/components/databasium/migrations/action.rb +39 -0
  43. data/app/components/databasium/migrations/file.rb +58 -0
  44. data/app/components/databasium/migrations/form.rb +222 -0
  45. data/app/components/databasium/migrations/header_actions.rb +87 -0
  46. data/app/components/databasium/migrations/migration_status.rb +22 -0
  47. data/app/components/databasium/migrations/preview.rb +29 -0
  48. data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
  49. data/app/components/databasium/migrations/sidebar.rb +28 -0
  50. data/app/components/databasium/models/attributes.rb +49 -0
  51. data/app/components/databasium/models/form.rb +100 -0
  52. data/app/components/databasium/models/header_actions.rb +51 -0
  53. data/app/components/databasium/models/model_preview.rb +31 -0
  54. data/app/components/databasium/models/sidebar.rb +25 -0
  55. data/app/components/databasium/models/templates/attribute.rb +99 -0
  56. data/app/components/databasium/models/templates/base.rb +6 -0
  57. data/app/components/databasium/models/templates/relation.rb +56 -0
  58. data/app/components/databasium/models/templates/validation.rb +285 -0
  59. data/app/components/databasium/navigation/base_icon.rb +32 -0
  60. data/app/components/databasium/navigation/frontend_icon.rb +17 -0
  61. data/app/components/databasium/navigation/get_icon.rb +26 -0
  62. data/app/components/databasium/navigation/icon.rb +28 -0
  63. data/app/components/databasium/navigation/icon_panel.rb +26 -0
  64. data/app/components/databasium/navigation/post_icon.rb +25 -0
  65. data/app/components/databasium/navigation/put_icon.rb +18 -0
  66. data/app/components/databasium/records/filter.rb +73 -0
  67. data/app/components/databasium/records/foreign_records.rb +84 -0
  68. data/app/components/databasium/records/header_actions.rb +110 -0
  69. data/app/components/databasium/records/show_turbo_stream.rb +75 -0
  70. data/app/components/databasium/records/sidebar.rb +23 -0
  71. data/app/components/databasium/records/table/record_panel.rb +60 -0
  72. data/app/components/databasium/records/table/row.rb +104 -0
  73. data/app/components/databasium/records/table.rb +125 -0
  74. data/app/components/databasium/records/table_turbo_frame.rb +37 -0
  75. data/app/components/databasium/records/utilities.rb +25 -0
  76. data/app/components/databasium/schemas/header_actions.rb +99 -0
  77. data/app/components/databasium/schemas/sidebar.rb +25 -0
  78. data/app/components/databasium/search_results/migrations.rb +37 -0
  79. data/app/components/databasium/search_results/models.rb +36 -0
  80. data/app/components/databasium/search_results/schema_models.rb +37 -0
  81. data/app/components/databasium/search_results/tables.rb +31 -0
  82. data/app/components/databasium/type_select.rb +35 -0
  83. data/app/controllers/databasium/application_controller.rb +68 -0
  84. data/app/controllers/databasium/homepage_controller.rb +5 -0
  85. data/app/controllers/databasium/migrations_controller.rb +186 -0
  86. data/app/controllers/databasium/models_controller.rb +105 -0
  87. data/app/controllers/databasium/records_controller.rb +156 -0
  88. data/app/controllers/databasium/schemas_controller.rb +52 -0
  89. data/app/helpers/databasium/application_helper.rb +4 -0
  90. data/app/helpers/databasium/heroicon_helper.rb +21 -0
  91. data/app/helpers/databasium/models_helper.rb +4 -0
  92. data/app/jobs/databasium/application_job.rb +4 -0
  93. data/app/mailers/databasium/application_mailer.rb +6 -0
  94. data/app/models/databasium/application_record.rb +5 -0
  95. data/app/models/model.json +0 -0
  96. data/app/services/databasium/migration.rb +176 -0
  97. data/app/services/databasium/model.rb +182 -0
  98. data/app/services/databasium/record.rb +65 -0
  99. data/app/services/databasium/schema.rb +146 -0
  100. data/app/views/base.rb +13 -0
  101. data/app/views/databasium/errors/non_development.rb +21 -0
  102. data/app/views/databasium/homepage/index.rb +29 -0
  103. data/app/views/databasium/migrations/index.rb +33 -0
  104. data/app/views/databasium/migrations/new.rb +29 -0
  105. data/app/views/databasium/models/index.rb +31 -0
  106. data/app/views/databasium/models/new.rb +37 -0
  107. data/app/views/databasium/records/index.rb +24 -0
  108. data/app/views/databasium/schemas/index.rb +39 -0
  109. data/app/views/layouts/databasium/application.rb +56 -0
  110. data/config/importmap.rb +12 -0
  111. data/config/initializers/heroicon.rb +12 -0
  112. data/config/initializers/pagy.rb +48 -0
  113. data/config/initializers/phlex.rb +19 -0
  114. data/config/routes.rb +31 -0
  115. data/config/tailwind.config.js +10 -0
  116. data/lib/databasium/engine.rb +57 -0
  117. data/lib/databasium/engine_mount.rb +37 -0
  118. data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
  119. data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
  120. data/lib/databasium/templates/migration.rb.tt +48 -0
  121. data/lib/databasium/templates/model.rb.tt +23 -0
  122. data/lib/databasium/version.rb +3 -0
  123. data/lib/databasium.rb +11 -0
  124. data/lib/tasks/databasium_tasks.rake +4 -0
  125. metadata +272 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Records::Filter < Components::Base
6
+ include Phlex::Rails::Helpers::FormWith
7
+ include Phlex::Rails::Helpers::HiddenFieldTag
8
+ include Phlex::Rails::Helpers::LinkTo
9
+ def initialize(model:, turbo_frame:, columns_names_types:, hidden: true)
10
+ @model = model
11
+ @turbo_frame = turbo_frame
12
+ @columns_names_types = columns_names_types
13
+ @hidden = hidden
14
+ end
15
+
16
+ def view_template
17
+ if @model
18
+ div(
19
+ class: class_names("hidden" => @hidden),
20
+ id: "filter",
21
+ data: {
22
+ toggle_target: "filter",
23
+ controller: "filter",
24
+ filter_columns_value: @columns_names_types.to_json
25
+ }
26
+ ) { render_filter }
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def render_filter
33
+ form_with(
34
+ url: databasium.records_records_path(table: @model.name, frame_id: @turbo_frame),
35
+ method: :get,
36
+ class:
37
+ "max-h-50 overflow-y-auto border-b-1 rounded-xl border-border p-2 flex flex-col gap-2",
38
+ data: {
39
+ action: "change->search#update",
40
+ filter_target: "form",
41
+ turbo_frame: @turbo_frame
42
+ }
43
+ ) do |form|
44
+ hidden_field_tag :table, @model.name
45
+ hidden_field_tag :frame_id, @turbo_frame
46
+ div(class: "w-full flex gap-4 items-center col-span-2") do
47
+ button(
48
+ data: {
49
+ action: "click->filter#addFilter"
50
+ },
51
+ class: "ps-4 py-1 rounded underline w-fit"
52
+ ) { "Add Filter" }
53
+ div(class: "flex items-center gap-5") do
54
+ form.submit(
55
+ "Run Filters",
56
+ class: "bg-accent px-4 py-2 rounded-md",
57
+ data: {
58
+ turbo_stream: true
59
+ }
60
+ )
61
+ end
62
+ end
63
+ heroicon "x-mark",
64
+ variant: :solid,
65
+ options: {
66
+ class: "w-8 h-8 hidden mr-2",
67
+ data_filter_target: "removeIcon"
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Records::ForeignRecords < Components::Base
6
+ include Phlex::Rails::Helpers::TurboFrameTag
7
+
8
+ def initialize(model:, columns_names_types:, frame_id: nil)
9
+ @model = model
10
+ @columns_names_types = columns_names_types
11
+ @frame_id = frame_id || "foreign_records_#{@model&.name}"
12
+ @table_id = @frame_id.sub(/\Aforeign_records_/, "foreign_records_table_")
13
+ end
14
+
15
+ def view_template
16
+ turbo_frame_tag(@frame_id) do
17
+ div(
18
+ class: "fixed inset-0 z-[9999] flex items-center justify-center bg-black/40 p-4",
19
+ data: {
20
+ table_select_target: "table"
21
+ }
22
+ ) do
23
+ div(
24
+ class:
25
+ "relative bg-panel p-4 rounded-xl border-1 border-border w-fit max-w-[50vw] flex flex-col h-[90dvh] overflow-auto shadow-2xl",
26
+ id: @frame_id,
27
+ data: {
28
+ action: "click->table-select#stopPropagation"
29
+ }
30
+ ) do
31
+ render_title
32
+ render_selected_record
33
+ render_filters
34
+ render_table
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def render_selected_record
43
+ div(class: "flex flex gap-2 items-center") do
44
+ h2 { "Selected Record:" }
45
+ p(class: "font-bold", data: { table_select_target: "selectedRecord" }) do
46
+ "You haven't yet selected a record"
47
+ end
48
+ end
49
+ end
50
+
51
+ def render_title
52
+ div(class: "flex justify-between items-center mb-2 border-b-1 border-border pb-2 mb-2") do
53
+ h1(class: "w-ful text-2xl font-bold") { "Records of #{@model&.name}" }
54
+ button(
55
+ class: "text-accent hover:text-accent-dark",
56
+ data: {
57
+ action: "click->table-select#toggleVisibility"
58
+ }
59
+ ) { heroicon("x-mark", variant: :solid, options: { class: "w-8 h-8" }) }
60
+ end
61
+ end
62
+
63
+ def render_filters
64
+ render Records::Filter.new(
65
+ model: @model,
66
+ turbo_frame: @table_id,
67
+ columns_names_types: @columns_names_types,
68
+ hidden: false
69
+ )
70
+ end
71
+
72
+ def render_table
73
+ turbo_frame_tag @table_id,
74
+ class: "flex-1 min-h-0 overflow-auto block",
75
+ src:
76
+ helpers.records_records_path(
77
+ table: @model&.name,
78
+ frame_id: @table_id,
79
+ lazy: true
80
+ ) { }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Records::HeaderActions < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::Routes
8
+ include Phlex::Rails::Helpers::TurboFrameTag
9
+ include Phlex::Rails::Helpers::ButtonTo
10
+ LIMITS = [ 10, 20, 50, 100 ].freeze
11
+ def initialize(filter:, table:, limit:)
12
+ @filter = filter
13
+ @table = table
14
+ @limit = limit
15
+ end
16
+
17
+ def view_template
18
+ div(id: "header_actions", class: "flex max-w-full items-center overflow-x-auto") do
19
+ limit = @limit.to_i
20
+ div(class: "flex items-center gap-2 mr-2") do
21
+ p(class: "text-sm font-bold") { "#{@table}" }
22
+ end
23
+ button(
24
+ type: "submit",
25
+ form: "delete_records_form",
26
+ data: {
27
+ turbo_stream: true
28
+ },
29
+ class: "hidden bg-accent px-4 py-1 rounded-xl text-base me-2"
30
+ ) { span(data: { table_target: "deleteButton" }) { } }
31
+
32
+ render Components::Databasium::Navigation::IconPanel.new(
33
+ icons_with_text: [
34
+ {
35
+ icon: "funnel",
36
+ text: "filter",
37
+ method: :frontend,
38
+ data_params: {
39
+ toggle: "filter",
40
+ action: "click->toggle#toggle"
41
+ }
42
+ },
43
+ {
44
+ icon: "plus-circle",
45
+ text: "add",
46
+ method: :frontend,
47
+ data_params: {
48
+ toggle: "addRecord",
49
+ action: "click->toggle#toggle"
50
+ }
51
+ },
52
+ {
53
+ icon: "pencil-square",
54
+ text: "edit",
55
+ method: :frontend,
56
+ data_params: {
57
+ toggle: "editRecord",
58
+ action: "click->toggle#toggleSticky"
59
+ }
60
+ },
61
+ {
62
+ icon: "chevron-double-up",
63
+ text: "10",
64
+ method: :get,
65
+ turbo_frame: "records_list",
66
+ path: records_path(),
67
+ active: limit == 10
68
+ },
69
+ {
70
+ icon: "chevron-double-up",
71
+ text: "20",
72
+ method: :get,
73
+ turbo_frame: "records_list",
74
+ path: records_path(limit: 20),
75
+ active: limit == 20
76
+ },
77
+ {
78
+ icon: "chevron-double-up",
79
+ text: "50",
80
+ method: :get,
81
+ turbo_frame: "records_list",
82
+ path: records_path(limit: 50),
83
+ active: limit == 50
84
+ },
85
+ {
86
+ icon: "chevron-double-up",
87
+ text: "100",
88
+ method: :get,
89
+ turbo_frame: "records_list",
90
+ path: records_path(limit: 100),
91
+ active: limit == 100
92
+ }
93
+ ]
94
+ )
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def records_path(limit: 10)
101
+ databasium.records_records_path(
102
+ table: @table,
103
+ frame_id: "records_list",
104
+ filter: @filter,
105
+ limit: limit
106
+ )
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,75 @@
1
+ class Components::Databasium::Records::ShowTurboStream < Components::Base
2
+ include Phlex::Rails::Helpers::TurboStream
3
+
4
+ def initialize(
5
+ refresh:,
6
+ filter:,
7
+ table:,
8
+ records:,
9
+ model:,
10
+ turbo_frame:,
11
+ pagy:,
12
+ feedback:,
13
+ columns_names_types:,
14
+ limit:
15
+ )
16
+ @filter = filter
17
+ @table = table
18
+ @records = records
19
+ @model = model
20
+ @turbo_frame = turbo_frame
21
+ @pagy = pagy
22
+ @feedback = feedback
23
+ @columns_names_types = columns_names_types
24
+ @limit = limit
25
+ @refresh = refresh
26
+ end
27
+
28
+ def view_template
29
+ foreign = @turbo_frame.start_with?("foreign_records_table_")
30
+ target_frame = foreign ? @turbo_frame : "records_list"
31
+
32
+ turbo_stream.replace(
33
+ target_frame,
34
+ Components::Databasium::Records::Table.new(
35
+ records: @records,
36
+ model: @model,
37
+ turbo_frame: target_frame,
38
+ pagy: @pagy,
39
+ feedback: @feedback
40
+ )
41
+ )
42
+
43
+ return if foreign
44
+
45
+ turbo_stream.update(
46
+ "header_actions",
47
+ Components::Databasium::Records::HeaderActions.new(filter: nil, table: @table, limit: @limit)
48
+ )
49
+ if @refresh
50
+ turbo_stream.replace(
51
+ "filter",
52
+ Components::Databasium::Records::Filter.new(
53
+ model: @model,
54
+ turbo_frame: @turbo_frame,
55
+ columns_names_types: @columns_names_types,
56
+ hidden: true
57
+ )
58
+ )
59
+ turbo_stream.replace(
60
+ "addRecord",
61
+ Components::Databasium::Forms::Model.new(
62
+ columns_names_types: @columns_names_types,
63
+ model: @model
64
+ )
65
+ )
66
+ turbo_stream.replace(
67
+ "records_utilities",
68
+ Components::Databasium::Records::Utilities.new(
69
+ model: @model,
70
+ columns_names_types: @columns_names_types
71
+ )
72
+ )
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ module Components
2
+ module Databasium
3
+ class Records::Sidebar < Components::Base
4
+ include Phlex::Rails::Helpers::TurboFrameTag
5
+
6
+ def initialize
7
+ end
8
+
9
+ def view_template
10
+ div(class: "flex flex-col w-full") do
11
+ render Components::Databasium::Forms::Search.new(
12
+ url: databasium.sidebar_records_path,
13
+ turbo_frame: "results",
14
+ placeholder: "Search for a table"
15
+ )
16
+ turbo_frame_tag("results", src: databasium.sidebar_records_path) do
17
+ p(class: "mt-2 animate-pulse") { "Loading tables..." }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ module Components
2
+ module Databasium
3
+ class Records::Table::RecordPanel < Components::Base
4
+ def initialize
5
+ end
6
+
7
+ def view_template
8
+ div(class: "flex flex-col min-h-0 items-end") do
9
+ div(
10
+ class:
11
+ "hidden bg-panel flex-1 min-h-0 overflow-y-auto rounded-bl-xl mb-2 min-h-[calc(100dvh-51px)] flex flex-col w-125",
12
+ data: {
13
+ table_target: "recordsPanel"
14
+ },
15
+ id: "editRecord"
16
+ ) do
17
+ button(
18
+ class:
19
+ "w-fit text-accent hover:text-accent-dark border-1 border-accent p-1 rounded-xl m-2 ms-auto",
20
+ data: {
21
+ action: "click->table#closeRecordsPanel"
22
+ }
23
+ ) { raw heroicon("x-mark", variant: :solid, options: { class: "w-6 h-6" }) }
24
+ div(
25
+ class:
26
+ "flex bg-background px-2 overflow-x-auto max-w-fill divide-x-1 divide-border gap-x-2",
27
+ data: {
28
+ table_target: "recordTabs"
29
+ }
30
+ ) do
31
+ template(data: { table_target: "recordTab" }) do
32
+ button(
33
+ type: "button",
34
+ class: "flex items-center gap-2 px-2 cursor-pointer",
35
+ data: {
36
+ action: "click->table#openTab"
37
+ }
38
+ ) do
39
+ div(data: { table_target: "recordTabTitle" }) { plain "Name" }
40
+ div(data: { action: "click->table#closeTab" }) do
41
+ raw heroicon(
42
+ "x-mark",
43
+ variant: :solid,
44
+ options: {
45
+ class: "w-3 h-3 me-auto hover:bg-panel"
46
+ }
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
52
+ div(class: "m-2", data: { table_target: "recordTabsContent" }) do
53
+ plain "Double click on a record to update to open update form"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,104 @@
1
+ class Components::Databasium::Records::Table::Row < Components::Base
2
+ include Phlex::Rails::Helpers::DOMID
3
+ include Phlex::Rails::Helpers::ClassNames
4
+
5
+ attr_reader :record
6
+
7
+ def initialize(record:, turbo_frame:, render_as_cards: false)
8
+ @record = record
9
+ @turbo_frame = turbo_frame
10
+ @render_as_cards = render_as_cards
11
+ end
12
+
13
+ def view_template
14
+ @render_as_cards ? render_card : render_row
15
+ end
16
+
17
+ private
18
+
19
+ def click_action
20
+ @turbo_frame == "records_list" ? "click->table#handleClick" : "click->table-select#selectRecord"
21
+ end
22
+
23
+ def render_card
24
+ article(
25
+ id: dom_id(record),
26
+ class:
27
+ "flex flex-col border-1 border-border rounded-md bg-panel " \
28
+ "hover:bg-background hover:cursor-pointer overflow-hidden min-w-0 w-full max-w-125",
29
+ data: {
30
+ action: click_action,
31
+ record_id: record.id
32
+ }
33
+ ) { record.class.columns.each { |column| render_card_field(column) } }
34
+ end
35
+
36
+ def render_card_field(column)
37
+ div(
38
+ class:
39
+ "grid grid-cols-2 divide-x divide-border " \
40
+ "border-b-1 border-border last:border-b-0 py-1",
41
+ data: {
42
+ attribute_name: column.name
43
+ }
44
+ ) do
45
+ div(
46
+ class: "text-base font-semibold px-2 overflow-x-auto whitespace-nowrap min-w-0 max-w-full"
47
+ ) { column.name }
48
+ div(
49
+ class: "text-base font-light px-2 overflow-x-auto whitespace-nowrap min-w-0 max-w-full"
50
+ ) { format_cell_value(record.public_send(column.name)) }
51
+ end
52
+ end
53
+
54
+ def render_row
55
+ tr(
56
+ id: dom_id(record),
57
+ class: "hover:bg-background hover:cursor-pointer",
58
+ data: {
59
+ action: click_action,
60
+ record_id: record.id
61
+ }
62
+ ) do
63
+ render_checkbox if @turbo_frame == "records_list"
64
+ record.class.columns.each { |column| render_plain_td(column) }
65
+ end
66
+ end
67
+
68
+ def render_checkbox
69
+ td(class: "text-center py-2 border-1 border-border overflow-auto") do
70
+ input(
71
+ type: "checkbox",
72
+ id: "#{record.id}",
73
+ name: "ids[]",
74
+ value: record.id,
75
+ data: {
76
+ table_target: "checkbox"
77
+ }
78
+ )
79
+ end
80
+ end
81
+
82
+ def render_plain_td(column)
83
+ td(class: cell_classes, data: { attribute_name: column.name }) do
84
+ plain format_cell_value(record.public_send(column.name))
85
+ end
86
+ end
87
+
88
+ def format_cell_value(value)
89
+ case value
90
+ when Time, DateTime, ActiveSupport::TimeWithZone
91
+ value.strftime("%Y-%m-%d %H:%M:%S")
92
+ when Date
93
+ value.strftime("%Y-%m-%d")
94
+ when File
95
+ link_to value.url, value.url, target: "_blank"
96
+ else
97
+ value.to_s
98
+ end
99
+ end
100
+
101
+ def cell_classes
102
+ "text-center max-w-15 h-15 p-2 border-1 border-border overflow-auto"
103
+ end
104
+ end
@@ -0,0 +1,125 @@
1
+ module Components
2
+ module Databasium
3
+ class Records::Table < Components::Base
4
+ include Phlex::Rails::Helpers::LinkTo
5
+ include Phlex::Rails::Helpers::HiddenFieldTag
6
+ include Phlex::Rails::Helpers::CheckBoxTag
7
+ include Phlex::Rails::Helpers::FormWith
8
+ include Phlex::Rails::Helpers::DOMID
9
+ include Phlex::Rails::Helpers::TurboFrameTag
10
+
11
+ def initialize(
12
+ records:,
13
+ model:,
14
+ turbo_frame:,
15
+ pagy: nil,
16
+ feedback: nil,
17
+ render_as_cards: false
18
+ )
19
+ @records = records
20
+ @model = model
21
+ @turbo_frame = turbo_frame
22
+ @pagy = pagy
23
+ @feedback = feedback
24
+ @render_as_cards = render_as_cards
25
+ end
26
+
27
+ def view_template
28
+ turbo_frame_tag(@turbo_frame, class: "flex min-h-0 min-w-0 flex-1 flex-col relative") do
29
+ form_with(
30
+ url: databasium.bulk_destroy_records_path,
31
+ data: {
32
+ turbo_method: :destroy,
33
+ action: "submit->table#resetDeleteButton"
34
+ },
35
+ method: :delete,
36
+ scope: :table,
37
+ id: "delete_records_form",
38
+ class: "flex min-h-0 min-w-0 flex-1 flex-col"
39
+ ) do |form|
40
+ hidden_field_tag(:table, @model.name)
41
+ div(class: "flex-1 min-h-0 min-w-0 max-h-fit overflow-auto") do
42
+ if @render_as_cards
43
+ render_card_body
44
+ else
45
+ table(class: "whitespace-nowrap bg-panel min-w-max w-full") do
46
+ render_table_head
47
+ render_table_body
48
+ end
49
+ end
50
+ end
51
+ render_pagy
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def render_table_head
59
+ thead do
60
+ tr(class: "bg-accent shadow-accent") do
61
+ if @turbo_frame == "records_list"
62
+ th(class: table_head_classes) do
63
+ button(
64
+ type: "button",
65
+ data: {
66
+ action: "click->table#toggleAllRecords",
67
+ table_target: "toggleAllRecordsButton"
68
+ },
69
+ class: "px-4 py-1 rounded-xl text-base me-2 hover:text-hover"
70
+ ) { heroicon("check-circle", variant: :solid, options: { class: "w-6 h-6" }) }
71
+ end
72
+ end
73
+ @model&.columns&.each { |column| th(class: table_head_classes) { plain column.name } }
74
+ end
75
+ end
76
+ end
77
+
78
+ def render_card_body
79
+ div(id: "records_body", class: "grid gap-4 w-full p-4 justify-center w-full") do
80
+ if @records&.any?
81
+ @records.each do |record|
82
+ render Components::Databasium::Records::Table::Row.new(
83
+ record: record,
84
+ turbo_frame: @turbo_frame,
85
+ render_as_cards: @render_as_cards
86
+ )
87
+ end
88
+ else
89
+ render Components::Databasium::Global::Suggestion.new(
90
+ suggestions: [ @feedback || "No records found for #{@model&.name} table." ]
91
+ )
92
+ end
93
+ end
94
+ end
95
+
96
+ def render_table_body
97
+ tbody(id: "records_body") do
98
+ if @records&.any?
99
+ @records.each do |record|
100
+ render Components::Databasium::Records::Table::Row.new(
101
+ record: record,
102
+ turbo_frame: @turbo_frame,
103
+ render_as_cards: @render_as_cards
104
+ )
105
+ end
106
+ else
107
+ render Components::Databasium::Global::Suggestion.new(
108
+ suggestions: [ @feedback || "No records found for #{@model&.name} table." ]
109
+ )
110
+ end
111
+ end
112
+ end
113
+
114
+ def render_pagy
115
+ if @pagy && @records&.any?
116
+ div(class: "m-4 flex justify-start") { raw @pagy.series_nav.html_safe }
117
+ end
118
+ end
119
+
120
+ def table_head_classes
121
+ "text-center w-55 max-w-55 py-2 border-1 border-border overflow-auto"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Records::TableTurboFrame < Components::Base
6
+ include Phlex::Rails::Helpers::TurboFrameTag
7
+
8
+ def initialize(model: nil, feedback: nil)
9
+ @model = model
10
+ @feedback = feedback
11
+ end
12
+
13
+ def view_template
14
+ div(class: "flex min-h-0 min-w-0 flex-1 flex-col") do
15
+ div(id: "records_utilities") { }
16
+ render_table
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def render_table
23
+ turbo_frame_tag("records_list") do
24
+ suggestion =
25
+ if @feedback
26
+ @feedback
27
+ elsif @model
28
+ "No records found for #{@model&.name} table."
29
+ else
30
+ "Please select a table to view records."
31
+ end
32
+ render Components::Databasium::Global::Suggestion.new(suggestions: [ suggestion ])
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end