From 5ff62067bd296c90d2313a37e23e06a7d09aa39a Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 26 Sep 2025 15:48:17 +0300 Subject: [PATCH 01/29] collections --- .../db-tables-list.component.css | 395 ++++++++++++++++++ .../db-tables-list.component.html | 123 +++++- .../db-tables-list.component.ts | 344 ++++++++++++++- 3 files changed, 857 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 12f0bb39e..59f70dd1a 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -1,8 +1,18 @@ +:host { + width: 240px; + max-width: 240px; + min-width: 240px; + display: block; + overflow: hidden; +} + .sidenav-header { display: flex; align-items: center; justify-content: space-between; padding: 20px 16px 4px; + width: 100%; + box-sizing: border-box; } .connection-title { @@ -27,12 +37,31 @@ line-height: 48px; } +.tables-list { + width: 100%; + box-sizing: border-box; +} + .tables-list ::ng-deep .table-list-item .mat-list-item-content { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.table-list-item { + cursor: pointer; + transition: opacity 0.2s ease; +} + +.table-list-item[draggable="true"]:hover { + opacity: 0.8; +} + +.table-list-item[draggable="true"]:active { + opacity: 0.6; +} + + .table-list-initials { display: flex; align-items: center; @@ -91,6 +120,8 @@ margin-left: 16px; margin-bottom: -24px; width: calc(100% - 32px); + max-width: calc(240px - 32px); + box-sizing: border-box; } .search-input ::ng-deep * { @@ -109,3 +140,367 @@ padding-top: 8px; padding-bottom: 8px; } + +.add-collection-button-container { + margin: 16px; + margin-top: 16px; + margin-bottom: 4px; + width: calc(100% - 32px); + box-sizing: border-box; +} + +.add-collection-button { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-weight: 500; + text-transform: none; + letter-spacing: 0.5px; + border-color: #9e9e9e !important; + color: #616161 !important; +} + +.add-collection-button:hover { + border-color: #757575 !important; + background-color: rgba(0, 0, 0, 0.04) !important; +} + +.add-collection-button mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +.collections-section { + margin: 0 16px; + margin-bottom: 8px; + width: calc(100% - 32px); + box-sizing: border-box; +} + +.collection-item { + margin-bottom: 4px; + border: none; + border-radius: 4px; + background: transparent; + width: 100%; + box-sizing: border-box; + position: relative; + transition: background-color 0.2s ease; +} + +.collection-item.drag-over { + background-color: rgba(33, 150, 243, 0.1); + border: 2px dashed #2196f3; +} + +.collection-item.expanded::after { + content: ''; + position: absolute; + bottom: 0; + left: 12px; + right: 12px; + height: 1px; + background-color: #e0e0e0; +} + +.collection-header { + display: flex; + align-items: center; + padding: 8px 4px 8px 0; + cursor: pointer; + user-select: none; +} + +.collection-header:hover { + background: #f5f5f5; +} + +.collection-actions { + display: flex; + align-items: center; + opacity: 0; + transition: opacity 0.2s ease; +} + +.collection-header:hover .collection-actions { + opacity: 1; +} + +.edit-collection-button { + width: 20px; + height: 20px; + line-height: 20px; + border: none; + border-radius: 0; + background-color: transparent !important; + color: #9e9e9e !important; + margin-left: 4px; +} + +.edit-collection-button:hover { + border: none !important; + background-color: rgba(0, 0, 0, 0.02) !important; + color: #757575 !important; +} + +.edit-collection-button mat-icon { + font-size: 14px; + width: 14px; + height: 14px; +} + +.collection-name { + flex: 1; + font-weight: 500; + color: #424242; +} + +.collection-name-edit-container { + display: flex; + align-items: center; + flex: 1; + max-width: calc(100% - 40px); +} + +.collection-name.editing { + background: white; + border: 1px solid #2196f3; + border-radius: 2px; + padding: 2px 6px; + margin: 0 4px; + flex: 1; + max-width: 100%; +} + +.enter-icon { + font-size: 16px; + width: 16px; + height: 16px; + color: #9e9e9e; + cursor: pointer; + margin-left: 4px; +} + +.enter-icon:hover { + color: #757575; +} + +.collection-expand-icon { + transition: transform 0.2s ease; +} + +.collection-expand-icon.expanded { + transform: rotate(90deg); +} + +.collection-tables { + padding: 0 12px 8px; + border-top: none; + width: 100%; + max-width: 240px; + box-sizing: border-box; + min-height: 40px; +} + +.collection-tables-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + margin-bottom: 4px; +} + +.tables-count { + font-size: 12px; + color: #666; + font-weight: 500; +} + +.add-table-button { + width: 24px; + height: 24px; + line-height: 24px; + border: none; + border-radius: 0; + background-color: transparent !important; + color: #9e9e9e !important; +} + +.add-table-button:hover { + border: none !important; + background-color: rgba(0, 0, 0, 0.02) !important; + color: #757575 !important; +} + +.add-table-button mat-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + +.selected-tables-list { + max-height: none; + overflow-y: visible; +} + +.selected-table-item { + display: flex; + align-items: center; + padding: 2px 8px 2px 8px; + margin-bottom: 0; + background: transparent; + border-radius: 0; + font-size: 14px; + width: 100%; + box-sizing: border-box; + overflow: hidden; +} + + +.table-link { + flex: 1; + display: block; + text-decoration: none; + color: inherit; + overflow: hidden; + min-width: 0; +} + +.table-link:hover { + background-color: rgba(0, 0, 0, 0.04); + border-radius: 2px; +} + +.table-link_active .table-name { + color: var(--color-accentedPalette-500); +} + +.table-name { + flex: 1; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + display: block; + padding: 4px 8px; +} + +.remove-table-button { + width: 20px; + height: 20px; + line-height: 20px; + opacity: 0.4; + flex-shrink: 0; + border: 1px solid transparent; + border-radius: 2px; + background-color: transparent !important; + color: #9e9e9e !important; +} + +.remove-table-button:hover { + opacity: 0.7; + border-color: #e0e0e0 !important; + background-color: rgba(0, 0, 0, 0.02) !important; + color: #757575 !important; +} + +.remove-table-button mat-icon { + font-size: 14px; + width: 14px; + height: 14px; +} + +.no-tables-message { + text-align: center; + padding: 16px 8px; + color: #666; + height: 200px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.no-tables-message p { + margin: 0 0 12px 0; + font-size: 14px; +} + +.no-tables-message button { + font-size: 12px; + border-color: #e0e0e0 !important; + color: #9e9e9e !important; + background-color: transparent !important; +} + +.no-tables-message button:hover { + border-color: #bdbdbd !important; + background-color: rgba(0, 0, 0, 0.02) !important; + color: #757575 !important; +} + +/* Add Table Dialog Styles */ +.add-table-dialog { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.dialog-content { + background: white; + border-radius: 8px; + max-width: 400px; + width: 90%; + max-height: 500px; + display: flex; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid #e0e0e0; +} + +.dialog-header h3 { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.dialog-body { + padding: 16px 20px; + max-height: 300px; + overflow-y: auto; +} + +.available-table-item { + padding: 8px 0; + border-bottom: 1px solid #f0f0f0; +} + +.available-table-item:last-child { + border-bottom: none; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 16px 20px; + border-top: 1px solid #e0e0e0; +} diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 8069fd56c..52cb74fa9 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -1,3 +1,10 @@ +
+ table_view + +

{{connectionTitle}}

+
+
+ @@ -9,6 +16,92 @@ (keyup)="searchSubstring()"> Nothing found. + +
+ +
+ + +
+ +
+
+ + chevron_right + +
+ {{collection.name}} +
+
+ + +
+ +
+ + keyboard_return +
+
+
+
+
+ +
+ +
+

No tables in this collection

+ +
+
+
+
+
+
+
- + + +
+
+
+

Add Tables to {{currentCollection?.name}}

+ +
+
+
+ + {{getTableName(table)}} + +
+
+
+ + +
+
+
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 538c1e450..589ac616c 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,8 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { Component, HostListener, Input, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ContentLoaderComponent } from '../../ui-components/content-loader/content-loader.component'; import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -12,6 +14,14 @@ import { RouterModule } from '@angular/router'; import { TableProperties } from 'src/app/models/table'; import { TableStateService } from 'src/app/services/table-state.service'; +export interface Collection { + id: string; + name: string; + expanded: boolean; + editing: boolean; + tableIds: string[]; +} + @Component({ selector: 'app-db-tables-list', templateUrl: './db-tables-list.component.html', @@ -19,6 +29,8 @@ import { TableStateService } from 'src/app/services/table-state.service'; imports: [ CommonModule, FormsModule, + MatButtonModule, + MatCheckboxModule, MatIconModule, MatFormFieldModule, MatInputModule, @@ -28,25 +40,69 @@ import { TableStateService } from 'src/app/services/table-state.service'; ContentLoaderComponent ] }) -export class DbTablesListComponent { +export class DbTablesListComponent implements OnInit { @Input() connectionID: string; + @Input() connectionTitle: string; @Input() tables: TableProperties[]; @Input() selectedTable: string; @Input() collapsed: boolean; public substringToSearch: string; public foundTables: TableProperties[]; + public collections: Collection[] = []; + private editingCollectionName: string = ''; + + // Dialog state + public showAddTableDialogFlag: boolean = false; + public currentCollection: Collection | null = null; + public selectedTablesForAdd: string[] = []; + + // Drag and drop state + public draggedTable: TableProperties | null = null; + public dragOverCollection: string | null = null; + constructor( private _tableState: TableStateService, ) { } ngOnInit() { this.foundTables = this.tables; + this.loadCollections(); } searchSubstring() { - this.foundTables = this.tables - .filter(tableItem => tableItem.table.toLowerCase().includes(this.substringToSearch?.toLowerCase()) || (tableItem.display_name && tableItem.display_name.toLowerCase().includes(this.substringToSearch.toLowerCase()))); + if (!this.substringToSearch || this.substringToSearch.trim() === '') { + this.foundTables = this.tables; + return; + } + + const searchTerm = this.substringToSearch.toLowerCase(); + + // Get all tables that match the search (including those in collections) + const allTables = [...this.tables]; + + // Add tables from collections that might not be in the main tables list + this.collections.forEach(collection => { + collection.tableIds.forEach(tableId => { + // Find the table object by ID + const tableInCollection = this.tables.find(t => t.table === tableId); + if (tableInCollection && !allTables.find(t => t.table === tableId)) { + allTables.push(tableInCollection); + } + }); + }); + + // Filter all tables by search term + this.foundTables = allTables.filter(tableItem => + tableItem.table.toLowerCase().includes(searchTerm) || + (tableItem.display_name && tableItem.display_name.toLowerCase().includes(searchTerm)) || + (tableItem.normalizedTableName && tableItem.normalizedTableName.toLowerCase().includes(searchTerm)) + ); + + // Remove duplicates + this.foundTables = this.foundTables.filter((table, index, self) => + index === self.findIndex(t => t.table === table.table) + ); } getTableName(table: TableProperties) { @@ -61,4 +117,284 @@ export class DbTablesListComponent { this._tableState.clearSelection(); this._tableState.closeAIpanel(); } + + closeAllCollections() { + this.collections.forEach(collection => { + collection.expanded = false; + }); + this.saveCollections(); + } + + onAddCollection() { + const newCollection: Collection = { + id: this.generateCollectionId(), + name: `Collection ${this.collections.length + 1}`, + expanded: false, + editing: false, + tableIds: [] + }; + this.collections.push(newCollection); + this.saveCollections(); + this.startEditCollection(newCollection); + } + + toggleCollection(collectionId: string) { + const collection = this.collections.find(c => c.id === collectionId); + if (collection) { + collection.expanded = !collection.expanded; + } + } + + startEditCollection(collection: Collection) { + // Cancel any other editing + this.collections.forEach(c => c.editing = false); + collection.editing = true; + this.editingCollectionName = collection.name; + } + + finishEditCollection(collection: Collection) { + collection.editing = false; + if (collection.name.trim() === '') { + collection.name = this.editingCollectionName; + } + this.saveCollections(); + } + + @HostListener('document:click', ['$event']) + onDocumentClick(event: Event) { + // Check if click is outside any editing collection + const target = event.target as HTMLElement; + const isEditingInput = target.closest('.collection-name-edit-container'); + const isEditButton = target.closest('.edit-collection-button'); + + if (!isEditingInput && !isEditButton) { + // Find any collection that is currently being edited + const editingCollection = this.collections.find(c => c.editing); + if (editingCollection) { + this.finishEditCollection(editingCollection); + } + } + } + + cancelEditCollection(collection: Collection) { + collection.editing = false; + collection.name = this.editingCollectionName; + } + + getCollectionTables(collection: Collection): TableProperties[] { + const collectionTables = this.tables.filter(table => collection.tableIds.includes(table.table)); + + // If there's a search term, filter the collection tables too + if (this.substringToSearch && this.substringToSearch.trim() !== '') { + const searchTerm = this.substringToSearch.toLowerCase(); + return collectionTables.filter(table => + table.table.toLowerCase().includes(searchTerm) || + (table.display_name && table.display_name.toLowerCase().includes(searchTerm)) || + (table.normalizedTableName && table.normalizedTableName.toLowerCase().includes(searchTerm)) + ); + } + + return collectionTables; + } + + getAvailableTables(collection: Collection | null): TableProperties[] { + if (!collection) return []; + return this.tables.filter(table => !collection.tableIds.includes(table.table)); + } + + isTableInAnyCollection(table: TableProperties): boolean { + return this.collections.some(collection => collection.tableIds.includes(table.table)); + } + + shouldShowCollection(collection: Collection): boolean { + // If no search term, show all collections + if (!this.substringToSearch || this.substringToSearch.trim() === '') { + return true; + } + + // Show collection if it has tables matching the search + const matchingTables = this.getCollectionTables(collection); + return matchingTables.length > 0; + } + + showAddTableDialog(collection: Collection) { + this.currentCollection = collection; + this.selectedTablesForAdd = []; + this.showAddTableDialogFlag = true; + } + + closeAddTableDialog(event?: Event) { + if (event && event.target !== event.currentTarget) return; + this.showAddTableDialogFlag = false; + this.currentCollection = null; + this.selectedTablesForAdd = []; + } + + toggleTableSelection(tableId: string, event: any) { + const isChecked = event.checked; + if (isChecked) { + if (!this.selectedTablesForAdd.includes(tableId)) { + this.selectedTablesForAdd.push(tableId); + } + } else { + this.selectedTablesForAdd = this.selectedTablesForAdd.filter(id => id !== tableId); + } + } + + addSelectedTablesToCollection() { + if (!this.currentCollection) return; + + this.selectedTablesForAdd.forEach(tableId => { + if (!this.currentCollection!.tableIds.includes(tableId)) { + this.currentCollection!.tableIds.push(tableId); + } + }); + + this.saveCollections(); + this.closeAddTableDialog(); + } + + removeTableFromCollection(collection: Collection, table: TableProperties, event: Event) { + event.stopPropagation(); + collection.tableIds = collection.tableIds.filter(id => id !== table.table); + this.saveCollections(); + } + + trackByCollectionId(index: number, collection: Collection): string { + return collection.id; + } + + private generateCollectionId(): string { + return Date.now().toString(); + } + + private loadCollections() { + const key = `collections_${this.connectionID}`; + const saved = localStorage.getItem(key); + if (saved) { + try { + this.collections = JSON.parse(saved); + // Reset editing state only, keep expanded state + this.collections.forEach(c => c.editing = false); + console.log('Collections loaded:', this.collections.map(c => ({ name: c.name, expanded: c.expanded }))); + } catch (e) { + console.error('Error loading collections:', e); + this.collections = []; + } + } else { + console.log('No saved collections found for key:', key); + } + } + + private saveCollections() { + try { + const key = `collections_${this.connectionID}`; + localStorage.setItem(key, JSON.stringify(this.collections)); + console.log('Collections saved:', this.collections.map(c => ({ name: c.name, expanded: c.expanded }))); + } catch (e) { + console.error('Error saving collections:', e); + } + } + + // Drag and drop methods + onTableDragStart(event: DragEvent, table: TableProperties) { + this.draggedTable = table; + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', table.table); + } + } + + onTableDragEnd(event: DragEvent) { + this.draggedTable = null; + this.dragOverCollection = null; + } + + onCollectionDragOver(event: DragEvent, collectionId: string) { + event.preventDefault(); + if (event.dataTransfer) { + event.dataTransfer.dropEffect = 'move'; + } + this.dragOverCollection = collectionId; + } + + onCollectionDragLeave(event: DragEvent, collectionId: string) { + // Only clear if we're actually leaving the collection (not moving to a child element) + const rect = (event.currentTarget as HTMLElement).getBoundingClientRect(); + const x = event.clientX; + const y = event.clientY; + + if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { + this.dragOverCollection = null; + } + } + + onCollectionDrop(event: DragEvent, collection: Collection) { + event.preventDefault(); + + if (this.draggedTable) { + if (collection.tableIds.includes(this.draggedTable.table)) { + // Show notification that table already exists + this.showTableExistsNotification(collection.name, this.getTableName(this.draggedTable)); + } else { + collection.tableIds.push(this.draggedTable.table); + this.saveCollections(); + } + } + + this.draggedTable = null; + this.dragOverCollection = null; + } + + private showTableExistsNotification(collectionName: string, tableName: string) { + // Create a temporary notification element + const notification = document.createElement('div'); + notification.className = 'table-exists-notification'; + notification.textContent = `"${tableName}" already exists in "${collectionName}"`; + + // Style the notification + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #ff9800; + color: white; + padding: 12px 16px; + border-radius: 4px; + font-size: 14px; + z-index: 10000; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + animation: slideIn 0.3s ease; + `; + + // Add animation styles + const style = document.createElement('style'); + style.textContent = ` + @keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + @keyframes slideOut { + from { transform: translateX(0); opacity: 1; } + to { transform: translateX(100%); opacity: 0; } + } + `; + document.head.appendChild(style); + + // Add to DOM + document.body.appendChild(notification); + + // Remove after 3 seconds + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease'; + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + if (style.parentNode) { + style.parentNode.removeChild(style); + } + }, 300); + }, 3000); + } } From c96d2f2bda6bd2087323336e9f2e5cfd8b7d149d Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 26 Sep 2025 17:19:12 +0300 Subject: [PATCH 02/29] collections fixed --- .../db-tables-list.component.css | 45 +++++++------ .../db-tables-list.component.html | 63 ++++++++----------- .../db-tables-list.component.ts | 54 ++++++++++++++++ 3 files changed, 105 insertions(+), 57 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 59f70dd1a..3d9796abc 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -158,7 +158,7 @@ font-weight: 500; text-transform: none; letter-spacing: 0.5px; - border-color: #9e9e9e !important; + border-color: #e0e0e0 !important; color: #616161 !important; } @@ -212,6 +212,7 @@ padding: 8px 4px 8px 0; cursor: pointer; user-select: none; + height: 40px; } .collection-header:hover { @@ -229,27 +230,27 @@ opacity: 1; } -.edit-collection-button { - width: 20px; - height: 20px; - line-height: 20px; +.more-collection-button { + width: 16px; + height: 16px; + line-height: 16px; border: none; border-radius: 0; background-color: transparent !important; - color: #9e9e9e !important; + color: #bdbdbd !important; margin-left: 4px; } -.edit-collection-button:hover { +.more-collection-button:hover { border: none !important; background-color: rgba(0, 0, 0, 0.02) !important; - color: #757575 !important; + color: #9e9e9e !important; } -.edit-collection-button mat-icon { - font-size: 14px; - width: 14px; - height: 14px; +.more-collection-button mat-icon { + font-size: 12px; + width: 12px; + height: 12px; } .collection-name { @@ -349,7 +350,7 @@ .selected-table-item { display: flex; align-items: center; - padding: 2px 8px 2px 8px; + padding: 2px 0 2px 8px; margin-bottom: 0; background: transparent; border-radius: 0; @@ -371,7 +372,9 @@ .table-link:hover { background-color: rgba(0, 0, 0, 0.04); - border-radius: 2px; + border-radius: 0; + margin: 0 -12px; + padding: 0 12px; } .table-link_active .table-name { @@ -380,13 +383,13 @@ .table-name { flex: 1; - font-weight: 500; + font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; display: block; - padding: 4px 8px; + padding: 4px 0 4px 0; } .remove-table-button { @@ -395,16 +398,18 @@ line-height: 20px; opacity: 0.4; flex-shrink: 0; - border: 1px solid transparent; - border-radius: 2px; + border: none; + border-radius: 0; background-color: transparent !important; color: #9e9e9e !important; + margin-left: auto; + margin-right: 0; } .remove-table-button:hover { opacity: 0.7; - border-color: #e0e0e0 !important; - background-color: rgba(0, 0, 0, 0.02) !important; + border: none !important; + background-color: transparent !important; color: #757575 !important; } diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 52cb74fa9..b6f35fdcd 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -41,7 +41,7 @@

{{connectionTitle}}

+ (dblclick)="onCollectionNameDoubleClick($event, collection)"> {{collection.name}}
@@ -51,11 +51,23 @@

{{connectionTitle}}

matTooltip="Add table to collection"> add - + + + +
@@ -65,7 +77,8 @@

{{connectionTitle}}

(keyup.enter)="finishEditCollection(collection)" (keyup.escape)="cancelEditCollection(collection)" #collectionNameInput - (click)="$event.stopPropagation()"> + (click)="$event.stopPropagation()" + (focus)="collectionNameInput.select()"> keyboard_return
@@ -73,7 +86,11 @@

{{connectionTitle}}

- diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 589ac616c..7b48ef194 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -10,6 +10,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatMenuModule } from '@angular/material/menu'; import { RouterModule } from '@angular/router'; import { TableProperties } from 'src/app/models/table'; import { TableStateService } from 'src/app/services/table-state.service'; @@ -36,6 +37,7 @@ export interface Collection { MatInputModule, MatListModule, MatTooltipModule, + MatMenuModule, RouterModule, ContentLoaderComponent ] @@ -146,10 +148,21 @@ export class DbTablesListComponent implements OnInit { } startEditCollection(collection: Collection) { + console.log('startEditCollection called for:', collection.name); // Cancel any other editing this.collections.forEach(c => c.editing = false); collection.editing = true; this.editingCollectionName = collection.name; + console.log('Collection editing state:', collection.editing); + + // Focus and select the input after the view updates + setTimeout(() => { + const input = document.querySelector('.collection-name.editing') as HTMLInputElement; + if (input) { + input.focus(); + input.select(); + } + }, 0); } finishEditCollection(collection: Collection) { @@ -160,6 +173,33 @@ export class DbTablesListComponent implements OnInit { this.saveCollections(); } + deleteCollection(collection: Collection) { + if (confirm(`Are you sure you want to delete the collection "${collection.name}"?`)) { + const index = this.collections.findIndex(c => c.id === collection.id); + if (index > -1) { + this.collections.splice(index, 1); + this.saveCollections(); + } + } + } + + onCollectionNameDoubleClick(event: Event, collection: Collection) { + if (collection.name !== 'All Tables') { + event.stopPropagation(); + this.startEditCollection(collection); + } + } + + onMenuClosed() { + // Menu closed, no action needed + } + + closeMenu(menu: any) { + if (menu && menu.close) { + menu.close(); + } + } + @HostListener('document:click', ['$event']) onDocumentClick(event: Event) { // Check if click is outside any editing collection @@ -284,6 +324,20 @@ export class DbTablesListComponent implements OnInit { } else { console.log('No saved collections found for key:', key); } + + // Create "All Tables" collection if it doesn't exist + const allTablesCollection = this.collections.find(c => c.name === 'All Tables'); + if (!allTablesCollection) { + const allTablesCollection: Collection = { + id: this.generateCollectionId(), + name: 'All Tables', + expanded: true, + editing: false, + tableIds: this.tables.map(table => table.table) + }; + this.collections.unshift(allTablesCollection); // Add to beginning + this.saveCollections(); + } } private saveCollections() { From ce23e38f46867b977051e9604968bbf1610aa46b Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 26 Sep 2025 17:28:23 +0300 Subject: [PATCH 03/29] =?UTF-8?q?fix:=20close=20open=20menus=20when=20star?= =?UTF-8?q?ting=20to=20edit=20a=20collection=20in=20the=20dashboard=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5:=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BF=D1=80=D0=B8=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list/db-tables-list.component.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 7b48ef194..9cae2f2dc 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -187,6 +187,17 @@ export class DbTablesListComponent implements OnInit { if (collection.name !== 'All Tables') { event.stopPropagation(); this.startEditCollection(collection); + + // Close any open menus after starting edit + setTimeout(() => { + const menus = document.querySelectorAll('.mat-mdc-menu-content'); + menus.forEach(menu => { + const menuElement = menu as HTMLElement; + if (menuElement.style.display !== 'none') { + menuElement.style.display = 'none'; + } + }); + }, 0); } } From 3991fa18f36ed9cd544c7acf6e0831e62366c0aa Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 15:45:26 +0300 Subject: [PATCH 04/29] =?UTF-8?q?=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BC=D0=B5=D0=BD=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/dashboard.component.css | 16 ++ .../db-tables-list.component.css | 141 +++++++++++++++++- .../db-tables-list.component.html | 33 +++- .../db-tables-list.component.ts | 68 +++++++++ 4 files changed, 255 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.css b/frontend/src/app/components/dashboard/dashboard.component.css index 066b922fe..76e14d31c 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.css +++ b/frontend/src/app/components/dashboard/dashboard.component.css @@ -58,6 +58,22 @@ width: calc(100% - 32px); } +.toggle-button-position button { + border-radius: 4px; +} + +.toggle-button-position button mat-icon { + border-radius: 4px; +} + +.toggle-button-position button:hover { + border-radius: 4px; +} + +.toggle-button-position button:hover mat-icon { + border-radius: 4px; +} + .dashboard { display: flex; flex-direction: column; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 3d9796abc..8e5a07039 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -3,7 +3,8 @@ max-width: 240px; min-width: 240px; display: block; - overflow: hidden; + overflow: visible; + position: relative; } .sidenav-header { @@ -15,6 +16,141 @@ box-sizing: border-box; } +.collapsed-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.collapsed-collections { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.collapsed-collection-item { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 4px; + background-color: var(--color-primaryPalette-50); + color: var(--color-primaryPalette-50-contrast); + cursor: pointer; + transition: background-color 0.2s ease; +} + +.collapsed-collection-item:hover { + background-color: var(--color-primaryPalette-100); +} + +.collapsed-collection-icon { + font-size: 20px; + height: 20px; + width: 20px; +} + +.collapsed-collection-icon.fallback { + font-size: 20px; + height: 20px; + width: 20px; +} + +/* Collapsed table list styles */ +.collapsed-tables-list { + position: relative; + background: white; + border: 1px solid #e0e0e0; + border-top: none; + max-height: 300px; + overflow-y: auto; + z-index: 1000; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px; + margin-top: 8px; + border-radius: 4px; + width: 48px; + min-width: 48px; +} + +.collapsed-table-item { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + text-decoration: none; + color: inherit; + cursor: pointer; + transition: all 0.2s ease; + border-radius: 6px; + flex-shrink: 0; + position: relative; +} + +.collapsed-table-item:hover { + transform: translateY(-1px); +} + +.collapsed-table-item:hover .collapsed-table-initials { + background: linear-gradient(135deg, var(--color-primaryPalette-200), var(--color-primaryPalette-300)); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + transform: scale(1.02); +} + +.collapsed-table-item_active { + background-color: var(--color-accentedPalette-500); + color: var(--color-accentedPalette-500-contrast); +} + +.collapsed-table-item_active:hover { + background-color: var(--color-accentedPalette-600); +} + +.collapsed-table-initials { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background: linear-gradient(135deg, var(--color-primaryPalette-100), var(--color-primaryPalette-200)); + color: var(--color-primaryPalette-800); + border-radius: 6px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.collapsed-table-item_active .collapsed-table-initials { + background: linear-gradient(135deg, var(--color-accentedPalette-500), var(--color-accentedPalette-600)); + color: var(--color-accentedPalette-500-contrast); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + transform: scale(1.05); +} + +.collapsed-table-text { + font-size: 11px; + font-weight: 600; + text-align: center; + letter-spacing: 0.5px; + text-transform: uppercase; +} + +.collapsed-table-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + .connection-title { display: -webkit-box; -webkit-line-clamp: 2; @@ -40,6 +176,7 @@ .tables-list { width: 100%; box-sizing: border-box; + min-height: 0; } .tables-list ::ng-deep .table-list-item .mat-list-item-content { @@ -68,7 +205,7 @@ justify-content: center; width: 32px; height: 32px; - border-radius: 50%; + border-radius: 4px; background-color: var(--color-primaryPalette-50); color: var(--color-primaryPalette-50-contrast); } diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index b6f35fdcd..a68ba7ceb 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -1,5 +1,19 @@
- table_view +
+ +
+
+ table_view + folder +
+
+ + table_view +

{{connectionTitle}}

@@ -9,6 +23,23 @@

{{connectionTitle}}

+ + + + word.length > 0); + + if (words.length >= 2) { + // Take first letter of first two words + return (words[0].charAt(0) + words[1].charAt(0)).toUpperCase(); + } else if (words.length === 1) { + // For single word, take first two characters + const word = words[0]; + if (word.length >= 2) { + return word.substring(0, 2).toUpperCase(); + } + return word.toUpperCase(); + } + + // Fallback to original logic + if (name.length >= 2) { + return name.substring(0, 2).toUpperCase(); + } + return name.toUpperCase(); + } + getTableNameLength(tableName: string) { return tableName.length; } @@ -147,6 +183,38 @@ export class DbTablesListComponent implements OnInit { } } + onCollapsedCollectionClick(collection: Collection) { + console.log('Clicked on collection:', collection.name); + if (collection.name === 'All Tables') { + // Toggle the collapsed table list for All Tables + this.showCollapsedTableList = !this.showCollapsedTableList; + console.log('showCollapsedTableList is now:', this.showCollapsedTableList); + } else { + // For other collections, just toggle them normally + this.toggleCollection(collection.id); + } + } + + getCollapsedTableList(): TableProperties[] { + const allTablesCollection = this.collections.find(c => c.name === 'All Tables'); + console.log('getCollapsedTableList - allTablesCollection:', allTablesCollection); + if (!allTablesCollection) { + console.log('No All Tables collection found'); + return []; + } + + const tables = this.getCollectionTables(allTablesCollection); + console.log('getCollapsedTableList - tables:', tables); + return tables; + } + + navigateToTable(table: TableProperties) { + // This method is called when clicking on a table in collapsed mode + // The actual navigation is handled by routerLink in the template + // We just need to close the sidebar after navigation + this.closeSidebar(); + } + startEditCollection(collection: Collection) { console.log('startEditCollection called for:', collection.name); // Cancel any other editing From b18b1aeaf39a11259e9701f686df23e9d56d2d43 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 16:09:56 +0300 Subject: [PATCH 05/29] =?UTF-8?q?style:=20update=20dashboard=20table=20lis?= =?UTF-8?q?t=20styles=20for=20collapsed=20sidebar=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D1=8C:=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=86=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B2=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D0=B3=D0=BE=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5=D0=BD=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 8e5a07039..7cb7736ca 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -7,6 +7,12 @@ position: relative; } +:host-context(.side-bar_collapsed) { + width: 65px; + max-width: 65px; + min-width: 65px; +} + .sidenav-header { display: flex; align-items: center; @@ -16,6 +22,11 @@ box-sizing: border-box; } +:host-context(.side-bar_collapsed) .sidenav-header { + padding: 20px 8px 4px; + justify-content: center; +} + .collapsed-content { display: flex; flex-direction: column; @@ -30,6 +41,12 @@ gap: 8px; } +:host-context(.side-bar_collapsed) .collapsed-collections { + width: 100%; + justify-content: center; + align-items: center; +} + .collapsed-collection-item { display: flex; align-items: center; @@ -65,8 +82,7 @@ background: white; border: 1px solid #e0e0e0; border-top: none; - max-height: 300px; - overflow-y: auto; + overflow: visible; z-index: 1000; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); display: flex; @@ -80,6 +96,11 @@ min-width: 48px; } +:host-context(.side-bar_collapsed) .collapsed-tables-list { + margin-left: auto; + margin-right: auto; +} + .collapsed-table-item { display: flex; align-items: center; @@ -100,8 +121,7 @@ } .collapsed-table-item:hover .collapsed-table-initials { - background: linear-gradient(135deg, var(--color-primaryPalette-200), var(--color-primaryPalette-300)); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + background-color: var(--color-primaryPalette-100); transform: scale(1.02); } @@ -120,20 +140,18 @@ justify-content: center; width: 100%; height: 100%; - background: linear-gradient(135deg, var(--color-primaryPalette-100), var(--color-primaryPalette-200)); - color: var(--color-primaryPalette-800); + background-color: var(--color-primaryPalette-50); + color: var(--color-primaryPalette-50-contrast); border-radius: 6px; font-size: 11px; font-weight: 600; letter-spacing: 0.5px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); transition: all 0.2s ease; } .collapsed-table-item_active .collapsed-table-initials { - background: linear-gradient(135deg, var(--color-accentedPalette-500), var(--color-accentedPalette-600)); + background-color: var(--color-accentedPalette-500); color: var(--color-accentedPalette-500-contrast); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); transform: scale(1.05); } From 888fd108af585e8db0d688d29e0fd44b9c92e292 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 16:17:43 +0300 Subject: [PATCH 06/29] =?UTF-8?q?style:=20update=20dashboard=20table=20lis?= =?UTF-8?q?t=20styles=20for=20collapsed=20sidebar=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D1=8C:=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D1=86=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B2=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D0=B3=D0=BE=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5=D0=BD=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/db-tables-list/db-tables-list.component.css | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 7cb7736ca..876fa10f3 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -385,6 +385,7 @@ opacity: 1; } + .more-collection-button { width: 16px; height: 16px; From 590d460b47f5501c12819b69b9745e4ab7355fe2 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 16:31:56 +0300 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20add=20search=20and=20add=20collec?= =?UTF-8?q?tion=20buttons=20to=20collapsed=20sidebar=20in=20dashboard=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F:=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BA=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=81=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/dashboard.component.html | 4 ++- .../dashboard/dashboard.component.ts | 4 +++ .../db-tables-list.component.css | 28 +++++++++++++++++++ .../db-tables-list.component.html | 18 ++++++++++++ .../db-tables-list.component.ts | 19 ++++++++++++- 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index 64eefe018..b6a568168 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -70,9 +70,11 @@

Rocketadmin can not find any tables

+ [selectedTable]="selectedTableName" + (expandSidebar)="toggleSideBar()"> diff --git a/frontend/src/app/components/dashboard/dashboard.component.ts b/frontend/src/app/components/dashboard/dashboard.component.ts index e4ed2e74d..efd97516d 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.ts +++ b/frontend/src/app/components/dashboard/dashboard.component.ts @@ -124,6 +124,10 @@ export class DashboardComponent implements OnInit, OnDestroy { return this._connections.currentConnection.isTestConnection } + get connectionTitle() { + return this._connections.currentConnection?.title || 'Database'; + } + get defaultTableToOpen () { return this._connections.defaultTableToOpen; } diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 876fa10f3..0ac2a9d73 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -76,6 +76,34 @@ width: 20px; } +/* Collapsed action buttons */ +.collapsed-search-button, +.collapsed-add-button { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 4px; + background-color: var(--color-primaryPalette-50); + color: var(--color-primaryPalette-50-contrast); + cursor: pointer; + transition: background-color 0.2s ease; + margin-bottom: 4px; +} + +.collapsed-search-button:hover, +.collapsed-add-button:hover { + background-color: var(--color-primaryPalette-100); +} + +.collapsed-action-icon { + font-size: 18px; + height: 18px; + width: 18px; +} + + /* Collapsed table list styles */ .collapsed-tables-list { position: relative; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index a68ba7ceb..c707806e1 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -1,5 +1,13 @@
+ +
+ search +
+
table_view + + +
+ add +

{{connectionTitle}}

@@ -25,6 +41,8 @@

{{connectionTitle}}

+ +
(); public substringToSearch: string; public foundTables: TableProperties[]; @@ -215,6 +217,21 @@ export class DbTablesListComponent implements OnInit { this.closeSidebar(); } + toggleCollapsedSearch() { + // Open the sidebar and activate search + this.expandSidebar.emit(); + // Focus on search input after sidebar expands + setTimeout(() => { + const input = document.querySelector('.search-input input') as HTMLInputElement; + if (input) { + input.focus(); + input.select(); + } + }, 300); // Wait for sidebar animation to complete + } + + + startEditCollection(collection: Collection) { console.log('startEditCollection called for:', collection.name); // Cancel any other editing From c4ec4bca637fdff3b55adecbe9673bb8e4cb7c0a Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 16:56:14 +0300 Subject: [PATCH 08/29] =?UTF-8?q?refactor:=20enhance=20collapsed=20sidebar?= =?UTF-8?q?=20functionality=20and=20styles=20in=20dashboard=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3:=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BB=D0=B8=20=D1=81=D0=B2=D0=B5=D1=80=D0=BD=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D0=B3=D0=BE=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 57 +++++++------ .../db-tables-list.component.html | 82 ++++++++++--------- .../db-tables-list.component.ts | 21 ++--- 3 files changed, 83 insertions(+), 77 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 0ac2a9d73..ef705f2c4 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -13,19 +13,6 @@ min-width: 65px; } -.sidenav-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 20px 16px 4px; - width: 100%; - box-sizing: border-box; -} - -:host-context(.side-bar_collapsed) .sidenav-header { - padding: 20px 8px 4px; - justify-content: center; -} .collapsed-content { display: flex; @@ -115,7 +102,6 @@ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; - align-items: center; gap: 4px; padding: 8px; margin-top: 8px; @@ -124,6 +110,36 @@ min-width: 48px; } +.collapsed-tables-header { + display: flex; + align-items: center; + justify-content: flex-end; + margin-bottom: 4px; + padding-bottom: 4px; + border-bottom: 1px solid #e0e0e0; +} + +.close-collapsed-list { + width: 16px; + height: 16px; + line-height: 16px; + border: none; + border-radius: 0; + background-color: transparent !important; + color: #9e9e9e !important; + flex-shrink: 0; +} + +.close-collapsed-list:hover { + color: #757575 !important; +} + +.close-collapsed-list mat-icon { + font-size: 12px; + width: 12px; + height: 12px; +} + :host-context(.side-bar_collapsed) .collapsed-tables-list { margin-left: auto; margin-right: auto; @@ -197,19 +213,6 @@ height: 16px; } -.connection-title { - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 22px; - margin: 0 !important; - overflow: hidden; - padding-right: 36px; - padding-bottom: 4px; - text-overflow: ellipsis; - word-wrap: anywhere; -} .mat-icon-button { margin-right: -8px; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index c707806e1..de77b9316 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -1,50 +1,58 @@ -
-
- -
- search -
- - -
-
- table_view - folder -
-
- - table_view - - -
- add -
-
- -

{{connectionTitle}}

-
-
+ +
+ +
+ search +
+ + +
+
+ table_view + folder +
+
+ + table_view + + +
+ add +
+
+ - +
+
+ +
c.name === 'All Tables'); - console.log('getCollapsedTableList - allTablesCollection:', allTablesCollection); - if (!allTablesCollection) { - console.log('No All Tables collection found'); + if (!this.currentCollapsedCollection) { + console.log('No current collapsed collection'); return []; } - const tables = this.getCollectionTables(allTablesCollection); + const tables = this.getCollectionTables(this.currentCollapsedCollection); console.log('getCollapsedTableList - tables:', tables); return tables; } From e9f677b7c6276b827354725665d5e34e60adb2b1 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 29 Sep 2025 18:09:27 +0300 Subject: [PATCH 09/29] =?UTF-8?q?refactor:=20improve=20active=20state=20ha?= =?UTF-8?q?ndling=20and=20styles=20for=20collapsed=20collection=20items=20?= =?UTF-8?q?in=20dashboard=20=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3:=20=D1=83=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D1=83=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D1=82?= =?UTF-8?q?=D1=8B=D1=85=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 38 +++++-------------- .../db-tables-list.component.html | 11 ++---- .../db-tables-list.component.ts | 15 ++++++-- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index ef705f2c4..c1ed2f9db 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -51,6 +51,15 @@ background-color: var(--color-primaryPalette-100); } +.collapsed-collection-item_active { + background-color: var(--color-accentedPalette-500); + color: var(--color-accentedPalette-500-contrast); +} + +.collapsed-collection-item_active:hover { + background-color: var(--color-accentedPalette-600); +} + .collapsed-collection-icon { font-size: 20px; height: 20px; @@ -110,35 +119,6 @@ min-width: 48px; } -.collapsed-tables-header { - display: flex; - align-items: center; - justify-content: flex-end; - margin-bottom: 4px; - padding-bottom: 4px; - border-bottom: 1px solid #e0e0e0; -} - -.close-collapsed-list { - width: 16px; - height: 16px; - line-height: 16px; - border: none; - border-radius: 0; - background-color: transparent !important; - color: #9e9e9e !important; - flex-shrink: 0; -} - -.close-collapsed-list:hover { - color: #757575 !important; -} - -.close-collapsed-list mat-icon { - font-size: 12px; - width: 12px; - height: 12px; -} :host-context(.side-bar_collapsed) .collapsed-tables-list { margin-left: auto; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index de77b9316..1768773b7 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -18,13 +18,14 @@
table_view - folder + folder
@@ -47,12 +48,6 @@
-
- -
Date: Mon, 29 Sep 2025 18:19:08 +0300 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20implement=20search=20functionalit?= =?UTF-8?q?y=20to=20collapse=20and=20expand=20collections=20based=20on=20m?= =?UTF-8?q?atching=20tables=20in=20dashboard=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F:=20=D1=80=D0=B5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B2=D0=BE=D1=80=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=B2=D0=BE=D1=80=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BD=D0=B0=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D0=BF=D0=B0=D0=B4=D0=B0=D1=8E=D1=89=D0=B8?= =?UTF-8?q?=D1=85=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list/db-tables-list.component.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 813555ca5..3d247f617 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -82,6 +82,10 @@ export class DbTablesListComponent implements OnInit { searchSubstring() { if (!this.substringToSearch || this.substringToSearch.trim() === '') { this.foundTables = this.tables; + // Collapse all collections when search is cleared + this.collections.forEach(collection => { + collection.expanded = false; + }); return; } @@ -112,6 +116,20 @@ export class DbTablesListComponent implements OnInit { this.foundTables = this.foundTables.filter((table, index, self) => index === self.findIndex(t => t.table === table.table) ); + + // Expand all collections that contain matching tables + this.collections.forEach(collection => { + const collectionTables = this.getCollectionTables(collection); + const hasMatchingTables = collectionTables.some(table => + this.foundTables.some(foundTable => foundTable.table === table.table) + ); + + if (hasMatchingTables) { + collection.expanded = true; + } else { + collection.expanded = false; + } + }); } getTableName(table: TableProperties) { From 560a6038b31b740f3f80828b7e3c218adc4c6f19 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 30 Sep 2025 11:46:37 +0300 Subject: [PATCH 11/29] =?UTF-8?q?refactor:=20rename=20collections=20to=20f?= =?UTF-8?q?olders=20and=20update=20related=20functionality=20in=20dashboar?= =?UTF-8?q?d=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3:=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=83=D1=8E=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 60 ++-- .../db-tables-list.component.html | 138 +++++----- .../db-tables-list.component.ts | 260 +++++++++--------- 3 files changed, 229 insertions(+), 229 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index c1ed2f9db..8171f4f2f 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -21,20 +21,20 @@ gap: 8px; } -.collapsed-collections { +.collapsed-folders { display: flex; flex-direction: column; align-items: center; gap: 8px; } -:host-context(.side-bar_collapsed) .collapsed-collections { +:host-context(.side-bar_collapsed) .collapsed-folders { width: 100%; justify-content: center; align-items: center; } -.collapsed-collection-item { +.collapsed-folder-item { display: flex; align-items: center; justify-content: center; @@ -47,26 +47,26 @@ transition: background-color 0.2s ease; } -.collapsed-collection-item:hover { +.collapsed-folder-item:hover { background-color: var(--color-primaryPalette-100); } -.collapsed-collection-item_active { +.collapsed-folder-item_active { background-color: var(--color-accentedPalette-500); color: var(--color-accentedPalette-500-contrast); } -.collapsed-collection-item_active:hover { +.collapsed-folder-item_active:hover { background-color: var(--color-accentedPalette-600); } -.collapsed-collection-icon { +.collapsed-folder-icon { font-size: 20px; height: 20px; width: 20px; } -.collapsed-collection-icon.fallback { +.collapsed-folder-icon.fallback { font-size: 20px; height: 20px; width: 20px; @@ -307,7 +307,7 @@ padding-bottom: 8px; } -.add-collection-button-container { +.add-folder-button-container { margin: 16px; margin-top: 16px; margin-bottom: 4px; @@ -315,7 +315,7 @@ box-sizing: border-box; } -.add-collection-button { +.add-folder-button { width: 100%; display: flex; align-items: center; @@ -328,25 +328,25 @@ color: #616161 !important; } -.add-collection-button:hover { +.add-folder-button:hover { border-color: #757575 !important; background-color: rgba(0, 0, 0, 0.04) !important; } -.add-collection-button mat-icon { +.add-folder-button mat-icon { font-size: 18px; width: 18px; height: 18px; } -.collections-section { +.folders-section { margin: 0 16px; margin-bottom: 8px; width: calc(100% - 32px); box-sizing: border-box; } -.collection-item { +.folder-item { margin-bottom: 4px; border: none; border-radius: 4px; @@ -357,12 +357,12 @@ transition: background-color 0.2s ease; } -.collection-item.drag-over { +.folder-item.drag-over { background-color: rgba(33, 150, 243, 0.1); border: 2px dashed #2196f3; } -.collection-item.expanded::after { +.folder-item.expanded::after { content: ''; position: absolute; bottom: 0; @@ -372,7 +372,7 @@ background-color: #e0e0e0; } -.collection-header { +.folder-header { display: flex; align-items: center; padding: 8px 4px 8px 0; @@ -381,23 +381,23 @@ height: 40px; } -.collection-header:hover { +.folder-header:hover { background: #f5f5f5; } -.collection-actions { +.folder-actions { display: flex; align-items: center; opacity: 0; transition: opacity 0.2s ease; } -.collection-header:hover .collection-actions { +.folder-header:hover .folder-actions { opacity: 1; } -.more-collection-button { +.more-folder-button { width: 16px; height: 16px; line-height: 16px; @@ -408,32 +408,32 @@ margin-left: 4px; } -.more-collection-button:hover { +.more-folder-button:hover { border: none !important; background-color: rgba(0, 0, 0, 0.02) !important; color: #9e9e9e !important; } -.more-collection-button mat-icon { +.more-folder-button mat-icon { font-size: 12px; width: 12px; height: 12px; } -.collection-name { +.folder-name { flex: 1; font-weight: 500; color: #424242; } -.collection-name-edit-container { +.folder-name-edit-container { display: flex; align-items: center; flex: 1; max-width: calc(100% - 40px); } -.collection-name.editing { +.folder-name.editing { background: white; border: 1px solid #2196f3; border-radius: 2px; @@ -456,15 +456,15 @@ color: #757575; } -.collection-expand-icon { +.folder-expand-icon { transition: transform 0.2s ease; } -.collection-expand-icon.expanded { +.folder-expand-icon.expanded { transform: rotate(90deg); } -.collection-tables { +.folder-tables { padding: 0 12px 8px; border-top: none; width: 100%; @@ -473,7 +473,7 @@ min-height: 40px; } -.collection-tables-header { +.folder-tables-header { display: flex; justify-content: space-between; align-items: center; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 1768773b7..335632c62 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -14,30 +14,30 @@ search
- -
-
+
+
- table_view - folder + (click)="onCollapsedFolderClick(folder)"> + table_view + folder
- - + table_view - +
add
@@ -46,7 +46,7 @@ - +
Nothing found. -
-
-
- -
-
- +
+ +
+
+ chevron_right -
- {{collection.name}} +
+ {{folder.name}}
-
- - - - -
- -
- +
+ - keyboard_return + (focus)="folderNameInput.select()"> + keyboard_return
-
-
+
-
{{getTableName(table)}} -
-

No tables in this collection

- @@ -189,13 +189,13 @@
-

Add Tables to {{currentCollection?.name}}

+

Add Tables to {{currentFolder?.name}}

-
+
{{getTableName(table)}} @@ -204,7 +204,7 @@

Add Tables to {{currentCollection?.name}}

-
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 3d247f617..b06ed995f 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -15,7 +15,7 @@ import { RouterModule } from '@angular/router'; import { TableProperties } from 'src/app/models/table'; import { TableStateService } from 'src/app/services/table-state.service'; -export interface Collection { +export interface Folder { id: string; name: string; expanded: boolean; @@ -53,21 +53,21 @@ export class DbTablesListComponent implements OnInit { public substringToSearch: string; public foundTables: TableProperties[]; - public collections: Collection[] = []; - private editingCollectionName: string = ''; + public folders: Folder[] = []; + private editingFolderName: string = ''; // Dialog state public showAddTableDialogFlag: boolean = false; - public currentCollection: Collection | null = null; + public currentFolder: Folder | null = null; public selectedTablesForAdd: string[] = []; // Drag and drop state public draggedTable: TableProperties | null = null; - public dragOverCollection: string | null = null; + public dragOverFolder: string | null = null; // Collapsed state public showCollapsedTableList: boolean = false; - public currentCollapsedCollection: Collection | null = null; + public currentCollapsedFolder: Folder | null = null; constructor( private _tableState: TableStateService, @@ -75,32 +75,32 @@ export class DbTablesListComponent implements OnInit { ngOnInit() { this.foundTables = this.tables; - this.loadCollections(); + this.loadFolders(); console.log('ngOnInit - showCollapsedTableList initialized to:', this.showCollapsedTableList); } searchSubstring() { if (!this.substringToSearch || this.substringToSearch.trim() === '') { this.foundTables = this.tables; - // Collapse all collections when search is cleared - this.collections.forEach(collection => { - collection.expanded = false; + // Collapse all folders when search is cleared + this.folders.forEach(folder => { + folder.expanded = false; }); return; } const searchTerm = this.substringToSearch.toLowerCase(); - // Get all tables that match the search (including those in collections) + // Get all tables that match the search (including those in folders) const allTables = [...this.tables]; - // Add tables from collections that might not be in the main tables list - this.collections.forEach(collection => { - collection.tableIds.forEach(tableId => { + // Add tables from folders that might not be in the main tables list + this.folders.forEach(folder => { + folder.tableIds.forEach(tableId => { // Find the table object by ID - const tableInCollection = this.tables.find(t => t.table === tableId); - if (tableInCollection && !allTables.find(t => t.table === tableId)) { - allTables.push(tableInCollection); + const tableInFolder = this.tables.find(t => t.table === tableId); + if (tableInFolder && !allTables.find(t => t.table === tableId)) { + allTables.push(tableInFolder); } }); }); @@ -117,17 +117,17 @@ export class DbTablesListComponent implements OnInit { index === self.findIndex(t => t.table === table.table) ); - // Expand all collections that contain matching tables - this.collections.forEach(collection => { - const collectionTables = this.getCollectionTables(collection); - const hasMatchingTables = collectionTables.some(table => + // Expand all folders that contain matching tables + this.folders.forEach(folder => { + const folderTables = this.getFolderTables(folder); + const hasMatchingTables = folderTables.some(table => this.foundTables.some(foundTable => foundTable.table === table.table) ); if (hasMatchingTables) { - collection.expanded = true; + folder.expanded = true; } else { - collection.expanded = false; + folder.expanded = false; } }); } @@ -177,57 +177,57 @@ export class DbTablesListComponent implements OnInit { this._tableState.closeAIpanel(); } - closeAllCollections() { - this.collections.forEach(collection => { + closeAllFolders() { + this.folders.forEach(collection => { collection.expanded = false; }); - this.saveCollections(); + this.saveFolders(); } - onAddCollection() { - const newCollection: Collection = { - id: this.generateCollectionId(), - name: `Collection ${this.collections.length + 1}`, + onAddFolder() { + const newFolder: Folder = { + id: this.generateFolderId(), + name: `Folder ${this.folders.length}`, expanded: false, editing: false, tableIds: [] }; - this.collections.push(newCollection); - this.saveCollections(); - this.startEditCollection(newCollection); + this.folders.push(newFolder); + this.saveFolders(); + this.startEditFolder(newFolder); } - toggleCollection(collectionId: string) { - const collection = this.collections.find(c => c.id === collectionId); - if (collection) { - collection.expanded = !collection.expanded; + toggleFolder(folderId: string) { + const folder = this.folders.find(f => f.id === folderId); + if (folder) { + folder.expanded = !folder.expanded; } } - onCollapsedCollectionClick(collection: Collection) { - console.log('Clicked on collection:', collection.name); + onCollapsedFolderClick(folder: Folder) { + console.log('Clicked on folder:', folder.name); - // If clicking on the same collection that's already open, close it - if (this.currentCollapsedCollection?.id === collection.id) { + // If clicking on the same folder that's already open, close it + if (this.currentCollapsedFolder?.id === folder.id) { this.showCollapsedTableList = false; - this.currentCollapsedCollection = null; + this.currentCollapsedFolder = null; } else { - // If clicking on a different collection, open it immediately + // If clicking on a different folder, open it immediately this.showCollapsedTableList = true; - this.currentCollapsedCollection = collection; + this.currentCollapsedFolder = folder; } console.log('showCollapsedTableList is now:', this.showCollapsedTableList); - console.log('currentCollapsedCollection is now:', this.currentCollapsedCollection?.name); + console.log('currentCollapsedFolder is now:', this.currentCollapsedFolder?.name); } getCollapsedTableList(): TableProperties[] { - if (!this.currentCollapsedCollection) { - console.log('No current collapsed collection'); + if (!this.currentCollapsedFolder) { + console.log('No current collapsed folder'); return []; } - const tables = this.getCollectionTables(this.currentCollapsedCollection); + const tables = this.getFolderTables(this.currentCollapsedFolder); console.log('getCollapsedTableList - tables:', tables); return tables; } @@ -254,17 +254,17 @@ export class DbTablesListComponent implements OnInit { - startEditCollection(collection: Collection) { - console.log('startEditCollection called for:', collection.name); + startEditFolder(folder: Folder) { + console.log('startEditFolder called for:', folder.name); // Cancel any other editing - this.collections.forEach(c => c.editing = false); - collection.editing = true; - this.editingCollectionName = collection.name; - console.log('Collection editing state:', collection.editing); + this.folders.forEach(f => f.editing = false); + folder.editing = true; + this.editingFolderName = folder.name; + console.log('Folder editing state:', folder.editing); // Focus and select the input after the view updates setTimeout(() => { - const input = document.querySelector('.collection-name.editing') as HTMLInputElement; + const input = document.querySelector('.folder-name.editing') as HTMLInputElement; if (input) { input.focus(); input.select(); @@ -272,28 +272,28 @@ export class DbTablesListComponent implements OnInit { }, 0); } - finishEditCollection(collection: Collection) { - collection.editing = false; - if (collection.name.trim() === '') { - collection.name = this.editingCollectionName; + finishEditFolder(folder: Folder) { + folder.editing = false; + if (folder.name.trim() === '') { + folder.name = this.editingFolderName; } - this.saveCollections(); + this.saveFolders(); } - deleteCollection(collection: Collection) { - if (confirm(`Are you sure you want to delete the collection "${collection.name}"?`)) { - const index = this.collections.findIndex(c => c.id === collection.id); + deleteFolder(folder: Folder) { + if (confirm(`Are you sure you want to delete the folder "${folder.name}"?`)) { + const index = this.folders.findIndex(f => f.id === folder.id); if (index > -1) { - this.collections.splice(index, 1); - this.saveCollections(); + this.folders.splice(index, 1); + this.saveFolders(); } } } - onCollectionNameDoubleClick(event: Event, collection: Collection) { - if (collection.name !== 'All Tables') { + onFolderNameDoubleClick(event: Event, folder: Folder) { + if (folder.name !== 'All Tables') { event.stopPropagation(); - this.startEditCollection(collection); + this.startEditFolder(folder); // Close any open menus after starting edit setTimeout(() => { @@ -327,56 +327,56 @@ export class DbTablesListComponent implements OnInit { if (!isEditingInput && !isEditButton) { // Find any collection that is currently being edited - const editingCollection = this.collections.find(c => c.editing); - if (editingCollection) { - this.finishEditCollection(editingCollection); + const editingFolder = this.folders.find(c => c.editing); + if (editingFolder) { + this.finishEditFolder(editingFolder); } } } - cancelEditCollection(collection: Collection) { - collection.editing = false; - collection.name = this.editingCollectionName; + cancelEditFolder(folder: Folder) { + folder.editing = false; + folder.name = this.editingFolderName; } - getCollectionTables(collection: Collection): TableProperties[] { - const collectionTables = this.tables.filter(table => collection.tableIds.includes(table.table)); + getFolderTables(folder: Folder): TableProperties[] { + const folderTables = this.tables.filter(table => folder.tableIds.includes(table.table)); // If there's a search term, filter the collection tables too if (this.substringToSearch && this.substringToSearch.trim() !== '') { const searchTerm = this.substringToSearch.toLowerCase(); - return collectionTables.filter(table => + return folderTables.filter(table => table.table.toLowerCase().includes(searchTerm) || (table.display_name && table.display_name.toLowerCase().includes(searchTerm)) || (table.normalizedTableName && table.normalizedTableName.toLowerCase().includes(searchTerm)) ); } - return collectionTables; + return folderTables; } - getAvailableTables(collection: Collection | null): TableProperties[] { - if (!collection) return []; - return this.tables.filter(table => !collection.tableIds.includes(table.table)); + getAvailableTables(folder: Folder | null): TableProperties[] { + if (!folder) return []; + return this.tables.filter(table => !folder.tableIds.includes(table.table)); } - isTableInAnyCollection(table: TableProperties): boolean { - return this.collections.some(collection => collection.tableIds.includes(table.table)); + isTableInAnyFolder(table: TableProperties): boolean { + return this.folders.some(folder => folder.tableIds.includes(table.table)); } - shouldShowCollection(collection: Collection): boolean { - // If no search term, show all collections + shouldShowFolder(folder: Folder): boolean { + // If no search term, show all folders if (!this.substringToSearch || this.substringToSearch.trim() === '') { return true; } // Show collection if it has tables matching the search - const matchingTables = this.getCollectionTables(collection); + const matchingTables = this.getFolderTables(folder); return matchingTables.length > 0; } - showAddTableDialog(collection: Collection) { - this.currentCollection = collection; + showAddTableDialog(folder: Folder) { + this.currentFolder = folder; this.selectedTablesForAdd = []; this.showAddTableDialogFlag = true; } @@ -384,7 +384,7 @@ export class DbTablesListComponent implements OnInit { closeAddTableDialog(event?: Event) { if (event && event.target !== event.currentTarget) return; this.showAddTableDialogFlag = false; - this.currentCollection = null; + this.currentFolder = null; this.selectedTablesForAdd = []; } @@ -399,72 +399,72 @@ export class DbTablesListComponent implements OnInit { } } - addSelectedTablesToCollection() { - if (!this.currentCollection) return; + addSelectedTablesToFolder() { + if (!this.currentFolder) return; this.selectedTablesForAdd.forEach(tableId => { - if (!this.currentCollection!.tableIds.includes(tableId)) { - this.currentCollection!.tableIds.push(tableId); + if (!this.currentFolder!.tableIds.includes(tableId)) { + this.currentFolder!.tableIds.push(tableId); } }); - this.saveCollections(); + this.saveFolders(); this.closeAddTableDialog(); } - removeTableFromCollection(collection: Collection, table: TableProperties, event: Event) { + removeTableFromFolder(folder: Folder, table: TableProperties, event: Event) { event.stopPropagation(); - collection.tableIds = collection.tableIds.filter(id => id !== table.table); - this.saveCollections(); + folder.tableIds = folder.tableIds.filter(id => id !== table.table); + this.saveFolders(); } - trackByCollectionId(index: number, collection: Collection): string { - return collection.id; + trackByFolderId(index: number, folder: Folder): string { + return folder.id; } - private generateCollectionId(): string { + private generateFolderId(): string { return Date.now().toString(); } - private loadCollections() { - const key = `collections_${this.connectionID}`; + private loadFolders() { + const key = `folders_${this.connectionID}`; const saved = localStorage.getItem(key); if (saved) { try { - this.collections = JSON.parse(saved); + this.folders = JSON.parse(saved); // Reset editing state only, keep expanded state - this.collections.forEach(c => c.editing = false); - console.log('Collections loaded:', this.collections.map(c => ({ name: c.name, expanded: c.expanded }))); + this.folders.forEach(c => c.editing = false); + console.log('Folders loaded:', this.folders.map(c => ({ name: c.name, expanded: c.expanded }))); } catch (e) { - console.error('Error loading collections:', e); - this.collections = []; + console.error('Error loading folders:', e); + this.folders = []; } } else { - console.log('No saved collections found for key:', key); + console.log('No saved folders found for key:', key); } // Create "All Tables" collection if it doesn't exist - const allTablesCollection = this.collections.find(c => c.name === 'All Tables'); - if (!allTablesCollection) { - const allTablesCollection: Collection = { - id: this.generateCollectionId(), + const allTablesFolder = this.folders.find(c => c.name === 'All Tables'); + if (!allTablesFolder) { + const allTablesFolder: Folder = { + id: this.generateFolderId(), name: 'All Tables', expanded: true, editing: false, tableIds: this.tables.map(table => table.table) }; - this.collections.unshift(allTablesCollection); // Add to beginning - this.saveCollections(); + this.folders.unshift(allTablesFolder); // Add to beginning + this.saveFolders(); } } - private saveCollections() { + private saveFolders() { try { - const key = `collections_${this.connectionID}`; - localStorage.setItem(key, JSON.stringify(this.collections)); - console.log('Collections saved:', this.collections.map(c => ({ name: c.name, expanded: c.expanded }))); + const key = `folders_${this.connectionID}`; + localStorage.setItem(key, JSON.stringify(this.folders)); + console.log('Folders saved:', this.folders.map(c => ({ name: c.name, expanded: c.expanded }))); } catch (e) { - console.error('Error saving collections:', e); + console.error('Error saving folders:', e); } } @@ -479,43 +479,43 @@ export class DbTablesListComponent implements OnInit { onTableDragEnd(event: DragEvent) { this.draggedTable = null; - this.dragOverCollection = null; + this.dragOverFolder = null; } - onCollectionDragOver(event: DragEvent, collectionId: string) { + onFolderDragOver(event: DragEvent, folderId: string) { event.preventDefault(); if (event.dataTransfer) { event.dataTransfer.dropEffect = 'move'; } - this.dragOverCollection = collectionId; + this.dragOverFolder = folderId; } - onCollectionDragLeave(event: DragEvent, collectionId: string) { + onFolderDragLeave(event: DragEvent, folderId: string) { // Only clear if we're actually leaving the collection (not moving to a child element) const rect = (event.currentTarget as HTMLElement).getBoundingClientRect(); const x = event.clientX; const y = event.clientY; if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { - this.dragOverCollection = null; + this.dragOverFolder = null; } } - onCollectionDrop(event: DragEvent, collection: Collection) { + onFolderDrop(event: DragEvent, folder: Folder) { event.preventDefault(); if (this.draggedTable) { - if (collection.tableIds.includes(this.draggedTable.table)) { + if (folder.tableIds.includes(this.draggedTable.table)) { // Show notification that table already exists - this.showTableExistsNotification(collection.name, this.getTableName(this.draggedTable)); + this.showTableExistsNotification(folder.name, this.getTableName(this.draggedTable)); } else { - collection.tableIds.push(this.draggedTable.table); - this.saveCollections(); + folder.tableIds.push(this.draggedTable.table); + this.saveFolders(); } } this.draggedTable = null; - this.dragOverCollection = null; + this.dragOverFolder = null; } private showTableExistsNotification(collectionName: string, tableName: string) { From e53258b0d521dfe8f9d41ea64476a4bd448189a5 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 30 Sep 2025 12:10:57 +0300 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20implement=20edit=20tables=20dialo?= =?UTF-8?q?g=20functionality=20in=20dashboard=20with=20updated=20styles=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F:?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B4=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B0=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=81=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8=20=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=D1=8F=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 69 ++++++++++++++++++- .../db-tables-list.component.html | 48 +++++-------- .../db-tables-list.component.ts | 55 +++++++-------- 3 files changed, 115 insertions(+), 57 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 8171f4f2f..43a33884a 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -616,7 +616,7 @@ } /* Add Table Dialog Styles */ -.add-table-dialog { +.edit-tables-dialog { position: fixed; top: 0; left: 0; @@ -669,6 +669,73 @@ border-bottom: none; } +/* Edit Tables Dialog specific styles */ +.edit-tables-dialog .dialog-content { + max-width: 450px; + width: 90%; +} + +.edit-tables-dialog .dialog-body { + max-height: 400px; + overflow-y: auto; +} + +.edit-tables-dialog .table-list { + margin-top: 16px; +} + +.edit-tables-dialog .table-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #e0e0e0; + transition: background-color 0.2s ease; + padding-left: 4px; + padding-right: 4px; +} + +.edit-tables-dialog .table-item:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.edit-tables-dialog .table-item:last-child { + border-bottom: none; +} + +.edit-tables-dialog .table-name { + flex: 1; + font-size: 14px; + color: #333; +} + +.edit-tables-dialog .action-button { + min-width: 60px; + height: 32px; + font-size: 12px; + font-weight: 500; + border-radius: 4px; + border: none; +} + +.edit-tables-dialog .add-button { + color: #4caf50; + background-color: transparent; +} + +.edit-tables-dialog .add-button:hover { + background-color: rgba(76, 175, 80, 0.1); +} + +.edit-tables-dialog .remove-button { + color: #f44336; + background-color: transparent; +} + +.edit-tables-dialog .remove-button:hover { + background-color: rgba(244, 67, 54, 0.1); +} + .dialog-actions { display: flex; justify-content: flex-end; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 335632c62..a487ae0fe 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -97,12 +97,6 @@ {{folder.name}}

No tables in this folder

- @@ -185,28 +177,26 @@

- -
+ +
-

Add Tables to {{currentFolder?.name}}

- +

Edit Tables in {{currentFolder?.name}}

-
- +

Manage tables in this folder:

+
+
{{getTableName(table)}} - + +
-
- - -
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index b06ed995f..a87d43e47 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -57,9 +57,8 @@ export class DbTablesListComponent implements OnInit { private editingFolderName: string = ''; // Dialog state - public showAddTableDialogFlag: boolean = false; + public showEditTablesDialogFlag: boolean = false; public currentFolder: Folder | null = null; - public selectedTablesForAdd: string[] = []; // Drag and drop state public draggedTable: TableProperties | null = null; @@ -375,43 +374,45 @@ export class DbTablesListComponent implements OnInit { return matchingTables.length > 0; } - showAddTableDialog(folder: Folder) { + showEditTablesDialog(folder: Folder) { this.currentFolder = folder; - this.selectedTablesForAdd = []; - this.showAddTableDialogFlag = true; + this.showEditTablesDialogFlag = true; } - closeAddTableDialog(event?: Event) { - if (event && event.target !== event.currentTarget) return; - this.showAddTableDialogFlag = false; + closeEditTablesDialog(event?: Event) { + if (event) { + event.stopPropagation(); + } + this.showEditTablesDialogFlag = false; this.currentFolder = null; - this.selectedTablesForAdd = []; } - toggleTableSelection(tableId: string, event: any) { - const isChecked = event.checked; - if (isChecked) { - if (!this.selectedTablesForAdd.includes(tableId)) { - this.selectedTablesForAdd.push(tableId); - } - } else { - this.selectedTablesForAdd = this.selectedTablesForAdd.filter(id => id !== tableId); - } + get allTables(): TableProperties[] { + return this.tables; } - addSelectedTablesToFolder() { + isTableInFolder(table: TableProperties): boolean { + return this.currentFolder ? this.currentFolder.tableIds.includes(table.table) : false; + } + + toggleTableInFolder(table: TableProperties) { if (!this.currentFolder) return; - - this.selectedTablesForAdd.forEach(tableId => { - if (!this.currentFolder!.tableIds.includes(tableId)) { - this.currentFolder!.tableIds.push(tableId); - } - }); - + + const tableId = table.table; + const isInFolder = this.currentFolder.tableIds.includes(tableId); + + if (isInFolder) { + // Remove from folder + this.currentFolder.tableIds = this.currentFolder.tableIds.filter(id => id !== tableId); + } else { + // Add to folder + this.currentFolder.tableIds.push(tableId); + } + this.saveFolders(); - this.closeAddTableDialog(); } + removeTableFromFolder(folder: Folder, table: TableProperties, event: Event) { event.stopPropagation(); folder.tableIds = folder.tableIds.filter(id => id !== table.table); From 4ec27ad808a50f74a781c61b32b02e6e0cf28e2b Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 30 Sep 2025 13:26:27 +0300 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20add=20folder=20state=20preservati?= =?UTF-8?q?on=20and=20restoration=20on=20sidebar=20collapse/expand=20in=20?= =?UTF-8?q?dashboard=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8F:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B0=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=B2=D0=BE=D1=80=D0=B0?= =?UTF-8?q?=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8/=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B2=D0=BE=D1=80=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B1=D0=BE=D0=BA=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 1 + .../db-tables-list.component.html | 7 +- .../db-tables-list.component.ts | 79 ++++++++++++++++++- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 43a33884a..5ab8a0556 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -743,3 +743,4 @@ padding: 16px 20px; border-top: 1px solid #e0e0e0; } + diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index a487ae0fe..565652242 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -106,15 +106,15 @@ @@ -149,6 +149,7 @@ {{getTableName(table)}}
+
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index a87d43e47..d58bbf695 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { Component, HostListener, Input, OnChanges, OnInit, Output, EventEmitter, SimpleChanges } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ContentLoaderComponent } from '../../ui-components/content-loader/content-loader.component'; @@ -42,7 +42,7 @@ export interface Folder { ContentLoaderComponent ] }) -export class DbTablesListComponent implements OnInit { +export class DbTablesListComponent implements OnInit, OnChanges { @Input() connectionID: string; @Input() connectionTitle: string; @Input() tables: TableProperties[]; @@ -67,6 +67,10 @@ export class DbTablesListComponent implements OnInit { // Collapsed state public showCollapsedTableList: boolean = false; public currentCollapsedFolder: Folder | null = null; + + // State preservation + private preservedFolderStates: { [key: string]: boolean } = {}; + private preservedActiveFolder: string | null = null; constructor( private _tableState: TableStateService, @@ -78,6 +82,18 @@ export class DbTablesListComponent implements OnInit { console.log('ngOnInit - showCollapsedTableList initialized to:', this.showCollapsedTableList); } + ngOnChanges(changes: SimpleChanges) { + if (changes['collapsed']) { + if (changes['collapsed'].currentValue === true) { + // Sidebar is being collapsed - preserve current state + this.preserveFolderStates(); + } else if (changes['collapsed'].currentValue === false) { + // Sidebar is being expanded - restore preserved state + this.restoreFolderStates(); + } + } + } + searchSubstring() { if (!this.substringToSearch || this.substringToSearch.trim() === '') { this.foundTables = this.tables; @@ -376,6 +392,8 @@ export class DbTablesListComponent implements OnInit { showEditTablesDialog(folder: Folder) { this.currentFolder = folder; + // Expand the folder if it's collapsed + folder.expanded = true; this.showEditTablesDialogFlag = true; } @@ -395,6 +413,13 @@ export class DbTablesListComponent implements OnInit { return this.currentFolder ? this.currentFolder.tableIds.includes(table.table) : false; } + isTableInCurrentDraggedFolder(table: TableProperties, folder: Folder): boolean { + return this.draggedTable && + this.draggedTable.table === table.table && + folder.tableIds.includes(table.table); + } + + toggleTableInFolder(table: TableProperties) { if (!this.currentFolder) return; @@ -423,6 +448,49 @@ export class DbTablesListComponent implements OnInit { return folder.id; } + private preserveFolderStates() { + // Save expanded states of all folders + this.folders.forEach(folder => { + this.preservedFolderStates[folder.id] = folder.expanded; + }); + + // Find and save the currently active folder (the one that contains selected table) + const activeFolder = this.findActiveFolder(); + this.preservedActiveFolder = activeFolder ? activeFolder.id : null; + + console.log('Preserved folder states:', this.preservedFolderStates); + console.log('Preserved active folder:', this.preservedActiveFolder); + } + + private restoreFolderStates() { + // Restore expanded states of all folders + this.folders.forEach(folder => { + if (this.preservedFolderStates.hasOwnProperty(folder.id)) { + folder.expanded = this.preservedFolderStates[folder.id]; + } + }); + + // Restore the active folder state in collapsed view + if (this.preservedActiveFolder) { + const activeFolder = this.folders.find(f => f.id === this.preservedActiveFolder); + if (activeFolder) { + this.currentCollapsedFolder = activeFolder; + this.showCollapsedTableList = true; + } + } + + console.log('Restored folder states:', this.preservedFolderStates); + console.log('Restored active folder:', this.preservedActiveFolder); + } + + private findActiveFolder(): Folder | null { + if (!this.selectedTable) return null; + + return this.folders.find(folder => + folder.tableIds.includes(this.selectedTable) + ) || null; + } + private generateFolderId(): string { return Date.now().toString(); } @@ -506,10 +574,12 @@ export class DbTablesListComponent implements OnInit { event.preventDefault(); if (this.draggedTable) { + // Check if table is already in this specific folder if (folder.tableIds.includes(this.draggedTable.table)) { - // Show notification that table already exists - this.showTableExistsNotification(folder.name, this.getTableName(this.draggedTable)); + // Table already exists in this folder, do nothing (no notification) + return; } else { + // Simply add table to the target folder (don't remove from other folders) folder.tableIds.push(this.draggedTable.table); this.saveFolders(); } @@ -519,6 +589,7 @@ export class DbTablesListComponent implements OnInit { this.dragOverFolder = null; } + private showTableExistsNotification(collectionName: string, tableName: string) { // Create a temporary notification element const notification = document.createElement('div'); From d9d441f6c23508a121d385c2c0ce985666fe9d4e Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 30 Sep 2025 14:15:59 +0300 Subject: [PATCH 14/29] =?UTF-8?q?=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 18 ++++++++++++++++-- .../db-tables-list.component.html | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 5ab8a0556..2695eb307 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -39,7 +39,7 @@ align-items: center; justify-content: center; width: 32px; - height: 32px; + height: 40px; border-radius: 4px; background-color: var(--color-primaryPalette-50); color: var(--color-primaryPalette-50-contrast); @@ -130,7 +130,7 @@ align-items: center; justify-content: center; width: 32px; - height: 32px; + height: 40px; text-decoration: none; color: inherit; cursor: pointer; @@ -456,8 +456,22 @@ color: #757575; } +.folder-icon { + font-size: 20px; + width: 20px; + height: 20px; + color: #666; + margin-right: 8px; + flex-shrink: 0; +} + .folder-expand-icon { transition: transform 0.2s ease; + font-size: 20px; + width: 20px; + height: 20px; + color: #666; + margin-left: auto; } .folder-expand-icon.expanded { diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 565652242..445a7fe82 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -87,15 +87,19 @@ (dragleave)="onFolderDragLeave($event, folder.id)" (drop)="onFolderDrop($event, folder)">
- - chevron_right - + + table_view + folder +
{{folder.name}}
+
+ + + + chevron_right + +
+
+ +
+

Manage tables in this folder

+
{{getTableName(table)}}
+
+ diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index a4e1aafcb..667b57da3 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -22,6 +22,7 @@ export interface Folder { expanded: boolean; editing: boolean; tableIds: string[]; + iconColor?: string; // Optional color for folder icon } @Component({ @@ -75,6 +76,18 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Table icons cache private tableIcons: { [key: string]: string } = {}; + + // Folder icon colors + public folderIconColors = [ + { name: 'Default', value: '#212121' }, + { name: 'Blue', value: '#2196F3' }, + { name: 'Green', value: '#4CAF50' }, + { name: 'Orange', value: '#FF9800' }, + { name: 'Red', value: '#F44336' }, + { name: 'Purple', value: '#9C27B0' }, + { name: 'Teal', value: '#009688' }, + { name: 'Pink', value: '#E91E63' } + ]; constructor( private _tableState: TableStateService, @@ -492,6 +505,22 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.currentFolder = null; } + + changeFolderIconColor(color: string) { + if (this.currentFolder) { + this.currentFolder.iconColor = color; + this.saveFolders(); + } + } + + getFolderIconColor(folder: Folder, isActive?: boolean): string { + if (isActive) { + return '#212121'; // Black for active folders + } + return folder.iconColor || '#212121'; // Default color + } + + get allTables(): TableProperties[] { return this.tables; } From b2ed8ee2c7783fb0791943140457ba651189911b Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Thu, 2 Oct 2025 17:09:01 +0300 Subject: [PATCH 18/29] =?UTF-8?q?feat:=20add=20empty=20folder=20styling=20?= =?UTF-8?q?and=20enhance=20folder=20state=20management=20in=20dashboard=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F:?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D1=82=D1=8B=D1=85=20=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA=20=D0=BD=D0=B0=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 76 ++++++++++++++++--- .../db-tables-list.component.html | 22 +++--- .../db-tables-list.component.ts | 57 ++++++++++++-- 3 files changed, 130 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index bb4ad39ea..023b0bd81 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -75,7 +75,6 @@ color: #212121; } - .collapsed-folder-item_active:hover { background-color: rgba(33, 33, 33, 0.15); } @@ -210,21 +209,30 @@ background-color: rgba(255, 255, 255, 0.05); } - .collapsed-folder-item .collapsed-folder-icon { - color: #ffffff; - } .collapsed-folder-item_active { - background-color: #212121; - color: #ffffff; + background-color: #212121 !important; + color: #ffffff !important; + } + + /* Более специфичный селектор для гарантии */ + .collapsed-folder-item.collapsed-folder-item_active { + background-color: #212121 !important; } .collapsed-folder-item_active .collapsed-folder-icon { color: #ffffff !important; } - .collapsed-folder-item_active:hover { - background-color: #000000; + .collapsed-folder-item_active:hover, + .collapsed-folder-item_active:focus, + .collapsed-folder-item_active:active { + background-color: #000000 !important; + } + + /* Гарантируем фон для активной папки во всех состояниях */ + .collapsed-folder-item_active:not(:hover):not(:focus):not(:active) { + background-color: #212121 !important; } .collapsed-table-item { @@ -315,7 +323,7 @@ justify-content: center; width: 32px; height: 32px; - background-color: #e0e0e0; + background-color: var(--color-primaryPalette-50); border-radius: 6px; transition: all 0.2s ease; } @@ -337,7 +345,7 @@ .collapsed-table-icon-container .collapsed-table-icon { font-size: 16px; - color: #666; + color: var(--color-primaryPalette-50-contrast); } .collapsed-table-item_active .collapsed-table-icon-container .collapsed-table-icon { @@ -444,6 +452,19 @@ background-color: var(--color-whitePalette-900); color: var(--color-whitePalette-900-contrast); } + + .folder-header.expanded { + background-color: #212121; + color: #ffffff; + } + + .folder-header.expanded .folder-icon { + color: #ffffff; + } + + .folder-header.expanded .folder-expand-icon { + color: #ffffff; + } } .table-list-initials:hover { @@ -675,7 +696,8 @@ width: 20px; height: 20px; color: #666; - margin-right: 16px; /* Changed from 8px to 16px (8px + 8px) */ + margin-right: 8px; /* Уменьшаем отступ до названия папки до 8px */ + margin-left: 8px; /* Сдвигаем иконку вправо на 8px */ flex-shrink: 0; } @@ -843,6 +865,38 @@ color: #757575 !important; } +/* Styles for empty folders */ +.empty-folder-message { + padding: 30px 20px; +} + +.empty-folder-text { + color: #999; + font-style: italic; + margin-bottom: 16px; +} + +.empty-folder-button { + border-style: dashed !important; + border-width: 2px !important; + border-color: #ccc !important; + background-color: transparent !important; + color: #666 !important; + font-size: 14px !important; + min-width: 200px !important; + padding: 12px 16px !important; +} + +.empty-folder-button:hover { + border-color: #2196F3 !important; + color: #2196F3 !important; + background-color: rgba(33, 150, 243, 0.04) !important; +} + +.empty-folder-button mat-icon { + margin-right: 8px; +} + /* Add Table Dialog Styles */ .edit-tables-dialog { position: fixed; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index cede6578e..95f5d45d2 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -25,16 +25,16 @@ matTooltipPosition="right" (click)="onCollapsedFolderClick(folder)"> dehaze folder
dehaze @@ -101,10 +101,10 @@ (click)="toggleFolder(folder.id)"> dehaze folder
-
-

No tables in this folder

-
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 667b57da3..c69a0b40d 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -23,6 +23,7 @@ export interface Folder { editing: boolean; tableIds: string[]; iconColor?: string; // Optional color for folder icon + isEmpty?: boolean; // Flag to indicate if folder is newly created and empty } @Component({ @@ -302,7 +303,8 @@ export class DbTablesListComponent implements OnInit, OnChanges { name: `Folder ${this.folders.length}`, expanded: false, editing: false, - tableIds: [] + tableIds: [], + isEmpty: true // Mark as empty for special styling }; this.folders.push(newFolder); this.saveFolders(); @@ -517,7 +519,15 @@ export class DbTablesListComponent implements OnInit, OnChanges { if (isActive) { return '#212121'; // Black for active folders } - return folder.iconColor || '#212121'; // Default color + + // Check if we're in dark theme + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + // In dark theme, use white for folders without custom color + return folder.iconColor || '#ffffff'; + } + + // In light theme, use default color + return folder.iconColor || '#212121'; } @@ -548,6 +558,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { } else { // Add to folder this.currentFolder.tableIds.push(tableId); + // Remove empty flag when adding tables + if (this.currentFolder.isEmpty) { + this.currentFolder.isEmpty = false; + } } this.saveFolders(); @@ -570,9 +584,21 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.preservedFolderStates[folder.id] = folder.expanded; }); - // Find and save the currently active folder (the one that contains selected table) - const activeFolder = this.findActiveFolder(); - this.preservedActiveFolder = activeFolder ? activeFolder.id : null; + // Check if there are only "All Tables" folder (no custom folders) + const hasCustomFolders = this.folders.some(folder => folder.name !== 'All Tables'); + + // If no custom folders exist, ensure "All Tables" is always expanded + if (!hasCustomFolders) { + const allTablesFolder = this.folders.find(folder => folder.name === 'All Tables'); + if (allTablesFolder) { + this.preservedFolderStates[allTablesFolder.id] = true; + this.preservedActiveFolder = allTablesFolder.id; + } + } else { + // Find and save the currently active folder (the one that contains selected table) + const activeFolder = this.findActiveFolder(); + this.preservedActiveFolder = activeFolder ? activeFolder.id : null; + } console.log('Preserved folder states:', this.preservedFolderStates); console.log('Preserved active folder:', this.preservedActiveFolder); @@ -580,6 +606,23 @@ export class DbTablesListComponent implements OnInit, OnChanges { } private restoreFolderStates() { + // Check if there are only "All Tables" folder (no custom folders) + const hasCustomFolders = this.folders.some(folder => folder.name !== 'All Tables'); + + // If no custom folders exist, always expand "All Tables" + if (!hasCustomFolders) { + const allTablesFolder = this.folders.find(folder => folder.name === 'All Tables'); + if (allTablesFolder) { + allTablesFolder.expanded = true; + this.currentCollapsedFolder = allTablesFolder; + this.showCollapsedTableList = true; + console.log('No custom folders - keeping All Tables expanded'); + // Save the collapsed menu state after restoration + this.saveCollapsedMenuState(); + return; + } + } + // Restore expanded states of all folders this.folders.forEach(folder => { if (this.preservedFolderStates.hasOwnProperty(folder.id)) { @@ -714,6 +757,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { } else { // Simply add table to the target folder (don't remove from other folders) folder.tableIds.push(this.draggedTable.table); + // Remove empty flag when adding tables via drag and drop + if (folder.isEmpty) { + folder.isEmpty = false; + } this.saveFolders(); } } From 8de6edffa6f342fa71f0002d1f50ee8514c8e1b6 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 3 Oct 2025 11:02:41 +0300 Subject: [PATCH 19/29] =?UTF-8?q?feat:=20enhance=20edit=20tables=20dialog?= =?UTF-8?q?=20with=20close=20button=20and=20keyboard=20accessibility=20in?= =?UTF-8?q?=20dashboard=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D1=8F:=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B4=D0=B8=D0=B0=D0=BB=D0=BE=D0=B3=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=20?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=BE=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= =?UTF-8?q?=D1=8E=20=D1=81=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=89=D1=8C=D1=8E=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D0=B2=D0=B8=D0=B0=D1=82=D1=83=D1=80=D1=8B=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 25 +++++++++++++++ .../db-tables-list.component.html | 9 +++++- .../db-tables-list.component.ts | 31 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 023b0bd81..1bc23765e 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -909,6 +909,10 @@ align-items: center; justify-content: center; z-index: 1000; + opacity: 1; + visibility: visible; + transition: opacity 0.2s ease, visibility 0.2s ease; + outline: none; } .dialog-content { @@ -920,6 +924,9 @@ display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); + transform: scale(1); + transition: transform 0.2s ease; + position: relative; } .dialog-header { @@ -936,6 +943,24 @@ font-weight: 500; } +.dialog-header .close-button { + color: #666; + width: 32px; + height: 32px; + line-height: 32px; +} + +.dialog-header .close-button:hover { + color: #333; + background-color: rgba(0, 0, 0, 0.04); +} + +.dialog-header .close-button mat-icon { + font-size: 20px; + width: 20px; + height: 20px; +} + .dialog-body { padding: 16px 20px; max-height: 300px; diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 95f5d45d2..5700cdeab 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -206,10 +206,17 @@ -
+

Edit Tables in {{currentFolder?.name}}

+
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index c69a0b40d..3f107f379 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -451,6 +451,20 @@ export class DbTablesListComponent implements OnInit, OnChanges { } } + @HostListener('document:keydown', ['$event']) + onDocumentKeyDown(event: KeyboardEvent) { + // Handle Escape key for closing dialogs + if (event.key === 'Escape') { + if (this.showEditTablesDialogFlag) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + this.closeEditTablesDialog(); + return false; + } + } + } + cancelEditFolder(folder: Folder) { folder.editing = false; folder.name = this.editingFolderName; @@ -497,14 +511,31 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Expand the folder if it's collapsed folder.expanded = true; this.showEditTablesDialogFlag = true; + + // Focus the dialog after it's rendered + setTimeout(() => { + const dialogElement = document.querySelector('.edit-tables-dialog') as HTMLElement; + if (dialogElement) { + dialogElement.focus(); + } + }, 100); } closeEditTablesDialog(event?: Event) { if (event) { + event.preventDefault(); event.stopPropagation(); + event.stopImmediatePropagation(); } + + // Ensure dialog is properly closed this.showEditTablesDialogFlag = false; this.currentFolder = null; + + // Force change detection to update the UI immediately + setTimeout(() => { + this.showEditTablesDialogFlag = false; + }, 0); } From dc5ec0aaf55178b5590bdfe158fd0c08473ecbc3 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 3 Oct 2025 11:37:45 +0300 Subject: [PATCH 20/29] =?UTF-8?q?feat:=20improve=20dialog=20close=20behavi?= =?UTF-8?q?or=20by=20distinguishing=20between=20inside=20and=20outside=20c?= =?UTF-8?q?licks=20in=20dashboard=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8F:=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=B8=D0=B0=D0=BB=D0=BE=D0=B3=D0=B0,=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BB=D0=B8=D1=87=D0=B0=D1=8F=20=D0=BA=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BD=D1=83=D1=82=D1=80=D0=B8=20=D0=B8=20=D1=81?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D1=83=D0=B6=D0=B8=20=D0=B2=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.html | 2 +- .../db-tables-list.component.ts | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 5700cdeab..cdf2041c8 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -209,7 +209,7 @@
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 3f107f379..ac389b132 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -455,7 +455,18 @@ export class DbTablesListComponent implements OnInit, OnChanges { onDocumentKeyDown(event: KeyboardEvent) { // Handle Escape key for closing dialogs if (event.key === 'Escape') { + // Check if the event originated from inside the dialog + const target = event.target as Element; + const isInsideDialog = target && target.closest('.edit-tables-dialog'); + + if (this.showEditTablesDialogFlag && isInsideDialog) { + // If Escape was pressed inside dialog, let the dialog handle it + // Don't do anything here - the dialog's own handler will take care of it + return; + } + if (this.showEditTablesDialogFlag) { + // Escape pressed outside dialog - close it normally event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); @@ -538,6 +549,25 @@ export class DbTablesListComponent implements OnInit, OnChanges { }, 0); } + closeEditTablesDialogOnly(event?: Event) { + // This method ONLY closes the dialog, without affecting the sidebar + if (event) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + event.stopImmediatePropagation(); // Double prevention for safety + } + + // Close only the dialog + this.showEditTablesDialogFlag = false; + // Don't reset currentFolder to preserve sidebar state + + // Force change detection to update the UI immediately + setTimeout(() => { + this.showEditTablesDialogFlag = false; + }, 0); + } + changeFolderIconColor(color: string) { if (this.currentFolder) { From 4ea7db6b3d2fe6b049d1677da5cec7852f49906f Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Fri, 3 Oct 2025 13:33:46 +0300 Subject: [PATCH 21/29] =?UTF-8?q?feat:=20update=20styles=20for=20empty=20f?= =?UTF-8?q?olders=20and=20improve=20sidebar=20menu=20alignment=20in=20dash?= =?UTF-8?q?board=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D1=82=D1=8B=D1=85=20=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B2=D1=8B=D1=80=D0=B0=D0=B2=D0=BD=D0=B8=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=B1=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B9=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-tables-list.component.css | 47 ++++++++++++++----- .../db-tables-list.component.html | 2 - .../db-tables-list.component.ts | 6 +-- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 1bc23765e..5a477b6bd 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -3,7 +3,8 @@ max-width: 240px; min-width: 240px; display: block; - overflow: visible; + overflow-x: hidden; + overflow-y: auto; position: relative; } @@ -11,6 +12,14 @@ width: 65px; max-width: 65px; min-width: 65px; + overflow-x: hidden; + overflow-y: auto; +} + +/* Prevent horizontal scroll in sidebar */ +:host * { + max-width: 100%; + box-sizing: border-box; } @@ -866,15 +875,12 @@ } /* Styles for empty folders */ -.empty-folder-message { - padding: 30px 20px; +.no-tables-message.empty-folder-message { + padding: 12px 20px; + height: auto; + min-height: auto; } -.empty-folder-text { - color: #999; - font-style: italic; - margin-bottom: 16px; -} .empty-folder-button { border-style: dashed !important; @@ -884,7 +890,7 @@ color: #666 !important; font-size: 14px !important; min-width: 200px !important; - padding: 12px 16px !important; + padding: 20px 16px !important; } .empty-folder-button:hover { @@ -893,8 +899,27 @@ background-color: rgba(33, 150, 243, 0.04) !important; } -.empty-folder-button mat-icon { - margin-right: 8px; +/* Folder menu alignment */ +::ng-deep .mat-menu-panel .mat-menu-item { + display: flex !important; + align-items: center !important; + gap: 12px !important; + min-height: 48px !important; +} + +::ng-deep .mat-menu-panel .mat-menu-item .material-icons-outlined { + font-size: 20px !important; + width: 20px !important; + height: 20px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + line-height: 1 !important; +} + +::ng-deep .mat-menu-panel .mat-menu-item span { + display: flex !important; + align-items: center !important; } /* Add Table Dialog Styles */ diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index cdf2041c8..521876564 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -177,12 +177,10 @@

No tables in this folder

-

This folder is empty

diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index ac389b132..069155396 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -301,7 +301,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { const newFolder: Folder = { id: this.generateFolderId(), name: `Folder ${this.folders.length}`, - expanded: false, + expanded: true, // Разворачиваем папку сразу после создания editing: false, tableIds: [], isEmpty: true // Mark as empty for special styling @@ -583,8 +583,8 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Check if we're in dark theme if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - // In dark theme, use white for folders without custom color - return folder.iconColor || '#ffffff'; + // In dark theme, use #212121 for folders without custom color + return folder.iconColor || '#212121'; } // In light theme, use default color From 1836a2a4c1d2b881fa1eedf5a0fb27240a0b7e19 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 21 Oct 2025 16:52:45 +0300 Subject: [PATCH 22/29] tables list: fix code indents --- .../db-tables-list.component.html | 186 +++++++++--------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 521876564..a133d15e8 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -3,51 +3,51 @@
- +
-
+
search
- +
-
- dehaze - folder +
+ dehaze + folder
- dehaze - + dehaze + - +
-
+
add
- +
-
+ class="folder-item" + [class.expanded]="folder.expanded" + [class.drag-over]="dragOverFolder === folder.id" + (dragover)="onFolderDragOver($event, folder.id)" + (dragleave)="onFolderDragLeave($event, folder.id)" + (drop)="onFolderDrop($event, folder)"> +
- dehaze - folder - -
+ dehaze + folder + +
{{folder.name}}
- +
- - + chevron_right @@ -145,42 +145,42 @@
+ [(ngModel)]="folder.name" + (blur)="finishEditFolder(folder)" + (keyup.enter)="finishEditFolder(folder)" + (keyup.escape)="cancelEditFolder(folder)" + #folderNameInput + (click)="$event.stopPropagation()" + (focus)="folderNameInput.select()"> keyboard_return
-
-
- +

No tables in this folder

-
@@ -204,8 +204,8 @@
-
@@ -221,7 +221,7 @@

Edit Tables in {{currentFolder?.name}}

Folder Icon Color

-
- +

Manage tables in this folder

{{getTableName(table)}} -
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 069155396..19e51cae1 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,6 +1,8 @@ -import { Component, HostListener, Input, OnChanges, OnInit, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { TableProperties, TableSettings } from 'src/app/models/table'; import { CommonModule } from '@angular/common'; +import { ConnectionsService } from 'src/app/services/connections.service'; import { ContentLoaderComponent } from '../../ui-components/content-loader/content-loader.component'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -9,12 +11,14 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; -import { MatTooltipModule } from '@angular/material/tooltip'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; -import { TableProperties, TableSettings } from 'src/app/models/table'; +import { TableCategory } from 'src/app/models/connection'; import { TableStateService } from 'src/app/services/table-state.service'; import { TablesService } from 'src/app/services/tables.service'; +import { UiSettingsService } from 'src/app/services/ui-settings.service'; +import { expand } from 'rxjs'; export interface Folder { id: string; @@ -51,33 +55,34 @@ export class DbTablesListComponent implements OnInit, OnChanges { @Input() tables: TableProperties[]; @Input() selectedTable: string; @Input() collapsed: boolean; - + @Output() expandSidebar = new EventEmitter(); + public tableCategories: TableCategory[] = []; public substringToSearch: string; public foundTables: TableProperties[]; public folders: Folder[] = []; private editingFolderName: string = ''; - + // Dialog state public showEditTablesDialogFlag: boolean = false; public currentFolder: Folder | null = null; - + // Drag and drop state public draggedTable: TableProperties | null = null; public dragOverFolder: string | null = null; - + // Collapsed state public showCollapsedTableList: boolean = false; public currentCollapsedFolder: Folder | null = null; - + // State preservation private preservedFolderStates: { [key: string]: boolean } = {}; private preservedActiveFolder: string | null = null; - + // Table icons cache private tableIcons: { [key: string]: string } = {}; - + // Folder icon colors public folderIconColors = [ { name: 'Default', value: '#212121' }, @@ -93,13 +98,14 @@ export class DbTablesListComponent implements OnInit, OnChanges { constructor( private _tableState: TableStateService, private _tablesService: TablesService, + private _connectionsService: ConnectionsService, + private _uiSettingsService: UiSettingsService ) { } ngOnInit() { this.foundTables = this.tables; this.loadFolders(); - this.loadTableIcons(); - this.loadCollapsedMenuState(); + this.loadAndSetExpandedFolders(); console.log('ngOnInit - showCollapsedTableList initialized to:', this.showCollapsedTableList); } @@ -126,10 +132,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { } const searchTerm = this.substringToSearch.toLowerCase(); - + // Get all tables that match the search (including those in folders) const allTables = [...this.tables]; - + // Add tables from folders that might not be in the main tables list this.folders.forEach(folder => { folder.tableIds.forEach(tableId => { @@ -142,24 +148,24 @@ export class DbTablesListComponent implements OnInit, OnChanges { }); // Filter all tables by search term - this.foundTables = allTables.filter(tableItem => - tableItem.table.toLowerCase().includes(searchTerm) || + this.foundTables = allTables.filter(tableItem => + tableItem.table.toLowerCase().includes(searchTerm) || (tableItem.display_name && tableItem.display_name.toLowerCase().includes(searchTerm)) || (tableItem.normalizedTableName && tableItem.normalizedTableName.toLowerCase().includes(searchTerm)) ); // Remove duplicates - this.foundTables = this.foundTables.filter((table, index, self) => + this.foundTables = this.foundTables.filter((table, index, self) => index === self.findIndex(t => t.table === table.table) ); // Expand all folders that contain matching tables this.folders.forEach(folder => { const folderTables = this.getFolderTables(folder); - const hasMatchingTables = folderTables.some(table => + const hasMatchingTables = folderTables.some(table => this.foundTables.some(foundTable => foundTable.table === table.table) ); - + if (hasMatchingTables) { folder.expanded = true; } else { @@ -172,115 +178,27 @@ export class DbTablesListComponent implements OnInit, OnChanges { return table.display_name || table.normalizedTableName || table.table } - getTableInitials(table: TableProperties) { - const name = this.getTableName(table); - - // Remove common prefixes and suffixes for better initials - let cleanName = name - .replace(/^(tbl_|table_|tb_)/i, '') // Remove table prefixes - .replace(/_/g, ' ') // Replace underscores with spaces - .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase - .trim(); - - // Split into words and take first letters - const words = cleanName.split(/\s+/).filter(word => word.length > 0); - - if (words.length >= 2) { - // Take first letter of first two words - return (words[0].charAt(0) + words[1].charAt(0)).toUpperCase(); - } else if (words.length === 1) { - // For single word, take first two characters - const word = words[0]; - if (word.length >= 2) { - return word.substring(0, 2).toUpperCase(); - } - return word.toUpperCase(); - } - - // Fallback to original logic - if (name.length >= 2) { - return name.substring(0, 2).toUpperCase(); - } - return name.toUpperCase(); - } - - getTableIcon(table: TableProperties): string | null { - const tableKey = `${this.connectionID}_${table.table}`; - - // Return cached icon if available - if (this.tableIcons[tableKey]) { - return this.tableIcons[tableKey]; - } - - // Return null if no custom icon is set (will show initials instead) - return null; - } - - hasTableIcon(table: TableProperties): boolean { - const tableKey = `${this.connectionID}_${table.table}`; - return !!this.tableIcons[tableKey]; - } - - loadTableIcon(table: TableProperties) { - const tableKey = `${this.connectionID}_${table.table}`; - - // Don't load if already cached - if (this.tableIcons[tableKey]) { - return; - } - - // Fetch table settings to get icon - this._tablesService.fetchTableSettings(this.connectionID, table.table).subscribe({ - next: (settings: TableSettings) => { - if (settings && settings.icon) { - this.tableIcons[tableKey] = settings.icon; - } - }, - error: (error) => { - // Silently fail and use default icon - console.log(`Could not load icon for table ${table.table}:`, error); + // saveCollapsedMenuState() { + // const state = { + // showCollapsedTableList: this.showCollapsedTableList, + // currentCollapsedFolderId: this.currentCollapsedFolder?.id || null + // }; + // const key = `collapsedMenuState_${this.connectionID}`; + // localStorage.setItem(key, JSON.stringify(state)); + // console.log('Collapsed menu state saved:', state); + // } + + loadAndSetExpandedFolders() { + this._uiSettingsService.getUiSettings().subscribe(settings => { + const expandedFolders = settings?.connections?.[this.connectionID].tableFoldersExpanded; + if (expandedFolders && expandedFolders.length > 0) { + this.folders.forEach(folder => { + folder.expanded = expandedFolders.includes(folder.id); + }); } }); } - loadTableIcons() { - // Load icons for all tables - if (this.tables) { - this.tables.forEach(table => { - this.loadTableIcon(table); - }); - } - } - - saveCollapsedMenuState() { - const state = { - showCollapsedTableList: this.showCollapsedTableList, - currentCollapsedFolderId: this.currentCollapsedFolder?.id || null - }; - const key = `collapsedMenuState_${this.connectionID}`; - localStorage.setItem(key, JSON.stringify(state)); - console.log('Collapsed menu state saved:', state); - } - - loadCollapsedMenuState() { - const key = `collapsedMenuState_${this.connectionID}`; - const saved = localStorage.getItem(key); - if (saved) { - try { - const state = JSON.parse(saved); - this.showCollapsedTableList = state.showCollapsedTableList || false; - - if (state.currentCollapsedFolderId) { - this.currentCollapsedFolder = this.folders.find(f => f.id === state.currentCollapsedFolderId) || null; - } - - console.log('Collapsed menu state loaded:', state); - } catch (e) { - console.error('Error loading collapsed menu state:', e); - } - } - } - getTableNameLength(tableName: string) { return tableName.length; } @@ -315,12 +233,14 @@ export class DbTablesListComponent implements OnInit, OnChanges { const folder = this.folders.find(f => f.id === folderId); if (folder) { folder.expanded = !folder.expanded; + const expandedFolders = this.folders.filter(f => f.expanded).map(f => f.id); + this._uiSettingsService.updateConnectionSetting(this.connectionID, 'tableFoldersExpanded', expandedFolders); } } onCollapsedFolderClick(folder: Folder) { console.log('Clicked on folder:', folder.name); - + // If clicking on the same folder that's already open, close it if (this.currentCollapsedFolder?.id === folder.id) { this.showCollapsedTableList = false; @@ -330,10 +250,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.showCollapsedTableList = true; this.currentCollapsedFolder = folder; } - + // Save the collapsed menu state - this.saveCollapsedMenuState(); - + // this.saveCollapsedMenuState(); + console.log('showCollapsedTableList is now:', this.showCollapsedTableList); console.log('currentCollapsedFolder is now:', this.currentCollapsedFolder?.name); } @@ -343,7 +263,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { console.log('No current collapsed folder'); return []; } - + const tables = this.getFolderTables(this.currentCollapsedFolder); console.log('getCollapsedTableList - tables:', tables); return tables; @@ -378,7 +298,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { folder.editing = true; this.editingFolderName = folder.name; console.log('Folder editing state:', folder.editing); - + // Focus and select the input after the view updates setTimeout(() => { const input = document.querySelector('.folder-name.editing') as HTMLInputElement; @@ -411,7 +331,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { if (folder.name !== 'All Tables') { event.stopPropagation(); this.startEditFolder(folder); - + // Close any open menus after starting edit setTimeout(() => { const menus = document.querySelectorAll('.mat-mdc-menu-content'); @@ -441,7 +361,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { const target = event.target as HTMLElement; const isEditingInput = target.closest('.collection-name-edit-container'); const isEditButton = target.closest('.edit-collection-button'); - + if (!isEditingInput && !isEditButton) { // Find any collection that is currently being edited const editingFolder = this.folders.find(c => c.editing); @@ -458,13 +378,13 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Check if the event originated from inside the dialog const target = event.target as Element; const isInsideDialog = target && target.closest('.edit-tables-dialog'); - + if (this.showEditTablesDialogFlag && isInsideDialog) { // If Escape was pressed inside dialog, let the dialog handle it // Don't do anything here - the dialog's own handler will take care of it return; } - + if (this.showEditTablesDialogFlag) { // Escape pressed outside dialog - close it normally event.preventDefault(); @@ -483,17 +403,17 @@ export class DbTablesListComponent implements OnInit, OnChanges { getFolderTables(folder: Folder): TableProperties[] { const folderTables = this.tables.filter(table => folder.tableIds.includes(table.table)); - + // If there's a search term, filter the collection tables too if (this.substringToSearch && this.substringToSearch.trim() !== '') { const searchTerm = this.substringToSearch.toLowerCase(); - return folderTables.filter(table => - table.table.toLowerCase().includes(searchTerm) || + return folderTables.filter(table => + table.table.toLowerCase().includes(searchTerm) || (table.display_name && table.display_name.toLowerCase().includes(searchTerm)) || (table.normalizedTableName && table.normalizedTableName.toLowerCase().includes(searchTerm)) ); } - + return folderTables; } @@ -511,7 +431,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { if (!this.substringToSearch || this.substringToSearch.trim() === '') { return true; } - + // Show collection if it has tables matching the search const matchingTables = this.getFolderTables(folder); return matchingTables.length > 0; @@ -522,7 +442,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { // Expand the folder if it's collapsed folder.expanded = true; this.showEditTablesDialogFlag = true; - + // Focus the dialog after it's rendered setTimeout(() => { const dialogElement = document.querySelector('.edit-tables-dialog') as HTMLElement; @@ -538,11 +458,11 @@ export class DbTablesListComponent implements OnInit, OnChanges { event.stopPropagation(); event.stopImmediatePropagation(); } - + // Ensure dialog is properly closed this.showEditTablesDialogFlag = false; this.currentFolder = null; - + // Force change detection to update the UI immediately setTimeout(() => { this.showEditTablesDialogFlag = false; @@ -557,11 +477,11 @@ export class DbTablesListComponent implements OnInit, OnChanges { event.stopImmediatePropagation(); event.stopImmediatePropagation(); // Double prevention for safety } - + // Close only the dialog this.showEditTablesDialogFlag = false; // Don't reset currentFolder to preserve sidebar state - + // Force change detection to update the UI immediately setTimeout(() => { this.showEditTablesDialogFlag = false; @@ -580,13 +500,13 @@ export class DbTablesListComponent implements OnInit, OnChanges { if (isActive) { return '#212121'; // Black for active folders } - + // Check if we're in dark theme if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { // In dark theme, use #212121 for folders without custom color return folder.iconColor || '#212121'; } - + // In light theme, use default color return folder.iconColor || '#212121'; } @@ -601,8 +521,8 @@ export class DbTablesListComponent implements OnInit, OnChanges { } isTableInCurrentDraggedFolder(table: TableProperties, folder: Folder): boolean { - return this.draggedTable && - this.draggedTable.table === table.table && + return this.draggedTable && + this.draggedTable.table === table.table && folder.tableIds.includes(table.table); } @@ -644,10 +564,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.folders.forEach(folder => { this.preservedFolderStates[folder.id] = folder.expanded; }); - + // Check if there are only "All Tables" folder (no custom folders) const hasCustomFolders = this.folders.some(folder => folder.name !== 'All Tables'); - + // If no custom folders exist, ensure "All Tables" is always expanded if (!hasCustomFolders) { const allTablesFolder = this.folders.find(folder => folder.name === 'All Tables'); @@ -660,7 +580,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { const activeFolder = this.findActiveFolder(); this.preservedActiveFolder = activeFolder ? activeFolder.id : null; } - + console.log('Preserved folder states:', this.preservedFolderStates); console.log('Preserved active folder:', this.preservedActiveFolder); console.log('Expanded folders count:', Object.values(this.preservedFolderStates).filter(expanded => expanded).length); @@ -669,7 +589,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { private restoreFolderStates() { // Check if there are only "All Tables" folder (no custom folders) const hasCustomFolders = this.folders.some(folder => folder.name !== 'All Tables'); - + // If no custom folders exist, always expand "All Tables" if (!hasCustomFolders) { const allTablesFolder = this.folders.find(folder => folder.name === 'All Tables'); @@ -679,51 +599,51 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.showCollapsedTableList = true; console.log('No custom folders - keeping All Tables expanded'); // Save the collapsed menu state after restoration - this.saveCollapsedMenuState(); + // this.saveCollapsedMenuState(); return; } } - + // Restore expanded states of all folders this.folders.forEach(folder => { if (this.preservedFolderStates.hasOwnProperty(folder.id)) { folder.expanded = this.preservedFolderStates[folder.id]; } }); - + // In collapsed view, show the table list if any folder was expanded const hasExpandedFolders = Object.values(this.preservedFolderStates).some(expanded => expanded); if (hasExpandedFolders) { // If there was an active folder, use it; otherwise use the first expanded folder let targetFolder = null; - + if (this.preservedActiveFolder) { targetFolder = this.folders.find(f => f.id === this.preservedActiveFolder); } - + if (!targetFolder) { // Find the first expanded folder targetFolder = this.folders.find(f => this.preservedFolderStates[f.id]); } - + if (targetFolder) { this.currentCollapsedFolder = targetFolder; this.showCollapsedTableList = true; } } - + console.log('Restored folder states:', this.preservedFolderStates); console.log('Restored active folder:', this.preservedActiveFolder); console.log('Has expanded folders:', hasExpandedFolders); - + // Save the collapsed menu state after restoration - this.saveCollapsedMenuState(); + // this.saveCollapsedMenuState(); } private findActiveFolder(): Folder | null { if (!this.selectedTable) return null; - - return this.folders.find(folder => + + return this.folders.find(folder => folder.tableIds.includes(this.selectedTable) ) || null; } @@ -748,7 +668,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { } else { console.log('No saved folders found for key:', key); } - + // Create "All Tables" collection if it doesn't exist const allTablesFolder = this.folders.find(c => c.name === 'All Tables'); if (!allTablesFolder) { @@ -769,6 +689,18 @@ export class DbTablesListComponent implements OnInit, OnChanges { const key = `folders_${this.connectionID}`; localStorage.setItem(key, JSON.stringify(this.folders)); console.log('Folders saved:', this.folders.map(c => ({ name: c.name, expanded: c.expanded }))); + this.tableCategories = this.folders.map(folder => ({ + category_name: folder.name, + tables: folder.tableIds + })); + // this._connectionsService.updateConnectionSettings(this.connectionID, { table_categories: this.tableCategories, hidden_tables: null }).subscribe({ + // next: () => { + // console.log('Connection settings updated with folders.'); + // }, + // error: (error) => { + // console.error('Error updating connection settings with folders:', error); + // } + // }); } catch (e) { console.error('Error saving folders:', e); } @@ -801,7 +733,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { const rect = (event.currentTarget as HTMLElement).getBoundingClientRect(); const x = event.clientX; const y = event.clientY; - + if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) { this.dragOverFolder = null; } @@ -809,7 +741,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { onFolderDrop(event: DragEvent, folder: Folder) { event.preventDefault(); - + if (this.draggedTable) { // Check if table is already in this specific folder if (folder.tableIds.includes(this.draggedTable.table)) { @@ -825,7 +757,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.saveFolders(); } } - + this.draggedTable = null; this.dragOverFolder = null; } @@ -836,7 +768,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { const notification = document.createElement('div'); notification.className = 'table-exists-notification'; notification.textContent = `"${tableName}" already exists in "${collectionName}"`; - + // Style the notification notification.style.cssText = ` position: fixed; @@ -851,7 +783,7 @@ export class DbTablesListComponent implements OnInit, OnChanges { box-shadow: 0 2px 8px rgba(0,0,0,0.2); animation: slideIn 0.3s ease; `; - + // Add animation styles const style = document.createElement('style'); style.textContent = ` @@ -865,10 +797,10 @@ export class DbTablesListComponent implements OnInit, OnChanges { } `; document.head.appendChild(style); - + // Add to DOM document.body.appendChild(notification); - + // Remove after 3 seconds setTimeout(() => { notification.style.animation = 'slideOut 0.3s ease'; diff --git a/frontend/src/app/models/connection.ts b/frontend/src/app/models/connection.ts index 4a022b1aa..02b099bda 100644 --- a/frontend/src/app/models/connection.ts +++ b/frontend/src/app/models/connection.ts @@ -55,12 +55,18 @@ export interface TestConnection { message: string } +export interface TableCategory { + category_name: string, + tables: string[] +} + export interface ConnectionSettings { hidden_tables?: string[], - default_showing_table: string, + default_showing_table?: string, primary_color?: string, secondary_color?: string, logo_url?: string, company_name?: string, tables_audit?: boolean, + table_categories?: TableCategory[] } \ No newline at end of file diff --git a/frontend/src/app/models/ui-settings.ts b/frontend/src/app/models/ui-settings.ts index 5e4ff662a..90c103377 100644 --- a/frontend/src/app/models/ui-settings.ts +++ b/frontend/src/app/models/ui-settings.ts @@ -10,6 +10,7 @@ export interface TableSettingsUI { export interface ConnectionSettingsUI { shownTableTitles: boolean; tables: { [tableName: string]: TableSettingsUI }; + tableFoldersExpanded?: string[]; } export interface UiSettings { From 103b24cd0c96690939a30e5500f48f8d1f61f97c Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Tue, 28 Oct 2025 14:29:18 +0200 Subject: [PATCH 24/29] table folders: - fix icons alignment in folder menu; - add get folders endpoint and apply expanded folders ui settings --- .../dashboard/dashboard.component.html | 1 + .../db-tables-list.component.html | 8 +- .../db-tables-list.component.ts | 237 +++++++++--------- frontend/src/app/models/connection.ts | 4 +- .../src/app/services/connections.service.ts | 31 +++ 5 files changed, 160 insertions(+), 121 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index b6a568168..d8f8b1b67 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -74,6 +74,7 @@

Rocketadmin can not find any tables

[tables]="tablesList" [connectionID]="connectionID" [selectedTable]="selectedTableName" + [uiSettings]="uiSettings" (expandSidebar)="toggleSideBar()"> diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index bc3b3d269..df157680a 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -129,15 +129,15 @@ @@ -234,7 +234,7 @@

Folder Icon Color

Manage tables in this folder

-
+
{{getTableName(table)}}
- -
- - keyboard_return -
-
f.editing = false); - folder.editing = true; - this.editingFolderName = folder.name; - console.log('Folder editing state:', folder.editing); - - // Focus and select the input after the view updates - setTimeout(() => { - const input = document.querySelector('.folder-name.editing') as HTMLInputElement; - if (input) { - input.focus(); - input.select(); - } - }, 0); - } - - finishEditFolder(folder: Folder) { - folder.editing = false; - if (folder.name.trim() === '') { - folder.name = this.editingFolderName; - } - console.log('finishEditFolder'); - this.saveFolders(); - } - deleteFolder(folder: Folder) { if (confirm(`Are you sure you want to delete the folder "${folder.name}"?`)) { const index = this.folders.findIndex(f => f.id === folder.id); @@ -314,25 +281,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { } } - onFolderNameDoubleClick(event: Event, folder: Folder) { - if (folder.name !== 'All Tables') { - event.stopPropagation(); - event.preventDefault(); - console.log('onFolderNameDoubleClick for:', folder.name); - this.startEditFolder(folder); - - // Close any open menus after starting edit - setTimeout(() => { - const menus = document.querySelectorAll('.mat-mdc-menu-content'); - menus.forEach(menu => { - const menuElement = menu as HTMLElement; - if (menuElement.style.display !== 'none') { - menuElement.style.display = 'none'; - } - }); - }, 0); - } - } onMenuClosed() { // Menu closed, no action needed @@ -344,21 +292,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { } } - @HostListener('document:click', ['$event']) - onDocumentClick(event: Event) { - // Check if click is outside any editing collection - const target = event.target as HTMLElement; - const isEditingInput = target.closest('.collection-name-edit-container'); - const isEditButton = target.closest('.edit-collection-button'); - - if (!isEditingInput && !isEditButton) { - // Find any collection that is currently being edited - const editingFolder = this.folders.find(c => c.editing); - if (editingFolder) { - this.finishEditFolder(editingFolder); - } - } - } @HostListener('document:keydown', ['$event']) onDocumentKeyDown(event: KeyboardEvent) { @@ -385,10 +318,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { } } - cancelEditFolder(folder: Folder) { - folder.editing = false; - folder.name = this.editingFolderName; - } getFolderTables(folder: Folder): TableProperties[] { const folderTables = this.tables.filter(table => folder.tableIds.includes(table.table)); @@ -652,7 +581,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { id: cat.category_id, name: cat.category_name, expanded: false, - editing: false, tableIds: cat.tables, iconColor: cat.category_color })); @@ -673,7 +601,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { id: '0', name: 'All Tables', expanded: expandedFolders && expandedFolders.length === 0 ? false : expandedFolders.includes('0'), - editing: false, tableIds: this.tables.map(table => table.table) }; this.folders.unshift(allTablesFolder); From 7d0f3a2af3d7538e22cec3af7d051d5c33af0ee6 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 30 Oct 2025 15:38:30 +0200 Subject: [PATCH 26/29] tables list: move edit table name to Table edit and fix UI --- .../db-folder-edit-dialog.component.css | 208 ++++++++++++++++++ .../db-folder-edit-dialog.component.html | 68 ++++++ .../db-folder-edit-dialog.component.ts | 116 ++++++++++ .../db-tables-list.component.html | 46 ---- .../db-tables-list.component.ts | 157 +++---------- 5 files changed, 417 insertions(+), 178 deletions(-) create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.css create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.ts diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.css new file mode 100644 index 000000000..ff3c148d5 --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.css @@ -0,0 +1,208 @@ +.dialog-body { + min-width: 400px; + max-height: 500px; + overflow-y: auto; +} + +.folder-name-section { + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid #e0e0e0; +} + +.folder-name-section h4 { + margin: 0 0 12px 0; + font-size: 14px; + font-weight: 500; + color: #666; +} + +.folder-name-field { + width: 100%; +} + +.folder-color-section { + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid #e0e0e0; +} + +.folder-color-section h4 { + margin: 0 0 12px 0; + font-size: 14px; + font-weight: 500; + color: #666; +} + +.color-radio-group { + width: 100%; +} + +.color-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + padding: 8px 0; +} + +.color-radio-label { + display: inline-block; + position: relative; + cursor: pointer; +} + +.color-radio-input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.color-swatch { + width: 40px; + height: 40px; + border-radius: 50%; + border: 2px solid transparent; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.color-radio-label:hover .color-swatch { + transform: scale(1.1); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2); +} + +.color-radio-label.selected .color-swatch { + /* border: 2px solid currentColor; */ + box-shadow: 0 0 0 3px rgba(33, 33, 33, 0.1), 0 3px 8px rgba(0, 0, 0, 0.2); + transform: scale(1.05); +} + +.color-radio-label.selected .color-swatch::after { + content: '✓'; + position: absolute; + /* font-size: 20px; */ + color: white; + /* text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); */ + /* font-weight: bold; */ +} + +.tables-section h4 { + margin: 0 0 12px 0; + font-size: 14px; + font-weight: 500; + color: #666; +} + +.table-list { + margin-top: 8px; +} + +.table-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 8px; + border-bottom: 1px solid #e0e0e0; + transition: background-color 0.2s ease; +} + +.table-item:hover { + background-color: rgba(0, 0, 0, 0.04); +} + +.table-item:last-child { + border-bottom: none; +} + +.table-name { + flex: 1; + font-size: 14px; + color: #333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 12px; +} + +.action-button { + min-width: 70px; + height: 32px; + font-size: 12px; + font-weight: 500; + border-radius: 4px; + transition: all 0.2s ease; +} + +.add-button { + color: #4caf50; + background-color: transparent; +} + +.add-button:hover { + background-color: rgba(76, 175, 80, 0.1); +} + +.remove-button { + color: #f44336; + background-color: transparent; +} + +.remove-button:hover { + background-color: rgba(244, 67, 54, 0.1); +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .folder-name-section { + border-bottom-color: #424242; + } + + .folder-name-section h4 { + color: #999; + } + + .folder-color-section { + border-bottom-color: #424242; + } + + .folder-color-section h4, + .tables-section h4 { + color: #999; + } + + .color-swatch { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + } + + .color-radio-label:hover .color-swatch { + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); + } + + .color-radio-label.selected .color-swatch { + border-color: currentColor; + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1), 0 3px 8px rgba(0, 0, 0, 0.4); + } + + .color-radio-label.selected .color-swatch::before { + border-color: currentColor; + opacity: 0.2; + } + + .table-item { + border-bottom-color: #424242; + } + + .table-item:hover { + background-color: rgba(255, 255, 255, 0.04); + } + + .table-name { + color: #ccc; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html new file mode 100644 index 000000000..9084b7cd9 --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html @@ -0,0 +1,68 @@ +

+ Edit Folder +

+ + + +
+

Folder Name

+ + + The "All Tables" folder cannot be renamed + + Folder name is required + + +
+ + +
+

Folder Icon Color

+
+
+ +
+
+
+ +
+

Manage tables in this folder

+
+
+ {{getTableName(table)}} + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.ts new file mode 100644 index 000000000..c1bc8370c --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.ts @@ -0,0 +1,116 @@ +import { Component, Inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { FormsModule } from '@angular/forms'; +import { TableProperties } from 'src/app/models/table'; + +export interface Folder { + id: string; + name: string; + expanded: boolean; + tableIds: string[]; + iconColor?: string; + isEmpty?: boolean; +} + +export interface DbFolderEditDialogData { + folder: Folder; + tables: TableProperties[]; + folderIconColors: { name: string; value: string }[]; +} + +@Component({ + selector: 'app-db-folder-edit-dialog', + templateUrl: './db-folder-edit-dialog.component.html', + styleUrls: ['./db-folder-edit-dialog.component.css'], + standalone: true, + imports: [ + CommonModule, + MatDialogModule, + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + FormsModule + ] +}) +export class DbFolderEditDialogComponent { + public folder: Folder; + public tables: TableProperties[]; + public folderIconColors: { name: string; value: string }[]; + private originalFolderName: string; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DbFolderEditDialogData + ) { + // Create a copy of the folder to work with + this.folder = { ...data.folder, tableIds: [...data.folder.tableIds] }; + // Store original name for validation + this.originalFolderName = data.folder.name; + // Set default color if not set + if (!this.folder.iconColor) { + this.folder.iconColor = '#757575'; + } + this.tables = data.tables; + this.folderIconColors = data.folderIconColors; + } + + getFolderIconColor(folder: Folder): string { + return folder.iconColor || '#757575'; + } + + onColorChange(color: string) { + console.log('Folder color changed to:', color); + } + + isTableInFolder(table: TableProperties): boolean { + return this.folder.tableIds.includes(table.table); + } + + getTableName(table: TableProperties): string { + return table.display_name || table.table; + } + + toggleTableInFolder(table: TableProperties) { + const tableId = table.table; + const isInFolder = this.folder.tableIds.includes(tableId); + + if (isInFolder) { + // Remove table from folder + this.folder.tableIds = this.folder.tableIds.filter(id => id !== tableId); + } else { + // Add table to folder + this.folder.tableIds.push(tableId); + // If this was an empty folder, mark it as no longer empty + if (this.folder.isEmpty) { + this.folder.isEmpty = false; + } + } + + console.log('toggleTableInFolder'); + } + + isAllTablesFolder(): boolean { + return this.folder.id === '0' || this.folder.name === 'All Tables'; + } + + onSave() { + // Validate folder name + if (!this.folder.name || this.folder.name.trim() === '') { + return; // Don't save if name is empty + } + + // Trim the folder name before saving + this.folder.name = this.folder.name.trim(); + this.dialogRef.close(this.folder); + } + + onCancel() { + this.dialogRef.close(); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index 2bb0b4338..d3c8cedaf 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -183,50 +183,4 @@

- -
-
-
-

Edit Tables in {{currentFolder?.name}}

- -
-
- -
-

Folder Icon Color

-
- -
-
- -
-

Manage tables in this folder

-
-
- {{getTableName(table)}} - -
-
-
-
-
-
diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index 7045beae5..e43f6970c 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,4 +1,6 @@ import { Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { DbFolderEditDialogComponent, DbFolderEditDialogData } from './db-folder-edit-dialog/db-folder-edit-dialog.component'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { TableProperties, TableSettings } from 'src/app/models/table'; import { CommonModule } from '@angular/common'; @@ -43,6 +45,7 @@ export interface Folder { MatListModule, MatTooltipModule, MatMenuModule, + MatDialogModule, RouterModule, ContentLoaderComponent ] @@ -63,7 +66,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { public folders: Folder[] = []; // Dialog state - public showEditTablesDialogFlag: boolean = false; public currentFolder: Folder | null = null; // Drag and drop state @@ -97,7 +99,8 @@ export class DbTablesListComponent implements OnInit, OnChanges { private _tableState: TableStateService, private _tablesService: TablesService, private _connectionsService: ConnectionsService, - private _uiSettingsService: UiSettingsService + private _uiSettingsService: UiSettingsService, + private dialog: MatDialog ) { } ngOnInit() { @@ -293,30 +296,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { } - @HostListener('document:keydown', ['$event']) - onDocumentKeyDown(event: KeyboardEvent) { - // Handle Escape key for closing dialogs - if (event.key === 'Escape') { - // Check if the event originated from inside the dialog - const target = event.target as Element; - const isInsideDialog = target && target.closest('.edit-tables-dialog'); - - if (this.showEditTablesDialogFlag && isInsideDialog) { - // If Escape was pressed inside dialog, let the dialog handle it - // Don't do anything here - the dialog's own handler will take care of it - return; - } - - if (this.showEditTablesDialogFlag) { - // Escape pressed outside dialog - close it normally - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - this.closeEditTablesDialog(); - return false; - } - } - } getFolderTables(folder: Folder): TableProperties[] { @@ -356,65 +335,34 @@ export class DbTablesListComponent implements OnInit, OnChanges { } showEditTablesDialog(folder: Folder) { - this.currentFolder = folder; // Expand the folder if it's collapsed folder.expanded = true; - this.showEditTablesDialogFlag = true; - - // Focus the dialog after it's rendered - setTimeout(() => { - const dialogElement = document.querySelector('.edit-tables-dialog') as HTMLElement; - if (dialogElement) { - dialogElement.focus(); - } - }, 100); - } - - closeEditTablesDialog(event?: Event) { - if (event) { - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - } - - // Ensure dialog is properly closed - this.showEditTablesDialogFlag = false; - this.currentFolder = null; - - // Force change detection to update the UI immediately - setTimeout(() => { - this.showEditTablesDialogFlag = false; - }, 0); - } - closeEditTablesDialogOnly(event?: Event) { - // This method ONLY closes the dialog, without affecting the sidebar - if (event) { - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - event.stopImmediatePropagation(); // Double prevention for safety - } + const dialogData: DbFolderEditDialogData = { + folder: folder, + tables: this.tables, + folderIconColors: this.folderIconColors + }; - // Close only the dialog - this.showEditTablesDialogFlag = false; - // Don't reset currentFolder to preserve sidebar state + const dialogRef = this.dialog.open(DbFolderEditDialogComponent, { + width: '32em', + data: dialogData, + panelClass: 'db-folder-edit-dialog' + }); - // Force change detection to update the UI immediately - setTimeout(() => { - this.showEditTablesDialogFlag = false; - }, 0); + dialogRef.afterClosed().subscribe((result: Folder | undefined) => { + if (result) { + // Update the folder with the result from dialog + const index = this.folders.findIndex(f => f.id === result.id); + if (index !== -1) { + this.folders[index] = result; + this.saveFolders(); + } + } + }); } - changeFolderIconColor(color: string) { - if (this.currentFolder) { - this.currentFolder.iconColor = color; - console.log('changeFolderIconColor', color); - this.saveFolders(); - } - } - getFolderIconColor(folder: Folder, isActive?: boolean): string { if (isActive) { return '#212121'; // Black for active folders @@ -608,8 +556,6 @@ export class DbTablesListComponent implements OnInit, OnChanges { error: (error) => { console.error('Error fetching folders from connection settings:', error); this.folders = []; - // Ensure "All Tables" collection exists even on error - // this.ensureAllTablesCollection(); } }); } @@ -693,57 +639,4 @@ export class DbTablesListComponent implements OnInit, OnChanges { this.draggedTable = null; this.dragOverFolder = null; } - - - // private showTableExistsNotification(collectionName: string, tableName: string) { - // // Create a temporary notification element - // const notification = document.createElement('div'); - // notification.className = 'table-exists-notification'; - // notification.textContent = `"${tableName}" already exists in "${collectionName}"`; - - // // Style the notification - // notification.style.cssText = ` - // position: fixed; - // top: 20px; - // right: 20px; - // background: #ff9800; - // color: white; - // padding: 12px 16px; - // border-radius: 4px; - // font-size: 14px; - // z-index: 10000; - // box-shadow: 0 2px 8px rgba(0,0,0,0.2); - // animation: slideIn 0.3s ease; - // `; - - // // Add animation styles - // const style = document.createElement('style'); - // style.textContent = ` - // @keyframes slideIn { - // from { transform: translateX(100%); opacity: 0; } - // to { transform: translateX(0); opacity: 1; } - // } - // @keyframes slideOut { - // from { transform: translateX(0); opacity: 1; } - // to { transform: translateX(100%); opacity: 0; } - // } - // `; - // document.head.appendChild(style); - - // // Add to DOM - // document.body.appendChild(notification); - - // // Remove after 3 seconds - // setTimeout(() => { - // notification.style.animation = 'slideOut 0.3s ease'; - // setTimeout(() => { - // if (notification.parentNode) { - // notification.parentNode.removeChild(notification); - // } - // if (style.parentNode) { - // style.parentNode.removeChild(style); - // } - // }, 300); - // }, 3000); - // } } From 7103024b92971cecd80240f4de8ea3df3ebf093c Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 30 Oct 2025 19:29:01 +0200 Subject: [PATCH 27/29] delete folder notification --- .../db-folder-delete-dialog.component.css | 124 ++++++++++++++++++ .../db-folder-delete-dialog.component.html | 35 +++++ .../db-folder-delete-dialog.component.ts | 37 ++++++ .../db-tables-list.component.ts | 26 +++- 4 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.css create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.html create mode 100644 frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.ts diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.css new file mode 100644 index 000000000..cc617fcbd --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.css @@ -0,0 +1,124 @@ +.confirmation-message { + margin: 0 0 20px 0; + font-size: 16px; + line-height: 1.5; + color: #424242; +} + +.confirmation-message strong { + color: #212121; + font-weight: 600; +} + +.folder-info { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 16px; + background-color: #e3f2fd; + border-radius: 4px; + margin-bottom: 20px; +} + +.info-icon { + color: #1976d2; + font-size: 20px; + width: 20px; + height: 20px; + margin-top: 2px; +} + +.info-text { + margin: 0; + font-size: 14px; + line-height: 1.5; + color: #424242; + flex: 1; +} + +.warning-note { + padding: 12px 16px; + background-color: #fff3e0; + border-radius: 4px; + border-left: 4px solid #ff9800; +} + +.warning-note p { + margin: 0; + font-size: 14px; + line-height: 1.5; + color: #424242; +} + +.warning-note strong { + color: #e65100; + font-weight: 600; +} + +.cancel-button { + color: #666; +} + +.delete-button { + display: flex; + align-items: center; + gap: 8px; +} + +.delete-button mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .dialog-title { + border-bottom-color: #424242; + } + + .dialog-content { + background-color: #303030; + } + + .confirmation-message { + color: #e0e0e0; + } + + .confirmation-message strong { + color: #fff; + } + + .folder-info { + background-color: #1a3a52; + } + + .info-icon { + color: #64b5f6; + } + + .info-text { + color: #e0e0e0; + } + + .warning-note { + background-color: #4a3c28; + border-left-color: #ffb74d; + } + + .warning-note p { + color: #e0e0e0; + } + + .warning-note strong { + color: #ffb74d; + } + + .dialog-actions { + border-top-color: #424242; + } + + .cancel-button { + color: #aaa; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.html new file mode 100644 index 000000000..5d5704819 --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.html @@ -0,0 +1,35 @@ +

+ Delete Folder +

+ + +

+ Are you sure you want to delete the folder "{{data.folderName}}"? +

+ +
+ info +

+ This folder contains {{data.tableCount}} table{{data.tableCount > 1 ? 's' : ''}}. + The tables will not be deleted, only removed from this folder. +

+
+ +
+

Note: This action cannot be undone.

+
+
+ + + + + \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.ts new file mode 100644 index 000000000..127251b8c --- /dev/null +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-delete-dialog/db-folder-delete-dialog.component.ts @@ -0,0 +1,37 @@ +import { Component, Inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; + +export interface DbFolderDeleteDialogData { + folderName: string; + tableCount: number; +} + +@Component({ + selector: 'app-db-folder-delete-dialog', + templateUrl: './db-folder-delete-dialog.component.html', + styleUrls: ['./db-folder-delete-dialog.component.css'], + standalone: true, + imports: [ + CommonModule, + MatDialogModule, + MatButtonModule, + MatIconModule + ] +}) +export class DbFolderDeleteDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DbFolderDeleteDialogData + ) {} + + onCancel(): void { + this.dialogRef.close(false); + } + + onDelete(): void { + this.dialogRef.close(true); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts index e43f6970c..795bbdb3f 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; +import { DbFolderDeleteDialogComponent, DbFolderDeleteDialogData } from './db-folder-delete-dialog/db-folder-delete-dialog.component'; import { DbFolderEditDialogComponent, DbFolderEditDialogData } from './db-folder-edit-dialog/db-folder-edit-dialog.component'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { TableProperties, TableSettings } from 'src/app/models/table'; @@ -275,13 +276,26 @@ export class DbTablesListComponent implements OnInit, OnChanges { } deleteFolder(folder: Folder) { - if (confirm(`Are you sure you want to delete the folder "${folder.name}"?`)) { - const index = this.folders.findIndex(f => f.id === folder.id); - if (index > -1) { - this.folders.splice(index, 1); - this.saveFolders(); + const dialogData: DbFolderDeleteDialogData = { + folderName: folder.name, + tableCount: folder.tableIds.length + }; + + const dialogRef = this.dialog.open(DbFolderDeleteDialogComponent, { + width: '24em', + data: dialogData, + panelClass: 'db-folder-delete-dialog' + }); + + dialogRef.afterClosed().subscribe((confirmed: boolean) => { + if (confirmed) { + const index = this.folders.findIndex(f => f.id === folder.id); + if (index > -1) { + this.folders.splice(index, 1); + this.saveFolders(); + } } - } + }); } From a0aee1f54d688da10a826960ece45b2ef4d8bf20 Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Fri, 31 Oct 2025 14:54:18 +0200 Subject: [PATCH 28/29] tables categories: hide Add table button and Edit buttons for non-admin --- .../dashboard/dashboard.component.html | 1 + .../db-folder-edit-dialog.component.html | 20 +++--- .../db-tables-list.component.css | 65 ++++++++++--------- .../db-tables-list.component.html | 6 +- .../db-tables-list.component.ts | 2 + 5 files changed, 49 insertions(+), 45 deletions(-) diff --git a/frontend/src/app/components/dashboard/dashboard.component.html b/frontend/src/app/components/dashboard/dashboard.component.html index d8f8b1b67..046eed9cf 100644 --- a/frontend/src/app/components/dashboard/dashboard.component.html +++ b/frontend/src/app/components/dashboard/dashboard.component.html @@ -75,6 +75,7 @@

Rocketadmin can not find any tables

[connectionID]="connectionID" [selectedTable]="selectedTableName" [uiSettings]="uiSettings" + [accessLevel]="currentConnectionAccessLevel" (expandSidebar)="toggleSideBar()"> diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html index 9084b7cd9..bf926b7d7 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-folder-edit-dialog/db-folder-edit-dialog.component.html @@ -27,18 +27,18 @@

Folder Icon Color

diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css index 5a477b6bd..e89875fdb 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.css @@ -213,110 +213,110 @@ background-color: transparent; color: #ffffff; } - + .collapsed-folder-item:hover { background-color: rgba(255, 255, 255, 0.05); } - - + + .collapsed-folder-item_active { background-color: #212121 !important; color: #ffffff !important; } - + /* Более специфичный селектор для гарантии */ .collapsed-folder-item.collapsed-folder-item_active { background-color: #212121 !important; } - + .collapsed-folder-item_active .collapsed-folder-icon { color: #ffffff !important; } - + .collapsed-folder-item_active:hover, .collapsed-folder-item_active:focus, .collapsed-folder-item_active:active { background-color: #000000 !important; } - + /* Гарантируем фон для активной папки во всех состояниях */ .collapsed-folder-item_active:not(:hover):not(:focus):not(:active) { background-color: #212121 !important; } - + .collapsed-table-item { color: #e0e0e0; } - + .collapsed-table-item:hover { background-color: rgba(255, 255, 255, 0.05); } - + .collapsed-table-item_active { background-color: #212121; color: #ffffff; } - + .collapsed-table-item_active:hover { background-color: #000000; } - + .collapsed-table-icon-container { background-color: #424242; } - + .collapsed-table-item:hover .collapsed-table-icon-container { background-color: rgba(255, 255, 255, 0.1); } - + .collapsed-table-item_active .collapsed-table-icon-container { background-color: #ffffff; color: #000000; } - + .collapsed-table-item_active:hover .collapsed-table-icon-container { background-color: #f5f5f5; } - + .collapsed-table-icon-container .collapsed-table-icon { color: #e0e0e0; } - + .collapsed-table-item_active .collapsed-table-icon-container .collapsed-table-icon { color: #000000; } - + .collapsed-table-initials { background-color: #424242; color: #e0e0e0; } - + .collapsed-table-item:hover .collapsed-table-initials { background-color: rgba(255, 255, 255, 0.1); } - + .collapsed-table-item_active .collapsed-table-initials { background-color: #ffffff; color: #000000; } - + .collapsed-table-item_active:hover .collapsed-table-initials { background-color: #f5f5f5; } - + .collapsed-search-button { color: #ffffff !important; } - + .collapsed-search-button:hover { background-color: rgba(255, 255, 255, 0.1); } - + .collapsed-add-button { background-color: #424242; color: #ffffff; } - + .collapsed-add-button:hover { background-color: #555555; } @@ -461,16 +461,16 @@ background-color: var(--color-whitePalette-900); color: var(--color-whitePalette-900-contrast); } - + .folder-header.expanded { background-color: #212121; color: #ffffff; } - + .folder-header.expanded .folder-icon { color: #ffffff; } - + .folder-header.expanded .folder-expand-icon { color: #ffffff; } @@ -514,7 +514,7 @@ background-color: transparent; margin-top: 12px; margin-left: 16px; - margin-bottom: -24px; + /* margin-bottom: -24px; */ width: calc(100% - 32px); max-width: calc(240px - 32px); box-sizing: border-box; @@ -538,9 +538,10 @@ } .add-folder-button-container { - margin: 16px; - margin-top: 16px; - margin-bottom: 4px; + margin-left: 16px; + margin-right: 16px; + margin-top: -12px; + margin-bottom: 12px; width: calc(100% - 32px); box-sizing: border-box; } diff --git a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html index d3c8cedaf..eda0e5dc1 100644 --- a/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html +++ b/frontend/src/app/components/dashboard/db-tables-list/db-tables-list.component.html @@ -60,7 +60,7 @@
-
+
Nothing found. -
+
-