Initial commit

This commit is contained in:
2026-09-17 21:45:57 +02:00
commit 9160e861e0
302 changed files with 31275 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ApiService } from '../../core/services/api.service';
import { CrudConfig, FieldConfig, SelectOption } from './crud-config';
interface DialogData { config: CrudConfig; entity: Record<string, any> | null; }
@Component({
selector: 'app-crud-form-dialog',
imports: [
ReactiveFormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatSelectModule,
MatDatepickerModule, MatButtonModule, MatIconModule,
],
templateUrl: './crud-form-dialog.component.html',
styleUrl: './crud-form-dialog.component.scss',
})
export class CrudFormDialogComponent {
private fb = inject(FormBuilder);
private api = inject(ApiService);
readonly data = inject<DialogData>(MAT_DIALOG_DATA);
private ref = inject(MatDialogRef<CrudFormDialogComponent>);
readonly isEdit = !!this.data.entity;
readonly form: FormGroup;
readonly options = signal<Record<string, SelectOption[]>>({});
constructor() {
const group: Record<string, any> = {};
for (const f of this.data.config.fields) {
const validators = [];
if (f.required) validators.push(Validators.required);
if (f.min != null) validators.push(Validators.min(f.min));
if (f.max != null) validators.push(Validators.max(f.max));
if (f.maxLength != null) validators.push(Validators.maxLength(f.maxLength));
group[f.key] = [this.initialValue(f), validators];
}
this.form = this.fb.group(group);
// Load lookup-backed select options.
for (const f of this.data.config.fields) {
if (f.options) {
this.options.update((o) => ({ ...o, [f.key]: f.options! }));
} else if (f.lookup) {
this.api.lookup(f.lookup).subscribe((items) => {
this.options.update((o) => ({ ...o, [f.key]: items.map((i) => ({ value: i.id, label: i.name })) }));
});
}
}
}
private initialValue(f: FieldConfig): any {
const raw = this.data.entity?.[f.key];
if (raw == null || raw === '') return f.type === 'select' ? null : null;
if (f.type === 'date') return new Date(raw);
if (f.type === 'datetime') return this.toLocalInput(new Date(raw));
return raw;
}
private toLocalInput(d: Date): string {
const p = (n: number) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
}
private toDateOnly(d: Date): string {
const p = (n: number) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
}
optionsFor(key: string): SelectOption[] {
return this.options()[key] ?? [];
}
save() {
if (this.form.invalid) { this.form.markAllAsTouched(); return; }
const payload: Record<string, any> = {};
for (const f of this.data.config.fields) {
let v = this.form.value[f.key];
if (v === '' || v === undefined) v = null;
if (v != null && f.type === 'date' && v instanceof Date) v = this.toDateOnly(v);
if (v != null && f.type === 'number') v = Number(v);
payload[f.key] = v;
}
this.ref.close(payload);
}
cancel() { this.ref.close(null); }
}