From b047ecc589300e662cabe1c7c76bb4a03c2194ba Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Fri, 21 Nov 2025 15:24:35 +0100 Subject: [PATCH] add breadcrumbs --- .../components/breadcrumbs/breadcrumbs.css | 0 .../components/breadcrumbs/breadcrumbs.html | 26 +++++++++ .../breadcrumbs/breadcrumbs.spec.ts | 23 ++++++++ .../lib/components/breadcrumbs/breadcrumbs.ts | 46 +++++++++++++++ .../ng-daisyui/src/lib/daisy.types.ts | 7 +++ .../src/lib/pipes/safe-html-pipe.ts | 16 ++++++ .../rschneider/ng-daisyui/src/public-api.ts | 1 + .../generic-action-column.html | 9 ++- .../generic-action-column.ts | 4 ++ .../cell-definition.interface.ts | 8 +++ .../generic-table/generic-table.html | 2 +- .../components/generic-table/generic-table.ts | 37 +++++++----- .../event-type-details.component.html | 17 +++--- .../event-type-details.component.ts | 40 ++++++++++--- .../event-type-form.component.html | 13 +++-- .../event-type-form.component.ts | 36 ++++++++++-- .../event-type-list.component.html | 9 ++- .../event-type-table.component.ts | 56 +++++++++++-------- admin/src/app/svg-icons.ts | 17 ++++++ 19 files changed, 294 insertions(+), 73 deletions(-) create mode 100644 admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.css create mode 100644 admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.html create mode 100644 admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.spec.ts create mode 100644 admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.ts create mode 100644 admin/projects/rschneider/ng-daisyui/src/lib/pipes/safe-html-pipe.ts create mode 100644 admin/src/app/svg-icons.ts diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.css b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.html b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.html new file mode 100644 index 0000000..4dc5f3e --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.html @@ -0,0 +1,26 @@ +@if (breadcrumbs()?.length) { + +} diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.spec.ts b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.spec.ts new file mode 100644 index 0000000..c9c7116 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Breadcrumbs } from './breadcrumbs'; + +describe('Breadcrumbs', () => { + let component: Breadcrumbs; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Breadcrumbs] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Breadcrumbs); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.ts b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.ts new file mode 100644 index 0000000..5df7697 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/components/breadcrumbs/breadcrumbs.ts @@ -0,0 +1,46 @@ +import { Component, input } from '@angular/core'; +import { Breadcrumb } from '../../daisy.types'; +import { RouterLink } from '@angular/router'; +import { SafeHtmlPipe } from '../../pipes/safe-html-pipe'; + +@Component({ + selector: 'rs-daisy-breadcrumbs', + imports: [ + RouterLink, + SafeHtmlPipe, + ], + templateUrl: './breadcrumbs.html', + styleUrl: './breadcrumbs.css', +}) +export class Breadcrumbs { + + breadcrumbs = input([]); + + defaultLinkIcon: string = ` + + + `; + + defaultIcon: string = ` + + + + `; + +} diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/daisy.types.ts b/admin/projects/rschneider/ng-daisyui/src/lib/daisy.types.ts index 701dedf..87a304a 100644 --- a/admin/projects/rschneider/ng-daisyui/src/lib/daisy.types.ts +++ b/admin/projects/rschneider/ng-daisyui/src/lib/daisy.types.ts @@ -11,3 +11,10 @@ export interface FooterNav{ export interface FooterConfig{ navs: FooterNav[] } + +export interface Breadcrumb{ + text: string; + url?: string; + showIcon?: boolean; + svgIcon?: string; +} diff --git a/admin/projects/rschneider/ng-daisyui/src/lib/pipes/safe-html-pipe.ts b/admin/projects/rschneider/ng-daisyui/src/lib/pipes/safe-html-pipe.ts new file mode 100644 index 0000000..de157d6 --- /dev/null +++ b/admin/projects/rschneider/ng-daisyui/src/lib/pipes/safe-html-pipe.ts @@ -0,0 +1,16 @@ +import { inject, Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; + +@Pipe({ + name: 'safeHtml' +}) +export class SafeHtmlPipe implements PipeTransform { + + private sanitized = inject(DomSanitizer); + + transform(value: string | undefined): SafeHtml { + if (!value) return ''; + return this.sanitized.bypassSecurityTrustHtml(value); + } + +} diff --git a/admin/projects/rschneider/ng-daisyui/src/public-api.ts b/admin/projects/rschneider/ng-daisyui/src/public-api.ts index d72bff5..43bd854 100644 --- a/admin/projects/rschneider/ng-daisyui/src/public-api.ts +++ b/admin/projects/rschneider/ng-daisyui/src/public-api.ts @@ -5,5 +5,6 @@ export * from './lib/ng-daisyui'; export * from './lib/components/button/button'; export * from './lib/components/footer/footer'; +export * from './lib/components/breadcrumbs/breadcrumbs'; export * from './lib/daisy.types'; export * from './lib/layout/'; diff --git a/admin/src/app/components/generic-action-column/generic-action-column.html b/admin/src/app/components/generic-action-column/generic-action-column.html index b889bf5..bc1301d 100644 --- a/admin/src/app/components/generic-action-column/generic-action-column.html +++ b/admin/src/app/components/generic-action-column/generic-action-column.html @@ -1,3 +1,10 @@ @for (action of actions(); track action){ - {{action.action}} + + @if(action.svgIcon){ + + } + @if ( action.text !== false){ + {{action.text || action.action}} + } + } diff --git a/admin/src/app/components/generic-action-column/generic-action-column.ts b/admin/src/app/components/generic-action-column/generic-action-column.ts index 839be1e..2ee0ea2 100644 --- a/admin/src/app/components/generic-action-column/generic-action-column.ts +++ b/admin/src/app/components/generic-action-column/generic-action-column.ts @@ -1,15 +1,19 @@ import { Component, inject, input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { SafeHtmlPipe } from '../../pipes/safe-html-pipe'; export interface ActionDefinition { action: string; + text?: string|false; generate: (action: string, item?: T) => string; handler?: (action: ActionDefinition, item?: T) => void; + svgIcon?: string; //https://heroicons.com/ } @Component({ selector: 'app-generic-action-column', imports: [ + SafeHtmlPipe, ], templateUrl: './generic-action-column.html', styleUrl: './generic-action-column.css', diff --git a/admin/src/app/components/generic-table/cell-definition.interface.ts b/admin/src/app/components/generic-table/cell-definition.interface.ts index 2c729c2..c674824 100644 --- a/admin/src/app/components/generic-table/cell-definition.interface.ts +++ b/admin/src/app/components/generic-table/cell-definition.interface.ts @@ -1,7 +1,15 @@ import { Type } from '@angular/core'; +import { ColumnDefinition } from './column-definition.interface'; + +export interface StyleClassContext{ + definition: ColumnDefinition ; + cellDefinition: CellDefinition; + item?: T; +} export interface CellDefinition { value?: ((item?: T) => any) | string; component?: Type; componentInputs?: (item?: T|null) => { [key: string]: any }; + styleClass?: (definition: StyleClassContext) => string; } diff --git a/admin/src/app/components/generic-table/generic-table.html b/admin/src/app/components/generic-table/generic-table.html index 75c39e8..6bd55f0 100644 --- a/admin/src/app/components/generic-table/generic-table.html +++ b/admin/src/app/components/generic-table/generic-table.html @@ -34,7 +34,7 @@ @for (column of config.columns; track column) { - + @if (column.valueCell) { @if (typeof column.valueCell === 'boolean') { {{ resolveValue(item, column) }} diff --git a/admin/src/app/components/generic-table/generic-table.ts b/admin/src/app/components/generic-table/generic-table.ts index 17fe103..4fafb71 100644 --- a/admin/src/app/components/generic-table/generic-table.ts +++ b/admin/src/app/components/generic-table/generic-table.ts @@ -18,12 +18,11 @@ import { GenericTableSearchForm } from './generic-table-search-form/generic-tabl export class GenericTable implements OnInit { - @Input() config!: GenericTableConfig; public data$!: Observable>; sanitizer = inject(DomSanitizer); - parser = new DOMParser() + parser = new DOMParser(); ngOnInit(): void { @@ -31,25 +30,25 @@ export class GenericTable implements OnInit { this.config.refresh$, this.config.filter$.pipe(startWith({})), this.config.page$.pipe(startWith(1)), - this.config.limit$.pipe(startWith(10)) + this.config.limit$.pipe(startWith(10)), ]).pipe( switchMap(([_, filter, page, limit]) => { const query = { ...filter, page, limit: 10 }; - console.info("filter is", filter) + console.info('filter is', filter); return this.config.dataProvider.getData({ params: { q: filter.term ?? '', page, - limit - } + limit, + }, }); - }) + }), ); } - resolveValue(item: T, column: ColumnDefinition, cell?: CellDefinition): any { - if ( cell) { + resolveValue(item: T, column: ColumnDefinition, cell?: CellDefinition): any { + if (cell) { if (cell.value) { if (typeof cell.value === 'string') { return cell.value; @@ -65,16 +64,16 @@ export class GenericTable implements OnInit { return ''; } - getComponentInputs(item: T|null, column: ColumnDefinition, cell: CellDefinition): { [key: string]: any } { + getComponentInputs(item: T | null, column: ColumnDefinition, cell: CellDefinition): { [key: string]: any } { if (cell.componentInputs) { return cell.componentInputs(item); } return { data: item }; // Default input } - getAsHtml(str: string){ + getAsHtml(str: string) { // return this.sanitizer.bypassSecurityTrustHtml(str); - return this.sanitizer.bypassSecurityTrustHtml(this.parser.parseFromString(str, 'text/html').body.innerHTML); + return this.sanitizer.bypassSecurityTrustHtml(this.parser.parseFromString(str, 'text/html').body.innerHTML); } changePage(newPage: number): void { @@ -84,7 +83,7 @@ export class GenericTable implements OnInit { } protected searchTermChanged(searchTerm: any) { - console.info("searchterm",searchTerm); + console.info('searchterm', searchTerm); this.config.page$.next(1); this.config.filter$.next(searchTerm); } @@ -93,4 +92,16 @@ export class GenericTable implements OnInit { this.config.page$.next(1); this.config.limit$.next(+value); } + + protected getValueStyleClass(columnDefinition: ColumnDefinition, cellDefinition: CellDefinition | boolean, item: T) { + console.info("co") + if ( (cellDefinition as any).hasOwnProperty("styleClass") ){ + const def = (cellDefinition as CellDefinition); + if (def && def.styleClass) { + return (columnDefinition.attribute as string) +" "+def.styleClass({definition: columnDefinition, cellDefinition: def, item}); + } + + } + return columnDefinition.attribute; + } } diff --git a/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.html b/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.html index f819f0c..9fcd437 100644 --- a/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.html +++ b/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.html @@ -2,28 +2,28 @@
+
-

EventType Details

- +

Esemény adatai

- + - + - + - + @@ -31,8 +31,7 @@ @@ -43,4 +42,4 @@ - \ No newline at end of file + diff --git a/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.ts b/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.ts index 8a40867..f6ff014 100644 --- a/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.ts +++ b/admin/src/app/features/event-type/components/event-type-details/event-type-details.component.ts @@ -5,30 +5,54 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { Observable } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { switchMap, tap } from 'rxjs/operators'; import { EventType } from '../../models/event-type.model'; import { EventTypeService } from '../../services/event-type.service'; +import { Breadcrumbs, Breadcrumb } from '@rschneider/ng-daisyui'; + @Component({ selector: 'app-event-type-details', templateUrl: './event-type-details.component.html', standalone: true, - imports: [CommonModule, RouterModule], + imports: [CommonModule, RouterModule, Breadcrumbs], }) export class EventTypeDetailsComponent implements OnInit { + + static readonly BC_LIST: Breadcrumb = { + text: 'Esemény típusok', + url: '/event-type/table', + }; + eventType$!: Observable; + protected breadcrumbs: Breadcrumb[] = [ + EventTypeDetailsComponent.BC_LIST, + ]; + constructor( private route: ActivatedRoute, - private eventTypeService: EventTypeService - ) {} + private eventTypeService: EventTypeService, + ) { + } ngOnInit(): void { this.eventType$ = this.route.params.pipe( switchMap(params => { - const id = params['id']; - return this.eventTypeService.findOne(id); - }) + const id = params['id']; + return this.eventTypeService.findOne(id); + + }, + ), + + tap(value => { + this.breadcrumbs = [ + EventTypeDetailsComponent.BC_LIST, + { + text: value.name, + }, + ]; + }), ); } -} \ No newline at end of file +} diff --git a/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.html b/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.html index fe8ad03..0b4149d 100644 --- a/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.html +++ b/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.html @@ -1,5 +1,5 @@ - +
@@ -10,14 +10,15 @@
-
+
-
+
-
-
+
+
Cancel @@ -28,4 +29,4 @@
-
\ No newline at end of file +
diff --git a/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.ts b/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.ts index dc007cb..86dcb94 100644 --- a/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.ts +++ b/admin/src/app/features/event-type/components/event-type-form/event-type-form.component.ts @@ -1,22 +1,34 @@ // dvbooking-cli/src/templates/angular/form.component.ts.tpl // Generated by the CLI -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import { Observable, of } from 'rxjs'; +import { delay, Observable, of } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import { EventType } from '../../models/event-type.model'; import { EventTypeService } from '../../services/event-type.service'; +import { Breadcrumbs, Breadcrumb } from '@rschneider/ng-daisyui'; + @Component({ selector: 'app-event-type-form', templateUrl: './event-type-form.component.html', standalone: true, - imports: [CommonModule, ReactiveFormsModule, RouterModule], + imports: [CommonModule, ReactiveFormsModule, RouterModule, Breadcrumbs], }) export class EventTypeFormComponent implements OnInit { + + static readonly BC_LIST: Breadcrumb = { + text: 'Esemény típusok', + url: '/event-type/table', + }; + + protected breadcrumbs: Breadcrumb[] = [ + EventTypeFormComponent.BC_LIST, + ]; + form: FormGroup; isEditMode = false; id: number | null = null; @@ -27,7 +39,8 @@ export class EventTypeFormComponent implements OnInit { private fb: FormBuilder, private route: ActivatedRoute, private router: Router, - private eventTypeService: EventTypeService + private eventTypeService: EventTypeService, + private cdr: ChangeDetectorRef ) { this.form = this.fb.group({ name: [null], @@ -49,10 +62,21 @@ export class EventTypeFormComponent implements OnInit { return this.eventTypeService.findOne(this.id); } return of(null); - }) + }), ).subscribe(eventType => { + console.info("Breadcrumb loaded") if (eventType) { this.form.patchValue(eventType); + + + this.breadcrumbs = [ + EventTypeFormComponent.BC_LIST, + { + text: eventType.name, + }, + ]; + this.cdr.markForCheck(); + console.info("breadcrumbs", this.breadcrumbs) } }); } @@ -84,4 +108,4 @@ export class EventTypeFormComponent implements OnInit { error: (err) => console.error('Failed to save event-type', err) }); } -} \ No newline at end of file +} diff --git a/admin/src/app/features/event-type/components/event-type-list/event-type-list.component.html b/admin/src/app/features/event-type/components/event-type-list/event-type-list.component.html index 028b4cf..d9c6935 100644 --- a/admin/src/app/features/event-type/components/event-type-list/event-type-list.component.html +++ b/admin/src/app/features/event-type/components/event-type-list/event-type-list.component.html @@ -8,7 +8,6 @@
-
idEsemény azonosító {{ eventType.id }}
nameNév {{ eventType.name }}
descriptionLeírás {{ eventType.description }}
colorSzín {{ eventType.color }}
@@ -28,9 +27,9 @@ @@ -61,4 +60,4 @@ - \ No newline at end of file + diff --git a/admin/src/app/features/event-type/components/event-type-table/event-type-table.component.ts b/admin/src/app/features/event-type/components/event-type-table/event-type-table.component.ts index 54fc01e..e3c6ab3 100644 --- a/admin/src/app/features/event-type/components/event-type-table/event-type-table.component.ts +++ b/admin/src/app/features/event-type/components/event-type-table/event-type-table.component.ts @@ -14,6 +14,7 @@ import { } from '../../../../components/generic-action-column/generic-action-column'; import { EventTypeService } from '../../services/event-type.service'; import { BehaviorSubject } from 'rxjs'; +import { SvgIcons } from '../../../../svg-icons'; @Component({ selector: 'app-event-type-table', @@ -24,9 +25,9 @@ import { BehaviorSubject } from 'rxjs'; export class EventTypeTableComponent implements OnInit { private refresh$ = new BehaviorSubject(undefined); - private filter$ = new BehaviorSubject({}); - private page$ = new BehaviorSubject(1); - private limit$ = new BehaviorSubject(10); + private filter$ = new BehaviorSubject({}); + private page$ = new BehaviorSubject(1); + private limit$ = new BehaviorSubject(10); router = inject(Router); tableConfig!: GenericTableConfig; @@ -44,8 +45,8 @@ export class EventTypeTableComponent implements OnInit { this.router.navigate(['/event-type', item?.id, 'edit']); break; case 'delete': - this.deleteItem(item.id); - break; + this.deleteItem(item.id); + break; } }; @@ -64,7 +65,10 @@ export class EventTypeTableComponent implements OnInit { { attribute: 'description', headerCell: true, - valueCell: true, + valueCell: { + styleClass: ctx => 'w-auto', + value: item => item?.description, + }, }, { attribute: 'color', @@ -79,9 +83,13 @@ export class EventTypeTableComponent implements OnInit { componentInputs: item => ({ item: item, actions: [ - { action: 'view', handler: actionHandler }, - { action: 'edit', handler: actionHandler }, - { action: 'delete', handler: actionHandler }, + { action: 'view', text: false, handler: actionHandler, svgIcon: SvgIcons.heroDocument }, + { + action: 'edit', text: false, + svgIcon: SvgIcons.heroCog6Tooth, + handler: actionHandler, + }, + { action: 'delete', text: false, handler: actionHandler, svgIcon: SvgIcons.heroTrash }, ] as ActionDefinition[], }), }, @@ -91,20 +99,20 @@ export class EventTypeTableComponent implements OnInit { }; } - deleteItem(id: number): void { - if (confirm('Are you sure you want to delete this item?')) { - this.eventTypeService.remove(id).subscribe({ - next: () => { - console.log(`Item with ID ${id} deleted successfully.`); - this.refresh$.next(); - }, - // --- THIS IS THE FIX --- - // Explicitly type 'err' to satisfy strict TypeScript rules. - error: (err: any) => { - console.error(`Error deleting item with ID ${id}:`, err); - } - }); - } + deleteItem(id: number): void { + if (confirm('Are you sure you want to delete this item?')) { + this.eventTypeService.remove(id).subscribe({ + next: () => { + console.log(`Item with ID ${id} deleted successfully.`); + this.refresh$.next(); + }, + // --- THIS IS THE FIX --- + // Explicitly type 'err' to satisfy strict TypeScript rules. + error: (err: any) => { + console.error(`Error deleting item with ID ${id}:`, err); + }, + }); } + } -} \ No newline at end of file +} diff --git a/admin/src/app/svg-icons.ts b/admin/src/app/svg-icons.ts new file mode 100644 index 0000000..c48b7d3 --- /dev/null +++ b/admin/src/app/svg-icons.ts @@ -0,0 +1,17 @@ +export class SvgIcons { + public static heroDocument = ` + + +`; + + public static heroTrash = ` + +`; + + public static heroCog6Tooth = ` + + + +`; + +}
{{ item.description }} {{ item.color }} - View - Edit - + Részletek + Módosítás +