dbdoc_engine 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 (198) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +331 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/builds/dbdoc_engine/application.css +5 -0
  5. data/app/assets/images/dbdoc_engine/arrowdown.svg +3 -0
  6. data/app/assets/images/dbdoc_engine/arrowhorizontal.svg +3 -0
  7. data/app/assets/images/dbdoc_engine/arrowleft.svg +3 -0
  8. data/app/assets/images/dbdoc_engine/changelog.svg +3 -0
  9. data/app/assets/images/dbdoc_engine/column_stats_dbdocs.svg +23 -0
  10. data/app/assets/images/dbdoc_engine/diagram.svg +3 -0
  11. data/app/assets/images/dbdoc_engine/double_arrow.svg +4 -0
  12. data/app/assets/images/dbdoc_engine/group_bu.svg +3 -0
  13. data/app/assets/images/dbdoc_engine/japan_circle.png +0 -0
  14. data/app/assets/images/dbdoc_engine/log_in_image.png +0 -0
  15. data/app/assets/images/dbdoc_engine/logo.svg +12 -0
  16. data/app/assets/images/dbdoc_engine/orange_changelog.svg +3 -0
  17. data/app/assets/images/dbdoc_engine/orange_fields.svg +23 -0
  18. data/app/assets/images/dbdoc_engine/orange_logo.svg +12 -0
  19. data/app/assets/images/dbdoc_engine/orange_table.svg +21 -0
  20. data/app/assets/images/dbdoc_engine/orange_updates.svg +43 -0
  21. data/app/assets/images/dbdoc_engine/orange_wiki.svg +3 -0
  22. data/app/assets/images/dbdoc_engine/search.svg +3 -0
  23. data/app/assets/images/dbdoc_engine/setting.svg +3 -0
  24. data/app/assets/images/dbdoc_engine/table_dbdocs.svg +21 -0
  25. data/app/assets/images/dbdoc_engine/uk_circle_transparent.png +0 -0
  26. data/app/assets/images/dbdoc_engine/update_stats_dbdocs.svg +43 -0
  27. data/app/assets/images/dbdoc_engine/wiki.svg +3 -0
  28. data/app/assets/stylesheets/dbdoc_engine/admin.css +176 -0
  29. data/app/assets/stylesheets/dbdoc_engine/admin_header.css +179 -0
  30. data/app/assets/stylesheets/dbdoc_engine/application.scss +1 -0
  31. data/app/assets/stylesheets/dbdoc_engine/changelog.css +173 -0
  32. data/app/assets/stylesheets/dbdoc_engine/dashboard.css +513 -0
  33. data/app/assets/stylesheets/dbdoc_engine/dbdoc_application.css +117 -0
  34. data/app/assets/stylesheets/dbdoc_engine/ecommerce.css +253 -0
  35. data/app/assets/stylesheets/dbdoc_engine/group_details.css +178 -0
  36. data/app/assets/stylesheets/dbdoc_engine/header.css +212 -0
  37. data/app/assets/stylesheets/dbdoc_engine/loading_spinner.css +127 -0
  38. data/app/assets/stylesheets/dbdoc_engine/login.css +213 -0
  39. data/app/assets/stylesheets/dbdoc_engine/schema_diagram.css +149 -0
  40. data/app/assets/stylesheets/dbdoc_engine/sidebar.css +296 -0
  41. data/app/assets/stylesheets/dbdoc_engine/table_details.css +417 -0
  42. data/app/controllers/dbdoc_engine/admin/base_controller.rb +23 -0
  43. data/app/controllers/dbdoc_engine/admin/dashboard_controller.rb +16 -0
  44. data/app/controllers/dbdoc_engine/admin/data_transfer_controller.rb +63 -0
  45. data/app/controllers/dbdoc_engine/admin/db_design_dynamic_tables_controller.rb +198 -0
  46. data/app/controllers/dbdoc_engine/admin/db_design_table_groups_controller.rb +107 -0
  47. data/app/controllers/dbdoc_engine/application_controller.rb +65 -0
  48. data/app/controllers/dbdoc_engine/concerns/internationalization.rb +57 -0
  49. data/app/controllers/dbdoc_engine/db_doc_sessions_controller.rb +33 -0
  50. data/app/controllers/dbdoc_engine/home_controller.rb +79 -0
  51. data/app/controllers/dbdoc_engine/schema_diagram_controller.rb +293 -0
  52. data/app/helper/dbdoc_engine/application_helper.rb +35 -0
  53. data/app/helpers/dbdoc_engine/application_helper.rb +4 -0
  54. data/app/helpers/dbdoc_engine/changelogs_helper.rb +27 -0
  55. data/app/helpers/dbdoc_engine/column_helper.rb +30 -0
  56. data/app/helpers/dbdoc_engine/db_design_dynamic_tables_helper.rb +15 -0
  57. data/app/helpers/dbdoc_engine/home_helper.rb +75 -0
  58. data/app/javascript/dbdoc_engine/application.js +12 -0
  59. data/app/javascript/dbdoc_engine/controllers/application.js +29 -0
  60. data/app/javascript/dbdoc_engine/controllers/auto_submit_controller.js +17 -0
  61. data/app/javascript/dbdoc_engine/controllers/chart_controller.js +58 -0
  62. data/app/javascript/dbdoc_engine/controllers/column-type_controller.js +149 -0
  63. data/app/javascript/dbdoc_engine/controllers/column_controller.js +362 -0
  64. data/app/javascript/dbdoc_engine/controllers/column_search_controller.js +42 -0
  65. data/app/javascript/dbdoc_engine/controllers/dbdoc_accordion_controller.js +42 -0
  66. data/app/javascript/dbdoc_engine/controllers/ecommerce_controller.js +73 -0
  67. data/app/javascript/dbdoc_engine/controllers/group_details_controller.js +88 -0
  68. data/app/javascript/dbdoc_engine/controllers/import_export_controller.js +200 -0
  69. data/app/javascript/dbdoc_engine/controllers/index.js +9 -0
  70. data/app/javascript/dbdoc_engine/controllers/language_controller.js +100 -0
  71. data/app/javascript/dbdoc_engine/controllers/loading_spinner_controller.js +48 -0
  72. data/app/javascript/dbdoc_engine/controllers/login_controller.js +75 -0
  73. data/app/javascript/dbdoc_engine/controllers/notification_controller.js +15 -0
  74. data/app/javascript/dbdoc_engine/controllers/schema_diagram_controller.js +1129 -0
  75. data/app/javascript/dbdoc_engine/controllers/select2_controller.js +67 -0
  76. data/app/javascript/dbdoc_engine/controllers/sidebar_controller.js +943 -0
  77. data/app/javascript/dbdoc_engine/controllers/table_details_controller.js +245 -0
  78. data/app/javascript/dbdoc_engine/controllers/table_group_validation_controller.js +148 -0
  79. data/app/javascript/dbdoc_engine/controllers/table_validation_controller.js +423 -0
  80. data/app/jobs/dbdoc_engine/application_job.rb +4 -0
  81. data/app/mailers/dbdoc_engine/application_mailer.rb +6 -0
  82. data/app/models/dbdoc_engine/application_record.rb +6 -0
  83. data/app/models/dbdoc_engine/concerns/soft_deletable.rb +30 -0
  84. data/app/models/dbdoc_engine/db_design_changelog.rb +44 -0
  85. data/app/models/dbdoc_engine/db_design_dynamic_column.rb +211 -0
  86. data/app/models/dbdoc_engine/db_design_dynamic_table.rb +124 -0
  87. data/app/models/dbdoc_engine/db_design_table_group.rb +88 -0
  88. data/app/models/dbdoc_engine/user.rb +21 -0
  89. data/app/queries/dbdoc_engine/admin_dashboard_queries.rb +71 -0
  90. data/app/queries/dbdoc_engine/db_design_changelog_queries.rb +68 -0
  91. data/app/queries/dbdoc_engine/db_design_dynamic_column_queries.rb +37 -0
  92. data/app/queries/dbdoc_engine/db_design_dynamic_table_commands.rb +106 -0
  93. data/app/queries/dbdoc_engine/db_design_dynamic_table_queries.rb +194 -0
  94. data/app/queries/dbdoc_engine/db_design_table_group_queries.rb +154 -0
  95. data/app/services/dbdoc_engine/db_design_dynamic_table_export_service.rb +38 -0
  96. data/app/services/dbdoc_engine/db_design_dynamic_table_handler_service.rb +49 -0
  97. data/app/services/dbdoc_engine/db_design_dynamic_tables_service.rb +21 -0
  98. data/app/services/dbdoc_engine/error_handler_service.rb +43 -0
  99. data/app/services/dbdoc_engine/schema_rb_import_service.rb +194 -0
  100. data/app/services/dbdoc_engine/schema_rb_parser_service.rb +339 -0
  101. data/app/services/dbdoc_engine/table_filter_service.rb +35 -0
  102. data/app/services/dbdoc_engine/table_groups_service.rb +199 -0
  103. data/app/services/dbdoc_engine/table_management_service.rb +192 -0
  104. data/app/views/dbdoc_engine/admin/dashboard/_action_badge.html.erb +11 -0
  105. data/app/views/dbdoc_engine/admin/dashboard/_changelog_rows.html.erb +22 -0
  106. data/app/views/dbdoc_engine/admin/dashboard/_changelog_table_headers.html.erb +8 -0
  107. data/app/views/dbdoc_engine/admin/dashboard/_filter_fields.html.erb +43 -0
  108. data/app/views/dbdoc_engine/admin/dashboard/index.html.erb +159 -0
  109. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_column_fields.html.erb +225 -0
  110. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_deleted_table_index.html.erb +110 -0
  111. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_foreign_key_fields.html.erb +51 -0
  112. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_form.html.erb +75 -0
  113. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_recent_activity.html.erb +39 -0
  114. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_columns.html.erb +127 -0
  115. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_index.html.erb +109 -0
  116. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_information.html.erb +99 -0
  117. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/deleted_tables.html.erb +95 -0
  118. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/edit.html.erb +23 -0
  119. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_all_to_excel.xlsx.axlsx +240 -0
  120. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_to_excel.xlsx.axlsx +135 -0
  121. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/index.html.erb +109 -0
  122. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/new.html.erb +25 -0
  123. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/show_table_info.html.erb +125 -0
  124. data/app/views/dbdoc_engine/admin/db_design_table_groups/_deleted_table_groups_list.html.erb +75 -0
  125. data/app/views/dbdoc_engine/admin/db_design_table_groups/_form.html.erb +88 -0
  126. data/app/views/dbdoc_engine/admin/db_design_table_groups/_table_groups_list.html.erb +82 -0
  127. data/app/views/dbdoc_engine/admin/db_design_table_groups/deleted_groups.html.erb +60 -0
  128. data/app/views/dbdoc_engine/admin/db_design_table_groups/edit.html.erb +25 -0
  129. data/app/views/dbdoc_engine/admin/db_design_table_groups/index.html.erb +85 -0
  130. data/app/views/dbdoc_engine/admin/db_design_table_groups/new.html.erb +26 -0
  131. data/app/views/dbdoc_engine/db_doc_sessions/new.html.erb +59 -0
  132. data/app/views/dbdoc_engine/home/changelog_details.html.erb +80 -0
  133. data/app/views/dbdoc_engine/home/changelogs.html.erb +20 -0
  134. data/app/views/dbdoc_engine/home/group_details.html.erb +94 -0
  135. data/app/views/dbdoc_engine/home/index.html.erb +11 -0
  136. data/app/views/dbdoc_engine/home/partials/_action_badge.html.erb +11 -0
  137. data/app/views/dbdoc_engine/home/partials/_breadcrumb_navigation.html.erb +30 -0
  138. data/app/views/dbdoc_engine/home/partials/_changelog_rows.html.erb +35 -0
  139. data/app/views/dbdoc_engine/home/partials/_changelog_table_headers.html.erb +16 -0
  140. data/app/views/dbdoc_engine/home/partials/_column_headers.html.erb +23 -0
  141. data/app/views/dbdoc_engine/home/partials/_column_row.html.erb +157 -0
  142. data/app/views/dbdoc_engine/home/partials/_filter_form.html.erb +47 -0
  143. data/app/views/dbdoc_engine/home/partials/_group_section.html.erb +84 -0
  144. data/app/views/dbdoc_engine/home/partials/_pagination.html.erb +5 -0
  145. data/app/views/dbdoc_engine/home/partials/_stats_container.html.erb +46 -0
  146. data/app/views/dbdoc_engine/home/partials/_table_groups.html.erb +7 -0
  147. data/app/views/dbdoc_engine/home/partials/_table_information_section.html.erb +50 -0
  148. data/app/views/dbdoc_engine/home/partials/_table_section.html.erb +48 -0
  149. data/app/views/dbdoc_engine/home/table_details.html.erb +9 -0
  150. data/app/views/dbdoc_engine/schema_diagram/index.html.erb +102 -0
  151. data/app/views/dbdoc_engine/shared/_admin_header.html.erb +78 -0
  152. data/app/views/dbdoc_engine/shared/_header.html.erb +94 -0
  153. data/app/views/dbdoc_engine/shared/_js_translations.html.erb +3 -0
  154. data/app/views/dbdoc_engine/shared/_language_button.html.erb +14 -0
  155. data/app/views/dbdoc_engine/shared/_sidebar.html.erb +128 -0
  156. data/app/views/kaminari/dbdoc_engine/_first_page.html.erb +3 -0
  157. data/app/views/kaminari/dbdoc_engine/_gap.html.erb +3 -0
  158. data/app/views/kaminari/dbdoc_engine/_last_page.html.erb +3 -0
  159. data/app/views/kaminari/dbdoc_engine/_next_page.html.erb +3 -0
  160. data/app/views/kaminari/dbdoc_engine/_page.html.erb +9 -0
  161. data/app/views/kaminari/dbdoc_engine/_paginator.html.erb +17 -0
  162. data/app/views/kaminari/dbdoc_engine/_prev_page.html.erb +3 -0
  163. data/app/views/layouts/dbdoc_engine/application.html.erb +107 -0
  164. data/app/views/layouts/dbdoc_engine/header.html.erb +108 -0
  165. data/config/importmap.rb +11 -0
  166. data/config/locales/en.yml +307 -0
  167. data/config/locales/ja.yml +306 -0
  168. data/config/routes.rb +73 -0
  169. data/db/migrate/rails7/20250227060610_create_db_design_table_groups.rb +15 -0
  170. data/db/migrate/rails7/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  171. data/db/migrate/rails7/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  172. data/db/migrate/rails7/20250401051453_create_db_design_changelogs.rb +26 -0
  173. data/db/migrate/rails7/20250411040822_create_users.rb +14 -0
  174. data/db/migrate/rails7/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  175. data/db/migrate/rails8/20250227060610_create_db_design_table_groups.rb +15 -0
  176. data/db/migrate/rails8/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  177. data/db/migrate/rails8/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  178. data/db/migrate/rails8/20250401051453_create_db_design_changelogs.rb +26 -0
  179. data/db/migrate/rails8/20250411040822_create_users.rb +14 -0
  180. data/db/migrate/rails8/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  181. data/db/seeds.rb +28 -0
  182. data/lib/dbdoc_engine/engine.rb +57 -0
  183. data/lib/dbdoc_engine/version.rb +3 -0
  184. data/lib/dbdoc_engine.rb +9 -0
  185. data/lib/generators/dbdoc_engine/install/install_generator.rb +245 -0
  186. data/lib/generators/dbdoc_engine/uninstall/uninstall_generator.rb +196 -0
  187. data/lib/tasks/dbdoc_engine_tasks.rake +44 -0
  188. data/public/dbdoc_engine_assets/images/camel_chess_head.png +0 -0
  189. data/public/dbdoc_engine_assets/images/dblogo.svg +4 -0
  190. data/public/dbdoc_engine_assets/images/japan_circle.png +0 -0
  191. data/public/dbdoc_engine_assets/images/king_chess_head.png +0 -0
  192. data/public/dbdoc_engine_assets/images/login-bg.svg +44 -0
  193. data/public/dbdoc_engine_assets/images/logo.png +0 -0
  194. data/public/dbdoc_engine_assets/images/logo.svg +12 -0
  195. data/public/dbdoc_engine_assets/images/queen_chess_head.png +0 -0
  196. data/public/dbdoc_engine_assets/images/soldier_chess_headd.png +0 -0
  197. data/public/dbdoc_engine_assets/images/uk_circle_transparent.png +0 -0
  198. metadata +415 -0
