mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
chore: update dependencies and add new path mappings
- Updated @nx/js from 20.4.6 to 20.8.1 - Updated angular-eslint from 19.1.0 to 19.2.0 - Added path mapping for @isa/common/print in tsconfig.base.json - Added path mapping for @isa/ui/dialog in tsconfig.base.json - Added path mapping for @isa/ui/list in tsconfig.base.json
This commit is contained in:
32
.github/instructions/nx.instructions.md
vendored
Normal file
32
.github/instructions/nx.instructions.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
// This file is automatically generated by Nx Console
|
||||
|
||||
You are in an nx workspace using Nx 20.4.6 and npm as the package manager.
|
||||
|
||||
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
|
||||
|
||||
# General Guidelines
|
||||
- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture
|
||||
- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration
|
||||
- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors
|
||||
- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool
|
||||
|
||||
# Generation Guidelines
|
||||
If the user wants to generate something, use the following flow:
|
||||
|
||||
- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable
|
||||
- get the available generators using the 'nx_generators' tool
|
||||
- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them
|
||||
- get generator details using the 'nx_generator_schema' tool
|
||||
- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure
|
||||
- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic
|
||||
- open the generator UI using the 'nx_open_generate_ui' tool
|
||||
- wait for the user to finish the generator
|
||||
- read the generator log file using the 'nx_read_generator_log' tool
|
||||
- use the information provided in the log file to answer the user's question or continue with what they were doing
|
||||
undefined
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -62,3 +62,6 @@ libs/swagger/src/lib/*
|
||||
|
||||
|
||||
storybook-static
|
||||
|
||||
.cursor\rules\nx-rules.mdc
|
||||
.github\instructions\nx.instructions.md
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -33,12 +33,6 @@
|
||||
{
|
||||
"file": ".github/copilot-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": ".github/review-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": ".github/testing-instructions.md"
|
||||
},
|
||||
{
|
||||
"file": "docs/tech-stack.md"
|
||||
},
|
||||
@@ -95,4 +89,5 @@
|
||||
"file": "docs/guidelines/testing.md"
|
||||
}
|
||||
],
|
||||
"nxConsole.generateAiAgentRules": true,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@use '../../../libs/ui/buttons/src/buttons.scss';
|
||||
@use '../../../libs/ui/datepicker/src/datepicker.scss';
|
||||
@use '../../../libs/ui/input-controls/src/input-controls.scss';
|
||||
@use '../../../libs/ui/progress-bar/src/lib/progress-bar.scss';
|
||||
@use "../../../libs/ui/buttons/src/buttons.scss";
|
||||
@use "../../../libs/ui/datepicker/src/datepicker.scss";
|
||||
@use "../../../libs/ui/dialog/src/dialog.scss";
|
||||
@use "../../../libs/ui/input-controls/src/input-controls.scss";
|
||||
@use "../../../libs/ui/list/src/list.scss";
|
||||
@use "../../../libs/ui/progress-bar/src/lib/progress-bar.scss";
|
||||
|
||||
7
libs/common/print/README.md
Normal file
7
libs/common/print/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# common-print
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test common-print` to execute the unit tests.
|
||||
34
libs/common/print/eslint.config.mjs
Normal file
34
libs/common/print/eslint.config.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'commonPrint',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'common-print',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
21
libs/common/print/jest.config.ts
Normal file
21
libs/common/print/jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'common-print',
|
||||
preset: '../../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../../coverage/libs/common/print',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
20
libs/common/print/project.json
Normal file
20
libs/common/print/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "common-print",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/common/print/src",
|
||||
"prefix": "common-print",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/common/print/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
libs/common/print/src/index.ts
Normal file
2
libs/common/print/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/models';
|
||||
export * from './lib/services';
|
||||
2
libs/common/print/src/lib/models/index.ts
Normal file
2
libs/common/print/src/lib/models/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './printer.model';
|
||||
export * from './printer-type.enum';
|
||||
15
libs/common/print/src/lib/models/printer-type.enum.ts
Normal file
15
libs/common/print/src/lib/models/printer-type.enum.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Enum representing the different types of printers available in the system
|
||||
*/
|
||||
export const PrinterType = {
|
||||
/** Standard office printers for documents */
|
||||
OFFICE: 'office',
|
||||
/** Specialized label printers */
|
||||
LABEL: 'label',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Type representing the possible printer types
|
||||
* Used for type safety when specifying printer types
|
||||
*/
|
||||
export type PrinterType = (typeof PrinterType)[keyof typeof PrinterType];
|
||||
18
libs/common/print/src/lib/models/printer.model.ts
Normal file
18
libs/common/print/src/lib/models/printer.model.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { KeyValueDTOOfStringAndString } from '@generated/swagger/print-api';
|
||||
|
||||
/**
|
||||
* Represents a printer in the system
|
||||
* Extends the basic KeyValueDTO with additional printer-specific properties
|
||||
*/
|
||||
export interface Printer extends KeyValueDTOOfStringAndString {
|
||||
/** Unique identifier for the printer */
|
||||
key: string;
|
||||
/** Display name of the printer */
|
||||
value: string;
|
||||
/** Whether this printer is currently selected as default */
|
||||
selected: boolean;
|
||||
/** Whether this printer is currently enabled for use */
|
||||
enabled: boolean;
|
||||
/** Additional information about the printer */
|
||||
description: string;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<p>
|
||||
Bitte wählen Sie aus der Liste den Drucker aus, mit dem Sie drucken möchten.
|
||||
</p>
|
||||
@if (error()) {
|
||||
<div>
|
||||
<p class="text-isa-accent-red">{{ formatError(error()) }}</p>
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
uiList
|
||||
[value]="selected()"
|
||||
(valueChange)="select($event.value[0])"
|
||||
[compareWith]="compareWith"
|
||||
[disabled]="printing()"
|
||||
class="max-h-96 overflow-y-auto"
|
||||
>
|
||||
@for (printer of data.printers; track printer.key) {
|
||||
<button uiTextListItem [value]="printer">{{ printer.value }}</button>
|
||||
}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button uiButton color="secondary" (click)="close({ printer: undefined })">
|
||||
Verlassen
|
||||
</button>
|
||||
<button
|
||||
uiButton
|
||||
color="primary"
|
||||
(click)="print()"
|
||||
[disabled]="!canPrint()"
|
||||
[pending]="printing()"
|
||||
>
|
||||
Drucken
|
||||
</button>
|
||||
</div>
|
||||
127
libs/common/print/src/lib/print-dialog/print-dialog.component.ts
Normal file
127
libs/common/print/src/lib/print-dialog/print-dialog.component.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Printer } from '../models';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { DialogContentDirective } from '@isa/ui/dialog';
|
||||
import { ListDirective, TextListItemDirective } from '@isa/ui/list';
|
||||
|
||||
/**
|
||||
* Input data for the printer dialog component
|
||||
*/
|
||||
export interface PrinterDialogData {
|
||||
/** Array of available printers to display */
|
||||
printers: Printer[];
|
||||
/** Optional error to display in the dialog */
|
||||
error?: unknown;
|
||||
/** Function to call when user selects a printer and clicks print */
|
||||
print: (printer: Printer) => Promise<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result returned when the printer dialog closes
|
||||
*/
|
||||
export interface PrinterDialogResult {
|
||||
/** Selected printer or undefined if user cancelled */
|
||||
printer: Printer | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog component for printer selection and print operations
|
||||
* Displays a list of printers, allows selection, and handles print errors
|
||||
*/
|
||||
@Component({
|
||||
selector: 'common-print-dialog',
|
||||
templateUrl: './print-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ButtonComponent, ListDirective, TextListItemDirective],
|
||||
})
|
||||
export class PrintDialogComponent extends DialogContentDirective<
|
||||
PrinterDialogData,
|
||||
PrinterDialogResult
|
||||
> {
|
||||
/** Signal for the currently selected printer */
|
||||
printer = signal<Printer | undefined>(
|
||||
this.data.printers.find((p) => p.selected),
|
||||
);
|
||||
|
||||
/** Computed array of selected printers for the listbox component */
|
||||
selected = computed(() => {
|
||||
const printer = this.printer();
|
||||
if (printer) {
|
||||
return [printer];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
/** Signal indicating if a print operation is in progress */
|
||||
printing = signal(false);
|
||||
|
||||
/** Signal for displaying error information */
|
||||
error = signal<unknown>(this.data.error);
|
||||
|
||||
/** Computed property determining if the print button should be enabled */
|
||||
canPrint = computed(() => {
|
||||
return this.printer() !== undefined && !this.printing();
|
||||
});
|
||||
|
||||
/**
|
||||
* Comparison function for printer objects
|
||||
* @param a First printer to compare
|
||||
* @param b Second printer to compare
|
||||
* @returns True if the printers have the same key
|
||||
*/
|
||||
compareWith(a: Printer, b: Printer) {
|
||||
return a.key === b.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles printer selection in the list
|
||||
* @param printer The printer that was selected
|
||||
*/
|
||||
select(printer: Printer) {
|
||||
this.error.set(undefined);
|
||||
this.printer.set(printer);
|
||||
}
|
||||
/**
|
||||
* Executes the print operation with the selected printer
|
||||
* Sets error state if the print operation fails
|
||||
* Closes the dialog with the selected printer if successful
|
||||
*/
|
||||
async print() {
|
||||
this.error.set(undefined);
|
||||
const printer = this.printer();
|
||||
if (!this.canPrint() || !printer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.printing.set(true);
|
||||
|
||||
try {
|
||||
await this.data.print(printer);
|
||||
} catch (error) {
|
||||
this.error.set(error);
|
||||
this.printing.set(false);
|
||||
return;
|
||||
}
|
||||
this.close({ printer });
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats error objects for display in the UI
|
||||
* @param error The error object to format
|
||||
* @returns A user-friendly error message string
|
||||
*/
|
||||
formatError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
} else if (typeof error === 'string') {
|
||||
return error;
|
||||
} else {
|
||||
return 'Unbekannter Fehler';
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/common/print/src/lib/services/index.ts
Normal file
1
libs/common/print/src/lib/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './print.service';
|
||||
94
libs/common/print/src/lib/services/print.service.ts
Normal file
94
libs/common/print/src/lib/services/print.service.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { PrintService as PrintApiService } from '@generated/swagger/print-api';
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
import { firstValueFrom, map, Observable } from 'rxjs';
|
||||
import { Printer, PrinterType } from '../models';
|
||||
|
||||
import { injectDialog } from '@isa/ui/dialog';
|
||||
import { PrintDialogComponent } from '../print-dialog/print-dialog.component';
|
||||
|
||||
/**
|
||||
* Service that provides access to printers available in the system.
|
||||
* Communicates with the print API to retrieve label and office printers.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PrintService {
|
||||
#printService = inject(PrintApiService);
|
||||
#printDailog = injectDialog(PrintDialogComponent, 'Drucken');
|
||||
#platform = inject(Platform);
|
||||
|
||||
/**
|
||||
* Retrieves a list of available label printers
|
||||
* @returns Observable of label printer array
|
||||
*/
|
||||
labelPrinters(): Observable<Printer[]> {
|
||||
return this.#printService
|
||||
.PrintLabelPrinters()
|
||||
.pipe(map((res) => res.result as Printer[]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of available office printers
|
||||
* @returns Observable of office printer array
|
||||
*/
|
||||
officePrinters(): Observable<Printer[]> {
|
||||
return this.#printService
|
||||
.PrintOfficePrinters()
|
||||
.pipe(map((res) => res.result as Printer[]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a print operation with platform-specific optimizations
|
||||
* On desktop, attempts to print directly using the default printer if available
|
||||
* Falls back to showing a printer selection dialog if needed or on mobile devices
|
||||
*
|
||||
* @param printerType The type of printer to use (LABEL or OFFICE)
|
||||
* @param printFn Function that performs the actual print operation with the selected printer
|
||||
* @returns Object containing the selected printer or undefined if operation was cancelled
|
||||
*/
|
||||
async print(
|
||||
printerType: PrinterType,
|
||||
printFn: (printer: Printer) => Promise<unknown>,
|
||||
): Promise<{ printer?: Printer }> {
|
||||
// Get the list of printers based on the printer type
|
||||
const printers$ =
|
||||
printerType === PrinterType.LABEL
|
||||
? this.labelPrinters()
|
||||
: this.officePrinters();
|
||||
|
||||
const printers = await firstValueFrom(printers$);
|
||||
|
||||
// If the platform is not Android or iOS, we can assume this is stationary devices
|
||||
// and we can try to print directly to the selected printer.
|
||||
// If it fails, we show the print dialog with the error.
|
||||
let error: unknown | undefined = undefined;
|
||||
if (!(this.#platform.ANDROID || this.#platform.IOS)) {
|
||||
const selectedPrinter = printers.find((p) => p.selected);
|
||||
|
||||
if (selectedPrinter) {
|
||||
try {
|
||||
await printFn(selectedPrinter);
|
||||
return { printer: selectedPrinter };
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default behavior: show the print dialog
|
||||
// and let the user select a printer.
|
||||
const result = await firstValueFrom(
|
||||
this.#printDailog({
|
||||
data: {
|
||||
printers,
|
||||
error,
|
||||
print: printFn,
|
||||
},
|
||||
}).closed,
|
||||
);
|
||||
|
||||
// Returns a printer if the print was successful
|
||||
// or undefined if the user canceled the dialog.
|
||||
return { printer: result?.printer };
|
||||
}
|
||||
}
|
||||
6
libs/common/print/src/test-setup.ts
Normal file
6
libs/common/print/src/test-setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
28
libs/common/print/tsconfig.json
Normal file
28
libs/common/print/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
17
libs/common/print/tsconfig.lib.json
Normal file
17
libs/common/print/tsconfig.lib.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
16
libs/common/print/tsconfig.spec.json
Normal file
16
libs/common/print/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
0
libs/core/process/README-location-strategy.md
Normal file
0
libs/core/process/README-location-strategy.md
Normal file
0
libs/core/process/src/lib/process.store.ts
Normal file
0
libs/core/process/src/lib/process.store.ts
Normal file
@@ -17,13 +17,5 @@ export * from './lib/errors';
|
||||
export * from './lib/models';
|
||||
export * from './lib/helpers/return-process';
|
||||
export * from './lib/schemas';
|
||||
export * from './lib/return-details.service';
|
||||
export * from './lib/return-details.store';
|
||||
export * from './lib/return-process.service';
|
||||
export * from './lib/return-process.store';
|
||||
export * from './lib/return-search.service';
|
||||
export * from './lib/return-search.store';
|
||||
export * from './lib/return-print-receipts.service';
|
||||
export * from './lib/return-task-list.service';
|
||||
export * from './lib/return-task-list.store';
|
||||
export * from './lib/return-can-return.service';
|
||||
export * from './lib/services';
|
||||
export * from './lib/stores';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export * from './errors';
|
||||
export * from './models';
|
||||
export * from './helpers/return-process';
|
||||
export * from './return-details.service';
|
||||
export * from './return-details.store';
|
||||
export * from './return-process.service';
|
||||
export * from './return-process.store';
|
||||
export * from './return-search.service';
|
||||
export * from './return-search.store';
|
||||
export * from './return-print-receipts.service';
|
||||
export * from './return-task-list.service';
|
||||
export * from './return-task-list.store';
|
||||
export * from './return-can-return.service';
|
||||
export * from './schemas';
|
||||
@@ -1,67 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { DomainPrinterService, Printer } from '@domain/printer';
|
||||
import { PrintModalComponent, PrintModalData } from '@modal/printer';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ReturnPrintReceiptsService {
|
||||
#printService = inject(DomainPrinterService);
|
||||
#environmentSerivce = inject(EnvironmentService);
|
||||
|
||||
// TODO: Refactor: CDK Dialog verwenden für neuen Printer Dialog
|
||||
#uiModal = inject(UiModalService);
|
||||
// TODO: Refactor: CDK Dialog verwenden für neuen Printer Dialog
|
||||
/**
|
||||
* Prints return receipts using the appropriate printer
|
||||
*
|
||||
* This method:
|
||||
* 1. Retrieves available label printers
|
||||
* 2. Determines if a printer is selected or if running on tablet mode
|
||||
* 3. Opens a print modal dialog if no printer is selected or on tablet
|
||||
* 4. Prints directly to the selected printer when available
|
||||
*
|
||||
* @param {number[]} receiptIds - Array of receipt IDs to print
|
||||
* @returns {Promise<number[]>} Promise resolving to the same array of receipt IDs
|
||||
* @throws {Error} When printing operations fail
|
||||
*/
|
||||
async printReturns(receiptIds: number[]) {
|
||||
const printerList = await firstValueFrom(
|
||||
this.#printService.getAvailableLabelPrinters(),
|
||||
);
|
||||
let printer: Printer | undefined = undefined;
|
||||
|
||||
if (Array.isArray(printerList)) {
|
||||
printer = printerList.find((printer) => printer.selected === true);
|
||||
}
|
||||
|
||||
if (!printer || this.#environmentSerivce.matchTablet()) {
|
||||
await this.#uiModal
|
||||
.open({
|
||||
content: PrintModalComponent,
|
||||
config: { showScrollbarY: false },
|
||||
data: {
|
||||
printImmediately: !this.#environmentSerivce.matchTablet(),
|
||||
printerType: 'Label',
|
||||
print: (printer) =>
|
||||
this.#printService
|
||||
.printReturnReceipt({
|
||||
printer: printer,
|
||||
receiptIds,
|
||||
})
|
||||
.toPromise(),
|
||||
} as PrintModalData,
|
||||
})
|
||||
.afterClosed$.toPromise();
|
||||
} else {
|
||||
await firstValueFrom(
|
||||
this.#printService.printReturnReceipt({
|
||||
printer: printer.key,
|
||||
receiptIds,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return receiptIds;
|
||||
}
|
||||
}
|
||||
6
libs/oms/data-access/src/lib/services/index.ts
Normal file
6
libs/oms/data-access/src/lib/services/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './return-can-return.service';
|
||||
export * from './return-details.service';
|
||||
export * from './return-print-receipts.service';
|
||||
export * from './return-process.service';
|
||||
export * from './return-search.service';
|
||||
export * from './return-task-list.service';
|
||||
@@ -3,16 +3,16 @@ import {
|
||||
ReceiptService,
|
||||
ReturnReceiptValuesDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { ReturnReceiptValues, ReturnReceiptValuesSchema } from './schemas';
|
||||
import { ReturnReceiptValues, ReturnReceiptValuesSchema } from '../schemas';
|
||||
import { debounceTime, firstValueFrom, map } from 'rxjs';
|
||||
import { CanReturn, ReturnProcess } from './models';
|
||||
import { CanReturn, ReturnProcess } from '../models';
|
||||
import {
|
||||
allReturnProcessQuestionsAnswered,
|
||||
getReturnProcessQuestions,
|
||||
returnReceiptValuesMapping,
|
||||
} from './helpers/return-process';
|
||||
} from '../helpers/return-process';
|
||||
import { memorize } from '@utils/common';
|
||||
import { isReturnProcessTypeGuard } from './guards';
|
||||
import { isReturnProcessTypeGuard } from '../guards';
|
||||
|
||||
/**
|
||||
* Service for determining if a return process can proceed based on
|
||||
@@ -2,8 +2,8 @@ import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
|
||||
import { ReturnDetailsService } from './return-details.service';
|
||||
import { ReceiptService } from '@generated/swagger/oms-api';
|
||||
import { of } from 'rxjs';
|
||||
import { FetchReturnDetails } from './schemas';
|
||||
import { Receipt } from './models';
|
||||
import { FetchReturnDetails } from '../schemas';
|
||||
import { Receipt } from '../models';
|
||||
|
||||
describe('ReturnDetailsService', () => {
|
||||
let spectator: SpectatorService<ReturnDetailsService>;
|
||||
@@ -3,11 +3,11 @@ import {
|
||||
FetchReturnDetails,
|
||||
FetchReturnDetailsSchema,
|
||||
ReturnReceiptValues,
|
||||
} from './schemas';
|
||||
} from '../schemas';
|
||||
import { map, Observable, throwError } from 'rxjs';
|
||||
import { ReceiptService } from '@generated/swagger/oms-api';
|
||||
import { Receipt, ReceiptItem } from './models';
|
||||
import { CategoryQuestions } from './questions';
|
||||
import { Receipt, ReceiptItem } from '../models';
|
||||
import { CategoryQuestions } from '../questions';
|
||||
import { KeyValue } from '@angular/common';
|
||||
import { ReturnCanReturnService } from './return-can-return.service';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { OMSPrintService } from '@generated/swagger/print-api';
|
||||
import { PrinterType, PrintService } from '@isa/common/print';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ReturnPrintReceiptsService {
|
||||
#omsPrintService = inject(OMSPrintService);
|
||||
#printService = inject(PrintService);
|
||||
|
||||
async printReturnReceipts({
|
||||
returnReceiptIds,
|
||||
}: {
|
||||
returnReceiptIds: number[];
|
||||
}) {
|
||||
return this.#printService.print(PrinterType.LABEL, (printer) => {
|
||||
return firstValueFrom(
|
||||
this.#omsPrintService.OMSPrintReturnReceipt({
|
||||
printer: printer.key,
|
||||
data: returnReceiptIds,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,19 @@ import {
|
||||
ReturnProcessAnswers,
|
||||
ReturnProcessQuestion,
|
||||
ReturnProcessQuestionType,
|
||||
} from './models';
|
||||
import { ProductCategory } from './questions';
|
||||
} from '../models';
|
||||
import { ProductCategory } from '../questions';
|
||||
import {
|
||||
ReturnProcessChecklistAnswerSchema,
|
||||
ReturnReceiptValues,
|
||||
ReturnReceiptValuesSchema,
|
||||
} from './schemas';
|
||||
} from '../schemas';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import {
|
||||
PropertyIsEmptyError,
|
||||
PropertyNullOrUndefinedError,
|
||||
} from '@isa/common/data-access';
|
||||
import { ReturnProcessIsNotCompleteError } from './errors/return-process';
|
||||
import { ReturnProcessIsNotCompleteError } from '../errors/return-process';
|
||||
import {
|
||||
activeReturnProcessQuestions,
|
||||
allReturnProcessQuestionsAnswered,
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
isTolinoEligibleForReturn,
|
||||
isTonDatentraegerEligibleForReturn,
|
||||
returnReceiptValuesMapping,
|
||||
} from './helpers/return-process';
|
||||
} from '../helpers/return-process';
|
||||
import { isEmpty, isNil } from 'lodash';
|
||||
import {
|
||||
ReceiptService,
|
||||
@@ -301,7 +301,9 @@ export class ReturnProcessService {
|
||||
|
||||
const receipts = response.result as Receipt[];
|
||||
const receiptIds = receipts.map((receipt) => receipt.id);
|
||||
await this.#printReceiptsService.printReturns(receiptIds);
|
||||
await this.#printReceiptsService.printReturnReceipts({
|
||||
returnReceiptIds: receiptIds,
|
||||
});
|
||||
|
||||
return receipts;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { QuerySettingsDTO, ReceiptService } from '@generated/swagger/oms-api';
|
||||
import { map, Observable, throwError } from 'rxjs';
|
||||
import { QueryTokenInput, QueryTokenSchema } from './schemas';
|
||||
import { ReceiptListItem } from './models';
|
||||
import { QueryTokenInput, QueryTokenSchema } from '../schemas';
|
||||
import { ReceiptListItem } from '../models';
|
||||
import { ListResponseArgs } from '@isa/common/data-access';
|
||||
import {
|
||||
ReturnParseQueryTokenError,
|
||||
ReturnSearchSearchError,
|
||||
} from './errors/return-search.error';
|
||||
} from '../errors/return-search.error';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -1,9 +1,9 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { map, Observable, throwError } from 'rxjs';
|
||||
import { ReceiptItemTaskListItem, TaskActionTypeType } from './models';
|
||||
import { QueryTokenInput, QueryTokenSchema } from './schemas';
|
||||
import { ReceiptItemTaskListItem, TaskActionTypeType } from '../models';
|
||||
import { QueryTokenInput, QueryTokenSchema } from '../schemas';
|
||||
import { ZodError } from 'zod';
|
||||
import { ReturnParseQueryTokenError } from './errors';
|
||||
import { ReturnParseQueryTokenError } from '../errors';
|
||||
import {
|
||||
ReceiptService,
|
||||
ResponseArgsOfReceiptItemTaskListItemDTO,
|
||||
4
libs/oms/data-access/src/lib/stores/index.ts
Normal file
4
libs/oms/data-access/src/lib/stores/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './return-details.store';
|
||||
export * from './return-process.store';
|
||||
export * from './return-search.store';
|
||||
export * from './return-task-list.store';
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createServiceFactory } from '@ngneat/spectator/jest';
|
||||
import { ReturnDetailsStore } from './return-details.store';
|
||||
import { ReturnDetailsService } from './return-details.service';
|
||||
import { ReturnDetailsService } from '../services';
|
||||
import { patchState } from '@ngrx/signals';
|
||||
import { AsyncResultStatus } from '@isa/common/data-access';
|
||||
import { addEntity } from '@ngrx/signals/entities';
|
||||
import { Receipt } from './models';
|
||||
import { Receipt } from '../models';
|
||||
import { of, throwError } from 'rxjs';
|
||||
|
||||
describe('ReturnDetailsStore', () => {
|
||||
@@ -4,9 +4,9 @@ import { AsyncResult, AsyncResultStatus } from '@isa/common/data-access';
|
||||
import { rxMethod } from '@ngrx/signals/rxjs-interop';
|
||||
import { pipe, switchMap, tap } from 'rxjs';
|
||||
import { inject } from '@angular/core';
|
||||
import { ReturnDetailsService } from './return-details.service';
|
||||
import { ReturnDetailsService } from '../services';
|
||||
import { tapResponse } from '@ngrx/operators';
|
||||
import { Receipt } from './models';
|
||||
import { Receipt } from '../models';
|
||||
|
||||
/**
|
||||
* Represents the result of a return operation, including the receipt data and status.
|
||||
@@ -4,8 +4,8 @@ import { IDBStorageProvider } from '@isa/core/storage';
|
||||
import { ProcessService } from '@isa/core/process';
|
||||
import { patchState } from '@ngrx/signals';
|
||||
import { setAllEntities } from '@ngrx/signals/entities';
|
||||
import { Product, Receipt, ReturnProcess } from './models';
|
||||
import { CreateReturnProcessError } from './errors/return-process';
|
||||
import { Product, Receipt, ReturnProcess } from '../models';
|
||||
import { CreateReturnProcessError } from '../errors/return-process';
|
||||
|
||||
const TEST_ITEMS: Record<number, ReturnProcess['receiptItem']> = {
|
||||
1: {
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
import { IDBStorageProvider, withStorage } from '@isa/core/storage';
|
||||
import { computed, effect, inject } from '@angular/core';
|
||||
import { ProcessService } from '@isa/core/process';
|
||||
import { Receipt, ReceiptItem, ReturnProcess } from './models';
|
||||
import { Receipt, ReceiptItem, ReturnProcess } from '../models';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import {
|
||||
CreateReturnProcessError,
|
||||
CreateReturnProcessErrorReason,
|
||||
} from './errors/return-process';
|
||||
} from '../errors/return-process';
|
||||
|
||||
/**
|
||||
* Interface representing the parameters required to start a return process.
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createServiceFactory } from '@ngneat/spectator/jest';
|
||||
import { ReturnSearchStore, ReturnSearchStatus } from './return-search.store';
|
||||
import { ReturnSearchService } from './return-search.service';
|
||||
import { ReturnSearchService } from '../services';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { ListResponseArgs } from '@isa/common/data-access';
|
||||
import { ReceiptListItem } from './models';
|
||||
import { ReceiptListItem } from '../models';
|
||||
|
||||
describe('ReturnSearchStore', () => {
|
||||
const createService = createServiceFactory({
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
withEntities,
|
||||
} from '@ngrx/signals/entities';
|
||||
import { pipe, switchMap, tap } from 'rxjs';
|
||||
import { ReturnSearchService } from './return-search.service';
|
||||
import { ReturnSearchService } from '../services';
|
||||
import { tapResponse } from '@ngrx/operators';
|
||||
import { effect, inject } from '@angular/core';
|
||||
import { QueryTokenSchema } from './schemas';
|
||||
import { QueryTokenSchema } from '../schemas';
|
||||
import { Callback, ListResponseArgs } from '@isa/common/data-access';
|
||||
import { ReceiptListItem } from './models';
|
||||
import { ReceiptListItem } from '../models';
|
||||
import { Query } from '@isa/shared/filter';
|
||||
import { SessionStorageProvider, withStorage } from '@isa/core/storage';
|
||||
import { ProcessService } from '@isa/core/process';
|
||||
@@ -1,14 +1,14 @@
|
||||
import { patchState, signalStore, withMethods } from '@ngrx/signals';
|
||||
import { withEntities, updateEntity, addEntity } from '@ngrx/signals/entities';
|
||||
import { inject } from '@angular/core';
|
||||
import { ReceiptItemTaskListItem } from './models';
|
||||
import { ReceiptItemTaskListItem } from '../models';
|
||||
import { AsyncResult, AsyncResultStatus } from '@isa/common/data-access';
|
||||
import { rxMethod } from '@ngrx/signals/rxjs-interop';
|
||||
import { ReturnTaskListService } from './return-task-list.service';
|
||||
import { ReturnTaskListService } from '../services';
|
||||
import { pipe, switchMap, tap } from 'rxjs';
|
||||
import { tapResponse } from '@ngrx/operators';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { QueryTokenInput } from './schemas';
|
||||
import { QueryTokenInput } from '../schemas';
|
||||
|
||||
/**
|
||||
* Represents the structure of a return task list item in the store.
|
||||
@@ -27,7 +27,9 @@ export class ReturnReviewComponent {
|
||||
this.#returnProcessStore.entityMap()[processId].receiptId;
|
||||
|
||||
if (receiptId) {
|
||||
await this.#printReceiptsService.printReturns([receiptId]);
|
||||
await this.#printReceiptsService.printReturnReceipts({
|
||||
returnReceiptIds: [receiptId],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, effect, inject, untracked } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
effect,
|
||||
inject,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
import { injectActivatedProcessId } from '@isa/core/process';
|
||||
@@ -19,9 +25,15 @@ function querySettingsFactory() {
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [RouterOutlet],
|
||||
providers: [provideFilter(withQuerySettingsFactory(querySettingsFactory), withQueryParamsSync())],
|
||||
providers: [
|
||||
provideFilter(
|
||||
withQuerySettingsFactory(querySettingsFactory),
|
||||
withQueryParamsSync(),
|
||||
),
|
||||
],
|
||||
host: {
|
||||
'[class]': '"flex flex-col gap-5 isa-desktop:gap-6 items-center overflow-x-hidden"',
|
||||
'[class]':
|
||||
'"flex flex-col gap-5 isa-desktop:gap-6 items-center overflow-x-hidden"',
|
||||
},
|
||||
})
|
||||
export class ReturnSearchComponent {
|
||||
@@ -64,7 +76,10 @@ export class ReturnSearchComponent {
|
||||
|
||||
if (items) {
|
||||
if (items?.length === 1) {
|
||||
return await this._navigateTo(['receipts', items[0].id.toString()]);
|
||||
return await this._navigateTo([
|
||||
'receipts',
|
||||
items[0].id.toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (items?.length >= 0) {
|
||||
|
||||
7
libs/ui/dialog/README.md
Normal file
7
libs/ui/dialog/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# ui-dialog
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test ui-dialog` to execute the unit tests.
|
||||
34
libs/ui/dialog/eslint.config.mjs
Normal file
34
libs/ui/dialog/eslint.config.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'ui',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'ui',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
21
libs/ui/dialog/jest.config.ts
Normal file
21
libs/ui/dialog/jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'ui-dialog',
|
||||
preset: '../../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../../coverage/libs/ui/dialog',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
20
libs/ui/dialog/project.json
Normal file
20
libs/ui/dialog/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "ui-dialog",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/ui/dialog/src",
|
||||
"prefix": "ui-dialog",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/ui/dialog/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
libs/ui/dialog/src/dialog.scss
Normal file
11
libs/ui/dialog/src/dialog.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.ui-dialog {
|
||||
@apply bg-isa-white p-8 flex gap-8 items-start rounded-[2rem] flex-col text-isa-neutral-900;
|
||||
|
||||
.ui-dialog-title {
|
||||
@apply isa-text-subtitle-1-bold;
|
||||
}
|
||||
|
||||
.ui-dialog-content {
|
||||
@apply flex flex-col gap-8;
|
||||
}
|
||||
}
|
||||
5
libs/ui/dialog/src/index.ts
Normal file
5
libs/ui/dialog/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './lib/dialog-content.directive';
|
||||
export * from './lib/dialog.component';
|
||||
export * from './lib/injects';
|
||||
export * from './lib/message-dialog/message-dialog.component';
|
||||
export * from './lib/tokens';
|
||||
30
libs/ui/dialog/src/lib/dialog-content.directive.ts
Normal file
30
libs/ui/dialog/src/lib/dialog-content.directive.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
|
||||
import { Directive, inject } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Base directive for dialog content components
|
||||
* Provides common dialog functionality and styling
|
||||
*
|
||||
* @template D Type of the data passed to the dialog
|
||||
* @template R Type of the result returned when the dialog closes
|
||||
*/
|
||||
@Directive({
|
||||
host: {
|
||||
'[class]': '["ui-dialog-content"]',
|
||||
},
|
||||
})
|
||||
export abstract class DialogContentDirective<D, R> {
|
||||
/** Reference to the dialog instance */
|
||||
readonly dialogRef = inject(DialogRef<R, DialogContentDirective<D, R>>);
|
||||
|
||||
/** Data passed to the dialog */
|
||||
readonly data = inject(DIALOG_DATA) as D;
|
||||
|
||||
/**
|
||||
* Closes the dialog with the specified result
|
||||
* @param result The value to return from the dialog
|
||||
*/
|
||||
close(result: R): void {
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
}
|
||||
5
libs/ui/dialog/src/lib/dialog.component.html
Normal file
5
libs/ui/dialog/src/lib/dialog.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<h2 class="ui-dialog-title">
|
||||
{{ title }}
|
||||
</h2>
|
||||
|
||||
<ng-container *ngComponentOutlet="component"> </ng-container>
|
||||
30
libs/ui/dialog/src/lib/dialog.component.ts
Normal file
30
libs/ui/dialog/src/lib/dialog.component.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { DialogContentDirective } from './dialog-content.directive';
|
||||
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
|
||||
import { ComponentType } from '@angular/cdk/portal';
|
||||
import { NgComponentOutlet } from '@angular/common';
|
||||
|
||||
/**
|
||||
* Base dialog component that serves as a container for dialog content
|
||||
* Handles the outer dialog shell including title and content projection
|
||||
*
|
||||
* @template D Type of data passed to the dialog
|
||||
* @template R Type of result returned from the dialog
|
||||
* @template C Type of content component displayed in the dialog
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-dialog',
|
||||
templateUrl: './dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgComponentOutlet],
|
||||
host: {
|
||||
'[class]': '["ui-dialog"]',
|
||||
},
|
||||
})
|
||||
export class DialogComponent<D, R, C extends DialogContentDirective<D, R>> {
|
||||
/** The title to display at the top of the dialog */
|
||||
title = inject(DIALOG_TITLE);
|
||||
|
||||
/** The component type to instantiate as the dialog content */
|
||||
readonly component = inject(DIALOG_CONTENT) as ComponentType<C>;
|
||||
}
|
||||
74
libs/ui/dialog/src/lib/injects.ts
Normal file
74
libs/ui/dialog/src/lib/injects.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Dialog } from '@angular/cdk/dialog';
|
||||
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 { MessageDialogComponent } from './message-dialog/message-dialog.component';
|
||||
|
||||
/**
|
||||
* Options for opening a dialog using injectDialog function
|
||||
* @template D The type of data passed to the dialog
|
||||
*/
|
||||
export interface OpenDialogOptions<D> {
|
||||
/** Optional title override for the dialog */
|
||||
title?: string;
|
||||
/** Data to pass to the dialog component */
|
||||
data: D;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function to open a dialog with a specific component
|
||||
* Uses Angular's DI system for dialog configuration
|
||||
*
|
||||
* @template C The type of component to display in the dialog
|
||||
* @param componentType The component class to instantiate in the dialog
|
||||
* @param title Default title for the dialog
|
||||
* @returns A function that opens the dialog with provided options
|
||||
*/
|
||||
export function injectDialog<C extends DialogContentDirective<any, any>>(
|
||||
componentType: ComponentType<C>,
|
||||
title?: string,
|
||||
) {
|
||||
type D = C extends DialogContentDirective<infer D, any> ? D : never;
|
||||
type R = C extends DialogContentDirective<any, infer R> ? R : never;
|
||||
|
||||
const cdkDialog = inject(Dialog);
|
||||
const injector = inject(Injector);
|
||||
|
||||
return (options?: OpenDialogOptions<D>) => {
|
||||
const dialogInjector = Injector.create({
|
||||
parent: injector,
|
||||
providers: [
|
||||
{
|
||||
provide: DIALOG_CONTENT,
|
||||
useValue: componentType,
|
||||
},
|
||||
{
|
||||
provide: DIALOG_TITLE,
|
||||
useValue: options?.title ?? title,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dialogRef = cdkDialog.open<R, D, DialogComponent<D, R, C>>(
|
||||
DialogComponent<D, R, C>,
|
||||
{
|
||||
data: options?.data,
|
||||
injector: dialogInjector,
|
||||
width: '30rem',
|
||||
hasBackdrop: true,
|
||||
disableClose: true,
|
||||
},
|
||||
);
|
||||
|
||||
return dialogRef;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that returns a pre-configured MessageDialog injector
|
||||
* @returns A function to open a message dialog
|
||||
*/
|
||||
export const injectMessageDialog = () => injectDialog(MessageDialogComponent);
|
||||
@@ -0,0 +1,8 @@
|
||||
<p class="isa-text-body-1-regular text-isa-neutral-600">
|
||||
{{ data.message }}
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<button uiButton (click)="close()">
|
||||
{{ data.closeText || 'Schließen' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
|
||||
import { DialogContentDirective } from '../dialog-content.directive';
|
||||
|
||||
/**
|
||||
* Input data for the message dialog
|
||||
*/
|
||||
export interface MessageDialogData {
|
||||
/** The message text to display in the dialog */
|
||||
message: string;
|
||||
/** Optional custom text for the close button (defaults to "Close" or equivalent) */
|
||||
closeText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple message dialog component
|
||||
* Used for displaying informational messages to the user
|
||||
* Returns void when closed (no result)
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-message-dialog',
|
||||
templateUrl: './message-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ButtonComponent],
|
||||
})
|
||||
export class MessageDialogComponent extends DialogContentDirective<
|
||||
MessageDialogData,
|
||||
void
|
||||
> {}
|
||||
16
libs/ui/dialog/src/lib/tokens.ts
Normal file
16
libs/ui/dialog/src/lib/tokens.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ComponentType } from '@angular/cdk/portal';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Injection token for providing the dialog title text
|
||||
* Used internally by the dialog system to display the title
|
||||
*/
|
||||
export const DIALOG_TITLE = new InjectionToken<string>('DIALOG_TITLE');
|
||||
|
||||
/**
|
||||
* Injection token for providing the dialog content component
|
||||
* Used internally by the dialog system to instantiate the correct content component
|
||||
*/
|
||||
export const DIALOG_CONTENT = new InjectionToken<ComponentType<unknown>>(
|
||||
'DIALOG_CONTENT',
|
||||
);
|
||||
6
libs/ui/dialog/src/test-setup.ts
Normal file
6
libs/ui/dialog/src/test-setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
28
libs/ui/dialog/tsconfig.json
Normal file
28
libs/ui/dialog/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
17
libs/ui/dialog/tsconfig.lib.json
Normal file
17
libs/ui/dialog/tsconfig.lib.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
16
libs/ui/dialog/tsconfig.spec.json
Normal file
16
libs/ui/dialog/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
7
libs/ui/list/README.md
Normal file
7
libs/ui/list/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# ui-list
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test ui-list` to execute the unit tests.
|
||||
34
libs/ui/list/eslint.config.mjs
Normal file
34
libs/ui/list/eslint.config.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'ui',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'ui',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
21
libs/ui/list/jest.config.ts
Normal file
21
libs/ui/list/jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'ui-list',
|
||||
preset: '../../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../../coverage/libs/ui/list',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
20
libs/ui/list/project.json
Normal file
20
libs/ui/list/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "ui-list",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/ui/list/src",
|
||||
"prefix": "ui",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/ui/list/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
libs/ui/list/src/index.ts
Normal file
2
libs/ui/list/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/list.directive';
|
||||
export * from './lib/text-list-item.directive';
|
||||
14
libs/ui/list/src/lib/_list.scss
Normal file
14
libs/ui/list/src/lib/_list.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
.ui-list {
|
||||
@apply flex flex-col items-stretch w-full;
|
||||
}
|
||||
|
||||
.ui-text-list-item {
|
||||
@apply flex h-12 px-6 flex-col justify-center items-start gap-[0.65rem] rounded-[.5rem] text-isa-neutral-700 bg-isa-white;
|
||||
@apply isa-text-body-2-bold;
|
||||
|
||||
&.cdk-option-active,
|
||||
&.active,
|
||||
&:hover {
|
||||
@apply text-isa-accent-blue bg-isa-neutral-200;
|
||||
}
|
||||
}
|
||||
0
libs/ui/list/src/lib/list.component.ts
Normal file
0
libs/ui/list/src/lib/list.component.ts
Normal file
34
libs/ui/list/src/lib/list.directive.ts
Normal file
34
libs/ui/list/src/lib/list.directive.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Directive } from '@angular/core';
|
||||
import { CdkListbox } from '@angular/cdk/listbox';
|
||||
|
||||
/**
|
||||
* Directive that adds UI styling to an Angular CDK listbox
|
||||
* Simplifies listbox creation by exposing common CDK listbox inputs/outputs
|
||||
* with more intuitive names for the application design system.
|
||||
*
|
||||
* Usage:
|
||||
* ```html
|
||||
* <div uiList [value]="selected" (valueChange)="handleChange($event)"
|
||||
* [compareWith]="compareItems" [disabled]="isDisabled">
|
||||
* <!-- List items go here -->
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[uiList]',
|
||||
host: {
|
||||
class: 'ui-list',
|
||||
},
|
||||
hostDirectives: [
|
||||
{
|
||||
directive: CdkListbox,
|
||||
inputs: [
|
||||
'cdkListboxValue: value',
|
||||
'cdkListboxCompareWith: compareWith',
|
||||
'cdkListboxDisabled: disabled',
|
||||
],
|
||||
outputs: ['cdkListboxValueChange: valueChange'],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ListDirective {}
|
||||
0
libs/ui/list/src/lib/list.examples.ts
Normal file
0
libs/ui/list/src/lib/list.examples.ts
Normal file
0
libs/ui/list/src/lib/text-list-item.component.ts
Normal file
0
libs/ui/list/src/lib/text-list-item.component.ts
Normal file
27
libs/ui/list/src/lib/text-list-item.directive.ts
Normal file
27
libs/ui/list/src/lib/text-list-item.directive.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Directive } from '@angular/core';
|
||||
import { CdkOption } from '@angular/cdk/listbox';
|
||||
|
||||
/**
|
||||
* Directive for text-based list items within a uiList container
|
||||
* Applies text-specific styling and behavior for list items
|
||||
*
|
||||
* Usage:
|
||||
* ```html
|
||||
* <button uiTextListItem [value]="item" [disabled]="isItemDisabled">
|
||||
* {{ item.label }}
|
||||
* </button>
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[uiTextListItem]',
|
||||
host: {
|
||||
class: 'ui-text-list-item',
|
||||
},
|
||||
hostDirectives: [
|
||||
{
|
||||
directive: CdkOption,
|
||||
inputs: ['cdkOption: value', 'cdkOptionDisabled: disabled'],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class TextListItemDirective {}
|
||||
1
libs/ui/list/src/list.scss
Normal file
1
libs/ui/list/src/list.scss
Normal file
@@ -0,0 +1 @@
|
||||
@use "./lib/list";
|
||||
6
libs/ui/list/src/test-setup.ts
Normal file
6
libs/ui/list/src/test-setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
28
libs/ui/list/tsconfig.json
Normal file
28
libs/ui/list/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
17
libs/ui/list/tsconfig.lib.json
Normal file
17
libs/ui/list/tsconfig.lib.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
16
libs/ui/list/tsconfig.spec.json
Normal file
16
libs/ui/list/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
3787
package-lock.json
generated
3787
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@
|
||||
"@nx/eslint": "20.4.6",
|
||||
"@nx/eslint-plugin": "20.4.6",
|
||||
"@nx/jest": "20.4.6",
|
||||
"@nx/js": "20.4.6",
|
||||
"@nx/js": "20.8.1",
|
||||
"@nx/storybook": "^20.4.6",
|
||||
"@nx/web": "20.4.6",
|
||||
"@nx/workspace": "20.4.6",
|
||||
@@ -93,7 +93,7 @@
|
||||
"@types/node": "18.16.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/utils": "^8.19.0",
|
||||
"angular-eslint": "^19.1.0",
|
||||
"angular-eslint": "^19.2.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"@hub/*": ["apps/isa-app/src/hub/*/index.ts"],
|
||||
"@isa/catalogue/data-access": ["libs/catalogue/data-access/src/index.ts"],
|
||||
"@isa/common/data-access": ["libs/common/data-access/src/index.ts"],
|
||||
"@isa/common/print": ["libs/common/print/src/index.ts"],
|
||||
"@isa/core/config": ["libs/core/config/src/index.ts"],
|
||||
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
|
||||
"@isa/core/notifications": ["libs/core/notifications/src/index.ts"],
|
||||
@@ -73,10 +74,12 @@
|
||||
"@isa/shared/product-image": ["libs/shared/product-image/src/index.ts"],
|
||||
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
|
||||
"@isa/ui/datepicker": ["libs/ui/datepicker/src/index.ts"],
|
||||
"@isa/ui/dialog": ["libs/ui/dialog/src/index.ts"],
|
||||
"@isa/ui/empty-state": ["libs/ui/empty-state/src/index.ts"],
|
||||
"@isa/ui/input-controls": ["libs/ui/input-controls/src/index.ts"],
|
||||
"@isa/ui/item-rows": ["libs/ui/item-rows/src/index.ts"],
|
||||
"@isa/ui/layout": ["libs/ui/layout/src/index.ts"],
|
||||
"@isa/ui/list": ["libs/ui/list/src/index.ts"],
|
||||
"@isa/ui/progress-bar": ["libs/ui/progress-bar/src/index.ts"],
|
||||
"@isa/ui/search-bar": ["libs/ui/search-bar/src/index.ts"],
|
||||
"@isa/ui/toolbar": ["libs/ui/toolbar/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user