feat: refactor dialog components to use light-bottomsheet for improved UX

- Replaced SharedDialogComponent with SharedLightBottomsheetComponent in customer-dialog, order-submitted-dialog, payment form-dialog, and pre-invoice-dialog components.
- Updated styles and properties for light-bottomsheet to enhance responsiveness and usability.
- Modified payload form components to use measure units instead of unit types for better clarity.
- Adjusted form controls to use consistent naming conventions for amounts and percentages.
- Enhanced support for overlay options in select components to manage z-index dynamically.
- Improved support for RTL layouts in input groups and other components.
This commit is contained in:
2026-05-13 13:44:33 +03:30
parent dba960c454
commit 42b8476b96
39 changed files with 396 additions and 143 deletions
+3 -3
View File
@@ -1,9 +1,9 @@
{ {
"$schema": "./node_modules/@angular/service-worker/config/schema.json", "$schema": "./node_modules/@angular/service-worker/config/schema.json",
"appData": { "appData": {
"appVersion": "0.0.9", "appVersion": "0.0.10",
"buildDate": "2026-05-13T06:42:17.700Z", "buildDate": "2026-05-13T09:21:01.165Z",
"buildNumber": 9 "buildNumber": 10
}, },
"assetGroups": [ "assetGroups": [
{ {
@@ -1,6 +1,5 @@
import ISummary from '@/core/models/summary'; import ISummary from '@/core/models/summary';
import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/landing/models'; import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/landing/models';
import { UnitType } from '@/utils';
export interface ISalesInvoicesRawResponse { export interface ISalesInvoicesRawResponse {
id: string; id: string;
@@ -29,7 +28,7 @@ interface Payment {
} }
interface Item { interface Item {
unit_type: UnitType; measure_unit: ISummary;
discount: string; discount: string;
quantity: string; quantity: string;
total_amount: string; total_amount: string;
@@ -46,6 +45,6 @@ interface Good {
barcode: null; barcode: null;
local_sku: null; local_sku: null;
pricing_model: string; pricing_model: string;
unit_type: string; measure_unit: ISummary;
category: ISummary; category: ISummary;
} }
@@ -31,14 +31,14 @@
} }
</ul> </ul>
</div> </div>
@if (isPwaBuild) {
<div class="mt-auto"> <div class="mt-auto">
<hr class="mb-4 mx-4 border-t border-0 border-surface" /> <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"> <div class="px-6 pb-6 text-sm text-muted-color">
نسخه برنامه : <span class="font-semibold">{{ appVersion }}</span> نسخه برنامه : <span class="font-semibold">{{ appVersion }}</span>
</div> </div>
}
</div> </div>
}
</div> </div>
</ng-template> </ng-template>
</p-drawer> </p-drawer>
@@ -53,7 +53,7 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
icon: 'pi pi-fw pi-cog', icon: 'pi pi-fw pi-cog',
}, },
{ {
label: 'درباره ما', label: 'درباره نرم‌افزار',
routerLink: posAboutNamedRoutes.about.meta.pagePath!(), routerLink: posAboutNamedRoutes.about.meta.pagePath!(),
icon: 'pi pi-fw pi-info-circle', icon: 'pi pi-fw pi-info-circle',
}, },
@@ -2,15 +2,7 @@ import { AuthService } from '@/core';
import { LayoutService } from '@/layout/service/layout.service'; import { LayoutService } from '@/layout/service/layout.service';
import { PageLoadingComponent } from '@/shared/components/page-loading.component'; import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import { Component, TemplateRef, ViewChild, computed, inject, signal } from '@angular/core';
AfterViewInit,
Component,
TemplateRef,
ViewChild,
computed,
inject,
signal,
} from '@angular/core';
import { RouterLinkWithHref, RouterOutlet } from '@angular/router'; import { RouterLinkWithHref, RouterOutlet } from '@angular/router';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { Button, ButtonDirective } from 'primeng/button'; import { Button, ButtonDirective } from 'primeng/button';
@@ -38,7 +30,7 @@ import { PosChooseCardsComponent } from './choose-pos.component';
RouterOutlet, RouterOutlet,
], ],
}) })
export class PosPagesLayoutComponent implements AfterViewInit { export class PosPagesLayoutComponent {
constructor() {} constructor() {}
// private readonly nativeBridgeService = inject(NativeBridgeService); // private readonly nativeBridgeService = inject(NativeBridgeService);
@@ -118,9 +110,6 @@ export class PosPagesLayoutComponent implements AfterViewInit {
ngOnInit() { ngOnInit() {
this.layoutService.changeIsFullPage(true); this.layoutService.changeIsFullPage(true);
}
ngAfterViewInit() {
this.layoutService.setTopbarStartSlot(this.topbarStart); this.layoutService.setTopbarStartSlot(this.topbarStart);
this.layoutService.setTopbarCenterSlot(this.topbarCenter); this.layoutService.setTopbarCenterSlot(this.topbarCenter);
this.layoutService.setTopbarEndSlot(this.topbarEnd); this.layoutService.setTopbarEndSlot(this.topbarEnd);
+1 -2
View File
@@ -1,5 +1,4 @@
import ISummary from '@/core/models/summary'; import ISummary from '@/core/models/summary';
import { UnitType } from '@/utils';
export interface IGoodRawResponse { export interface IGoodRawResponse {
id: string; id: string;
@@ -10,7 +9,7 @@ export interface IGoodRawResponse {
name: string; name: string;
}; };
category: ISummary; category: ISummary;
unit_type: UnitType; measure_unit: ISummary;
pricing_model: string; pricing_model: string;
description?: string; description?: string;
image_url: string; image_url: string;
@@ -1,6 +1,55 @@
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4"> <div class="w-full min-h-[60svh] p-4 md:p-6">
<div class="text-center"> <section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
<h2 class="text-xl font-semibold mb-2">درباره سیستم</h2> <div class="bg-gradient-to-l from-emerald-50 via-teal-50 to-cyan-50 p-5 md:p-7">
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p> <p class="mb-2 inline-flex items-center rounded-full bg-white px-3 py-1 text-xs font-semibold text-emerald-700">
{{ brandingInfo.appTitle }}
</p>
<span class="block mb-4 text-xl font-bold text-surface-900 md:text-3xl">درباره نسخه تیس</span>
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
این نسخه برای استفاده روزانه روی دستگاه های PSP بهینه شده است تا عملیات ثبت و صدور صورتحساب‌‌های مالیاتی با سرعت
بالاتر، پایداری بیشتر و تجربه کاربری ساده تر انجام شود.
</p>
</div>
<div class="grid gap-3 p-4 md:grid-cols-3 md:gap-4 md:p-6">
<!-- <article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
<h2 class="mb-2 text-sm font-bold text-surface-900">پایداری در وب ویو</h2>
<p class="text-sm leading-6 text-surface-700">
رفتار رابط کاربری برای محیط WebView تنظیم شده تا روی سخت افزارهای ضعیف تر هم اجرای روان تری داشته باشد.
</p>
</article> -->
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
<span class="block mb-4 text-xl font-bold text-surface-900">تمرکز بر سرعت عملیات</span>
<p class="text-sm leading-6 text-surface-700">
مسیرهای کلیدی مانند انتخاب کالا، ورود مقادیر و ثبت نهایی برای کاهش تاخیر و افزایش سرعت کاربر بهینه شده اند.
</p>
</article>
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
<span class="block mb-4 text-xl font-bold text-surface-900">سازگار با سناریوهای فروش</span>
<p class="text-sm leading-6 text-surface-700">
طراحی این نسخه بر مبنای سناریوهای واقعی فروشگاه انجام شده تا اپراتور با کمترین کلیک فرآیند را تکمیل کند.
</p>
</article>
</div>
<div class="mx-4 mb-4 rounded-xl border border-surface-200 bg-surface-50 p-4 md:mx-6">
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="text-sm text-surface-700">
نسخه برنامه:
<span class="font-semibold text-surface-900">{{ appVersion() }}</span>
</div>
<button pButton type="button" [outlined]="!hasUpdate()" [disabled]="isCheckingUpdate()" (click)="onUpdateCtaClick()">
{{ updateCtaLabel() }}
</button>
</div> </div>
</div> </div>
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
<p class="text-xs leading-6 text-surface-600 md:text-sm">
در صورت نیاز به راهنمایی بیشتر، به صفحه‌ی راهنمای استفاده مراجعه کنید یا با پشتیبانی تماس بگیرید.
</p>
</div>
</section>
</div>
@@ -1,7 +1,87 @@
import { Component } from '@angular/core'; import { brandingConfig } from '@/branding/branding.config';
import { Component, computed, inject, signal } from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { ButtonDirective } from 'primeng/button';
import { filter } from 'rxjs';
@Component({ @Component({
selector: 'pos-about-page', selector: 'pos-about-page',
templateUrl: './root.component.html', templateUrl: './root.component.html',
imports: [ButtonDirective],
}) })
export class PosAboutPageComponent {} export class PosAboutPageComponent {
private readonly swUpdate = inject(SwUpdate);
brandingInfo = brandingConfig;
appVersion = signal('0.0.0');
isPwaBuild = this.swUpdate.isEnabled;
isCheckingUpdate = signal(false);
hasUpdate = signal(false);
isUpdated = signal(false);
updateCtaLabel = computed(() => {
if (!this.isPwaBuild) return 'به روزرسانی فقط در نسخه PWA';
if (this.isUpdated()) return 'به روز است';
if (this.hasUpdate()) return 'به روزرسانی برنامه';
if (this.isCheckingUpdate()) return 'در حال بررسی...';
return 'بررسی به روزرسانی';
});
ngOnInit() {
if (!this.isPwaBuild) return;
this.loadVersion();
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.set(appData?.appVersion || this.appVersion());
this.hasUpdate.set(true);
this.isUpdated.set(false);
});
}
async onUpdateCtaClick() {
if (!this.isPwaBuild || this.isCheckingUpdate()) return;
if (this.hasUpdate()) {
await this.applyUpdate();
return;
}
await this.checkForUpdate();
}
private async checkForUpdate() {
this.isCheckingUpdate.set(true);
this.isUpdated.set(false);
try {
const hasUpdate = await this.swUpdate.checkForUpdate();
this.hasUpdate.set(hasUpdate);
this.isUpdated.set(!hasUpdate);
} finally {
this.isCheckingUpdate.set(false);
}
}
private async applyUpdate() {
this.isCheckingUpdate.set(true);
try {
await this.swUpdate.activateUpdate();
this.isUpdated.set(true);
this.hasUpdate.set(false);
window.location.reload();
} finally {
this.isCheckingUpdate.set(false);
}
}
private loadVersion() {
fetch('/ngsw.json')
.then((res) => res.json())
.then((data: { appData?: { appVersion?: string } }) => {
this.appVersion.set(data?.appData?.appVersion || this.appVersion());
})
.catch(() => {});
}
}
@@ -1,4 +1,9 @@
<shared-dialog [(visible)]="visible" header="اطلاعات مشتری" [modal]="true" [style]="{ width: '560px' }"> <shared-light-bottomsheet
[(visible)]="visible"
header="اطلاعات مشتری"
[modal]="true"
[style]="{ 'max-height': '90svh', height: 'auto' }"
>
<div class="card p-2! flex justify-center"> <div class="card p-2! flex justify-center">
<p-selectbutton [options]="customerTypes" [(ngModel)]="selectedCustomerType" optionValue="value"> <p-selectbutton [options]="customerTypes" [(ngModel)]="selectedCustomerType" optionValue="value">
<ng-template #item let-item> <ng-template #item let-item>
@@ -13,4 +18,4 @@
} @else if (selectedCustomerType() === "LEGAL") { } @else if (selectedCustomerType() === "LEGAL") {
<pos-customer-legal-form (onSubmit)="onSubmitCustomer()" (onClose)="close()" /> <pos-customer-legal-form (onSubmit)="onSubmitCustomer()" (onClose)="close()" />
} }
</shared-dialog> </shared-light-bottomsheet>
@@ -1,5 +1,5 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { CustomerType } from '@/shared/localEnum/constants/customerTypes'; import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
import { Component, EventEmitter, inject, Output, signal } from '@angular/core'; import { Component, EventEmitter, inject, Output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -13,7 +13,7 @@ import { CustomerUnknownFormComponent } from './unknown/form.component';
selector: 'pos-order-customer-dialog', selector: 'pos-order-customer-dialog',
templateUrl: './customer-dialog.component.html', templateUrl: './customer-dialog.component.html',
imports: [ imports: [
SharedDialogComponent, SharedLightBottomsheetComponent,
CustomerIndividualFormComponent, CustomerIndividualFormComponent,
FormsModule, FormsModule,
CustomerLegalFormComponent, CustomerLegalFormComponent,
@@ -16,7 +16,7 @@
</span> </span>
</div> </div>
<span class="text-sm text-ellipsis text-nowrap overflow-hidden"> <span class="text-sm text-ellipsis text-nowrap overflow-hidden">
{{ inOrderGood.quantity }} {{ enumTranslator(inOrderGood.good.unit_type) }} {{ inOrderGood.quantity }} {{ inOrderGood.good.measure_unit.name }}
</span> </span>
</div> </div>
@if (!inOrderGood.discount_amount) { @if (!inOrderGood.discount_amount) {
@@ -53,9 +53,11 @@
</div> </div>
</div> </div>
@if (visible()) {
<pos-payload-form-dialog <pos-payload-form-dialog
[(visible)]="visible" [(visible)]="visible"
[good]="inOrderGood.good" [good]="inOrderGood.good"
[initialValues]="inOrderGood" [initialValues]="inOrderGood"
[orderItemId]="inOrderGood.id" [orderItemId]="inOrderGood.id"
/> />
}
@@ -1,8 +1,8 @@
<shared-dialog <shared-light-bottomsheet
[(visible)]="visible" [(visible)]="visible"
header="ثبت موفقیت‌آمیز" header="ثبت موفقیت‌آمیز"
[modal]="true" [modal]="true"
[style]="{ width: '420px' }" [style]="{ 'max-height': '90svh', height: 'auto' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()"
> >
@@ -15,4 +15,4 @@
<button pButton type="button" outlined (click)="sendInvoice()">ارسال به سامانه مودیان</button> <button pButton type="button" outlined (click)="sendInvoice()">ارسال به سامانه مودیان</button>
<button pButton type="button" outlined (click)="openOrderDetails()">جزئیات</button> <button pButton type="button" outlined (click)="openOrderDetails()">جزئیات</button>
</div> </div>
</shared-dialog> </shared-light-bottomsheet>
@@ -2,7 +2,7 @@ import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import { PosInfoStore } from '@/domains/pos/store'; import { PosInfoStore } from '@/domains/pos/store';
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedDialogComponent } from '@/shared/components'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { formatJalali } from '@/utils'; import { formatJalali } from '@/utils';
import { Component, computed, inject, Input, signal } from '@angular/core'; import { Component, computed, inject, Input, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -15,7 +15,7 @@ import { IPosOrderResponse } from '../../models';
@Component({ @Component({
selector: 'pos-order-submitted-dialog', selector: 'pos-order-submitted-dialog',
templateUrl: 'order-submitted-dialog.component.html', templateUrl: 'order-submitted-dialog.component.html',
imports: [SharedDialogComponent, ButtonDirective], imports: [SharedLightBottomsheetComponent, ButtonDirective],
}) })
export class PosOrderSubmittedDialogComponent extends AbstractDialog { export class PosOrderSubmittedDialogComponent extends AbstractDialog {
@Input({ required: true }) invoice!: IPosOrderResponse; @Input({ required: true }) invoice!: IPosOrderResponse;
@@ -12,7 +12,7 @@
} @else if (isStandardMode()) { } @else if (isStandardMode()) {
<pos-standard-payload-form <pos-standard-payload-form
[initialValues]="standardPayload()" [initialValues]="standardPayload()"
[unitType]="good.unit_type" [measureUnit]="good.measure_unit"
[editMode]="editMode()" [editMode]="editMode()"
(onSubmit)="submit($event)" (onSubmit)="submit($event)"
/> />
@@ -6,7 +6,7 @@
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.wages_percentage" [percentageControl]="form.controls.payload.controls.wages_percentage"
[amountControl]="form.controls.payload.controls.wages_amount" [amountControl]="form.controls.payload.controls.wages"
[baseAmount]="unitWithQuantity()" [baseAmount]="unitWithQuantity()"
[minAmount]="0" [minAmount]="0"
[maxAmount]="unitWithQuantity()" [maxAmount]="unitWithQuantity()"
@@ -15,7 +15,7 @@
/> />
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.commission_percentage" [percentageControl]="form.controls.payload.controls.commission_percentage"
[amountControl]="form.controls.payload.controls.commission_amount" [amountControl]="form.controls.payload.controls.commission"
[baseAmount]="unitWithQuantity()" [baseAmount]="unitWithQuantity()"
[minAmount]="0" [minAmount]="0"
[maxAmount]="unitWithQuantity()" [maxAmount]="unitWithQuantity()"
@@ -24,7 +24,7 @@
/> />
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.profit_percentage" [percentageControl]="form.controls.payload.controls.profit_percentage"
[amountControl]="form.controls.payload.controls.profit_amount" [amountControl]="form.controls.payload.controls.profit"
[baseAmount]="totalAmountBeforeProfit()" [baseAmount]="totalAmountBeforeProfit()"
[minAmount]="0" [minAmount]="0"
[maxAmount]="totalAmountBeforeProfit()" [maxAmount]="totalAmountBeforeProfit()"
@@ -35,7 +35,7 @@
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage" [percentageControl]="form.controls.discount_percentage"
[amountControl]="form.controls.discount_amount" [amountControl]="form.controls.discount"
[baseAmount]="baseAmountForDiscountCalculation()" [baseAmount]="baseAmountForDiscountCalculation()"
[minAmount]="0" [minAmount]="0"
[maxAmount]="baseAmountForDiscountCalculation()" [maxAmount]="baseAmountForDiscountCalculation()"
@@ -59,7 +59,7 @@
<pos-form-dialog-amount-card-template <pos-form-dialog-amount-card-template
[totalAmount]="totalAmount()" [totalAmount]="totalAmount()"
[baseTotalAmount]="baseTotalAmount()" [baseTotalAmount]="baseTotalAmount()"
[discountAmount]="form.controls.discount_amount.value || 0" [discountAmount]="form.controls.discount.value || 0"
[taxAmount]="taxAmount()" [taxAmount]="taxAmount()"
/> />
<button pButton class="sm:w-auto w-full" (click)="submit()">{{ preparedCTAText() }}</button> <button pButton class="sm:w-auto w-full" (click)="submit()">{{ preparedCTAText() }}</button>
@@ -31,6 +31,17 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
IPosOrderItem<IGoldPayload>, IPosOrderItem<IGoldPayload>,
IPosOrderItem<IGoldPayload> IPosOrderItem<IGoldPayload>
> { > {
override defaultValues: Partial<IPosOrderItem<IGoldPayload>> = {
unit_price: 200_000_000,
quantity: 2,
payload: {
commission: 10_000,
wages: 10_000,
profit: 10_000,
karat: '18' as TGoldKarat,
} as any,
};
@Output() onSubmitForm = new EventEmitter<IGoldPayload>(); @Output() onSubmitForm = new EventEmitter<IGoldPayload>();
@Output() onChangeTotalAmount = new EventEmitter<number>(); @Output() onChangeTotalAmount = new EventEmitter<number>();
@@ -59,14 +70,14 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
const form = this.fb.group({ const form = this.fb.group({
unit_price: [this.initialValues?.unit_price || 0, [Validators.required]], unit_price: [this.initialValues?.unit_price || 0, [Validators.required]],
quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]], quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]],
discount_amount: [this.initialValues?.discount || 0], discount: [this.initialValues?.discount || 0],
discount_percentage: [0, [Validators.max(100), Validators.min(0)]], discount_percentage: [0, [Validators.max(100), Validators.min(0)]],
payload: this.fb.group({ payload: this.fb.group({
commission_amount: [this.initialValues?.payload?.commission || 0, [Validators.required]], commission: [this.initialValues?.payload?.commission || 0, [Validators.required]],
commission_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]], commission_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
wages_amount: [this.initialValues?.payload?.wages || 0, [Validators.required]], wages: [this.initialValues?.payload?.wages || 0, [Validators.required]],
wages_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]], wages_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
profit_amount: [this.initialValues?.payload?.profit || 0, [Validators.required]], profit: [this.initialValues?.payload?.profit || 0, [Validators.required]],
profit_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]], profit_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
karat: [this.initialValues?.payload?.karat || '', [Validators.required]], karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
}), }),
@@ -76,17 +87,15 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
this.updateCalculateAmount(value as any); this.updateCalculateAmount(value as any);
}); });
form.controls.payload.controls.profit_amount.valueChanges form.controls.payload.controls.profit.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
.pipe(takeUntilDestroyed())
.subscribe(() => {
// if ((form.controls.discount_amount.value || 0) > (value || 0)) { // if ((form.controls.discount_amount.value || 0) > (value || 0)) {
form.controls.discount_amount.setValue(0, { emitEvent: false }); form.controls.discount.setValue(0, { emitEvent: false });
// } // }
}); });
form.controls.payload.controls.profit_percentage.valueChanges form.controls.payload.controls.profit_percentage.valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(() => { .subscribe(() => {
form.controls.discount_amount.setValue(0, { emitEvent: false }); form.controls.discount.setValue(0, { emitEvent: false });
}); });
return form; return form;
@@ -101,19 +110,19 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
base_total_amount: this.baseTotalAmount(), base_total_amount: this.baseTotalAmount(),
tax_amount: this.taxAmount(), tax_amount: this.taxAmount(),
payload: { payload: {
commission: this.form.controls.payload.controls.commission_amount.value || 0, commission: this.form.controls.payload.controls.commission.value || 0,
karat: this.form.controls.payload.controls.karat.value as TGoldKarat, karat: this.form.controls.payload.controls.karat.value as TGoldKarat,
profit: this.form.controls.payload.controls.profit_amount.value || 0, profit: this.form.controls.payload.controls.profit.value || 0,
wages: this.form.controls.payload.controls.wages_amount.value || 0, wages: this.form.controls.payload.controls.wages.value || 0,
}, },
}); });
} }
updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) { updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) {
const commissionAmount = Number(payload.payload?.commission_amount ?? '0'); const commissionAmount = Number(payload.payload?.commission ?? '0');
const wageAmount = Number(payload.payload?.wages_amount ?? '0'); const wageAmount = Number(payload.payload?.wages ?? '0');
const discountAmount = Number(payload.discount_amount ?? '0'); const discountAmount = Number(payload.discount ?? '0');
const profitAmount = Number(payload.payload?.profit_amount ?? '0'); const profitAmount = Number(payload.payload?.profit ?? '0');
const unitPrice = Number(payload.unit_price ?? '0'); const unitPrice = Number(payload.unit_price ?? '0');
const quantity = Number(payload.quantity ?? '0'); const quantity = Number(payload.quantity ?? '0');
@@ -3,9 +3,9 @@
<app-input <app-input
[control]="form.controls.quantity" [control]="form.controls.quantity"
name="quantity" name="quantity"
[label]="preparedFormVisuals().quantityLabel" [label]="measureUnit.name"
type="number" type="number"
[suffix]="preparedFormVisuals().quantitySymbolText" [suffix]="measureUnit.name"
[min]="0" [min]="0"
/> />
@@ -1,7 +1,8 @@
import ISummary from '@/core/models/summary';
import { AbstractForm } from '@/shared/abstractClasses'; import { AbstractForm } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components'; import { InputComponent } from '@/shared/components';
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component'; import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
import { formatWithCurrency, getGoodUnitTypeProperties, UnitType } from '@/utils'; import { formatWithCurrency } from '@/utils';
import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core'; import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule, Validators } from '@angular/forms';
@@ -24,7 +25,7 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
IPosOrderItem<IStandardPayload>, IPosOrderItem<IStandardPayload>,
IPosOrderItem<IStandardPayload> IPosOrderItem<IStandardPayload>
> { > {
@Input({ required: true }) unitType!: UnitType; @Input({ required: true }) measureUnit!: ISummary;
@Output() onChangeTotalAmount = new EventEmitter<number>(); @Output() onChangeTotalAmount = new EventEmitter<number>();
private readonly initialForm = () => { private readonly initialForm = () => {
@@ -53,8 +54,6 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
}); });
} }
preparedFormVisuals = computed(() => getGoodUnitTypeProperties(this.unitType));
baseTotalAmount = signal<number>(0); baseTotalAmount = signal<number>(0);
totalAmount = signal<number>(0); totalAmount = signal<number>(0);
discountAmount = signal<number>(0); discountAmount = signal<number>(0);
@@ -1,8 +1,8 @@
<shared-dialog <shared-light-bottomsheet
[(visible)]="visible" [(visible)]="visible"
header="اطلاعات پرداخت" header="اطلاعات پرداخت"
[modal]="true" [modal]="true"
[style]="{ width: '500px' }" [style]="{ 'max-height': '90svh', height: 'auto' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()"
> >
@@ -71,4 +71,4 @@
/> />
</form> </form>
</div> </div>
</shared-dialog> </shared-light-bottomsheet>
@@ -3,7 +3,7 @@ import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import { AbstractFormDialog } from '@/shared/abstractClasses'; import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, KeyValueComponent } from '@/shared/components'; import { InputComponent, KeyValueComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFieldComponent } from '@/uikit'; import { UikitFieldComponent } from '@/uikit';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
@@ -27,7 +27,7 @@ import { PosLandingStore } from '../../store/main.store';
FormFooterActionsComponent, FormFooterActionsComponent,
KeyValueComponent, KeyValueComponent,
ButtonDirective, ButtonDirective,
SharedDialogComponent, SharedLightBottomsheetComponent,
Select, Select,
UikitFieldComponent, UikitFieldComponent,
], ],
@@ -1,11 +1,11 @@
<shared-dialog <shared-light-bottomsheet
[(visible)]="visible" [(visible)]="visible"
header="پیش فاکتور" header="پیش فاکتور"
[modal]="true" [modal]="true"
[style]="{ width: '500px' }" [style]="{ 'max-height': '90svh', height: 'auto' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()"
> >
<form [formGroup]="form"></form> <form [formGroup]="form"></form>
<uikit-datepicker [control]="form.controls.invoiceDate" label="تاریخ فاکتور" /> <uikit-datepicker [control]="form.controls.invoiceDate" label="تاریخ فاکتور" />
</shared-dialog> </shared-light-bottomsheet>
@@ -1,14 +1,14 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit'; import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { PosLandingStore } from '../store/main.store'; import { PosLandingStore } from '../store/main.store';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({ @Component({
selector: 'pos-pre-invoice-dialog', selector: 'pos-pre-invoice-dialog',
templateUrl: './pre-invoice-dialog.component.html', templateUrl: './pre-invoice-dialog.component.html',
imports: [SharedDialogComponent, UikitFlatpickrJalaliComponent, ReactiveFormsModule], imports: [SharedLightBottomsheetComponent, UikitFlatpickrJalaliComponent, ReactiveFormsModule],
}) })
export class PosPreInvoiceDialogComponent extends AbstractDialog { export class PosPreInvoiceDialogComponent extends AbstractDialog {
private readonly store = inject(PosLandingStore); private readonly store = inject(PosLandingStore);
@@ -1,5 +1,5 @@
import ISummary from '@/core/models/summary';
import { IGoodResponse } from '@/domains/pos/models/good.io'; import { IGoodResponse } from '@/domains/pos/models/good.io';
import { UnitType } from '@/utils';
import { TPosOrderGoodPayload } from './payload'; import { TPosOrderGoodPayload } from './payload';
export interface IPosOrderItem<T = TPosOrderGoodPayload> { export interface IPosOrderItem<T = TPosOrderGoodPayload> {
@@ -7,7 +7,7 @@ export interface IPosOrderItem<T = TPosOrderGoodPayload> {
quantity: number; quantity: number;
total_amount: number; total_amount: number;
discount_amount: number; discount_amount: number;
unit_type: UnitType; measure_unit: ISummary;
good_id?: string; good_id?: string;
service_id?: string; service_id?: string;
notes?: string; notes?: string;
@@ -221,7 +221,7 @@ export class PosLandingStore {
items: this.inOrderGoods().map(({ good, ...rest }) => ({ items: this.inOrderGoods().map(({ good, ...rest }) => ({
...rest, ...rest,
good_id: good.id, good_id: good.id,
unit_type: good.unit_type, measure_unit: good.measure_unit,
})), })),
invoice_date: new Date().toISOString(), invoice_date: new Date().toISOString(),
payments: this.payments(), payments: this.payments(),
@@ -34,17 +34,11 @@
</div> </div>
</button> </button>
} }
<shared-dialog <shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [modal]="true" [closable]="true" header="فاکتور">
[(visible)]="showInvoiceBottomSheet"
position="bottom"
[modal]="true"
[closable]="true"
header="فاکتور"
>
@if (showInvoiceBottomSheet()) { @if (showInvoiceBottomSheet()) {
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSuccess)="onSuccessSubmit($event)" /> <pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSuccess)="onSuccessSubmit($event)" />
} }
</shared-dialog> </shared-light-bottomsheet>
@if (responseInvoice()) { @if (responseInvoice()) {
<pos-order-submitted-dialog [(visible)]="isVisibleSuccessDialog" [invoice]="responseInvoice()!" /> <pos-order-submitted-dialog [(visible)]="isVisibleSuccessDialog" [invoice]="responseInvoice()!" />
@@ -2,7 +2,7 @@
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { PosInfoStore } from '@/domains/pos/store/pos.store'; import { PosInfoStore } from '@/domains/pos/store/pos.store';
import { AbstractIsMobileComponent } from '@/shared/abstractClasses/abstract-is-mobile'; import { AbstractIsMobileComponent } from '@/shared/abstractClasses/abstract-is-mobile';
import { SharedDialogComponent } from '@/shared/components'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { PageLoadingComponent } from '@/shared/components/page-loading.component'; 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';
@@ -24,7 +24,7 @@ import { PosLandingStore } from '../store/main.store';
PosOrderSectionComponent, PosOrderSectionComponent,
PageLoadingComponent, PageLoadingComponent,
PriceMaskDirective, PriceMaskDirective,
SharedDialogComponent, SharedLightBottomsheetComponent,
ButtonDirective, ButtonDirective,
PosOrderSubmittedDialogComponent, PosOrderSubmittedDialogComponent,
], ],
@@ -1,6 +1,47 @@
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4"> <div class="w-full min-h-[60svh] p-4 md:p-6">
<div class="text-center"> <section class="mx-auto max-w-3xl overflow-hidden rounded-2xl border border-surface-200 bg-white">
<h2 class="text-xl font-semibold mb-2">پشتیبانی</h2> <div class="bg-gradient-to-l from-sky-50 via-cyan-50 to-teal-50 p-5 md:p-7">
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p> <h1 class="text-2xl font-bold text-surface-900 md:text-3xl">پشتیبانی تیس</h1>
<p class="mt-3 max-w-2xl text-sm leading-7 text-surface-700 md:text-base">
در صورت بروز مشکل در ثبت سفارش، صدور صورتحساب یا تنظیمات دستگاه، تیم پشتیبانی آماده پاسخگویی است. لطفا هنگام
تماس، نام پایانه و شماره سریال دستگاه را همراه داشته باشید.
</p>
</div> </div>
<div class="grid gap-3 p-4 md:grid-cols-2 md:gap-4 md:p-6">
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
<h2 class="mb-3 text-sm font-bold text-surface-900">شماره های تماس مستقیم</h2>
<ul class="space-y-2 text-sm text-surface-700">
<li class="flex items-center justify-between gap-2">
<span>واحد پشتیبانی 1</span>
<a class="font-semibold text-primary" href="tel:02191012345" dir="ltr">021-91012345</a>
</li>
<li class="flex items-center justify-between gap-2">
<span>واحد پشتیبانی 2</span>
<a class="font-semibold text-primary" href="tel:02191012346" dir="ltr">021-91012346</a>
</li>
<li class="flex items-center justify-between gap-2">
<span>پاسخگویی اضطراری</span>
<a class="font-semibold text-primary" href="tel:09121234567" dir="ltr">0912-123-4567</a>
</li>
</ul>
</article>
<article class="rounded-xl border border-surface-200 bg-surface-50 p-4">
<h2 class="mb-3 text-sm font-bold text-surface-900">راهنمای سریع قبل از تماس</h2>
<ul class="space-y-2 text-sm leading-6 text-surface-700">
<li>اتصال اینترنت دستگاه را بررسی کنید.</li>
<li>یک بار برنامه را ببندید و دوباره اجرا کنید.</li>
<li>در صفحه درباره نرم افزار، نسخه برنامه را اعلام کنید.</li>
<li>در صورت خطا، متن خطا یا تصویر آن را برای پشتیبانی ارسال کنید.</li>
</ul>
</article>
</div>
<div class="border-t border-surface-200 bg-surface-50 px-4 py-4 md:px-6">
<p class="text-xs leading-6 text-surface-600 md:text-sm">
ساعات پاسخگویی: شنبه تا چهارشنبه 8:30 تا 17:30 و پنجشنبه 8:30 تا 13:00
</p>
</div>
</section>
</div> </div>
@@ -38,6 +38,11 @@ export class AppLayout {
public renderer: Renderer2, public renderer: Renderer2,
public router: Router, public router: Router,
) { ) {
this.layoutService.headerSlot$.subscribe((tpl) => (this.topBarMoreAction = tpl));
this.layoutService.topbarStartSlot$.subscribe((tpl) => (this.topBarStartAction = tpl));
this.layoutService.topbarCenterSlot$.subscribe((tpl) => (this.topBarCenterAction = tpl));
this.layoutService.topbarEndSlot$.subscribe((tpl) => (this.topBarEndAction = tpl));
this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => { this.overlayMenuOpenSubscription = this.layoutService.overlayOpen$.subscribe(() => {
if (!this.menuOutsideClickListener) { if (!this.menuOutsideClickListener) {
this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => { this.menuOutsideClickListener = this.renderer.listen('document', 'click', (event) => {
@@ -133,11 +138,6 @@ export class AppLayout {
}); });
ngOnInit() { ngOnInit() {
this.layoutService.headerSlot$.subscribe((tpl) => (this.topBarMoreAction = tpl));
this.layoutService.topbarStartSlot$.subscribe((tpl) => (this.topBarStartAction = tpl));
this.layoutService.topbarCenterSlot$.subscribe((tpl) => (this.topBarCenterAction = tpl));
this.layoutService.topbarEndSlot$.subscribe((tpl) => (this.topBarEndAction = tpl));
if (window.location.pathname === '/') { if (window.location.pathname === '/') {
if (!this.authService.currentAccount()) { if (!this.authService.currentAccount()) {
this.router.navigateByUrl('auth'); this.router.navigateByUrl('auth');
@@ -37,14 +37,14 @@ export abstract class AbstractForm<
} }
ngOnInit() { ngOnInit() {
this.form.reset(this.initialValues ?? this.defaultValues); this.form.patchValue(this.initialValues ?? this.defaultValues);
// Object.entries(this.form.controls).forEach(([key, control]) => { // Object.entries(this.form.controls).forEach(([key, control]) => {
// console.log(key, control); // console.log(key, control);
// }); // });
} }
ngOnChanges() { ngOnChanges() {
this.form.reset(this.initialValues ?? this.defaultValues); this.form.patchValue(this.initialValues ?? this.defaultValues);
} }
onSuccess(response: Response): void {} onSuccess(response: Response): void {}
@@ -7,6 +7,7 @@
[formControl]="control" [formControl]="control"
[showClear]="showClear" [showClear]="showClear"
[name]="name || type" [name]="name || type"
[overlayOptions]="overlayOptions()"
appendTo="body" appendTo="body"
/> />
</uikit-field> </uikit-field>
+21 -1
View File
@@ -1,5 +1,6 @@
import { UikitFieldComponent } from '@/uikit'; import { UikitFieldComponent } from '@/uikit';
import { Component, computed, inject, Input } from '@angular/core'; import { DOCUMENT } from '@angular/common';
import { Component, Inject, computed, inject, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select'; import { Select } from 'primeng/select';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -19,6 +20,9 @@ export class EnumSelectComponent extends AbstractSelectComponent<string, IApiEnu
@Input() customLabel?: string; @Input() customLabel?: string;
private readonly service = inject(EnumsService); private readonly service = inject(EnumsService);
constructor(@Inject(DOCUMENT) private readonly document: Document) {
super();
}
label = computed(() => this.customLabel ?? apiEnumTranslates[this.type]); label = computed(() => this.customLabel ?? apiEnumTranslates[this.type]);
@@ -29,4 +33,20 @@ export class EnumSelectComponent extends AbstractSelectComponent<string, IApiEnu
ngOnInit() { ngOnInit() {
this.getData(); this.getData();
} }
overlayOptions() {
return {
autoZIndex: true,
baseZIndex: this.overlayBaseZIndex(),
};
}
overlayBaseZIndex() {
const drawers = Array.from(this.document.body.querySelectorAll('.p-drawer,[data-pc-name="drawer"]')) as HTMLElement[];
const zIndexes = drawers
.map((drawer) => Number.parseInt(drawer.style.zIndex || '0', 10))
.filter((zIndex) => Number.isFinite(zIndex) && zIndex > 0);
return zIndexes.length ? Math.max(...zIndexes) + 2 : 1000;
}
} }
@@ -24,8 +24,8 @@
[attr.name]="name" [attr.name]="name"
[attr.placeholder]="placeholder" [attr.placeholder]="placeholder"
[attr.autocomplete]="autocomplete" [attr.autocomplete]="autocomplete"
[class]="` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`" [inputStyle]="{ direction: 'ltr' }"
dir="ltr" [inputStyleClass]="` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
[required]="required" [required]="required"
[invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)" [invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)"
(onInput)="onPriceInput($event)" (onInput)="onPriceInput($event)"
@@ -58,9 +58,10 @@
<p-select <p-select
[formControl]="selectedType" [formControl]="selectedType"
[options]="typeSelectOptions" [options]="typeSelectOptions"
tabindex="-1"
optionValue="value" optionValue="value"
size="small" size="small"
class="border-0!" class="border-none! shadow-none! w-full!"
/> />
</p-inputgroup-addon> </p-inputgroup-addon>
</p-inputgroup> </p-inputgroup>
@@ -180,11 +180,16 @@ export class AmountPercentageInputComponent {
const amountValue = Number(this.amountControl.value || 0); const amountValue = Number(this.amountControl.value || 0);
const percentageValue = Number(this.percentageControl.value || 0); const percentageValue = Number(this.percentageControl.value || 0);
const isPercentageType = percentageValue > 0 && amountValue <= 0; const isPercentageType = percentageValue > 0 && amountValue <= 0;
console.log(amountValue, percentageValue, isPercentageType);
this.preparedLabel.set(this.prepareLabel(isPercentageType)); this.preparedLabel.set(this.prepareLabel(isPercentageType));
this.selectedType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => { this.selectedType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
this.preparedLabel.set(this.prepareLabel(value === 'percentage')); this.preparedLabel.set(this.prepareLabel(value === 'percentage'));
}); });
this.selectedType.setValue(isPercentageType ? 'percentage' : 'amount');
} }
ngOnChanges() { ngOnChanges() {
@@ -1,6 +1,6 @@
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<p-checkBox [formControl]="control" [name]="name" [id]="name" binary class="shrink-0"> </p-checkBox> <p-checkBox [formControl]="control" [name]="name" [inputId]="name" binary class="shrink-0"> </p-checkBox>
<uikit-label [name]="name"> {{ label }} </uikit-label> <uikit-label [name]="name"> {{ label }} </uikit-label>
</div> </div>
@@ -1,20 +1,21 @@
<div class="light-bottomsheet-mask" (click)="onMaskClick()"></div> <div class="light-bottomsheet-mask" (click)="onMaskClick()"></div>
<section <section class="light-bottomsheet-panel" [ngStyle]="style || { 'max-height': mobileDrawerHeight, height: 'auto' }">
class="light-bottomsheet-panel"
role="dialog"
aria-modal="true"
[attr.aria-hidden]="!visible"
[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">
<h3 class="light-bottomsheet-title">{{ header }}</h3> <span class="text-xl font-bold">{{ header }}</span>
@if (closable) { @if (closable) {
<button type="button" class="light-bottomsheet-close" (click)="onVisibilityChange(false)" aria-label="Close"> <p-button
× type="button"
</button> icon="pi pi-times"
text
size="small"
class="light-bottomsheet-close"
(click)="onVisibilityChange(false)"
aria-label="Close"
>
</p-button>
} }
</header> </header>
} }
@@ -1,27 +1,29 @@
import { NgStyle } from '@angular/common'; import { DOCUMENT, NgStyle } from '@angular/common';
import { DOCUMENT } from '@angular/common';
import { import {
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
Inject, Inject,
Input, Input,
OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
Output, Output,
Renderer2, Renderer2,
SimpleChanges,
} from '@angular/core'; } from '@angular/core';
import { Button } from 'primeng/button';
@Component({ @Component({
selector: 'shared-light-bottomsheet', selector: 'shared-light-bottomsheet',
templateUrl: './light-bottomsheet.component.html', templateUrl: './light-bottomsheet.component.html',
imports: [NgStyle], imports: [NgStyle, Button],
styles: [ styles: [
` `
:host { :host {
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 11020; z-index: 1210;
} }
.light-bottomsheet-mask { .light-bottomsheet-mask {
@@ -66,8 +68,6 @@ import {
.light-bottomsheet-title { .light-bottomsheet-title {
margin: 0; margin: 0;
font-size: 0.95rem;
font-weight: 600;
} }
.light-bottomsheet-close { .light-bottomsheet-close {
@@ -98,11 +98,17 @@ import {
`, `,
], ],
host: { host: {
'[attr.role]': '"complementary"',
'[attr.aria-modal]': 'true',
'[attr.pfocustrap]': 'true',
'[attr.data-pc-name]': "'drawer'",
'[attr.data-pc-section]': "'root'",
'[attr.aria-hidden]': '!visible',
'[style.display]': 'visible ? "block" : "none"', '[style.display]': 'visible ? "block" : "none"',
'[style.--sheet-transition]': 'transitionOptions', '[style.--sheet-transition]': 'transitionOptions',
}, },
}) })
export class SharedLightBottomsheetComponent implements OnInit, OnDestroy { export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnChanges {
@Input() header = ''; @Input() header = '';
@Input() visible = false; @Input() visible = false;
@Output() visibleChange = new EventEmitter<boolean>(); @Output() visibleChange = new EventEmitter<boolean>();
@@ -116,6 +122,7 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy {
@Output() onHide = new EventEmitter<any>(); @Output() onHide = new EventEmitter<any>();
private originalParent: Node | null = null; private originalParent: Node | null = null;
private readonly defaultDrawerZIndex = 1102;
constructor( constructor(
private readonly elementRef: ElementRef<HTMLElement>, private readonly elementRef: ElementRef<HTMLElement>,
@@ -127,9 +134,20 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy {
const host = this.elementRef.nativeElement; const host = this.elementRef.nativeElement;
this.originalParent = host.parentNode; this.originalParent = host.parentNode;
this.renderer.appendChild(this.document.body, host); this.renderer.appendChild(this.document.body, host);
this.syncZIndex();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['visible'] && this.visible) {
this.syncZIndex();
}
} }
ngOnDestroy() { ngOnDestroy() {
this.removeDrawer();
}
private removeDrawer() {
const host = this.elementRef.nativeElement; const host = this.elementRef.nativeElement;
if (this.originalParent) { if (this.originalParent) {
this.renderer.appendChild(this.originalParent, host); this.renderer.appendChild(this.originalParent, host);
@@ -139,6 +157,9 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy {
onVisibilityChange(nextValue: boolean) { onVisibilityChange(nextValue: boolean) {
this.visible = nextValue; this.visible = nextValue;
this.visibleChange.emit(nextValue); this.visibleChange.emit(nextValue);
if (nextValue) {
this.syncZIndex();
}
if (!nextValue) { if (!nextValue) {
this.onHide.emit(); this.onHide.emit();
} }
@@ -149,4 +170,19 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy {
this.onVisibilityChange(false); this.onVisibilityChange(false);
} }
} }
private syncZIndex() {
const host = this.elementRef.nativeElement;
const siblingDrawers = Array.from(
this.document.body.querySelectorAll('.p-drawer'),
) as HTMLElement[];
const drawerZIndexes = siblingDrawers
.map((drawer) => Number.parseInt(drawer.style.zIndex || '0', 10))
.filter((zIndex) => Number.isFinite(zIndex) && zIndex > 0);
const maxDrawerZIndex = drawerZIndexes.length
? Math.max(...drawerZIndexes)
: this.defaultDrawerZIndex;
this.renderer.setStyle(host, 'z-index', `${maxDrawerZIndex + 1}`);
}
} }
@@ -7,6 +7,7 @@
[formControl]="control" [formControl]="control"
[showClear]="showClear" [showClear]="showClear"
[filter]="true" [filter]="true"
[overlayOptions]="overlayOptions()"
appendTo="body" appendTo="body"
/> />
</uikit-field> </uikit-field>
+22 -1
View File
@@ -1,5 +1,6 @@
import { UikitFieldComponent } from '@/uikit'; import { UikitFieldComponent } from '@/uikit';
import { Component, computed, Input } from '@angular/core'; import { DOCUMENT } from '@angular/common';
import { Component, Inject, computed, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select'; import { Select } from 'primeng/select';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -16,6 +17,10 @@ export class EnumSelectComponent extends AbstractSelectComponent<ISelectItem, IS
@Input() type!: TLocalEnum; @Input() type!: TLocalEnum;
@Input() customLabel?: string; @Input() customLabel?: string;
constructor(@Inject(DOCUMENT) private readonly document: Document) {
super();
}
label = computed(() => this.customLabel ?? LOCAL_ENUMS[this.type].label); label = computed(() => this.customLabel ?? LOCAL_ENUMS[this.type].label);
override getDataService() { override getDataService() {
@@ -27,4 +32,20 @@ export class EnumSelectComponent extends AbstractSelectComponent<ISelectItem, IS
ngOnInit() { ngOnInit() {
this.getData(); this.getData();
} }
overlayOptions() {
return {
autoZIndex: true,
baseZIndex: this.overlayBaseZIndex(),
};
}
overlayBaseZIndex() {
const drawers = Array.from(this.document.body.querySelectorAll('.p-drawer,[data-pc-name="drawer"]')) as HTMLElement[];
const zIndexes = drawers
.map((drawer) => Number.parseInt(drawer.style.zIndex || '0', 10))
.filter((zIndex) => Number.isFinite(zIndex) && zIndex > 0);
return zIndexes.length ? Math.max(...zIndexes) + 2 : 1000;
}
} }
+4 -3
View File
@@ -48,11 +48,12 @@ input.p-inputtext {
margin-inline-end: 0.5rem; margin-inline-end: 0.5rem;
} }
.p-inputgroup > .p-component.hasSuffix:first-child { .p-inputgroup > .p-component.hasSuffix:first-child,
.p-inputgroup > .p-component > .hasSuffix {
border-start-start-radius: 0 !important; border-start-start-radius: 0 !important;
border-end-start-radius: 0 !important; border-end-start-radius: 0 !important;
border-end-end-radius: var(--p-inputgroup-addon-border-radius); border-end-end-radius: var(--p-inputgroup-addon-border-radius) !important;
border-start-end-radius: var(--p-inputgroup-addon-border-radius); border-start-end-radius: var(--p-inputgroup-addon-border-radius) !important;
} }
.p-tablist { .p-tablist {