create amountPercentage field component
This commit is contained in:
@@ -67,10 +67,6 @@ export class ConsumerCustomerListComponent extends AbstractList<ICustomerRespons
|
|||||||
}
|
}
|
||||||
|
|
||||||
toSinglePage(item: ICustomerResponse) {
|
toSinglePage(item: ICustomerResponse) {
|
||||||
console.log(item);
|
|
||||||
|
|
||||||
console.log(consumerCustomersNamedRoutes.customer.meta);
|
|
||||||
|
|
||||||
this.router.navigateByUrl(consumerCustomersNamedRoutes.customer.meta.pagePath!(item.id));
|
this.router.navigateByUrl(consumerCustomersNamedRoutes.customer.meta.pagePath!(item.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface ICustomerSaleInvoicesRawResponse {
|
|||||||
account: ConsumerAccount;
|
account: ConsumerAccount;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
items_count: number;
|
items_count: number;
|
||||||
|
payments: Payments[];
|
||||||
}
|
}
|
||||||
export interface ICustomerSaleInvoicesResponse extends ICustomerSaleInvoicesRawResponse {}
|
export interface ICustomerSaleInvoicesResponse extends ICustomerSaleInvoicesRawResponse {}
|
||||||
|
|
||||||
@@ -35,3 +36,9 @@ interface Pos extends ISummary {
|
|||||||
interface Complex extends ISummary {
|
interface Complex extends ISummary {
|
||||||
business_activity: ISummary;
|
business_activity: ISummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Payments {
|
||||||
|
amount: string;
|
||||||
|
paid_at: string;
|
||||||
|
payment_method: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ export class ConsumerCustomerSaleInvoiceStore extends EntityStore<
|
|||||||
breadcrumbItems = computed(() => this._state().breadcrumbItems);
|
breadcrumbItems = computed(() => this._state().breadcrumbItems);
|
||||||
|
|
||||||
private setBreadcrumb(customerId: string, invoiceId: string) {
|
private setBreadcrumb(customerId: string, invoiceId: string) {
|
||||||
console.log(consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.title);
|
|
||||||
|
|
||||||
this.patchState({
|
this.patchState({
|
||||||
breadcrumbItems: [
|
breadcrumbItems: [
|
||||||
{
|
{
|
||||||
|
|||||||
+35
-1
@@ -1 +1,35 @@
|
|||||||
<div class="flex flex-col gap-6"></div>
|
<div class="flex flex-col gap-6">
|
||||||
|
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="true" [(editMode)]="editMode">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
|
<app-key-value label="کد رهگیری" [value]="invoice()?.code" />
|
||||||
|
<app-key-value label="تاریخ فاکتور" [value]="invoice()?.invoice_date" type="dateTime" />
|
||||||
|
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice()?.created_at" type="dateTime" />
|
||||||
|
<div class="col-span-3">
|
||||||
|
<app-key-value label="توضیحات" [value]="invoice()?.notes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p-divider align="center"> اطلاعات پرداخت </p-divider>
|
||||||
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
|
<app-key-value label="مجموع قابل پرداخت" [value]="invoice()?.total_amount" type="price" />
|
||||||
|
@for (payment of invoice()?.payments; track $index) {
|
||||||
|
<app-key-value
|
||||||
|
[label]="
|
||||||
|
`${payment.payment_method === 'SET_OF' ? 'تهاتر' : payment.payment_method === 'TERMINAL' ? 'پایانه' : 'نقدی'}`
|
||||||
|
"
|
||||||
|
[value]="payment.amount"
|
||||||
|
type="price"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p-divider align="center"> اطلاعات صادر کننده </p-divider>
|
||||||
|
<div class="grid grid-cols-3 gap-4 items-center">
|
||||||
|
<app-key-value label="کسب و کار" [value]="invoice()?.pos!.complex.business_activity.name" />
|
||||||
|
<app-key-value label="مجموعه" [value]="invoice()?.pos!.complex.name" />
|
||||||
|
<app-key-value label="پایانهی فروش" [value]="invoice()?.pos!.name" />
|
||||||
|
<app-key-value label="ایجاد کننده" [value]="invoice()?.account?.account?.username" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-card-data>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { AppCardComponent, KeyValueComponent } from '@/shared/components';
|
|||||||
import pageParamsUtils from '@/utils/page-params.utils';
|
import pageParamsUtils from '@/utils/page-params.utils';
|
||||||
import { Component, computed, effect, inject, signal } from '@angular/core';
|
import { Component, computed, effect, inject, signal } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Divider } from 'primeng/divider';
|
||||||
import { ConsumerCustomerStore } from '../../store/customer.store';
|
import { ConsumerCustomerStore } from '../../store/customer.store';
|
||||||
import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store';
|
import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'consumer-customer-saleInvoice',
|
selector: 'consumer-customer-saleInvoice',
|
||||||
templateUrl: './single.component.html',
|
templateUrl: './single.component.html',
|
||||||
imports: [AppCardComponent, KeyValueComponent],
|
imports: [AppCardComponent, KeyValueComponent, Divider],
|
||||||
})
|
})
|
||||||
export class ConsumerCustomerSaleInvoiceComponent {
|
export class ConsumerCustomerSaleInvoiceComponent {
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
@@ -22,11 +23,11 @@ export class ConsumerCustomerSaleInvoiceComponent {
|
|||||||
invoiceId = signal<string>(this.pageParams()['invoiceId']);
|
invoiceId = signal<string>(this.pageParams()['invoiceId']);
|
||||||
editMode = signal<boolean>(false);
|
editMode = signal<boolean>(false);
|
||||||
|
|
||||||
private readonly customer = computed(() => this.customerStore.entity());
|
readonly invoice = computed(() => this.store.entity());
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
if (this.customer()?.id) {
|
if (this.invoice()?.id) {
|
||||||
this.setBreadcrumb();
|
this.setBreadcrumb();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,49 +2,43 @@
|
|||||||
<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 [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
|
<app-input [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
|
||||||
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" />
|
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" />
|
||||||
<app-input
|
|
||||||
[control]="form.controls.payload.controls.wages"
|
<app-amount-percentage-input
|
||||||
|
[percentageControl]="form.controls.payload.controls.wages_percentage"
|
||||||
|
[amountControl]="form.controls.payload.controls.wages_amount"
|
||||||
|
[baseAmount]="unitWithQuantity()"
|
||||||
name="wages"
|
name="wages"
|
||||||
[label]="`اجرت ${toPriceFormat(wagePrice())}`"
|
label="اجرت"
|
||||||
type="number"
|
|
||||||
suffix="درصد"
|
|
||||||
[min]="0"
|
|
||||||
[max]="100"
|
|
||||||
/>
|
/>
|
||||||
<app-input
|
<app-amount-percentage-input
|
||||||
[control]="form.controls.payload.controls.commission"
|
[percentageControl]="form.controls.payload.controls.commission_percentage"
|
||||||
|
[amountControl]="form.controls.payload.controls.commission_amount"
|
||||||
|
[baseAmount]="unitWithQuantity()"
|
||||||
name="commission"
|
name="commission"
|
||||||
[label]="`حقالعمل ${toPriceFormat(commissionPrice())}`"
|
label="حقالعمل"
|
||||||
type="number"
|
|
||||||
suffix="درصد"
|
|
||||||
[min]="0"
|
|
||||||
[max]="100"
|
|
||||||
/>
|
/>
|
||||||
<app-input
|
<app-amount-percentage-input
|
||||||
[control]="form.controls.payload.controls.profit"
|
[percentageControl]="form.controls.payload.controls.profit_percentage"
|
||||||
|
[amountControl]="form.controls.payload.controls.profit_amount"
|
||||||
|
[baseAmount]="totalAmountBeforeProfit()"
|
||||||
name="profit"
|
name="profit"
|
||||||
[label]="`سود ${toPriceFormat(profitPrice())}`"
|
label="سود"
|
||||||
type="number"
|
|
||||||
suffix="درصد"
|
|
||||||
[min]="0"
|
|
||||||
[max]="100"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<app-input
|
<app-amount-percentage-input
|
||||||
[control]="form.controls.discount"
|
[percentageControl]="form.controls.discount_percentage"
|
||||||
|
[amountControl]="form.controls.discount_amount"
|
||||||
|
[baseAmount]="baseTotalAmount()"
|
||||||
name="discount"
|
name="discount"
|
||||||
label="تخفیف"
|
label="تخفیف"
|
||||||
type="number"
|
|
||||||
suffix="درصد"
|
|
||||||
[min]="0"
|
|
||||||
[max]="100"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex flex-col gap-4 justify-center w-full">
|
<div class="flex flex-col gap-4 justify-center w-full">
|
||||||
<pos-form-dialog-amount-card-template
|
<pos-form-dialog-amount-card-template
|
||||||
[totalAmount]="totalAmount()"
|
[totalAmount]="totalAmount()"
|
||||||
[baseTotalAmount]="baseTotalAmount()"
|
[baseTotalAmount]="baseTotalAmount()"
|
||||||
[discountAmount]="discountAmount()"
|
[discountAmount]="form.controls.discount_amount.value || 0"
|
||||||
[taxAmount]="taxAmount()"
|
[taxAmount]="taxAmount()"
|
||||||
/>
|
/>
|
||||||
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
|
<p-button (onClick)="submit()"> افزودن به لیست خرید </p-button>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { AbstractForm } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||||
import { InputComponent } from '@/shared/components';
|
import { InputComponent } from '@/shared/components';
|
||||||
|
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
|
||||||
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
||||||
import { formatWithCurrency } from '@/utils';
|
import { formatWithCurrency } from '@/utils';
|
||||||
import { Component, EventEmitter, Output, signal } from '@angular/core';
|
import { Component, computed, EventEmitter, Output } from '@angular/core';
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { Button } from 'primeng/button';
|
import { Button } from 'primeng/button';
|
||||||
import { IGoldPayload, IPosOrderItem } from '../../../models';
|
import { IGoldPayload, IPosOrderItem } from '../../../models';
|
||||||
@@ -18,6 +19,7 @@ import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-
|
|||||||
EnumSelectComponent,
|
EnumSelectComponent,
|
||||||
Button,
|
Button,
|
||||||
PosFormDialogAmountCardTemplateComponent,
|
PosFormDialogAmountCardTemplateComponent,
|
||||||
|
AmountPercentageInputComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PosGoldPayloadFormComponent extends AbstractForm<
|
export class PosGoldPayloadFormComponent extends AbstractForm<
|
||||||
@@ -31,19 +33,22 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
const form = this.fb.group({
|
const form = this.fb.group({
|
||||||
unit_price: [this.initialValues?.unit_price || 200_000_000, [Validators.required]],
|
unit_price: [this.initialValues?.unit_price || 200_000_000, [Validators.required]],
|
||||||
quantity: [this.initialValues?.quantity || 2, [Validators.required]],
|
quantity: [this.initialValues?.quantity || 2, [Validators.required]],
|
||||||
discount: [this.initialValues?.discount || 0],
|
discount_amount: [this.initialValues?.discount || 0],
|
||||||
|
discount_percentage: [this.initialValues?.discount || 0],
|
||||||
payload: this.fb.group({
|
payload: this.fb.group({
|
||||||
commission: [this.initialValues?.payload?.commission || 10, [Validators.required]],
|
commission_amount: [this.initialValues?.payload?.commission || 0, [Validators.required]],
|
||||||
|
commission_percentage: [
|
||||||
|
this.initialValues?.payload?.commission || 10,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
wages_amount: [this.initialValues?.payload?.wages || 0, [Validators.required]],
|
||||||
|
wages_percentage: [this.initialValues?.payload?.wages || 10, [Validators.required]],
|
||||||
|
profit_amount: [this.initialValues?.payload?.profit || 0, [Validators.required]],
|
||||||
|
profit_percentage: [this.initialValues?.payload?.profit || 10, [Validators.required]],
|
||||||
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
|
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
|
||||||
wages: [this.initialValues?.payload?.wages || 10, [Validators.required]],
|
|
||||||
profit: [this.initialValues?.payload?.profit || 10, [Validators.required]],
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
form.valueChanges.subscribe((value) => {
|
|
||||||
this.updateCalculateAmount(value as IPosOrderItem<IGoldPayload>);
|
|
||||||
});
|
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,56 +58,51 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
...payload,
|
...payload,
|
||||||
total_amount: this.totalAmount(),
|
total_amount: this.totalAmount(),
|
||||||
base_total_amount: this.baseTotalAmount(),
|
base_total_amount: this.baseTotalAmount(),
|
||||||
discount_amount: this.discountAmount(),
|
|
||||||
tax_amount: this.taxAmount(),
|
tax_amount: this.taxAmount(),
|
||||||
payload: {
|
payload: {
|
||||||
commission_amount: this.commissionPrice(),
|
commission: this.form.controls.payload.controls.commission_amount.value || 0,
|
||||||
profit_amount: this.profitPrice(),
|
|
||||||
wages_amount: this.wagePrice(),
|
|
||||||
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.value || 0,
|
profit: this.form.controls.payload.controls.profit_amount.value || 0,
|
||||||
wages: this.form.controls.payload.controls.wages.value || 0,
|
wages: this.form.controls.payload.controls.wages_amount.value || 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
wagePrice = signal<number>(0);
|
unitWithQuantity = computed(
|
||||||
karatPrice = signal<number>(0);
|
() => (this.form.value.unit_price ?? 0) * (this.form.value.quantity ?? 0),
|
||||||
commissionPrice = signal<number>(0);
|
);
|
||||||
profitPrice = signal<number>(0);
|
|
||||||
totalAmount = signal<number>(0);
|
|
||||||
baseTotalAmount = signal<number>(0);
|
|
||||||
discountAmount = signal<number>(0);
|
|
||||||
taxAmount = signal<number>(0);
|
|
||||||
|
|
||||||
toPriceFormat(amount: number) {
|
totalAmountBeforeProfit = computed(() => {
|
||||||
|
const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0');
|
||||||
|
const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0');
|
||||||
|
return this.unitWithQuantity() + wageAmount + commissionAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
baseTotalAmount = computed(() => {
|
||||||
|
const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0');
|
||||||
|
const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0');
|
||||||
|
const profitAmount = Number(this.form.value.payload?.profit_amount ?? '0');
|
||||||
|
|
||||||
|
return this.unitWithQuantity() + profitAmount + commissionAmount + wageAmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
taxAmount = computed(() => {
|
||||||
|
const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0');
|
||||||
|
const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0');
|
||||||
|
const profitAmount = Number(this.form.value.payload?.profit_amount ?? '0');
|
||||||
|
const discountAmount = Number(this.form.value.discount_amount ?? '0');
|
||||||
|
return (profitAmount + commissionAmount + wageAmount - discountAmount) * 0.1;
|
||||||
|
});
|
||||||
|
|
||||||
|
totalAmount = computed(() => {
|
||||||
|
const discountAmount = Number(this.form.value.discount_amount ?? '0');
|
||||||
|
return this.baseTotalAmount() - discountAmount + this.taxAmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
toAmountFormat(amount: number) {
|
||||||
if (!amount) {
|
if (!amount) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return `(${formatWithCurrency(amount)})`;
|
return `(${formatWithCurrency(amount)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCalculateAmount(value: Partial<IPosOrderItem<IGoldPayload>>) {
|
|
||||||
const unitWithQuantity = (value.unit_price ?? 0) * (value.quantity ?? 0);
|
|
||||||
const commissionPrice = (unitWithQuantity * Number(value.payload?.commission ?? '0')) / 100;
|
|
||||||
const wagePrice = (unitWithQuantity * Number(value.payload?.wages ?? '0')) / 100;
|
|
||||||
const totalPriceBeforeProfit = unitWithQuantity + wagePrice + commissionPrice;
|
|
||||||
|
|
||||||
const profitPrice = (totalPriceBeforeProfit * Number(value.payload?.profit ?? '0')) / 100;
|
|
||||||
|
|
||||||
const baseTotalAmount = unitWithQuantity + profitPrice + commissionPrice + wagePrice;
|
|
||||||
|
|
||||||
const discountPrice = (baseTotalAmount * Number(value.discount || '0')) / 100;
|
|
||||||
const taxAmount = (profitPrice + commissionPrice + wagePrice - discountPrice) * 0.1;
|
|
||||||
|
|
||||||
this.wagePrice.set(wagePrice);
|
|
||||||
this.commissionPrice.set(commissionPrice);
|
|
||||||
this.profitPrice.set(profitPrice);
|
|
||||||
this.discountAmount.set(discountPrice);
|
|
||||||
this.taxAmount.set(taxAmount);
|
|
||||||
this.baseTotalAmount.set(baseTotalAmount);
|
|
||||||
|
|
||||||
this.totalAmount.set(baseTotalAmount - discountPrice + taxAmount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ export interface IGoldPayload {
|
|||||||
wages: number;
|
wages: number;
|
||||||
profit: number;
|
profit: number;
|
||||||
commission: number;
|
commission: number;
|
||||||
wages_amount: number;
|
// wages_amount: number;
|
||||||
profit_amount: number;
|
// profit_amount: number;
|
||||||
commission_amount: number;
|
// commission_amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IStandardPayload {}
|
export interface IStandardPayload {}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
|
|||||||
customerDetails: {
|
customerDetails: {
|
||||||
type: CustomerType.UNKNOWN,
|
type: CustomerType.UNKNOWN,
|
||||||
},
|
},
|
||||||
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC),
|
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC_WITH_FULL_TIME),
|
||||||
payments: {
|
payments: {
|
||||||
cash: 0,
|
cash: 0,
|
||||||
set_off: 0,
|
set_off: 0,
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
<uikit-field
|
||||||
|
[label]="preparedLabel()"
|
||||||
|
[name]="name"
|
||||||
|
[control]="selectedType.value === 'amount' ? amountControl : percentageControl"
|
||||||
|
[showLabel]="!!label"
|
||||||
|
[showErrors]="showErrors"
|
||||||
|
>
|
||||||
|
<p-inputgroup>
|
||||||
|
@if (selectedType.value === "amount") {
|
||||||
|
<p-inputnumber
|
||||||
|
[attr.id]="name"
|
||||||
|
[formControl]="amountControl"
|
||||||
|
[attr.name]="name"
|
||||||
|
[attr.placeholder]="placeholder"
|
||||||
|
[attr.autocomplete]="autocomplete"
|
||||||
|
[class]="
|
||||||
|
` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`
|
||||||
|
"
|
||||||
|
dir="ltr"
|
||||||
|
[required]="required"
|
||||||
|
[invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)"
|
||||||
|
(onInput)="onPriceInput($event)"
|
||||||
|
/>
|
||||||
|
<!-- (input)="onInput($event)" -->
|
||||||
|
} @else {
|
||||||
|
<input
|
||||||
|
pInputText
|
||||||
|
[attr.id]="name"
|
||||||
|
[formControl]="percentageControl"
|
||||||
|
[attr.name]="name"
|
||||||
|
[attr.placeholder]="placeholder"
|
||||||
|
[attr.inputmode]="'numeric'"
|
||||||
|
[attr.type]="'number'"
|
||||||
|
dir="ltr"
|
||||||
|
[attr.autocomplete]="autocomplete"
|
||||||
|
[classList]="
|
||||||
|
[
|
||||||
|
'w-full hasSuffix text-left',
|
||||||
|
`${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`,
|
||||||
|
].join(' ')
|
||||||
|
"
|
||||||
|
[required]="required"
|
||||||
|
[invalid]="percentageControl.invalid && (percentageControl.touched || percentageControl.dirty)"
|
||||||
|
(input)="onInput($event)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<p-inputgroup-addon class="py-0!">
|
||||||
|
<p-select
|
||||||
|
[formControl]="selectedType"
|
||||||
|
[options]="typeSelectOptions"
|
||||||
|
optionValue="value"
|
||||||
|
size="small"
|
||||||
|
class="border-0!"
|
||||||
|
/>
|
||||||
|
</p-inputgroup-addon>
|
||||||
|
</p-inputgroup>
|
||||||
|
@if (hint) {
|
||||||
|
<small [attr.id]="name + '-help'">{{ hint }}</small>
|
||||||
|
}
|
||||||
|
</uikit-field>
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
|
import { UikitFieldComponent } from '@/uikit';
|
||||||
|
import { formatWithCurrency } from '@/utils';
|
||||||
|
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
||||||
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { InputGroup } from 'primeng/inputgroup';
|
||||||
|
import { InputGroupAddon } from 'primeng/inputgroupaddon';
|
||||||
|
import { InputNumberInputEvent, InputNumberModule } from 'primeng/inputnumber';
|
||||||
|
import { InputText } from 'primeng/inputtext';
|
||||||
|
import { Select } from 'primeng/select';
|
||||||
|
|
||||||
|
type TAmountPercentageInput = 'amount' | 'percentage';
|
||||||
|
interface IAmountPercentageInputSelect {
|
||||||
|
label: string;
|
||||||
|
value: TAmountPercentageInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-amount-percentage-input',
|
||||||
|
templateUrl: './amount-percentage-input.component.html',
|
||||||
|
imports: [
|
||||||
|
UikitFieldComponent,
|
||||||
|
InputGroup,
|
||||||
|
InputGroupAddon,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InputText,
|
||||||
|
Select,
|
||||||
|
InputNumberModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AmountPercentageInputComponent {
|
||||||
|
@Input() defaultType: TAmountPercentageInput = 'percentage';
|
||||||
|
@Input({ required: true }) percentageControl!: FormControl<Maybe<any>>;
|
||||||
|
@Input({ required: true }) amountControl!: FormControl<Maybe<any>>;
|
||||||
|
@Input({ required: true }) name!: string;
|
||||||
|
@Input({ required: true }) baseAmount!: number;
|
||||||
|
@Input() label: string = '';
|
||||||
|
@Input() placeholder?: string;
|
||||||
|
@Input() required = false;
|
||||||
|
@Input() disabled = false;
|
||||||
|
@Input() size?: 'small' | 'large';
|
||||||
|
@Input() autocomplete?: string = 'off';
|
||||||
|
@Input() showErrors = true;
|
||||||
|
@Input() hint?: string;
|
||||||
|
@Input() isLtrInput = false;
|
||||||
|
@Input() minAmount?: number = 0;
|
||||||
|
@Input() maxAmount?: number;
|
||||||
|
@Input() minPercentage?: number = 0;
|
||||||
|
@Input() maxPercentage?: number = 100;
|
||||||
|
@Output() valueChange = new EventEmitter<string | number>();
|
||||||
|
@Output() onTypeChange = new EventEmitter<TAmountPercentageInput>();
|
||||||
|
@Output() blur = new EventEmitter<void>();
|
||||||
|
|
||||||
|
private readonly toastService = inject(ToastService);
|
||||||
|
|
||||||
|
readonly typeSelectOptions: IAmountPercentageInputSelect[] = [
|
||||||
|
{
|
||||||
|
label: 'قیمت',
|
||||||
|
value: 'amount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'درصد',
|
||||||
|
value: 'percentage',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
selectedType = new FormControl({
|
||||||
|
value: this.defaultType,
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
preparedLabel = signal(this.label);
|
||||||
|
|
||||||
|
onPriceInput($event: InputNumberInputEvent) {
|
||||||
|
this.onInput($event.originalEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput($event: Event, isPriceType: boolean = false) {
|
||||||
|
// @ts-ignore
|
||||||
|
let value = $event.target.value as string;
|
||||||
|
if (isPriceType) {
|
||||||
|
value = value.replace(/,/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modifyControlsData(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private modifyControlsData(value: string) {
|
||||||
|
let newValue = parseFloat(value);
|
||||||
|
const isPercentageType = this.selectedType.value === 'percentage';
|
||||||
|
|
||||||
|
const min = (isPercentageType ? this.minPercentage : this.minAmount) || 0;
|
||||||
|
const max = (isPercentageType ? this.maxPercentage : this.maxAmount) || Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
const maxValidator = max < newValue;
|
||||||
|
const minValidator = min > newValue;
|
||||||
|
|
||||||
|
if (minValidator || maxValidator) {
|
||||||
|
if (minValidator) {
|
||||||
|
this.toastService.warn({
|
||||||
|
text: `مقدار فیلد باید بیشتر از ${min} باشد.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (maxValidator) {
|
||||||
|
this.toastService.warn({
|
||||||
|
text: `مقدار فیلد باید کمتر از ${max} باشد.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newValue = parseFloat(value.slice(0, value.length - 1));
|
||||||
|
if (newValue < 0 || isNaN(newValue)) {
|
||||||
|
newValue = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPercentageType) {
|
||||||
|
const amountValue = (this.baseAmount * newValue) / 100;
|
||||||
|
this.amountControl.setValue(amountValue);
|
||||||
|
} else {
|
||||||
|
const percentageValue = (newValue / this.baseAmount) * 100;
|
||||||
|
|
||||||
|
this.percentageControl.setValue(percentageValue);
|
||||||
|
this.preparedLabel.set(`${this.label} (${percentageValue} درصد)`);
|
||||||
|
// @ts-ignore
|
||||||
|
// $event.target.value = newValue;
|
||||||
|
}
|
||||||
|
this.preparedLabel.set(this.prepareLabel(isPercentageType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareLabel(isPercentageType: boolean) {
|
||||||
|
if (isPercentageType) {
|
||||||
|
return `${this.label} (${formatWithCurrency(this.amountControl.value || 0)})`;
|
||||||
|
}
|
||||||
|
return `${this.label} (${this.percentageControl.value || 0} درصد)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const isPercentageType = this.selectedType.value === 'percentage';
|
||||||
|
this.modifyControlsData(
|
||||||
|
isPercentageType ? this.percentageControl.value : this.amountControl.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.selectedType.valueChanges.subscribe((value) => {
|
||||||
|
this.preparedLabel.set(this.prepareLabel(value === 'percentage'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user