feat(layout): enhance topbar with customizable templates and full-page support

- Updated app.layout.component.html to include start, center, and end templates for the topbar.
- Modified app.layout.component.ts to manage new template references and added isFullPage getter.
- Refactored app.topbar.component.html to utilize new templates and improved layout structure.
- Enhanced app.topbar.component.ts to accept new input properties for templates.
- Updated layout.service.ts to manage full-page state and new topbar slots.
- Added new payment bridge services for POS functionality.
- Introduced greater validator for form validation.
- Improved dialog component to support mobile drawer and responsive design.
- Updated styles for better mobile support and layout adjustments.
- Added new main menu sidebar for POS with dynamic content.
This commit is contained in:
2026-04-30 16:27:42 +03:30
parent c89d4027d6
commit 8104f1b7a7
56 changed files with 1130 additions and 434 deletions
@@ -1,8 +1,31 @@
<ng-template #topbarStart>
<p-button (click)="toggleMenu()" icon="pi pi-bars" outlined size="large" />
</ng-template>
<ng-template #topbarCenter>
@if (posInfo()) {
<div class="flex flex-col items-center justify-center gap-2">
<div class="w-8 h-8">
<img
[src]="posInfo()?.partner?.logo_url ?? placeholderLogo"
alt="Logo"
class="w-full h-full object-cover rounded-md bg-surface-card"
/>
</div>
<span class="text-base font-semibold">{{ posInfo()?.partner?.name }}</span>
</div>
}
</ng-template>
<ng-template #topbarEnd>
<p-button (click)="toggleMenu()" icon="pi pi-user" outlined size="large" />
</ng-template>
@if (loading()) {
<shared-page-loading />
} @else {
<div class="w-svw h-svh bg-surface-ground flex flex-col gap-4 p-4">
<div class="w-full flex items-center gap-4 shrink-0 justify-between">
<!-- <div class="w-svw h-svh bg-surface-ground flex flex-col gap-4 p-4"> -->
<!-- <div class="w-full flex items-center gap-4 shrink-0 justify-between">
<div class="flex gap-2 items-center">
@if (posInfo()) {
<div class="w-10 h-10">
@@ -24,27 +47,28 @@
</button>
</div>
</div>
</div>
@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>
}
</div> -->
@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>
} @else {
<pos-main-menu-sidebar [(visible)]="mainMenuVisible" />
<router-outlet></router-outlet>
}
<!-- </div> -->
}
@@ -1,15 +1,25 @@
import { AuthService } from '@/core';
import { LayoutService } from '@/layout/service/layout.service';
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { JalaliDateDirective } from '@/shared/directives';
import { Component, computed, inject } from '@angular/core';
import {
AfterViewInit,
Component,
TemplateRef,
ViewChild,
computed,
inject,
signal,
} from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { ButtonDirective } from 'primeng/button';
import { Button, ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
import { Menu } from 'primeng/menu';
import images from 'src/assets/images';
import { PosInfoStore, PosProfileStore } from '../store';
import { PosChooseCardsComponent } from './choose-pos.component';
import { PosMainMenuSidebarComponent } from './mainMenuSidebar/main-menu-sidebar.component';
@Component({
selector: 'pos-layout',
@@ -22,14 +32,22 @@ import { PosChooseCardsComponent } from './choose-pos.component';
ButtonDirective,
Card,
PosChooseCardsComponent,
Button,
PosMainMenuSidebarComponent,
],
})
export class PosLayoutComponent {
export class PosLayoutComponent implements AfterViewInit {
constructor() {}
@ViewChild('topbarStart', { static: true }) topbarStart!: TemplateRef<any>;
@ViewChild('topbarCenter', { static: true }) topbarCenter!: TemplateRef<any>;
@ViewChild('topbarEnd', { static: true }) topbarEnd!: TemplateRef<any>;
private readonly posProfileStore = inject(PosProfileStore);
private readonly posInfoStore = inject(PosInfoStore);
private readonly authService = inject(AuthService);
private readonly layoutService = inject(LayoutService);
mainMenuVisible = signal(false);
readonly posProfileLoading = computed(() => this.posProfileStore.loading());
readonly posProfile = computed(() => this.posProfileStore.entity());
@@ -71,11 +89,28 @@ export class PosLayoutComponent {
});
}
toggleMenu() {
this.mainMenuVisible.update((v) => !v);
}
onChoosePos() {
this.getData();
}
ngOnInit() {
this.layoutService.changeIsFullPage(true);
this.getData();
}
ngAfterViewInit() {
this.layoutService.setTopbarStartSlot(this.topbarStart);
this.layoutService.setTopbarCenterSlot(this.topbarCenter);
this.layoutService.setTopbarEndSlot(this.topbarEnd);
}
ngOnDestroy() {
this.layoutService.setTopbarStartSlot(null);
this.layoutService.setTopbarCenterSlot(null);
this.layoutService.setTopbarEndSlot(null);
}
}
@@ -0,0 +1,67 @@
<p-drawer #drawerRef position="right" [(visible)]="visible">
<ng-template #headless>
<div class="flex flex-col h-full">
<div class="flex items-center justify-between px-6 pt-4 shrink-0">
<div class="flex flex-col gap-2">
<span class="font-semibold text-xl text-primary">{{ posInfo()?.name }}</span>
<span class="font-medium text-md text-muted-color">
{{ posInfo()?.businessActivity?.name }} - {{ posInfo()?.complex?.name }}
</span>
</div>
<span>
<p-button type="button" (click)="drawerRef.close($event)" icon="pi pi-times" outlined="true"></p-button>
</span>
</div>
<hr class="mt-4 mx-4 border-b border-0 border-surface" />
<div class="overflow-y-auto">
<ul class="list-none p-4 m-0">
<li>
<a
pRipple
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">مشاهده‌ی فاکتورها</span>
</a>
</li>
<li>
<a
pRipple
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">فروش کالا</span>
</a>
</li>
<li>
<a
pRipple
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">درباره‌ی سیستم</span>
</a>
</li>
<li>
<a
pRipple
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">ارتباط با پشتیبانی</span>
</a>
</li>
</ul>
</div>
<div class="mt-auto">
<hr class="mb-4 mx-4 border-t border-0 border-surface" />
@if (isPwaBuild) {
<div class="px-6 pb-6 text-sm text-muted-color">
نسخه برنامه : <span class="font-semibold">{{ appVersion }}</span>
</div>
}
</div>
</div>
</ng-template>
</p-drawer>
@@ -0,0 +1,51 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { Component, computed, inject } from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { Button } from 'primeng/button';
import { Drawer } from 'primeng/drawer';
import { Ripple } from 'primeng/ripple';
import { filter } from 'rxjs';
import { PosInfoStore } from '../../store';
@Component({
selector: 'pos-main-menu-sidebar',
templateUrl: './main-menu-sidebar.component.html',
imports: [Drawer, Button, Ripple],
})
export class PosMainMenuSidebarComponent extends AbstractDialog {
private readonly posInfoStore = inject(PosInfoStore);
private readonly swUpdate = inject(SwUpdate);
readonly posInfo = computed(() => this.posInfoStore.entity());
appVersion = '0.0.0';
isPwaBuild = false;
readonly posName = computed(() => {
if (this.posInfo()) {
const { name, businessActivity, complex } = this.posInfo()!;
return `${name} (${businessActivity.name} - ${complex.name})`;
}
return '';
});
ngOnInit() {
this.isPwaBuild = this.swUpdate.isEnabled;
if (!this.isPwaBuild) return;
fetch('/ngsw.json')
.then((res) => res.json())
.then((data: { appData?: { appVersion?: string } }) => {
this.appVersion = data?.appData?.appVersion || this.appVersion;
})
.catch(() => {});
this.swUpdate.versionUpdates
.pipe(filter((event): event is VersionReadyEvent => event.type === 'VERSION_READY'))
.subscribe((event) => {
const appData = event.latestVersion.appData as { appVersion?: string } | undefined;
this.appVersion = appData?.appVersion || this.appVersion;
});
this.swUpdate.checkForUpdate().catch(() => {});
}
}
+27 -7
View File
@@ -2,13 +2,11 @@ import ISummary from '@/core/models/summary';
export interface IPosInfoRawResponse {
name: string;
complex: {
id: string;
name: string;
branch_code: string;
};
businessActivity: ISummary;
guild: ISummary;
complex: Complex;
businessActivity: BusinessActivity;
guild: Guild;
license_info: LicenseInfo;
partner: Partner;
}
export interface IPosInfoResponse extends IPosInfoRawResponse {}
@@ -21,3 +19,25 @@ export interface IPosAccessibleResponse extends IPosAccessibleRawResponse {}
interface Complex extends ISummary {
business_activity: ISummary;
}
interface LicenseInfo {
expires_at: string;
license_id: string;
}
interface Guild extends ISummary {
code: string;
}
interface Partner extends ISummary {
code: string;
logo_url?: string;
}
interface BusinessActivity extends ISummary {
economic_code: string;
}
interface Complex extends ISummary {
branch_code: string;
}
@@ -1,4 +1,5 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
import { Component, EventEmitter, inject, Output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@@ -7,7 +8,6 @@ import { PosLandingStore } from '../../store/main.store';
import { CustomerIndividualFormComponent } from './individual/form.component';
import { CustomerLegalFormComponent } from './legal/form.component';
import { CustomerUnknownFormComponent } from './unknown/form.component';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({
selector: 'pos-order-customer-dialog',
@@ -28,15 +28,15 @@ export class PosOrderCustomerDialogComponent extends AbstractDialog {
customerTypes = [
{
label: 'بدون اطلاعات خریدار (نوع دوم)',
label: 'بدون اطلاعات (نوع دوم)',
value: 'UNKNOWN',
},
{
label: 'خریدار حقیقی (نوع اول)',
label: 'حقیقی (نوع اول)',
value: 'INDIVIDUAL',
},
{
label: 'خریدار حقوقی (نوع اول)',
label: 'حقوقی (نوع اول)',
value: 'LEGAL',
},
] as {
@@ -1,29 +1,40 @@
<div class="flex flex-col">
<div class="sticky top-0 bg-surface-ground z-10 pb-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 text-muted-color">
<div class="flex flex-col min-h-full">
<div class="bg-surface-ground z-10 p-4 border-b border-surface-border shadow-[0_-4px_16px_rgba(0,0,0,0.08)]">
<div class="flex items-center justify-end mb-4">
<!-- <div class="flex items-center gap-2 text-muted-color">
<i class="pi pi-list"></i>
<span class="text-base font-bold">لیست کالاها</span>
</div>
<div class="flex items-center gap-2">
<app-search-input (search)="onSearch($event)"></app-search-input>
<div class="card p-2! flex justify-center">
<p-selectbutton [options]="viewOptions" [(ngModel)]="viewType" optionValue="value">
<ng-template #item let-item>
<i [class]="item.icon"></i>
</ng-template>
</p-selectbutton>
</div>
</div> -->
<div class="flex items-center gap-2 w-full">
<app-search-input class="sm:w-auto w-full max-w-xs" (search)="onSearch($event)"></app-search-input>
<p-divider layout="vertical" />
<p-selectbutton
[options]="viewOptions"
[(ngModel)]="viewType"
optionValue="value"
[allowEmpty]="false"
class="shrink-0 border border-surface-border bg-surface-ground"
>
<ng-template #item let-item>
<i [class]="item.icon"></i>
</ng-template>
</p-selectbutton>
</div>
</div>
<pos-good-categories (categoryChange)="onCategoryChange($event)" />
</div>
@if (viewType === "grid") {
<pos-good-grid-view (onAdd)="addGood($event)" />
} @else {
<pos-goods-list-view (onAdd)="addGood($event)" />
}
<div [class]="`p-4 ${!loading() && !goods()?.length ? ' grow flex items-center justify-center' : ''}`">
@if (!loading() && !goods()?.length) {
<div class="flex flex-col items-center gap-4 mt-10">
<i class="pi pi-box text-6xl! text-muted-color"></i>
<span class="text-xl font-semibold text-muted-color">کالایی برای نمایش وجود ندارد.</span>
</div>
} @else if (viewType === "grid") {
<pos-good-grid-view (onAdd)="addGood($event)" />
} @else {
<pos-goods-list-view (onAdd)="addGood($event)" />
}
</div>
@if (selectedGoodToAdd()) {
<pos-payload-form-dialog
@@ -3,6 +3,7 @@ import { IGoodResponse } from '@/domains/pos/models/good.io';
import { SearchInputComponent } from '@/shared/components/search/search-input.component';
import { Component, computed, inject, Input, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Divider } from 'primeng/divider';
import { SelectButton } from 'primeng/selectbutton';
import { PosLandingStore, TViewType } from '../store/main.store';
import { PosGoodCategoriesComponent } from './categories.component';
@@ -21,6 +22,7 @@ import { PayloadFormDialogComponent } from './payloads';
PosGoodsGridViewComponent,
SearchInputComponent,
PayloadFormDialogComponent,
Divider,
],
})
export class PosGoodsComponent {
@@ -42,6 +44,10 @@ export class PosGoodsComponent {
readonly activatedCategoryProducts = computed(() => this.store.activatedCategoryGoods());
readonly stock = computed(() => this.store.goods());
readonly inOrderGoods = computed(() => this.store.inOrderGoods());
readonly priceInfo = computed(() => this.store.orderPricingInfo());
readonly goods = computed(() => this.store.filteredGoods());
readonly loading = computed(() => this.store.getGoodsLoading());
get viewType() {
return this.store.viewType();
@@ -52,7 +58,7 @@ export class PosGoodsComponent {
onSearch(searchTerm: string) {
this.store.updateSearchQuery(searchTerm);
this.store.getGoods();
this.store.filterGoods();
}
onCategoryChange(categoryId: string) {
@@ -7,10 +7,10 @@
}
} @else {
@for (good of goods(); track good.id) {
<div class="flex-1 bg-surface-card rounded-lg p-1 shadow-xs">
<div class="flex-1 bg-surface-card rounded-lg p-1 shadow-xs" (click)="addProduct(good)">
<img [src]="good.image_url || goodPlaceholder" class="w-full aspect-[1.3] object-cover rounded-md" />
<div class="mt-2">
<span class="text-lg font-bold">
<div class="mt-2 text-center">
<span class="text-lg font-bold text-center">
{{ good.name }}
@if (!good.is_default_guild_good) {
<small class="text-xs text-muted-color">*</small>
@@ -18,9 +18,7 @@
</span>
<div class="flex items-center justify-between mt-1 w-full px-2 my-2">
<!-- <span [appPriceMask]="good.salePrice" class="text-base font-bold"></span> -->
<button pButton type="button" icon="pi pi-plus" size="small" class="w-full!" (click)="addProduct(good)">
انتخاب
</button>
<button pButton type="button" icon="pi pi-plus" class="w-full!">انتخاب</button>
</div>
</div>
</div>
@@ -14,7 +14,7 @@ export class PosGoodsGridViewComponent {
private readonly store = inject(PosLandingStore);
@Output() onAdd = new EventEmitter<IGoodResponse>();
goods = computed(() => this.store.activatedCategoryGoods());
goods = computed(() => this.store.filteredGoods());
loading = computed(() => this.store.getGoodsLoading());
goodPlaceholder = images.placeholders.default;
@@ -14,7 +14,7 @@ export class PosGoodsListViewComponent {
private readonly store = inject(PosLandingStore);
@Output() onAdd = new EventEmitter<IGoodResponse>();
goods = computed(() => this.store.activatedCategoryGoods());
goods = computed(() => this.store.filteredGoods());
loading = computed(() => this.store.getGoodsLoading());
goodPlaceholder = images.placeholders.default;
@@ -12,9 +12,6 @@
<span class="font-bold text-lg text-ellipsis text-nowrap overflow-hidden">
{{ inOrderGood.good.name }}
</span>
<div class="grow">
<span class="text-sm text-muted-color">({{ inOrderGood.good.sku }})</span>
</div>
</div>
<span class="text-sm text-ellipsis text-nowrap overflow-hidden">
{{ inOrderGood.quantity }} {{ enumTranslator(inOrderGood.good.unit_type) }}
@@ -1,16 +1,29 @@
<div class="bg-surface-card w-md shrink-0 h-full overflow-hidden rounded-xl p-4 pb-0 flex flex-col">
<div class="bg-surface-card shrink-0 h-full overflow-hidden md:rounded-xl md:p-4 md:w-md pb-0 flex flex-col">
<div class="grow flex flex-col overflow-hidden">
<div class="flex items-center justify-between shrink-0">
<div class="flex items-center gap-2 text-muted-color">
<i class="pi pi-shopping-cart"></i>
<span class="text-base font-bold">سفارش‌ها</span>
</div>
<button pButton type="button" icon="pi pi-refresh" outlined size="small" (click)="clearOrderList()">
پاک کردن سفارش‌ها
</button>
<div class="flex gap-2 shrink-0">
<button pButton type="button" icon="pi pi-plus" outlined size="small" (click)="addMoreGoods()">
افزودن کالا
</button>
<button
pButton
type="button"
icon="pi pi-refresh"
outlined
severity="danger"
size="small"
(click)="clearOrderList()"
>
پاک کردن سفارش‌ها
</button>
</div>
</div>
@if (!inOrderGoods().length) {
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color">
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color py-5">
<i class="pi pi-inbox text-6xl!"></i>
<span class="text-lg text-muted-color">هیچ سفارشی ثبت نشده است.</span>
</div>
@@ -42,7 +55,7 @@
severity="primary"
icon="pi pi-check"
class="w-full"
[attr.disabled]="inOrderGoods().length === 0 ? true : null"
size="large"
(click)="submitAndPay()"
></button>
</div>
@@ -1,7 +1,7 @@
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
// import { ICustomerResponse } from '@/modules/customers/models';
import { KeyValueComponent } from '@/shared/components';
import { Component, computed, inject, signal } from '@angular/core';
import { Component, computed, EventEmitter, inject, Output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
@@ -30,6 +30,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
})
export class PosOrderSectionComponent {
private readonly store = inject(PosLandingStore);
@Output() onAddMoreGoods = new EventEmitter<void>();
placeholderImage = images.placeholders.default;
@@ -77,6 +78,10 @@ export class PosOrderSectionComponent {
this.isVisiblePaymentForm.set(true);
}
addMoreGoods() {
this.onAddMoreGoods.emit();
}
submitCustomer() {}
submitPayment() {}
}
@@ -8,11 +8,12 @@
>
@if (good) {
@if (isGoldMode()) {
<pos-gold-payload-form [initialValues]="goldPayload()" (onSubmit)="submit($event)" />
<pos-gold-payload-form [initialValues]="goldPayload()" [editMode]="editMode()" (onSubmit)="submit($event)" />
} @else if (isStandardMode()) {
<pos-standard-payload-form
[initialValues]="standardPayload()"
[unitType]="good.unit_type"
[editMode]="editMode()"
(onSubmit)="submit($event)"
/>
}
@@ -1,11 +1,11 @@
import { IGoodResponse } from '@/domains/pos/models/good.io';
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { IGoldPayload, IPosOrderItem, IStandardPayload, TPosOrderGoodPayload } from '../models';
import { PosLandingStore } from '../store/main.store';
import { PosGoldPayloadFormComponent } from './payloads/gold/form.component';
import { PosStandardPayloadFormComponent } from './payloads/standard/form.component';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({
selector: 'pos-payload-form-dialog',
@@ -1,35 +1,37 @@
<form [formGroup]="form">
<app-input [control]="form.controls.unit_price" name="unit_price" label="قیمت پایه هر گرم" type="price" />
<app-input [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" name="karat" />
<div class="form-group">
<form [formGroup]="form">
<app-input [control]="form.controls.unit_price" name="unit_price" label="قیمت پایه هر گرم" type="price" />
<app-input [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" name="karat" />
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.wages_percentage"
[amountControl]="form.controls.payload.controls.wages_amount"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="wages"
label="اجرت"
/>
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.commission_percentage"
[amountControl]="form.controls.payload.controls.commission_amount"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="commission"
label="حق‌العمل"
/>
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.profit_percentage"
[amountControl]="form.controls.payload.controls.profit_amount"
[baseAmount]="totalAmountBeforeProfit()"
[minAmount]="0"
[maxAmount]="totalAmountBeforeProfit()"
name="profit"
label="سود"
/>
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.wages_percentage"
[amountControl]="form.controls.payload.controls.wages_amount"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="wages"
label="اجرت"
/>
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.commission_percentage"
[amountControl]="form.controls.payload.controls.commission_amount"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="commission"
label="حق‌العمل"
/>
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.profit_percentage"
[amountControl]="form.controls.payload.controls.profit_amount"
[baseAmount]="totalAmountBeforeProfit()"
[minAmount]="0"
[maxAmount]="totalAmountBeforeProfit()"
name="profit"
label="سود"
/>
</form>
<app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage"
@@ -43,6 +45,8 @@
<ng-template #labelSuffix>
<p-selectButton
[options]="discountTypeItems"
[(ngModel)]="discountType"
[allowEmpty]="false"
optionLabel="label"
optionValue="value"
(onChange)="changeDiscountCalculation($event)"
@@ -58,6 +62,6 @@
[discountAmount]="form.controls.discount_amount.value || 0"
[taxAmount]="taxAmount()"
/>
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
<button pButton class="sm:w-auto w-full" (click)="submit()">{{ preparedCTAText() }}</button>
</div>
</form>
</div>
@@ -1,12 +1,13 @@
import { greaterThanValidator } from '@/core/validators/greater.validator';
import { AbstractForm } from '@/shared/abstractClasses';
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
import { InputComponent } from '@/shared/components';
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
import { formatWithCurrency } from '@/utils';
import { Component, EventEmitter, Output, signal } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Button } from 'primeng/button';
import { Component, computed, EventEmitter, Output, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { SelectButton, SelectButtonChangeEvent } from 'primeng/selectbutton';
import { IGoldPayload, IPosOrderItem } from '../../../models';
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
@@ -15,13 +16,14 @@ import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-
selector: 'pos-gold-payload-form',
templateUrl: './form.component.html',
imports: [
FormsModule,
ReactiveFormsModule,
InputComponent,
EnumSelectComponent,
Button,
PosFormDialogAmountCardTemplateComponent,
SelectButton,
AmountPercentageInputComponent,
ButtonDirective,
],
})
export class PosGoldPayloadFormComponent extends AbstractForm<
@@ -50,10 +52,12 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
taxAmount = signal<number>(0);
totalAmount = signal<number>(0);
preparedCTAText = computed(() => (this.editMode ? 'اعمال ویرایش' : 'افزودن به لیست خرید'));
private readonly initialForm = () => {
const form = this.fb.group({
unit_price: [this.initialValues?.unit_price || 0, [Validators.required]],
quantity: [this.initialValues?.quantity || 0, [Validators.required]],
quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]],
discount_amount: [this.initialValues?.discount || 0],
discount_percentage: [0, [Validators.max(100), Validators.min(0)]],
payload: this.fb.group({
@@ -27,6 +27,6 @@
[discountAmount]="discountAmount()"
[taxAmount]="taxAmount()"
/>
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
<p-button (onClick)="submit()"> {{ preparedCTAText() }} </p-button>
</div>
</form>
@@ -59,6 +59,8 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
discountAmount = signal<number>(0);
taxAmount = signal<number>(0);
preparedCTAText = computed(() => (this.editMode ? 'اعمال ویرایش' : 'افزودن به لیست خرید'));
toPriceFormat(amount: number) {
if (!amount) {
return '';
@@ -6,43 +6,69 @@
[closable]="true"
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()">
<app-key-value label="مبلغ قابل پرداخت" [value]="totalAmount().toString()" type="price" />
<app-key-value label="مبلغ باقی‌مانده" [value]="remainedAmount().toString()" type="price" />
<hr />
<div class="form-group">
<form [formGroup]="form" (submit)="submit()">
<app-key-value label="مبلغ قابل پرداخت" [value]="totalAmount().toString()" type="price" />
<app-key-value label="مبلغ باقی‌مانده" [value]="remainedAmount().toString()" type="price" />
<hr />
</form>
<app-input
[control]="form.controls.terminal"
name="terminal"
label="پرداخت با پایانه"
type="price"
[max]="terminalMax()"
>
<ng-template #suffixTemp>
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('TERMINAL')">
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
<app-input [control]="form.controls.cash" name="cash" label="نقدی" type="price" [max]="cashMax()">
<ng-template #suffixTemp>
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('CASH')">
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
<app-input [control]="form.controls.set_off" name="setOff" label="تهاتر" type="price" [max]="setOffMax()">
<ng-template #suffixTemp>
<button pButton size="small" outlined type="button" class="border-s-0!" (click)="fillRemained('SET_OFF')">
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
<div class="form-group">
<uikit-field label="تعداد مراحل پرداخت با پایانه" name="pay_by_terminal_steps">
<p-select
[options]="payByTerminalSteps"
[ngModel]="selectedPayByTerminalStep()"
[ngModelOptions]="{ standalone: true }"
optionLabel="label"
optionValue="value"
[disabled]="!remainedAmount()"
class="w-full"
(onChange)="changePayByTerminalSteps($event.value)"
/>
</uikit-field>
@for (terminalControl of terminalControls; track $index) {
<app-input
[control]="terminalControl"
[name]="'terminal_' + $index"
[label]="$index === 0 ? 'پرداخت با پایانه' : 'پرداخت با پایانه - مرحله ' + ($index + 1)"
type="price"
[max]="terminalsMax()[$index]"
>
<ng-template #suffixTemp>
<button
pButton
size="small"
type="button"
[disabled]="!remainedAmount()"
(click)="fillTerminalRemained($index)"
>
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
}
</div>
<form [formGroup]="form" (submit)="submit()">
<app-input [control]="form.controls.cash" name="cash" label="نقدی" type="price" [max]="cashMax()">
<ng-template #suffixTemp>
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('CASH')">
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
<app-input [control]="form.controls.set_off" name="setOff" label="تهاتر" type="price" [max]="setOffMax()">
<ng-template #suffixTemp>
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('SET_OFF')">
تمامی بدهی باقی‌مانده
</button>
</ng-template>
</app-input>
<app-form-footer-actions
submitLabel="ثبت اطلاعات پرداخت و ادامه"
[loading]="submitOrderLoading()"
(onCancel)="close()"
/>
</form>
<app-form-footer-actions
submitLabel="ثبت اطلاعات پرداخت و ادامه"
[loading]="submitOrderLoading()"
(onCancel)="close()"
/>
</form>
</div>
</shared-dialog>
@@ -1,31 +1,37 @@
import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, KeyValueComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { Component, computed, inject, signal } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { catchError, throwError } from 'rxjs';
import { IPayment, TOrderPaymentTypes } from '../../models';
import { PosLandingStore } from '../../store/main.store';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFieldComponent } from '@/uikit';
import { Component, computed, inject, signal } from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { Select } from 'primeng/select';
import { IPayment, TOrderPaymentTypes } from '../../models';
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
import { PosLandingStore } from '../../store/main.store';
@Component({
selector: 'pos-payment-form-dialog',
templateUrl: './form-dialog.component.html',
imports: [
FormsModule,
ReactiveFormsModule,
InputComponent,
FormFooterActionsComponent,
KeyValueComponent,
ButtonDirective,
SharedDialogComponent,
Select,
UikitFieldComponent,
],
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
})
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, IPayment> {
private readonly store = inject(PosLandingStore);
private readonly nativeBridge = inject(NativeBridgeService);
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
private readonly toastServices = inject(ToastService);
readonly totalAmount = computed(() => this.store.orderPricingInfo().totalAmount);
@@ -33,35 +39,79 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
readonly remainedAmount = signal(this.totalAmount());
setOffMax = signal(this.remainedAmount());
cashMax = signal(this.remainedAmount());
terminalMax = signal(this.remainedAmount());
terminalsMax = signal([this.remainedAmount()]);
submitOrderLoading = computed(() => this.store.submitOrderLoading());
payByTerminalSteps = Array.from({ length: 10 }, (_, i) => ({
value: i + 1,
label: `${i + 1} مرحله‌ای`,
}));
selectedPayByTerminalStep = signal(1);
payedInTerminal = signal<Array<number>>([]);
private initForm = () => {
const form = this.fb.group({
set_off: [this.initialValues?.set_off || 0],
cash: [this.initialValues?.cash || 0],
terminal: [this.initialValues?.terminal || 0],
terminals: this.fb.array([this.fb.control(0)]),
});
form.valueChanges.subscribe((value) => {
const totalValue = (value.cash || 0) + (value.set_off || 0) + (value.terminal || 0);
const terminalTotal =
(value.terminals || []).reduce((acc, curr) => (acc || 0) + (curr || 0), 0) || 0;
const totalValue = (value.cash || 0) + (value.set_off || 0) + terminalTotal;
const remainedAmount = this.totalAmount() - totalValue;
const setOffMax = remainedAmount + (value.set_off || 0);
const cashMax = remainedAmount + (value.cash || 0);
const terminalMax = remainedAmount + (value.terminal || 0);
form.controls.set_off.clearValidators();
form.controls.set_off.addValidators([Validators.max(setOffMax)]);
if (!setOffMax) {
form.controls.set_off.disable({
onlySelf: true,
});
} else {
form.controls.set_off.enable({
onlySelf: true,
});
}
form.controls.cash.clearValidators();
form.controls.cash.addValidators([Validators.max(cashMax)]);
form.controls.terminal.clearValidators();
form.controls.terminal.addValidators([Validators.max(terminalMax)]);
if (!cashMax) {
form.controls.cash.disable({
onlySelf: true,
});
} else {
form.controls.cash.enable({
onlySelf: true,
});
}
form.controls.terminals.controls.forEach((terminal, i) => {
terminal.clearValidators();
this.terminalsMax.update((max) => {
const terminalMax = remainedAmount + (terminal.value || 0);
terminal.addValidators([Validators.max(terminalMax)]);
if (terminalMax) {
terminal.enable({
onlySelf: true,
});
} else {
terminal.disable({
onlySelf: true,
});
}
return max.map((m, index) => (index === i ? terminalMax : m));
});
});
this.remainedAmount.set(remainedAmount);
this.cashMax.set(cashMax);
this.terminalMax.set(terminalMax);
this.setOffMax.set(setOffMax);
});
@@ -69,13 +119,55 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
};
form = this.initForm();
get terminalControls() {
return this.form.controls.terminals.controls as FormControl<number | null>[];
}
fillRemained(type: TOrderPaymentTypes) {
changePayByTerminalSteps(steps: number) {
const normalizedSteps = Math.max(1, Number(steps) || 1);
this.selectedPayByTerminalStep.set(normalizedSteps);
const terminals = this.form.controls.terminals;
const currentLength = terminals.length;
if (normalizedSteps > currentLength) {
for (let i = currentLength; i < normalizedSteps; i += 1) {
terminals.push(this.fb.control(0));
const terminalMax = this.remainedAmount();
this.form.controls.terminals.controls[i].addValidators([Validators.max(terminalMax)]);
this.terminalsMax.update((max) => [...max, terminalMax]);
}
return;
}
if (normalizedSteps < currentLength) {
const removedSum = terminals.controls
.slice(normalizedSteps)
.reduce((acc, control) => acc + (control.value || 0), 0);
this.remainedAmount.update((value) => value + removedSum);
while (terminals.length > normalizedSteps) {
terminals.removeAt(terminals.length - 1);
}
// terminals.at(0)?.setValue(firstValue + removedSum);
}
}
fillTerminalRemained(index: number) {
this.fillRemained('TERMINAL', index);
}
fillRemained(type: TOrderPaymentTypes, _terminalIndex?: number) {
switch (type) {
case 'TERMINAL':
this.form.controls.terminal.setValue(
(this.form.controls.terminal.value || 0) + this.remainedAmount(),
);
const terminalIndex = _terminalIndex ?? 0;
this.form.controls.terminals
.at(terminalIndex)
?.setValue(
(this.form.controls.terminals.at(terminalIndex)?.value || 0) + this.remainedAmount(),
);
break;
case 'CASH':
@@ -101,43 +193,50 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
});
}
const payment = this.form.value as IPayment;
const rawPayment = this.form.getRawValue();
const payment: IPayment = {
cash: Number(rawPayment.cash || 0),
set_off: Number(rawPayment.set_off || 0),
terminals: (rawPayment.terminals || []).map((value) => Number(value || 0)),
};
if (payment.terminal > 0 && this.nativeBridge.isEnabled()) {
const payResult = this.nativeBridge.pay({
amount: payment.terminal,
totalAmount: this.totalAmount(),
invoiceDate: new Date().toISOString(),
});
if (!payResult.success) {
return this.toastServices.warn({
text: payResult.error || 'پرداخت با دستگاه انجام نشد. لطفا دوباره تلاش کنید.',
if (payment.terminals.some((terminal) => terminal > 0) && this.paymentBridge.isEnabled()) {
for (let terminal of payment.terminals) {
if (terminal <= 0) continue;
const payResult = this.paymentBridge.pay({
amount: terminal,
id: '0',
});
if (!payResult.success) {
return this.toastServices.warn({
text: payResult.error || 'پرداخت با دستگاه انجام نشد. لطفا دوباره تلاش کنید.',
});
}
}
}
this.store.setPayment(payment);
// this.store.setPayment(payment);
this.store
.submitOrder()
.pipe(
catchError((err) => {
return throwError(() => err);
}),
)
.subscribe((res) => {
if (this.nativeBridge.isEnabled()) {
this.nativeBridge.print({
invoiceId: res?.id,
code: res?.code,
});
}
this.close();
this.toastServices.success({
text: 'فاکتور شما با موفقیت ایجاد شد',
});
});
// this.store
// .submitOrder()
// .pipe(
// catchError((err) => {
// return throwError(() => err);
// }),
// )
// .subscribe((res) => {
// if (this.paymentBridge.isEnabled()) {
// this.paymentBridge.print({
// invoiceId: res?.id,
// code: res?.code,
// });
// }
// this.close();
// this.toastServices.success({
// text: 'فاکتور شما با موفقیت ایجاد شد',
// });
// });
}
override onSuccess(response: IPayment): void {}
@@ -1,7 +1,18 @@
export interface IPayment {
terminal: number;
terminals: number[];
cash: number;
set_off: number;
}
export type TOrderPaymentTypes = 'TERMINAL' | 'CASH' | 'SET_OFF';
export interface IPaymentTerminal {
amount: number;
terminal_id: string;
stan: string;
rrn: string;
response_code: string;
customer_card_no: string;
transaction_date_time: Date;
description?: string;
}
@@ -37,7 +37,9 @@ export class PosService {
}
getGoods(searchQuery: string): Observable<IListingResponse<IGoodResponse>> {
return this.http.get<IListingResponse<IGoodRawResponse>>(this.apiRoutes.goods());
return this.http.get<IListingResponse<IGoodRawResponse>>(this.apiRoutes.goods(), {
params: { search: searchQuery },
});
}
getGoodCategories(): Observable<IListingResponse<IGoodCategoryResponse>> {
@@ -0,0 +1,11 @@
import { INativeBridgeResult, INativePayRequest, INativePrintRequest } from '@/core/services/native-bridge.service';
export abstract class PosPaymentBridgeAbstract {
abstract isEnabled(): boolean;
abstract canPay(): boolean;
abstract canPrint(): boolean;
abstract pay(request: INativePayRequest): INativeBridgeResult;
abstract print(request: INativePrintRequest): INativeBridgeResult;
abstract registerPaymentResultListener(handler: (payload: unknown) => void): () => void;
abstract emitPaymentResultForTest(payload: unknown): void;
}
@@ -0,0 +1,62 @@
import { NativeBridgeService } from '@/core/services';
import {
INativeBridgeResult,
INativePayRequest,
INativePrintRequest,
} from '@/core/services/native-bridge.service';
import { Injectable, inject } from '@angular/core';
import { PosPaymentBridgeAbstract } from './payment-bridge.abstract';
@Injectable({ providedIn: 'root' })
export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
private readonly nativeBridge = inject(NativeBridgeService);
isEnabled(): boolean {
return this.nativeBridge.isEnabled();
}
canPay(): boolean {
return this.nativeBridge.canPay();
}
canPrint(): boolean {
return this.nativeBridge.canPrint();
}
pay(request: INativePayRequest): INativeBridgeResult {
return this.nativeBridge.pay(request);
}
print(request: INativePrintRequest): INativeBridgeResult {
return this.nativeBridge.print(request);
}
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
const w = window as unknown as {
onPaymentResult?: (payload: unknown) => void;
AndroidPSP?: {
onPaymentResult?: (payload: unknown) => void;
};
};
const previousGlobal = w.onPaymentResult;
const previousBridge = w.AndroidPSP?.onPaymentResult;
w.onPaymentResult = handler;
if (w.AndroidPSP) {
w.AndroidPSP.onPaymentResult = handler;
}
return () => {
w.onPaymentResult = previousGlobal;
if (w.AndroidPSP) {
w.AndroidPSP.onPaymentResult = previousBridge;
}
};
}
emitPaymentResultForTest(payload: unknown): void {
const w = window as unknown as { onPaymentResult?: (payload: unknown) => void };
w.onPaymentResult?.(payload);
}
}
@@ -47,7 +47,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
payments: {
cash: 0,
set_off: 0,
terminal: 0,
terminals: [],
},
submitOrderLoading: false,
};
@@ -59,6 +59,7 @@ export class PosLandingStore {
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
readonly goods = computed(() => this.state$().goods);
readonly getGoodsLoading = computed(() => this.state$().getGoodsLoading);
readonly inOrderGoods = computed(() => this.state$().inOrderGoods);
readonly goodCategories = computed(() => this.state$().goodCategories);
@@ -73,6 +74,9 @@ export class PosLandingStore {
}
return this.state$().goods?.filter((s) => s.category.id === this.state$().activeGoodCategory);
});
readonly filteredGoods = computed(() =>
this.activatedCategoryGoods()?.filter((good) => good.name.includes(this.state$().searchQuery)),
);
readonly customer = computed(() => this.state$().customerDetails);
readonly invoiceDate = computed(() => this.state$().invoiceDate);
@@ -194,6 +198,8 @@ export class PosLandingStore {
this.setState({ searchQuery });
}
filterGoods() {}
resetInOrderGoods() {
this.setState({ inOrderGoods: [] });
}
@@ -241,7 +247,7 @@ export class PosLandingStore {
payments: {
cash: 0,
set_off: 0,
terminal: 0,
terminals: [],
},
orderNote: '',
});
@@ -1,13 +1,47 @@
@if (loading()) {
<shared-page-loading />
} @else if (pos()) {
<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 class="w-full h-[calc(100dvh-5.5rem)] overflow-hidden">
<div
[class]="`w-full h-full flex max-md:flex-col gap-4 pb-4 pt-0 overflow-hidden ${inOrderGoods().length > 0 ? 'pb-18!' : ''}`"
>
<div class="grow h-full overflow-auto">
<pos-goods class="block h-full" />
</div>
<div class="md:shrink-0 md:h-full md:block hidden p-4 overflow-auto">
<pos-order-section />
</div>
</div>
@if (inOrderGoods().length > 0) {
<button
type="button"
pButton
class="sticky! bottom-0 inset-x-0 w-full right-0 z-1200 border-t border-surface-border bg-surface-0 px-4 py-3 shadow-[0_-4px_16px_rgba(0,0,0,0.08)] md:hidden rounded-b-none!"
[style.paddingBottom]="'calc(0.75rem + env(safe-area-inset-bottom))'"
(click)="openInvoiceBottomSheet()"
>
<div class="mx-auto flex w-full max-w-3xl items-center justify-between">
<div class="flex flex-col items-start gap-1">
<span class="text-base">مبلغ کل فاکتور</span>
<span class="text-xl font-semibold" [appPriceMask]="priceInfo().totalAmount"></span>
</div>
<div class="flex items-center gap-2">
<span class="text-base font-medium">مشاهده فاکتور</span>
<i class="pi pi-chevron-up"></i>
</div>
</div>
</button>
}
<shared-dialog
[(visible)]="showInvoiceBottomSheet"
position="bottom"
[modal]="true"
[closable]="true"
header="فاکتور"
>
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" />
</shared-dialog>
</div>
}
@@ -1,25 +1,48 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { PosInfoStore } from '@/domains/pos/store/pos.store';
import { SharedDialogComponent } from '@/shared/components';
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { Component, computed, inject } from '@angular/core';
import { PriceMaskDirective } from '@/shared/directives';
import { Component, computed, inject, signal } from '@angular/core';
import { ButtonDirective } from 'primeng/button';
import { PosGoodsComponent } from '../components/goods.component';
import { PosOrderSectionComponent } from '../components/order/order-section.component';
import { PosLandingStore } from '../store/main.store';
@Component({
selector: 'pos-landing',
templateUrl: './root.component.html',
host: { class: 'grow overflow-hidden' },
imports: [PosGoodsComponent, PosOrderSectionComponent, PageLoadingComponent],
imports: [
PosGoodsComponent,
PosOrderSectionComponent,
PageLoadingComponent,
PriceMaskDirective,
SharedDialogComponent,
ButtonDirective,
],
})
export class PosLandingComponent {
private readonly store = inject(PosInfoStore);
private readonly infoStore = inject(PosInfoStore);
private readonly landingStore = inject(PosLandingStore);
readonly loading = computed(() => this.store.loading());
readonly pos = computed(() => this.store.entity());
readonly error = computed(() => this.store.error());
readonly loading = computed(() => this.infoStore.loading());
readonly pos = computed(() => this.infoStore.entity());
readonly error = computed(() => this.infoStore.error());
readonly inOrderGoods = computed(() => this.landingStore.inOrderGoods());
readonly priceInfo = computed(() => this.landingStore.orderPricingInfo());
readonly showInvoiceBottomSheet = signal(false);
getData() {
this.store.getData().subscribe();
this.infoStore.getData().subscribe();
}
openInvoiceBottomSheet() {
this.showInvoiceBottomSheet.set(true);
}
closeInvoiceBottomSheet() {
this.showInvoiceBottomSheet.set(false);
}
}