Skip to content

Commit d6513c7

Browse files
Merge pull request #1201 from rocket-admin/records-view
Records view
2 parents 5ce6e52 + 6904edc commit d6513c7

11 files changed

Lines changed: 231 additions & 42 deletions

frontend/src/app/components/dashboard/dashboard.component.html

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ <h3 class='mat-subheading-2'>Rocketadmin can not find any tables</h3>
9393
</app-db-table>
9494
</div>
9595
<app-db-table-row-view *ngIf="selectedRow"
96-
[columns]="dataSource.columns"
97-
[foreignKeys]="dataSource.foreignKeys"
98-
[foreignKeysList]="dataSource.foreignKeysList"
99-
[widgets]="dataSource.widgets"
100-
[widgetsList]="dataSource.widgetsList"
10196
[activeFilters]="filters"
10297
></app-db-table-row-view>
10398
<app-db-table-ai-panel *ngIf="isAIpanelOpened"

frontend/src/app/components/dashboard/db-table-row-view/db-table-row-view.component.html

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
<div class="row-preview-sidebar__header">
55
<h2 class="mat-heading-2 row-preview-sidebar__title">Preview</h2>
66
<div class="row-preview-sidebar__actions">
7-
<button mat-icon-button
7+
<button mat-icon-button *ngIf="selectedRow.link"
88
matTooltip="Copy link to this record"
99
[cdkCopyToClipboard]="getDedicatedPageLink()"
1010
(cdkCopyToClipboardCopied)="showCopyNotification('Link to this record was copied to clipboard.')">
1111
<mat-icon>link</mat-icon>
1212
</button>
13-
<a mat-icon-button
13+
<a mat-icon-button *ngIf="selectedRow.link"
1414
[routerLink]="selectedRow.link"
1515
[queryParams]="selectedRow.primaryKeys"
1616
matTooltip="Open the record"
@@ -24,22 +24,27 @@ <h2 class="mat-heading-2 row-preview-sidebar__title">Preview</h2>
2424
</div>
2525

2626
<br />
27-
<div *ngFor="let column of columns" class="row-preview-sidebar__field">
28-
<strong>{{column.normalizedTitle}}</strong>
29-
<span *ngIf="isForeignKey(column.title); else recordContent" class="row-preview-sidebar__field-value">
30-
{{getForeignKeyValue(column.title)}}
31-
</span>
32-
<ng-template #recordContent>
33-
<div *ngIf="isWidget(column.title); else stringValue">
34-
<div *ngIf="widgets[column.title].widget_type === 'Image'">
35-
<img [src]="selectedRow.record[column.title]" alt="Image" class="row-preview-sidebar__image">
27+
<ng-container *ngIf="selectedRow && selectedRow.record; else loadingContent">
28+
<div *ngFor="let column of columns" class="row-preview-sidebar__field">
29+
<strong>{{column.normalizedTitle}}</strong>
30+
<span *ngIf="isForeignKey(column.title); else recordContent" class="row-preview-sidebar__field-value">
31+
{{getForeignKeyValue(column.title)}}
32+
</span>
33+
<ng-template #recordContent>
34+
<div *ngIf="isWidget(column.title); else stringValue">
35+
<div *ngIf="selectedRow.widgets[column.title].widget_type === 'Image'">
36+
<img [src]="selectedRow.record[column.title]" alt="Image" class="row-preview-sidebar__image">
37+
</div>
38+
<span class="row-preview-sidebar__field-value">{{selectedRow.record[column.title] || '—'}}</span>
3639
</div>
37-
<span class="row-preview-sidebar__field-value">{{selectedRow.record[column.title] || '—'}}</span>
38-
</div>
39-
<ng-template #stringValue>
40-
<span class="row-preview-sidebar__field-value">{{selectedRow.record[column.title] || '—'}}</span>
40+
<ng-template #stringValue>
41+
<span class="row-preview-sidebar__field-value">{{selectedRow.record[column.title] || '—'}}</span>
42+
</ng-template>
4143
</ng-template>
42-
</ng-template>
44+
</div>
45+
</ng-container>
4346

44-
</div>
47+
<ng-template #loadingContent>
48+
<app-placeholder-record-view></app-placeholder-record-view>
49+
</ng-template>
4550
</div>

