feat: enhance input component to support price type and add new payment method components

- Updated InputComponent to handle 'price' type with appropriate formatting and validation.
- Introduced new API routes for purchase receipt payments.
- Created constants for purchase receipts and invoices.
- Developed SupplierInvoicePayFormComponent for processing invoice payments.
- Implemented SupplierLedgerComponent to display supplier transactions.
- Added services and stores for managing supplier invoices and payments.
- Created components for selecting payment methods and displaying payment type tags.
- Enhanced UI with new templates for invoices and payment forms.
- Added utility functions for handling payment method types and statuses.
This commit is contained in:
2025-12-27 20:35:02 +03:30
parent c6efda319c
commit 879e29f54f
70 changed files with 1094 additions and 334 deletions
@@ -8,8 +8,8 @@ import { Subscribable } from 'rxjs';
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractFormDialog<T, U> {
@Input() initialValues?: T;
export abstract class AbstractFormDialog<Response, Request> {
@Input() initialValues?: Response;
@Input()
set visible(v: boolean) {
@@ -21,28 +21,34 @@ export abstract class AbstractFormDialog<T, U> {
private visibleSignal = signal(false);
@Output() visibleChange = new EventEmitter<boolean>();
@Output() onSubmit = new EventEmitter<T>();
@Output() onSubmit = new EventEmitter<Response>();
protected readonly fb = inject(FormBuilder);
constructor(private toastService: ToastService) {
effect(() => {
const v = this.visibleSignal();
if (!v) this.form.reset();
if (!v) this.form.reset(this.defaultValues);
});
}
abstract form: ReturnType<FormBuilder['group']>;
defaultValues: Partial<Request> = {};
abstract submitForm(payload: U): Subscribable<T> | void;
abstract submitForm(payload: Request): Subscribable<Response> | void;
submitLoading = signal(false);
submit() {
console.log('first');
this.form.markAllAsTouched();
if (this.form.valid) {
console.log('isValid');
console.log(this.submitForm);
this.form.disable();
this.submitLoading.set(true);
this.submitForm(this.form.value as U)?.subscribe({
this.submitForm(this.form.value as Request)?.subscribe({
next: (res) => {
this.toastService.success({
text: `با موفقیت ایجاد شد`,
@@ -57,6 +63,11 @@ export abstract class AbstractFormDialog<T, U> {
this.submitLoading.set(false);
this.form.enable();
},
complete: () => {
this.submitLoading.set(false);
this.form.enable();
console.log('end');
},
});
}
}
@@ -0,0 +1,15 @@
<div class="flex items-center">
<div class="grow">
<div class="flex items-center gap-2">
@if (backRoute) {
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="backRoute" />
}
<h3 class="m-0">{{ pageTitle }}</h3>
</div>
</div>
@if (actions) {
<div class="flex items-center gap-2">
<ng-container [ngTemplateOutlet]="actions"> </ng-container>
</div>
}
</div>
@@ -0,0 +1,17 @@
import { Maybe } from '@/core';
import { NgTemplateOutlet } from '@angular/common';
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
import { RouterLink, UrlTree } from '@angular/router';
import { Button } from 'primeng/button';
@Component({
selector: 'app-inner-pages-header',
templateUrl: './inner-pages-header.component.html',
imports: [Button, RouterLink, NgTemplateOutlet],
})
export class InnerPagesHeaderComponent {
@Input() pageTitle!: string;
@Input() backRoute?: UrlTree | string | any[];
@ContentChild('actions', { static: true }) actions?: Maybe<TemplateRef<any>>;
constructor() {}
}
@@ -1,6 +1,24 @@
<uikit-field [label]="label" [name]="name" [control]="control" [showLabel]="!!label" [showErrors]="showErrors">
@if (type === "switch") {
<p-toggleSwitch [formControl]="control" />
} @else if (type === "price") {
<p-inputgroup>
<p-inputnumber
[attr.id]="name"
[formControl]="control"
[attr.name]="name"
[attr.placeholder]="placeholder"
[attr.inputmode]="inputMode"
[attr.maxlength]="maxLength || null"
[attr.type]="htmlType"
[attr.autocomplete]="autocomplete"
[class]="` w-full ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
[required]="isRequired"
[invalid]="control.invalid && (control.touched || control.dirty)"
(input)="onInput($event)"
/>
<p-inputgroup-addon>ریال</p-inputgroup-addon>
</p-inputgroup>
} @else {
<input
pInputText
@@ -18,4 +36,7 @@
(input)="onInput($event)"
/>
}
@if (hint) {
<small [attr.id]="name + '-help'">{{ hint }}</small>
}
</uikit-field>
@@ -1,20 +1,41 @@
import { Maybe } from '@/core';
import { PriceMaskDirective } from '@/shared/directives';
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { InputGroupModule } from 'primeng/inputgroup';
import { InputGroupAddon } from 'primeng/inputgroupaddon';
import { InputNumberModule } from 'primeng/inputnumber';
import { InputTextModule } from 'primeng/inputtext';
import { ToggleSwitch } from 'primeng/toggleswitch';
@Component({
selector: 'app-input',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent, ToggleSwitch],
imports: [
CommonModule,
ReactiveFormsModule,
InputTextModule,
InputGroupModule,
UikitFieldComponent,
ToggleSwitch,
PriceMaskDirective,
InputGroupAddon,
InputNumberModule,
],
templateUrl: './input.component.html',
})
export class InputComponent {
@Input() type: 'simple' | 'postalCode' | 'mobile' | 'phone' | 'email' | 'checkbox' | 'switch' =
'simple';
@Input() type:
| 'simple'
| 'postalCode'
| 'mobile'
| 'phone'
| 'email'
| 'checkbox'
| 'switch'
| 'price' = 'simple';
@Input() control!: FormControl<Maybe<any>>;
@Input() name!: string;
@Input() label: string = '';
@@ -24,11 +45,12 @@ export class InputComponent {
@Input() size?: 'small' | 'large';
@Input() autocomplete?: string = 'off';
@Input() showErrors = false;
@Output() valueChange = new EventEmitter<string>();
@Input() hint?: string;
@Output() valueChange = new EventEmitter<string | number>();
onInput(ev: Event) {
const v = (ev.target as HTMLInputElement).value;
this.valueChange.emit(v);
this.valueChange.emit(this.type === 'price' ? parseFloat(v) : v);
}
get placeholder(): string | null {
@@ -42,6 +64,8 @@ export class InputComponent {
return 'شماره تلفن';
case 'email':
return 'آدرس ایمیل خود را وارد کنید';
case 'price':
return 'مبلغ';
default:
return '';
}
@@ -52,6 +76,7 @@ export class InputComponent {
case 'mobile':
case 'phone':
case 'postalCode':
case 'price':
return 'numeric';
default:
return 'text';
@@ -75,6 +100,7 @@ export class InputComponent {
switch (this.type) {
case 'mobile':
case 'phone':
case 'price':
return 'ltrInput';
case 'email':
case 'postalCode':