small improvements:

- add colorview component
 - improve generic action column
 - improve generic table
 - improve event-type-table.component.ts
 - add headerText to admin layout
This commit is contained in:
Schneider Roland 2025-11-21 21:14:00 +01:00
parent b047ecc589
commit 008b644bb1
14 changed files with 116 additions and 23 deletions

View File

@ -12,7 +12,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg> </svg>
</label> </label>
<a class="btn btn-ghost text-xl">daisyUI</a> <a class="btn btn-ghost text-xl">{{headerText()}}</a>
</div> </div>
@if (loggedIn()) { @if (loggedIn()) {
<div class="flex-none"> <div class="flex-none">

View File

@ -8,6 +8,7 @@ import { Component, input, output } from '@angular/core';
}) })
export class AdminLayoutRs1 { export class AdminLayoutRs1 {
headerText = input<string>();
readonly loggedIn = input<boolean>(false) readonly loggedIn = input<boolean>(false)

View File

@ -1,4 +1,4 @@
<rs-daisy-admin-layout-rs1 (clickEvent)="logout()" [loggedIn]="loggedIn()"> <rs-daisy-admin-layout-rs1 [headerText]="'Foglalási rendszer'" (clickEvent)="logout()" [loggedIn]="loggedIn()">
<app-menu <app-menu
[title]="menuTitle" [title]="menuTitle"

View File

@ -0,0 +1,8 @@
@if (color()) {
<div class="flex flex-row gap-1 items-center">
<div class='min-w-3 min-h-3 w-3 h-3'
[style]="'background-color: ' + color()"
></div>
{{color()}}
</div>
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ColorView } from './color-view';
describe('ColorView', () => {
let component: ColorView;
let fixture: ComponentFixture<ColorView>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ColorView]
})
.compileComponents();
fixture = TestBed.createComponent(ColorView);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,11 @@
import { Component, input } from '@angular/core';
@Component({
selector: 'app-color-view',
imports: [],
templateUrl: './color-view.html',
styleUrl: './color-view.css',
})
export class ColorView {
color = input<string>();
}

View File

@ -1,5 +1,7 @@
<div class="flex flex-row gap-1 items-center">
@for (action of actions(); track action){ @for (action of actions(); track action){
<a class="btn btn-primary" (click)="onClick($event,action)"> <a class="btn btn-primary" title="{{action.title ?? '' }}" (click)="onClick($event,action)">
@if(action.svgIcon){ @if(action.svgIcon){
<span [outerHTML]="action.svgIcon | safeHtml"></span> <span [outerHTML]="action.svgIcon | safeHtml"></span>
} }
@ -8,3 +10,4 @@
} }
</a> </a>
} }
</div>

View File

