feat(pos): add shop and statistics modules with components, services, and routing

- Implemented shop module with root component, loading state, and invoice handling.
- Created statistics module with invoice type card component, API routes, and data models.
- Added season picker component for selecting year and season.
- Integrated services for fetching statistics data and managing state.
- Established routing for statistics and shop views.
This commit is contained in:
2026-05-23 18:09:44 +03:30
parent 8c07dc7c3f
commit 6ad1a73c16
100 changed files with 768 additions and 231 deletions
@@ -50,7 +50,7 @@ export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
customer_mobile: this.fb.control<string>(''),
customer_national_id: this.fb.control<string>(''),
customer_economic_code: this.fb.control<string>(''),
status: this.fb.control<string | null>(null),
status: this.fb.control<'NOT_SEND' | 'SUCCESS' | 'FAILURE' | 'QUEUED' | null>(null),
total_amount_from: this.fb.control<number | null>(null),
total_amount_to: this.fb.control<number | null>(null),
});
@@ -74,7 +74,7 @@ export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
total_amount_from: this.value.total_amount_from ?? null,
total_amount_to: this.value.total_amount_to ?? null,
},
{ emitEvent: false },
{ emitEvent: false }
);
}
}
@@ -1,7 +1,8 @@
import { POS_ROUTES } from '@/domains/pos/routes';
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
import { gregorianToJalali } from '@/utils';
import { Component, computed, inject, signal } from '@angular/core';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Button, ButtonDirective } from 'primeng/button';
import { Chip } from 'primeng/chip';
import { Skeleton } from 'primeng/skeleton';
@@ -32,6 +33,7 @@ import { SaleInvoiceCardComponent } from './sale-invoice-card.component';
export class PosSaleInvoiceListComponent {
private readonly service = inject(PosSaleInvoicesService);
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
loading = signal(true);
items = signal<IPosSaleInvoicesSummaryResponse[]>([]);
@@ -57,16 +59,28 @@ export class PosSaleInvoiceListComponent {
.filter(([, value]) => value !== undefined && value !== null && value !== '')
.map(([key, value]) => {
const typedKey = key as keyof IPosSaleInvoicesFilterDto;
const formattedValue =
typedKey === 'status' && value === 'NOT_SEND'
? 'ارسال نشده'
: typedKey === 'status' && value === 'SUCCESS'
? 'موفق'
: typedKey === 'status' && value === 'FAILURE'
? 'ناموفق'
: typedKey === 'status' && value === 'QUEUED'
? 'در انتظار ارسال'
: String(value);
let formattedValue = '';
if (typedKey === 'status') {
formattedValue =
value === 'NOT_SEND'
? 'ارسال نشده'
: value === 'SUCCESS'
? 'موفق'
: value === 'FAILURE'
? 'ناموفق'
: value === 'QUEUED'
? 'در انتظار ارسال'
: String(value);
} else if (
key === 'invoice_date_from' ||
key === 'invoice_date_to' ||
key === 'created_at_from' ||
key === 'created_at_to'
) {
formattedValue = gregorianToJalali(value);
} else {
formattedValue = String(value);
}
return { key: typedKey, label: labels[typedKey] ?? typedKey, value: formattedValue };
});
});
@@ -74,6 +88,7 @@ export class PosSaleInvoiceListComponent {
backRoute = POS_ROUTES.path || '/';
ngOnInit() {
this.hydrateFiltersFromQueryParams();
this.getData();
}
@@ -96,6 +111,7 @@ export class PosSaleInvoiceListComponent {
onFilterApply(query: IPosSaleInvoicesFilterDto) {
this.filters.set(query);
this.syncFiltersToQueryParams();
this.getData();
}
@@ -103,10 +119,57 @@ export class PosSaleInvoiceListComponent {
const next = { ...this.filters() };
delete next[key];
this.filters.set(next);
this.syncFiltersToQueryParams();
this.getData();
}
toSinglePage(item: IPosSaleInvoicesResponse) {
this.router.navigateByUrl(posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id));
}
private hydrateFiltersFromQueryParams() {
const queryParams = this.route.snapshot.queryParams;
const keys: (keyof IPosSaleInvoicesFilterDto)[] = [
'invoice_date_from',
'invoice_date_to',
'created_at_from',
'created_at_to',
'code',
'customer_name',
'customer_mobile',
'customer_national_id',
'customer_economic_code',
'status',
'total_amount_from',
'total_amount_to',
];
const parsed = keys.reduce<IPosSaleInvoicesFilterDto>((acc, key) => {
const value = queryParams[key];
if (value !== undefined && value !== null && value !== '') {
acc[key] = value;
}
return acc;
}, {});
this.filters.set(parsed);
}
private syncFiltersToQueryParams() {
const queryParams = Object.entries(this.filters()).reduce<Record<string, string>>(
(acc, [key, value]) => {
if (value !== undefined && value !== null && value !== '') {
acc[key] = String(value);
}
return acc;
},
{}
);
this.router.navigate([], {
relativeTo: this.route,
queryParams,
replaceUrl: true,
});
}
}