frontend/src/app/components/dashboard/db-table-row-view/db-table-row-view.component.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
66
import { CommonModule } from '@angular/common';
77
import { MatButtonModule } from '@angular/material/button';
88
import { MatIconModule } from '@angular/material/icon';
9+
import { MatTooltipModule } from '@angular/material/tooltip';
910
import { NotificationsService } from 'src/app/services/notifications.service';
1011
import { TableStateService } from 'src/app/services/table-state.service';
11-
import { MatTooltipModule } from '@angular/material/tooltip';
12+
import { normalizeFieldName } from 'src/app/lib/normalize';
13+
import { PlaceholderRecordViewComponent } from '../../skeletons/placeholder-record-view/placeholder-record-view.component';
1214

1315
@Component({
1416
selector: 'app-db-table-row-view',
@@ -20,18 +22,18 @@ import { MatTooltipModule } from '@angular/material/tooltip';
2022
ClipboardModule,
2123
MatTooltipModule,
2224
RouterModule,
23-
CommonModule
25+
CommonModule,
26+
PlaceholderRecordViewComponent
2427
]
2528
})
2629
export class DbTableRowViewComponent implements OnInit {
27-
@Input() columns: object[];
28-
@Input() foreignKeys: object;
29-
@Input() foreignKeysList: string[];
30-
@Input() widgets: { string: Widget };
31-
@Input() widgetsList: string[];
3230
@Input() activeFilters: object;
3331

3432
public selectedRow: TableRow;
33+
public columns: {
34+
title: string;
35+
normalizedTitle: string;
36+
}[] = [];
3537

3638
constructor(
3739
private _tableState: TableStateService,
@@ -42,28 +44,37 @@ export class DbTableRowViewComponent implements OnInit {
4244
ngOnInit(): void {
4345
this._tableState.cast.subscribe((row) => {
4446
this.selectedRow = row;
47+
if (row.columnsOrder) {
48+
const columnsOrder = this.selectedRow.columnsOrder.length ? this.selectedRow.columnsOrder : Object.keys(this.selectedRow.record);
49+
this.columns = columnsOrder.map(column => {
50+
return {
51+
title: column,
52+
normalizedTitle: normalizeFieldName(column)
53+
}
54+
})
55+
}
4556
});
4657
}
4758

4859
isForeignKey(columnName: string) {
49-
return this.foreignKeysList.includes(columnName);
60+
return this.selectedRow.foreignKeysList.includes(columnName);
5061
}
5162

5263
getForeignKeyValue(field: string) {
5364
if (this.selectedRow) {
54-
const identityColumnName = Object.keys(this.selectedRow.record[field]).find(key => key !== this.foreignKeys[field].referenced_column_name);
65+
const identityColumnName = Object.keys(this.selectedRow.record[field]).find(key => key !== this.selectedRow.foreignKeys[field].referenced_column_name);
5566
if (identityColumnName) {
5667
return this.selectedRow.record[field][identityColumnName];
5768
} else {
58-
const referencedColumnName = this.foreignKeys[field].referenced_column_name;
59-
return this.selectedRow.record[field][referencedColumnName];
69+
// const referencedColumnName = this.selectedRow.foreignKeys[field].referenced_column_name;
70+
return this.selectedRow.record[field];
6071
}
6172
};
6273
return '';
6374
}
6475

6576
isWidget(columnName: string) {
66-
return this.widgetsList.includes(columnName);
77+
return this.selectedRow.widgetsList.includes(columnName);
6778
}
6879

6980
getDedicatedPageLink() {

frontend/src/app/components/dashboard/db-table/db-table.component.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,17 @@ tr.mat-row:hover {
533533
top: 40px;
534534
}
535535

536-
.foreign-key-link {
536+
.foreign-key-button {
537+
background: transparent;
538+
border: none;
537539
color: var(--color-accentedPalette-500);
540+
text-decoration: underline;
541+
font-size: inherit;
542+
}
543+
544+
.foreign-key-button_selected {
545+
color: var(--color-accentedPalette-700);
546+
font-weight: 600;
538547
}
539548

540549
.empty-table {

frontend/src/app/components/dashboard/db-table/db-table.component.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,16 @@ <h2 class="mat-h2 table-name">{{ displayName }}</h2>
217217
<mat-cell *matCellDef="let element; let i = index" [attr.data-label]="tableData.dataNormalizedColumns[column]" class="db-table-cell" data-hj-suppress>
218218
<div class="table-cell-content">
219219
<span *ngIf="isForeignKey(column); else contentCell" class="field-value">
220-
<a routerLink="/dashboard/{{connectionID}}/{{tableData.foreignKeys[column].referenced_table_name}}/entry"
220+
<!--<a routerLink="/dashboard/{{connectionID}}/{{tableData.foreignKeys[column].referenced_table_name}}/entry"
221221
class="foreign-key-link"
222222
[queryParams]="getForeignKeyQueryParams(tableData.foreignKeys[column], element[column])">
223223
{{ getCellValue(tableData.foreignKeys[column], element[column]) }}
224-
</a>
224+
</a>-->
225+
<button class="foreign-key-button" type="button"
226+
[ngClass]="{'foreign-key-button_selected': isForeignKeySelected(element[column], tableData.foreignKeys[column])}"
227+
(click)="handleForeignKeyView($event, tableData.foreignKeys[column], element[column])">
228+
{{ getCellValue(tableData.foreignKeys[column], element[column]) }}
229+
</button>
225230
<button type="button" mat-icon-button
226231
class="field-value-copy-button"
227232
matTooltip="Copy"
@@ -347,7 +352,7 @@ <h2 class="mat-h2 table-name">{{ displayName }}</h2>
347352
<mat-row *matRowDef="let row; columns: tableData.actionsColumnWidth === '0' ? tableData.displayedDataColumns : tableData.displayedColumns;"
348353
class="db-table-row"
349354
[ngClass]="{'db-table-row_selected': isRowSelected(tableData.getQueryParams(row))}"
350-
(click)="handleViewRow({record: row, primaryKeys: tableData.getQueryParams(row)})">
355+
(click)="handleViewRow(row)">
351356
</mat-row>
352357
</mat-table>
353358
</div>

frontend/src/app/components/dashboard/db-table/db-table.component.ts

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import * as JSON5 from 'json5';
2+
13
import { ActivatedRoute, Router } from '@angular/router';
24
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
3-
import { CustomAction, TableForeignKey, TablePermissions, TableProperties, TableRow } from 'src/app/models/table';
5+
import { CustomAction, TableForeignKey, TablePermissions, TableProperties, TableRow, Widget } from 'src/app/models/table';
46
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
57
import { Observable, merge, of } from 'rxjs';
68
import { map, startWith, tap } from 'rxjs/operators';
@@ -33,6 +35,7 @@ import { NotificationsService } from 'src/app/services/notifications.service';
3335
import { PlaceholderTableDataComponent } from '../../skeletons/placeholder-table-data/placeholder-table-data.component';
3436
import { RouterModule } from '@angular/router';
3537
import { SelectionModel } from '@angular/cdk/collections';
38+
import { TableRowService } from 'src/app/services/table-row.service';
3639
import { TableStateService } from 'src/app/services/table-state.service';
3740
import { normalizeTableName } from '../../../lib/normalize'
3841

@@ -112,6 +115,7 @@ export class DbTableComponent implements OnInit {
112115
lte: "<="
113116
}
114117
public selectedRow: TableRow = null;
118+
public selectedRowType: 'record' | 'foreignKey';
115119

116120
@Input() set table(value){
117121
if (value) this.tableData = value;
@@ -123,6 +127,7 @@ export class DbTableComponent implements OnInit {
123127
constructor(
124128
private _tableState: TableStateService,
125129
private _notifications: NotificationsService,
130+
private _tableRow: TableRowService,
126131
private route: ActivatedRoute,
127132
public router: Router,
128133
public dialog: MatDialog,
@@ -383,15 +388,90 @@ export class DbTableComponent implements OnInit {
383388
}
384389

385390
handleViewRow(row: TableRow) {
386-
this._tableState.selectRow({...row, link: `/dashboard/${this.connectionID}/${this.name}/entry`});
391+
this.selectedRowType = 'record';
392+
this._tableState.selectRow({
393+
tableName: this.name,
394+
record: row,
395+
columnsOrder: this.tableData.dataColumns,
396+
primaryKeys: this.tableData.getQueryParams(row),
397+
foreignKeys: this.tableData.foreignKeys,
398+
foreignKeysList: this.tableData.foreignKeysList,
399+
widgets: this.tableData.widgets,
400+
widgetsList: this.tableData.widgetsList,
401+
link: `/dashboard/${this.connectionID}/${this.name}/entry`
402+
});
403+
}
404+
405+
handleForeignKeyView(event, foreignKeys, row) {
406+
event.stopPropagation();
407+
this.selectedRowType = 'foreignKey';
408+
409+
console.log('handleForeignKeyView', foreignKeys, row);
410+
411+
this._tableState.selectRow({
412+
tableName: null,
413+
record: null,
414+
columnsOrder: null,
415+
primaryKeys: null,
416+
foreignKeys: null,
417+
foreignKeysList: null,
418+
widgets: null,
419+
widgetsList: null,
420+
link: null
421+
})
422+
423+
this._tableRow.fetchTableRow(this.connectionID, foreignKeys.referenced_table_name, {[foreignKeys.referenced_column_name]: row[foreignKeys.referenced_column_name]})
424+
.subscribe(res => {
425+
console.log('Fetched foreign key row:', res);
426+
this._tableState.selectRow({
427+
tableName: foreignKeys.referenced_table_name,
428+
record: res.row,
429+
columnsOrder: res.list_fields,
430+
primaryKeys: {
431+
[foreignKeys.referenced_column_name]: res.row[foreignKeys.referenced_column_name]
432+
},
433+
foreignKeys: Object.assign({}, ...res.foreignKeys.map((foreignKey: TableForeignKey) => ({[foreignKey.column_name]: foreignKey}))),
434+
foreignKeysList: res.foreignKeys.map(fk => fk.column_name),
435+
widgets: Object.assign({}, ...res.table_widgets.map((widget: Widget) => {
436+
let parsedParams;
437+
438+
try {
439+
parsedParams = JSON5.parse(widget.widget_params);
440+
} catch {
441+
parsedParams = {};
442+
}
443+
444+
return {
445+
[widget.field_name]: {
446+
...widget,
447+
widget_params: parsedParams,
448+
},
449+
};
450+
})
451+
),
452+
widgetsList: res.table_widgets.map(widget => widget.field_name),
453+
link: `/dashboard/${this.connectionID}/${foreignKeys.referenced_table_name}/entry`
454+
});
455+
})
387456
}
388457

389458
handleViewAIpanel() {
390459
this._tableState.handleViewAIpanel();
391460
}
392461

393462
isRowSelected(primaryKeys) {
394-
return this.selectedRow && Object.keys(this.selectedRow.primaryKeys).length && JSON.stringify(this.selectedRow.primaryKeys) === JSON.stringify(primaryKeys);
463+
if (this.selectedRowType === 'record' && this.selectedRow.primaryKeys !== null) return this.selectedRow && Object.keys(this.selectedRow.primaryKeys).length && JSON.stringify(this.selectedRow.primaryKeys) === JSON.stringify(primaryKeys);
464+
return false;
465+
}
466+
467+
isForeignKeySelected(record, foreignKey: TableForeignKey) {
468+
if (this.selectedRow) console.log('isForeignKeySelected', this.selectedRow.primaryKeys, foreignKey);
469+
const primaryKeyValue = record[foreignKey.referenced_column_name];
470+
471+
if (this.selectedRowType === 'foreignKey' && this.selectedRow && this.selectedRow.record !== null) {
472+
return Object.values(this.selectedRow.primaryKeys)[0] === primaryKeyValue && this.selectedRow.tableName === foreignKey.referenced_table_name;
473+
}
474+
return false;
395475
}
396476

397477
showCopyNotification(message: string) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.record-field {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 4px;
5+
padding: 12px 16px;
6+
}
7+
8+
.record-field:not(:last-child) {
9+
border-bottom: solid 1px rgba(0, 0, 0, 0.12);
10+
}
11+
12+
.record-field__key {
13+
mix-blend-mode: normal !important;
14+
height: 20px;
15+
width: 120px;
16+
}
17+
18+
.record-field__value {
19+
mix-blend-mode: normal !important;
20+
height: 20px;
21+
width: 200px
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<div class="wrapper skeleton">
2+
<div class="record-field">
3+
<div class="bone record-field__key"></div>
4+
<div class="bone record-field__value"></div>
5+
</div>
6+
<div class="record-field">
7+
<div class="bone record-field__key"></div>
8+
<div class="bone record-field__value"></div>
9+
</div>
10+
<div class="record-field">
11+
<div class="bone record-field__key"></div>
12+
<div class="bone record-field__value"></div>
13+
</div>
14+
<div class="record-field">
15+
<div class="bone record-field__key"></div>
16+
<div class="bone record-field__value"></div>
17+
</div>
18+
<div class="record-field">
19+
<div class="bone record-field__key"></div>
20+
<div class="bone record-field__value"></div>
21+
</div>
22+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { PlaceholderRecordViewComponent } from './placeholder-record-view.component';
4+
5+
describe('PlaceholderRecordViewComponent', () => {
6+
let component: PlaceholderRecordViewComponent;
7+
let fixture: ComponentFixture<PlaceholderRecordViewComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [PlaceholderRecordViewComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(PlaceholderRecordViewComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});

0 commit comments

Comments
 (0)