@ -1,10 +1,14 @@
import { Component, inject, input, OnInit } from '@angular/core'; import { Component, inject, input, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { SafeHtmlPipe } from '../../pipes/safe-html-pipe'; import { SafeHtmlPipe } from '../../pipes/safe-html-pipe';
import { SvgIcons } from '../../svg-icons';
import { EventType } from '../../features/event-type/models/event-type.model';
import { CellDefinition, StyleClassContext } from '../generic-table/cell-definition.interface';
export interface ActionDefinition<T> { export interface ActionDefinition<T> {
action: string; action: string;
text?: string | false; text?: string | false;
title?: string;
generate: (action: string, item?: T) => string; generate: (action: string, item?: T) => string;
handler?: (action: ActionDefinition<T>, item?: T) => void; handler?: (action: ActionDefinition<T>, item?: T) => void;
svgIcon?: string; //https://heroicons.com/ svgIcon?: string; //https://heroicons.com/
@ -30,12 +34,45 @@ export class GenericActionColumn<T> implements OnInit {
ngOnInit(): void { ngOnInit(): void {
} }
onClick($event: any, actionDefinition: ActionDefinition<T>) { onClick($event: any, actionDefinition: ActionDefinition<T>) {
if (actionDefinition?.handler) { if (actionDefinition?.handler) {
actionDefinition.handler(actionDefinition, this.item()); actionDefinition.handler(actionDefinition, this.item());
} }
} }
} }
export interface CreateActionColumnCellDefinitionContext<T> {
actionHandler?: (action: ActionDefinition<T>, item?: T) => void;
styleClass?: (definition: StyleClassContext<T>) => string;
}
export const createActionColumnCellDefinition = <T>(createCtx: CreateActionColumnCellDefinitionContext<T>): CellDefinition<T> => {
return {
styleClass: createCtx.styleClass ?? (ctx => 'w-[1%]'),
component: GenericActionColumn,
componentInputs: item => ({
item: item,
actions: [
{
action: 'view',
title: 'Részletek',
text: false,
handler: createCtx.actionHandler,
svgIcon: SvgIcons.heroDocument,
},
{
action: 'edit', title: 'Szerkesztés', text: false,
svgIcon: SvgIcons.heroCog6Tooth,
handler: createCtx.actionHandler,
},
{
action: 'delete',
title: 'Törlés',
text: false,
handler: createCtx.actionHandler,
svgIcon: SvgIcons.heroTrash,
},
] as ActionDefinition<EventType>[],
}),
};
};

View File

@ -5,8 +5,6 @@ export interface TypeDefinition{
params?: Record<string, any>; params?: Record<string, any>;
} }
export interface ColumnDefinition<T> { export interface ColumnDefinition<T> {
attribute: keyof T; attribute: keyof T;
type: TypeDefinition; type: TypeDefinition;

View File

@ -4,7 +4,7 @@
</div> </div>
@if (data$ | async; as getDataResponse) { @if (data$ | async; as getDataResponse) {
<table class="table w-full table-zebra"> <table class="table w-full table-zebra" [class]="config.tableCssClass ?? ''">
<thead> <thead>
<tr> <tr>
@ -37,10 +37,10 @@
<td [class]="getValueStyleClass(column!,column.valueCell!,item) "> <td [class]="getValueStyleClass(column!,column.valueCell!,item) ">
@if (column.valueCell) { @if (column.valueCell) {
@if (typeof column.valueCell === 'boolean') { @if (typeof column.valueCell === 'boolean') {
{{ resolveValue(item, column) }} <div [outerHTML]=" resolveValue(item, column) | safeHtml "></div>
} @else { } @else {
@if (!column.valueCell.component) { @if (!column.valueCell.component) {
{{ resolveValue(item, column, column.valueCell) }} <div [outerHTML]=" resolveValue(item, column, column.valueCell) | safeHtml "></div>
} @else { } @else {
<ng-container <ng-container
*ngComponentOutlet="column.valueCell.component; inputs: getComponentInputs(item, column,column.valueCell)"></ng-container> *ngComponentOutlet="column.valueCell.component; inputs: getComponentInputs(item, column,column.valueCell)"></ng-container>

View File

@ -8,11 +8,12 @@ import { DomSanitizer } from '@angular/platform-browser';
import { GenericTableConfig } from './generic-table.config'; import { GenericTableConfig } from './generic-table.config';
import { startWith, switchMap } from 'rxjs/operators'; import { startWith, switchMap } from 'rxjs/operators';
import { GenericTableSearchForm } from './generic-table-search-form/generic-table-search-form'; import { GenericTableSearchForm } from './generic-table-search-form/generic-table-search-form';
import { SafeHtmlPipe } from '../../pipes/safe-html-pipe';
@Component({ @Component({
selector: 'app-generic-table', selector: 'app-generic-table',
templateUrl: './generic-table.html', templateUrl: './generic-table.html',
imports: [NgClass, AsyncPipe, NgComponentOutlet, GenericTableSearchForm], imports: [NgClass, AsyncPipe, NgComponentOutlet, GenericTableSearchForm, SafeHtmlPipe],
standalone: true, standalone: true,
}) })
export class GenericTable<T> implements OnInit { export class GenericTable<T> implements OnInit {
@ -68,7 +69,7 @@ export class GenericTable<T> implements OnInit {
if (cell.componentInputs) { if (cell.componentInputs) {
return cell.componentInputs(item); return cell.componentInputs(item);
} }
return { data: item }; // Default input return { item }; // Default input
} }
getAsHtml(str: string) { getAsHtml(str: string) {

View File

@ -3,7 +3,7 @@
<!-- Generated by the CLI --> <!-- Generated by the CLI -->
<div class="p-4 md:p-8 space-y-6"> <div class="p-4 md:p-8 space-y-6">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h1 class="text-3xl font-bold">EventTypes (Generic Table)</h1> <h1 class="text-3xl font-bold">Esemény típusok</h1>
<a routerLink="/event-type/new" class="btn btn-primary">Create New</a> <a routerLink="/event-type/new" class="btn btn-primary">Create New</a>
</div> </div>

View File

@ -15,6 +15,7 @@ import {
import { EventTypeService } from '../../services/event-type.service'; import { EventTypeService } from '../../services/event-type.service';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { SvgIcons } from '../../../../svg-icons'; import { SvgIcons } from '../../../../svg-icons';
import { ColorView } from '../../../../components/color-view/color-view';
@Component({ @Component({
selector: 'app-event-type-table', selector: 'app-event-type-table',
@ -60,42 +61,52 @@ export class EventTypeTableComponent implements OnInit {
{ {
attribute: 'name', attribute: 'name',
headerCell: true, headerCell: true,
valueCell: true, valueCell: {
styleClass: ctx => 'w-[1%]',
value: item => item?.name,
},
}, },
{ {
attribute: 'description', attribute: 'description',
headerCell: true, headerCell: true,
valueCell: { valueCell: {
styleClass: ctx => 'w-auto',
value: item => item?.description, value: item => item?.description,
}, },
}, },
{ {
attribute: 'color', attribute: 'color',
headerCell: true, headerCell: true,
valueCell: true, valueCell: {
styleClass: ctx => 'w-[1%]',
value: item => item?.color,
component: ColorView,
componentInputs: item => {
return { color: item?.color };
}
},
}, },
{ {
attribute: 'actions', attribute: 'actions',
headerCell: { value: 'Actions' }, headerCell: { value: 'Actions' },
valueCell: { valueCell: {
styleClass: ctx => 'w-[1%]',
component: GenericActionColumn, component: GenericActionColumn,
componentInputs: item => ({ componentInputs: item => ({
item: item, item: item,
actions: [ actions: [
{ action: 'view', text: false, handler: actionHandler, svgIcon: SvgIcons.heroDocument }, { action: 'view', title: 'Részletek', text: false, handler: actionHandler, svgIcon: SvgIcons.heroDocument },
{ {
action: 'edit', text: false, action: 'edit', title: 'Szerkesztés', text: false,
svgIcon: SvgIcons.heroCog6Tooth, svgIcon: SvgIcons.heroCog6Tooth,
handler: actionHandler, handler: actionHandler,
}, },
{ action: 'delete', text: false, handler: actionHandler, svgIcon: SvgIcons.heroTrash }, { action: 'delete', title: 'Törlés', text: false, handler: actionHandler, svgIcon: SvgIcons.heroTrash },
] as ActionDefinition<EventType>[], ] as ActionDefinition<EventType>[],
}), }),
}, },
}, },
] as ColumnDefinition<EventType>[], ] as ColumnDefinition<EventType>[],
tableCssClass: 'event-type-table-container', tableCssClass: 'event-type-table-container w-full',
}; };
} }