From 8cedb7754eb33d9c4cc095c1df5a0d8983a63e23 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 2 Jun 2026 15:27:00 +0300 Subject: [PATCH 01/13] feat(mobile): inline quick-filter chips with horizontal scroll + paginator polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add a "+" / "Create fast filter" trigger after the Filter button on mobile, with a vertical divider in between - Horizontally scroll the saved-filter chips inside the search row (custom chip render, absolutely positioned strip so it can't push the page wide) - Each chip has a three-dots menu (Edit / Delete) wired to the saved-filters-panel handlers via @ViewChild - Hide the panel's own mobile dropdown/list — its conditions editor remains visible below the row when a fast filter is selected - When conditions show, the panel takes the full second row (:has) and Columns/Sort stay right-aligned on the row above - Tighten spacing between conditions and the Columns/Sort row; align the "where" label and chips vertically (min-height: 32px) - Make Sort by default black/white, accent only when sort is active; pull the swap_vert icon tight to the label - Inline preview divider only spans the text area (::after with 20px insets) instead of edge-to-edge - Paginator on mobile: items-per-page flush left, range/buttons right, single row, "Rows per page:" label, looser select for readability - Drop the trailing period from the "No records match…" empty-state strings - Open the filter dialog as a mobile bottom sheet when the active filter chip is tapped (was a centered modal) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../db-table-row-view.component.css | 15 +- .../db-table-view/db-table-view.component.css | 270 +++++++++++++++++- .../db-table-view.component.html | 52 +++- .../db-table-view/db-table-view.component.ts | 16 +- .../saved-filters-panel.component.css | 20 +- 5 files changed, 355 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-row-view/db-table-row-view.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-row-view/db-table-row-view.component.css index a4cc7de50..c0dbacf53 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-row-view/db-table-row-view.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-row-view/db-table-row-view.component.css @@ -125,6 +125,7 @@ } .row-preview-sidebar__field { + position: relative; display: flex; flex-direction: column; align-items: flex-start; @@ -132,13 +133,19 @@ padding: 12px 20px; } -.row-preview-sidebar__field:not(:last-child) { - border-bottom: solid 1px rgba(0, 0, 0, 0.12); +.row-preview-sidebar__field:not(:last-child)::after { + content: ''; + position: absolute; + left: 20px; + right: 20px; + bottom: 0; + height: 1px; + background: rgba(0, 0, 0, 0.12); } @media (prefers-color-scheme: dark) { - .row-preview-sidebar__field:not(:last-child) { - border-bottom: solid 1px rgba(255, 255, 255, 0.04); + .row-preview-sidebar__field:not(:last-child)::after { + background: rgba(255, 255, 255, 0.04); } } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css index fc6e05603..ad5a97fe1 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css @@ -613,7 +613,11 @@ display: contents; } -.search-row__filter { +.search-row__filter, +.search-row__add-quick-filter, +.search-row__create-fast-filter, +.search-row__divider, +.search-row__quick-chips-slot { display: none; } @@ -677,6 +681,161 @@ height: 18px; } + .search-row__add-quick-filter { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 32px; + height: 32px; + min-width: 32px; + min-height: 32px; + padding: 0 !important; + --mdc-icon-button-state-layer-size: 32px; + --mat-icon-button-state-layer-size: 32px; + --mdc-icon-button-icon-size: 20px; + } + + .search-row__add-quick-filter .mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + margin: 0; + } + + .search-row__add-quick-filter ::ng-deep .mat-mdc-button-touch-target { + width: 32px; + height: 32px; + } + + .search-row__add-quick-filter ::ng-deep .mat-mdc-button-persistent-ripple, + .search-row__add-quick-filter ::ng-deep .mat-mdc-focus-indicator { + inset: 0; + border-radius: 50%; + } + + .search-row__create-fast-filter { + display: inline-flex; + flex: 0 0 auto; + height: 32px; + line-height: 32px; + padding: 0 12px; + border-radius: 999px; + font-size: 13px; + gap: 4px; + } + + .search-row__create-fast-filter .mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } + + .db-table-header, + .db-table-actions, + .search-row { + min-width: 0; + } + + .search-row__buttons { + min-width: 0; + max-width: 100%; + overflow: hidden; + } + + .search-row__quick-chips-slot { + position: relative; + display: block; + flex: 1 1 0; + min-width: 0; + width: 0; + max-width: 100%; + height: 32px; + overflow: hidden; + } + + .search-row__quick-chips { + position: absolute; + inset: 0; + display: flex; + align-items: center; + gap: 6px; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + + .search-row__quick-chips::-webkit-scrollbar { + display: none; + } + + .search-row__quick-chip { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + border: 1px solid var(--mdc-outlined-button-outline-color, rgba(0, 0, 0, 0.12)); + background: transparent; + color: var(--color-primaryPalette-700); + font-size: 13px; + font-weight: 500; + padding: 0 8px 0 12px; + height: 32px; + line-height: 30px; + border-radius: 999px; + white-space: nowrap; + cursor: pointer; + } + + .search-row__quick-chip_active { + background: var(--color-accentedPalette-500); + border-color: var(--color-accentedPalette-500); + color: var(--color-accentedPalette-500-contrast); + } + + .search-row__quick-chip-menu { + background: transparent; + border: 0; + padding: 0; + margin-left: 4px; + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: inherit; + opacity: 0.7; + } + + .search-row__quick-chip-menu .mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + @media (prefers-color-scheme: dark) { + .search-row__quick-chip { + border-color: var(--mdc-outlined-button-outline-color, rgba(255, 255, 255, 0.24)); + color: var(--color-primaryPalette-100); + } + } + + .search-row__divider { + display: inline-block; + flex: 0 0 auto; + width: 1px; + align-self: stretch; + margin: 4px 4px; + background: var(--color-primaryPalette-200); + } + + @media (prefers-color-scheme: dark) { + .search-row__divider { + background: var(--color-primaryPalette-700); + } + } + .saved-filters-row__columns { display: inline-flex; flex: 0 0 auto; @@ -762,25 +921,69 @@ @media (width <= 600px) { .saved-filters-row { display: flex; + flex-wrap: wrap; align-items: flex-start; - gap: 8px; + justify-content: flex-end; + gap: 4px 8px; + width: 100%; + max-width: 100%; + min-width: 0; + overflow: hidden; } .saved-filters-row > app-saved-filters-panel { flex: 1 1 auto; min-width: 0; + max-width: 100%; display: block; + overflow: hidden; + } + + .saved-filters-row:has(.filters-container) > app-saved-filters-panel { + flex: 0 0 100%; + order: 1; + } + + .saved-filters-row:has(.filters-container) > .saved-filters-row__columns, + .saved-filters-row:has(.filters-container) > .mobile-sort-button { + order: 2; } .mobile-sort-button { display: inline-flex !important; align-items: center; - gap: 4px; + gap: 2px; flex: 0 0 auto; min-height: 36px; padding: 0 10px; font-size: 13px; font-weight: 500; + --mdc-text-button-label-text-color: rgba(0, 0, 0, 0.87); + --mat-mdc-button-persistent-ripple-color: rgba(0, 0, 0, 0.87); + color: rgba(0, 0, 0, 0.87) !important; + } + + .mobile-sort-button .mat-icon, + .mobile-sort-button .mat-mdc-button-touch-target ~ * { + color: inherit !important; + } + + .mobile-sort-button ::ng-deep .mat-mdc-button > .mat-icon, + .mobile-sort-button ::ng-deep .mdc-button__label > .mat-icon, + .mobile-sort-button .mat-icon { + margin-right: 2px !important; + margin-left: 0 !important; + } + + .mobile-sort-button ::ng-deep .mdc-button__label { + display: inline-flex; + align-items: center; + gap: 2px; + } + + .mobile-sort-button.mobile-sort-button_active { + --mdc-text-button-label-text-color: var(--color-accentedPalette-700); + color: var(--color-accentedPalette-700) !important; } .saved-filters-row__columns { @@ -830,6 +1033,10 @@ } @media (prefers-color-scheme: dark) and (width <= 600px) { + .mobile-sort-button { + color: rgba(255, 255, 255, 0.87); + } + .mobile-sort-button_active { background: var(--color-accentedPalette-900); border-color: var(--color-accentedPalette-600) !important; @@ -1002,6 +1209,63 @@ margin-bottom: 72px; } +@media (width <= 600px) { + .table-surface mat-paginator { + margin-top: 12px; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-outer-container, + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-container { + justify-content: space-between !important; + flex-wrap: nowrap !important; + min-height: 48px; + padding: 0 !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size { + margin: 0 !important; + padding: 0 !important; + margin-right: auto !important; + flex: 0 0 auto; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-label { + margin: 0 4px 0 0 !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-select { + margin: 0 8px 0 0 !important; + width: auto !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-form-field-infix { + width: auto !important; + min-width: 36px !important; + padding-right: 4px !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-select { + width: auto !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-select-arrow-wrapper { + padding-left: 6px !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-text-field-wrapper { + padding: 0 10px !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-range-actions { + flex: 0 0 auto; + margin: 0 !important; + } + + .table-surface mat-paginator ::ng-deep .mat-mdc-paginator-range-label { + margin: 0 4px !important; + } +} + @media (prefers-color-scheme: dark) { .table-surface { --mat-table-background-color: var(--surface-dark-color); diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html index af7131eb3..795b68eb3 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html @@ -98,6 +98,56 @@

{{ displayName }}

filter_list Filter + + + + +
+
+ +
+ {{ f.name }} + +
+
+
+ + + +
@@ -504,7 +554,7 @@

{{ displayName }}

filter_list_off

- {{ hasSavedFilterActive ? 'No records match the selected fast filter.' : 'No records match this filter.' }} + {{ hasSavedFilterActive ? 'No records match the selected fast filter' : 'No records match this filter' }}

diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts index 359a875e2..df75b6b05 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts @@ -28,7 +28,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; -import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; +import { MatPaginator, MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator'; import { MatSelectModule } from '@angular/material/select'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; @@ -179,6 +179,9 @@ export class DbTableViewComponent implements OnInit, OnChanges { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; + @ViewChild(SavedFiltersPanelComponent) savedFiltersPanel?: SavedFiltersPanelComponent; + + public chipFilterForMenu: any = null; public defaultSort: { column: string; direction: 'asc' | 'desc' } | null = null; private sortInitialized: boolean = false; @@ -193,7 +196,11 @@ export class DbTableViewComponent implements OnInit, OnChanges { public router: Router, public dialog: MatDialog, private cdr: ChangeDetectorRef, - ) {} + private paginatorIntl: MatPaginatorIntl, + ) { + this.paginatorIntl.itemsPerPageLabel = 'Rows per page:'; + this.paginatorIntl.changes.next(); + } ngAfterViewInit() { this.tableData.paginator = this.paginator; @@ -586,6 +593,10 @@ export class DbTableViewComponent implements OnInit, OnChanges { this.searchString = ''; } + handleOpenCreateQuickFilter() { + this.savedFiltersPanel?.handleOpenSavedFiltersDialog(); + } + handleSearch() { this.searchString = this.searchString.trim(); this.staticSearchString = this.searchString; @@ -898,6 +909,7 @@ export class DbTableViewComponent implements OnInit, OnChanges { handleActiveFilterClick(filterKey: string) { const dialogRef = this.dialog.open(DbTableFiltersDialogComponent, { width: '56em', + panelClass: 'mobile-bottom-sheet-dialog', data: { connectionID: this.connectionID, tableName: this.name, diff --git a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css index 87e09fed1..ec07bf5d6 100644 --- a/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/saved-filters-panel/saved-filters-panel.component.css @@ -26,12 +26,7 @@ } @media (width <= 600px) { - .saved-filters-trigger { - display: inline-flex; - } - - .create-filter-button, - .saved-filters-tabs { + .saved-filters-list { display: none !important; } } @@ -297,11 +292,12 @@ @media (width <= 600px) { .filters-container { + display: flex; align-items: center; flex-direction: row; flex-wrap: wrap; - gap: 6px; - margin: 8px 0 16px; + gap: 6px 8px; + margin: 4px 0 0; padding: 8px 16px; border-radius: 10px; background: rgba(0, 0, 0, 0.03); @@ -312,6 +308,11 @@ overflow-y: visible; } + .filters-container > .static-filters { + align-items: center; + min-height: 32px; + } + .dynamic-column-editor { flex: 1 1 100%; @@ -412,6 +413,9 @@ @media (width <= 600px) { .filters-where-label { margin-top: 0 !important; + min-height: 32px; + align-items: center; + line-height: 1; } } From 0893e98a48205c5f8618bb24c9317c39adc63aaf Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 2 Jun 2026 22:14:16 +0300 Subject: [PATCH 02/13] feat(mobile): widgets page, AI panel, sidebar polish + misc fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Widgets page mobile layout: stacked widget cards (display:contents → block), header with arrow-back + "Widgets" / table-name stack, hide breadcrumbs and per-card delete button, drop the bottom "Back" button (arrow-back covers it), Save aligns right - Widgets empty state mobile: hide right-side widget cards grid, hide divider and the tune icon, symmetric padding, Docs link + "Create first widget" button right-aligned together, breadcrumbs hidden, extra bottom buffer so Create doesn't collide with the version footer - AI panel: full-size only on mobile (removed expand/collapse toggle and the +175px expanded padding), trim "AI insights for " to plain "AI insights" so the title doesn't push out, FAB Add row hides while the chat is open, and on mobile the chat panel state is reset on each table view init (so navigating away doesn't reopen the chat on return) - Sidebar drawer mobile: sits below the 44px nav-bar (with backdrop offset), hide the dark mat-toolbar, "Connections" hidden when already inside a connection, "Hosted databases" added between Company and Secrets, account-section items (.account-section-item) get smaller text + icons, drop the connection-nav top border, Upgrade button nudged up - Connections list dropdown: divider + "Hosted databases" link at the bottom - Logo: short variant only on mobile when inside a connection (connections-list, account, hosted-databases pages keep the full wordmark) - Row edit form: drop the leftover .widget grid column reservation so inputs fill width - Mobile loading skeleton: dedicated card-shape placeholder for ≤600px, no left label column, no elevation around the wrapper - Row preview dividers: inset 20px each side via ::after so they don't reach the popup edges - Filter dialog when tapping an active filter chip: opens as the mobile bottom sheet (panelClass: 'mobile-bottom-sheet-dialog') Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/app/app.component.css | 32 +++- frontend/src/app/app.component.html | 29 +++- frontend/src/app/app.component.ts | 2 + .../db-table-ai-panel.component.css | 16 +- .../db-table-ai-panel.component.html | 8 +- .../db-table-ai-panel.component.ts | 6 +- .../db-table-view/db-table-view.component.css | 9 +- .../db-table-view/db-table-view.component.ts | 7 +- .../db-table-widgets.component.css | 156 ++++++++++++++++++ .../db-table-widgets.component.html | 13 +- .../widgets-empty-state.component.css | 31 ++++ .../db-table-row-edit.component.css | 3 +- .../placeholder-table-data.component.css | 56 +++++++ .../placeholder-table-data.component.html | 11 +- .../placeholder-table-data.component.ts | 1 + frontend/src/styles.scss | 4 + 16 files changed, 342 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/app.component.css b/frontend/src/app/app.component.css index 3c436c2c5..47eda27f3 100644 --- a/frontend/src/app/app.component.css +++ b/frontend/src/app/app.component.css @@ -22,6 +22,34 @@ width: 60vw; } +.main-menu-sidenav .account-section-item { + --mdc-list-list-item-label-text-size: 13px; + --mat-list-list-item-leading-icon-size: 18px; + --mdc-list-list-item-one-line-container-height: 40px; + --mdc-list-list-item-two-line-container-height: 52px; +} + +.main-menu-sidenav .account-section-item .mat-icon { + font-size: 18px; + width: 18px; + height: 18px; +} + +@media (width <= 600px) { + .main-menu-sidenav.mat-drawer { + top: 44px !important; + height: calc(100vh - 44px) !important; + } + + .main-menu-container ::ng-deep .mat-drawer-backdrop.mat-drawer-shown { + top: 44px !important; + } + + .main-menu-sidenav mat-toolbar { + display: none !important; + } +} + .nav-bar { position: sticky; top: 0; @@ -218,8 +246,6 @@ display: flex; flex-direction: column; align-items: flex-start; - border-top: var(--mat-table-row-item-outline-width, 1px) solid - var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); border-bottom: var(--mat-table-row-item-outline-width, 1px) solid var(--mat-table-row-item-outline-color, rgba(0, 0, 0, 0.12)); margin-left: 8px; @@ -243,7 +269,7 @@ } .connection-navigation__upgrade-button { - margin-top: 8px; + margin-top: -12px; margin-left: 8px; width: calc(100% - 16px); } diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 1ff5688b2..f27fee039 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -6,7 +6,7 @@ > Rocketadmin - @@ -21,7 +21,7 @@ {{navigationTabs[tab].caption}} - + - + - + + + database + +
Hosted databases
+
+ - + - + - + @@ -88,7 +96,7 @@ class="logo__image"> - + Rocketadmin logo @@ -125,6 +133,11 @@ [ngClass]="{'connection_active': connectionID === connection.connection.id}"> {{connection.displayTitle}} + + + database + Hosted databases + demo diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 8d08e92af..c0f2240b7 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component } from '@angular/core'; import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from '@angular/material/menu'; @@ -50,6 +51,7 @@ amplitude.getInstance().init('9afd282be91f94da735c11418d5ff4f5'); MatButtonModule, MatBadgeModule, MatMenuModule, + MatDividerModule, MatTooltipModule, Angulartics2OnModule, FeatureNotificationComponent, diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css index 507a5c926..d6c1c19b8 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css @@ -73,7 +73,7 @@ .ai-panel-sidebar-content_open.ai-panel-sidebar-content_expanded { left: 65px; width: calc(100vw - 65px); - padding-left: 175px; + padding-left: 0; border-left: none; z-index: 3; transition: @@ -99,6 +99,7 @@ .ai-panel-sidebar-content_open.ai-panel-sidebar-content_expanded { left: 0; width: 100%; + padding-left: 0; } } @@ -677,19 +678,8 @@ } .ai-panel-sidebar-content_expanded .ai-panel-sidebar__header { - position: relative; width: 100%; - max-width: 800px; - margin: 0 auto; - padding-left: 0; - padding-right: 0; - justify-content: flex-start; -} - -.ai-panel-sidebar-content_expanded .ai-panel-sidebar__actions { - position: fixed; - right: 16px; - top: 64px; + justify-content: space-between; } .ai-panel-sidebar-content_expanded .ai-welcome__section { diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.html index 4007b624e..e7faaa311 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.html @@ -4,14 +4,8 @@
-

- AI insights for {{displayName}} - AI insights -

+

AI insights

- diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.ts index 9a84da51a..a99d1d9cc 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.ts @@ -65,7 +65,7 @@ export class DbTableAiPanelComponent implements OnInit, AfterViewInit, OnDestroy public activeCompletions: string[] = []; public showCompletions: boolean = false; public submitting: boolean = false; - public isExpanded: boolean = false; + public isExpanded: boolean = true; public textareaRows: number = 4; public currentLoadingStep: string = ''; @@ -97,8 +97,8 @@ export class DbTableAiPanelComponent implements OnInit, AfterViewInit, OnDestroy this.isAIpanelOpened = isAIpanelOpened; }); - this._tableState.aiPanelExpandedCast.subscribe((isExpanded) => { - this.isExpanded = isExpanded; + this._tableState.aiPanelExpandedCast.subscribe(() => { + this.isExpanded = true; }); this.adjustTextareaRows(); diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css index ad5a97fe1..a2b14f623 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css @@ -776,7 +776,7 @@ align-items: center; border: 1px solid var(--mdc-outlined-button-outline-color, rgba(0, 0, 0, 0.12)); background: transparent; - color: var(--color-primaryPalette-700); + color: rgba(0, 0, 0, 0.87); font-size: 13px; font-weight: 500; padding: 0 8px 0 12px; @@ -817,7 +817,7 @@ @media (prefers-color-scheme: dark) { .search-row__quick-chip { border-color: var(--mdc-outlined-button-outline-color, rgba(255, 255, 255, 0.24)); - color: var(--color-primaryPalette-100); + color: rgba(255, 255, 255, 0.87); } } @@ -919,6 +919,11 @@ } @media (width <= 600px) { + .skeleton.mat-elevation-z4 { + box-shadow: none !important; + background: transparent !important; + } + .saved-filters-row { display: flex; flex-wrap: wrap; diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts index df75b6b05..ee1c17702 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts @@ -198,7 +198,7 @@ export class DbTableViewComponent implements OnInit, OnChanges { private cdr: ChangeDetectorRef, private paginatorIntl: MatPaginatorIntl, ) { - this.paginatorIntl.itemsPerPageLabel = 'Rows per page:'; + this.paginatorIntl.itemsPerPageLabel = 'per page:'; this.paginatorIntl.changes.next(); } @@ -302,6 +302,11 @@ export class DbTableViewComponent implements OnInit, OnChanges { this.searchString = this.route.snapshot.queryParams.search; // this.hasSavedFilterActive = !!this.route.snapshot.queryParams.saved_filter; + // On mobile, never restore a previously open AI panel — user should land on the table, not the chat. + if (this.isMobileView) { + this._tableState.closeAIpanel(); + } + const connectionType = this._connections.currentConnection.type; this.displayCellComponents = tableDisplayTypes[connectionType]; diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css index 916c16fc9..849c191f9 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css @@ -31,6 +31,10 @@ gap: 16px; } +.mobile-header { + display: none; +} + .widget-settings { display: grid; grid-template-columns: minmax(10%, 130px) 1fr 2fr 1fr 2fr 50px; @@ -71,3 +75,155 @@ --background-color: #fff; } } + +@media (width <= 600px) { + .wrapper { + width: 100%; + margin: 0 auto 88px; + padding: 0 16px; + min-width: 0; + } + + .row-breadcrumbs { + display: none !important; + } + + .mobile-header { + display: flex; + align-items: center; + gap: 8px; + margin: 20px 0 12px -12px; + } + + .mobile-header__back { + margin-right: 0; + } + + .mobile-header__titles { + display: flex; + flex-direction: column; + min-width: 0; + } + + .mobile-header__title { + margin: 0 !important; + padding: 0 !important; + font-size: 18px !important; + font-weight: 600 !important; + line-height: 18px !important; + } + + .mobile-header__subtitle { + display: block; + margin: 4px 0 0 !important; + padding: 0 !important; + font-size: 12px; + line-height: 14px; + color: rgba(0, 0, 0, 0.54); + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .actions__back { + display: none !important; + } + + .actions { + justify-content: flex-end; + } + + .header { + flex-direction: column; + gap: 12px; + margin: 16px 0 16px; + } + + .header-actions-box { + width: 100%; + align-items: stretch; + gap: 8px; + } + + .header-actions { + flex-wrap: wrap; + gap: 8px; + width: 100%; + } + + .header-actions > button { + flex: 1 1 auto; + } + + .header-links { + flex-direction: column; + align-items: flex-start; + gap: 0; + } + + .widget-settings { + display: flex; + flex-direction: column; + gap: 16px; + } + + .widget-item { + display: block; + position: relative; + background: var(--mat-table-background-color, #fff); + border-radius: 12px; + padding: 16px 16px 8px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04); + } + + .widget-item ::ng-deep .mat-mdc-form-field { + width: 100%; + } + + .widget-item ::ng-deep .widget-field-name { + display: block; + font-weight: 600; + margin: 0 0 12px 0; + font-size: 14px; + } + + .widget-item ::ng-deep .widget-delete-button { + display: none !important; + } + + .widget-item ::ng-deep .code-editor-box { + min-height: 140px; + margin-bottom: 16px; + } + + .actions { + padding: 0 16px; + justify-content: space-between; + gap: 8px; + } + + .actions > a, + .actions > button { + flex: 1 1 auto; + } + + .actions_empty-state { + display: none !important; + } + + .wrapper:has(app-widgets-empty-state) { + margin-bottom: 80px; + } +} + +@media (prefers-color-scheme: dark) and (width <= 600px) { + .widget-item { + background: var(--surface-dark-color); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); + } + + .mobile-header__subtitle { + color: rgba(255, 255, 255, 0.6); + } +} diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html index ddb9fe69f..63a58aaa7 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html @@ -3,6 +3,15 @@
+
+ + arrow_back + +
+

Widgets

+ {{ tableName }} +
+
@@ -61,7 +70,7 @@
- + Back @@ -75,7 +84,7 @@ -
+
Back diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css index 847de74da..cf19415af 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css @@ -357,3 +357,34 @@ color: rgba(255, 255, 255, 0.54); } } + +@media (width <= 600px) { + .empty-state { + flex-direction: column; + } + + .empty-divider, + .empty-right, + .empty-icon-box { + display: none !important; + } + + .empty-left { + width: 100%; + max-width: 100%; + padding: 16px 0; + } + + .empty-cta { + justify-content: flex-end; + gap: 12px; + } + + .empty-cta > button { + order: 2; + } + + .empty-cta > .empty-docs-link { + order: 1; + } +} diff --git a/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.css b/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.css index 100d0bb99..f3f9fe005 100644 --- a/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.css +++ b/frontend/src/app/components/db-table-row-edit/db-table-row-edit.component.css @@ -189,8 +189,7 @@ } .widget { - display: grid; - grid-template-columns: 0 1fr 36px; + display: block; } .widget-info { diff --git a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.css b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.css index 435716ab7..1b2301f0d 100644 --- a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.css +++ b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.css @@ -14,3 +14,59 @@ .table-cell-content { height: 24px; } + +.data-table_mobile { + display: none; +} + +@media (width <= 600px) { + .data-table_desktop { + display: none; + } + + :host { + display: block; + box-shadow: none !important; + } + + .data-table_mobile { + display: flex; + flex-direction: column; + gap: 12px; + padding: 0 0 16px; + } + + .mobile-card { + background: var(--mat-table-background-color, #fff); + border: none; + border-radius: 12px; + padding: 12px 16px; + display: flex; + flex-direction: column; + gap: 8px; + } + + .mobile-card__value { + width: 100%; + height: 14px; + border-radius: 4px; + } + + .mobile-card__value_short { + width: 30%; + } + + .mobile-card__value_medium { + width: 60%; + } + + .mobile-card__value_long { + width: 80%; + } +} + +@media (prefers-color-scheme: dark) and (width <= 600px) { + .mobile-card { + background: var(--surface-dark-color); + } +} diff --git a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.html b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.html index c9d962ac8..20d8b6c0f 100644 --- a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.html +++ b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.html @@ -1,5 +1,14 @@ -
+
+ +
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.ts b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.ts index 06ae1ed10..a15ac8a0d 100644 --- a/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.ts +++ b/frontend/src/app/components/skeletons/placeholder-table-data/placeholder-table-data.component.ts @@ -9,4 +9,5 @@ import { Component } from '@angular/core'; }) export class PlaceholderTableDataComponent { public numberOfDivs = Array.from({ length: 42 }, (_, index) => index); + public numberOfCards = Array.from({ length: 6 }, (_, index) => index); } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index ee99bb913..07e745d9b 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -89,6 +89,10 @@ body { } } +body:has(.ai-panel-sidebar-content_open) .add-row-fab { + display: none !important; +} + @keyframes shimmer { 100% { transform: translateX(100%); From 78617cfaa18f475cd2ba9172f85a26638c4f6457 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 2 Jun 2026 23:04:09 +0300 Subject: [PATCH 03/13] feat(mobile): audit feed view + sidebar/AI panel polish - Audit mobile feed: replace mat-table with a card list (sticky date headers grouping entries per day, avatar with initials, top row user/time, middle row action+table, bottom row status+Details, last card fades for an infinite-scroll hint, tap anywhere on the card opens the details dialog) - Audit header on mobile: arrow-back next to the "Audit" title, Tables/Users selects side-by-side without breaking layout (force min-width: 0 chain, no horizontal overflow), select arrow pinned to the right edge of the field - Audit paginator: match the table paginator (compact "Per page:" select on the left, range + nav buttons on the right, single row, capitalized label) - AI panel: raise z-index of the expanded content back to 100 so close/send buttons aren't covered (they were being intercepted by other UI when the panel is force-expanded) - Sidebar drawer: close automatically on any nav-list click and on Upgrade click Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/app/app.component.html | 5 +- .../app/components/audit/audit.component.css | 459 ++++++++++++++++++ .../app/components/audit/audit.component.html | 51 +- .../app/components/audit/audit.component.ts | 49 +- .../db-table-ai-panel.component.css | 2 +- .../db-table-view/db-table-view.component.ts | 2 +- 6 files changed, 560 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index f27fee039..299d561e0 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -5,7 +5,7 @@ class="main-menu-sidenav" > Rocketadmin - + @@ -66,7 +66,8 @@ + routerLinkActive="nav-bar__button_active" + (click)="drawer.close()"> Upgrade diff --git a/frontend/src/app/components/audit/audit.component.css b/frontend/src/app/components/audit/audit.component.css index bf7327dfa..d5a7162dd 100644 --- a/frontend/src/app/components/audit/audit.component.css +++ b/frontend/src/app/components/audit/audit.component.css @@ -20,6 +20,21 @@ header { margin: 0; } +.title-block { + display: flex; + align-items: center; + gap: 8px; +} + +.title-block h1 { + margin: 0; + line-height: 1; +} + +.back-button { + display: none !important; +} + .filters { display: flex; /* grid-template-columns: 1fr 1fr; */ @@ -215,3 +230,447 @@ td.mat-cell { color: rgba(255, 255, 255, 0.6); } } + +@media (width <= 600px) { + .wrapper { + width: 100%; + max-width: 100%; + min-width: 0; + padding: 0 16px; + box-sizing: border-box; + overflow-x: clip; + overflow-y: visible; + } + + header { + flex-direction: column; + align-items: stretch; + gap: 12px; + margin: 16px 0 12px; + } + + .back-button { + display: inline-flex !important; + align-items: center; + justify-content: center; + margin-left: -12px; + } + + .title-block { + gap: 0; + align-items: center; + } + + .title-block h1 { + font-size: 22px; + font-weight: 600; + line-height: 1; + } + + .filters { + flex-direction: row; + gap: 8px; + width: 100%; + max-width: 100%; + min-width: 0; + } + + .filters mat-form-field { + flex: 1 1 0; + min-width: 0; + max-width: 100%; + width: auto; + } + + .filters mat-form-field ::ng-deep .mat-mdc-text-field-wrapper, + .filters mat-form-field ::ng-deep .mat-mdc-form-field-flex { + min-width: 0 !important; + width: 100% !important; + } + + .filters mat-form-field ::ng-deep .mat-mdc-form-field-infix { + min-width: 0 !important; + flex: 1 1 auto !important; + width: auto !important; + } + + .filters mat-form-field ::ng-deep .mat-mdc-select { + width: 100%; + } + + .filters mat-form-field ::ng-deep .mat-mdc-select-arrow-wrapper { + padding-left: 4px; + } + + .audit-banner { + margin: 0 -16px 12px; + width: calc(100% + 32px); + } + + .table-wrapper { + width: 100%; + max-width: 100%; + min-width: 0; + background: transparent !important; + box-shadow: none !important; + box-sizing: border-box; + } + + .table-wrapper table.mat-mdc-table, + .table-wrapper table.mat-mdc-table thead, + .table-wrapper table.mat-mdc-table tbody, + .table-wrapper table.mat-mdc-table tr { + display: block; + width: 100%; + } + + .table-wrapper table.mat-mdc-table thead, + .table-wrapper table.mat-mdc-table tr.mat-mdc-header-row, + .table-wrapper table.mat-mdc-table th.mat-mdc-header-cell { + display: none !important; + } + + .table-wrapper table.mat-mdc-table tbody { + display: flex; + flex-direction: column; + gap: 10px; + } + + .table-wrapper tr.mat-mdc-row { + background: var(--mat-table-background-color, #fff); + border-radius: 12px; + padding: 10px 14px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04); + display: grid !important; + grid-template-columns: auto 1fr; + grid-column-gap: 12px; + grid-row-gap: 4px; + height: auto !important; + width: calc(100vw - 32px) !important; + min-width: 0 !important; + max-width: calc(100vw - 32px) !important; + box-sizing: border-box; + margin: 0 !important; + } + + .table-wrapper table.mat-mdc-table { + min-width: 0 !important; + max-width: 100% !important; + width: 100% !important; + background: transparent !important; + } + + .table-wrapper table.mat-mdc-table tbody { + width: 100% !important; + } + + .table-wrapper td.mat-mdc-cell { + display: block; + border: 0 !important; + padding: 0 !important; + font-size: 13px; + } + + .table-wrapper td.mat-mdc-cell .table-cell-content { + padding: 0; + } + + /* columns order: User(1) Table(2) Action(3) Status(4) Date(5) Changes(6) + Card layout: + row 1: User (name + email) full width + row 2: Action + Table (inline) + row 3: Status + row 4: Date + Details */ + .table-wrapper td.mat-mdc-cell:nth-of-type(1) { + grid-column: 1 / -1; + grid-row: 1; + } + + .table-wrapper td.mat-mdc-cell:nth-of-type(3) { + grid-column: 1 / 2; + grid-row: 2; + font-weight: 500; + } + + .table-wrapper td.mat-mdc-cell:nth-of-type(2) { + grid-column: 2 / 3; + grid-row: 2; + font-weight: 600; + font-size: 14px; + justify-self: start; + } + + .table-wrapper td.mat-mdc-cell:nth-of-type(4) { + grid-column: 1 / -1; + grid-row: 3; + } + + .table-wrapper td.mat-mdc-cell:nth-of-type(5) { + grid-column: 1 / 2; + grid-row: 4; + opacity: 0.75; + font-size: 12px; + } + + .table-wrapper td.mat-mdc-cell:nth-of-type(6) { + grid-column: 2 / 3; + grid-row: 4; + justify-self: end; + } + + .status-badge { + padding: 2px 8px; + font-size: 12px; + } + + .mat-h1 { + font-size: 22px; + } + + .table-wrapper mat-paginator ::ng-deep .mat-mdc-paginator-container { + flex-wrap: nowrap !important; + justify-content: space-between !important; + padding: 0 8px !important; + min-height: 48px; + } + + .table-wrapper mat-paginator ::ng-deep .mat-mdc-paginator-page-size { + margin: 0 !important; + margin-right: auto !important; + } + + .table-wrapper mat-paginator ::ng-deep .mat-mdc-paginator-range-label { + margin: 0 4px !important; + } + + .table-wrapper mat-paginator { + margin-top: 8px; + background: transparent !important; + } +} + +@media (prefers-color-scheme: dark) and (width <= 600px) { + .table-wrapper tr.mat-mdc-row { + background: var(--surface-dark-color); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); + } +} + +@media (width <= 600px) { + .audit-feed { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + max-width: 100%; + min-width: 0; + padding-bottom: 16px; + } + + .audit-feed.hidden { + display: none; + } + + .audit-feed__date-header { + position: sticky; + top: 44px; + z-index: 2; + background: var(--mat-sidenav-content-background-color, #fff); + padding: 8px 0 4px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgba(0, 0, 0, 0.54); + } + + .audit-card { + display: flex; + align-items: flex-start; + gap: 12px; + background: var(--mat-table-background-color, #fff); + border-radius: 12px; + padding: 10px 14px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04); + cursor: pointer; + transition: opacity 200ms ease; + } + + .audit-card_fade { + opacity: 0.6; + } + + .audit-card__avatar { + flex: 0 0 36px; + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--color-accentedPalette-100); + color: var(--color-accentedPalette-700); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 600; + } + + .audit-card__body { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; + } + + .audit-card__row { + display: flex; + align-items: center; + gap: 6px; + } + + .audit-card__row_top { + justify-content: space-between; + } + + .audit-card__user { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 600; + font-size: 14px; + } + + .audit-card__user-email { + font-weight: 400; + color: rgba(0, 0, 0, 0.72); + } + + .audit-card__time { + flex: 0 0 auto; + font-size: 12px; + color: rgba(0, 0, 0, 0.54); + } + + .audit-card__row_middle { + font-size: 13px; + } + + .audit-card__action-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + + .audit-card__action { + font-weight: 500; + } + + .audit-card__sep { + color: rgba(0, 0, 0, 0.3); + } + + .audit-card__table { + font-family: "IBM Plex Mono", monospace; + font-size: 12px; + color: rgba(0, 0, 0, 0.72); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .audit-card__row_bottom { + justify-content: space-between; + margin-top: 2px; + } + + .audit-card__details-link { + color: var(--color-accentedPalette-500); + font-size: 13px; + font-weight: 500; + } + + .audit-feed__paginator { + margin-top: 12px; + background: transparent !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-outer-container, + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-container { + justify-content: space-between !important; + flex-wrap: nowrap !important; + min-height: 48px; + padding: 0 !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size { + margin: 0 !important; + padding: 0 !important; + margin-right: auto !important; + flex: 0 0 auto; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-label { + margin: 0 4px 0 0 !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-select { + margin: 0 8px 0 0 !important; + width: auto !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-form-field-infix { + width: auto !important; + min-width: 36px !important; + padding-right: 4px !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-select { + width: auto !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-select-arrow-wrapper { + padding-left: 6px !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-page-size-select .mat-mdc-text-field-wrapper { + padding: 0 10px !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-range-actions { + flex: 0 0 auto; + margin: 0 !important; + } + + .audit-feed__paginator ::ng-deep .mat-mdc-paginator-range-label { + margin: 0 4px !important; + } +} + +@media (prefers-color-scheme: dark) and (width <= 600px) { + .audit-feed__date-header { + background: var(--surface-dark-color); + color: rgba(255, 255, 255, 0.6); + } + + .audit-card { + background: var(--surface-dark-color); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); + } + + .audit-card__user-email { + color: rgba(255, 255, 255, 0.7); + } + + .audit-card__time { + color: rgba(255, 255, 255, 0.54); + } + + .audit-card__sep { + color: rgba(255, 255, 255, 0.3); + } + + .audit-card__table { + color: rgba(255, 255, 255, 0.72); + } +} diff --git a/frontend/src/app/components/audit/audit.component.html b/frontend/src/app/components/audit/audit.component.html index 7e6c2ca45..fc1926423 100644 --- a/frontend/src/app/components/audit/audit.component.html +++ b/frontend/src/app/components/audit/audit.component.html @@ -1,6 +1,11 @@
-

Audit

+
+ + arrow_back + +

Audit

+
Tables @@ -67,7 +72,49 @@

Rocketadmin can not find any tables

-
+ +
{{ group.date }}
+
+
{{ getInitials(entry.UserEmail) }}
+
+
+ + {{ getUserName(entry.UserEmail) }} + {{ entry.UserEmail }} + + {{ entry.TimeOnly }} +
+
+ {{ entry.ActionIcon }} + {{ entry.Action || '—' }} + · + {{ entry.Table }} +
+
+ + {{ entry.Status === 'successfully' ? 'Success' : (entry.Status || '—') }} + + Details › +
+
+
+
+ + +
+ +
diff --git a/frontend/src/app/components/audit/audit.component.ts b/frontend/src/app/components/audit/audit.component.ts index 2969e8f5a..51b8d5850 100644 --- a/frontend/src/app/components/audit/audit.component.ts +++ b/frontend/src/app/components/audit/audit.component.ts @@ -5,7 +5,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; -import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; +import { MatPaginator, MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator'; import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; import { Title } from '@angular/platform-browser'; @@ -69,6 +69,12 @@ export class AuditComponent implements OnInit { public dataSource: AuditDataSource = null; + public mobileGroupedLogs: { date: string; entries: any[] }[] = []; + + get isMobileView(): boolean { + return typeof window !== 'undefined' && window.innerWidth <= 600; + } + @ViewChild(MatPaginator) paginator: MatPaginator; constructor( @@ -78,7 +84,11 @@ export class AuditComponent implements OnInit { private _companyService: CompanyService, public dialog: MatDialog, private title: Title, - ) {} + private paginatorIntl: MatPaginatorIntl, + ) { + this.paginatorIntl.itemsPerPageLabel = 'Per page:'; + this.paginatorIntl.changes.next(); + } ngAfterViewInit() { this.dataSource.paginator = this.paginator; @@ -100,6 +110,9 @@ export class AuditComponent implements OnInit { this.columns = ['User', 'Table', 'Action', 'Status', 'Date', 'Changes']; this.dataColumns = ['User', 'Table', 'Action', 'Status', 'Date']; this.dataSource = new AuditDataSource(this._connections); + this.dataSource.connect(null as any).subscribe((rows) => { + this.mobileGroupedLogs = this.groupByDate(rows); + }); this.loadLogsPage(); this._tables.fetchTables(this.connectionID).subscribe( @@ -149,4 +162,36 @@ export class AuditComponent implements OnInit { const user = this.usersList.find((u) => u.email === email); return user?.name || null; } + + getInitials(email: string): string { + if (!email) return '?'; + const name = this.getUserName(email); + if (name) { + const parts = name.trim().split(/\s+/); + return parts + .slice(0, 2) + .map((p) => p[0]?.toUpperCase() ?? '') + .join(''); + } + const local = email.split('@')[0]; + const parts = local.split(/[._-]/); + return parts + .slice(0, 2) + .map((p) => p[0]?.toUpperCase() ?? '') + .join(''); + } + + trackByDate(_index: number, group: { date: string }): string { + return group.date; + } + + private groupByDate(rows: any[]): { date: string; entries: any[] }[] { + const groups = new Map(); + for (const row of rows) { + const date = row.DateOnly; + if (!groups.has(date)) groups.set(date, []); + groups.get(date)!.push(row); + } + return Array.from(groups, ([date, entries]) => ({ date, entries })); + } } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css index d6c1c19b8..6882fbdeb 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css @@ -75,7 +75,7 @@ width: calc(100vw - 65px); padding-left: 0; border-left: none; - z-index: 3; + z-index: 100; transition: left 400ms cubic-bezier(0.4, 0, 0.2, 1), width 400ms cubic-bezier(0.4, 0, 0.2, 1), diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts index ee1c17702..4579f2717 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts @@ -198,7 +198,7 @@ export class DbTableViewComponent implements OnInit, OnChanges { private cdr: ChangeDetectorRef, private paginatorIntl: MatPaginatorIntl, ) { - this.paginatorIntl.itemsPerPageLabel = 'per page:'; + this.paginatorIntl.itemsPerPageLabel = 'Per page:'; this.paginatorIntl.changes.next(); } From f7d097913860311d10b04a8e67a5787bb68b0c3b Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 2 Jun 2026 23:43:56 +0300 Subject: [PATCH 04/13] feat(mobile): permissions dialog as bottom sheet + policy/empty-state polish - Permissions (Cedar policy) dialog opens as a mobile bottom sheet via panelClass - Empty state in the policy list gets an inline "Add policy" CTA (accent color), and the dialog actions now keep only one "Add policy" once there's at least one policy - Cancel pinned to the left of the dialog footer; Save stays on the right - User add dialog: swap order so the primary "Add" sits before "Cancel" - Users page on mobile: wrapper full-width with 16px padding, header flex-wraps the New group button if needed, smaller h1 - Connection settings on mobile: keep Primary/Accented color inputs on one row by forcing min-width: 0 through the Material form-field internals Co-Authored-By: Claude Opus 4.7 (1M context) --- .../connection-settings.component.css | 30 +++++++++++++++++++ .../cedar-policy-editor-dialog.component.html | 6 ++-- .../cedar-policy-list.component.css | 10 ++++++- .../cedar-policy-list.component.html | 7 ++++- .../user-add-dialog.component.html | 2 +- .../app/components/users/users.component.css | 22 ++++++++++++++ .../app/components/users/users.component.ts | 2 ++ 7 files changed, 73 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/connection-settings/connection-settings.component.css b/frontend/src/app/components/connection-settings/connection-settings.component.css index da3d1c426..a34b0039d 100644 --- a/frontend/src/app/components/connection-settings/connection-settings.component.css +++ b/frontend/src/app/components/connection-settings/connection-settings.component.css @@ -178,3 +178,33 @@ .audit-toggle { margin-top: 4px; } + +@media (width <= 600px) { + .color-theme { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-gap: 8px; + align-items: center; + } + + .color-item { + min-width: 0; + } + + .color-input { + width: 100%; + min-width: 0; + } + + .color-input ::ng-deep .mat-mdc-text-field-wrapper, + .color-input ::ng-deep .mat-mdc-form-field-flex, + .color-input ::ng-deep .mat-mdc-form-field-infix { + min-width: 0 !important; + width: auto !important; + } + + .color-input ::ng-deep input.mat-mdc-input-element { + padding-left: 24px; + min-width: 0; + } +} diff --git a/frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.html b/frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.html index d82d46ca1..8bdbe20c6 100644 --- a/frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.html +++ b/frontend/src/app/components/users/cedar-policy-editor-dialog/cedar-policy-editor-dialog.component.html @@ -48,14 +48,14 @@

Policy — {{ data.groupTitle }}

} - @if (editorMode() === 'form' && !formParseError() && !loading() && !policyList?.showAddForm) { + + + @if (editorMode() === 'form' && !formParseError() && !loading() && !policyList?.showAddForm && policyList?.policies()?.length) { } - - } diff --git a/frontend/src/app/components/users/user-add-dialog/user-add-dialog.component.html b/frontend/src/app/components/users/user-add-dialog/user-add-dialog.component.html index 5a468467c..7501c2261 100644 --- a/frontend/src/app/components/users/user-add-dialog/user-add-dialog.component.html +++ b/frontend/src/app/components/users/user-add-dialog/user-add-dialog.component.html @@ -26,7 +26,6 @@

Add user to {{ data.group.title }} group - @if (data.availableMembers.length) { diff --git a/frontend/src/app/components/users/users.component.css b/frontend/src/app/components/users/users.component.css index cdd58c670..14d9166d3 100644 --- a/frontend/src/app/components/users/users.component.css +++ b/frontend/src/app/components/users/users.component.css @@ -131,3 +131,25 @@ header { .no-access { margin-top: 32px !important; } + +@media (width <= 600px) { + .wrapper { + width: 100%; + padding: 0 16px; + box-sizing: border-box; + } + + header { + flex-wrap: wrap; + gap: 8px; + margin: 16px 0 12px; + } + + .add-group-button { + flex: 0 0 auto; + } + + .mat-h1 { + font-size: 22px; + } +} diff --git a/frontend/src/app/components/users/users.component.ts b/frontend/src/app/components/users/users.component.ts index 1dcd05419..ceda8e065 100644 --- a/frontend/src/app/components/users/users.component.ts +++ b/frontend/src/app/components/users/users.component.ts @@ -157,6 +157,7 @@ export class UsersComponent implements OnInit { if (createdGroup) { this._dialog.open(CedarPolicyEditorDialogComponent, { width: '40em', + panelClass: 'mobile-bottom-sheet-dialog', data: { groupId: createdGroup.id, groupTitle: createdGroup.title, cedarPolicy: null }, }); } @@ -194,6 +195,7 @@ export class UsersComponent implements OnInit { openCedarPolicyDialog(group: UserGroup) { this._dialog.open(CedarPolicyEditorDialogComponent, { width: '40em', + panelClass: 'mobile-bottom-sheet-dialog', data: { groupId: group.id, groupTitle: group.title, cedarPolicy: group.cedarPolicy }, }); } From 37cf40ea00cbc7fe3e2aa40e12532d91d5e0f1fc Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Mon, 1 Jun 2026 14:18:02 +0300 Subject: [PATCH 05/13] wip(auto-configure): scaffold streaming progress UI (awaiting backend) - ConfigurationStateService: replace the blocking GET on /ai/v2/setup/:id with a fetch + ReadableStream consumer that parses newline-delimited JSON or SSE-style "data:" lines and pushes typed ConfigProgress events to a progress$ BehaviorSubject. Falls back cleanly when the body is empty (current backend behavior). - AutoConfigureComponent: subscribes to progress$ and exposes a signal + computed percent; renders the current step / table name and a determinate progress bar fill when total is known, otherwise keeps the indeterminate look. - Template shows "Configuring

" with a "X of N" counter once the backend starts streaming events. Backend endpoint update pending. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../auto-configure.component.html | 17 +++++- .../auto-configure.component.ts | 24 ++++++-- .../services/configuration-state.service.ts | 61 +++++++++++++++++-- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/components/auto-configure/auto-configure.component.html b/frontend/src/app/components/auto-configure/auto-configure.component.html index 7e818bf08..7383cd366 100644 --- a/frontend/src/app/components/auto-configure/auto-configure.component.html +++ b/frontend/src/app/components/auto-configure/auto-configure.component.html @@ -7,8 +7,21 @@

Configuring your database

We're analyzing your structure and applying the best settings. This is running in the background.

-
-
+ +

+ + {{ p.step || 'Configuring' }} + {{ p.table }} + + {{ p.message }} + + · {{ p.current }} of {{ p.total }} + +

+ +
+