@@ -0,0 +1,943 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ /**
4
+ * Sidebar Controller
5
+ * Manages the sidebar functionality including resizing, collapsing,
6
+ * searching, and navigation between groups and tables.
7
+ */
8
+ export default class extends Controller {
9
+ // Define controller targets
10
+ static targets = ['sidebar', 'searchInput', 'groupList', 'toggleButton', 'resizer'];
11
+
12
+ // Define controller values for image paths
13
+ static values = {
14
+ arrowDownPath: String,
15
+ arrowHorizontalPath: String
16
+ }
17
+
18
+ /**
19
+ * Connect method - runs when controller is connected to the DOM
20
+ * Initializes sidebar functionality
21
+ */
22
+ connect() {
23
+ // Get arrow image paths from data attributes
24
+ this.arrowDownPathValue = this.element.dataset.arrowDownPath || '/assets/arrowdown.svg';
25
+ this.arrowHorizontalPathValue = this.element.dataset.arrowHorizontalPath || '/assets/arrowhorizontal.svg';
26
+
27
+ // Initialize sidebar state
28
+ this.isExpanded = false;
29
+
30
+ // Setup sidebar components and functionality
31
+ this.setupResizableSidebar();
32
+ this.storeOriginalContent();
33
+ this.hasStoredOriginalContent = true;
34
+ this.setupItemSelection();
35
+ this.restoreSidebarState();
36
+ this.setActiveItemFromCurrentPath();
37
+ this.setupTooltips();
38
+ }
39
+
40
+ /**
41
+ * Sets up click handlers for selecting items in the sidebar
42
+ * Manages active state of items and stores selection in localStorage
43
+ */
44
+ setupItemSelection() {
45
+ // Add click handlers for all clickable items in sidebar
46
+ document.querySelectorAll('.group-row, .table-row, .column-row').forEach(row => {
47
+ row.addEventListener('click', (event) => {
48
+ // Remove active class from all items
49
+ document.querySelectorAll('.sidebar-item-active').forEach(activeItem => {
50
+ activeItem.classList.remove('sidebar-item-active');
51
+ });
52
+
53
+ // Add active class to clicked row
54
+ row.classList.add('sidebar-item-active');
55
+
56
+ // Store the active item ID in localStorage for persistence
57
+ const parentElement = row.closest('.group, .db-table, .column');
58
+ if (parentElement) {
59
+ const itemId = parentElement.dataset.groupId ||
60
+ parentElement.dataset.tableId ||
61
+ (parentElement.querySelector('.column-name') ?
62
+ parentElement.querySelector('.column-name').dataset.physicalColumnName : '');
63
+
64
+ if (itemId) {
65
+ localStorage.setItem('activeItemId', itemId);
66
+ }
67
+ }
68
+
69
+ // Store sidebar state before navigation
70
+ this.storeSidebarState();
71
+ });
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Highlights the active item based on current URL path
77
+ * This handles direct navigation to specific pages
78
+ */
79
+ setActiveItemFromCurrentPath() {
80
+ const currentPath = window.location.pathname;
81
+
82
+ // Check if we're on the root path
83
+ // Define paths where nothing should be active
84
+ const noActiveSidebarRoutes = [
85
+ '/dbdoc/',
86
+ '/dbdoc/changelogs'
87
+ ];
88
+
89
+ // Check if current path matches any "no active" route, including subpages
90
+ const isNoActiveSidebar = noActiveSidebarRoutes.some(route =>
91
+ currentPath === route || currentPath.startsWith(route + '/')
92
+ );
93
+
94
+ if (isNoActiveSidebar || currentPath === '') {
95
+ // Clear any active highlighting...
96
+ document.querySelectorAll('.sidebar-item-active').forEach(item => {
97
+ item.classList.remove('sidebar-item-active');
98
+ });
99
+ // Also clear the active item from localStorage
100
+ localStorage.removeItem('activeItemId');
101
+ return; // Exit the method early
102
+ }
103
+
104
+ // Match group details path
105
+ const groupMatch = currentPath.match(/\/group_details\/(\d+)/);
106
+ const tableMatch = currentPath.match(/\/table_details\/(\d+)/);
107
+
108
+ if (groupMatch) {
109
+ // Highlight the matching group
110
+ const groupId = groupMatch[1];
111
+ const groupItem = document.querySelector(`.group[data-group-id="${groupId}"]`);
112
+ if (groupItem && groupItem.querySelector('.group-row')) {
113
+ groupItem.querySelector('.group-row').classList.add('sidebar-item-active');
114
+ }
115
+ } else if (tableMatch) {
116
+ // Highlight the matching table
117
+ const tableId = tableMatch[1];
118
+ const tableItem = document.querySelector(`.db-table[data-table-id="${tableId}"]`);
119
+ if (tableItem && tableItem.querySelector('.table-row')) {
120
+ tableItem.querySelector('.table-row').classList.add('sidebar-item-active');
121
+ }
122
+ } else {
123
+ // Restore active item from localStorage if no match in URL
124
+ const activeItemId = localStorage.getItem('activeItemId');
125
+ if (activeItemId) {
126
+ const groupWithId = document.querySelector(`.group[data-group-id="${activeItemId}"]`);
127
+ const tableWithId = document.querySelector(`.db-table[data-table-id="${activeItemId}"]`);
128
+
129
+ if (groupWithId && groupWithId.querySelector('.group-row')) {
130
+ groupWithId.querySelector('.group-row').classList.add('sidebar-item-active');
131
+ } else if (tableWithId && tableWithId.querySelector('.table-row')) {
132
+ tableWithId.querySelector('.table-row').classList.add('sidebar-item-active');
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Stores the current state of the sidebar in localStorage
140
+ * Includes sidebar width, collapsed state, and expanded items
141
+ */
142
+ storeSidebarState() {
143
+ // Store whether sidebar is closed
144
+ localStorage.setItem('sidebarClosed', this.element.classList.contains('closed'));
145
+
146
+ // Store sidebar width
147
+ localStorage.setItem('sidebarWidth', this.element.style.width || '250px');
148
+
149
+ // Store expanded groups
150
+ const expandedGroups = [];
151
+ document.querySelectorAll('.group').forEach(group => {
152
+ const tableList = group.querySelector('.table-list');
153
+ if (tableList && tableList.classList.contains('visible')) {
154
+ const groupId = group.dataset.groupId;
155
+ if (groupId) expandedGroups.push(groupId);
156
+ }
157
+ });
158
+ localStorage.setItem('expandedGroups', JSON.stringify(expandedGroups));
159
+
160
+ // Store expanded tables
161
+ const expandedTables = [];
162
+ document.querySelectorAll('.db-table').forEach(table => {
163
+ const columnList = table.querySelector('.column-list');
164
+ if (columnList && columnList.classList.contains('visible')) {
165
+ const tableId = table.dataset.tableId;
166
+ if (tableId) expandedTables.push(tableId);
167
+ }
168
+ });
169
+ localStorage.setItem('expandedTables', JSON.stringify(expandedTables));
170
+ }
171
+
172
+ /**
173
+ * Restores the sidebar state from localStorage
174
+ * Applied when the page is loaded
175
+ */
176
+ restoreSidebarState() {
177
+ // Restore sidebar closed state
178
+ const sidebarClosed = localStorage.getItem('sidebarClosed') === 'true';
179
+ if (sidebarClosed) {
180
+ this.element.classList.add('closed');
181
+ } else {
182
+ this.element.classList.remove('closed');
183
+ }
184
+
185
+ // Restore sidebar width
186
+ const sidebarWidth = localStorage.getItem('sidebarWidth');
187
+ if (sidebarWidth) {
188
+ this.element.style.width = sidebarWidth;
189
+ }
190
+
191
+ // Restore expanded groups
192
+ try {
193
+ const expandedGroups = JSON.parse(localStorage.getItem('expandedGroups') || '[]');
194
+ expandedGroups.forEach(groupId => {
195
+ const group = document.querySelector(`.group[data-group-id="${groupId}"]`);
196
+ if (group) {
197
+ const tableList = group.querySelector('.table-list');
198
+ if (tableList) {
199
+ tableList.classList.add('visible');
200
+ tableList.classList.remove('hidden');
201
+
202
+ // Update arrow rotation
203
+ const groupToggle = group.querySelector('.group-toggle svg');
204
+ if (groupToggle) {
205
+ groupToggle.style.transform = 'rotate(90deg)';
206
+ }
207
+ }
208
+ }
209
+ });
210
+ } catch (e) {
211
+ console.error('Error restoring expanded groups:', e);
212
+ }
213
+
214
+ // Restore expanded tables
215
+ try {
216
+ const expandedTables = JSON.parse(localStorage.getItem('expandedTables') || '[]');
217
+ expandedTables.forEach(tableId => {
218
+ const table = document.querySelector(`.db-table[data-table-id="${tableId}"]`);
219
+ if (table) {
220
+ const columnList = table.querySelector('.column-list');
221
+ if (columnList) {
222
+ columnList.classList.add('visible');
223
+ columnList.classList.remove('hidden');
224
+
225
+ // Update arrow rotation
226
+ const tableToggle = table.querySelector('.table-toggle svg');
227
+ if (tableToggle) {
228
+ tableToggle.style.transform = 'rotate(90deg)';
229
+ }
230
+ }
231
+ }
232
+ });
233
+ } catch (e) {
234
+ console.error('Error restoring expanded tables:', e);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Sets up resizable sidebar functionality
240
+ * Allows user to adjust sidebar width by dragging
241
+ */
242
+ setupResizableSidebar() {
243
+ const sidebar = this.element;
244
+ const resizer = this.resizerTarget;
245
+
246
+ let startX = 0;
247
+ let startWidth = 0;
248
+
249
+ // Function that starts the resize process
250
+ const startResize = (e) => {
251
+ e.preventDefault();
252
+ startX = e.clientX;
253
+ startWidth = parseInt(document.defaultView.getComputedStyle(sidebar).width, 10);
254
+ document.addEventListener('mousemove', resize);
255
+ document.addEventListener('mouseup', stopResize);
256
+ document.body.style.cursor = 'ew-resize';
257
+ };
258
+
259
+ // Function that updates the sidebar width during resize
260
+ const resize = (e) => {
261
+ const newWidth = startWidth + (e.clientX - startX);
262
+ // Limit sidebar width within reasonable bounds
263
+ if (newWidth > 300 && newWidth < 500) {
264
+ sidebar.style.width = `${newWidth}px`;
265
+ }
266
+ };
267
+
268
+ // Function that ends the resize process
269
+ const stopResize = () => {
270
+ document.removeEventListener('mousemove', resize);
271
+ document.removeEventListener('mouseup', stopResize);
272
+ document.body.style.cursor = '';
273
+
274
+ // Store sidebar state after resizing
275
+ this.storeSidebarState();
276
+ };
277
+
278
+ // Add event listener to start resizing
279
+ resizer.addEventListener('mousedown', startResize);
280
+ }
281
+
282
+ /**
283
+ * Toggles the sidebar between expanded and collapsed states
284
+ * Maintains toggle button and resizer visibility
285
+ */
286
+ toggleSidebar() {
287
+ const sidebar = this.element;
288
+ const toggleButton = this.toggleButtonTarget;
289
+
290
+ if (sidebar.classList.contains('closed')) {
291
+ // Open the sidebar
292
+ sidebar.classList.remove('closed');
293
+ sidebar.style.width = sidebar.dataset.lastWidth || '250px';
294
+ } else {
295
+ // Store current width before closing
296
+ sidebar.dataset.lastWidth = sidebar.style.width || getComputedStyle(sidebar).width;
297
+ // Close the sidebar but keep a minimal width for the resizer and button
298
+ sidebar.classList.add('closed');
299
+ sidebar.style.width = '20px';
300
+ }
301
+
302
+ // Toggle the arrow direction
303
+ const arrowImg = toggleButton.querySelector('img');
304
+ if (arrowImg) {
305
+ arrowImg.style.transform = sidebar.classList.contains('closed') ? 'rotate(180deg)' : '';
306
+ }
307
+
308
+ // Store sidebar state
309
+ this.storeSidebarState();
310
+ }
311
+
312
+ /**
313
+ * Toggles a group's table list visibility
314
+ * Updates the arrow rotation indicator
315
+ */
316
+ toggleGroup(event) {
317
+ const group = event.currentTarget.closest('.group');
318
+ const tableList = group.querySelector('.table-list');
319
+ tableList.classList.toggle('visible');
320
+
321
+ if (tableList.classList.contains('visible')) {
322
+ tableList.classList.remove('hidden');
323
+ } else {
324
+ tableList.classList.add('hidden');
325
+ }
326
+
327
+ const svg = event.currentTarget.querySelector('svg');
328
+ // Update rotation to indicate expanded/collapsed state
329
+ svg.style.transform = tableList.classList.contains('visible') ? 'rotate(90deg)' : '';
330
+
331
+ // Store sidebar state
332
+ this.storeSidebarState();
333
+
334
+ // Prevent event from bubbling to parent handlers
335
+ event.stopPropagation();
336
+ }
337
+
338
+ /**
339
+ * Toggles a table's column list visibility
340
+ * Updates the arrow rotation indicator
341
+ */
342
+ toggleTable(event) {
343
+ const table = event.currentTarget.closest('.db-table');
344
+ const columnList = table.querySelector('.column-list');
345
+ columnList.classList.toggle('visible');
346
+
347
+ if (columnList.classList.contains('visible')) {
348
+ columnList.classList.remove('hidden');
349
+ } else {
350
+ columnList.classList.add('hidden');
351
+ }
352
+
353
+ const svg = event.currentTarget.querySelector('svg');
354
+ // Update rotation to indicate expanded/collapsed state
355
+ svg.style.transform = columnList.classList.contains('visible') ? 'rotate(90deg)' : '';
356
+
357
+ // Store sidebar state
358
+ this.storeSidebarState();
359
+
360
+ // Prevent event from bubbling to parent handlers
361
+ event.stopPropagation();
362
+ }
363
+
364
+ /**
365
+ * Toggles all groups and tables between expanded and collapsed states
366
+ */
367
+ toggleGroupList() {
368
+ // Toggle the groupList collapsed class
369
+ this.groupListTarget.classList.toggle('collapsed');
370
+
371
+ // Toggle expanded state
372
+ this.isExpanded = !this.isExpanded;
373
+
374
+ if (this.isExpanded) {
375
+ // When clicking the collapse button once, expand everything
376
+ this.expandAll();
377
+ } else {
378
+ // When clicking again, collapse tables and columns but keep groups visible
379
+ this.collapseTablesAndColumns();
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Expands all groups and tables in the sidebar
385
+ */
386
+ expandAll() {
387
+ // Expand all group table lists
388
+ document.querySelectorAll('.group').forEach(group => {
389
+ const tableList = group.querySelector('.table-list');
390
+ tableList.classList.add('visible');
391
+ tableList.classList.remove('hidden');
392
+ const groupToggleSvg = group.querySelector('.group-toggle svg');
393
+ if (groupToggleSvg) {
394
+ groupToggleSvg.style.transform = 'rotate(90deg)';
395
+ }
396
+ });
397
+
398
+ // Expand all table column lists
399
+ document.querySelectorAll('.db-table').forEach(table => {
400
+ const columnList = table.querySelector('.column-list');
401
+ columnList.classList.add('visible');
402
+ columnList.classList.remove('hidden');
403
+ const tableToggleSvg = table.querySelector('.table-toggle svg');
404
+ if (tableToggleSvg) {
405
+ tableToggleSvg.style.transform = 'rotate(90deg)';
406
+ }
407
+ });
408
+ }
409
+
410
+ /**
411
+ * Collapses all tables and columns in the sidebar
412
+ */
413
+ collapseTablesAndColumns() {
414
+ // Collapse all table column lists
415
+ document.querySelectorAll('.db-table').forEach(table => {
416
+ const columnList = table.querySelector('.column-list');
417
+ columnList.classList.remove('visible');
418
+ columnList.classList.add('hidden');
419
+ const tableToggleSvg = table.querySelector('.table-toggle svg');
420
+ if (tableToggleSvg) {
421
+ tableToggleSvg.style.transform = '';
422
+ }
423
+ });
424
+
425
+ // Collapse all group table lists
426
+ document.querySelectorAll('.group').forEach(group => {
427
+ const tableList = group.querySelector('.table-list');
428
+ tableList.classList.remove('visible');
429
+ tableList.classList.add('hidden');
430
+ const groupToggleSvg = group.querySelector('.group-toggle svg');
431
+ if (groupToggleSvg) {
432
+ groupToggleSvg.style.transform = '';
433
+ }
434
+ });
435
+ }
436
+
437
+ /**
438
+ * Stores original content of sidebar items for search reset functionality
439
+ */
440
+ storeOriginalContent() {
441
+ // Store original group content
442
+ document.querySelectorAll('.group').forEach((groupEl) => {
443
+ const groupTextEl = groupEl.querySelector('.group-heading');
444
+ if (groupTextEl) {
445
+ groupEl.dataset.originalContent = groupTextEl.innerHTML;
446
+ groupEl.dataset.searchEnglish = groupTextEl.textContent;
447
+ }
448
+ });
449
+
450
+ // Store original table content
451
+ document.querySelectorAll('.db-table').forEach((tableEl) => {
452
+ const tableNameEl = tableEl.querySelector('.table-name');
453
+ if (tableNameEl) {
454
+ tableEl.dataset.originalContent = tableNameEl.innerHTML;
455
+ tableEl.dataset.searchEnglish = tableNameEl.dataset.originalTableName || tableNameEl.textContent;
456
+ tableEl.dataset.searchJapanese = tableNameEl.dataset.physicalTableName || '';
457
+ }
458
+ });
459
+
460
+ // Store original column content
461
+ document.querySelectorAll('.column').forEach((columnEl) => {
462
+ const columnNameEl = columnEl.querySelector('.column-name');
463
+ if (columnNameEl) {
464
+ columnEl.dataset.originalContent = columnNameEl.innerHTML;
465
+ columnEl.dataset.searchEnglish = columnNameEl.dataset.originalColumnName || columnNameEl.textContent;
466
+ columnEl.dataset.searchJapanese = columnNameEl.dataset.physicalColumnName || '';
467
+ columnEl.dataset.dataType = columnNameEl.dataset.dataType || '';
468
+ }
469
+ });
470
+ }
471
+
472
+ /**
473
+ * Sidebar search functionality with highlight and filter
474
+ * @param {Event} event - The input event from the search field
475
+ */
476
+ search(event) {
477
+ let filter = event.target.value.toLowerCase().trim();
478
+ let matchesFound = false;
479
+
480
+ // Get or create the "no matches" message element
481
+ let noMatchesElement = document.getElementById('no-matches-message');
482
+ if (!noMatchesElement) {
483
+ noMatchesElement = document.createElement('div');
484
+ noMatchesElement.id = 'no-matches-message';
485
+ noMatchesElement.className = 'text-center p-3 mt-3';
486
+ noMatchesElement.style.color = 'white';
487
+ noMatchesElement.style.fontStyle = 'italic';
488
+ noMatchesElement.textContent = 'No matches found';
489
+ this.groupListTarget.appendChild(noMatchesElement);
490
+ }
491
+
492
+ // Initially hide the no matches message
493
+ noMatchesElement.style.display = 'none';
494
+
495
+ if (filter.length > 0) {
496
+ // First, expand all groups and tables to make everything visible
497
+ this.expandAll();
498
+
499
+ // Track visibility of elements
500
+ let visibleGroups = new Set();
501
+ let visibleTables = new Set();
502
+ let visibleColumns = new Set();
503
+
504
+ // Process all groups
505
+ document.querySelectorAll('.group').forEach((groupEl) => {
506
+ let groupMatch = false;
507
+ // Clear any previous highlighting in group text
508
+ const groupTextEl = groupEl.querySelector('.group-heading');
509
+ if (groupTextEl && groupEl.dataset.originalContent) {
510
+ groupTextEl.innerHTML = groupEl.dataset.originalContent;
511
+ }
512
+
513
+ // Check group name (English only since groups don't have Japanese names)
514
+ const groupNameText = groupTextEl.textContent.toLowerCase();
515
+ if (groupNameText.includes(filter)) {
516
+ groupMatch = true;
517
+ // Highlight group name
518
+ const regex = new RegExp(`(${filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
519
+ groupTextEl.innerHTML = groupTextEl.textContent.replace(regex, '<span class="highlight">$1</span>');
520
+ }
521
+
522
+ // Process tables within this group
523
+ let hasVisibleTables = false;
524
+ groupEl.querySelectorAll('.db-table').forEach((tableEl) => {
525
+ let tableMatch = false;
526
+ // Clear any previous highlighting
527
+ const tableNameEl = tableEl.querySelector('.table-name');
528
+ if (tableNameEl && tableEl.dataset.originalContent) {
529
+ tableNameEl.innerHTML = tableEl.dataset.originalContent;
530
+ }
531
+
532
+ // Get both English and Japanese table names
533
+ const tableName = tableNameEl.dataset.originalTableName || tableNameEl.textContent;
534
+ const physicalTableName = tableNameEl.dataset.physicalTableName || '';
535
+
536
+ // Convert to lowercase for case-insensitive search
537
+ const tableNameLower = tableName.toLowerCase();
538
+ const physicalTableNameLower = physicalTableName.toLowerCase();
539
+
540
+ // Check if filter matches either name
541
+ if (tableNameLower.includes(filter) || physicalTableNameLower.includes(filter)) {
542
+ tableMatch = true;
543
+ hasVisibleTables = true;
544
+
545
+ // Highlight the matching text
546
+ if (tableNameLower.includes(filter)) {
547
+ // If English name matches, highlight the specific match
548
+ const regex = new RegExp(`(${filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
549
+ tableNameEl.innerHTML = tableName.replace(regex, '<span class="highlight">$1</span>');
550
+ } else {
551
+ // If only Japanese name matches, highlight the entire English name
552
+ tableNameEl.innerHTML = `<span class="highlight">${tableName}</span>`;
553
+ }
554
+
555
+ visibleTables.add(tableEl.dataset.tableId);
556
+ }
557
+
558
+ // Process columns within this table
559
+ let hasVisibleColumns = false;
560
+ tableEl.querySelectorAll('.column').forEach((columnEl) => {
561
+ let columnMatch = false;
562
+ // Clear any previous highlighting
563
+ const columnNameEl = columnEl.querySelector('.column-name');
564
+ if (columnNameEl && columnEl.dataset.originalContent) {
565
+ columnNameEl.innerHTML = columnEl.dataset.originalContent;
566
+ }
567
+
568
+ // Get both English and Japanese column names
569
+ const columnName = columnNameEl.dataset.originalColumnName || columnNameEl.textContent;
570
+ const physicalColumnName = columnNameEl.dataset.physicalColumnName || '';
571
+
572
+ // Convert to lowercase for case-insensitive search
573
+ const columnNameLower = columnName.toLowerCase();
574
+ const physicalColumnNameLower = physicalColumnName.toLowerCase();
575
+
576
+ // Check if filter matches either name
577
+ if (columnNameLower.includes(filter) || physicalColumnNameLower.includes(filter)) {
578
+ columnMatch = true;
579
+ hasVisibleColumns = true;
580
+ tableMatch = true; // Column match makes table visible too
581
+
582
+ // Highlight the matching text
583
+ if (columnNameLower.includes(filter)) {
584
+ // If English name matches, highlight the specific match
585
+ const regex = new RegExp(`(${filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
586
+ columnNameEl.innerHTML = columnName.replace(regex, '<span class="highlight">$1</span>');
587
+ } else {
588
+ // If only Japanese name matches, highlight the entire English name
589
+ columnNameEl.innerHTML = `<span class="highlight">${columnName}</span>`;
590
+ }
591
+
592
+ visibleColumns.add(columnEl.querySelector('.column-name').dataset.physicalColumnName);
593
+ }
594
+
595
+ // Show or hide column based on match
596
+ columnEl.style.display = columnMatch ? 'block' : 'none';
597
+ });
598
+
599
+ // If any column matches, make table and its column list visible
600
+ if (hasVisibleColumns) {
601
+ tableEl.querySelector('.column-list').classList.add('visible');
602
+ tableEl.querySelector('.column-list').classList.remove('hidden');
603
+ tableEl.querySelector('.table-toggle svg').style.transform = 'rotate(90deg)';
604
+ hasVisibleTables = true;
605
+ tableMatch = true;
606
+ }
607
+
608
+ // Show or hide table based on match
609
+ tableEl.style.display = tableMatch ? 'block' : 'none';
610
+
611
+ // If table matches, make sure its parent group is visible
612
+ if (tableMatch) {
613
+ groupMatch = true;
614
+ }
615
+ });
616
+
617
+ // If any table matches, make group and its table list visible
618
+ if (hasVisibleTables) {
619
+ groupEl.querySelector('.table-list').classList.add('visible');
620
+ groupEl.querySelector('.table-list').classList.remove('hidden');
621
+ groupEl.querySelector('.group-toggle svg').style.transform = 'rotate(90deg)';
622
+ groupMatch = true;
623
+ }
624
+
625
+ // Show or hide group based on match
626
+ groupEl.style.display = groupMatch ? 'block' : 'none';
627
+
628
+ // Update matchesFound flag
629
+ if (groupMatch) {
630
+ matchesFound = true;
631
+ visibleGroups.add(groupEl.dataset.groupId);
632
+ }
633
+ });
634
+
635
+ // Show "No matches found" message if no matches were found
636
+ if (!matchesFound) {
637
+ noMatchesElement.style.display = 'block';
638
+ }
639
+ } else {
640
+ // If the search is empty, restore original content and respect the current expand/collapse state
641
+ this.resetAndRespectExpandState();
642
+ }
643
+
644
+ // Update tooltips after search is complete
645
+ this.updateTooltips();
646
+ }
647
+
648
+ /**
649
+ * Reset search highlighting and restore expand/collapse state
650
+ */
651
+ resetAndRespectExpandState() {
652
+ // Hide the "no matches" message if it exists
653
+ const noMatchesElement = document.getElementById('no-matches-message');
654
+ if (noMatchesElement) {
655
+ noMatchesElement.style.display = 'none';
656
+ }
657
+
658
+ // Reset all groups to their original content and make them visible
659
+ document.querySelectorAll('.group').forEach(groupEl => {
660
+ const groupTextEl = groupEl.querySelector('.group-heading');
661
+ if (groupTextEl && groupEl.dataset.originalContent) {
662
+ groupTextEl.innerHTML = groupEl.dataset.originalContent;
663
+ }
664
+ groupEl.style.display = 'block';
665
+ });
666
+
667
+ // Reset all tables to their original content and make them visible
668
+ document.querySelectorAll('.db-table').forEach(tableEl => {
669
+ const tableNameEl = tableEl.querySelector('.table-name');
670
+ if (tableNameEl && tableEl.dataset.originalContent) {
671
+ tableNameEl.innerHTML = tableEl.dataset.originalContent;
672
+ }
673
+ tableEl.style.display = 'block';
674
+ });
675
+
676
+ // Reset all columns to their original content and make them visible
677
+ document.querySelectorAll('.column').forEach(columnEl => {
678
+ const columnNameEl = columnEl.querySelector('.column-name');
679
+ if (columnNameEl && columnEl.dataset.originalContent) {
680
+ columnNameEl.innerHTML = columnEl.dataset.originalContent;
681
+ }
682
+ columnEl.style.display = 'block';
683
+ });
684
+
685
+ // Respect the current expand/collapse state
686
+ if (this.isExpanded) {
687
+ this.expandAll();
688
+ } else {
689
+ this.collapseTablesAndColumns();
690
+ }
691
+
692
+ // Update tooltips after resetting
693
+ this.updateTooltips();
694
+ }
695
+
696
+ /**
697
+ * Handles navigation to a group details page
698
+ * @param {Event} event - The click event
699
+ */
700
+ navigateToGroup(event) {
701
+ // Find the group row and add active class
702
+ const groupRow = event.target.closest('.group-row');
703
+ if (groupRow) {
704
+ // Remove active class from all items
705
+ document.querySelectorAll('.sidebar-item-active').forEach(activeItem => {
706
+ activeItem.classList.remove('sidebar-item-active');
707
+ });
708
+
709
+ // Add active class to this row
710
+ groupRow.classList.add('sidebar-item-active');
711
+ }
712
+
713
+ // Store sidebar state before navigation
714
+ this.storeSidebarState();
715
+
716
+ const groupItem = event.target.closest('.group');
717
+ const groupId = groupItem.dataset.groupId;
718
+ if (groupId) {
719
+ window.location.href = '/dbdoc/group_details/' + groupId;
720
+ }
721
+
722
+ // Prevent default to handle navigation manually
723
+ event.preventDefault();
724
+ }
725
+
726
+ /**
727
+ * Handles navigation to a table details page
728
+ * @param {Event} event - The click event
729
+ */
730
+ navigateToTable(event) {
731
+ // Find the table row and add active class
732
+ const tableRow = event.target.closest('.table-row');
733
+ if (tableRow) {
734
+ // Remove active class from all items
735
+ document.querySelectorAll('.sidebar-item-active').forEach(activeItem => {
736
+ activeItem.classList.remove('sidebar-item-active');
737
+ });
738
+
739
+ // Add active class to this row
740
+ tableRow.classList.add('sidebar-item-active');
741
+ }
742
+
743
+ // Store sidebar state before navigation
744
+ this.storeSidebarState();
745
+
746
+ const tableItem = event.target.closest('.db-table');
747
+ const tableId = tableItem.dataset.tableId;
748
+ if (tableId) {
749
+ window.location.href = '/dbdoc/table_details/' + tableId;
750
+ }
751
+
752
+ // Prevent default to handle navigation manually
753
+ event.preventDefault();
754
+ }
755
+
756
+ /**
757
+ * Sets up tooltip functionality for sidebar items
758
+ */
759
+ setupTooltips() {
760
+ // Create tooltip element if it doesn't exist
761
+ if (!document.getElementById('sidebar-tooltip')) {
762
+ const tooltip = document.createElement('div');
763
+ tooltip.id = 'sidebar-tooltip';
764
+ tooltip.classList.add('sidebar-tooltip');
765
+
766
+ // Create container for tooltip content
767
+ const tooltipContent = document.createElement('div');
768
+ tooltipContent.classList.add('tooltip-content');
769
+
770
+ tooltip.appendChild(tooltipContent);
771
+ document.body.appendChild(tooltip);
772
+ }
773
+
774
+ // Add event listeners to the row elements instead of just the text
775
+ const tooltipItems = document.querySelectorAll(
776
+ '.group-row, .table-row, .column-row'
777
+ );
778
+
779
+ tooltipItems.forEach(item => {
780
+ item.addEventListener('mouseover', this.showTooltip.bind(this));
781
+ item.addEventListener('mouseout', this.hideTooltip.bind(this));
782
+ item.addEventListener('mousemove', this.moveTooltip.bind(this));
783
+ });
784
+ }
785
+
786
+ /**
787
+ * Shows tooltip with relevant information
788
+ * @param {Event} event - The mouseover event
789
+ */
790
+ showTooltip(event) {
791
+ const tooltip = document.getElementById('sidebar-tooltip');
792
+ const tooltipContent = tooltip.querySelector('.tooltip-content');
793
+
794
+ // Clear previous content
795
+ tooltipContent.innerHTML = '';
796
+
797
+ // Find the closest relevant element
798
+ const groupRow = event.currentTarget.closest('.group-row');
799
+ const tableRow = event.currentTarget.closest('.table-row');
800
+ const columnRow = event.currentTarget.closest('.column-row');
801
+
802
+ if (groupRow) {
803
+ // Group tooltip
804
+ const groupElement = groupRow.closest('.group');
805
+ const groupName = groupRow.querySelector('.group-heading').textContent;
806
+ const tableCount = groupElement.querySelectorAll('.db-table').length;
807
+
808
+ tooltipContent.innerHTML = `
809
+ <div class="tooltip-line">
810
+ <span class="tooltip-label">Name:</span>
811
+ <span class="tooltip-value">${groupName}</span>
812
+ </div>
813
+ <div class="tooltip-line">
814
+ <span class="tooltip-label">Tables:</span>
815
+ <span class="tooltip-value">${tableCount}</span>
816
+ </div>
817
+ `;
818
+ } else if (tableRow) {
819
+ // Table tooltip
820
+ const tableElement = tableRow.closest('.db-table');
821
+ const tableNameElement = tableRow.querySelector('.table-name');
822
+ const originalName = tableNameElement.dataset.originalTableName || 'N/A';
823
+ const physicalName = tableNameElement.dataset.physicalTableName || 'N/A';
824
+ const columnCount = tableElement.querySelectorAll('.column').length;
825
+
826
+ tooltipContent.innerHTML = `
827
+ <div class="tooltip-line">
828
+ <span class="tooltip-label">Original:</span>
829
+ <span class="tooltip-value">${originalName}</span>
830
+ </div>
831
+ <div class="tooltip-line">
832
+ <span class="tooltip-label">Physical:</span>
833
+ <span class="tooltip-value">${physicalName}</span>
834
+ </div>
835
+ <div class="tooltip-line">
836
+ <span class="tooltip-label">Fields:</span>
837
+ <span class="tooltip-value">${columnCount}</span>
838
+ </div>
839
+ `;
840
+ } else if (columnRow) {
841
+ // Column tooltip
842
+ const columnNameElement = columnRow.querySelector('.column-name');
843
+ const originalName = columnNameElement.dataset.originalColumnName || 'N/A';
844
+ const physicalName = columnNameElement.dataset.physicalColumnName || 'N/A';
845
+ const dataType = columnNameElement.dataset.dataType || 'N/A';
846
+
847
+ tooltipContent.innerHTML = `
848
+ <div class="tooltip-line">
849
+ <span class="tooltip-label">Name:</span>
850
+ <span class="tooltip-value">${originalName}</span>
851
+ </div>
852
+ <div class="tooltip-line">
853
+ <span class="tooltip-label">Physical:</span>
854
+ <span class="tooltip-value">${physicalName}</span>
855
+ </div>
856
+ <div class="tooltip-line">
857
+ <span class="tooltip-label">Type:</span>
858
+ <span class="tooltip-value">${dataType}</span>
859
+ </div>
860
+ `;
861
+ }
862
+
863
+ tooltip.classList.add('visible');
864
+
865
+ // Position the tooltip
866
+ this.moveTooltip(event);
867
+ }
868
+
869
+ /**
870
+ * Updates tooltip position based on mouse movement
871
+ * @param {Event} event - The mousemove event
872
+ */
873
+ moveTooltip(event) {
874
+ const tooltip = document.getElementById('sidebar-tooltip');
875
+ const sidebar = document.getElementById('sidebar');
876
+
877
+ // Get the sidebar width and position
878
+ const sidebarRect = sidebar.getBoundingClientRect();
879
+ const sidebarRight = sidebarRect.right;
880
+
881
+ // Position tooltip to the right of the sidebar at the same vertical position as the mouse
882
+ let left = sidebarRight + 15; // Add some margin
883
+ let top = event.clientY;
884
+
885
+ // Ensure tooltip doesn't go off the screen
886
+ const viewportWidth = window.innerWidth;
887
+ const viewportHeight = window.innerHeight;
888
+ const tooltipRect = tooltip.getBoundingClientRect();
889
+
890
+ // Adjust horizontal position if needed
891
+ if (left + tooltipRect.width > viewportWidth) {
892
+ left = viewportWidth - tooltipRect.width - 10;
893
+ }
894
+
895
+ // Adjust vertical position to center the tooltip relative to the cursor
896
+ top = top - (tooltipRect.height / 2);
897
+
898
+ // Ensure tooltip doesn't go off the top or bottom of the screen
899
+ if (top < 10) {
900
+ top = 10;
901
+ } else if (top + tooltipRect.height > viewportHeight) {
902
+ top = viewportHeight - tooltipRect.height - 10;
903
+ }
904
+
905
+ tooltip.style.left = `${left}px`;
906
+ tooltip.style.top = `${top}px`;
907
+ }
908
+
909
+ /**
910
+ * Hides the tooltip
911
+ */
912
+ hideTooltip() {
913
+ const tooltip = document.getElementById('sidebar-tooltip');
914
+ tooltip.classList.remove('visible');
915
+ }
916
+ hideTooltipForce() {
917
+ const tooltip = document.getElementById('sidebar-tooltip');
918
+ if (tooltip) {
919
+ tooltip.classList.remove('visible');
920
+ tooltip.style.opacity = 0;
921
+ }
922
+ }
923
+
924
+ /**
925
+ * Updates tooltip listeners after DOM changes
926
+ * Call this after search or other DOM modifications
927
+ */
928
+ updateTooltips() {
929
+ // Remove existing listeners
930
+ const tooltipItems = document.querySelectorAll(
931
+ '.group-text, .table-name, .column-name'
932
+ );
933
+
934
+ tooltipItems.forEach(item => {
935
+ item.removeEventListener('mouseover', this.showTooltip);
936
+ item.removeEventListener('mouseout', this.hideTooltip);
937
+ item.removeEventListener('mousemove', this.moveTooltip);
938
+ });
939
+
940
+ // Re-setup tooltips
941
+ this.setupTooltips();
942
+ }
943
+ }