feat: update app version and build details in ngsw-config.json
fix: add baseZIndex to p-toast component in app.component.ts feat: enhance pagination support in saleInvoices list component refactor: modify saleInvoices service to accept pagination query feat: update API routes for saleInvoices to support pagination fix: adjust layout for consumer accounts in partner module style: improve layout for consumer business activities in partner module fix: update license info display in dashboard component fix: ensure price info card displays default values for discount and tax refactor: clean up gold payload form component fix: adjust root component layout for mobile view refactor: unify consumer account list configuration across modules chore: remove deprecated partner consumer account list config chore: remove deprecated superAdmin consumer account list config feat: add new consumer account list configuration style: add summary list styling in customize.scss chore: switch API base URL for TIS environment
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Maybe } from '@/core';
|
||||
import {
|
||||
IListingResponse,
|
||||
IPaginatedQuery,
|
||||
IPaginatedResponse,
|
||||
IResponseMetadata,
|
||||
} from '@/core/models/service.model';
|
||||
@@ -82,6 +83,16 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
}
|
||||
|
||||
changePage(page: number) {
|
||||
// this.
|
||||
const perPage = this.responseMetaData()?.perPage;
|
||||
this.requestPayload.update(
|
||||
(prev) =>
|
||||
({
|
||||
...(prev || {}),
|
||||
page,
|
||||
...(perPage ? ({ pageSize: perPage } as IPaginatedQuery) : {}),
|
||||
}) as any,
|
||||
);
|
||||
this.responseMetaData.update((prev) => (prev ? { ...prev, page } : prev));
|
||||
this.getData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
[inputStyleClass]="` w-full hasSuffix text-left ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
|
||||
[required]="required"
|
||||
[invalid]="amountControl.invalid && (amountControl.touched || amountControl.dirty)"
|
||||
(onInput)="onPriceInput($event)"
|
||||
/>
|
||||
<!-- (input)="onInput($event)" -->
|
||||
} @else {
|
||||
<input
|
||||
pInputText
|
||||
@@ -50,7 +48,6 @@
|
||||
"
|
||||
[required]="required"
|
||||
[invalid]="percentageControl.invalid && (percentageControl.touched || percentageControl.dirty)"
|
||||
(input)="onInput($event)"
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
+97
-76
@@ -19,7 +19,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { InputGroup } from 'primeng/inputgroup';
|
||||
import { InputGroupAddon } from 'primeng/inputgroupaddon';
|
||||
import { InputNumberInputEvent, InputNumberModule } from 'primeng/inputnumber';
|
||||
import { InputNumberModule } from 'primeng/inputnumber';
|
||||
import { InputText } from 'primeng/inputtext';
|
||||
import { Select } from 'primeng/select';
|
||||
|
||||
@@ -90,83 +90,85 @@ export class AmountPercentageInputComponent {
|
||||
});
|
||||
|
||||
preparedLabel = signal(this.label);
|
||||
private lastValidAmount = 0;
|
||||
private lastValidPercentage = 0;
|
||||
|
||||
onPriceInput($event: InputNumberInputEvent) {
|
||||
this.onInput($event.originalEvent, true);
|
||||
private parseValue(value: Maybe<any>) {
|
||||
const normalized = `${value ?? ''}`.replace(/,/g, '');
|
||||
const parsed = parseFloat(normalized);
|
||||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
}
|
||||
|
||||
onInput($event: Event, isPriceType: boolean = false) {
|
||||
// @ts-ignore
|
||||
let value = $event.target.value as string;
|
||||
// @ts-ignore
|
||||
const isDotInput = $event.data === '.';
|
||||
private getRangeLabel(value: number, isPercentageType: boolean) {
|
||||
return isPercentageType ? `${value}%` : formatWithCurrency(value);
|
||||
}
|
||||
|
||||
if (isDotInput) {
|
||||
private isInRange(value: number, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
private showOutOfRangeToast(value: number, isPercentageType: boolean, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
||||
if (value < min) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} باید بیشتر از ${this.getRangeLabel(min, isPercentageType)} باشد.`,
|
||||
});
|
||||
}
|
||||
if (value > max) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} باید کمتر از ${this.getRangeLabel(max, isPercentageType)} باشد.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getAmountRange() {
|
||||
const min = this.minAmount ?? 0;
|
||||
const maxFromInput = this.maxAmount;
|
||||
const max = maxFromInput ?? (this.baseAmount > 0 ? this.baseAmount : Number.MAX_SAFE_INTEGER);
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
private getPercentageRange() {
|
||||
const min = this.minPercentage ?? 0;
|
||||
const max = this.maxPercentage ?? 100;
|
||||
return { min, max };
|
||||
}
|
||||
|
||||
private syncFromPercentage(rawValue: Maybe<any>) {
|
||||
const { min, max } = this.getPercentageRange();
|
||||
const percentageValue = this.parseValue(rawValue);
|
||||
if (!this.isInRange(percentageValue, min, max)) {
|
||||
this.showOutOfRangeToast(percentageValue, true, min, max);
|
||||
this.percentageControl.setValue(this.lastValidPercentage, { emitEvent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPriceType) {
|
||||
value = value.replace(/,/g, '');
|
||||
}
|
||||
this.lastValidPercentage = percentageValue;
|
||||
const amountValue = (this.baseAmount * percentageValue) / 100;
|
||||
this.lastValidAmount = Number.isFinite(amountValue) ? amountValue : 0;
|
||||
|
||||
const newValueToSet = this.modifyControlsData(value);
|
||||
|
||||
if (newValueToSet !== null) {
|
||||
// @ts-ignore
|
||||
$event.target.value = newValueToSet;
|
||||
}
|
||||
this.percentageControl.setValue(this.lastValidPercentage, { emitEvent: false });
|
||||
this.amountControl.setValue(this.lastValidAmount, { emitEvent: false });
|
||||
this.preparedLabel.set(this.prepareLabel(true));
|
||||
}
|
||||
|
||||
private modifyControlsData(value: string) {
|
||||
let newValueToSet: Maybe<string> = null;
|
||||
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;
|
||||
|
||||
const notValid = minValidator || maxValidator;
|
||||
|
||||
if (notValid) {
|
||||
if (minValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} باید بیشتر از ${formatWithCurrency(min)} باشد.`,
|
||||
});
|
||||
}
|
||||
if (maxValidator) {
|
||||
this.toastService.warn({
|
||||
text: `مقدار ${this.label} باید کمتر از ${formatWithCurrency(max)} باشد.`,
|
||||
});
|
||||
}
|
||||
|
||||
newValue = parseFloat(value.slice(0, value.length - 1));
|
||||
if (newValue < 0 || isNaN(newValue)) {
|
||||
newValue = 0;
|
||||
}
|
||||
private syncFromAmount(rawValue: Maybe<any>) {
|
||||
const { min, max } = this.getAmountRange();
|
||||
const amountValue = this.parseValue(rawValue);
|
||||
if (!this.isInRange(amountValue, min, max)) {
|
||||
this.showOutOfRangeToast(amountValue, false, min, max);
|
||||
this.amountControl.setValue(this.lastValidAmount, { emitEvent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPercentageType) {
|
||||
const amountValue = (this.baseAmount * newValue) / 100;
|
||||
newValueToSet = newValue + '';
|
||||
this.amountControl.setValue(isNaN(amountValue) ? 0 : amountValue, { emitEvent: false });
|
||||
if (notValid) {
|
||||
this.percentageControl.setValue(newValue, { emitEvent: false });
|
||||
}
|
||||
} else {
|
||||
const percentageValue = ((newValue / this.baseAmount) * 100).toFixed(2);
|
||||
newValueToSet = newValue + '';
|
||||
this.percentageControl.setValue(percentageValue, { emitEvent: false });
|
||||
this.preparedLabel.set(`${this.label} (${percentageValue} درصد)`);
|
||||
if (notValid) {
|
||||
this.amountControl.setValue(newValue, { emitEvent: false });
|
||||
}
|
||||
}
|
||||
this.preparedLabel.set(this.prepareLabel(isPercentageType));
|
||||
this.lastValidAmount = amountValue;
|
||||
const percentageValue = this.baseAmount
|
||||
? ((amountValue / this.baseAmount) * 100).toFixed(2)
|
||||
: '0';
|
||||
this.lastValidPercentage = this.parseValue(percentageValue);
|
||||
|
||||
return newValue;
|
||||
this.amountControl.setValue(this.lastValidAmount, { emitEvent: false });
|
||||
this.percentageControl.setValue(percentageValue, { emitEvent: false });
|
||||
this.preparedLabel.set(this.prepareLabel(false));
|
||||
}
|
||||
|
||||
private prepareLabel(isPercentageType: boolean) {
|
||||
@@ -177,25 +179,44 @@ export class AmountPercentageInputComponent {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const amountValue = Number(this.amountControl.value || 0);
|
||||
const percentageValue = Number(this.percentageControl.value || 0);
|
||||
const isPercentageType = percentageValue > 0 && amountValue <= 0;
|
||||
|
||||
console.log(amountValue, percentageValue, isPercentageType);
|
||||
|
||||
this.preparedLabel.set(this.prepareLabel(isPercentageType));
|
||||
const isPercentageType = this.defaultType === 'percentage';
|
||||
|
||||
this.selectedType.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
|
||||
this.preparedLabel.set(this.prepareLabel(value === 'percentage'));
|
||||
});
|
||||
|
||||
this.selectedType.setValue(isPercentageType ? 'percentage' : 'amount');
|
||||
this.percentageControl.valueChanges
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((value) => {
|
||||
if (this.selectedType.value === 'percentage') {
|
||||
this.syncFromPercentage(value);
|
||||
}
|
||||
});
|
||||
|
||||
this.amountControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
|
||||
if (this.selectedType.value === 'amount') {
|
||||
this.syncFromAmount(value);
|
||||
}
|
||||
});
|
||||
|
||||
this.selectedType.setValue(isPercentageType ? 'percentage' : 'amount', { emitEvent: false });
|
||||
this.lastValidAmount = this.parseValue(this.amountControl.value);
|
||||
this.lastValidPercentage = this.parseValue(this.percentageControl.value);
|
||||
if (isPercentageType) {
|
||||
this.syncFromPercentage(this.percentageControl.value);
|
||||
} else {
|
||||
this.syncFromAmount(this.amountControl.value);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
const amountValue = Number(this.amountControl.value || 0);
|
||||
const percentageValue = Number(this.percentageControl.value || 0);
|
||||
const isPercentageType = percentageValue > 0 && amountValue <= 0;
|
||||
const isPercentageType = this.selectedType.value === 'percentage';
|
||||
if (isPercentageType) {
|
||||
this.syncFromPercentage(this.percentageControl.value);
|
||||
} else {
|
||||
this.syncFromAmount(this.amountControl.value);
|
||||
}
|
||||
|
||||
this.preparedLabel.set(this.prepareLabel(isPercentageType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
@if (showIndex) {
|
||||
<td>
|
||||
{{ i * (currentPage || 1) + 1 }}
|
||||
{{ i + 1 + (currentPage && perPage ? (currentPage - 1) * perPage : 0) }}
|
||||
</td>
|
||||
}
|
||||
@for (col of columns; track col.field) {
|
||||
|
||||
@@ -18,8 +18,8 @@ import { SkeletonModule } from 'primeng/skeleton';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { KeyValueComponent } from '../key-value.component/key-value.component';
|
||||
import { TableActionRowComponent } from '../table-action-row.component';
|
||||
import { PageDataValueComponent } from './page-data-value.component';
|
||||
import { IColumn } from './page-data-list.component';
|
||||
import { PageDataValueComponent } from './page-data-value.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-data-list-table-view',
|
||||
@@ -50,6 +50,7 @@ export class AppPageDataListTableView<I = any> {
|
||||
@Input() emptyPlaceholderDescription?: string = '';
|
||||
@Input() emptyPlaceholderCtaLabel?: string;
|
||||
@Input() currentPage?: number = 1;
|
||||
@Input() perPage?: number = 1;
|
||||
@Input() showEdit: boolean = false;
|
||||
@Input() showDelete: boolean = false;
|
||||
@Input() showDetails: boolean = false;
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
[emptyPlaceholderCtaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||
[addNewCtaLabel]="addNewCtaLabel"
|
||||
[currentPage]="currentPage"
|
||||
[perPage]="perPage"
|
||||
[showEdit]="showEdit"
|
||||
[showDelete]="showDelete"
|
||||
[showDetails]="showDetails"
|
||||
@@ -84,9 +85,11 @@
|
||||
[expandable]="expandable"
|
||||
[expandableItemKey]="expandableItemKey"
|
||||
[expandColumns]="expandColumns"
|
||||
[showPaginator]="showPaginator"
|
||||
(onEdit)="edit($event)"
|
||||
(onDelete)="remove($event)"
|
||||
(onDetails)="details($event)"
|
||||
(onChangePage)="onPage($event)"
|
||||
>
|
||||
@if (pageTitle || showAdd || filter || showRefresh || moreActions) {
|
||||
<ng-template #captionBox>
|
||||
|
||||
@@ -176,6 +176,8 @@ export class PageDataListComponent<I = any> {
|
||||
};
|
||||
|
||||
onPage = ($event: any) => {
|
||||
console.log('$event', $event);
|
||||
|
||||
this.onChangePage.emit($event);
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { IListConfig } from './list-config.model';
|
||||
|
||||
export const partnerConsumerAccountListConfig: IListConfig = {
|
||||
export const consumerAccountListConfig: IListConfig = {
|
||||
pageTitle: 'مدیریت حسابهای مشتری',
|
||||
addNewCtaLabel: 'افزودن حساب',
|
||||
emptyPlaceholderTitle: 'حسابی یافت نشد',
|
||||
@@ -27,7 +27,7 @@ export const partnerConsumerAccountListConfig: IListConfig = {
|
||||
field: 'status',
|
||||
header: 'وضعیت',
|
||||
type: 'nested',
|
||||
nestedOption: { path: 'account.status.translate' },
|
||||
nestedOption: { path: 'status.translate' },
|
||||
variant: 'tag',
|
||||
},
|
||||
],
|
||||
@@ -2,6 +2,7 @@ export * from './account-list.const';
|
||||
export * from './business-activity-list.const';
|
||||
export * from './category-list.const';
|
||||
export * from './complex-list.const';
|
||||
export * from './consumer-account-list.const';
|
||||
export * from './consumer-list.const';
|
||||
export * from './device-brand-list.const';
|
||||
export * from './device-list.const';
|
||||
@@ -9,12 +10,10 @@ export * from './good-list.const';
|
||||
export * from './license-list.const';
|
||||
export * from './list-config.model';
|
||||
export * from './partner-account-list.const';
|
||||
export * from './partner-consumer-account-list.const';
|
||||
export * from './partner-customer-list.const';
|
||||
export * from './partner-list.const';
|
||||
export * from './pos-list.const';
|
||||
export * from './provider-list.const';
|
||||
export * from './sale-invoice-list.const';
|
||||
export * from './sku-list.const';
|
||||
export * from './superAdmin-consumer-account-list.const';
|
||||
export * from './user-list.const';
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { IListConfig } from './list-config.model';
|
||||
|
||||
export const superAdminConsumerAccountListConfig: IListConfig = {
|
||||
pageTitle: 'مدیریت حسابهای مشتری',
|
||||
addNewCtaLabel: 'افزودن حساب',
|
||||
emptyPlaceholderTitle: 'حسابی یافت نشد',
|
||||
emptyPlaceholderDescription: 'برای افزودن حساب، روی دکمهٔ بالا کلیک کنید.',
|
||||
columns: [
|
||||
{
|
||||
field: 'account',
|
||||
header: 'نام کاربری',
|
||||
type: 'nested',
|
||||
nestedOption: { path: 'account.username' },
|
||||
},
|
||||
{ field: 'role', header: 'نوع حساب', type: 'nested', nestedOption: { path: 'role.translate' } },
|
||||
{
|
||||
field: 'pos',
|
||||
header: 'پایانه',
|
||||
customDataModel(item: any) {
|
||||
return `${item.pos.name} (${item.pos.complex.name} - ${item.pos.complex.business_activity.name})`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
header: 'وضعیت',
|
||||
type: 'nested',
|
||||
nestedOption: { path: 'account.status.translate' },
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
header: 'تاریخ ایجاد',
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user