Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.

Commit ecd453f

Browse files
committed
Enhanced design by a lot
1 parent 2fc8376 commit ecd453f

26 files changed

+191
-114
lines changed

EEDU-Frontend/src/app/common/abstract-list/abstract-list.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface ListItemInfo<T> {
2525
title: (value: T) => string;
2626
icon?: (value: T) => string;
2727
chips?: (value: T) => string[];
28+
content?: Type<ListItemContent<T>>;
2829
}
2930

3031
export interface GeneralListInfo<T> {
@@ -41,7 +42,6 @@ export class AbstractList<T> {
4142

4243
public readonly itemInfo: InputSignal<ListItemInfo<T> | null> = input<ListItemInfo<T> | null>(null);
4344

44-
public readonly componentContent: InputSignal<Type<ListItemContent<T>> | null> = input<Type<ListItemContent<T>> | null>(null);
4545
public readonly generalListInfo: InputSignal<GeneralListInfo<T> | null> = input<GeneralListInfo<T> | null>(null);
4646
public readonly selectionType: InputSignal<SelectionType> = input<SelectionType>(SelectionType.SINGLE);
4747
public readonly height: InputSignal<number | undefined> = input<number | undefined>();
@@ -73,11 +73,11 @@ export class AbstractList<T> {
7373
protected get hasChips(): boolean { return !!this.itemInfo()!.chips; }
7474

7575
protected get hasContent(): boolean {
76-
return !!this.componentContent();
76+
return !!this.itemInfo()?.content;
7777
}
7878

7979
protected get content(): Type<any> {
80-
return this.componentContent()!;
80+
return this.itemInfo()!.content!;
8181
}
8282

8383
protected get partiallySelected(): boolean {

EEDU-Frontend/src/app/common/abstract-list/checkboxes/single-check-box.component.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,21 @@ import {NgIf} from "@angular/common";
55
import {MatCheckbox} from "@angular/material/checkbox";
66

77
@Component({
8-
selector: 'single-checkbox',
9-
template: `
8+
selector: 'single-checkbox', template: `
109
<a matListItemTitle>
1110
<mat-checkbox
1211
*ngIf="show()"
1312
[checked]="checked()"
1413
(click)="$event.stopPropagation()"
1514
(change)="onToggle.emit()"
1615
(keydown)="keyboardEvent.emit($event)">
16+
<a class="title">
17+
<mat-icon class="icon" *ngIf="icon()">{{ icon() }}</mat-icon>
18+
{{ title() }}
19+
</a>
1720
</mat-checkbox>
18-
<mat-icon class="icon" *ngIf="icon()">{{ icon() }}</mat-icon>
19-
<a class="title">{{ title() }}</a>
2021
</a>
21-
`,
22-
imports: [
23-
MatCheckbox,
24-
NgIf,
25-
MatListItemTitle,
26-
MatIcon
27-
]
22+
`, styles: '.title { user-select: none }', imports: [MatCheckbox, NgIf, MatListItemTitle, MatIcon]
2823
})
2924
export class SingleCheckBoxComponent {
3025
@Output() public readonly keyboardEvent: EventEmitter<KeyboardEvent> = new EventEmitter<KeyboardEvent>;

EEDU-Frontend/src/app/entity/entity-list/entity-list.component.html

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,40 @@
66
<mat-icon class="empty-list-icon">folder_open</mat-icon>
77
<button (click)="openCreateDialog()" mat-flat-button>Create first entry</button>
88
</div>
9+
910
<list
1011
#list
1112
*ngIf="loaded && values.length > 0"
12-
[height]="435"
13+
[height]="480"
1314
[itemInfo]="itemInfo()"
14-
[selectionType]="SelectionType.MULTIPLE" [values]="values">
15+
[selectionType]="SelectionType.MULTIPLE"
16+
[values]="pagedValues">
1517
<div style="margin-left: auto">
1618
<button (click)="deletePressed.emit(list.selected)" *ngIf="list.selected.length > 0" mat-icon-button>
1719
<mat-icon>delete</mat-icon>
1820
</button>
1921
<button (click)="openCreateDialog()" mat-icon-button>
2022
<mat-icon>add</mat-icon>
2123
</button>
24+
<button mat-icon-button>
25+
<mat-icon>filter_list</mat-icon>
26+
</button>
2227
</div>
2328
</list>
29+
30+
<div *ngIf="loaded && pagedValues.length > 10" class="scrollable-button">
31+
<p class="mat-caption">You can scroll here</p>
32+
<mat-icon >keyboard_double_arrow_down</mat-icon>
33+
</div>
34+
35+
<div *ngIf="paginatorRequired" class="paginator-tools">
36+
<mat-checkbox [checked]="!paginatorEnabled" (click)="togglePaginator()">Disable Paginator</mat-checkbox>
37+
<mat-paginator
38+
[disabled]="!paginatorEnabled"
39+
[length]="values.length"
40+
[pageSize]="pageSize"
41+
[pageSizeOptions]="[5, 10, 25]"
42+
(page)="onPageChange($event)">
43+
</mat-paginator>
44+
</div>
2445
</div>

EEDU-Frontend/src/app/entity/entity-list/entity-list.component.scss

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
.container {
2-
height: 500px;
3-
border-bottom: solid 1px rgba(50, 50, 50, 0.1);
2+
height: 605px;
43

54
mat-progress-bar {
65
width: 50%;
@@ -30,6 +29,21 @@
3029
}
3130
}
3231

32+
.paginator-tools {
33+
border-top: solid 1px rgba(50, 50, 50, 0.1);;
34+
display: flex;
35+
align-items: center;
36+
justify-content: space-between;
37+
}
38+
39+
.scrollable-button
40+
{
41+
text-align: center;
42+
position: absolute;
43+
bottom: 80px;
44+
left: 50%;
45+
transform: translateX(-50%);
46+
}
3347
}
3448

3549

EEDU-Frontend/src/app/entity/entity-list/entity-list.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
33
import { EntityListComponent } from './entity-list.component';
44

55
describe('EntityListComponent', () => {
6-
let component: EntityListComponent;
7-
let fixture: ComponentFixture<EntityListComponent>;
6+
let component: EntityListComponent<any>;
7+
let fixture: ComponentFixture<EntityListComponent<any>>;
88

99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({

EEDU-Frontend/src/app/entity/entity-list/entity-list.component.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import {MatButton, MatIconButton} from "@angular/material/button";
77
import {EntityService} from "../entity-service";
88
import {MatDialog} from "@angular/material/dialog";
99
import {ComponentType} from "@angular/cdk/overlay";
10+
import {MatPaginator, PageEvent} from "@angular/material/paginator";
11+
import {MatCheckbox} from "@angular/material/checkbox";
1012

1113
@Component({
1214
selector: 'app-entity-list',
13-
imports: [MatIcon, AbstractList, NgIf, MatProgressBar, MatIconButton, MatButton],
15+
imports: [MatIcon, AbstractList, NgIf, MatProgressBar, MatIconButton, MatButton, MatPaginator, MatCheckbox],
1416
templateUrl: './entity-list.component.html',
1517
styleUrl: './entity-list.component.scss'
1618
})
@@ -19,16 +21,55 @@ export class EntityListComponent<T extends { id: any }> implements AfterViewInit
1921
@Output() public readonly deletePressed: EventEmitter<T[]> = new EventEmitter<T[]>;
2022
public readonly service: InputSignal<EntityService<any, T, any, any> | null> = input<EntityService<any, T, any, any> | null>(null)
2123
public readonly itemInfo: InputSignal<ListItemInfo<T> | null> = input<ListItemInfo<T> | null>(null);
24+
2225
protected readonly SelectionType: typeof SelectionType = SelectionType;
2326

27+
private _paginatorEnabled: boolean = true;
28+
private _values: readonly T[] = [];
29+
private _pageIndex: number = 0;
30+
private _pageSize: number = 10;
31+
32+
private _pagedValues: readonly T[] = [];
33+
2434
public constructor(private readonly _matDialog: MatDialog, private readonly cdr: ChangeDetectorRef) {}
2535

26-
private _values: readonly T[] = [];
36+
protected get paginatorEnabled(): boolean {
37+
return this._paginatorEnabled;
38+
}
39+
protected get paginatorRequired(): boolean
40+
{
41+
return this._values.length > 10;
42+
}
43+
44+
protected togglePaginator(): void {
45+
this._paginatorEnabled = !this._paginatorEnabled;
46+
this.updatePagedValues();
47+
}
2748

2849
protected get values(): readonly T[] {
2950
return this._values;
3051
}
3152

53+
protected get pagedValues(): readonly T[] {
54+
return this._pagedValues;
55+
}
56+
57+
protected get pageIndex(): number {
58+
return this._pageIndex;
59+
}
60+
61+
protected set pageIndex(value: number) {
62+
this._pageIndex = value;
63+
}
64+
65+
protected get pageSize(): number {
66+
return this._pageSize;
67+
}
68+
69+
protected set pageSize(value: number) {
70+
this._pageSize = value;
71+
}
72+
3273
protected get loaded(): boolean {
3374
return this.service()?.fetched || false;
3475
}
@@ -39,11 +80,30 @@ export class EntityListComponent<T extends { id: any }> implements AfterViewInit
3980
}
4081
this.service()?.value$.subscribe((value: T[]): void => {
4182
this._values = value;
83+
this.updatePagedValues();
4284
this.cdr.detectChanges();
4385
})
4486
}
4587

4688
public openCreateDialog(): void {
47-
this._matDialog.open(this.service()?.createDialogType as ComponentType<T>, {width: '600px'});
89+
this._matDialog.open(this.service()?.createDialogType as ComponentType<any>, {width: '600px'});
90+
}
91+
92+
protected onPageChange(event: PageEvent) {
93+
this.pageIndex = event.pageIndex;
94+
this.pageSize = event.pageSize;
95+
this.updatePagedValues();
96+
}
97+
98+
private updatePagedValues(): void {
99+
if(!this.paginatorEnabled)
100+
{
101+
this._pagedValues = this._values;
102+
return;
103+
}
104+
105+
const startIndex: number = this.pageIndex * this.pageSize;
106+
const endIndex: number = startIndex + this.pageSize;
107+
this._pagedValues = this._values.slice(startIndex, endIndex);
48108
}
49109
}

EEDU-Frontend/src/app/entity/entity-service.ts

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,19 @@ import {ComponentType} from "@angular/cdk/overlay";
77
export abstract class EntityService<P, T extends { id: P }, G, C> {
88

99
private readonly _subject: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);
10-
private _fetched: boolean = false
11-
12-
protected constructor(
13-
private readonly _http: HttpClient,
14-
private readonly _location: string,
15-
private readonly _createDialog: typeof AbstractSimpleCreateEntity,
16-
) {}
17-
18-
public get createDialogType(): ComponentType<any> {
19-
return this._createDialog as ComponentType<any>;
20-
}
21-
22-
public abstract translate(obj: G): T;
23-
24-
protected get BACKEND_URL(): string {
25-
return `${environment.backendUrl}/${this._location}`;
26-
}
2710

28-
public create(models: C[]): Observable<T[]> {
29-
const url: string = `${this.BACKEND_URL}/create`;
30-
return this.http.post<G[]>(url, this.toPackets(models), {withCredentials: true}).pipe(this.translateValue, tap((response: T[]): void => this.pushCreated(response)));
31-
}
32-
33-
public get(id: P): Observable<T> {
34-
const url: string = `${this.BACKEND_URL}/get/${id}`;
35-
return this.http.get<G>(url).pipe(// this will be a part of my flashback of why I have gone crazy
36-
map((response: G): G[] => [response]), this.translateValue, map((response: any[]): any => response[0])
37-
);
38-
}
11+
protected constructor(private readonly _http: HttpClient, private readonly _location: string, private readonly _createDialog: ComponentType<AbstractSimpleCreateEntity>,) {}
3912

40-
public delete(id: P[]): Observable<void> {
41-
const url: string = `${this.BACKEND_URL}/delete/${id}`;
42-
return this.http.delete(url, {withCredentials: true}).pipe(map((): void => { this.postDelete(id); }));
43-
}
13+
private _fetched: boolean = false
4414

4515
public get fetched(): boolean {
4616
return this._fetched;
4717
}
4818

19+
public get createDialogType(): ComponentType<AbstractSimpleCreateEntity> {
20+
return this._createDialog;
21+
}
22+
4923
public get translateValue(): OperatorFunction<G[], T[]> {
5024
return map((response: G[]): T[] => response.map((item: G): T => this.translate(item)));
5125
}
@@ -54,10 +28,6 @@ export abstract class EntityService<P, T extends { id: P }, G, C> {
5428
return this.value$.value;
5529
}
5630

57-
protected get http(): HttpClient {
58-
return this._http;
59-
}
60-
6131
public get value$(): BehaviorSubject<T[]> {
6232
if (!this.fetched) {
6333
this.fetchAll.subscribe();
@@ -73,6 +43,40 @@ export abstract class EntityService<P, T extends { id: P }, G, C> {
7343
}));
7444
}
7545

46+
protected get BACKEND_URL(): string {
47+
return `${environment.backendUrl}/${this._location}`;
48+
}
49+
50+
protected get http(): HttpClient {
51+
return this._http;
52+
}
53+
54+
public abstract translate(obj: G): T;
55+
56+
public create(models: C[]): Observable<T[]> {
57+
const url: string = `${this.BACKEND_URL}/create`;
58+
return this.http.post<G[]>(url, this.toPackets(models), {withCredentials: true}).pipe(this.translateValue, tap((response: T[]): void => this.pushCreated(response)));
59+
}
60+
61+
public get(id: P): Observable<T> {
62+
const url: string = `${this.BACKEND_URL}/get/${id}`;
63+
return this.http.get<G>(url).pipe(// this will be a part of my flashback of why I have gone crazy
64+
map((response: G): G[] => [response]), this.translateValue, map((response: any[]): any => response[0]));
65+
}
66+
67+
public delete(id: P[]): Observable<void> {
68+
const url: string = `${this.BACKEND_URL}/delete/${id}`;
69+
return this.http.delete(url, {withCredentials: true}).pipe(map((): void => { this.postDelete(id); }));
70+
}
71+
72+
public clearCache(): void {
73+
this._fetched = false;
74+
}
75+
76+
public update(): void {
77+
this.value$.next([...this.value]);
78+
}
79+
7680
protected toPackets(models: C[]): any[] {
7781
return models.map((model: C): any => {
7882
if (typeof model === 'object' && model !== null && 'toPacket' in model) {
@@ -90,12 +94,4 @@ export abstract class EntityService<P, T extends { id: P }, G, C> {
9094
protected postDelete(id: P[]): void {
9195
this.value$.next(this.value.filter(((value: T): boolean => !id.includes(value.id))));
9296
}
93-
94-
public clearCache(): void {
95-
this._fetched = false;
96-
}
97-
98-
public update(): void {
99-
this.value$.next([...this.value]);
100-
}
10197
}

EEDU-Frontend/src/app/management/lazy-loaded-accordion/lazy-loaded-accordion.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<mat-tab-group style="margin: 8px;">
22
<mat-tab *ngFor="let tab of tabs()" [label]="tab.label">
33
<ng-template mat-tab-label>
4-
<mat-icon class="course-component-icon">{{ tab.icon }}</mat-icon>
4+
<mat-icon class="component-icon">{{ tab.icon }}</mat-icon>
55
{{ tab.label }}
66
</ng-template>
77
<ng-template matTabContent>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.course-component-icon
1+
.component-icon
22
{
33
margin-right: 8px;
44
}

0 commit comments

Comments
 (0)