refactor: restructure order and payload components for improved functionality

- Updated `PosOrderSectionComponent` to emit `onSubmit` instead of `onSuccess` and removed payment form visibility logic.
- Deleted unused `payload-form.component.html` and `payload-form.component.ts`.
- Introduced `PayloadFormDialogComponent` to handle payload form logic and rapid invoice creation.
- Enhanced `PosGoldPayloadFormComponent` and `PosStandardPayloadFormComponent` to support rapid invoice submissions.
- Added `PosGoodCategoriesComponent` for better category management in the goods view.
- Implemented `FavoriteCTAComponent` for managing favorite goods with loading states.
- Created `PosGoodsGridViewComponent` and `PosGoodsListViewComponent` for displaying goods in grid and list formats respectively.
- Improved UI elements across various components for consistency and better user experience.
This commit is contained in:
2026-05-24 12:42:21 +03:30
parent f18d7a1f04
commit cdd2bd6bee
30 changed files with 132 additions and 96 deletions
@@ -1,6 +1,7 @@
<div class="bg-surface-card"> <div class="bg-surface-card">
<app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute"> <app-inner-pages-header [pageTitle]="preparedPageTitle()">
<ng-template #actions> <ng-template #actions>
<button pButton icon="pi pi-plus" type="button" size="small" outlined [routerLink]="createRoute"></button>
<button pButton icon="pi pi-refresh" type="button" size="small" outlined (click)="refresh()"></button> <button pButton icon="pi pi-refresh" type="button" size="small" outlined (click)="refresh()"></button>
</ng-template> </ng-template>
</app-inner-pages-header> </app-inner-pages-header>
@@ -1,4 +1,3 @@
import { POS_ROUTES } from '@/domains/pos/routes';
import { PosInfoStore } from '@/domains/pos/store'; import { PosInfoStore } from '@/domains/pos/store';
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
@@ -26,7 +25,8 @@ export class PosOwnedGoodsListComponent {
loading = signal(true); loading = signal(true);
items = signal<IPosGoodsManagementResponse[]>([]); items = signal<IPosGoodsManagementResponse[]>([]);
backRoute = POS_ROUTES.path || '/'; // backRoute = POS_ROUTES.path || '/';
createRoute = PosGoodsManagementRoutes.create.meta.pagePath!();
preparedPageTitle = computed( preparedPageTitle = computed(
() => `لیست کالاهای ${this.infoStore.entity()?.businessActivity.name}` () => `لیست کالاهای ${this.infoStore.entity()?.businessActivity.name}`
@@ -31,7 +31,7 @@ export const PosGoodsManagementRoutes: NamedRoutes<TPosGoodsManagementRouteNames
import('../../views/create.component').then((m) => m.PosGoodsCreateComponent), import('../../views/create.component').then((m) => m.PosGoodsCreateComponent),
meta: { meta: {
title: 'ایجاد کالا', title: 'ایجاد کالا',
pagePath: () => baseRoute, pagePath: () => `${baseRoute}/create`,
}, },
}, },
}; };
@@ -3,7 +3,7 @@ import { SharedGoodFormComponent } from '@/shared/components/good';
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
import { Component, computed, inject } from '@angular/core'; import { Component, computed, inject } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import config from 'src/config'; import { PosGoodsManagementRoutes } from '../constants';
import { PosOwnedGoodsService } from '../services/main.service'; import { PosOwnedGoodsService } from '../services/main.service';
@Component({ @Component({
@@ -18,7 +18,7 @@ export class PosGoodsCreateComponent {
readonly guildId = computed(() => this.store.entity()?.guild.id || ''); readonly guildId = computed(() => this.store.entity()?.guild.id || '');
readonly backRoute = config.isPosApplication ? '/' : '/pos'; readonly backRoute = PosGoodsManagementRoutes.goods.meta.pagePath!();
preparedPageTitle = computed( preparedPageTitle = computed(
() => `ایجاد کالا برای ${this.store.entity()?.businessActivity.name}` () => `ایجاد کالا برای ${this.store.entity()?.businessActivity.name}`
@@ -5,7 +5,7 @@ import { ButtonDirective } from 'primeng/button';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { IPosInOrderGood } from '../../models'; import { IPosInOrderGood } from '../../models';
import { PosLandingStore } from '../../store/main.store'; import { PosLandingStore } from '../../store/main.store';
import { PayloadFormDialogComponent } from '../payload-form.component'; import { PayloadFormDialogComponent } from '../payloads/payload-form.component';
@Component({ @Component({
selector: 'pos-order-card-template', selector: 'pos-order-card-template',
@@ -1,11 +1,11 @@
<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="bg-surface-card flex h-full shrink-0 flex-col overflow-hidden pb-0 md:w-md md:rounded-xl md:p-4">
<div class="grow flex flex-col overflow-hidden"> <div class="flex grow flex-col overflow-hidden">
<div class="flex items-center justify-between shrink-0"> <div class="flex shrink-0 items-center justify-between">
<div class="flex items-center gap-2 text-muted-color"> <div class="text-muted-color flex items-center gap-2">
<i class="pi pi-shopping-cart"></i> <i class="pi pi-shopping-cart"></i>
<span class="text-base font-bold">سفارش‌ها</span> <span class="text-base font-bold">سفارش‌ها</span>
</div> </div>
<div class="flex gap-2 shrink-0"> <div class="flex shrink-0 gap-2">
<button pButton type="button" icon="pi pi-plus" outlined size="small" (click)="addMoreGoods()"> <button pButton type="button" icon="pi pi-plus" outlined size="small" (click)="addMoreGoods()">
افزودن کالا افزودن کالا
</button> </button>
@@ -16,21 +16,20 @@
outlined outlined
severity="danger" severity="danger"
size="small" size="small"
(click)="clearOrderList()" (click)="clearOrderList()">
>
پاک کردن سفارش‌ها پاک کردن سفارش‌ها
</button> </button>
</div> </div>
</div> </div>
@if (!inOrderGoods().length) { @if (!inOrderGoods().length) {
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color py-5"> <div class="text-muted-color flex grow flex-col items-center justify-center gap-5 py-5 text-center">
<i class="pi pi-inbox text-6xl!"></i> <i class="pi pi-inbox text-6xl!"></i>
<span class="text-lg text-muted-color">هیچ سفارشی ثبت نشده است.</span> <span class="text-muted-color text-lg">هیچ سفارشی ثبت نشده است.</span>
</div> </div>
} @else { } @else {
<div class="flex flex-col gap-2 mt-3 overflow-auto"> <div class="mt-3 flex flex-col gap-2 overflow-auto">
@for (inOrderGood of inOrderGoods(); track inOrderGood.id) { @for (inOrderGood of inOrderGoods(); track inOrderGood.id) {
@if (inOrderGood.good.pricing_model === "GOLD") { @if (inOrderGood.good.pricing_model === 'GOLD') {
<gold-payload-order-card [orderItem]="inOrderGood" /> <gold-payload-order-card [orderItem]="inOrderGood" />
} @else { } @else {
<standard-payload-order-card [orderItem]="inOrderGood" /> <standard-payload-order-card [orderItem]="inOrderGood" />
@@ -40,9 +39,9 @@
} }
</div> </div>
<hr /> <hr />
<div class="flex flex-col gap-4 sticky bottom-0 py-2"> <div class="sticky bottom-0 flex flex-col gap-4 py-2">
<p-card class="border border-surface-border"> <p-card class="border-surface-border border">
<div class="flex gap-2 items-center justify-between shrink-0"> <div class="flex shrink-0 items-center justify-between gap-2">
<app-key-value label="مشتری" [value]="customerNameToShow()" /> <app-key-value label="مشتری" [value]="customerNameToShow()" />
<button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button> <button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button>
</div> </div>
@@ -56,11 +55,8 @@
icon="pi pi-check" icon="pi pi-check"
class="w-full" class="w-full"
size="large" size="large"
(click)="submitAndPay()" (click)="submitAndPay()"></button>
></button>
</div> </div>
</div> </div>
<pos-order-customer-dialog [(visible)]="isVisibleCustomerForm" (onSubmit)="submitCustomer()" /> <pos-order-customer-dialog [(visible)]="isVisibleCustomerForm" (onSubmit)="submitCustomer()" />
<!-- <pos-pre-invoice-dialog [(visible)]="isVisiblePreInvoiceForm" /> -->
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment($event)" />
@@ -1,17 +1,23 @@
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component'; // import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
// import { ICustomerResponse } from '@/modules/customers/models'; // import { ICustomerResponse } from '@/modules/customers/models';
import { KeyValueComponent } from '@/shared/components'; import { KeyValueComponent } from '@/shared/components';
import { ChangeDetectionStrategy, Component, computed, EventEmitter, inject, Output, signal } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
computed,
EventEmitter,
inject,
Output,
signal,
} from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card'; import { Card } from 'primeng/card';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { IPosOrderResponse } from '../../models';
import { PosLandingStore } from '../../store/main.store'; import { PosLandingStore } from '../../store/main.store';
import { PosOrderCustomerDialogComponent } from '../customers/customer-dialog.component'; import { PosOrderCustomerDialogComponent } from '../customers/customer-dialog.component';
import { GoldPayloadOrderCardComponent } from '../payloads/gold/order-card.component'; import { GoldPayloadOrderCardComponent } from '../payloads/gold/order-card.component';
import { StandardPayloadOrderCardComponent } from '../payloads/standard/order-card.component'; import { StandardPayloadOrderCardComponent } from '../payloads/standard/order-card.component';
import { PosPaymentFormDialogComponent } from '../payment/form-dialog.component';
import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
@Component({ @Component({
@@ -26,20 +32,17 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
StandardPayloadOrderCardComponent, StandardPayloadOrderCardComponent,
PosOrderCustomerDialogComponent, PosOrderCustomerDialogComponent,
KeyValueComponent, KeyValueComponent,
PosPaymentFormDialogComponent,
Card, Card,
], ],
}) })
export class PosOrderSectionComponent { export class PosOrderSectionComponent {
private readonly store = inject(PosLandingStore); private readonly store = inject(PosLandingStore);
@Output() onAddMoreGoods = new EventEmitter<void>(); @Output() onAddMoreGoods = new EventEmitter<void>();
@Output() onSuccess = new EventEmitter<IPosOrderResponse>(); @Output() onSubmit = new EventEmitter<void>();
placeholderImage = images.placeholders.default; placeholderImage = images.placeholders.default;
isVisibleCustomerForm = signal(false); isVisibleCustomerForm = signal(false);
isVisiblePaymentForm = signal(false);
isVisiblePreInvoiceForm = signal(false);
inOrderGoods = computed(() => this.store.inOrderGoods()); inOrderGoods = computed(() => this.store.inOrderGoods());
customer = computed(() => this.store.customer()); customer = computed(() => this.store.customer());
@@ -77,8 +80,7 @@ export class PosOrderSectionComponent {
} }
submitAndPay() { submitAndPay() {
this.isVisiblePaymentForm.set(false); this.onSubmit.emit();
queueMicrotask(() => this.isVisiblePaymentForm.set(true));
} }
addMoreGoods() { addMoreGoods() {
@@ -86,7 +88,4 @@ export class PosOrderSectionComponent {
} }
submitCustomer() {} submitCustomer() {}
submitPayment(invoice: IPosOrderResponse) {
this.onSuccess.emit(invoice);
}
} }
@@ -44,6 +44,7 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
}; };
@Input({ required: true }) vatPercentage!: number; @Input({ required: true }) vatPercentage!: number;
@Input() isRapidInvoice!: boolean;
@Output() onSubmitForm = new EventEmitter<IGoldPayload>(); @Output() onSubmitForm = new EventEmitter<IGoldPayload>();
@Output() onChangeTotalAmount = new EventEmitter<number>(); @Output() onChangeTotalAmount = new EventEmitter<number>();
@@ -71,7 +72,9 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
goldDefaultUnitPrice = signal<number>(0); goldDefaultUnitPrice = signal<number>(0);
preparedCTAText = computed(() => (this.editMode ? 'اعمال ویرایش' : 'افزودن به لیست خرید')); preparedCTAText = computed(() =>
this.editMode ? 'اعمال ویرایش' : this.isRapidInvoice ? 'ثبت و پرداخت' : 'افزودن به لیست خرید'
);
private readonly initialForm = () => { private readonly initialForm = () => {
const defaultPrice = this.posGoldConfig.get(); const defaultPrice = this.posGoldConfig.get();
@@ -1 +1 @@
export * from '../payload-form.component'; export * from './payload-form.component';
@@ -4,24 +4,23 @@
[modal]="true" [modal]="true"
[style]="{ 'max-height': '90svh', height: 'auto' }" [style]="{ 'max-height': '90svh', height: 'auto' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()">
>
@if (good) { @if (good) {
@if (isGoldMode()) { @if (isGoldMode()) {
<pos-gold-payload-form <pos-gold-payload-form
[initialValues]="goldPayload()" [initialValues]="goldPayload()"
[vatPercentage]="vatPercentage()" [vatPercentage]="vatPercentage()"
[editMode]="editMode()" [editMode]="editMode()"
(onSubmit)="submit($event)" [isRapidInvoice]="isRapidInvoice()"
/> (onSubmit)="submit($event)" />
} @else if (isStandardMode()) { } @else if (isStandardMode()) {
<pos-standard-payload-form <pos-standard-payload-form
[initialValues]="standardPayload()" [initialValues]="standardPayload()"
[vatPercentage]="vatPercentage()" [vatPercentage]="vatPercentage()"
[measureUnit]="good.measure_unit" [measureUnit]="good.measure_unit"
[editMode]="editMode()" [editMode]="editMode()"
(onSubmit)="submit($event)" [isRapidInvoice]="isRapidInvoice()"
/> (onSubmit)="submit($event)" />
} }
} }
</shared-light-bottomsheet> </shared-light-bottomsheet>
@@ -2,10 +2,11 @@ import { IGoodResponse } from '@/domains/pos/models/good.io';
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core'; import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { IGoldPayload, IPosOrderItem, IStandardPayload, TPosOrderGoodPayload } from '../models'; import { PosConfigRapidInvoiceService } from '../../../configs/components/rapidInvoice/services/main.service';
import { PosLandingStore } from '../store/main.store'; import { IGoldPayload, IPosOrderItem, IStandardPayload } from '../../models';
import { PosGoldPayloadFormComponent } from './payloads/gold/form.component'; import { PosLandingStore } from '../../store/main.store';
import { PosStandardPayloadFormComponent } from './payloads/standard/form.component'; import { PosGoldPayloadFormComponent } from './gold/form.component';
import { PosStandardPayloadFormComponent } from './standard/form.component';
@Component({ @Component({
selector: 'pos-payload-form-dialog', selector: 'pos-payload-form-dialog',
@@ -20,9 +21,10 @@ export class PayloadFormDialogComponent extends AbstractDialog {
@Input({ required: true }) good!: IGoodResponse; @Input({ required: true }) good!: IGoodResponse;
@Input() initialValues?: IPosOrderItem; @Input() initialValues?: IPosOrderItem;
@Input() orderItemId?: string; @Input() orderItemId?: string;
@Output() onSubmit = new EventEmitter<TPosOrderGoodPayload>(); @Output() onCreateRapidOrder = new EventEmitter<void>();
private readonly store = inject(PosLandingStore); private readonly store = inject(PosLandingStore);
private readonly rapidInvoiceConfig = inject(PosConfigRapidInvoiceService);
totalAmount = signal<number>(0); totalAmount = signal<number>(0);
preparedTitle = computed(() => this.good?.name); preparedTitle = computed(() => this.good?.name);
@@ -32,6 +34,8 @@ export class PayloadFormDialogComponent extends AbstractDialog {
isGoldMode = computed(() => this.good.pricing_model === 'GOLD'); isGoldMode = computed(() => this.good.pricing_model === 'GOLD');
isStandardMode = computed(() => this.good.pricing_model === 'STANDARD'); isStandardMode = computed(() => this.good.pricing_model === 'STANDARD');
isRapidInvoice = computed(() => this.rapidInvoiceConfig.get());
goldPayload = computed(() => goldPayload = computed(() =>
this.isGoldMode() && this.editMode() this.isGoldMode() && this.editMode()
? (this.initialValues as IPosOrderItem<IGoldPayload>) ? (this.initialValues as IPosOrderItem<IGoldPayload>)
@@ -52,6 +56,9 @@ export class PayloadFormDialogComponent extends AbstractDialog {
this.store.editInOrderGoods({ id: this.orderItemId!, good: this.good, ...payload }); this.store.editInOrderGoods({ id: this.orderItemId!, good: this.good, ...payload });
} else { } else {
this.store.addToInOrderGoods(this.good.id, payload); this.store.addToInOrderGoods(this.good.id, payload);
if (this.isRapidInvoice()) {
this.onCreateRapidOrder.emit();
}
} }
this.close(); this.close();
} }
@@ -1,13 +1,12 @@
<form [formGroup]="form" (submit)="submit()"> <form [formGroup]="form" (submit)="submit()">
<app-input [control]="form.controls.unit_price" name="unit_price" label="قیمت پایه" type="price" /> <app-input [control]="form.controls.unit_price" name="unit_price" label="مبلغ واحد" type="price" />
<app-input <app-input
[control]="form.controls.quantity" [control]="form.controls.quantity"
name="quantity" name="quantity"
[label]="measureUnit.name" [label]="measureUnit.name"
type="number" type="number"
[suffix]="measureUnit.name" [suffix]="measureUnit.name"
[min]="0" [min]="0" />
/>
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage" [percentageControl]="form.controls.discount_percentage"
@@ -16,17 +15,15 @@
[minAmount]="0" [minAmount]="0"
[maxAmount]="baseTotalAmount()" [maxAmount]="baseTotalAmount()"
name="discount" name="discount"
label="تخفیف" label="تخفیف" />
/>
<hr /> <hr />
<div class="flex flex-col gap-4 justify-center w-full"> <div class="flex w-full flex-col justify-center gap-4">
<pos-form-dialog-amount-card-template <pos-form-dialog-amount-card-template
[totalAmount]="totalAmount()" [totalAmount]="totalAmount()"
[baseTotalAmount]="baseTotalAmount()" [baseTotalAmount]="baseTotalAmount()"
[discountAmount]="discountAmount()" [discountAmount]="discountAmount()"
[taxAmount]="taxAmount()" [taxAmount]="taxAmount()" />
/> <button pButton class="w-full sm:w-auto" (onClick)="submit()">{{ preparedCTAText() }}</button>
<button pButton class="sm:w-auto w-full" (onClick)="submit()">{{ preparedCTAText() }}</button>
</div> </div>
</form> </form>
@@ -27,6 +27,7 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
> { > {
@Input({ required: true }) measureUnit!: ISummary; @Input({ required: true }) measureUnit!: ISummary;
@Input({ required: true }) vatPercentage!: number; @Input({ required: true }) vatPercentage!: number;
@Input() isRapidInvoice!: boolean;
@Output() onChangeTotalAmount = new EventEmitter<number>(); @Output() onChangeTotalAmount = new EventEmitter<number>();
private readonly initialForm = () => { private readonly initialForm = () => {
@@ -60,7 +61,9 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
discountAmount = signal<number>(0); discountAmount = signal<number>(0);
taxAmount = signal<number>(0); taxAmount = signal<number>(0);
preparedCTAText = computed(() => (this.editMode ? 'اعمال ویرایش' : 'افزودن به لیست خرید')); preparedCTAText = computed(() =>
this.editMode ? 'اعمال ویرایش' : this.isRapidInvoice ? 'ثبت و پرداخت' : 'افزودن به لیست خرید'
);
toPriceFormat(amount: number) { toPriceFormat(amount: number) {
if (!amount) { if (!amount) {
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, inject, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, inject, Output } from '@angular/core';
import { Skeleton } from 'primeng/skeleton'; import { Skeleton } from 'primeng/skeleton';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../../store/main.store';
@Component({ @Component({
selector: 'pos-good-categories', selector: 'pos-good-categories',
@@ -1,8 +1,8 @@
import { Component, inject, Input, signal } from '@angular/core'; import { Component, inject, Input, signal } from '@angular/core';
import { Button } from 'primeng/button'; import { Button } from 'primeng/button';
import { finalize, Observable } from 'rxjs'; import { finalize, Observable } from 'rxjs';
import { PosGoodFavoriteService } from '../services/favorite.service'; import { PosGoodFavoriteService } from '../../services/favorite.service';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../../store/main.store';
@Component({ @Component({
selector: 'pos-good-favorite-cta', selector: 'pos-good-favorite-cta',
@@ -39,14 +39,14 @@ export class FavoriteCTAComponent {
private handleFavoriteRequest( private handleFavoriteRequest(
request$: Observable<unknown>, request$: Observable<unknown>,
goodId: string, goodId: string,
isFavorite: boolean, isFavorite: boolean
) { ) {
this.loading.set(true); this.loading.set(true);
request$ request$
.pipe( .pipe(
finalize(() => { finalize(() => {
this.loading.set(false); this.loading.set(false);
}), })
) )
.subscribe(() => { .subscribe(() => {
this.store.toggleGoodFavorite(goodId, isFavorite); this.store.toggleGoodFavorite(goodId, isFavorite);
@@ -36,6 +36,10 @@
</div> </div>
@if (showPayloadForm() && selectedGoodToAdd(); as selectedGood) { @if (showPayloadForm() && selectedGoodToAdd(); as selectedGood) {
<pos-payload-form-dialog [(visible)]="showPayloadForm" [good]="selectedGood" (onClose)="onClosePayloadForm()" /> <pos-payload-form-dialog
[(visible)]="showPayloadForm"
[good]="selectedGood"
(onClose)="onClosePayloadForm()"
(onCreateRapidOrder)="createRapidOrder()" />
} }
</div> </div>
@@ -1,15 +1,24 @@
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { IGoodResponse } from '@/domains/pos/models/good.io'; import { IGoodResponse } from '@/domains/pos/models/good.io';
import { SearchInputComponent } from '@/shared/components/search/search-input.component'; import { SearchInputComponent } from '@/shared/components/search/search-input.component';
import { ChangeDetectionStrategy, Component, computed, inject, Input, signal } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
computed,
EventEmitter,
inject,
Input,
Output,
signal,
} from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Divider } from 'primeng/divider'; import { Divider } from 'primeng/divider';
import { SelectButton } from 'primeng/selectbutton'; import { SelectButton } from 'primeng/selectbutton';
import { PosLandingStore, TViewType } from '../store/main.store'; import { PosLandingStore, TViewType } from '../../store/main.store';
import { PayloadFormDialogComponent } from '../payloads';
import { PosGoodCategoriesComponent } from './categories.component'; import { PosGoodCategoriesComponent } from './categories.component';
import { PosGoodsGridViewComponent } from './grid-view.component'; import { PosGoodsGridViewComponent } from './grid-view.component';
import { PosGoodsListViewComponent } from './list-view.component'; import { PosGoodsListViewComponent } from './list-view.component';
import { PayloadFormDialogComponent } from './payloads';
@Component({ @Component({
selector: 'pos-goods', selector: 'pos-goods',
@@ -28,6 +37,7 @@ import { PayloadFormDialogComponent } from './payloads';
}) })
export class PosGoodsComponent { export class PosGoodsComponent {
@Input() activeCategory = signal<Maybe<string>>(null); @Input() activeCategory = signal<Maybe<string>>(null);
@Output() onCreateRapidOrder = new EventEmitter<void>();
private readonly store = inject(PosLandingStore); private readonly store = inject(PosLandingStore);
@@ -77,4 +87,8 @@ export class PosGoodsComponent {
this.showPayloadForm.set(false); this.showPayloadForm.set(false);
this.selectedGoodToAdd.set(null); this.selectedGoodToAdd.set(null);
} }
createRapidOrder() {
this.onCreateRapidOrder.emit();
}
} }
@@ -12,7 +12,7 @@ import {
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
import { Skeleton } from 'primeng/skeleton'; import { Skeleton } from 'primeng/skeleton';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../../store/main.store';
import { FavoriteCTAComponent } from './favorite-CTA.component'; import { FavoriteCTAComponent } from './favorite-CTA.component';
@Component({ @Component({
@@ -12,7 +12,7 @@ import {
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
import { Skeleton } from 'primeng/skeleton'; import { Skeleton } from 'primeng/skeleton';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../../store/main.store';
import { FavoriteCTAComponent } from './favorite-CTA.component'; import { FavoriteCTAComponent } from './favorite-CTA.component';
@Component({ @Component({
@@ -1,13 +1,13 @@
@if (loading()) { @if (loading()) {
<shared-page-loading /> <shared-page-loading />
} @else if (pos()) { } @else if (pos()) {
<div class="w-full min-h-[calc(100dvh-5.5rem)] md:overflow-hidden"> <div class="min-h-[calc(100dvh-5.5rem)] w-full md:overflow-hidden">
<div class="w-full! h-full flex max-md:flex-col gap-4 pt-0"> <div class="flex h-full w-full! gap-4 pt-0 max-md:flex-col">
<div class="grow min-h-[calc(100dvh-5.5rem)] w-full"> <div class="min-h-[calc(100dvh-5.5rem)] w-full grow">
<pos-goods class="block h-full" /> <pos-goods class="block h-full" (onCreateRapidOrder)="createRapidOrder()" />
</div> </div>
@if (!isMobile()) { @if (!isMobile()) {
<div class="md:shrink-0 h-full md:block hidden p-4 overflow-auto"> <div class="hidden h-full overflow-auto p-4 md:block md:shrink-0">
<pos-order-section /> <pos-order-section />
</div> </div>
} }
@@ -18,9 +18,8 @@
<button <button
type="button" type="button"
pButton 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!" class="border-surface-border bg-surface-0 sticky! inset-x-0 right-0 bottom-0 z-1200 w-full rounded-b-none! border-t px-4 py-3 shadow-[0_-4px_16px_rgba(0,0,0,0.08)] md:hidden!"
(click)="openInvoiceBottomSheet()" (click)="openInvoiceBottomSheet()">
>
<!-- [style.paddingBottom]="'calc(0.75rem + {{env(safe-area-inset-bottom)}})'" --> <!-- [style.paddingBottom]="'calc(0.75rem + {{env(safe-area-inset-bottom)}})'" -->
<div class="mx-auto flex w-full max-w-3xl items-center justify-between"> <div class="mx-auto flex w-full max-w-3xl items-center justify-between">
<div class="flex flex-col items-start gap-1"> <div class="flex flex-col items-start gap-1">
@@ -34,11 +33,15 @@
</div> </div>
</button> </button>
} }
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [modal]="true" [closable]="true" header="فاکتور">
@if (showInvoiceBottomSheet()) { @if (showInvoiceBottomSheet()) {
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSuccess)="onSuccessSubmit($event)" /> <shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [modal]="true" [closable]="true" header="فاکتور">
} <pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
</shared-light-bottomsheet> </shared-light-bottomsheet>
}
@if (isVisiblePaymentForm()) {
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment($event)" />
}
@if (responseInvoice()) { @if (responseInvoice()) {
<pos-order-submitted-dialog [(visible)]="isVisibleSuccessDialog" [invoice]="responseInvoice()!" /> <pos-order-submitted-dialog [(visible)]="isVisibleSuccessDialog" [invoice]="responseInvoice()!" />
@@ -7,9 +7,10 @@ import { PageLoadingComponent } from '@/shared/components/page-loading.component
import { PriceMaskDirective } from '@/shared/directives'; import { PriceMaskDirective } from '@/shared/directives';
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
import { PosGoodsComponent } from '../components/goods.component';
import { PosOrderSectionComponent } from '../components/order/order-section.component'; import { PosOrderSectionComponent } from '../components/order/order-section.component';
import { PosOrderSubmittedDialogComponent } from '../components/order/order-submitted-dialog.component'; import { PosOrderSubmittedDialogComponent } from '../components/order/order-submitted-dialog.component';
import { PosPaymentFormDialogComponent } from '../components/payment/form-dialog.component';
import { PosGoodsComponent } from '../components/views/goods.component';
import { IPosOrderResponse } from '../models'; import { IPosOrderResponse } from '../models';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../store/main.store';
@@ -27,21 +28,23 @@ import { PosLandingStore } from '../store/main.store';
SharedLightBottomsheetComponent, SharedLightBottomsheetComponent,
ButtonDirective, ButtonDirective,
PosOrderSubmittedDialogComponent, PosOrderSubmittedDialogComponent,
PosPaymentFormDialogComponent,
], ],
}) })
export class PosShopComponent extends AbstractIsMobileComponent { export class PosShopComponent extends AbstractIsMobileComponent {
private readonly infoStore = inject(PosInfoStore); private readonly infoStore = inject(PosInfoStore);
private readonly landingStore = inject(PosLandingStore); private readonly landingStore = inject(PosLandingStore);
isVisibleSuccessDialog = signal(false); readonly isVisibleSuccessDialog = signal(false);
responseInvoice = signal<Maybe<IPosOrderResponse>>(null); readonly showInvoiceBottomSheet = signal(false);
readonly isVisiblePaymentForm = signal(false);
readonly responseInvoice = signal<Maybe<IPosOrderResponse>>(null);
readonly loading = computed(() => this.infoStore.loading()); readonly loading = computed(() => this.infoStore.loading());
readonly pos = computed(() => this.infoStore.entity()); readonly pos = computed(() => this.infoStore.entity());
readonly error = computed(() => this.infoStore.error()); readonly error = computed(() => this.infoStore.error());
readonly inOrderGoods = computed(() => this.landingStore.inOrderGoods()); readonly inOrderGoods = computed(() => this.landingStore.inOrderGoods());
readonly priceInfo = computed(() => this.landingStore.orderPricingInfo()); readonly priceInfo = computed(() => this.landingStore.orderPricingInfo());
readonly showInvoiceBottomSheet = signal(false);
// getData() { // getData() {
// this.infoStore.getData().subscribe(); // this.infoStore.getData().subscribe();
@@ -55,7 +58,15 @@ export class PosShopComponent extends AbstractIsMobileComponent {
this.showInvoiceBottomSheet.set(false); this.showInvoiceBottomSheet.set(false);
} }
onSuccessSubmit(invoice: IPosOrderResponse) { createRapidOrder() {
this.isVisiblePaymentForm.set(true);
}
submitOrder() {
this.isVisiblePaymentForm.set(true);
}
submitPayment(invoice: IPosOrderResponse) {
this.closeInvoiceBottomSheet(); this.closeInvoiceBottomSheet();
this.responseInvoice.set(invoice); this.responseInvoice.set(invoice);
this.isVisibleSuccessDialog.set(true); this.isVisibleSuccessDialog.set(true);
@@ -1,5 +1,5 @@
<div <div
class="text-surface-700 relative flex flex-col gap-4 overflow-hidden rounded-2xl border px-4 py-4 pe-2" class="text-surface-700 relative flex flex-col gap-4 overflow-hidden rounded-lg border px-4 py-4 pe-2"
[style.backgroundColor]="preparedInfo().bgColor" [style.backgroundColor]="preparedInfo().bgColor"
[style.borderColor]="preparedInfo().borderColor" [style.borderColor]="preparedInfo().borderColor"
(click)="showDetails()"> (click)="showDetails()">
@@ -3,7 +3,7 @@
<section class="light-bottomsheet-panel" [ngStyle]="style || { 'max-height': mobileDrawerHeight, height: 'auto' }"> <section class="light-bottomsheet-panel" [ngStyle]="style || { 'max-height': mobileDrawerHeight, height: 'auto' }">
<div class="light-bottomsheet-content"> <div class="light-bottomsheet-content">
@if (header || closable) { @if (header || closable) {
<header class="light-bottomsheet-header"> <header class="light-bottomsheet-header border-surface-border border-b">
<span class="text-xl font-bold">{{ header }}</span> <span class="text-xl font-bold">{{ header }}</span>
@if (closable) { @if (closable) {
<p-button <p-button
@@ -13,8 +13,7 @@
size="small" size="small"
class="light-bottomsheet-close" class="light-bottomsheet-close"
(click)="onVisibilityChange(false)" (click)="onVisibilityChange(false)"
aria-label="Close" aria-label="Close">
>
</p-button> </p-button>
} }
</header> </header>
@@ -5,7 +5,7 @@ import { InputComponent } from '../input/input.component';
@Component({ @Component({
selector: 'field-unit-price', selector: 'field-unit-price',
template: `<app-input template: `<app-input
label="قیمت پایه" label="مبلغ واحد"
[control]="control" [control]="control"
[name]="name" [name]="name"
[hint]="hint" [hint]="hint"
@@ -4,7 +4,7 @@
@if (backRoute) { @if (backRoute) {
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined size="small" [routerLink]="backRoute" /> <p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined size="small" [routerLink]="backRoute" />
} }
<span class="text-xl font-bold">{{ pageTitle }}</span> <h5 class="py-1.5 font-bold">{{ pageTitle }}</h5>
</div> </div>
</div> </div>
@if (actions) { @if (actions) {
@@ -1,4 +1,4 @@
<div class="flex items-center gap-2 overflow-hidden rounded-xl border border-slate-200 bg-white p-2"> <div class="border-surface-border bg-surface-card flex items-center gap-2 overflow-hidden rounded-lg border p-2">
<p-button icon="pi pi-chevron-right" size="small" text class="shrink-0" (onClick)="prevSeason()" /> <p-button icon="pi pi-chevron-right" size="small" text class="shrink-0" (onClick)="prevSeason()" />
<div class="flex flex-1 cursor-pointer justify-center" (click)="openPicker()"> <div class="flex flex-1 cursor-pointer justify-center" (click)="openPicker()">
<span class="text-base font-semibold">{{ currentSeason() }}</span> <span class="text-base font-semibold">{{ currentSeason() }}</span>