ui update, init to consumer statistics and manage pos user types
This commit is contained in:
@@ -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">
|
||||
<img [src]="placeholderLogo" alt="Logo" class="w-full h-full object-cover rounded-md bg-surface-card" />
|
||||
</div>
|
||||
<span class="text-lg font-bold">{{ posInfo()?.name }} - فروشگاه {{ posInfo()?.complex?.name }}</span>
|
||||
@if (posInfo()) {
|
||||
<span class="text-lg font-bold">{{ posInfo()?.name }} - فروشگاه {{ posInfo()?.complex?.name }}</span>
|
||||
}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="bg-surface-card py-1 px-3 rounded-md shadow-sm">
|
||||
<span class="text-base"> {{ userInfo()?.username }} </span>
|
||||
<div class="">
|
||||
<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>
|
||||
<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>
|
||||
}
|
||||
|
||||
@@ -3,13 +3,26 @@ import { PageLoadingComponent } from '@/shared/components/page-loading.component
|
||||
import { JalaliDateDirective } from '@/shared/directives';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
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 { PosStore } from '../pos.store';
|
||||
import { PosChooseCardsComponent } from './choose-pos.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-layout',
|
||||
templateUrl: './layout.component.html',
|
||||
imports: [PageLoadingComponent, RouterOutlet, JalaliDateDirective],
|
||||
imports: [
|
||||
PageLoadingComponent,
|
||||
RouterOutlet,
|
||||
JalaliDateDirective,
|
||||
Menu,
|
||||
ButtonDirective,
|
||||
Card,
|
||||
PosChooseCardsComponent,
|
||||
],
|
||||
})
|
||||
export class PosLayoutComponent {
|
||||
constructor() {}
|
||||
@@ -19,8 +32,26 @@ export class PosLayoutComponent {
|
||||
|
||||
readonly loading = computed(() => this.store.loading());
|
||||
readonly posInfo = computed(() => this.store.entity());
|
||||
readonly error = computed(() => this.store.error());
|
||||
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;
|
||||
|
||||
now = new Date();
|
||||
@@ -29,6 +60,10 @@ export class PosLayoutComponent {
|
||||
this.store.getData().subscribe();
|
||||
}
|
||||
|
||||
onChoosePos() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ISummary from '@/core/models/summary';
|
||||
|
||||
export interface IPosInfoRawResponse {
|
||||
name: true;
|
||||
complex: {
|
||||
@@ -16,3 +18,12 @@ export interface 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 = {
|
||||
info: () => `${baseUrl}`,
|
||||
accessible: () => `${baseUrl}/accessible`,
|
||||
goods: () => `${baseUrl}/goods`,
|
||||
goodCategories: () => `${baseUrl}/good_categories`,
|
||||
submitOrder: () => `${baseUrl}/sales_invoices`,
|
||||
|
||||
@@ -5,7 +5,12 @@ import {
|
||||
IGoodRawResponse,
|
||||
IGoodResponse,
|
||||
} 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 { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -21,6 +26,11 @@ export class PosService {
|
||||
getInfo(): Observable<IPosInfoResponse> {
|
||||
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>> {
|
||||
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="grow h-full overflow-auto">
|
||||
<pos-goods />
|
||||
</div>
|
||||
<div class="shrink-0 h-full">
|
||||
<pos-order-section />
|
||||
</div>
|
||||
@if (posInfo()) {
|
||||
<div class="grow h-full overflow-auto">
|
||||
<pos-goods />
|
||||
</div>
|
||||
<div class="shrink-0 h-full">
|
||||
<pos-order-section />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 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 { PosOrderSectionComponent } from '../components/order/order-section.component';
|
||||
|
||||
@@ -10,4 +11,8 @@ import { PosOrderSectionComponent } from '../components/order/order-section.comp
|
||||
|
||||
imports: [PosGoodsComponent, PosOrderSectionComponent],
|
||||
})
|
||||
export class PosLandingComponent {}
|
||||
export class PosLandingComponent {
|
||||
private readonly posStore = inject(PosStore);
|
||||
|
||||
readonly posInfo = computed(() => this.posStore.entity());
|
||||
}
|
||||
|
||||
@@ -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 { 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 { PosService } from './modules/landing/services/main.service';
|
||||
|
||||
@@ -17,12 +19,8 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
|
||||
|
||||
constructor(private readonly cookieService: CookieService) {
|
||||
super({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
posId: cookieService.get('posId'),
|
||||
...defaultBaseStateData,
|
||||
posId: cookieService.get(COOKIE_KEYS.POS_ID),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,9 +33,15 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
|
||||
this.patchState({ loading: false });
|
||||
}),
|
||||
|
||||
catchError((err: HttpErrorResponse) => {
|
||||
this.setError(err);
|
||||
// if (err.status === 403) debugger;
|
||||
throw err;
|
||||
}),
|
||||
|
||||
map((entity: IPosInfoResponse) => {
|
||||
if (entity) {
|
||||
this.patchState({ entity: entity });
|
||||
this.setEntity(entity);
|
||||
}
|
||||
return entity;
|
||||
}),
|
||||
@@ -46,12 +50,8 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
entity: null,
|
||||
initialized: false,
|
||||
isRefreshing: false,
|
||||
posId: this.cookieService.get('posId'),
|
||||
...defaultBaseStateData,
|
||||
posId: this.cookieService.get(COOKIE_KEYS.POS_ID),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user