Compare commits

...

2 Commits

Author SHA1 Message Date
ahasani cdd2bd6bee 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.
2026-05-24 12:42:21 +03:30
ahasani f18d7a1f04 feat: implement change password and send to fiscal activation modules with forms and services 2026-05-24 10:44:35 +03:30
49 changed files with 378 additions and 212 deletions
@@ -43,12 +43,12 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
icon: 'pi pi-fw pi-home', icon: 'pi pi-fw pi-home',
}, },
{ {
label: 'فروش', label: 'ایجاد صورت‌حساب',
routerLink: PosShopNamedRoutes.shop.meta.pagePath!(), routerLink: PosShopNamedRoutes.shop.meta.pagePath!(),
icon: 'pi pi-fw pi-cart-arrow-down', icon: 'pi pi-fw pi-cart-arrow-down',
}, },
{ {
label: 'فاکتورها', label: 'صورت‌حساب‌ها',
routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(), routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
icon: 'pi pi-fw pi-receipt', icon: 'pi pi-fw pi-receipt',
}, },
@@ -0,0 +1,11 @@
<app-card-data cardTitle="تغییر گذرواژه" class="w-full">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<shared-password-input
[passwordControl]="form.controls.password"
[confirmPasswordControl]="form.controls.confirmPassword" />
<div class="flex w-full justify-end">
<button pButton label="ذخیره گذرواژه" [loading]="submitLoading()" class="w-full max-w-40"></button>
</div>
</form>
</app-card-data>
@@ -0,0 +1,51 @@
import { MustMatch } from '@/core/validators';
import { AbstractForm } from '@/shared/abstractClasses';
import { AppCardComponent } from '@/shared/components';
import { SharedPasswordInputComponent } from '@/shared/components/passwordInput/password-input.component';
import { fieldControl } from '@/shared/constants';
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { tap } from 'rxjs';
import { IChangePasswordSubmitPayload } from './model';
import { PosChangePasswordService } from './services/main.service';
@Component({
selector: 'pos-change-password-form',
templateUrl: './form.component.html',
imports: [
ReactiveFormsModule,
SharedPasswordInputComponent,
CommonModule,
ButtonDirective,
AppCardComponent,
],
})
export class PosChangePasswordFormDialogComponent extends AbstractForm<
IChangePasswordSubmitPayload,
any
> {
private readonly changePasswordService = inject(PosChangePasswordService);
form = this.fb.group(
{
password: fieldControl.password(),
confirmPassword: fieldControl.confirmPassword(),
},
{
validators: [MustMatch('password', 'confirmPassword')],
}
);
override submitForm() {
const payload = this.form.getRawValue().password!;
return this.changePasswordService.changePassword({ password: payload }).pipe(
tap({
next: (res) => {
this.form.reset();
},
})
);
}
}
@@ -0,0 +1,3 @@
export interface IChangePasswordSubmitPayload {
password: string;
}
@@ -0,0 +1,15 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { IChangePasswordSubmitPayload } from '../model';
@Injectable({
providedIn: 'root',
})
export class PosChangePasswordService {
constructor(private readonly http: HttpClient) {}
changePassword(payload: IChangePasswordSubmitPayload): Observable<unknown> {
return this.http.put('/api/v1/pos/update-password', payload);
}
}
@@ -1,9 +1,7 @@
<app-card-data cardTitle="قیمت پیش‌فرض هر گرم طلا" class="w-full"> <app-input
<p-message variant="text" severity="info" icon="pi pi-info-circle"> [control]="goldPrice"
در این بخش می‌توانید قیمت پیش‌فرض هر گرم طلا را وارد کنید. label="قیمت پیش‌فرض هر گرم طلا"
</p-message> name="goldBasePrice"
<form [formGroup]="form" class="mt-6" (submit)="submit()"> type="price"
<field-unit-price [control]="form.controls.price" /> [min]="0"
<app-form-footer-actions (onCancel)="close()" /> hint="در این بخش می‌توانید قیمت پیش‌فرض هر گرم طلا را وارد کنید." />
</form>
</app-card-data>
@@ -1,53 +1,31 @@
import { AbstractForm } from '@/shared/abstractClasses'; import { InputComponent } from '@/shared/components';
import { AppCardComponent, UnitPriceComponent } from '@/shared/components'; import { Component, inject } from '@angular/core';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { fieldControl } from '@/shared/constants'; import { debounce, timer } from 'rxjs';
import { Component, inject, signal } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { Message } from 'primeng/message';
import { IPosConfigGoldPricePayload, IPosConfigGoldPriceResponse } from './models';
import { PosConfigGoldPriceService } from './services/main.service'; import { PosConfigGoldPriceService } from './services/main.service';
@Component({ @Component({
selector: 'pos-config-gold-price-form', selector: 'pos-config-gold-price-form',
templateUrl: 'form.component.html', templateUrl: 'form.component.html',
imports: [ imports: [ReactiveFormsModule, InputComponent],
ReactiveFormsModule,
FormFooterActionsComponent,
AppCardComponent,
Message,
UnitPriceComponent,
],
}) })
export class PosConfigGoldPriceFormComponent extends AbstractForm< export class PosConfigGoldPriceFormComponent {
IPosConfigGoldPricePayload,
IPosConfigGoldPriceResponse
> {
private readonly service = inject(PosConfigGoldPriceService); private readonly service = inject(PosConfigGoldPriceService);
private readonly fb = inject(FormBuilder);
loading = signal(true); goldPrice = this.fb.control(0, { nonNullable: true });
initForm = () => { ngOnInit() {
const form = this.fb.group({ const initialValues = this.service.get();
price: fieldControl.unit_price(), this.goldPrice.setValue(initialValues);
this.goldPrice.valueChanges.pipe(debounce(() => timer(300))).subscribe((value) => {
this.submit();
}); });
return form;
};
form = this.initForm();
override async ngOnInit() {
this.loading.set(true);
const initialValues = await this.service.get();
this.form.controls.price.setValue((initialValues || 0) + '');
this.loading.set(false);
} }
override submitForm() { submit() {
const formValue = this.form.value.price as IPosConfigGoldPricePayload; const formValue = this.goldPrice.value;
this.toastService.success({ text: 'تغییرات با موفقیت اعمال شد.' });
return this.service.submit(formValue); return this.service.submit(formValue);
} }
} }
@@ -1,12 +1,9 @@
<app-card-data cardTitle="ثبت سریع صورت‌حساب" class="w-full"> <div class="gap-4">
<p-message variant="text" severity="info" icon="pi pi-info-circle"> <div class="flex items-center justify-between">
در صورتی که فروش شما تک محصولی / خدماتی است، این قابلیت را فعال کنید.
</p-message>
<form [formGroup]="form" class="mt-6" (submit)="submit()">
<div class="flex items-center gap-4">
<uikit-label name="rapidInvoice"> ثبت سریع صورت‌حساب </uikit-label> <uikit-label name="rapidInvoice"> ثبت سریع صورت‌حساب </uikit-label>
<p-toggleSwitch [formControl]="form.controls.rapidInvoice" /> <p-toggleSwitch [formControl]="rapidInvoice" />
</div> </div>
<app-form-footer-actions (onCancel)="close()" /> <small id="rapidInvoice-help" class="text-muted-color mt-2 block">
</form> در صورتی که فروش شما تک محصولی / خدماتی است، این قابلیت را فعال کنید.
</app-card-data> </small>
</div>
@@ -1,55 +1,33 @@
import { AbstractForm } from '@/shared/abstractClasses';
import { AppCardComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitLabelComponent } from '@/uikit'; import { UikitLabelComponent } from '@/uikit';
import { Component, inject, signal } from '@angular/core'; import { Component, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { Message } from 'primeng/message';
import { ToggleSwitch } from 'primeng/toggleswitch'; import { ToggleSwitch } from 'primeng/toggleswitch';
import { IPosConfigRapidInvoicePayload, IPosConfigRapidInvoiceResponse } from './models'; import { debounce, timer } from 'rxjs';
import { PosConfigRapidInvoiceService } from './services/main.service'; import { PosConfigRapidInvoiceService } from './services/main.service';
@Component({ @Component({
selector: 'pos-config-rapid-invoice-form', selector: 'pos-config-rapid-invoice-form',
templateUrl: 'form.component.html', templateUrl: 'form.component.html',
imports: [ imports: [ReactiveFormsModule, UikitLabelComponent, ToggleSwitch],
ReactiveFormsModule,
FormFooterActionsComponent,
AppCardComponent,
Message,
UikitLabelComponent,
ToggleSwitch,
],
}) })
export class PosConfigRapidInvoiceFormComponent extends AbstractForm< export class PosConfigRapidInvoiceFormComponent {
IPosConfigRapidInvoicePayload,
IPosConfigRapidInvoiceResponse
> {
private readonly service = inject(PosConfigRapidInvoiceService); private readonly service = inject(PosConfigRapidInvoiceService);
loading = signal(true); private readonly fb = inject(FormBuilder);
initForm = () => { rapidInvoice = this.fb.control(false, { nonNullable: true });
const form = this.fb.group({
rapidInvoice: [false], ngOnInit() {
const initialValues = this.service.get();
this.rapidInvoice.setValue(initialValues);
this.rapidInvoice.valueChanges.pipe(debounce(() => timer(300))).subscribe((value) => {
this.submit();
}); });
return form;
};
form = this.initForm();
override async ngOnInit() {
this.loading.set(true);
const initialValues = await this.service.get();
this.form.controls.rapidInvoice.setValue(initialValues || false);
this.loading.set(false);
} }
override submitForm() { submit() {
const formValue = this.form.value.rapidInvoice as IPosConfigRapidInvoicePayload; const formValue = this.rapidInvoice.value;
this.toastService.success({ text: 'تغییرات با موفقیت اعمال شد.' });
return this.service.submit(formValue); return this.service.submit(formValue);
} }
} }
@@ -0,0 +1,9 @@
<div class="gap-4">
<div class="flex items-center justify-between">
<uikit-label name="sendToFiscalActivation"> ارسال سریع صورت‌حساب به مالیات </uikit-label>
<p-toggleSwitch [formControl]="sendToFiscalActivation" />
</div>
<small id="sendToFiscalActivation-help" class="text-muted-color mt-2 block">
در صورتی که می‌خوا‌هید همزمان با ساخت صورت‌حساب، اطلاعات آن به سازمان مالیاتی ارسال شود این قابلیت را فعال کنید.
</small>
</div>
@@ -0,0 +1,33 @@
import { UikitLabelComponent } from '@/uikit';
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { ToggleSwitch } from 'primeng/toggleswitch';
import { debounce, timer } from 'rxjs';
import { PosConfigSendToFiscalActivationService } from './services/main.service';
@Component({
selector: 'pos-config-send-to-fiscal-activation-form',
templateUrl: 'form.component.html',
imports: [ReactiveFormsModule, UikitLabelComponent, ToggleSwitch],
})
export class PosConfigSendToFiscalActivationFormComponent {
private readonly service = inject(PosConfigSendToFiscalActivationService);
private readonly fb = inject(FormBuilder);
sendToFiscalActivation = this.fb.control(false, { nonNullable: true });
ngOnInit() {
const initialValues = this.service.get();
this.sendToFiscalActivation.setValue(initialValues);
this.sendToFiscalActivation.valueChanges.pipe(debounce(() => timer(300))).subscribe((value) => {
this.submit();
});
}
submit() {
const formValue = this.sendToFiscalActivation.value;
return this.service.submit(formValue);
}
}
@@ -0,0 +1,3 @@
export type IPosConfigSendToFiscalActivationResponse = boolean;
export type IPosConfigSendToFiscalActivationPayload = boolean;
@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { LOCAL_STORAGE_KEYS } from 'src/assets/constants';
import {
IPosConfigSendToFiscalActivationPayload,
IPosConfigSendToFiscalActivationResponse,
} from '../models';
@Injectable({ providedIn: 'root' })
export class PosConfigSendToFiscalActivationService {
get(): IPosConfigSendToFiscalActivationResponse {
const defaultPrice = Boolean(
window.localStorage.getItem(LOCAL_STORAGE_KEYS.POS_CONFIG_SEND_TO_FISCAL_ACTIVATION) ===
'true'
);
this.submit(defaultPrice);
return defaultPrice;
}
submit(data: IPosConfigSendToFiscalActivationPayload) {
window.localStorage.setItem(
LOCAL_STORAGE_KEYS.POS_CONFIG_SEND_TO_FISCAL_ACTIVATION,
data.toString()
);
}
}
@@ -1,9 +1,17 @@
<div class="flex h-full w-full flex-col items-center justify-center gap-6 p-4"> <div class="flex h-full w-full flex-col items-center justify-center gap-6 p-4">
<app-card-data cardTitle="تنظیمات صدور صورت‌حساب" class="w-full">
<div class="flex w-full flex-col gap-4">
@if (info()?.guild!.code.toLocaleLowerCase() === 'gold') { @if (info()?.guild!.code.toLocaleLowerCase() === 'gold') {
<pos-config-gold-price-form class="w-full" (onClose)="returnToMainPage()" /> <pos-config-gold-price-form class="w-full" />
<hr />
} }
<pos-config-rapid-invoice-form class="w-full" (onClose)="returnToMainPage()" /> <pos-config-rapid-invoice-form class="w-full" />
<app-card-data cardTitle="تنظیمات فاکتور" class="w-full"> <hr />
<pos-config-send-to-fiscal-activation-form class="w-full" />
</div>
</app-card-data>
<pos-change-password-form class="w-full" />
<app-card-data cardTitle="تنظیمات پرینت صورت‌حساب" class="w-full">
<p-message variant="text" severity="info" icon="pi pi-info-circle"> <p-message variant="text" severity="info" icon="pi pi-info-circle">
هریک از موارد زیر را که می‌خواهید در چاپ فاکتور نمایش داده‌ شوند را انتخاب کنید هریک از موارد زیر را که می‌خواهید در چاپ فاکتور نمایش داده‌ شوند را انتخاب کنید
</p-message> </p-message>
@@ -3,9 +3,11 @@ import { AppCardComponent } from '@/shared/components';
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 { Message } from 'primeng/message'; import { Message } from 'primeng/message';
import { PosChangePasswordFormDialogComponent } from '../components/changePassword/form.component';
import { PosConfigGoldPriceFormComponent } from '../components/goldPrice/form.component'; import { PosConfigGoldPriceFormComponent } from '../components/goldPrice/form.component';
import { PosConfigPrintFormComponent } from '../components/print/form.component'; import { PosConfigPrintFormComponent } from '../components/print/form.component';
import { PosConfigRapidInvoiceFormComponent } from '../components/rapidInvoice/form.component'; import { PosConfigRapidInvoiceFormComponent } from '../components/rapidInvoice/form.component';
import { PosConfigSendToFiscalActivationFormComponent } from '../components/sendToFiscalActivation/form.component';
@Component({ @Component({
selector: 'pos-config-page', selector: 'pos-config-page',
@@ -16,6 +18,8 @@ import { PosConfigRapidInvoiceFormComponent } from '../components/rapidInvoice/f
Message, Message,
PosConfigGoldPriceFormComponent, PosConfigGoldPriceFormComponent,
PosConfigRapidInvoiceFormComponent, PosConfigRapidInvoiceFormComponent,
PosConfigSendToFiscalActivationFormComponent,
PosChangePasswordFormDialogComponent,
], ],
}) })
export class PosConfigPageComponent { export class PosConfigPageComponent {
@@ -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>
@@ -4,10 +4,17 @@ import { InputComponent } from '../input/input.component';
@Component({ @Component({
selector: 'field-unit-price', selector: 'field-unit-price',
template: `<app-input label="قیمت پایه" [control]="control" [name]="name" type="price" />`, template: `<app-input
label="مبلغ واحد"
[control]="control"
[name]="name"
[hint]="hint"
type="price"
/>`,
imports: [ReactiveFormsModule, InputComponent], imports: [ReactiveFormsModule, InputComponent],
}) })
export class UnitPriceComponent { export class UnitPriceComponent {
@Input({ required: true }) control = new FormControl<string>(''); @Input({ required: true }) control = new FormControl<string>('');
@Input() name = 'unit_price'; @Input() name = 'unit_price';
@Input() 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,7 +1,7 @@
<uikit-field [label]="label" [name]="name" [control]="control" [showLabel]="!!label" [showErrors]="showErrors"> <uikit-field [label]="label" [name]="name" [control]="control" [showLabel]="!!label" [showErrors]="showErrors">
@if (labelSuffix) { @if (labelSuffix) {
<ng-template #labelView> <ng-template #labelView>
<div class="flex gap-1 items-center"> <div class="flex items-center gap-1">
<uikit-label [name]="name"> <uikit-label [name]="name">
@if (labelTextView) { @if (labelTextView) {
<ng-container [ngTemplateOutlet]="labelTextView"></ng-container> <ng-container [ngTemplateOutlet]="labelTextView"></ng-container>
@@ -13,12 +13,12 @@
</div> </div>
</ng-template> </ng-template>
} }
@if (type === "switch") { @if (type === 'switch') {
<p-toggleSwitch [attr.disabled]="disabled" [formControl]="control" /> <p-toggleSwitch [attr.disabled]="disabled" [formControl]="control" />
} @else { } @else {
<p-inputgroup> <p-inputgroup>
<!-- [minlength]="minlength || null" --> <!-- [minlength]="minlength || null" -->
@if (type === "price") { @if (type === 'price') {
<p-inputnumber <p-inputnumber
[attr.id]="name" [attr.id]="name"
[formControl]="control" [formControl]="control"
@@ -28,12 +28,11 @@
[attr.type]="htmlType" [attr.type]="htmlType"
[attr.autocomplete]="autocomplete" [attr.autocomplete]="autocomplete"
[attr.disabled]="disabled" [attr.disabled]="disabled"
[class]="` w-full ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`" [class]="`w-full ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
[required]="isRequired" [required]="isRequired"
[invalid]="control.invalid && (control.touched || control.dirty)" [invalid]="control.invalid && (control.touched || control.dirty)"
(onInput)="onPriceInput($event)" (onInput)="onPriceInput($event)"
(onBlur)="blur.emit()" (onBlur)="blur.emit()" />
/>
<!-- (input)="onInput($event)" --> <!-- (input)="onInput($event)" -->
} @else { } @else {
<input <input
@@ -58,8 +57,7 @@
[required]="isRequired" [required]="isRequired"
[invalid]="control.invalid && (control.touched || control.dirty)" [invalid]="control.invalid && (control.touched || control.dirty)"
(onBlur)="blur.emit()" (onBlur)="blur.emit()"
(input)="onInput($event)" (input)="onInput($event)" />
/>
} }
@if (suffixTemp) { @if (suffixTemp) {
<ng-container [ngTemplateOutlet]="suffixTemp" /> <ng-container [ngTemplateOutlet]="suffixTemp" />
@@ -69,6 +67,6 @@
</p-inputgroup> </p-inputgroup>
} }
@if (hint) { @if (hint) {
<small [attr.id]="name + '-help'">{{ hint }}</small> <small [attr.id]="name + '-help'" class="text-muted-color">{{ hint }}</small>
} }
</uikit-field> </uikit-field>
@@ -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>
@@ -5,6 +5,7 @@ export enum LOCAL_STORAGE_KEYS {
LAST_LOGIN_TIME = 'lastLoginTime', LAST_LOGIN_TIME = 'lastLoginTime',
LOGIN_ATTEMPTS = 'loginAttempts', LOGIN_ATTEMPTS = 'loginAttempts',
POS_CONFIG_RAPID_INVOICE = 'posConfigRapidInvoice', POS_CONFIG_RAPID_INVOICE = 'posConfigRapidInvoice',
POS_CONFIG_SEND_TO_FISCAL_ACTIVATION = 'posConfigSendToFiscalActivation',
POS_CONFIG_PRINT = 'posConfigPrint', POS_CONFIG_PRINT = 'posConfigPrint',
POS_CONFIG_GOLD_PRICE = 'posConfigGoldPrice', POS_CONFIG_GOLD_PRICE = 'posConfigGoldPrice',
} }
+13 -3
View File
@@ -15,17 +15,27 @@ form,
gap: 1rem; gap: 1rem;
} }
body.tenant-tis .p-drawer { body.application-pos .p-drawer {
will-change: transform; will-change: transform;
transform: translateZ(0); transform: translateZ(0);
backface-visibility: hidden; backface-visibility: hidden;
box-shadow: none !important;
} }
body.tenant-tis .p-drawer-mask { body.application-pos .p-drawer-mask {
will-change: opacity; will-change: opacity;
} }
body.tenant-tis .p-drawer .p-drawer-content { body.application-pos .p-drawer .p-drawer-content {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
overscroll-behavior: contain; overscroll-behavior: contain;
} }
body.application-pos .p-card {
box-shadow: none !important;
border: 1px solid var(--surface-border) !important;
}
body.application-pos .p-select {
box-shadow: none !important;
}
+1 -1
View File
@@ -32,7 +32,7 @@ if (themeMeta) {
} }
if (config.isPosApplication) { if (config.isPosApplication) {
document.body.classList.add('tenant-tis'); document.body.classList.add('application-pos');
} }
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err)); bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));