Merged PR 1890: #5230 #5233 Remi Starten Feedback

- feat(remission-data-access,remission-list,remission-return-receipt-details): improve remission list UX and persist store state
- feat(remission-list, remission-data-access): implement resource-based receipt data fetching
- Merge branch 'develop' into feature/5230-Feedback-Remi-Starten
- feat(remission-data-access, remission-list, ui-dialog, remission-start-dialog): consolidate remission workflow and enhance dialog system
- feat(remission-list-item): extract selection logic into dedicated component
Refs: #5230 #5233
This commit is contained in:
Nino Righi
2025-07-24 21:22:02 +00:00
committed by Lorenz Hilpert
parent f4b541c7c0
commit c5182809ac
29 changed files with 1054 additions and 234 deletions

View File

@@ -13,9 +13,4 @@
@apply overflow-y-auto overflow-x-hidden;
@apply min-h-0;
}
&:has(ui-feedback-dialog),
&:has(remi-remission-start-dialog) {
@apply gap-0;
}
}

View File

@@ -1,7 +1,7 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { DialogComponent } from './dialog.component';
import { DialogContentDirective } from './dialog-content.directive';
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { DIALOG_CLASS_LIST, DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { Component } from '@angular/core';
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';
import { NgComponentOutlet } from '@angular/common';
@@ -12,13 +12,18 @@ import { NgComponentOutlet } from '@angular/common';
template: '<div>Mock Content</div>',
standalone: true,
})
class MockDialogContentComponent extends DialogContentDirective<unknown, unknown> {}
class MockDialogContentComponent extends DialogContentDirective<
unknown,
unknown
> {}
describe('DialogComponent', () => {
let spectator: Spectator<DialogComponent<unknown, unknown, MockDialogContentComponent>>;
let spectator: Spectator<
DialogComponent<unknown, unknown, MockDialogContentComponent>
>;
const mockTitle = 'Test Dialog Title';
const mockDialogRef = { close: jest.fn() };
const createComponent = createComponentFactory({
component: DialogComponent,
imports: [NgComponentOutlet, MockDialogContentComponent], // Use imports instead of declarations for standalone components
@@ -26,7 +31,8 @@ describe('DialogComponent', () => {
{ provide: DIALOG_TITLE, useValue: mockTitle },
{ provide: DIALOG_CONTENT, useValue: MockDialogContentComponent },
{ provide: DialogRef, useValue: mockDialogRef },
{ provide: DIALOG_DATA, useValue: {} }
{ provide: DIALOG_DATA, useValue: {} },
{ provide: DIALOG_CLASS_LIST, useValue: [] },
],
});

View File

@@ -1,11 +1,12 @@
import {
ChangeDetectionStrategy,
Component,
computed,
inject,
signal,
} from '@angular/core';
import { DialogContentDirective } from './dialog-content.directive';
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { DIALOG_CLASS_LIST, DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { ComponentType } from '@angular/cdk/portal';
import { NgComponentOutlet } from '@angular/common';
@@ -23,7 +24,7 @@ import { NgComponentOutlet } from '@angular/common';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgComponentOutlet],
host: {
'[class]': '["ui-dialog"]',
'[class]': 'classes()',
},
})
export class DialogComponent<D, R, C extends DialogContentDirective<D, R>> {
@@ -32,4 +33,18 @@ export class DialogComponent<D, R, C extends DialogContentDirective<D, R>> {
/** The component type to instantiate as the dialog content */
readonly component = inject(DIALOG_CONTENT) as ComponentType<C>;
/** Additional CSS classes provided via injection */
private readonly classList =
inject(DIALOG_CLASS_LIST, { optional: true }) ?? [];
/**
* Computed property that combines the base dialog class with any additional classes
* This allows for dynamic styling of the dialog based on external configuration
*
* @return An array of CSS class names to apply to the dialog element
*/
classes = computed(() => {
return ['ui-dialog', ...this.classList];
});
}

View File

@@ -5,6 +5,7 @@ import {
injectFeedbackDialog,
injectMessageDialog,
injectTextInputDialog,
injectNumberInputDialog,
} from './injects';
import { MessageDialogComponent } from './message-dialog/message-dialog.component';
import { DialogComponent } from './dialog.component';
@@ -13,6 +14,7 @@ import { Component } from '@angular/core';
import { DialogContentDirective } from './dialog-content.directive';
import { TextInputDialogComponent } from './text-input-dialog/text-input-dialog.component';
import { FeedbackDialogComponent } from './feedback-dialog/feedback-dialog.component';
import { NumberInputDialogComponent } from './number-input-dialog/number-input-dialog.component';
// Test component extending DialogContentDirective for testing
@Component({ template: '' })
@@ -177,6 +179,27 @@ describe('Dialog Injects', () => {
});
});
describe('injectNumberInputDialog', () => {
it('should create a dialog injector for NumberInputDialogComponent', () => {
// Act
const openNumberInputDialog = TestBed.runInInjectionContext(() =>
injectNumberInputDialog(),
);
openNumberInputDialog({
data: {
message: 'Test message',
inputLabel: 'Enter value',
inputDefaultValue: 0,
},
});
// Assert
const callOptions = mockDialogOpen.mock.calls[0][1];
const injector = callOptions.injector;
expect(injector.get(DIALOG_CONTENT)).toBe(NumberInputDialogComponent);
});
});
describe('injectFeedbackDialog', () => {
it('should create a dialog injector for FeedbackDialogComponent', () => {
// Act

View File

@@ -4,9 +4,10 @@ import { ComponentType } from '@angular/cdk/portal';
import { inject, Injector } from '@angular/core';
import { DialogContentDirective } from './dialog-content.directive';
import { DialogComponent } from './dialog.component';
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { DIALOG_CLASS_LIST, DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
import { MessageDialogComponent } from './message-dialog/message-dialog.component';
import { TextInputDialogComponent } from './text-input-dialog/text-input-dialog.component';
import { NumberInputDialogComponent } from './number-input-dialog/number-input-dialog.component';
import {
FeedbackDialogComponent,
FeedbackDialogData,
@@ -16,6 +17,9 @@ export interface InjectDialogOptions {
/** Optional title override for the dialog */
title?: string;
/** Optional additional CSS classes to apply to the dialog */
classList?: string[];
/** Optional width for the dialog */
width?: string;
@@ -81,6 +85,10 @@ export function injectDialog<C extends DialogContentDirective<any, any>>(
provide: DIALOG_TITLE,
useValue: openOptions?.title ?? injectOptions?.title,
},
{
provide: DIALOG_CLASS_LIST,
useValue: openOptions?.classList ?? injectOptions?.classList ?? [],
},
],
});
@@ -119,6 +127,13 @@ export const injectMessageDialog = () => injectDialog(MessageDialogComponent);
export const injectTextInputDialog = () =>
injectDialog(TextInputDialogComponent);
/**
* Convenience function that returns a pre-configured NumberInputDialog injector
* @returns A function to open a number input dialog
*/
export const injectNumberInputDialog = () =>
injectDialog(NumberInputDialogComponent);
/**
* Convenience function that returns a pre-configured FeedbackDialog injector
* @returns A function to open a feedback dialog
@@ -129,5 +144,6 @@ export const injectFeedbackDialog = (
injectDialog(FeedbackDialogComponent, {
disableClose: false,
minWidth: '20rem',
classList: ['gap-0'],
...options,
});

View File

@@ -16,3 +16,11 @@ export const DIALOG_TITLE = new InjectionToken<string | undefined>(
export const DIALOG_CONTENT = new InjectionToken<ComponentType<unknown>>(
'DIALOG_CONTENT',
);
/**
* Injection token for providing additional CSS classes to the dialog
* Allows customization of dialog appearance through external styles
*/
export const DIALOG_CLASS_LIST = new InjectionToken<string[]>(
'DIALOG_CLASS_LIST',
);