mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1899: feat(empty-state): enhance empty state component with new appearance options...
feat(empty-state): enhance empty state component with new appearance options and integration in remission details Related work items: #5232
This commit is contained in:
committed by
Nino Righi
parent
239ab52890
commit
b39abe630d
@@ -1,10 +1,16 @@
|
||||
import { argsToTemplate, type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';
|
||||
import { EmptyStateComponent } from '@isa/ui/empty-state';
|
||||
import {
|
||||
argsToTemplate,
|
||||
type Meta,
|
||||
type StoryObj,
|
||||
moduleMetadata,
|
||||
} from '@storybook/angular';
|
||||
import { EmptyStateAppearance, EmptyStateComponent } from '@isa/ui/empty-state';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
|
||||
type EmptyStateComponentInputs = {
|
||||
title: string;
|
||||
description: string;
|
||||
appearance: EmptyStateAppearance;
|
||||
};
|
||||
|
||||
const meta: Meta<EmptyStateComponentInputs> = {
|
||||
@@ -22,6 +28,10 @@ const meta: Meta<EmptyStateComponentInputs> = {
|
||||
description: {
|
||||
control: 'text',
|
||||
},
|
||||
appearance: {
|
||||
control: 'select',
|
||||
options: Object.values(EmptyStateAppearance),
|
||||
},
|
||||
},
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
@@ -40,5 +50,6 @@ export const Default: Story = {
|
||||
args: {
|
||||
title: 'Keine Suchergebnisse',
|
||||
description: 'Suchen Sie nach einer Rechnungsnummer oder Kundennamen.',
|
||||
appearance: EmptyStateAppearance.NoResults,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -130,7 +130,10 @@ describe('RemissionReturnReceiptService', () => {
|
||||
of({ result: mockReturns, error: null }),
|
||||
);
|
||||
|
||||
const result = await service.fetchRemissionReturnReceipts();
|
||||
const result = await service.fetchRemissionReturnReceipts({
|
||||
returncompleted: true,
|
||||
start: new Date(),
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockReturns);
|
||||
expect(mockRemissionStockService.fetchAssignedStock).toHaveBeenCalledWith(
|
||||
@@ -139,7 +142,7 @@ describe('RemissionReturnReceiptService', () => {
|
||||
expect(mockReturnService.ReturnQueryReturns).toHaveBeenCalledWith({
|
||||
stockId: 123,
|
||||
queryToken: {
|
||||
input: { returncompleted: 'true' },
|
||||
filter: { returncompleted: 'true' },
|
||||
start: expect.any(String),
|
||||
eagerLoading: 3,
|
||||
},
|
||||
@@ -151,7 +154,7 @@ describe('RemissionReturnReceiptService', () => {
|
||||
of({ result: mockReturns, error: null }),
|
||||
);
|
||||
|
||||
await service.fetchRemissionReturnReceipts();
|
||||
await service.fetchRemissionReturnReceipts({ returncompleted: true });
|
||||
|
||||
const callArgs = mockReturnService.ReturnQueryReturns.mock.calls[0][0];
|
||||
const startDate = new Date(callArgs.queryToken.start);
|
||||
@@ -169,7 +172,10 @@ describe('RemissionReturnReceiptService', () => {
|
||||
of({ result: mockReturns, error: null }),
|
||||
);
|
||||
|
||||
await service.fetchRemissionReturnReceipts(abortController.signal);
|
||||
await service.fetchRemissionReturnReceipts(
|
||||
{ returncompleted: true },
|
||||
abortController.signal,
|
||||
);
|
||||
|
||||
expect(mockRemissionStockService.fetchAssignedStock).toHaveBeenCalledWith(
|
||||
abortController.signal,
|
||||
@@ -181,9 +187,9 @@ describe('RemissionReturnReceiptService', () => {
|
||||
const errorResponse = { error: 'API Error', result: null };
|
||||
mockReturnService.ReturnQueryReturns.mockReturnValue(of(errorResponse));
|
||||
|
||||
await expect(service.fetchRemissionReturnReceipts()).rejects.toThrow(
|
||||
ResponseArgsError,
|
||||
);
|
||||
await expect(
|
||||
service.fetchRemissionReturnReceipts({ returncompleted: true }),
|
||||
).rejects.toThrow(ResponseArgsError);
|
||||
});
|
||||
|
||||
it('should return empty array when result is null', async () => {
|
||||
@@ -191,7 +197,9 @@ describe('RemissionReturnReceiptService', () => {
|
||||
of({ result: null, error: null }),
|
||||
);
|
||||
|
||||
const result = await service.fetchRemissionReturnReceipts();
|
||||
const result = await service.fetchRemissionReturnReceipts({
|
||||
returncompleted: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
@@ -199,7 +207,9 @@ describe('RemissionReturnReceiptService', () => {
|
||||
it('should return empty array when result is undefined', async () => {
|
||||
mockReturnService.ReturnQueryReturns.mockReturnValue(of({ error: null }));
|
||||
|
||||
const result = await service.fetchRemissionReturnReceipts();
|
||||
const result = await service.fetchRemissionReturnReceipts({
|
||||
returncompleted: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
@@ -209,158 +219,13 @@ describe('RemissionReturnReceiptService', () => {
|
||||
new Error('Stock error'),
|
||||
);
|
||||
|
||||
await expect(service.fetchRemissionReturnReceipts()).rejects.toThrow(
|
||||
'Stock error',
|
||||
);
|
||||
await expect(
|
||||
service.fetchRemissionReturnReceipts({ returncompleted: true }),
|
||||
).rejects.toThrow('Stock error');
|
||||
expect(mockReturnService.ReturnQueryReturns).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// describe('fetchIncompletedRemissionReturnReceipts', () => {
|
||||
// beforeEach(() => {
|
||||
// mockRemissionStockService.fetchAssignedStock.mockResolvedValue(mockStock);
|
||||
// });
|
||||
|
||||
// it('should fetch incompleted return receipts successfully', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: mockReturns, error: null }),
|
||||
// );
|
||||
|
||||
// const result = await service.fetchIncompletedRemissionReturnReceipts();
|
||||
|
||||
// expect(result).toEqual(mockReturns);
|
||||
// expect(mockRemissionStockService.fetchAssignedStock).toHaveBeenCalledWith(
|
||||
// undefined,
|
||||
// );
|
||||
// expect(mockReturnService.ReturnQueryReturns).toHaveBeenCalledWith({
|
||||
// stockId: 123,
|
||||
// queryToken: {
|
||||
// input: { returncompleted: 'false' },
|
||||
// start: expect.any(String),
|
||||
// eagerLoading: 3,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('should use correct date range (7 days ago)', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: mockReturns, error: null }),
|
||||
// );
|
||||
|
||||
// await service.fetchIncompletedRemissionReturnReceipts();
|
||||
|
||||
// const callArgs = mockReturnService.ReturnQueryReturns.mock.calls[0][0];
|
||||
// const startDate = new Date(callArgs.queryToken.start);
|
||||
// const expectedDate = subDays(new Date(), 7);
|
||||
|
||||
// // Check that dates are within 1 second of each other (to handle timing differences)
|
||||
// expect(
|
||||
// Math.abs(startDate.getTime() - expectedDate.getTime()),
|
||||
// ).toBeLessThan(1000);
|
||||
// });
|
||||
|
||||
// it('should handle abort signal', async () => {
|
||||
// const abortController = new AbortController();
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: mockReturns, error: null }),
|
||||
// );
|
||||
|
||||
// await service.fetchIncompletedRemissionReturnReceipts(
|
||||
// abortController.signal,
|
||||
// );
|
||||
|
||||
// expect(mockRemissionStockService.fetchAssignedStock).toHaveBeenCalledWith(
|
||||
// abortController.signal,
|
||||
// );
|
||||
// expect(mockReturnService.ReturnQueryReturns).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should throw ResponseArgsError when API returns error', async () => {
|
||||
// const errorResponse = { error: 'API Error', result: null };
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(of(errorResponse));
|
||||
|
||||
// await expect(
|
||||
// service.fetchIncompletedRemissionReturnReceipts(),
|
||||
// ).rejects.toThrow(ResponseArgsError);
|
||||
// });
|
||||
|
||||
// it('should return empty array when result is null', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: null, error: null }),
|
||||
// );
|
||||
|
||||
// const result = await service.fetchIncompletedRemissionReturnReceipts();
|
||||
|
||||
// expect(result).toEqual([]);
|
||||
// });
|
||||
|
||||
// it('should return empty array when result is undefined', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(of({ error: null }));
|
||||
|
||||
// const result = await service.fetchIncompletedRemissionReturnReceipts();
|
||||
|
||||
// expect(result).toEqual([]);
|
||||
// });
|
||||
|
||||
// it('should handle stock service errors', async () => {
|
||||
// mockRemissionStockService.fetchAssignedStock.mockRejectedValue(
|
||||
// new Error('Stock error'),
|
||||
// );
|
||||
|
||||
// await expect(
|
||||
// service.fetchIncompletedRemissionReturnReceipts(),
|
||||
// ).rejects.toThrow('Stock error');
|
||||
// expect(mockReturnService.ReturnQueryReturns).not.toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should handle observable errors', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// throwError(() => new Error('Observable error')),
|
||||
// );
|
||||
|
||||
// await expect(
|
||||
// service.fetchIncompletedRemissionReturnReceipts(),
|
||||
// ).rejects.toThrow('Observable error');
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('edge cases', () => {
|
||||
// beforeEach(() => {
|
||||
// mockRemissionStockService.fetchAssignedStock.mockResolvedValue(mockStock);
|
||||
// });
|
||||
|
||||
// it('should handle empty returns array', async () => {
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: [], error: null }),
|
||||
// );
|
||||
|
||||
// const completedResult =
|
||||
// await service.fetchRemissionReturnReceipts();
|
||||
// const incompletedResult =
|
||||
// await service.fetchIncompletedRemissionReturnReceipts();
|
||||
|
||||
// expect(completedResult).toEqual([]);
|
||||
// expect(incompletedResult).toEqual([]);
|
||||
// });
|
||||
|
||||
// it('should handle stock with no id', async () => {
|
||||
// mockRemissionStockService.fetchAssignedStock.mockResolvedValue({
|
||||
// ...mockStock,
|
||||
// id: undefined,
|
||||
// });
|
||||
// mockReturnService.ReturnQueryReturns.mockReturnValue(
|
||||
// of({ result: mockReturns, error: null }),
|
||||
// );
|
||||
|
||||
// await service.fetchRemissionReturnReceipts();
|
||||
|
||||
// expect(mockReturnService.ReturnQueryReturns).toHaveBeenCalledWith({
|
||||
// stockId: undefined,
|
||||
// queryToken: expect.any(Object),
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('fetchRemissionReturnReceipt', () => {
|
||||
const mockReceipt: Receipt = {
|
||||
id: 101,
|
||||
|
||||
@@ -77,19 +77,22 @@ export class RemissionReturnReceiptService {
|
||||
const { start, returncompleted } =
|
||||
FetchRemissionReturnReceiptsSchema.parse(params);
|
||||
|
||||
// Default to 7 days ago if no start date is provided
|
||||
const startDate = start ?? subDays(new Date(), 7);
|
||||
|
||||
const assignedStock =
|
||||
await this.#remissionStockService.fetchAssignedStock(abortSignal);
|
||||
|
||||
this.#logger.info('Fetching completed returns from API', () => ({
|
||||
stockId: assignedStock.id,
|
||||
startDate: start?.toISOString(),
|
||||
startDate: startDate.toISOString(),
|
||||
}));
|
||||
|
||||
let req$ = this.#returnService.ReturnQueryReturns({
|
||||
stockId: assignedStock.id,
|
||||
queryToken: {
|
||||
filter: { returncompleted: returncompleted ? 'true' : 'false' },
|
||||
start: start?.toISOString(),
|
||||
start: startDate.toISOString(),
|
||||
eagerLoading: 3,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const EMPTY_WBS_TITLE = 'Keine Artikel';
|
||||
export const EMPTY_WBS_DESCRIPTION =
|
||||
'Fügen Sie dem Warenbegleitschein Artikel hinzu, um die Wanne abzuschließen';
|
||||
@@ -34,8 +34,24 @@
|
||||
></ui-icon-button>
|
||||
</div>
|
||||
} @else if (items.length === 0) {
|
||||
<div class="isa-text-body-2-regular">
|
||||
Keine Artikel auf dem Warenbegleitschein.
|
||||
<div class="flex items-center justify-center">
|
||||
<ui-empty-state
|
||||
[title]="emptyWbsTitle"
|
||||
[description]="emptyWbsDescription"
|
||||
appearance="noArticles"
|
||||
>
|
||||
<button
|
||||
class="mt-[1.5rem]"
|
||||
uiButton
|
||||
type="button"
|
||||
appearance="secondary"
|
||||
size="large"
|
||||
[disabled]="store.remissionStarted()"
|
||||
(click)="continueRemission()"
|
||||
>
|
||||
Jetzt befüllen
|
||||
</button>
|
||||
</ui-empty-state>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="bg-isa-white rounded-2xl p-6 grid grid-flow-row gap-6">
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
RemissionStore,
|
||||
} from '@isa/remission/data-access';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { EmptyStateComponent } from '@isa/ui/empty-state';
|
||||
import { EMPTY_WBS_DESCRIPTION, EMPTY_WBS_TITLE } from './constants';
|
||||
|
||||
/**
|
||||
* Component for displaying detailed information about a remission return receipt.
|
||||
@@ -52,6 +54,7 @@ import { logger } from '@isa/core/logging';
|
||||
RemissionReturnReceiptDetailsCardComponent,
|
||||
RemissionReturnReceiptDetailsItemComponent,
|
||||
StatefulButtonComponent,
|
||||
EmptyStateComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionChevronLeft, isaLoading })],
|
||||
})
|
||||
@@ -62,6 +65,9 @@ export class RemissionReturnReceiptDetailsComponent {
|
||||
|
||||
#remissionReturnReceiptService = inject(RemissionReturnReceiptService);
|
||||
|
||||
emptyWbsTitle = EMPTY_WBS_TITLE;
|
||||
emptyWbsDescription = EMPTY_WBS_DESCRIPTION;
|
||||
|
||||
/** Instance of the RemissionStore for managing remission state */
|
||||
store = inject(RemissionStore);
|
||||
|
||||
@@ -120,6 +126,13 @@ export class RemissionReturnReceiptDetailsComponent {
|
||||
completingReturn = signal(false);
|
||||
completeReturnError = signal<string | null>(null);
|
||||
|
||||
async continueRemission() {
|
||||
this.store.startRemission({
|
||||
returnId: this.returnId(),
|
||||
receiptId: this.receiptId(),
|
||||
});
|
||||
}
|
||||
|
||||
async completeReturn() {
|
||||
if (this.completingReturn()) {
|
||||
return;
|
||||
|
||||
@@ -31,7 +31,7 @@ vi.mock('@isa/shared/filter', async () => {
|
||||
class MockReturnReceiptListItemComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'isa-order-by-toolbar',
|
||||
selector: 'remi-order-by-toolbar',
|
||||
template: '<div>Mock Order By Toolbar</div>',
|
||||
standalone: true,
|
||||
})
|
||||
@@ -43,7 +43,9 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
let mockRemissionReturnReceiptService: {
|
||||
fetchRemissionReturnReceipts: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let mockFilterService: any;
|
||||
let mockFilterService: {
|
||||
orderBy: ReturnType<typeof signal>;
|
||||
};
|
||||
|
||||
const mockCompletedReturn: Return = {
|
||||
id: 1,
|
||||
@@ -146,7 +148,8 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
// Assert - Private fields cannot be directly tested
|
||||
// Instead, we verify the component initializes correctly with dependencies
|
||||
expect(component).toBeDefined();
|
||||
expect(component.remissionReturnsResource).toBeDefined();
|
||||
expect(component.completedRemissionReturnsResource).toBeDefined();
|
||||
expect(component.incompletedRemissionReturnsResource).toBeDefined();
|
||||
expect(component.orderDateBy).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -161,9 +164,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
});
|
||||
|
||||
describe('Resource Loading', () => {
|
||||
it('should initialize remissionReturnsResource', () => {
|
||||
it('should initialize completed and incomplete resources', () => {
|
||||
// Assert
|
||||
expect(component.remissionReturnsResource).toBeDefined();
|
||||
expect(component.completedRemissionReturnsResource).toBeDefined();
|
||||
expect(component.incompletedRemissionReturnsResource).toBeDefined();
|
||||
});
|
||||
|
||||
it('should fetch remission return receipts on component initialization', () => {
|
||||
@@ -192,7 +196,8 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
const newComponent = newFixture.componentInstance;
|
||||
|
||||
// Assert
|
||||
expect(newComponent.remissionReturnsResource.isLoading()).toBeDefined();
|
||||
expect(newComponent.completedRemissionReturnsResource.isLoading()).toBeDefined();
|
||||
expect(newComponent.incompletedRemissionReturnsResource.isLoading()).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle error state when service fails', async () => {
|
||||
@@ -211,16 +216,19 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
|
||||
// Assert
|
||||
const errorComponent = errorFixture.componentInstance;
|
||||
expect(errorComponent.remissionReturnsResource.error).toBeDefined();
|
||||
expect(errorComponent.completedRemissionReturnsResource.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('returns computed signal', () => {
|
||||
it('should combine returns with incompleted first', () => {
|
||||
// Arrange
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
|
||||
mockReturns
|
||||
);
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
mockCompletedReturn
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
mockIncompletedReturn
|
||||
]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -236,9 +244,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
it('should filter out receipts without data', () => {
|
||||
// Arrange
|
||||
const returnsWithNullData = [mockCompletedReturn, mockReturnWithoutReceiptData];
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue(
|
||||
returnsWithNullData
|
||||
);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -251,7 +260,8 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
|
||||
it('should handle empty returns array', () => {
|
||||
// Arrange
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([]);
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -263,9 +273,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
|
||||
it('should handle null value from resource', () => {
|
||||
// Arrange
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
|
||||
null as any
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue(
|
||||
undefined as Return[] | undefined
|
||||
);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -277,9 +288,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
|
||||
it('should handle undefined value from resource', () => {
|
||||
// Arrange
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue(
|
||||
undefined as Return[] | undefined
|
||||
);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -318,9 +330,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
],
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
returnWithMultipleReceipts,
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -409,10 +422,11 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
],
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
olderReturn,
|
||||
newerReturn,
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -469,10 +483,11 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
],
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(sortedComponent.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(sortedComponent.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
newerReturn,
|
||||
olderReturn,
|
||||
]);
|
||||
vi.spyOn(sortedComponent.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = sortedComponent.returns();
|
||||
@@ -524,10 +539,11 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
],
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
returnWithDate,
|
||||
returnWithoutDate,
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -558,9 +574,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
receipts: [],
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
returnWithEmptyReceipts,
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -571,15 +588,13 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
|
||||
it('should handle mixed returns with and without receipt data', () => {
|
||||
// Arrange
|
||||
const mixedReturns = [
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
mockCompletedReturn,
|
||||
mockReturnWithoutReceiptData,
|
||||
mockIncompletedReturn,
|
||||
];
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue(
|
||||
mixedReturns
|
||||
);
|
||||
mockReturnWithoutReceiptData
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
mockIncompletedReturn
|
||||
]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
@@ -607,9 +622,10 @@ describe('RemissionReturnReceiptListComponent', () => {
|
||||
})),
|
||||
} as Return;
|
||||
|
||||
vi.spyOn(component.remissionReturnsResource, 'value').mockReturnValue([
|
||||
vi.spyOn(component.completedRemissionReturnsResource, 'value').mockReturnValue([
|
||||
returnWithManyReceipts,
|
||||
]);
|
||||
vi.spyOn(component.incompletedRemissionReturnsResource, 'value').mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const returns = component.returns();
|
||||
|
||||
@@ -7,11 +7,12 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
let component: ReturnReceiptListItemComponent;
|
||||
let fixture: ComponentFixture<ReturnReceiptListItemComponent>;
|
||||
|
||||
const createMockReturn = (overrides: Partial<Return> = {}): Return => ({
|
||||
id: 1,
|
||||
receipts: [],
|
||||
...overrides,
|
||||
} as Return);
|
||||
const createMockReturn = (overrides: Partial<Return> = {}): Return =>
|
||||
({
|
||||
id: 1,
|
||||
receipts: [],
|
||||
...overrides,
|
||||
}) as Return;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -52,6 +53,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -70,6 +72,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -77,6 +80,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -84,6 +88,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 3,
|
||||
receiptNumber: 'REC-2024-003-GHI',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -106,6 +111,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -124,6 +130,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: undefined as any,
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -131,6 +138,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -149,6 +157,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'SHORT',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -176,6 +185,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 1,
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: new Array(5),
|
||||
},
|
||||
},
|
||||
@@ -183,6 +193,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 2,
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: new Array(3),
|
||||
},
|
||||
},
|
||||
@@ -190,6 +201,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 3,
|
||||
data: {
|
||||
id: 3,
|
||||
receiptNumber: 'REC-2024-003-GHI',
|
||||
items: new Array(7),
|
||||
},
|
||||
},
|
||||
@@ -230,13 +242,15 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 1,
|
||||
data: {
|
||||
id: 1,
|
||||
items: undefined,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: undefined as any,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: new Array(5),
|
||||
},
|
||||
},
|
||||
@@ -255,6 +269,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 1,
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
@@ -262,6 +277,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 2,
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: new Array(2),
|
||||
},
|
||||
},
|
||||
@@ -394,6 +410,8 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 1,
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'REC-2024-001-ABC',
|
||||
items: [],
|
||||
completed: undefined as any,
|
||||
},
|
||||
},
|
||||
@@ -401,6 +419,8 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 2,
|
||||
data: {
|
||||
id: 2,
|
||||
receiptNumber: 'REC-2024-002-DEF',
|
||||
items: [],
|
||||
completed: true,
|
||||
},
|
||||
},
|
||||
@@ -562,7 +582,9 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: 1,
|
||||
data: {
|
||||
id: 1,
|
||||
receiptNumber: 'PREFIX-VERY-LONG-RECEIPT-NUMBER-THAT-EXCEEDS-NORMAL-LENGTH',
|
||||
receiptNumber:
|
||||
'PREFIX-VERY-LONG-RECEIPT-NUMBER-THAT-EXCEEDS-NORMAL-LENGTH',
|
||||
items: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -580,7 +602,7 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
id: i + 1,
|
||||
receiptNumber: `REC-2024-${String(i + 1).padStart(3, '0')}-ABC`,
|
||||
items: new Array(2),
|
||||
completed: i % 2 === 0,
|
||||
completed: i % 2 === 0 ? '2024-01-15T10:30:00.000Z' : undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -594,4 +616,4 @@ describe('ReturnReceiptListItemComponent', () => {
|
||||
expect(component.receiptNumber()).toContain('24-100');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './lib/empty-state.component';
|
||||
export * from './lib/types';
|
||||
|
||||
5
libs/ui/empty-state/src/lib/constants.ts
Normal file
5
libs/ui/empty-state/src/lib/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const NO_RESULTS =
|
||||
'<svg class="ui-empty-state-icon" xmlns="http://www.w3.org/2000/svg" width="114" height="102" viewBox="0 0 114 102" fill="none" > <path d="M75.5 43.794V30.1846L61.9243 15H27.7699C23.2025 14.9998 19.5 18.5625 19.5 22.9574V82.0424C19.5 86.4373 23.2025 90 27.7699 90H67.2301C71.3894 90 74.4181 87.8467 75 84" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M33.5 49H59.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M33.5 59H53.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M33.5 41H63.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M33.5 32H50.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M61.5 16V30H74.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M53.8139 70H33.5V80H61.5" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M105.003 94.095C103.549 95.6424 101.19 95.6437 99.734 94.098L89.5 84.6013L94.7629 79L105 88.5C106.454 90.0444 106.455 92.5488 105.003 94.095Z" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M88 78L91.5 81" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M78.5 59L68.5 70" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M78.5 70L68.5 59" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> <path d="M73 84C83.7696 84 92.5 75.2695 92.5 64.5C92.5 53.7304 83.7696 45 73 45C62.2304 45 53.5 53.7304 53.5 64.5C53.5 75.2695 62.2304 84 73 84Z" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" /> </svg>';
|
||||
|
||||
export const NO_ARTICLES =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="113" height="102" viewBox="0 0 113 102" fill="none"> <path d="M74.5 84L75 30.1846L61.4243 15H27.2699C22.7025 14.9998 19 18.5625 19 22.9574V82.0424C19 86.4373 22.7025 90 27.2699 90H66.7301C70.8894 90 73.9181 87.8467 74.5 84Z" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M61 16V30H74" stroke="#CED4DA" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M54.25 61.5L39.75 47" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M39.75 61.5L54.25 47" stroke="#6C757D" stroke-width="3" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
@@ -1,110 +1,4 @@
|
||||
<div class="ui-empty-state-circle">
|
||||
<svg
|
||||
class="ui-empty-state-icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="114"
|
||||
height="102"
|
||||
viewBox="0 0 114 102"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M75.5 43.794V30.1846L61.9243 15H27.7699C23.2025 14.9998 19.5 18.5625 19.5 22.9574V82.0424C19.5 86.4373 23.2025 90 27.7699 90H67.2301C71.3894 90 74.4181 87.8467 75 84"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M33.5 49H59.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M33.5 59H53.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M33.5 41H63.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M33.5 32H50.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M61.5 16V30H74.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M53.8139 70H33.5V80H61.5"
|
||||
stroke="#CED4DA"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M105.003 94.095C103.549 95.6424 101.19 95.6437 99.734 94.098L89.5 84.6013L94.7629 79L105 88.5C106.454 90.0444 106.455 92.5488 105.003 94.095Z"
|
||||
stroke="#6C757D"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M88 78L91.5 81"
|
||||
stroke="#6C757D"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M78.5 59L68.5 70"
|
||||
stroke="#6C757D"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M78.5 70L68.5 59"
|
||||
stroke="#6C757D"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M73 84C83.7696 84 92.5 75.2695 92.5 64.5C92.5 53.7304 83.7696 45 73 45C62.2304 45 53.5 53.7304 53.5 64.5C53.5 75.2695 62.2304 84 73 84Z"
|
||||
stroke="#6C757D"
|
||||
stroke-width="3"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ui-empty-state-circle" [innerHTML]="sanitizedIcon()"></div>
|
||||
<div></div>
|
||||
<div class="ui-empty-state-title">{{ title() }}</div>
|
||||
<div class="ui-empty-state-description">{{ description() }}</div>
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, input, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
input,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { NO_RESULTS, NO_ARTICLES } from './constants';
|
||||
import { EmptyStateAppearance } from './types';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-empty-state',
|
||||
@@ -11,6 +21,25 @@ import { ChangeDetectionStrategy, Component, input, ViewEncapsulation } from '@a
|
||||
},
|
||||
})
|
||||
export class EmptyStateComponent {
|
||||
#sanitizer = inject(DomSanitizer);
|
||||
|
||||
title = input.required<string>();
|
||||
description = input.required<string>();
|
||||
|
||||
appearance = input<EmptyStateAppearance>(EmptyStateAppearance.NoResults);
|
||||
|
||||
icon = computed(() => {
|
||||
const appearance = this.appearance();
|
||||
switch (appearance) {
|
||||
case EmptyStateAppearance.NoArticles:
|
||||
return NO_ARTICLES;
|
||||
case EmptyStateAppearance.NoResults:
|
||||
default:
|
||||
return NO_RESULTS;
|
||||
}
|
||||
});
|
||||
|
||||
sanitizedIcon = computed(() => {
|
||||
return this.#sanitizer.bypassSecurityTrustHtml(this.icon());
|
||||
});
|
||||
}
|
||||
|
||||
7
libs/ui/empty-state/src/lib/types.ts
Normal file
7
libs/ui/empty-state/src/lib/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const EmptyStateAppearance = {
|
||||
NoResults: 'noResults',
|
||||
NoArticles: 'noArticles',
|
||||
} as const;
|
||||
|
||||
export type EmptyStateAppearance =
|
||||
(typeof EmptyStateAppearance)[keyof typeof EmptyStateAppearance];
|
||||
Reference in New Issue
Block a user