This commit is contained in:
2026-03-10 13:36:45 +03:30
parent 45d48f79dc
commit b379787002
208 changed files with 2328 additions and 2510 deletions
@@ -1,41 +0,0 @@
import { ToastService } from '@/core/services/toast.service';
import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractForm } from './abstract-form';
@Component({
selector: 'abstract-form-dialog',
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractFormDialog<Response, Request> extends AbstractForm<
Response,
Request
> {
@Input()
set visible(v: boolean) {
this.visibleSignal.set(!!v);
this.visibleChange.emit(!!v);
}
get visible() {
return this.visibleSignal();
}
private visibleSignal = signal(false);
@Output() visibleChange = new EventEmitter<boolean>();
constructor() {
super(inject(ToastService));
effect(() => {
const v = this.visibleSignal();
if (!v) this.form.reset(this.defaultValues);
});
this.submit.bind(() => {
this.close();
});
}
override close() {
this.visibleSignal.set(false);
}
}
@@ -1,57 +0,0 @@
import { ToastService } from '@/core/services/toast.service';
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { Subscribable } from 'rxjs';
@Component({
selector: 'abstract-form-dialog',
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractForm<Response, Request> {
@Input() initialValues?: Response;
@Output() onSubmit = new EventEmitter<Response>();
@Output() onClose = new EventEmitter<void>();
protected readonly fb = inject(FormBuilder);
constructor(private toastService: ToastService) {}
abstract form: ReturnType<FormBuilder['group']>;
defaultValues: Partial<Request> = {};
abstract submitForm(payload: Request): Subscribable<Response> | void;
submitLoading = signal(false);
submit() {
this.form.markAllAsTouched();
if (this.form.valid) {
this.form.disable();
this.submitLoading.set(true);
this.submitForm(this.form.value as Request)?.subscribe({
next: (res) => {
this.toastService.success({
text: `با موفقیت ایجاد شد`,
});
this.submitLoading.set(false);
this.form.enable();
this.form.reset();
this.onSubmit.emit(res);
},
error: () => {
this.submitLoading.set(false);
this.form.enable();
},
complete: () => {
this.submitLoading.set(false);
this.form.enable();
},
});
}
}
close() {
this.onClose.emit();
}
}
@@ -1,101 +0,0 @@
import { Maybe } from '@/core';
import { Component, EventEmitter, Input, model, Output, signal } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
export interface ISelectItem {
id: number;
name: string;
}
@Component({
selector: 'abstract-select-field',
template: '',
})
export abstract class AbstractSelectComponent<T = ISelectItem, serviceResponse = T[]> {
@Input() control = new FormControl<Maybe<number | T>>(null);
@Input() canInsert: boolean = false;
@Input() showLabel: boolean = true;
@Input() showErrors: boolean = true;
@Input() showClear: boolean = true;
@Input() isFullDataOptionValue: boolean = false;
@Input() optionValue = 'id';
@Output() onChange = new EventEmitter<Maybe<T>>();
@Output() onSetDefaultDataItem = new EventEmitter<T>();
loading = signal(false);
items = signal<Maybe<T[]>>(null);
isOpenFormDialog = signal(false);
value = model<Maybe<T>>(null);
constructor() {
if (this.value()) {
this.control.setValue(this.value());
}
this.control.valueChanges.subscribe((val) => {
this.value.set(val as Maybe<T>);
});
}
abstract getDataService(): Observable<serviceResponse>;
getData() {
this.loading.set(true);
this.getDataService().subscribe({
next: (res) => {
if (Array.isArray(res)) {
this.items.set(res);
// @ts-ignore
} else if ('data' in res) {
// @ts-ignore
this.items.set(res.data);
}
const defaultValue = this.control.value;
if (defaultValue) {
const selectedItem = this.items()?.find(
(item) => (item as Record<string, any>)[this.optionValue] == defaultValue,
) as T;
if (selectedItem) {
this.onSetDefaultDataItem.emit(selectedItem);
}
}
this.loading.set(false);
},
error: () => {
this.loading.set(false);
},
});
}
setDefaultDataItem(item: T) {
this.onSetDefaultDataItem.emit(item);
}
refresh() {
this.getData();
}
onOpenForm() {
this.isOpenFormDialog.set(true);
}
get selectOptionValue() {
if (this.isFullDataOptionValue) {
return undefined;
}
return this.optionValue;
}
change(value: Maybe<T>) {
console.log('first');
if (typeof value !== 'object') {
// @ts-ignore
this.onChange.emit(this.items()?.find((item) => item[this.optionValue] === value));
} else {
this.onChange.emit(value);
}
}
}
@@ -2,7 +2,7 @@
<ng-template #title>
<ng-container [ngTemplateOutlet]="header">
<div class="flex items-center gap-10 justify-between">
<span>{{ cardTitle }}</span>
<h5 class="font-bold">{{ cardTitle }}</h5>
<div class="flex items-center gap-2 shrink-0">
<ng-container [ngTemplateOutlet]="moreAction"></ng-container>
@if (editable) {
@@ -1,6 +1,14 @@
import { Maybe } from '@/core';
import { CommonModule } from '@angular/common';
import { Component, ContentChild, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
import {
Component,
ContentChild,
EventEmitter,
Input,
Output,
signal,
TemplateRef,
} from '@angular/core';
import { ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card';
@@ -13,22 +21,30 @@ import { Card } from 'primeng/card';
export class AppCardComponent {
@Input() cardTitle!: string;
@Input() editable: boolean = false;
@Input() editMode: boolean = false;
@Output() onEdit = new EventEmitter<void>();
@Input()
set editMode(v: boolean) {
this.editModeSignal.set(!!v);
this.editModeChange.emit(!!v);
}
get editMode() {
return this.editModeSignal();
}
private editModeSignal = signal(false);
@Output() editModeChange = new EventEmitter<boolean>();
@ContentChild('header', { static: true }) header?: Maybe<TemplateRef<any>>;
@ContentChild('moreAction', { static: true }) moreAction?: Maybe<TemplateRef<any>>;
constructor() {
if (this.editable) {
if (!this.onEdit) {
throw new Error('onEdit output must be bound when editable is true');
}
}
}
// constructor() {
// if (this.editable) {
// if (!this.ed) {
// throw new Error('onEdit output must be bound when editable is true');
// }
// }
// }
onEditClick() {
this.onEdit.emit();
this.editModeChange.emit(!this.editMode);
}
}
@@ -1,78 +0,0 @@
<p-table
stripedRows
[value]="items"
[loading]="loading"
showGridlines
tableStyleClass="table-fixed border-collapse"
scrollable
>
<ng-template #header>
<tr>
<th rowspan="2" [style]="{ textAlign: 'center', width: '4rem' }">ردیف</th>
<th rowspan="2" [style]="{ textAlign: 'center', width: '8rem' }">تاریخ</th>
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">شرح</th>
<th rowspan="2" [style]="{ textAlign: 'center', width: '6rem' }">شماره</th>
<th rowspan="2" [style]="{ textAlign: 'center', minWidth: '14rem', width: '14rem' }">طرف حساب</th>
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">انبار</th>
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">ورودی</th>
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">خروجی</th>
<th colspan="1" class="text-center!" [style]="{ width: '5rem' }">مانده</th>
</tr>
<tr>
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
<!-- <th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
<th [style]="{ textAlign: 'center', width: '10rem' }">مجموع قیمت</th> -->
</tr>
</ng-template>
<ng-template #body let-item let-i="rowIndex">
<tr>
<td class="text-center!">{{ i + 1 }}</td>
<td class="text-center!" [jalaliDate]="item.createdAt"></td>
<td class="text-center!">
<catalog-movement-reference-tag [type]="item.referenceType" [movementType]="item.type" />
</td>
<td class="text-center!">{{ item.referenceId || "-" }}</td>
<td class="text-center!">{{ item.supplier?.name || "-" }}</td>
<td class="text-center!">{{ item.inventory.name || "-" }}</td>
<td class="text-center!">
{{ item.type === "IN" ? item.quantity : 0 }}
</td>
<td class="text-center!">
@if (item.type === "IN") {
<span [appPriceMask]="item.unitPrice"></span>
} @else {
0
}
</td>
<td class="text-center!">
{{ item.type === "OUT" ? item.quantity : 0 }}
</td>
<td class="text-center!">
@if (item.type === "OUT") {
<span [appPriceMask]="item.unitPrice"></span>
} @else {
0
}
</td>
<td class="text-center!">{{ item.remainedInStock }}</td>
<!-- <td class="text-center!"><span [appPriceMask]="item.unitPrice"></span></td>
<td class="text-center!"><span [appPriceMask]="item.totalCost"></span></td> -->
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="11">
<uikit-empty-state
[title]="'هیچ رکوردی یافت نشد'"
[description]="'برای این بازه زمانی و فیلترهای انتخاب شده، رکوردی وجود ندارد.'"
></uikit-empty-state>
</td>
</tr>
</ng-template>
</p-table>
@@ -1,24 +0,0 @@
import { CatalogMovementReferenceTagComponent } from '@/shared/catalog/movementReferenceTypes';
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
import { UikitEmptyStateComponent } from '@/uikit';
import { Component, Input } from '@angular/core';
import { TableModule } from 'primeng/table';
import { ICardexItem } from './type';
@Component({
selector: 'shared-cardex',
templateUrl: './cardex.component.html',
imports: [
TableModule,
JalaliDateDirective,
CatalogMovementReferenceTagComponent,
PriceMaskDirective,
UikitEmptyStateComponent,
],
hostDirectives: [PriceMaskDirective],
})
export class CardexComponent {
@Input() items: ICardexItem[] = [];
@Input() loading: boolean = false;
constructor() {}
}
-3
View File
@@ -1,3 +0,0 @@
import { IInventoryStockMovementResponse } from '@/modules/inventories/models';
export interface ICardexItem extends IInventoryStockMovementResponse {}
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './abstract-select.component';
// export * from './abstract-select.component';
export * from './breadcrumb.component';
export * from './card-data.component';
export * from './inlineConfirmation/inline-confirmation.component';
@@ -1,4 +1,4 @@
<div class="h-full bg-surface-overlay">
<div class="h-full bg-surface-overlay rounded-(--p-card-border-radius) overflow-hidden">
<div class="h-full flex flex-col gap-4">
<p-table
[columns]="columns"
@@ -5,9 +5,11 @@ import {
Component,
computed,
ContentChild,
ElementRef,
EventEmitter,
Input,
Output,
Renderer2,
signal,
TemplateRef,
} from '@angular/core';
@@ -32,6 +34,9 @@ export interface IColumn<T = any> {
@Component({
selector: 'app-page-data-list',
templateUrl: './page-data-list.component.html',
// host: {
// class: 'inline-block w-full',
// },
imports: [
CommonModule,
TableActionRowComponent,
@@ -46,7 +51,10 @@ export interface IColumn<T = any> {
],
})
export class PageDataListComponent<I> {
constructor() {}
constructor(
private host: ElementRef,
private renderer: Renderer2,
) {}
@Input() pageTitle!: string;
@Input() addNewCtaLabel?: string;
@@ -64,7 +72,8 @@ export class PageDataListComponent<I> {
@Input() showDetails: boolean = false;
@Input() showAdd: boolean = false;
@Input() isFiltered: boolean = false;
@Input() fullHeight: boolean = false;
@Input() fullHeight?: boolean = false;
@Input() height: string = '500px';
@ContentChild('filter', { static: true }) filter!: TemplateRef<any> | null;
@ContentChild('caption', { static: true }) caption!: TemplateRef<any> | null;
@@ -78,8 +87,15 @@ export class PageDataListComponent<I> {
filterDrawerVisible = signal<boolean>(false);
ngOnInit() {
console.log(this.fullHeight);
console.log(this.height);
this.renderer.setStyle(
this.host.nativeElement,
'height',
this.fullHeight ? '100cqmin' : this.height,
);
// if (this.fullHeight) {
// this.renderer.setStyle(this.host.nativeElement, 'height', '100cqmin');
// }
}
@@ -1,4 +1,4 @@
@if (showPaymentForm()) {
<!-- @if (showPaymentForm()) {
<app-supplier-invoice-pay-form
[(visible)]="showPaymentForm"
[debtAmount]="purchaseReceipt.totalAmount"
@@ -7,4 +7,4 @@
[invoiceId]="purchaseReceipt.id.toString()"
(onSubmit)="submitted()"
/>
}
} -->
@@ -1,55 +1,55 @@
import { ToastService } from '@/core/services/toast.service';
import { SupplierInvoicePayFormComponent } from '@/modules/suppliers/components/invoices/pay-form.component';
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
import { IPurchaseReceiptResponse } from '../../../modules/purchases/models';
import { ConfirmationDialogService } from '../confirmationDialog/confirmation-dialog.service';
// import { ToastService } from '@/core/services/toast.service';
// import { SupplierInvoicePayFormComponent } from '@/modules/suppliers/components/invoices/pay-form.component';
// import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
// import { IPurchaseReceiptResponse } from '../../../modules/purchases/models';
// import { ConfirmationDialogService } from '../confirmationDialog/confirmation-dialog.service';
@Component({
selector: 'app-purchase-receipt-payment-wrapper',
templateUrl: './wrapper.component.html',
imports: [SupplierInvoicePayFormComponent],
})
export class PurchaseReceiptPaymentWrapperComponent {
@Input() purchaseReceipt!: IPurchaseReceiptResponse;
// @Component({
// selector: 'app-purchase-receipt-payment-wrapper',
// templateUrl: './wrapper.component.html',
// imports: [SupplierInvoicePayFormComponent],
// })
// export class PurchaseReceiptPaymentWrapperComponent {
// @Input() purchaseReceipt!: IPurchaseReceiptResponse;
@Output() onSubmit = new EventEmitter<void>();
// @Output() onSubmit = new EventEmitter<void>();
showPaymentForm = signal(false);
constructor(
private confirmService: ConfirmationDialogService,
private toastService: ToastService,
) {
setTimeout(() => {
this.showConfirm();
}, 1);
}
// showPaymentForm = signal(false);
// constructor(
// private confirmService: ConfirmationDialogService,
// private toastService: ToastService,
// ) {
// setTimeout(() => {
// this.showConfirm();
// }, 1);
// }
showConfirm() {
this.confirmService.confirm({
message: 'آیا پرداخت فاکتور انجام شده است؟',
header: 'وضعیت پرداخت فاکتور',
acceptLabel: 'بله',
rejectLabel: 'خیر',
accept: () => {
this.toPaymentForm();
},
reject: () => {
this.toastService.info({
text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.',
title: 'پرداخت فاکتور انجام نشد',
});
},
});
}
// showConfirm() {
// this.confirmService.confirm({
// message: 'آیا پرداخت فاکتور انجام شده است؟',
// header: 'وضعیت پرداخت فاکتور',
// acceptLabel: 'بله',
// rejectLabel: 'خیر',
// accept: () => {
// this.toPaymentForm();
// },
// reject: () => {
// this.toastService.info({
// text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.',
// title: 'پرداخت فاکتور انجام نشد',
// });
// },
// });
// }
submitted() {
console.log('submitted');
// submitted() {
// console.log('submitted');
this.showPaymentForm.set(false);
this.onSubmit.emit();
}
// this.showPaymentForm.set(false);
// this.onSubmit.emit();
// }
toPaymentForm() {
this.showPaymentForm.set(true);
}
}
// toPaymentForm() {
// this.showPaymentForm.set(true);
// }
// }