Add System component with table display for custom and all beans

This commit is contained in:
liosha84
2025-06-19 18:36:09 +03:00
parent 4e7af10940
commit ab36ee35b4
24 changed files with 919 additions and 57 deletions
+5 -2
View File
@@ -10,6 +10,8 @@ import { ProfileComponent } from './components/profile/profile.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
import { HomeComponent } from './components/home/home.component';
import {SystemComponent} from './components/system.component/system.component';
const routes: Routes = [
{ path: 'home', component: HomeComponent },
@@ -22,11 +24,12 @@ const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'tutorials', component: TutorialsListComponent },
{ path: 'tutorials/:id', component: TutorialDetailsComponent },
{ path: 'add', component: AddTutorialComponent }
{ path: 'add', component: AddTutorialComponent },
{ path: 'system', component: SystemComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {useHash: true})],
exports: [RouterModule]
})
export class AppRoutingModule { }
export class AppRoutingModule { }
+8 -50
View File
@@ -1,52 +1,3 @@
<div>
<!-- <nav class="navbar navbar-expand navbar-dark bg-dark">
<a href="#" class="navbar-brand">Spring-AngularTS</a>
<ul class="navbar-nav mr-auto" routerLinkActive="active">
<li class="nav-item">
<a routerLink="tutorials" class="nav-link">Tutorials</a>
</li>
<li class="nav-item">
<a routerLink="add" class="nav-link">Add</a>
</li>
<li class="nav-item" *ngIf="showAdminBoard">
<a href="/admin" class="nav-link" routerLink="admin">Admin Board</a>
</li>
<li class="nav-item" *ngIf="showModeratorBoard">
<a href="/mod" class="nav-link" routerLink="mod">Moderator Board</a>
</li>
<li class="nav-item">
<a href="/user" class="nav-link" *ngIf="isLoggedIn" routerLink="user">User</a>
</li>
</ul>
<ul class="navbar-nav ml-auto" *ngIf="!isLoggedIn">
<li class="nav-item">
<a href="/register" class="nav-link" routerLink="register">Sign Up</a>
</li>
<li class="nav-item">
<a href="/login" class="nav-link" routerLink="login">Login</a>
</li>
</ul>
<ul class="navbar-nav ml-auto" *ngIf="isLoggedIn">
<li class="nav-item">
<a href="/profile" class="nav-link" routerLink="profile">{{ username }}</a>
</li>
<li class="nav-item">
<a href class="nav-link" (click)="logout()">LogOut</a>
</li>
</ul>
</nav>-->
<div class="card flex justify-center">
<p-button label="Check" />
</div>
<mat-toolbar class="fixed-top">
<button mat-raised-button routerLink="/" href="#">
@@ -67,6 +18,13 @@
</button>
}
@if (showAdminBoard) {
<button mat-raised-button routerLink="system" >
<mat-icon>settings_applications</mat-icon>
System
</button>
}
<span class="example-spacer"></span>
<button mat-raised-button routerLink="register" *ngIf="!isLoggedIn">
<mat-icon>app_registration</mat-icon>
@@ -94,4 +52,4 @@
<div class="router_outlet_padding">
<router-outlet></router-outlet>
</div>
</div>
@@ -0,0 +1,110 @@
<mat-card>
<mat-card-header>
<mat-card-title>System</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-divider></mat-divider>
<mat-tab-group>
<mat-tab label="Custom beans">
Content 1
<table mat-table
[dataSource]="customBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
@for (column of displayedColumns; track column) {
<ng-container matColumnDef="{{column}}">
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
</ng-container>
}
<ng-container matColumnDef="expand">
<th mat-header-cell *matHeaderCellDef aria-label="row actions">&nbsp;</th>
<td mat-cell *matCellDef="let element">
<button
matIconButton
aria-label="expand row"
(click)="toggle(element); $event.stopPropagation()"
class="example-toggle-button"
[class.example-toggle-button-expanded]="isExpanded(element)">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
</td>
</ng-container>
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
<div class="example-element-detail-wrapper"
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
<div class="example-element-detail">
<mat-list role="list">
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
</mat-list>
</div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="isExpanded(element)"
(click)="toggle(element)">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>
</mat-tab>
<mat-tab label="All beans">
Content 2
<table mat-table
[dataSource]="allBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
@for (column of displayedColumns; track column) {
<ng-container matColumnDef="{{column}}">
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
</ng-container>
}
<ng-container matColumnDef="expand">
<th mat-header-cell *matHeaderCellDef aria-label="row actions">&nbsp;</th>
<td mat-cell *matCellDef="let element">
<button
matIconButton
aria-label="expand row"
(click)="toggle(element); $event.stopPropagation()"
class="example-toggle-button"
[class.example-toggle-button-expanded]="isExpanded(element)">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
</td>
</ng-container>
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
<ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
<div class="example-element-detail-wrapper"
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
<div class="example-element-detail">
<mat-list role="list">
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
</mat-list>
</div>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
class="example-element-row"
[class.example-expanded-row]="isExpanded(element)"
(click)="toggle(element)">
</tr>
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
@@ -0,0 +1,113 @@
mat-card{
margin: 20px;
background-color: rgba(240, 248, 255, 0.7);
backdrop-filter: blur(8px);
}
mat-card-title{
color: cyan;
}
mat-card-subtitle{
color: #009dff;
}
mat-tab{
color:black;
}
table {
width: 100%;
margin-top: 20px;
color:lightgrey;
}
mat-list{
//background-color: #1389d3;
color:black;
width: 100%;
}
mat-list-item{
margin: 10px 10px 10px 10px;
background-color: #447694;
color: #009dff;
}
tr.example-detail-row {
height: 0;
background: #fffdfd;
}
tr.example-element-row {
cursor: pointer;
color: #1389d3;
background: #fffdfd;
}
tr.example-element-row:not(.example-expanded-row):hover {
background: whitesmoke;
}
tr.example-element-row:not(.example-expanded-row):active {
background: #efefef;
}
.example-element-row td {
border-bottom-width: 0;
}
.example-element-detail-wrapper {
overflow: hidden;
display: grid;
grid-template-rows: 0fr;
grid-template-columns: 100%;
transition: grid-template-rows 225ms cubic-bezier(0.4, 0, 0.2, 1);
}
.example-element-detail-wrapper-expanded {
grid-template-rows: 1fr;
}
.example-element-detail {
display: flex;
min-height: 0;
}
.example-element-diagram {
min-width: 80px;
border: 2px solid black;
padding: 8px;
font-weight: lighter;
margin: 8px 0;
height: 104px;
}
.example-element-symbol {
font-weight: bold;
font-size: 40px;
line-height: normal;
}
.example-element-description {
padding: 16px;
}
.example-element-description-attribution {
opacity: 0.5;
}
.example-toggle-button {
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
}
.example-toggle-button-expanded {
transform: rotate(180deg);
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SystemComponent } from './system.component';
describe('SystemComponent', () => {
let component: SystemComponent;
let fixture: ComponentFixture<SystemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SystemComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SystemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,148 @@
import { Component } from '@angular/core';
import {Bean} from '../../models/Bean';
import {SystemService} from '../../services/system.service';
import {
MatCard,
MatCardContent,
MatCardHeader,
MatCardSubtitle,
MatCardTitle,
MatCardTitleGroup
} from '@angular/material/card';
import {MatDivider} from '@angular/material/divider';
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef, MatHeaderRow,
MatHeaderRowDef, MatRow, MatRowDef,
MatTable
} from '@angular/material/table';
import {MatIconButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {MatList, MatListItem} from '@angular/material/list';
import {MatTab, MatTabGroup} from '@angular/material/tabs';
@Component({
selector: 'app-system.component',
imports: [
MatCard,
MatCardSubtitle,
MatCardContent,
MatCardHeader,
MatCardTitle,
MatDivider,
MatTable,
MatColumnDef,
MatHeaderCell,
MatCell,
MatIconButton,
MatIcon,
MatHeaderCellDef,
MatCellDef,
MatHeaderRowDef,
MatRowDef,
MatRow,
MatHeaderRow,
MatList,
MatListItem,
MatTabGroup,
MatTab
],
templateUrl: './system.component.html',
styleUrl: './system.component.scss'
})
export class SystemComponent {
// @ViewChild(MatPaginator) paginator: MatPaginator;
// @ViewChild(MatSort) sort: MatSort;
customBeans:Bean[] = [] ;
allBeans:Bean[] = [] ;
displayedColumns: string[] = ['shortName', 'typeShortName', 'scope'];
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
expandedElement: Bean | null = null;
resultsLength = 0;
isLoadingResults = true;
isRateLimitReached = false;
constructor(private systemService: SystemService) { }
ngOnInit(): void {
this.retrieveBeans();
}
retrieveBeans(): void {
this.systemService.getCustomBeans()
.subscribe(
(data: Bean[]) => {
this.customBeans = data;
console.log(data);
},
(error: any) => {
console.log(error);
});
this.systemService.getAllBeans()
.subscribe(
(data: Bean[]) => {
this.allBeans = data;
console.log(data);
},
(error: any) => {
console.log(error);
});
}
/** Checks whether an element is expanded. */
isExpanded(element: Bean) {
return this.expandedElement === element;
}
/** Toggles the expanded state of an element. */
toggle(element: Bean) {
this.expandedElement = this.isExpanded(element) ? null : element;
}
ngAfterViewInit() {
//this.exampleDatabase = new ExampleHttpDatabase(this._httpClient);
// If the user changes the sort order, reset back to the first page.
/* this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.exampleDatabase!.getRepoIssues(
this.sort.active,
this.sort.direction,
this.paginator.pageIndex,
).pipe(catchError(() => observableOf(null)));
}),
map(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
this.isRateLimitReached = data === null;
if (data === null) {
return [];
}
// Only refresh the result length if there is new data. In case of rate
// limit errors, we do not want to reset the paginator to zero, as that
// would prevent users from re-triggering requests.
this.resultsLength = data.total_count;
return data.items;
}),
)
.subscribe(data => (this.data = data));*/
}
}
@@ -0,0 +1,8 @@
import { ColumnResizeDirective } from './column-resize.directive';
describe('ColumnResizeDirective', () => {
it('should create an instance', () => {
const directive = new ColumnResizeDirective();
expect(directive).toBeTruthy();
});
});
@@ -0,0 +1,114 @@
import {
Directive,
ElementRef,
OnDestroy,
OnInit,
Renderer2,
NgZone,
Input,
} from '@angular/core';
import { Subject, fromEvent, takeUntil } from 'rxjs';
@Directive({
selector: '[appColumnResize]',
})
export class ColumnResizeDirective implements OnInit, OnDestroy {
@Input() resizableTable: HTMLElement | null = null;
private isResizing = false;
private startX!: number;
private startWidth!: number;
private column: HTMLElement;
private table: HTMLElement | null = null;
private resizer!: HTMLElement;
private destroy$ = new Subject<void>();
constructor(
private el: ElementRef,
private renderer: Renderer2,
private zone: NgZone
) {
this.column = this.el.nativeElement;
}
ngOnInit() {
this.table = this.resizableTable || this.findParentTable(this.column);
if (!this.table) {
console.error(
'Parent table not found. Make sure the directive is applied to a th element within a table.'
);
return;
}
this.createResizer();
this.initializeResizeListener();
}
private createResizer() {
this.resizer = this.renderer.createElement('div');
this.renderer.addClass(this.resizer, 'column-resizer');
this.renderer.setStyle(this.resizer, 'position', 'absolute');
this.renderer.setStyle(this.resizer, 'right', '0');
this.renderer.setStyle(this.resizer, 'top', '0');
this.renderer.setStyle(this.resizer, 'height', '100%');
this.renderer.setStyle(this.resizer, 'width', '5px');
this.renderer.setStyle(this.resizer, 'cursor', 'col-resize');
this.renderer.appendChild(this.column, this.resizer);
}
private initializeResizeListener() {
this.zone.runOutsideAngular(() => {
fromEvent(this.resizer, 'mousedown')
.pipe(takeUntil(this.destroy$))
.subscribe((e: Event) => this.onMouseDown(e as MouseEvent));
fromEvent(document, 'mousemove')
.pipe(takeUntil(this.destroy$))
.subscribe((e: Event) => this.onMouseMove(e as MouseEvent));
fromEvent(document, 'mouseup')
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.onMouseUp());
});
}
private onMouseDown(e: MouseEvent) {
e.preventDefault();
this.isResizing = true;
this.startX = e.pageX;
this.startWidth = this.column.offsetWidth;
this.renderer.addClass(this.column, 'resizing');
if (this.table) {
this.renderer.addClass(this.table, 'resizing');
}
}
private onMouseMove(e: MouseEvent) {
if (!this.isResizing) return;
const width = this.startWidth + (e.pageX - this.startX);
this.renderer.setStyle(this.column, 'width', `${width}px`);
}
private onMouseUp() {
if (!this.isResizing) return;
this.isResizing = false;
this.renderer.removeClass(this.column, 'resizing');
if (this.table) {
this.renderer.removeClass(this.table, 'resizing');
}
}
private findParentTable(element: HTMLElement): HTMLElement | null {
while (element && element.tagName !== 'TABLE') {
element = element.parentElement as HTMLElement;
if (!element) return null;
}
return element;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
+7
View File
@@ -0,0 +1,7 @@
export class Bean {
name?:string;
shortName?:string;
type?:string;
typeShortName?:string;
scope?:string;
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SystemService } from './system.service';
describe('SystemService', () => {
let service: SystemService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SystemService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Tutorial} from '../models/tutorial.model';
import {Bean} from '../models/Bean';
@Injectable({
providedIn: 'root'
})
export class SystemService {
baseUrl : string = 'http://localhost:8080/api/system';
constructor(private http: HttpClient) { }
getAllBeans(): Observable<Bean[]> {
return this.http.get<Bean[]>(this.baseUrl+"/beans");
}
getCustomBeans(): Observable<Bean[]> {
return this.http.get<Bean[]>(this.baseUrl+"/beans/custom");
}
}