ui update, init to consumer statistics and manage pos user types

This commit is contained in:
2026-04-13 13:22:40 +03:30
parent d4dff4ebfd
commit af3123e61e
85 changed files with 1054 additions and 407 deletions
-2
View File
@@ -8,11 +8,9 @@ import { ToastModule } from 'primeng/toast';
standalone: true, standalone: true,
imports: [RouterModule, ToastModule, ConfirmDialog], imports: [RouterModule, ToastModule, ConfirmDialog],
template: ` template: `
<div>
<p-toast position="bottom-right" /> <p-toast position="bottom-right" />
<p-confirmDialog /> <p-confirmDialog />
<router-outlet /> <router-outlet />
</div>
`, `,
}) })
export class AppComponent {} export class AppComponent {}
+1 -1
View File
@@ -4,11 +4,11 @@ import { POS_ROUTES } from '@/domains/pos/routes';
import { PROVIDER_ROUTES } from '@/domains/provider/routes'; import { PROVIDER_ROUTES } from '@/domains/provider/routes';
import { SUPER_ADMIN_ROUTES } from '@/domains/superAdmin/routes'; import { SUPER_ADMIN_ROUTES } from '@/domains/superAdmin/routes';
import { AuthComponent } from '@/modules/auth/pages/auth.component'; import { AuthComponent } from '@/modules/auth/pages/auth.component';
import { Notfound } from '@/pages/notfound/notfound.component';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { AppLayout } from './app/layout/default/app.layout.component'; import { AppLayout } from './app/layout/default/app.layout.component';
import { Dashboard } from './app/pages/dashboard/dashboard'; import { Dashboard } from './app/pages/dashboard/dashboard';
import { Documentation } from './app/pages/documentation/documentation'; import { Documentation } from './app/pages/documentation/documentation';
import { Notfound } from './app/pages/notfound/notfound.component';
export const appRoutes: Routes = [ export const appRoutes: Routes = [
{ {
@@ -43,7 +43,7 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => {
errorMessage = 'منبع مورد نظر یافت نشد'; errorMessage = 'منبع مورد نظر یافت نشد';
break; break;
case 412: case 412:
errorMessage = error.error?.detail || 'اطلاعات ارسالی نامعتبر است'; errorMessage = error.error?.message || '';
break; break;
case 422: case 422:
errorMessage = error.error?.message || 'اطلاعات ارسالی نامعتبر است'; errorMessage = error.error?.message || 'اطلاعات ارسالی نامعتبر است';
@@ -118,6 +118,7 @@ function showErrorNotification(message: string, status: number): void {
* Error types for better error handling * Error types for better error handling
*/ */
export enum ErrorType { export enum ErrorType {
PRE_CONDITION = 'PRE_CONDITION',
NETWORK = 'NETWORK', NETWORK = 'NETWORK',
SERVER = 'SERVER', SERVER = 'SERVER',
CLIENT = 'CLIENT', CLIENT = 'CLIENT',
@@ -136,9 +137,10 @@ export function getErrorType(error: HttpErrorResponse): ErrorType {
switch (error.status) { switch (error.status) {
case 400: case 400:
case 412:
case 422: case 422:
return ErrorType.VALIDATION; return ErrorType.VALIDATION;
case 412:
return ErrorType.PRE_CONDITION;
case 401: case 401:
return ErrorType.AUTHENTICATION; return ErrorType.AUTHENTICATION;
case 403: case 403:
+10 -1
View File
@@ -5,9 +5,10 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { computed, inject, Injectable, signal } from '@angular/core'; import { computed, inject, Injectable, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';
import { BehaviorSubject, Observable, throwError } from 'rxjs'; import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators'; import { catchError, finalize, tap } from 'rxjs/operators';
import { LOCAL_STORAGE_KEYS } from '../../../assets/constants'; import { COOKIE_KEYS, LOCAL_STORAGE_KEYS } from '../../../assets/constants';
import { import {
IAuthAccountResponse, IAuthAccountResponse,
IAuthResponse, IAuthResponse,
@@ -28,6 +29,7 @@ export class AuthService {
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly cookieService = inject(CookieService);
private captchaService = inject(CaptchaService); private captchaService = inject(CaptchaService);
private readonly currentAccountSubject = new BehaviorSubject<Maybe<IAuthAccountResponse>>(null); private readonly currentAccountSubject = new BehaviorSubject<Maybe<IAuthAccountResponse>>(null);
@@ -81,6 +83,7 @@ export class AuthService {
.pipe( .pipe(
tap((response) => { tap((response) => {
this.handleAuthSuccess(response); this.handleAuthSuccess(response);
this.refreshCookies();
return response; return response;
}), }),
catchError((error) => { catchError((error) => {
@@ -98,11 +101,17 @@ export class AuthService {
localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA);
localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
this.refreshCookies();
this.setCurrentUser(null); this.setCurrentUser(null);
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
} }
refreshCookies() {
this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date());
this.cookieService.set(COOKIE_KEYS.ACCESS_TOKEN, '', new Date());
}
doRefreshToken(): Observable<IAuthResponse> { doRefreshToken(): Observable<IAuthResponse> {
const refreshToken = this.getRefreshToken(); const refreshToken = this.getRefreshToken();
if (!refreshToken) { if (!refreshToken) {
+16 -8
View File
@@ -26,18 +26,16 @@ export class ErrorHandlerService {
*/ */
private handleHttpError(error: HttpErrorResponse): void { private handleHttpError(error: HttpErrorResponse): void {
const errorType = getErrorType(error); const errorType = getErrorType(error);
const userMessage = this.getUserMessage(error); const userMessage = this.getUserMessage(error);
if (userMessage) {
this.toastService.error({
title: error.error?.title || 'خطا',
text: userMessage,
});
} else {
switch (errorType) { switch (errorType) {
case ErrorType.NETWORK: case ErrorType.NETWORK:
this.showNetworkError(userMessage); this.showNetworkError(userMessage);
break; break;
case ErrorType.PRE_CONDITION:
this.showPreConditionError(userMessage);
break;
case ErrorType.AUTHENTICATION: case ErrorType.AUTHENTICATION:
this.showAuthError(userMessage); this.showAuthError(userMessage);
break; break;
@@ -50,17 +48,18 @@ export class ErrorHandlerService {
case ErrorType.SERVER: case ErrorType.SERVER:
this.showServerError(userMessage); this.showServerError(userMessage);
break; break;
case ErrorType.SERVER:
this.showServerError(userMessage);
break;
default: default:
this.showGenericError(userMessage); this.showGenericError(userMessage);
} }
} }
}
/** /**
* Handle generic JavaScript errors * Handle generic JavaScript errors
*/ */
private handleGenericError(error: any): void { private handleGenericError(error: any): void {
console.error('Generic error:', error);
this.toastService.error({ this.toastService.error({
text: 'یک خطای غیرمنتظره رخ داده است. لطفاً صفحه را تازه‌سازی کنید.', text: 'یک خطای غیرمنتظره رخ داده است. لطفاً صفحه را تازه‌سازی کنید.',
}); });
@@ -76,6 +75,15 @@ export class ErrorHandlerService {
}); });
} }
/**
* Show preCondition error notification
*/
private showPreConditionError(message: string): void {
this.toastService.warn({
text: message,
});
}
/** /**
* Show authentication error notification * Show authentication error notification
*/ */
+14 -3
View File
@@ -1,3 +1,4 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Signal, WritableSignal, computed, signal } from '@angular/core'; import { Signal, WritableSignal, computed, signal } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { Maybe } from '../models'; import { Maybe } from '../models';
@@ -8,7 +9,7 @@ import { IPaginatedQuery, IResponseMetadata } from '../models/service.model';
*/ */
export interface BaseState { export interface BaseState {
loading: boolean; loading: boolean;
error: Maybe<string>; error: Maybe<HttpErrorResponse>;
isRefreshing?: boolean; isRefreshing?: boolean;
initialized: boolean; initialized: boolean;
} }
@@ -72,6 +73,15 @@ export interface StateChange<T = any> {
timestamp: number; timestamp: number;
} }
export const defaultBaseStateData = {
loading: false,
error: null,
errorStatus: null,
entity: null,
initialized: false,
isRefreshing: false,
};
/** /**
* Base store class with common functionality * Base store class with common functionality
*/ */
@@ -82,7 +92,7 @@ export abstract class BaseStore<T extends BaseState> {
// Public readonly signals // Public readonly signals
readonly state: Signal<T>; readonly state: Signal<T>;
readonly loading: Signal<boolean>; readonly loading: Signal<boolean>;
readonly error: Signal<Maybe<string>>; readonly error: Signal<Maybe<HttpErrorResponse>>;
readonly initialized: Signal<boolean>; readonly initialized: Signal<boolean>;
// Observable for RxJS compatibility // Observable for RxJS compatibility
@@ -135,7 +145,7 @@ export abstract class BaseStore<T extends BaseState> {
/** /**
* Set error state * Set error state
*/ */
protected setError(error: string): void { protected setError(error: HttpErrorResponse): void {
this.patchState({ this.patchState({
loading: false, loading: false,
error, error,
@@ -180,6 +190,7 @@ export abstract class EntityStore<
setEntity(entity: Maybe<T>): void { setEntity(entity: Maybe<T>): void {
this.patchState({ this.patchState({
entity, entity,
error: null,
} as Partial<TState>); } as Partial<TState>);
} }
override reset(): void { override reset(): void {
@@ -0,0 +1,49 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="کد رهگیری" [value]="invoice()?.code" />
<app-key-value label="تاریخ فاکتور" [value]="invoice()?.invoice_date" type="dateTime" />
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice()?.created_at" type="dateTime" />
<app-key-value label="وضعیت صدور به سامانه‌ی مودیان">
<p-badge value="ارسال نشده" severity="danger" />
</app-key-value>
<div class="col-span-3">
<app-key-value label="توضیحات" [value]="invoice()?.notes" />
</div>
</div>
<p-divider align="center"> اطلاعات پرداخت </p-divider>
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="مجموع قابل پرداخت" [value]="invoice()?.total_amount" type="price" />
@for (payment of invoice()?.payments; track $index) {
<app-key-value
[label]="
`${payment.payment_method === 'SET_OF' ? 'تهاتر' : payment.payment_method === 'TERMINAL' ? 'پایانه' : 'نقدی'}`
"
[value]="payment.amount"
type="price"
/>
}
</div>
<p-divider align="center"> اطلاعات صادر کننده </p-divider>
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="کسب و کار" [value]="invoice()?.pos!.complex.business_activity.name" />
<app-key-value label="مجموعه" [value]="invoice()?.pos!.complex.name" />
<app-key-value label="پایانه‌ی فروش" [value]="invoice()?.pos!.name" />
<app-key-value label="ایجاد کننده" [value]="invoice()?.account?.account?.username" />
</div>
@if (variant !== "customer") {
<p-divider align="center"> اطلاعات مشتری </p-divider>
<div class="grid grid-cols-3 gap-4 items-center">
@if (invoice()?.customer) {}
<!-- <app-key-value label="کسب و کار" [value]="invoice()?.pos!.complex.business_activity.name" />
<app-key-value label="مجموعه" [value]="invoice()?.pos!.complex.name" />
<app-key-value label="پایانه‌ی فروش" [value]="invoice()?.pos!.name" />
<app-key-value label="ایجاد کننده" [value]="invoice()?.account?.account?.username" /> -->
</div>
}
</div>
</app-card-data>
</div>
@@ -0,0 +1,20 @@
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import { Component, Input, signal } from '@angular/core';
import { Badge } from 'primeng/badge';
import { Divider } from 'primeng/divider';
export type TConsumerSaleInvoice = 'full' | 'customer' | 'pos';
@Component({
selector: 'consumer-saleInvoice-shared',
templateUrl: './single.component.html',
imports: [AppCardComponent, KeyValueComponent, Badge, Divider],
})
export class ConsumerSaleInvoiceSharedComponent {
@Input({ required: true }) loading!: boolean;
@Input({ required: true }) invoice!: any;
@Input() editable = false;
@Input() variant: TConsumerSaleInvoice = 'full';
editMode = signal<boolean>(false);
}
@@ -31,6 +31,8 @@ export class LayoutComponent {
}); });
ngOnInit() { ngOnInit() {
console.log('first');
this.layoutService.setMenuItems(CONSUMER_MENU_ITEMS); this.layoutService.setMenuItems(CONSUMER_MENU_ITEMS);
this.store.getData(); this.store.getData();
} }
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerAccountsNamedRoutes } from '../constants'; import { consumerAccountsNamedRoutes } from '../constants';
import { IAccountResponse } from '../models'; import { IAccountResponse } from '../models';
import { AccountsService } from '../services/main.service'; import { AccountsService } from '../services/main.service';
@@ -51,11 +51,9 @@ export class AccountStore extends EntityStore<IAccountResponse, AccountState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerBusinessActivityNamedRoutes } from '../constants'; import { consumerBusinessActivityNamedRoutes } from '../constants';
import { IBusinessActivityResponse } from '../models'; import { IBusinessActivityResponse } from '../models';
import { BusinessActivitiesService } from '../services/main.service'; import { BusinessActivitiesService } from '../services/main.service';
@@ -55,11 +55,9 @@ export class BusinessActivityStore extends EntityStore<
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerComplexesNamedRoutes } from '../constants/routes/complexes'; import { consumerComplexesNamedRoutes } from '../constants/routes/complexes';
import { IComplexResponse } from '../models'; import { IComplexResponse } from '../models';
import { ConsumerComplexesService } from '../services/complexes.service'; import { ConsumerComplexesService } from '../services/complexes.service';
@@ -50,11 +50,9 @@ export class ConsumerComplexStore extends EntityStore<IComplexResponse, ComplexS
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerPosesNamedRoutes } from '../constants/routes/poses'; import { consumerPosesNamedRoutes } from '../constants/routes/poses';
import { IPosResponse } from '../models'; import { IPosResponse } from '../models';
import { ConsumerPosesService } from '../services/poses.service'; import { ConsumerPosesService } from '../services/poses.service';
@@ -51,11 +51,9 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -5,6 +5,7 @@ import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
import { Button } from 'primeng/button'; import { Button } from 'primeng/button';
import { COOKIE_KEYS } from 'src/assets/constants';
import { ConsumerPosFormComponent } from '../../components/poses/form.component'; import { ConsumerPosFormComponent } from '../../components/poses/form.component';
import { ConsumerSalesInvoicesComponent } from '../../components/salesInvoices/list.component'; import { ConsumerSalesInvoicesComponent } from '../../components/salesInvoices/list.component';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
@@ -53,8 +54,8 @@ export class SuperAdminUserPosComponent {
} }
toPosLanding() { toPosLanding() {
// this.cookieService.delete('posId', '/', 'localhost', false, 'Lax'); this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date());
this.cookieService.set('posId', this.posId(), { this.cookieService.set(COOKIE_KEYS.POS_ID, this.posId(), {
sameSite: 'Lax', // or 'Strict' for same-site requests only sameSite: 'Lax', // or 'Strict' for same-site requests only
secure: false, secure: false,
path: '/', path: '/',
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerCustomersNamedRoutes } from '../constants'; import { consumerCustomersNamedRoutes } from '../constants';
import { ICustomerResponse } from '../models'; import { ICustomerResponse } from '../models';
import { CustomersService } from '../services/main.service'; import { CustomersService } from '../services/main.service';
@@ -54,11 +54,9 @@ export class ConsumerCustomerStore extends EntityStore<ICustomerResponse, Consum
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerCustomerSaleInvoicesNamedRoutes } from '../constants/routes/saleInvoices'; import { consumerCustomerSaleInvoicesNamedRoutes } from '../constants/routes/saleInvoices';
import { ICustomerSaleInvoicesResponse } from '../models'; import { ICustomerSaleInvoicesResponse } from '../models';
import { CustomerSaleInvoicesService } from '../services/saleInvoices.service'; import { CustomerSaleInvoicesService } from '../services/saleInvoices.service';
@@ -58,11 +58,9 @@ export class ConsumerCustomerSaleInvoiceStore extends EntityStore<
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -0,0 +1,13 @@
<app-page-data-list
pageTitle="آخرین فاکتورهای صادر شده‌ی امروز"
[columns]="columns"
emptyPlaceholderTitle="تا به این لحظه فاکتوری صادر نشده است."
[items]="items()"
[showDetails]="true"
[loading]="loading()"
(onDetails)="toSinglePage($event)"
>
<ng-template #moreActions>
<a routerLink pButton [routerLink]="invoicesPageRoute" outlined>تمامی فاکتورها</a>
</ng-template>
</app-page-data-list>
@@ -0,0 +1,42 @@
import { consumerSaleInvoicesNamedRoutes } from '@/domains/consumer/modules/saleInvoices/constants';
import { AbstractList } from '@/shared/abstractClasses';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import { ButtonDirective } from 'primeng/button';
import { IStatisticsSaleInvoicesResponse } from '../../../models';
import { CustomerStatisticsService } from '../../../services/main.service';
@Component({
selector: 'consumer-statistics-latest-Invoices',
templateUrl: './latest-Invoices.component.html',
imports: [PageDataListComponent, ButtonDirective, RouterLink],
})
export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<IStatisticsSaleInvoicesResponse> {
private readonly service = inject(CustomerStatisticsService);
private readonly router = inject(Router);
readonly invoicesPageRoute = consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!();
override setColumns(): void {
this.columns = [
{ field: 'code', header: 'کد پیگیری' },
{ field: 'total_amount', header: 'مجموع قیمت', type: 'price' },
{
field: 'created_at',
header: 'تاریخ ایجاد',
type: 'date',
},
];
}
override getDataRequest() {
return this.service.getInvoices();
}
toSinglePage(invoice: IStatisticsSaleInvoicesResponse) {
this.router.navigateByUrl(
consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoice.id),
);
}
}
@@ -0,0 +1,5 @@
const baseUrl = '/api/v1/consumer/statistics';
export const CONSUMER_STATISTICS_API_ROUTES = {
invoices: () => `${baseUrl}/invoices`,
};
@@ -0,0 +1 @@
export * from './apiRoutes';
@@ -0,0 +1 @@
export * from './saleInvoices.io';
@@ -0,0 +1,27 @@
import ISummary from '@/core/models/summary';
export interface IStatisticsSaleInvoicesRawResponse {
id: string;
code: string;
created_date: string;
total_amount: string;
pos: Pos;
consumer_account: ConsumerAccount;
created_at: string;
}
export interface IStatisticsSaleInvoicesResponse extends IStatisticsSaleInvoicesRawResponse {}
interface ConsumerAccount {
role: string;
account: {
username: string;
};
}
interface Pos extends ISummary {
complex: Complex;
}
interface Complex extends ISummary {
business_activity: ISummary;
}
@@ -0,0 +1,19 @@
import { IListingResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CONSUMER_STATISTICS_API_ROUTES } from '../constants';
import { IStatisticsSaleInvoicesRawResponse, IStatisticsSaleInvoicesResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class CustomerStatisticsService {
constructor(private http: HttpClient) {}
private apiRoutes = CONSUMER_STATISTICS_API_ROUTES;
getInvoices(): Observable<IListingResponse<IStatisticsSaleInvoicesResponse>> {
return this.http.get<IListingResponse<IStatisticsSaleInvoicesRawResponse>>(
this.apiRoutes.invoices(),
);
}
}
@@ -1,3 +1,5 @@
<div class="flex items-center justify-center h-[100cqmin]"> <div class="grid grid-cols-2 gap-10">
<span class="text-center"> به پنل کسب و کار خوش آمدید </span> <div class="h-[300px] flex">
<consumer-statistics-latest-Invoices class="w-full" />
</div>
</div> </div>
@@ -1,8 +1,10 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ConsumerStatisticsLatestInvoicesComponent } from '../components/statistics/latestInvoices/latest-Invoices.component';
@Component({ @Component({
selector: 'consumer-dashboard', selector: 'consumer-dashboard',
templateUrl: './index.component.html', templateUrl: './index.component.html',
imports: [ConsumerStatisticsLatestInvoicesComponent],
}) })
export class DashboardComponent { export class DashboardComponent {
constructor() {} constructor() {}
@@ -0,0 +1,5 @@
@if (loading()) {
<shared-page-loading />
} @else {
<router-outlet></router-outlet>
}
@@ -0,0 +1,26 @@
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
import { ConsumerSaleInvoiceStore } from '../store/main.store';
@Component({
selector: 'consumer-customer-layout',
templateUrl: './layout.component.html',
imports: [PageLoadingComponent, RouterOutlet],
})
export class ConsumerCustomerLayoutComponent {
private readonly route = inject(ActivatedRoute);
private readonly store = inject(ConsumerSaleInvoiceStore);
readonly invoiceId = signal<string>(this.route.snapshot.paramMap.get('invoiceId')!);
readonly loading = computed(() => this.store.loading());
getData() {
this.store.getData(this.invoiceId());
}
ngOnInit() {
this.getData();
}
}
@@ -0,0 +1,9 @@
<app-page-data-list
[pageTitle]="'لیست فاکتورهای ایجاد شده'"
[columns]="columns"
emptyPlaceholderTitle="فاکتوری یافت نشد"
[items]="items()"
[loading]="loading()"
[showDetails]="true"
(onDetails)="toSinglePage($event)"
/>
@@ -0,0 +1,66 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import {
IColumn,
PageDataListComponent,
} from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject, Input } from '@angular/core';
import { Router } from '@angular/router';
import { consumerSaleInvoicesNamedRoutes } from '../constants/routes';
import { IConsumerSaleInvoicesResponse } from '../models';
import { ConsumerSaleInvoicesService } from '../services/main.service';
@Component({
selector: 'consumer-saleInvoice-list',
templateUrl: './list.component.html',
imports: [PageDataListComponent],
})
export class ConsumerSaleInvoiceListComponent extends AbstractList<IConsumerSaleInvoicesResponse> {
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'code', header: 'کد رهگیری' },
{
field: 'total_amount',
header: 'قیمت نهایی',
type: 'price',
},
{
field: 'pos',
header: 'فروشگاه',
customDataModel(item: IConsumerSaleInvoicesResponse) {
return `${item.pos.complex.business_activity.name}، فروشگاه ${item.pos.complex.name}، پایانه‌ی فروش ${item.pos.name}`;
},
},
{
field: 'account',
header: 'ایجاد شده توسط',
type: 'nested',
nestedPath: 'account.account.username',
},
{
field: 'invoice_date',
header: 'تاریخ فاکتور',
type: 'date',
},
{
field: 'created_at',
header: 'تاریخ ایجاد',
type: 'date',
},
];
private readonly service = inject(ConsumerSaleInvoicesService);
private readonly router = inject(Router);
override setColumns(): void {
this.columns = this.header;
}
override getDataRequest() {
return this.service.getAll();
}
toSinglePage(item: IConsumerSaleInvoicesResponse) {
this.router.navigateByUrl(consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id));
}
}
@@ -0,0 +1,6 @@
const baseUrl = () => `/api/v1/consumer/sale-invoices`;
export const CONSUMER_SALE_INVOICES_API_ROUTES = {
list: () => baseUrl(),
single: (id: string) => `${baseUrl()}/${id}`,
};
@@ -0,0 +1,2 @@
export * from './apiRoutes';
export * from './routes';
@@ -0,0 +1,30 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
export type TConsumerSaleInvoicesRouteNames = 'saleInvoices' | 'saleInvoice';
export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesRouteNames> = {
saleInvoices: {
path: 'saleInvoices',
loadComponent: () =>
import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent),
meta: {
title: 'فاکتورهای صادر شده',
pagePath: () => `/consumer/saleInvoices`,
},
},
saleInvoice: {
path: 'saleInvoices/:invoiceId',
loadComponent: () =>
import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent),
meta: {
title: 'فاکتور صادر شده',
pagePath: (saleInvoiceId: string) => `/consumer/saleInvoices/${saleInvoiceId}`,
},
},
};
export const CONSUMER_SALE_INVOICES_ROUTES: Routes = [
consumerSaleInvoicesNamedRoutes.saleInvoices,
consumerSaleInvoicesNamedRoutes.saleInvoice,
];
@@ -0,0 +1 @@
export * from './io';
@@ -0,0 +1,44 @@
import ISummary from '@/core/models/summary';
export interface IConsumerSaleInvoicesRawResponse {
id: string;
code: string;
invoice_date: string;
notes?: string;
total_amount: string;
pos: Pos;
account: ConsumerAccount;
created_at: string;
items_count: number;
payments: Payments[];
}
export interface IConsumerSaleInvoicesResponse extends IConsumerSaleInvoicesRawResponse {}
interface ConsumerAccount {
role: string;
user: User;
account: Account;
}
interface Account {
username: string;
}
interface User {
first_name: string;
last_name: string;
}
interface Pos extends ISummary {
complex: Complex;
}
interface Complex extends ISummary {
business_activity: ISummary;
}
interface Payments {
amount: string;
paid_at: string;
payment_method: string;
}
@@ -0,0 +1,22 @@
import { IPaginatedResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CONSUMER_SALE_INVOICES_API_ROUTES } from '../constants';
import { IConsumerSaleInvoicesRawResponse, IConsumerSaleInvoicesResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class ConsumerSaleInvoicesService {
constructor(private http: HttpClient) {}
private apiRoutes = CONSUMER_SALE_INVOICES_API_ROUTES;
getAll(): Observable<IPaginatedResponse<IConsumerSaleInvoicesResponse>> {
return this.http.get<IPaginatedResponse<IConsumerSaleInvoicesRawResponse>>(
this.apiRoutes.list(),
);
}
getSingle(invoiceId: string): Observable<IConsumerSaleInvoicesResponse> {
return this.http.get<IConsumerSaleInvoicesRawResponse>(this.apiRoutes.single(invoiceId));
}
}
@@ -0,0 +1,78 @@
import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { consumerSaleInvoicesNamedRoutes } from '../constants';
import { IConsumerSaleInvoicesResponse } from '../models';
import { ConsumerSaleInvoicesService } from '../services/main.service';
interface ConsumerSaleInvoiceState extends EntityState<IConsumerSaleInvoicesResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({
providedIn: 'root',
})
export class ConsumerSaleInvoiceStore extends EntityStore<
IConsumerSaleInvoicesResponse,
ConsumerSaleInvoiceState
> {
private readonly service = inject(ConsumerSaleInvoicesService);
constructor() {
super({
loading: false,
error: null,
entity: null,
initialized: false,
isRefreshing: false,
breadcrumbItems: [],
});
}
breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(invoiceId: string) {
this.patchState({
breadcrumbItems: [
{
...consumerSaleInvoicesNamedRoutes.saleInvoices.meta,
routerLink: consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
},
{
title: this.entity()?.code,
routerLink: consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoiceId),
},
],
});
}
getData(invoiceId: string) {
this.patchState({ loading: true });
this.service
.getSingle(invoiceId)
.pipe(
finalize(() => {
this.patchState({ loading: false });
}),
catchError((error) => {
this.setError(error);
throw error;
}),
)
.subscribe((entity) => {
this.patchState({ entity });
this.setBreadcrumb(invoiceId);
});
}
override reset(): void {
this.setState({
loading: false,
error: null,
entity: null,
initialized: false,
isRefreshing: false,
breadcrumbItems: [],
});
}
}
@@ -0,0 +1,2 @@
export * from './list.component';
export * from './single.component';
@@ -0,0 +1 @@
<consumer-saleInvoice-list />
@@ -0,0 +1,10 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { Component } from '@angular/core';
import { ConsumerSaleInvoiceListComponent } from '../components/list.component';
@Component({
selector: 'consumer-saleInvoices',
templateUrl: './list.component.html',
imports: [ConsumerSaleInvoiceListComponent],
})
export class ConsumerSaleInvoicesComponent {}
@@ -0,0 +1 @@
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice" />
@@ -0,0 +1,44 @@
import { BreadcrumbService } from '@/core/services';
import { ConsumerSaleInvoiceSharedComponent } from '@/domains/consumer/components/invoices/single.component';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerSaleInvoiceStore } from '../store/main.store';
@Component({
selector: 'consumer-saleInvoice',
templateUrl: './single.component.html',
imports: [ConsumerSaleInvoiceSharedComponent],
})
export class ConsumerSaleInvoiceComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly store = inject(ConsumerSaleInvoiceStore);
pageParams = computed(() => pageParamsUtils(this.route));
invoiceId = signal<string>(this.pageParams()['invoiceId']);
editMode = signal<boolean>(false);
readonly invoice = computed(() => this.store.entity());
readonly loading = computed(() => this.store.loading());
constructor() {
effect(() => {
if (this.invoice()?.id) {
this.setBreadcrumb();
}
});
}
ngOnInit() {
this.getData();
}
getData() {
this.store.getData(this.invoiceId());
}
setBreadcrumb() {
this.breadcrumbService.setItems([...this.store.breadcrumbItems()]);
}
}
+2
View File
@@ -2,6 +2,7 @@ import { Route } from '@angular/router';
import { CONSUMER_ACCOUNTS_ROUTES } from './modules/accounts/constants'; import { CONSUMER_ACCOUNTS_ROUTES } from './modules/accounts/constants';
import { CONSUMER_BUSINESS_ACTIVITIES_ROUTES } from './modules/businessActivities/constants'; import { CONSUMER_BUSINESS_ACTIVITIES_ROUTES } from './modules/businessActivities/constants';
import { CONSUMER_CUSTOMERS_ROUTES } from './modules/customers/constants'; import { CONSUMER_CUSTOMERS_ROUTES } from './modules/customers/constants';
import { CONSUMER_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants';
export const CONSUMER_ROUTES = { export const CONSUMER_ROUTES = {
path: 'consumer', path: 'consumer',
@@ -15,5 +16,6 @@ export const CONSUMER_ROUTES = {
...CONSUMER_ACCOUNTS_ROUTES, ...CONSUMER_ACCOUNTS_ROUTES,
...CONSUMER_BUSINESS_ACTIVITIES_ROUTES, ...CONSUMER_BUSINESS_ACTIVITIES_ROUTES,
...CONSUMER_CUSTOMERS_ROUTES, ...CONSUMER_CUSTOMERS_ROUTES,
...CONSUMER_SALE_INVOICES_ROUTES,
], ],
} as Route; } as Route;
+4 -6
View File
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { LayoutService } from '@/layout/service/layout.service'; import { LayoutService } from '@/layout/service/layout.service';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { IConsumerInfoResponse } from '../models'; import { IConsumerInfoResponse } from '../models';
import { ConsumerService } from '../services/main.service'; import { ConsumerService } from '../services/main.service';
@@ -32,11 +32,9 @@ export class ConsumerStore extends EntityStore<IConsumerInfoResponse, ConsumerSt
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -0,0 +1,24 @@
@if (loading()) {
<shared-page-loading />
} @else if (items()) {
<div class="h-full w-full max-w-6xl mx-auto flex flex-col items-center justify-center gap-10">
<p-message severity="warn"> برای ادامه می‌بایست یکی از پایانه‌های زیر را انتخاب کنید. </p-message>
<div class="grid grid-cols-2 gap-10 items-stretch w-full">
@for (item of items(); track $index) {
<p-card
class="border border-surface-border bg-surface-card rounded-2xl cursor-pointer hover:bg-surface-50! transition-colors"
(click)="selectPos(item)"
>
<div class="flex flex-col gap-2 items-center justify-between shrink-0">
<i class="pi pi-select text-6xl! mb-3 text-surface-card" aria-hidden="true"></i>
<span class="text-xl font-semibold mb-2 block"> {{ item.name }} </span>
<span class="inline-block text-base">
{{ item.complex.business_activity.name }} - {{ item.complex.name }}
</span>
<button pButton class="w-full max-w-xs mx-auto mt-4">انتخاب پایانه</button>
</div>
</p-card>
}
</div>
</div>
}
@@ -0,0 +1,59 @@
import { Maybe } from '@/core';
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, inject, Output, signal } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
import { Message } from 'primeng/message';
import { catchError, finalize } from 'rxjs';
import { COOKIE_KEYS } from 'src/assets/constants';
import { IPosAccessibleResponse } from '../models/pos.io';
import { PosService } from '../modules/landing/services/main.service';
@Component({
selector: 'pos-choose-cards',
templateUrl: './choose-pos.component.html',
imports: [PageLoadingComponent, Card, ButtonDirective, Message],
})
export class PosChooseCardsComponent {
@Output() onChoose = new EventEmitter();
private readonly service = inject(PosService);
private readonly cookieService = inject(CookieService);
loading = signal(true);
items = signal<IPosAccessibleResponse[]>([]);
error = signal<Maybe<HttpErrorResponse>>(null);
getData() {
this.loading.set(true);
this.service
.getAccessible()
.pipe(
finalize(() => this.loading.set(false)),
catchError((err) => {
this.error.set(err);
throw err;
}),
)
.subscribe((res) => {
this.items.set(res?.data || []);
});
}
ngOnInit() {
this.getData();
}
selectPos(pos: IPosAccessibleResponse) {
this.cookieService.set(COOKIE_KEYS.POS_ID, pos.id, {
sameSite: 'Lax', // or 'Strict' for same-site requests only
secure: false,
path: '/',
domain: 'localhost',
});
this.onChoose.emit();
}
}
@@ -7,17 +7,42 @@
<div class="w-10 h-10"> <div class="w-10 h-10">
<img [src]="placeholderLogo" alt="Logo" class="w-full h-full object-cover rounded-md bg-surface-card" /> <img [src]="placeholderLogo" alt="Logo" class="w-full h-full object-cover rounded-md bg-surface-card" />
</div> </div>
@if (posInfo()) {
<span class="text-lg font-bold">{{ posInfo()?.name }} - فروشگاه {{ posInfo()?.complex?.name }}</span> <span class="text-lg font-bold">{{ posInfo()?.name }} - فروشگاه {{ posInfo()?.complex?.name }}</span>
}
</div> </div>
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<div class="bg-surface-card py-1 px-3 rounded-md shadow-sm"> <div class="bg-surface-card px-3 rounded-md shadow-sm h-9 flex items-center">
<span [jalaliDate]="now" jalaliFormat="dddd YYYY/MM/DD" class="text-base"></span> <span [jalaliDate]="now" jalaliFormat="dddd YYYY/MM/DD" class="text-base"></span>
</div> </div>
<div class="bg-surface-card py-1 px-3 rounded-md shadow-sm"> <div class="">
<span class="text-base"> {{ userInfo()?.username }} </span> <p-menu #menu [model]="profileMenuItems" [popup]="true" />
<button pButton (click)="menu.toggle($event)" icon="pi pi-user" severity="contrast">
{{ userInfo()?.username }}
</button>
</div> </div>
</div> </div>
</div> </div>
<router-outlet></router-outlet> @if (error()) {
@switch (error()?.status) {
@case (412) {
<pos-choose-cards class="h-full" (onChoose)="onChoosePos()" />
}
@default {
<div class="h-full w-full flex items-center justify-center">
<p-card class="border border-surface-border">
<div class="flex flex-col gap-2 items-center justify-between shrink-0">
<i class="pi pi-exclamation-triangle text-6xl! mb-3 text-error" aria-hidden="true"></i>
<span class="text-xl font-semibold mb-2 text-error block"> عدم دسترسی </span>
<p class="inline-block text-base">متاسفانه امکان دسترسی برای کاربر شما فراهم نیست</p>
<button pButton outlined severity="danger" class="mt-5 w-32" (click)="logout()">خروج</button>
</div>
</p-card>
</div>
}
}
} @else {
<router-outlet></router-outlet>
}
</div> </div>
} }
@@ -3,13 +3,26 @@ import { PageLoadingComponent } from '@/shared/components/page-loading.component
import { JalaliDateDirective } from '@/shared/directives'; import { JalaliDateDirective } from '@/shared/directives';
import { Component, computed, inject } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
import { Menu } from 'primeng/menu';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { PosStore } from '../pos.store'; import { PosStore } from '../pos.store';
import { PosChooseCardsComponent } from './choose-pos.component';
@Component({ @Component({
selector: 'pos-layout', selector: 'pos-layout',
templateUrl: './layout.component.html', templateUrl: './layout.component.html',
imports: [PageLoadingComponent, RouterOutlet, JalaliDateDirective], imports: [
PageLoadingComponent,
RouterOutlet,
JalaliDateDirective,
Menu,
ButtonDirective,
Card,
PosChooseCardsComponent,
],
}) })
export class PosLayoutComponent { export class PosLayoutComponent {
constructor() {} constructor() {}
@@ -19,8 +32,26 @@ export class PosLayoutComponent {
readonly loading = computed(() => this.store.loading()); readonly loading = computed(() => this.store.loading());
readonly posInfo = computed(() => this.store.entity()); readonly posInfo = computed(() => this.store.entity());
readonly error = computed(() => this.store.error());
readonly userInfo = computed(() => this.authService.currentAccount()); readonly userInfo = computed(() => this.authService.currentAccount());
logout = () => {
this.authService.logout();
};
profileMenuItems: MenuItem[] = [
{
label: 'راهنمای سامانه',
icon: 'pi pi-question-circle',
disabled: true,
},
{
label: 'خروج',
icon: 'pi pi-sign-out',
command: this.logout,
},
];
placeholderLogo = images.placeholders.logo; placeholderLogo = images.placeholders.logo;
now = new Date(); now = new Date();
@@ -29,6 +60,10 @@ export class PosLayoutComponent {
this.store.getData().subscribe(); this.store.getData().subscribe();
} }
onChoosePos() {
this.getData();
}
ngOnInit() { ngOnInit() {
this.getData(); this.getData();
} }
+11
View File
@@ -1,3 +1,5 @@
import ISummary from '@/core/models/summary';
export interface IPosInfoRawResponse { export interface IPosInfoRawResponse {
name: true; name: true;
complex: { complex: {
@@ -16,3 +18,12 @@ export interface IPosInfoRawResponse {
} }
export interface IPosInfoResponse extends IPosInfoRawResponse {} export interface IPosInfoResponse extends IPosInfoRawResponse {}
export interface IPosAccessibleRawResponse extends ISummary {
complex: Complex;
}
export interface IPosAccessibleResponse extends IPosAccessibleRawResponse {}
interface Complex extends ISummary {
business_activity: ISummary;
}
@@ -2,6 +2,7 @@ const baseUrl = '/api/v1/pos';
export const POS_API_ROUTES = { export const POS_API_ROUTES = {
info: () => `${baseUrl}`, info: () => `${baseUrl}`,
accessible: () => `${baseUrl}/accessible`,
goods: () => `${baseUrl}/goods`, goods: () => `${baseUrl}/goods`,
goodCategories: () => `${baseUrl}/good_categories`, goodCategories: () => `${baseUrl}/good_categories`,
submitOrder: () => `${baseUrl}/sales_invoices`, submitOrder: () => `${baseUrl}/sales_invoices`,
@@ -5,7 +5,12 @@ import {
IGoodRawResponse, IGoodRawResponse,
IGoodResponse, IGoodResponse,
} from '@/domains/pos/models/good.io'; } from '@/domains/pos/models/good.io';
import { IPosInfoRawResponse, IPosInfoResponse } from '@/domains/pos/models/pos.io'; import {
IPosAccessibleRawResponse,
IPosAccessibleResponse,
IPosInfoRawResponse,
IPosInfoResponse,
} from '@/domains/pos/models/pos.io';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -21,6 +26,11 @@ export class PosService {
getInfo(): Observable<IPosInfoResponse> { getInfo(): Observable<IPosInfoResponse> {
return this.http.get<IPosInfoRawResponse>(this.apiRoutes.info()); return this.http.get<IPosInfoRawResponse>(this.apiRoutes.info());
} }
getAccessible(): Observable<IListingResponse<IPosAccessibleResponse>> {
return this.http.get<IListingResponse<IPosAccessibleRawResponse>>(this.apiRoutes.accessible());
}
getGoods(searchQuery: string): Observable<IListingResponse<IGoodResponse>> { getGoods(searchQuery: string): Observable<IListingResponse<IGoodResponse>> {
return this.http.get<IListingResponse<IGoodRawResponse>>(this.apiRoutes.goods()); return this.http.get<IListingResponse<IGoodRawResponse>>(this.apiRoutes.goods());
} }
@@ -1,8 +1,10 @@
<div class="flex gap-4 grow overflow-hidden h-full"> <div class="flex gap-4 grow overflow-hidden h-full">
@if (posInfo()) {
<div class="grow h-full overflow-auto"> <div class="grow h-full overflow-auto">
<pos-goods /> <pos-goods />
</div> </div>
<div class="shrink-0 h-full"> <div class="shrink-0 h-full">
<pos-order-section /> <pos-order-section />
</div> </div>
}
</div> </div>
@@ -1,5 +1,6 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; // import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { Component } from '@angular/core'; import { PosStore } from '@/domains/pos/pos.store';
import { Component, computed, inject } from '@angular/core';
import { PosGoodsComponent } from '../components/goods.component'; import { PosGoodsComponent } from '../components/goods.component';
import { PosOrderSectionComponent } from '../components/order/order-section.component'; import { PosOrderSectionComponent } from '../components/order/order-section.component';
@@ -10,4 +11,8 @@ import { PosOrderSectionComponent } from '../components/order/order-section.comp
imports: [PosGoodsComponent, PosOrderSectionComponent], imports: [PosGoodsComponent, PosOrderSectionComponent],
}) })
export class PosLandingComponent {} export class PosLandingComponent {
private readonly posStore = inject(PosStore);
readonly posInfo = computed(() => this.posStore.entity());
}
+15 -15
View File
@@ -1,7 +1,9 @@
import { EntityState, EntityStore } from '@/core/state'; import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
import { HttpErrorResponse } from '@angular/common/http';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
import { finalize, map } from 'rxjs'; import { catchError, finalize, map } from 'rxjs';
import { COOKIE_KEYS } from 'src/assets/constants';
import { IPosInfoResponse } from './models/pos.io'; import { IPosInfoResponse } from './models/pos.io';
import { PosService } from './modules/landing/services/main.service'; import { PosService } from './modules/landing/services/main.service';
@@ -17,12 +19,8 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
constructor(private readonly cookieService: CookieService) { constructor(private readonly cookieService: CookieService) {
super({ super({
loading: false, ...defaultBaseStateData,
error: null, posId: cookieService.get(COOKIE_KEYS.POS_ID),
entity: null,
initialized: false,
isRefreshing: false,
posId: cookieService.get('posId'),
}); });
} }
@@ -35,9 +33,15 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError((err: HttpErrorResponse) => {
this.setError(err);
// if (err.status === 403) debugger;
throw err;
}),
map((entity: IPosInfoResponse) => { map((entity: IPosInfoResponse) => {
if (entity) { if (entity) {
this.patchState({ entity: entity }); this.setEntity(entity);
} }
return entity; return entity;
}), }),
@@ -46,12 +50,8 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
override reset(): void { override reset(): void {
this.setState({ this.setState({
loading: false, ...defaultBaseStateData,
error: null, posId: this.cookieService.get(COOKIE_KEYS.POS_ID),
entity: null,
initialized: false,
isRefreshing: false,
posId: this.cookieService.get('posId'),
}); });
} }
} }
@@ -14,7 +14,7 @@
hint="مدت زمان لایسنس‌ها ۱ ساله هستند." hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
/> />
<!-- <uikit-datepicker label="تاریخ انقضای لایسنس" [control]="form.controls.expires_at" name="expires_at" /> --> <!-- <uikit-datepicker label="تاریخ انقضای لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" /> <app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form> </form>
</p-dialog> </p-dialog>
@@ -16,12 +16,13 @@ export class SuperAdminConsumerPosLayoutComponent {
readonly loading = computed(() => this.store.loading()); readonly loading = computed(() => this.store.loading());
readonly pageParams = computed(() => pageParamsUtils(this.route)); readonly pageParams = computed(() => pageParamsUtils(this.route));
readonly consumerId = computed(() => this.pageParams()['consumerId']!);
readonly businessId = computed(() => this.pageParams()['businessActivityId']!); readonly businessId = computed(() => this.pageParams()['businessActivityId']!);
readonly complexId = computed(() => this.pageParams()['complexId']!); readonly complexId = computed(() => this.pageParams()['complexId']!);
readonly posId = computed(() => this.pageParams()['posId']!); readonly posId = computed(() => this.pageParams()['posId']!);
getData() { getData() {
this.store.getData(this.businessId(), this.complexId(), this.posId()); this.store.getData(this.consumerId(), this.businessId(), this.complexId(), this.posId());
} }
ngOnInit() { ngOnInit() {
@@ -1,10 +1,14 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../constants/routes/businessActivities';
import { IBusinessActivityResponse } from '../models'; import { IBusinessActivityResponse } from '../models';
import { AdminConsumerBusinessActivitiesService } from '../services/businessActivities.service'; import { AdminConsumerBusinessActivitiesService } from '../services/businessActivities.service';
interface BusinessActivityState extends EntityState<IBusinessActivityResponse> {} interface BusinessActivityState extends EntityState<IBusinessActivityResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -21,26 +25,47 @@ export class BusinessActivityStore extends EntityStore<
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
getData(consumerId: string, business_id: string) { breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(consumerId: string, businessId: string) {
this.patchState({
breadcrumbItems: [
{
...superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta,
routerLink:
superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.pagePath!(
consumerId,
),
},
{
title: this.entity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta
.pagePath!(consumerId, businessId),
},
],
});
}
getData(consumerId: string, businessId: string) {
this.patchState({ loading: true }); this.patchState({ loading: true });
this.service this.service
.getSingle(consumerId, business_id) .getSingle(consumerId, businessId)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
this.setBreadcrumb(consumerId, businessId);
}); });
} }
@@ -51,6 +76,7 @@ export class BusinessActivityStore extends EntityStore<
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
} }
@@ -1,10 +1,14 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { superAdminConsumerComplexesNamedRoutes } from '../constants/routes/complexes';
import { IComplexResponse } from '../models'; import { IComplexResponse } from '../models';
import { AdminComplexesService } from '../services/complexes.service'; import { AdminComplexesService } from '../services/complexes.service';
interface ComplexState extends EntityState<IComplexResponse> {} interface ComplexState extends EntityState<IComplexResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -18,6 +22,31 @@ export class ComplexStore extends EntityStore<IComplexResponse, ComplexState> {
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
});
}
breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(consumerId: string, businessId: string, complexId: string) {
this.patchState({
breadcrumbItems: [
{
...superAdminConsumerComplexesNamedRoutes.complexes.meta,
routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
consumerId,
businessId,
),
},
{
title: this.entity()?.name,
routerLink: superAdminConsumerComplexesNamedRoutes.complex.meta.pagePath!(
consumerId,
businessId,
complexId,
),
},
],
}); });
} }
@@ -29,15 +58,14 @@ export class ComplexStore extends EntityStore<IComplexResponse, ComplexState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
this.setBreadcrumb(consumerId, businessId, complexId);
}); });
} }
@@ -48,6 +76,7 @@ export class ComplexStore extends EntityStore<IComplexResponse, ComplexState> {
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
} }
@@ -1,10 +1,14 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { superAdminConsumersNamedRoutes } from '../constants';
import { IConsumerResponse } from '../models'; import { IConsumerResponse } from '../models';
import { ConsumersService } from '../services/main.service'; import { ConsumersService } from '../services/main.service';
interface ConsumerState extends EntityState<IConsumerResponse> {} interface ConsumerState extends EntityState<IConsumerResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -18,26 +22,43 @@ export class ConsumerStore extends EntityStore<IConsumerResponse, ConsumerState>
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
getData(partnerId: string) { breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(consumerId: string) {
this.patchState({
breadcrumbItems: [
{
...superAdminConsumersNamedRoutes.consumers.meta,
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(consumerId),
},
],
});
}
getData(consumerId: string) {
this.patchState({ loading: true }); this.patchState({ loading: true });
this.service this.service
.getSingle(partnerId) .getSingle(consumerId)
.pipe( .pipe(
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
this.setBreadcrumb(consumerId);
}); });
} }
} }
@@ -1,10 +1,14 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { superAdminConsumerPosesNamedRoutes } from '../constants/routes/poses';
import { IPosResponse } from '../models'; import { IPosResponse } from '../models';
import { AdminPosesService } from '../services/poses.service'; import { AdminPosesService } from '../services/poses.service';
interface PosState extends EntityState<IPosResponse> {} interface PosState extends EntityState<IPosResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -18,10 +22,38 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
getData(businessId: string, complexId: string, posId: string) { breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(consumerId: string, businessId: string, complexId: string, posId: string) {
this.patchState({
breadcrumbItems: [
{
title: superAdminConsumerPosesNamedRoutes.poses.meta.title,
routerLink: superAdminConsumerPosesNamedRoutes.poses.meta.pagePath!(
consumerId,
businessId,
complexId,
),
},
{
title: this.entity()?.name,
routerLink: superAdminConsumerPosesNamedRoutes.pos.meta.pagePath!(
consumerId,
businessId,
complexId,
posId,
),
},
],
});
}
getData(consumerId: string, businessId: string, complexId: string, posId: string) {
this.patchState({ loading: true }); this.patchState({ loading: true });
this.service this.service
.getSingle(businessId, complexId, posId) .getSingle(businessId, complexId, posId)
@@ -29,15 +61,14 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
this.setBreadcrumb(consumerId, businessId, complexId, posId);
}); });
} }
@@ -48,6 +79,7 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
entity: null, entity: null,
initialized: false, initialized: false,
isRefreshing: false, isRefreshing: false,
breadcrumbItems: [],
}); });
} }
} }
@@ -3,7 +3,6 @@ import { BreadcrumbService } from '@/core/services';
import { Component, inject, signal } from '@angular/core'; import { Component, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerAccountListComponent } from '../../components/accounts/list.component'; import { ConsumerAccountListComponent } from '../../components/accounts/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerAccountsNamedRoutes } from '../../constants/routes/accounts'; import { superAdminConsumerAccountsNamedRoutes } from '../../constants/routes/accounts';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -25,14 +24,7 @@ export class ConsumerAccountsComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.store.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta,
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.store.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{ {
title: superAdminConsumerAccountsNamedRoutes.accounts.meta.title, title: superAdminConsumerAccountsNamedRoutes.accounts.meta.title,
}, },
@@ -4,7 +4,6 @@ import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerBusinessActivitiesComponent } from '../../components/businessActivities/list.component'; import { ConsumerBusinessActivitiesComponent } from '../../components/businessActivities/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities'; import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -27,14 +26,7 @@ export class SuperAdminUserBusinessActivitiesComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta,
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{ {
title: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title, title: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title,
}, },
@@ -6,8 +6,6 @@ import { ActivatedRoute } from '@angular/router';
import { ConsumerBusinessActivitiesFormComponent } from '../../components/businessActivities/form.component'; import { ConsumerBusinessActivitiesFormComponent } from '../../components/businessActivities/form.component';
import { ConsumerComplexesComponent } from '../../components/complexes/list.component'; import { ConsumerComplexesComponent } from '../../components/complexes/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -50,26 +48,8 @@ export class SuperAdminUserBusinessActivityComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta, ...this.store.breadcrumbItems(),
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(this.consumerId()),
},
{
...superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta
.pagePath!(this.consumerId()),
},
{
title: this.businessActivity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
]); ]);
} }
} }
@@ -4,8 +4,6 @@ import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerComplexesComponent } from '../../components/complexes/list.component'; import { ConsumerComplexesComponent } from '../../components/complexes/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { superAdminConsumerComplexesNamedRoutes } from '../../constants/routes/complexes'; import { superAdminConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -31,26 +29,8 @@ export class SuperAdminUserComplexesComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta, ...this.businessStore.breadcrumbItems(),
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{
title: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta
.pagePath!(this.consumerId()),
},
{
title: this.businessStore.entity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{ {
title: superAdminConsumerComplexesNamedRoutes.complexes.meta.title, title: superAdminConsumerComplexesNamedRoutes.complexes.meta.title,
routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!( routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
@@ -5,9 +5,6 @@ import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerComplexFormComponent } from '../../components/complexes/form.component'; import { ConsumerComplexFormComponent } from '../../components/complexes/form.component';
import { ConsumerPosesComponent } from '../../components/poses/list.component'; import { ConsumerPosesComponent } from '../../components/poses/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { superAdminConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store'; import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -54,41 +51,9 @@ export class SuperAdminUserComplexComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta, ...this.businessStore.breadcrumbItems(),
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(), ...this.store.breadcrumbItems(),
},
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{
...superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta
.pagePath!(this.consumerId()),
},
{
title: this.businessStore.entity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
...superAdminConsumerComplexesNamedRoutes.complexes.meta,
routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
title: this.complex()?.name,
routerLink: superAdminConsumerComplexesNamedRoutes.complex.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
),
},
]); ]);
} }
} }
@@ -4,9 +4,6 @@ import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerPosesComponent } from '../../components/poses/list.component'; import { ConsumerPosesComponent } from '../../components/poses/list.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { superAdminConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { superAdminConsumerPosesNamedRoutes } from '../../constants/routes/poses'; import { superAdminConsumerPosesNamedRoutes } from '../../constants/routes/poses';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store'; import { ComplexStore } from '../../store/complex.store';
@@ -35,43 +32,9 @@ export class SuperAdminUserPosesComponent {
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta, ...this.businessStore.breadcrumbItems(),
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(), ...this.complexStore.breadcrumbItems(),
},
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{
title: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta
.pagePath!(this.consumerId()),
},
{
title: this.businessStore.entity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
title: superAdminConsumerComplexesNamedRoutes.complexes.meta.title,
routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
title: this.complexStore.entity()?.name,
routerLink: superAdminConsumerComplexesNamedRoutes.complex.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
),
},
{ {
title: superAdminConsumerPosesNamedRoutes.poses.meta.title, title: superAdminConsumerPosesNamedRoutes.poses.meta.title,
routerLink: superAdminConsumerPosesNamedRoutes.poses.meta.pagePath!( routerLink: superAdminConsumerPosesNamedRoutes.poses.meta.pagePath!(
@@ -4,10 +4,6 @@ import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, effect, inject, signal } from '@angular/core'; import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerPosFormComponent } from '../../components/poses/form.component'; import { ConsumerPosFormComponent } from '../../components/poses/form.component';
import { superAdminConsumersNamedRoutes } from '../../constants';
import { superAdminConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { superAdminConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { superAdminConsumerPosesNamedRoutes } from '../../constants/routes/poses';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store'; import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store'; import { ConsumerStore } from '../../store/consumer.store';
@@ -46,72 +42,15 @@ export class SuperAdminUserPosComponent {
readonly pos = computed(() => this.store.entity()); readonly pos = computed(() => this.store.entity());
getData() { getData() {
this.store.getData(this.businessId(), this.complexId(), this.posId()); this.store.getData(this.consumerId(), this.businessId(), this.complexId(), this.posId());
}
toPosLanding() {
// @ts-ignore
window.cookieStore.set('posId', this.posId());
// window.open('pos')
} }
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems([
{ ...this.consumerStore.breadcrumbItems(),
...superAdminConsumersNamedRoutes.consumers.meta, ...this.businessStore.breadcrumbItems(),
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(), ...this.complexStore.breadcrumbItems(),
}, ...this.store.breadcrumbItems(),
{
title: this.consumerStore.entity()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
{
title: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivities.meta
.pagePath!(this.consumerId()),
},
{
title: this.businessStore.entity()?.name,
routerLink: superAdminConsumerBusinessActivitiesNamedRoutes.businessActivity.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
title: superAdminConsumerComplexesNamedRoutes.complexes.meta.title,
routerLink: superAdminConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
{
title: this.complexStore.entity()?.name,
routerLink: superAdminConsumerComplexesNamedRoutes.complex.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
),
},
{
title: superAdminConsumerPosesNamedRoutes.poses.meta.title,
routerLink: superAdminConsumerPosesNamedRoutes.poses.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
),
},
{
title: this.pos()?.name,
routerLink: superAdminConsumerPosesNamedRoutes.pos.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
this.posId(),
),
},
]); ]);
} }
} }
@@ -9,7 +9,6 @@ import { ConsumerAccountListComponent } from '../components/accounts/list.compon
import { ConsumerBusinessActivitiesComponent } from '../components/businessActivities/list.component'; import { ConsumerBusinessActivitiesComponent } from '../components/businessActivities/list.component';
import { ConsumerUserFormComponent } from '../components/form.component'; import { ConsumerUserFormComponent } from '../components/form.component';
import { ConsumerLicenseFormComponent } from '../components/licenses/form.component'; import { ConsumerLicenseFormComponent } from '../components/licenses/form.component';
import { superAdminConsumersNamedRoutes } from '../constants';
import { ConsumerStore } from '../store/consumer.store'; import { ConsumerStore } from '../store/consumer.store';
@Component({ @Component({
@@ -62,16 +61,7 @@ export class ConsumerComponent {
} }
setBreadcrumb() { setBreadcrumb() {
this.breadcrumbService.setItems([ this.breadcrumbService.setItems(this.store.breadcrumbItems());
{
...superAdminConsumersNamedRoutes.consumers.meta,
routerLink: superAdminConsumersNamedRoutes.consumers.meta.pagePath!(),
},
{
title: this.consumer()?.fullname,
routerLink: superAdminConsumersNamedRoutes.consumer.meta.pagePath!(this.consumerId()),
},
]);
} }
openLicenseForm() { openLicenseForm() {
@@ -1,6 +1,6 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { IGuildResponse } from '../models'; import { IGuildResponse } from '../models';
import { GuildsService } from '../services/main.service'; import { GuildsService } from '../services/main.service';
@@ -29,11 +29,9 @@ export class GuildStore extends EntityStore<IGuildResponse, GuildState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -8,8 +8,12 @@
</div> </div>
</app-card-data> </app-card-data>
<admin-guild-good-categories-list [guildId]="guildId()" /> <div class="max-h-[500px] flex">
<admin-guild-goods-list [guildId]="guildId()" /> <admin-guild-good-categories-list [guildId]="guildId()" class="w-full" />
</div>
<div class="max-h-[500px] flex">
<admin-guild-goods-list [guildId]="guildId()" class="w-full" />
</div>
<admin-guild-form <admin-guild-form
[(visible)]="editMode" [(visible)]="editMode"
@@ -1,6 +1,6 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { IPartnerResponse } from '../models'; import { IPartnerResponse } from '../models';
import { PartnersService } from '../services/main.service'; import { PartnersService } from '../services/main.service';
@@ -29,11 +29,9 @@ export class PartnerStore extends EntityStore<IPartnerResponse, PartnerState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -1,6 +1,6 @@
import { EntityState, EntityStore } from '@/core/state'; import { EntityState, EntityStore } from '@/core/state';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { catchError, finalize, throwError } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { IUserResponse } from '../models'; import { IUserResponse } from '../models';
import { UsersService } from '../services/main.service'; import { UsersService } from '../services/main.service';
@@ -29,11 +29,9 @@ export class UserStore extends EntityStore<IUserResponse, UserState> {
finalize(() => { finalize(() => {
this.patchState({ loading: false }); this.patchState({ loading: false });
}), }),
catchError(() => { catchError((error) => {
this.patchState({ this.setError(error);
error: '', throw error;
});
return throwError('');
}), }),
) )
.subscribe((entity) => { .subscribe((entity) => {
@@ -3,14 +3,14 @@
<ng-container [ngTemplateOutlet]="topBarMoreAction"></ng-container> <ng-container [ngTemplateOutlet]="topBarMoreAction"></ng-container>
</app-topbar> </app-topbar>
@if (fullLoading) { @if (fullLoading) {
<div class="flex justify-center align-items-center h-svh items-center"> <div class="flex justify-center align-items-center grow items-center">
<p-progressSpinner></p-progressSpinner> <p-progressSpinner></p-progressSpinner>
</div> </div>
} @else { } @else {
@if (showMenu()) { @if (showMenu()) {
<app-sidebar></app-sidebar> <app-sidebar></app-sidebar>
} }
<div [class]="`layout-main-container ${!showMenu() ? 'hideMenu' : ''}`"> <div [class]="`layout-main-container ${!showMenu() ? 'hideMenu' : ''} grow`">
<div class="layout-main flex flex-col gap-4"> <div class="layout-main flex flex-col gap-4">
@if (showBreadcrumb) { @if (showBreadcrumb) {
<app-breadcrumb class="rounded-md overflow-hidden shrink-0" /> <app-breadcrumb class="rounded-md overflow-hidden shrink-0" />
@@ -1,4 +1,4 @@
<div class="layout-topbar"> <div class="layout-topbar shrink-0">
<div class="layout-topbar-logo-container"> <div class="layout-topbar-logo-container">
@if (showMenu) { @if (showMenu) {
<button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()"> <button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
@@ -1,7 +1,7 @@
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { IPaginatedResponse } from '@/core/models/service.model'; import { IListingResponse, IPaginatedResponse } from '@/core/models/service.model';
import { Component, signal } from '@angular/core'; import { Component, signal } from '@angular/core';
import { Observable } from 'rxjs'; import { catchError, finalize, Observable } from 'rxjs';
import { IColumn } from '../components/pageDataList/page-data-list.component'; import { IColumn } from '../components/pageDataList/page-data-list.component';
@Component({ @Component({
@@ -28,9 +28,20 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
getData() { getData() {
this.loading.set(true); this.loading.set(true);
this.getDataRequest(this.requestPayload())?.subscribe((res) => { this.items.set([]);
this.getDataRequest(this.requestPayload())
?.pipe(
finalize(() => {
console.log('first');
this.loading.set(false); this.loading.set(false);
this.items.set(res.data); }),
catchError((error) => {
throw error;
}),
)
.subscribe((res) => {
this.items.set(res.data ?? []);
}); });
} }
@@ -42,7 +53,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
abstract getDataRequest( abstract getDataRequest(
payload: Maybe<RequestParams>, payload: Maybe<RequestParams>,
): Observable<IPaginatedResponse<ResponseModel>> | void; ): Observable<IPaginatedResponse<ResponseModel> | IListingResponse<ResponseModel>> | void;
abstract setColumns(): void; abstract setColumns(): void;
@@ -17,7 +17,7 @@ export abstract class AbstractSelectComponent<T = ISelectItem, serviceResponse =
@Input() canInsert: boolean = false; @Input() canInsert: boolean = false;
@Input() showLabel: boolean = true; @Input() showLabel: boolean = true;
@Input() showErrors: boolean = true; @Input() showErrors: boolean = true;
@Input() showClear: boolean = true; @Input() showClear: boolean = false;
@Input() isFullDataOptionValue: boolean = false; @Input() isFullDataOptionValue: boolean = false;
@Input() optionValue = 'id'; @Input() optionValue = 'id';
@Output() onChange = new EventEmitter<Maybe<T>>(); @Output() onChange = new EventEmitter<Maybe<T>>();
+1 -1
View File
@@ -14,7 +14,7 @@ import { EnumsService } from './services';
imports: [UikitFieldComponent, Select, ReactiveFormsModule], imports: [UikitFieldComponent, Select, ReactiveFormsModule],
}) })
export class EnumSelectComponent extends AbstractSelectComponent<string, IApiEnumResponse[]> { export class EnumSelectComponent extends AbstractSelectComponent<string, IApiEnumResponse[]> {
@Input() type!: TEnumApi; @Input({ required: true }) type!: TEnumApi;
@Input() customLabel?: string; @Input() customLabel?: string;
private readonly service = inject(EnumsService); private readonly service = inject(EnumsService);
@@ -16,8 +16,9 @@
<ng-container [ngTemplateOutlet]="caption"> <ng-container [ngTemplateOutlet]="caption">
<div class="flex justify-between items-center gap-4"> <div class="flex justify-between items-center gap-4">
<h5 class="font-bold">{{ pageTitle }}</h5> <h5 class="font-bold">{{ pageTitle }}</h5>
@if (showAdd || filter) { @if (showAdd || filter || moreActions) {
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<ng-container [ngTemplateOutlet]="moreActions" />
@if (filter) { @if (filter) {
<p-button <p-button
label="فیلتر" label="فیلتر"
@@ -35,7 +35,7 @@ export interface IColumn<T = any> {
selector: 'app-page-data-list', selector: 'app-page-data-list',
templateUrl: './page-data-list.component.html', templateUrl: './page-data-list.component.html',
host: { host: {
class: 'block w-full', class: 'block w-full h-full overflow-hidden',
}, },
imports: [ imports: [
CommonModule, CommonModule,
@@ -77,6 +77,7 @@ export class PageDataListComponent<I> {
@ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null; @ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null;
@ContentChild('caption', { static: true }) caption!: TemplateRef<any> | null; @ContentChild('caption', { static: true }) caption!: TemplateRef<any> | null;
@ContentChild('moreActions', { static: true }) moreActions!: TemplateRef<any> | null;
@Output() onEdit = new EventEmitter<I>(); @Output() onEdit = new EventEmitter<I>();
@Output() onDelete = new EventEmitter<I>(); @Output() onDelete = new EventEmitter<I>();
+4
View File
@@ -0,0 +1,4 @@
export enum COOKIE_KEYS {
POS_ID = 'posId',
ACCESS_TOKEN = 'accessToken',
}
+1
View File
@@ -1,4 +1,5 @@
export * from './accountTypes.const'; export * from './accountTypes.const';
export * from './cookieKeys.const';
export * from './localStorageKeys.const'; export * from './localStorageKeys.const';
export * from './roleTypes.const'; export * from './roleTypes.const';
export * from './userRoles.const'; export * from './userRoles.const';
+2
View File
@@ -15,6 +15,8 @@ a {
.layout-wrapper { .layout-wrapper {
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
} }
html { html {
+1 -1
View File
@@ -4,7 +4,7 @@
// height: 100vh; // height: 100vh;
// overflow: hidden; // overflow: hidden;
justify-content: space-between; justify-content: space-between;
padding: 6rem 1rem 2rem 2rem; padding: 2rem 1rem 2rem 2rem;
transition: margin-inline-start var(--layout-section-transition-duration); transition: margin-inline-start var(--layout-section-transition-duration);
&.hideMenu { &.hideMenu {
margin: 0 !important; margin: 0 !important;
-1
View File
@@ -1,7 +1,6 @@
@use "mixins" as *; @use "mixins" as *;
.layout-topbar { .layout-topbar {
position: fixed;
height: 4rem; height: 4rem;
z-index: 997; z-index: 997;
right: 0; right: 0;
+1
View File
@@ -3,6 +3,7 @@ export const environment = {
production: false, production: false,
// apiBaseUrl: 'http://194.59.214.243:5002', // apiBaseUrl: 'http://194.59.214.243:5002',
apiBaseUrl: 'http://localhost:5002', apiBaseUrl: 'http://localhost:5002',
// host: 'http://194.59.214.243',
host: 'localhost', host: 'localhost',
port: 5000, port: 5000,
enableLogging: true, enableLogging: true,
+1 -1
View File
@@ -9,7 +9,7 @@
<link rel="manifest" href="/favicon/site.webmanifest" /> <link rel="manifest" href="/favicon/site.webmanifest" />
</head> </head>
<body> <body class="min-h-svh overflow-auto">
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>