mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
✨ feat(navigation): implement title management and enhance tab system This commit introduces a comprehensive title management system and extends the tab functionality with subtitle support, improving navigation clarity and user experience across the application. Key changes: Title Management System: - Add @isa/common/title-management library with dual approach: - IsaTitleStrategy for route-based static titles - usePageTitle() for component-based dynamic titles - Implement TitleRegistryService for nested component hierarchies - Automatic ISA prefix addition and TabService integration - Comprehensive test coverage (1,158 lines of tests) Tab System Enhancement: - Add subtitle field to tab schema for additional context - Update TabService API (addTab, patchTab) to support subtitles - Extend Zod schemas with subtitle validation - Update documentation with usage examples Routing Modernization: - Consolidate route guards using ActivateProcessIdWithConfigKeyGuard - Replace 4+ specific guards with generic config-key-based approach - Add title attributes to 100+ routes across all modules - Remove deprecated ProcessIdGuard in favor of ActivateProcessIdGuard Code Cleanup: - Remove deprecated preview component and related routes - Clean up unused imports and exports - Update TypeScript path aliases Dependencies: - Update package.json and package-lock.json - Add @isa/common/title-management to tsconfig path mappings Refs: #5351, #5418, #5419, #5420
197 lines
5.3 KiB
TypeScript
197 lines
5.3 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Store } from '@ngrx/store';
|
|
import { BranchDTO } from '@generated/swagger/checkout-api';
|
|
import { isBoolean, isNumber } from '@utils/common';
|
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
import { first, map, switchMap } from 'rxjs/operators';
|
|
import { ApplicationProcess } from './defs';
|
|
import {
|
|
removeProcess,
|
|
selectSection,
|
|
selectProcesses,
|
|
setSection,
|
|
addProcess,
|
|
setActivatedProcess,
|
|
selectActivatedProcess,
|
|
patchProcess,
|
|
patchProcessData,
|
|
selectTitle,
|
|
setTitle,
|
|
} from './store';
|
|
|
|
@Injectable()
|
|
export class ApplicationService {
|
|
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
|
|
|
get activatedProcessId() {
|
|
return this.activatedProcessIdSubject.value;
|
|
}
|
|
|
|
get activatedProcessId$() {
|
|
return this.activatedProcessIdSubject.asObservable();
|
|
}
|
|
|
|
constructor(private store: Store) {}
|
|
|
|
getProcesses$(section?: 'customer' | 'branch') {
|
|
const processes$ = this.store.select(selectProcesses);
|
|
return processes$.pipe(
|
|
map((processes) =>
|
|
processes.filter((process) =>
|
|
section ? process.section === section : true,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
getProcessById$(processId: number): Observable<ApplicationProcess> {
|
|
return this.getProcesses$().pipe(
|
|
map((processes) => processes.find((process) => process.id === processId)),
|
|
);
|
|
}
|
|
|
|
getSection$() {
|
|
return this.store.select(selectSection);
|
|
}
|
|
|
|
getTitle$() {
|
|
return this.getSection$().pipe(
|
|
map((section) => {
|
|
return section === 'customer' ? 'Kundenbereich' : 'Filialbereich';
|
|
}),
|
|
);
|
|
}
|
|
|
|
/** @deprecated */
|
|
getActivatedProcessId$() {
|
|
return this.store
|
|
.select(selectActivatedProcess)
|
|
.pipe(map((process) => process?.id));
|
|
}
|
|
|
|
activateProcess(activatedProcessId: number) {
|
|
this.store.dispatch(setActivatedProcess({ activatedProcessId }));
|
|
this.activatedProcessIdSubject.next(activatedProcessId);
|
|
}
|
|
|
|
removeProcess(processId: number) {
|
|
this.store.dispatch(removeProcess({ processId }));
|
|
}
|
|
|
|
patchProcess(processId: number, changes: Partial<ApplicationProcess>) {
|
|
this.store.dispatch(patchProcess({ processId, changes }));
|
|
}
|
|
|
|
patchProcessData(processId: number, data: Record<string, any>) {
|
|
this.store.dispatch(patchProcessData({ processId, data }));
|
|
}
|
|
|
|
getSelectedBranch$(processId?: number): Observable<BranchDTO> {
|
|
if (!processId) {
|
|
return this.activatedProcessId$.pipe(
|
|
switchMap((processId) =>
|
|
this.getProcessById$(processId).pipe(
|
|
map((process) => process?.data?.selectedBranch),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return this.getProcessById$(processId).pipe(
|
|
map((process) => process?.data?.selectedBranch),
|
|
);
|
|
}
|
|
|
|
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
|
|
|
|
async createCustomerProcess(processId?: number): Promise<ApplicationProcess> {
|
|
const processes = await this.getProcesses$('customer')
|
|
.pipe(first())
|
|
.toPromise();
|
|
|
|
const processIds = processes
|
|
.filter((x) => this.REGEX_PROCESS_NAME.test(x.name))
|
|
.map((x) => +x.name.split(' ')[1]);
|
|
|
|
const maxId = processIds.length > 0 ? Math.max(...processIds) : 0;
|
|
|
|
const process: ApplicationProcess = {
|
|
id: processId ?? Date.now(),
|
|
type: 'cart',
|
|
name: `Vorgang ${maxId + 1}`,
|
|
section: 'customer',
|
|
closeable: true,
|
|
};
|
|
|
|
await this.createProcess(process);
|
|
|
|
return process;
|
|
}
|
|
|
|
async createProcess(process: ApplicationProcess) {
|
|
const existingProcess = await this.getProcessById$(process?.id)
|
|
.pipe(first())
|
|
.toPromise();
|
|
if (existingProcess?.id === process?.id) {
|
|
throw new Error('Process Id existiert bereits');
|
|
}
|
|
|
|
if (!isNumber(process.id)) {
|
|
throw new Error('Process Id nicht gesetzt');
|
|
}
|
|
|
|
if (!isBoolean(process.closeable)) {
|
|
process.closeable = true;
|
|
}
|
|
|
|
if (!isBoolean(process.confirmClosing)) {
|
|
process.confirmClosing = true;
|
|
}
|
|
|
|
process.created = this._createTimestamp();
|
|
process.activated = 0;
|
|
this.store.dispatch(addProcess({ process }));
|
|
}
|
|
|
|
setSection(section: 'customer' | 'branch') {
|
|
this.store.dispatch(setSection({ section }));
|
|
}
|
|
|
|
getLastActivatedProcessWithSectionAndType$(
|
|
section: 'customer' | 'branch',
|
|
type: string,
|
|
): Observable<ApplicationProcess> {
|
|
return this.getProcesses$(section).pipe(
|
|
map((processes) =>
|
|
processes
|
|
?.filter((process) => process.type === type)
|
|
?.reduce((latest, current) => {
|
|
if (!latest) {
|
|
return current;
|
|
}
|
|
return latest?.activated > current?.activated ? latest : current;
|
|
}, undefined),
|
|
),
|
|
);
|
|
}
|
|
|
|
getLastActivatedProcessWithSection$(
|
|
section: 'customer' | 'branch',
|
|
): Observable<ApplicationProcess> {
|
|
return this.getProcesses$(section).pipe(
|
|
map((processes) =>
|
|
processes?.reduce((latest, current) => {
|
|
if (!latest) {
|
|
return current;
|
|
}
|
|
return latest?.activated > current?.activated ? latest : current;
|
|
}, undefined),
|
|
),
|
|
);
|
|
}
|
|
|
|
private _createTimestamp() {
|
|
return Date.now();
|
|
}
|
|
}
|