mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 14:32:10 +01:00
chore: update dependencies and add vitest configuration
- Added @analogjs/vite-plugin-angular and @analogjs/vitest-angular to devDependencies. - Updated @nx/vite to version 20.1.4. - Added @vitest/coverage-v8 and @vitest/ui to devDependencies. - Added jsdom to devDependencies. - Added vite and vitest to devDependencies. - Updated tsconfig.base.json to include new paths for shared libraries. - Created vitest.workspace.ts for vitest configuration. Refs: #5135
This commit is contained in:
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__context7__get-library-docs"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -66,3 +66,5 @@ storybook-static
|
||||
.github\instructions\nx.instructions.md
|
||||
.cursor/rules/nx-rules.mdc
|
||||
.github/instructions/nx.instructions.md
|
||||
|
||||
vite.config.*.timestamp*
|
||||
@@ -5,7 +5,7 @@
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 80,
|
||||
"endOfLine": "crlf",
|
||||
"endOfLine": "auto",
|
||||
"arrowParens": "always",
|
||||
"quoteProps": "consistent",
|
||||
"overrides": [
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
@use "../../../libs/ui/input-controls/src/input-controls.scss";
|
||||
@use "../../../libs/ui/menu/src/menu.scss";
|
||||
@use "../../../libs/ui/progress-bar/src/lib/progress-bar.scss";
|
||||
@use "../../../libs/ui/search-bar/src/search-bar.scss";
|
||||
@use "../../../libs/ui/skeleton-loader/src/skeleton-loader.scss";
|
||||
@use "../../../libs/ui/tooltip/src/tooltip.scss";
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './lib/services';
|
||||
export * from './lib/schemas';
|
||||
export * from './lib/models';
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { AvailabilityDTO } from '@generated/swagger/cat-search-api';
|
||||
import { Price } from './price';
|
||||
|
||||
export interface Availability extends AvailabilityDTO {
|
||||
price: Price;
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
export * from './availability';
|
||||
export * from './item';
|
||||
export * from './price-value';
|
||||
export * from './price';
|
||||
export * from './product';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ItemDTO } from '@generated/swagger/cat-search-api';
|
||||
import { Product } from './product';
|
||||
import { Availability } from './availability';
|
||||
|
||||
export interface Item extends ItemDTO {
|
||||
id: number;
|
||||
product: Product;
|
||||
catalogAvailability: Availability;
|
||||
}
|
||||
|
||||
5
libs/catalogue/data-access/src/lib/models/price-value.ts
Normal file
5
libs/catalogue/data-access/src/lib/models/price-value.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PriceValueDTO } from '@generated/swagger/cat-search-api';
|
||||
|
||||
export interface PriceValue extends PriceValueDTO {
|
||||
value: number;
|
||||
}
|
||||
6
libs/catalogue/data-access/src/lib/models/price.ts
Normal file
6
libs/catalogue/data-access/src/lib/models/price.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { PriceDTO } from '@generated/swagger/cat-search-api';
|
||||
import { PriceValue } from './price-value';
|
||||
|
||||
export interface Price extends PriceDTO {
|
||||
value: PriceValue;
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SearchByTermParamsSchema = z.object({
|
||||
term: z.string().min(1, 'Search term must not be empty'),
|
||||
export const SearchByTermSchema = z.object({
|
||||
searchTerm: z.string().min(1, 'Search term must not be empty'),
|
||||
skip: z.number().int().min(0).default(0),
|
||||
take: z.number().int().min(1).max(100).default(20),
|
||||
});
|
||||
|
||||
export type SearchByTermParams = z.infer<typeof SearchByTermParamsSchema>;
|
||||
export type SearchByTerm = z.infer<typeof SearchByTermSchema>;
|
||||
|
||||
export type SearchByTermInput = z.input<typeof SearchByTermSchema>;
|
||||
|
||||
@@ -4,9 +4,10 @@ import { firstValueFrom, map, Observable } from 'rxjs';
|
||||
import { takeUntilAborted } from '@isa/common/data-access';
|
||||
import { Item } from '../models';
|
||||
import {
|
||||
SearchByTermParams,
|
||||
SearchByTermParamsSchema,
|
||||
SearchByTermInput,
|
||||
SearchByTermSchema,
|
||||
} from '../schemas/catalouge-search.schemas';
|
||||
import { ListResponseArgs } from '@isa/common/data-access';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CatalougeSearchService {
|
||||
@@ -25,15 +26,15 @@ export class CatalougeSearchService {
|
||||
}
|
||||
|
||||
async searchByTerm(
|
||||
params: SearchByTermParams,
|
||||
params: SearchByTermInput,
|
||||
abortSignal: AbortSignal,
|
||||
): Promise<Item[]> {
|
||||
const { term, skip, take } = SearchByTermParamsSchema.parse(params);
|
||||
): Promise<ListResponseArgs<Item>> {
|
||||
const { searchTerm, skip, take } = SearchByTermSchema.parse(params);
|
||||
|
||||
const req$ = this.#searchService
|
||||
.SearchSearch({
|
||||
filter: {
|
||||
qs: term,
|
||||
qs: searchTerm,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
@@ -46,6 +47,6 @@ export class CatalougeSearchService {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
return res.result as Item[];
|
||||
return res as ListResponseArgs<Item>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './lib/errors';
|
||||
export * from './lib/helpers';
|
||||
export * from './lib/models';
|
||||
export * from './lib/operators';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './data-access.error';
|
||||
export * from './property-is-empty.error';
|
||||
export * from './property-is-null-or-undefined.error';
|
||||
export * from './response-args.error';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ResponseArgs } from '../models';
|
||||
import { DataAccessError } from './data-access.error';
|
||||
|
||||
function invalidPropertyErrorMessage(
|
||||
invalidProperties: Record<string, string> | undefined,
|
||||
): string | undefined {
|
||||
if (!invalidProperties || Object.keys(invalidProperties).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.entries(invalidProperties)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
export class ResponseArgsError<
|
||||
T,
|
||||
> extends DataAccessError<'RESPONSE_ARGS_ERROR'> {
|
||||
constructor(public readonly responseArgs: Omit<ResponseArgs<T>, 'result'>) {
|
||||
super(
|
||||
'RESPONSE_ARGS_ERROR',
|
||||
responseArgs.message ||
|
||||
invalidPropertyErrorMessage(responseArgs.invalidProperties) ||
|
||||
'An error occurred while processing response arguments',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function createEscAbortControllerHelper(): AbortController {
|
||||
const escAbortController = new AbortController();
|
||||
const escKeyHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
escAbortController.abort();
|
||||
document.removeEventListener('keydown', escKeyHandler);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', escKeyHandler);
|
||||
|
||||
return escAbortController;
|
||||
}
|
||||
1
libs/common/data-access/src/lib/helpers/index.ts
Normal file
1
libs/common/data-access/src/lib/helpers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './create-esc-abort-controller.helper';
|
||||
@@ -15,7 +15,7 @@ export interface ResponseArgs<T> {
|
||||
* Map of property names to error messages for validation failures
|
||||
* Keys represent property names, values contain validation error messages
|
||||
*/
|
||||
invalidProperties: Record<string, string>;
|
||||
invalidProperties?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* Optional message providing additional information about the response
|
||||
|
||||
@@ -14,7 +14,7 @@ import { PrintDialogComponent } from '../print-dialog/print-dialog.component';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PrintService {
|
||||
#printService = inject(PrintApiService);
|
||||
#printDailog = injectDialog(PrintDialogComponent, 'Drucken');
|
||||
#printDailog = injectDialog(PrintDialogComponent, { title: 'Drucken' });
|
||||
#platform = inject(Platform);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ export function hash(obj: object | string): string {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const str = JSON.stringify(obj, Object.keys(obj).sort());
|
||||
const str = JSON.stringify(obj);
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = (hash << 5) - hash + str.charCodeAt(i);
|
||||
|
||||
@@ -4,12 +4,15 @@ import { z } from 'zod';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import { hash } from './hash.utils';
|
||||
|
||||
export const USER_SUB = new InjectionToken<() => string>('core.storage.user-sub', {
|
||||
factory: () => {
|
||||
const auth = inject(OAuthService, { optional: true });
|
||||
return () => auth?.getIdentityClaims()?.['sub'] ?? 'anonymous';
|
||||
export const USER_SUB = new InjectionToken<() => string>(
|
||||
'core.storage.user-sub',
|
||||
{
|
||||
factory: () => {
|
||||
const auth = inject(OAuthService, { optional: true });
|
||||
return () => auth?.getIdentityClaims()?.['sub'] ?? 'anonymous';
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
export class Storage {
|
||||
private readonly userSub = inject(USER_SUB);
|
||||
@@ -25,7 +28,10 @@ export class Storage {
|
||||
return this.storageProvider.set(this.getKey(token), value);
|
||||
}
|
||||
|
||||
async get<T>(token: string | object, schema?: z.ZodType<T>): Promise<T | unknown> {
|
||||
async get<T>(
|
||||
token: string | object,
|
||||
schema?: z.ZodType<T>,
|
||||
): Promise<T | unknown> {
|
||||
const data = await this.storageProvider.get(this.getKey(token));
|
||||
if (schema) {
|
||||
return schema.parse(data);
|
||||
|
||||
@@ -11,10 +11,9 @@ export class UncompletedTasksGuard
|
||||
implements CanDeactivate<ReturnReviewComponent>
|
||||
{
|
||||
#returnTaskListStore = inject(ReturnTaskListStore);
|
||||
#confirmationDialog = injectDialog(
|
||||
ConfirmationDialogComponent,
|
||||
'Aufgaben erledigen',
|
||||
);
|
||||
#confirmationDialog = injectDialog(ConfirmationDialogComponent, {
|
||||
title: 'Aufgaben erledigen',
|
||||
});
|
||||
|
||||
processId = injectActivatedTabId();
|
||||
|
||||
|
||||
@@ -2,5 +2,4 @@ import { PriceValueDTO } from '@generated/swagger/inventory-api';
|
||||
|
||||
export interface PriceValue extends PriceValueDTO {
|
||||
value: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const FetchReturnReasonSchema = z.object({
|
||||
stockId: z.number(),
|
||||
});
|
||||
|
||||
export type FetchReturnReason = z.infer<typeof FetchReturnReasonSchema>;
|
||||
|
||||
export type FetchReturnReasonParams = z.input<typeof FetchReturnReasonSchema>;
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './fetch-product-groups.schema';
|
||||
export * from './fetch-query-settings.schema';
|
||||
export * from './fetch-return-reason.schema';
|
||||
export * from './fetch-stock-in-stock.schema';
|
||||
export * from './fetch-suppliers.schema';
|
||||
export * from './fetch-query-settings.schema';
|
||||
export * from './query-token.schema';
|
||||
export * from './fetch-product-groups.schema';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './remission-stock.service';
|
||||
export * from './remission-product-group.service';
|
||||
export * from './remission-reason.service';
|
||||
export * from './remission-search.service';
|
||||
export * from './remission-stock.service';
|
||||
export * from './remission-supplier.service';
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ReturnService } from '@generated/swagger/inventory-api';
|
||||
import { FetchReturnReasonParams, FetchReturnReasonSchema } from '../schemas';
|
||||
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { KeyValueStringAndString } from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RemissionReasonService {
|
||||
#returnService = inject(ReturnService);
|
||||
|
||||
async fetchReturnReasons(
|
||||
params: FetchReturnReasonParams,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<KeyValueStringAndString[]> {
|
||||
const { stockId } = FetchReturnReasonSchema.parse(params);
|
||||
|
||||
let req$ = this.#returnService.ReturnGetReturnReasons({ stockId });
|
||||
|
||||
if (abortSignal) {
|
||||
req$ = req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error) {
|
||||
throw new ResponseArgsError(res);
|
||||
}
|
||||
|
||||
return res.result as KeyValueStringAndString[];
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@ import { Stock, StockInfo } from '../models';
|
||||
import { FetchStockInStock, FetchStockInStockSchema } from '../schemas';
|
||||
import { injectStorage, MemoryStorageProvider } from '@isa/core/storage';
|
||||
import { ASSIGNED_STOCK_STORAGE_KEY } from '../constants';
|
||||
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RemissionStockService {
|
||||
#stockService = inject(StockService);
|
||||
#memoryStorage = injectStorage(MemoryStorageProvider);
|
||||
|
||||
async fetchAssignedStock(): Promise<Stock> {
|
||||
async fetchAssignedStock(abortSignal?: AbortSignal): Promise<Stock> {
|
||||
const cached = await this.#memoryStorage.get(ASSIGNED_STOCK_STORAGE_KEY);
|
||||
|
||||
if (cached) {
|
||||
@@ -20,10 +21,14 @@ export class RemissionStockService {
|
||||
|
||||
const req$ = this.#stockService.StockCurrentStock();
|
||||
|
||||
if (abortSignal) {
|
||||
req$.pipe(takeUntilAborted(abortSignal));
|
||||
}
|
||||
|
||||
const res = await firstValueFrom(req$);
|
||||
|
||||
if (res.error || !res.result) {
|
||||
throw new Error(res.message || 'Failed to fetch CurrentStock data');
|
||||
throw new ResponseArgsError(res);
|
||||
}
|
||||
|
||||
this.#memoryStorage.set(ASSIGNED_STOCK_STORAGE_KEY, res.result);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { injectDialog } from '@isa/ui/dialog';
|
||||
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-start-card',
|
||||
@@ -9,7 +11,9 @@ import { ButtonComponent } from '@isa/ui/buttons';
|
||||
imports: [ButtonComponent],
|
||||
})
|
||||
export class RemissionStartCardComponent {
|
||||
searchItemToRemitDialog = injectDialog(SearchItemToRemitDialogComponent);
|
||||
|
||||
startRemission() {
|
||||
console.log('Start');
|
||||
this.searchItemToRemitDialog({ data: { searchTerm: 'Pokemon' } });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# remission-shared-search-item-to-remit-dialog
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test remission-shared-search-item-to-remit-dialog` to execute the unit tests.
|
||||
@@ -0,0 +1,34 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'remi',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'remi',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "remission-shared-search-item-to-remit-dialog",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/remission/shared/search-item-to-remit-dialog/src",
|
||||
"prefix": "remi",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
"options": {
|
||||
"reportsDirectory": "../../../../coverage/libs/remission/shared/search-item-to-remit-dialog"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './lib/search-item-to-remit-dialog.component';
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { ListResponseArgs } from '@isa/common/data-access';
|
||||
|
||||
export const DEFAULT_LIST_RESPONSE_ARGS_OF_ITEM: ListResponseArgs<Item> = {
|
||||
error: false,
|
||||
hits: 0,
|
||||
result: [],
|
||||
skip: 0,
|
||||
take: 0,
|
||||
invalidProperties: {},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="isa-text-body-1-bold">Menge {{ position() }}</div>
|
||||
<div class="-mr-4">
|
||||
<ui-dropdown [(ngModel)]="quantity" class="border-none">
|
||||
<ui-dropdown-option [value]="1">1</ui-dropdown-option>
|
||||
<ui-dropdown-option [value]="2">2</ui-dropdown-option>
|
||||
<ui-dropdown-option [value]="3">3</ui-dropdown-option>
|
||||
<ui-dropdown-option [value]="4">4</ui-dropdown-option>
|
||||
<ui-dropdown-option [value]="5">5</ui-dropdown-option>
|
||||
</ui-dropdown>
|
||||
<ui-dropdown [(ngModel)]="reason" class="border-none">
|
||||
@if (reasonResource.value(); as reasons) {
|
||||
@for (reson of reasons; track reson.key) {
|
||||
<ui-dropdown-option [value]="reson.value">
|
||||
{{ reson.value }}
|
||||
</ui-dropdown-option>
|
||||
}
|
||||
}
|
||||
|
||||
<ui-dropdown-option [value]="1">1</ui-dropdown-option>
|
||||
</ui-dropdown>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-cols-[1fr,auto] items-center justify-between px-4 py-[.44rem] border border-isa-neutral-400 rounded-lg;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
linkedSignal,
|
||||
model,
|
||||
resource,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import {
|
||||
RemissionReasonService,
|
||||
RemissionStockService,
|
||||
} from '@isa/remission/data-access';
|
||||
import {
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-quantity-and-reason-item',
|
||||
templateUrl: './quantity-and-reason-item.component.html',
|
||||
styleUrls: ['./quantity-and-reason-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [DropdownButtonComponent, DropdownOptionComponent, FormsModule],
|
||||
})
|
||||
export class QuantityAndReasonItemComponent {
|
||||
#reasonService = inject(RemissionReasonService);
|
||||
#stockService = inject(RemissionStockService);
|
||||
|
||||
position = input.required<number>();
|
||||
|
||||
quantity = linkedSignal(() => 1);
|
||||
|
||||
reason = linkedSignal(() => {
|
||||
const returnReasons = this.reasonResource.value();
|
||||
if (!returnReasons || returnReasons.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return returnReasons[0].value;
|
||||
});
|
||||
|
||||
assignedStockResource = resource({
|
||||
loader: ({ abortSignal }) =>
|
||||
this.#stockService.fetchAssignedStock(abortSignal),
|
||||
});
|
||||
|
||||
reasonResource = resource({
|
||||
params: this.assignedStockResource.value,
|
||||
loader: async ({ abortSignal, params }) => {
|
||||
if (!params || !params.id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.#reasonService.fetchReturnReasons(
|
||||
{
|
||||
stockId: params.id,
|
||||
},
|
||||
abortSignal,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@if (item()) {
|
||||
<remi-select-remi-quantity-and-reason></remi-select-remi-quantity-and-reason>
|
||||
} @else {
|
||||
<button
|
||||
class="absolute top-1 right-[1.33rem]"
|
||||
type="button"
|
||||
uiTextButton
|
||||
siez="small"
|
||||
color="subtle"
|
||||
(click)="close(undefined)"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<remi-search-item-to-remit-list></remi-search-item-to-remit-list>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply block h-full;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
effect,
|
||||
isSignal,
|
||||
linkedSignal,
|
||||
signal,
|
||||
Signal,
|
||||
} from '@angular/core';
|
||||
import { DialogContentDirective } from '@isa/ui/dialog';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionSearch } from '@isa/icons';
|
||||
import { SearchItemToRemitListComponent } from './search-item-to-remit-list.component';
|
||||
import { SelectRemiQuantityAndReasonComponent } from './select-remi-quantity-and-reason.component';
|
||||
|
||||
export type SearchItemToRemitDialogData = {
|
||||
searchTerm: string | Signal<string>;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit-dialog',
|
||||
templateUrl: './search-item-to-remit-dialog.component.html',
|
||||
styleUrls: ['./search-item-to-remit-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionSearch })],
|
||||
})
|
||||
export class SearchItemToRemitDialogComponent extends DialogContentDirective<
|
||||
SearchItemToRemitDialogData,
|
||||
Item | undefined
|
||||
> {
|
||||
searchTerm = linkedSignal(() =>
|
||||
isSignal(this.data.searchTerm)
|
||||
? this.data.searchTerm()
|
||||
: this.data.searchTerm,
|
||||
);
|
||||
|
||||
item = signal<Item | undefined>(undefined);
|
||||
|
||||
itemEffect = effect(() => {
|
||||
const item = this.item();
|
||||
this.dialogRef.updateSize(item ? '36rem' : 'auto');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<ui-search-bar appearance="main" class="bg-isa-neutral-100 w-full">
|
||||
<input
|
||||
[(ngModel)]="host.searchTerm"
|
||||
type="text"
|
||||
placeholder="Rechnungsnummer, E-Mail, Kundenkarte, Name..."
|
||||
(keydown.enter)="triggerSearch()"
|
||||
/>
|
||||
<ui-search-bar-clear></ui-search-bar-clear>
|
||||
<ui-icon-button
|
||||
type="submit"
|
||||
name="isaActionSearch"
|
||||
color="brand"
|
||||
(click)="triggerSearch()"
|
||||
[pending]="searchResource.isLoading()"
|
||||
></ui-icon-button>
|
||||
</ui-search-bar>
|
||||
<p
|
||||
class="text-isa-neutral-600 isa-text-body-1-regular pb-4 border-b border-b-isa-neutral-300"
|
||||
>
|
||||
Sie können Artikel die nicht auf der Remi Liste stehen direkt zum
|
||||
Warenbegleitschein hinzufügen.
|
||||
</p>
|
||||
<div class="overflow-y-auto">
|
||||
@if (searchResource.value()?.result; as items) {
|
||||
@for (item of items; track item.id) {
|
||||
@defer {
|
||||
<remi-search-item-to-remit [item]="item"></remi-search-item-to-remit>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-rows-[auto,auto,1fr] gap-6 h-full;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
OnInit,
|
||||
resource,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { isaActionSearch } from '@isa/icons';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import {
|
||||
UiSearchBarClearComponent,
|
||||
UiSearchBarComponent,
|
||||
} from '@isa/ui/search-bar';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { SearchItemToRemitComponent } from './search-item-to-remit.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { DEFAULT_LIST_RESPONSE_ARGS_OF_ITEM } from './constants';
|
||||
import {
|
||||
CatalougeSearchService,
|
||||
SearchByTermInput,
|
||||
Item,
|
||||
} from '@isa/catalogue/data-access';
|
||||
import {
|
||||
ListResponseArgs,
|
||||
createEscAbortControllerHelper,
|
||||
} from '@isa/common/data-access';
|
||||
import { injectStorage, MemoryStorageProvider } from '@isa/core/storage';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit-list',
|
||||
templateUrl: './search-item-to-remit-list.component.html',
|
||||
styleUrls: ['./search-item-to-remit-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
UiSearchBarClearComponent,
|
||||
UiSearchBarComponent,
|
||||
IconButtonComponent,
|
||||
FormsModule,
|
||||
SearchItemToRemitComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionSearch })],
|
||||
})
|
||||
export class SearchItemToRemitListComponent implements OnInit {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
#catalougeSearchService = inject(CatalougeSearchService);
|
||||
#memoryStorage = injectStorage(MemoryStorageProvider);
|
||||
|
||||
searchParams = signal<SearchByTermInput | undefined>(undefined);
|
||||
|
||||
#setSearchResourceCache(
|
||||
params: SearchByTermInput,
|
||||
data: ListResponseArgs<Item>,
|
||||
): Promise<void> {
|
||||
return this.#memoryStorage.set(
|
||||
{
|
||||
component: 'SearchItemToRemitDialogComponent',
|
||||
params,
|
||||
},
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
#getSearchResourceCache(
|
||||
params: SearchByTermInput,
|
||||
): Promise<ListResponseArgs<Item> | undefined> {
|
||||
return this.#memoryStorage.get({
|
||||
component: 'SearchItemToRemitDialogComponent',
|
||||
params,
|
||||
}) as Promise<ListResponseArgs<Item> | undefined>;
|
||||
}
|
||||
|
||||
triggerSearch(): void {
|
||||
this.searchParams.set({
|
||||
searchTerm: this.host.searchTerm(),
|
||||
});
|
||||
}
|
||||
|
||||
searchResource = resource({
|
||||
params: this.searchParams,
|
||||
loader: async ({ abortSignal, params }) => {
|
||||
if (params === undefined) {
|
||||
return DEFAULT_LIST_RESPONSE_ARGS_OF_ITEM;
|
||||
}
|
||||
let result = await this.#getSearchResourceCache(params);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const escAbortController = createEscAbortControllerHelper();
|
||||
|
||||
try {
|
||||
result = await this.#catalougeSearchService.searchByTerm(
|
||||
params,
|
||||
AbortSignal.any([abortSignal, escAbortController.signal]),
|
||||
);
|
||||
|
||||
this.#setSearchResourceCache(params, result);
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Unknown error';
|
||||
result = {
|
||||
...DEFAULT_LIST_RESPONSE_ARGS_OF_ITEM,
|
||||
error: true,
|
||||
message,
|
||||
};
|
||||
} finally {
|
||||
escAbortController.abort();
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
ngOnInit(): void {
|
||||
this.host.dialog.title.set('');
|
||||
if (this.host.searchTerm().length) {
|
||||
this.triggerSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<remi-product-info
|
||||
[item]="{
|
||||
product: item().product,
|
||||
retailPrice: item().catalogAvailability.price,
|
||||
}"
|
||||
></remi-product-info>
|
||||
<div class="text-right">
|
||||
<button
|
||||
class="-mr-5"
|
||||
type="button"
|
||||
uiTextButton
|
||||
color="strong"
|
||||
(click)="host.item.set(item())"
|
||||
>
|
||||
Remimenge auswählen
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row border-b border-b-isa-neutral-300 pt-6 pb-4 gap-4;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { ProductInfoComponent } from '@isa/remission/shared/product';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit',
|
||||
templateUrl: './search-item-to-remit.component.html',
|
||||
styleUrls: ['./search-item-to-remit.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ProductInfoComponent, TextButtonComponent],
|
||||
})
|
||||
export class SearchItemToRemitComponent {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
|
||||
item = input.required<Item>();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<p class="text-isa-neutral-600 isa-text-body-1-regular">
|
||||
Wie viele Exemplare können remittiert werden?
|
||||
</p>
|
||||
<div>
|
||||
<remi-quantity-and-reason-item [position]="1"></remi-quantity-and-reason-item>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 -ml-5"
|
||||
uiTextButton
|
||||
color="strong"
|
||||
>
|
||||
<ng-icon name="isaActionPlus" size="1.5rem"></ng-icon>
|
||||
<div>Menge hinzufügen</div>
|
||||
</button>
|
||||
</div>
|
||||
<div></div>
|
||||
<div class="grid grid-cols-2 items-center gap-2">
|
||||
<button type="button" color="secondary" size="large" uiButton>Zurück</button>
|
||||
<button type="button" color="primary" size="large" uiButton>Speichern</button>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-6;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
import { QuantityAndReasonItemComponent } from './quantity-and-reason-item.component';
|
||||
import { ButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionPlus } from '@isa/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-select-remi-quantity-and-reason',
|
||||
templateUrl: './select-remi-quantity-and-reason.component.html',
|
||||
styleUrls: ['./select-remi-quantity-and-reason.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
QuantityAndReasonItemComponent,
|
||||
TextButtonComponent,
|
||||
NgIcon,
|
||||
ButtonComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionPlus })],
|
||||
})
|
||||
export class SelectRemiQuantityAndReasonComponent implements OnInit {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
ngOnInit(): void {
|
||||
this.host.dialog.title.set('Dieser Artikel steht nicht auf der Remi Liste');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting,
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"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",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/test-setup.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"files": ["src/test-setup.ts"]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir:
|
||||
'../../../../node_modules/.vite/libs/remission/shared/search-item-to-remit-dialog',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory:
|
||||
'../../../../coverage/libs/remission/shared/search-item-to-remit-dialog',
|
||||
provider: 'v8',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
# remission-shared-select-remission-quantity-and-reason-dialog
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test remission-shared-select-remission-quantity-and-reason-dialog` to execute the unit tests.
|
||||
@@ -0,0 +1,34 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'remi',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'remi',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "remission-shared-select-remission-quantity-and-reason-dialog",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/remission/shared/select-remission-quantity-and-reason-dialog/src",
|
||||
"prefix": "remi",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
"options": {
|
||||
"reportsDirectory": "../../../../coverage/libs/remission/shared/select-remission-quantity-and-reason-dialog"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './lib/remission-shared-select-remission-quantity-and-reason-dialog/remission-shared-select-remission-quantity-and-reason-dialog.component';
|
||||
@@ -0,0 +1 @@
|
||||
<p>remission-shared-select-remission-quantity-and-reason-dialog works!</p>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RemissionSharedSelectRemissionQuantityAndReasonDialogComponent } from './remission-shared-select-remission-quantity-and-reason-dialog.component';
|
||||
|
||||
describe('RemissionSharedSelectRemissionQuantityAndReasonDialogComponent', () => {
|
||||
let component: RemissionSharedSelectRemissionQuantityAndReasonDialogComponent;
|
||||
let fixture: ComponentFixture<RemissionSharedSelectRemissionQuantityAndReasonDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RemissionSharedSelectRemissionQuantityAndReasonDialogComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(
|
||||
RemissionSharedSelectRemissionQuantityAndReasonDialogComponent,
|
||||
);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-remission-shared-select-remission-quantity-and-reason-dialog',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl:
|
||||
'./remission-shared-select-remission-quantity-and-reason-dialog.component.html',
|
||||
styleUrl:
|
||||
'./remission-shared-select-remission-quantity-and-reason-dialog.component.css',
|
||||
})
|
||||
export class RemissionSharedSelectRemissionQuantityAndReasonDialogComponent {}
|
||||
@@ -0,0 +1,12 @@
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting,
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"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",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/test-setup.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"files": ["src/test-setup.ts"]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir:
|
||||
'../../../../node_modules/.vite/libs/remission/shared/select-remission-quantity-and-reason-dialog',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory:
|
||||
'../../../../coverage/libs/remission/shared/select-remission-quantity-and-reason-dialog',
|
||||
provider: 'v8',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -37,7 +37,6 @@ import { isaLoading } from '@isa/icons';
|
||||
'[tabindex]': 'tabIndex()',
|
||||
'[attr.aria-disabled]': 'disabled()',
|
||||
'[attr.aria-label]': 'name()',
|
||||
'[attr.aria-hidden]': 'pending()',
|
||||
'[attr.aria-busy]': 'pending()',
|
||||
'[attr.role]': 'pending() ? "progressbar" : "button"',
|
||||
},
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
.ui-dialog {
|
||||
@apply bg-isa-white p-8 flex gap-8 items-start rounded-[2rem] flex-col text-isa-neutral-900;
|
||||
@apply bg-isa-white p-8 grid gap-8 items-start rounded-[2rem] grid-flow-row text-isa-neutral-900 relative;
|
||||
@apply max-h-[90vh] max-w-[90vw] overflow-hidden;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
.ui-dialog-title {
|
||||
@apply isa-text-subtitle-1-bold;
|
||||
@apply flex-shrink-0;
|
||||
}
|
||||
|
||||
.ui-dialog-content {
|
||||
@apply flex flex-col gap-8;
|
||||
@apply overflow-y-auto overflow-x-hidden;
|
||||
@apply min-h-0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
|
||||
import { Directive, inject } from '@angular/core';
|
||||
import { DialogComponent } from './dialog.component';
|
||||
|
||||
/**
|
||||
* Base directive for dialog content components
|
||||
@@ -14,6 +15,8 @@ import { Directive, inject } from '@angular/core';
|
||||
},
|
||||
})
|
||||
export abstract class DialogContentDirective<D, R> {
|
||||
readonly dialog = inject(DialogComponent<D, R, DialogContentDirective<D, R>>);
|
||||
|
||||
/** Reference to the dialog instance */
|
||||
readonly dialogRef = inject(DialogRef<R, DialogContentDirective<D, R>>);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<h2 class="ui-dialog-title" data-what="title">
|
||||
{{ title }}
|
||||
{{ title() }}
|
||||
</h2>
|
||||
|
||||
<ng-container *ngComponentOutlet="component"> </ng-container>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { DialogContentDirective } from './dialog-content.directive';
|
||||
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
|
||||
import { ComponentType } from '@angular/cdk/portal';
|
||||
@@ -23,7 +28,7 @@ import { NgComponentOutlet } from '@angular/common';
|
||||
})
|
||||
export class DialogComponent<D, R, C extends DialogContentDirective<D, R>> {
|
||||
/** The title to display at the top of the dialog */
|
||||
title = inject(DIALOG_TITLE);
|
||||
title = signal(inject(DIALOG_TITLE));
|
||||
|
||||
/** The component type to instantiate as the dialog content */
|
||||
readonly component = inject(DIALOG_CONTENT) as ComponentType<C>;
|
||||
|
||||
@@ -7,13 +7,40 @@ import { DialogComponent } from './dialog.component';
|
||||
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
|
||||
import { MessageDialogComponent } from './message-dialog/message-dialog.component';
|
||||
|
||||
export interface InjectDialogOptions {
|
||||
/** Optional title override for the dialog */
|
||||
title?: string;
|
||||
|
||||
/** Optional width for the dialog */
|
||||
width?: string;
|
||||
|
||||
/** Optional height for the dialog */
|
||||
height?: string;
|
||||
|
||||
/** Optional minWidth for the dialog */
|
||||
minWidth?: string;
|
||||
|
||||
/** Optional maxWidth for the dialog */
|
||||
maxWidth?: string;
|
||||
|
||||
/** Optional minHeight for the dialog */
|
||||
minHeight?: string;
|
||||
|
||||
/** Optional maxHeight for the dialog */
|
||||
maxHeight?: string;
|
||||
|
||||
/** Optional hasBackdrop for the dialog */
|
||||
hasBackdrop?: boolean;
|
||||
|
||||
/** Optional disableClose for the dialog */
|
||||
disableClose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
export interface OpenDialogOptions<D> extends InjectDialogOptions {
|
||||
/** Data to pass to the dialog component */
|
||||
data: D;
|
||||
}
|
||||
@@ -29,7 +56,7 @@ export interface OpenDialogOptions<D> {
|
||||
*/
|
||||
export function injectDialog<C extends DialogContentDirective<any, any>>(
|
||||
componentType: ComponentType<C>,
|
||||
title?: string,
|
||||
injectOptions?: InjectDialogOptions,
|
||||
) {
|
||||
type D = C extends DialogContentDirective<infer D, any> ? D : never;
|
||||
type R = C extends DialogContentDirective<any, infer R> ? R : never;
|
||||
@@ -37,7 +64,7 @@ export function injectDialog<C extends DialogContentDirective<any, any>>(
|
||||
const cdkDialog = inject(Dialog);
|
||||
const injector = inject(Injector);
|
||||
|
||||
return (options?: OpenDialogOptions<D>) => {
|
||||
return (openOptions?: OpenDialogOptions<D>) => {
|
||||
const dialogInjector = Injector.create({
|
||||
parent: injector,
|
||||
providers: [
|
||||
@@ -47,7 +74,7 @@ export function injectDialog<C extends DialogContentDirective<any, any>>(
|
||||
},
|
||||
{
|
||||
provide: DIALOG_TITLE,
|
||||
useValue: options?.title ?? title,
|
||||
useValue: openOptions?.title ?? injectOptions?.title,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -55,11 +82,18 @@ export function injectDialog<C extends DialogContentDirective<any, any>>(
|
||||
const dialogRef = cdkDialog.open<R, D, DialogComponent<D, R, C>>(
|
||||
DialogComponent<D, R, C>,
|
||||
{
|
||||
data: options?.data,
|
||||
data: openOptions?.data,
|
||||
injector: dialogInjector,
|
||||
width: '30rem',
|
||||
hasBackdrop: true,
|
||||
disableClose: true,
|
||||
width: openOptions?.width ?? injectOptions?.width,
|
||||
height: openOptions?.height ?? injectOptions?.height,
|
||||
minWidth: openOptions?.minWidth ?? injectOptions?.minWidth ?? '30rem',
|
||||
maxWidth: openOptions?.maxWidth ?? injectOptions?.maxWidth,
|
||||
minHeight: openOptions?.minHeight ?? injectOptions?.minHeight,
|
||||
maxHeight: openOptions?.maxHeight ?? injectOptions?.maxHeight,
|
||||
hasBackdrop:
|
||||
openOptions?.hasBackdrop ?? injectOptions?.hasBackdrop ?? true,
|
||||
disableClose:
|
||||
openOptions?.disableClose ?? injectOptions?.disableClose ?? true,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ export type SearchbarAppearance =
|
||||
@Component({
|
||||
selector: 'ui-search-bar',
|
||||
templateUrl: './search-bar.component.html',
|
||||
styleUrl: './search-bar.component.scss',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: {
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
&:has(input[type='text']:placeholder-shown) {
|
||||
&:has(input[type="text"]:placeholder-shown) {
|
||||
.ui-search-bar__action__close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
input[type="text"] {
|
||||
appearance: none;
|
||||
flex-grow: 1;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
@apply text-isa-neutral-900;
|
||||
background-color: inherit;
|
||||
|
||||
&::placeholder {
|
||||
@apply text-isa-neutral-500;
|
||||
@@ -57,7 +57,7 @@
|
||||
@apply pr-4;
|
||||
}
|
||||
|
||||
&:has(input[type='text']:placeholder-shown) {
|
||||
&:has(input[type="text"]:placeholder-shown) {
|
||||
@apply pl-0;
|
||||
|
||||
button[prefix][uiIconButton] {
|
||||
@@ -65,10 +65,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input[type='text']) {
|
||||
&:has(input[type="text"]) {
|
||||
@apply pl-4;
|
||||
|
||||
input[type='text'] {
|
||||
input[type="text"] {
|
||||
@apply pr-4 whitespace-nowrap overflow-hidden overflow-ellipsis;
|
||||
}
|
||||
|
||||
4
nx.json
4
nx.json
@@ -52,6 +52,10 @@
|
||||
"cache": true,
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["production", "^production"]
|
||||
},
|
||||
"@nx/vite:test": {
|
||||
"cache": true,
|
||||
"inputs": ["default", "^production"]
|
||||
}
|
||||
},
|
||||
"defaultBase": "develop",
|
||||
|
||||
3074
package-lock.json
generated
3074
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -58,6 +58,8 @@
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@analogjs/vite-plugin-angular": "~1.9.1",
|
||||
"@analogjs/vitest-angular": "~1.9.1",
|
||||
"@angular-devkit/build-angular": "20.0.3",
|
||||
"@angular-devkit/core": "20.0.2",
|
||||
"@angular-devkit/schematics": "20.0.2",
|
||||
@@ -73,6 +75,7 @@
|
||||
"@nx/jest": "21.2.0",
|
||||
"@nx/js": "21.2.0",
|
||||
"@nx/storybook": "21.2.0",
|
||||
"@nx/vite": "20.1.4",
|
||||
"@nx/web": "21.2.0",
|
||||
"@nx/workspace": "21.2.0",
|
||||
"@schematics/angular": "20.0.2",
|
||||
@@ -88,6 +91,8 @@
|
||||
"@types/node": "18.16.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/utils": "^8.19.0",
|
||||
"@vitest/coverage-v8": "^1.0.4",
|
||||
"@vitest/ui": "^1.3.1",
|
||||
"angular-eslint": "^19.2.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.8.0",
|
||||
@@ -99,6 +104,7 @@
|
||||
"jest-environment-node": "^29.7.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"jest-preset-angular": "14.6.0",
|
||||
"jsdom": "~22.1.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"ng-mocks": "14.13.5",
|
||||
"ng-packagr": "20.0.1",
|
||||
@@ -113,7 +119,9 @@
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "^8.19.0"
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-x64": "^0.25.5"
|
||||
|
||||
@@ -75,11 +75,17 @@
|
||||
],
|
||||
"@isa/remission/helpers": ["libs/remission/helpers/src/index.ts"],
|
||||
"@isa/remission/shared": ["libs/remission/shared/src/index.ts"],
|
||||
"@isa/remission/shared/product": [
|
||||
"libs/remission/shared/product/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/product-details": [
|
||||
"libs/remission/shared/product-details/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/product": [
|
||||
"libs/remission/shared/product/src/index.ts"
|
||||
"@isa/remission/shared/search-item-to-remit-dialog": [
|
||||
"libs/remission/shared/search-item-to-remit-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/remission/shared/select-remission-quantity-and-reason-dialog": [
|
||||
"libs/remission/shared/select-remission-quantity-and-reason-dialog/src/index.ts"
|
||||
],
|
||||
"@isa/shared/filter": ["libs/shared/filter/src/index.ts"],
|
||||
"@isa/shared/product-foramt": ["libs/shared/product-format/src/index.ts"],
|
||||
|
||||
1
vitest.workspace.ts
Normal file
1
vitest.workspace.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default ['**/*/vite.config.{ts,mts}', '**/*/vitest.config.{ts,mts}'];
|
||||
Reference in New Issue
Block a user