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
@@ -0,0 +1,23 @@
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
@Component({
selector: 'abstract-dialog',
template: '',
})
export abstract class AbstractDialog {
@Input()
set visible(v: boolean) {
this.visibleSignal.set(!!v);
this.visibleChange.emit(!!v);
}
get visible() {
return this.visibleSignal();
}
private visibleSignal = signal(false);
close() {
this.visible = false;
}
@Output() visibleChange = new EventEmitter<boolean>();
}
@@ -1,5 +1,4 @@
import { ToastService } from '@/core/services/toast.service';
import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { Component, effect, EventEmitter, Input, Output, signal } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractForm } from './abstract-form';
@@ -8,9 +7,9 @@ import { AbstractForm } from './abstract-form';
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractFormDialog<Response, Request> extends AbstractForm<
Response,
Request
export abstract class AbstractFormDialog<Request, Response> extends AbstractForm<
Request,
Response
> {
@Input()
set visible(v: boolean) {
@@ -25,17 +24,19 @@ export abstract class AbstractFormDialog<Response, Request> extends AbstractForm
@Output() visibleChange = new EventEmitter<boolean>();
constructor() {
super(inject(ToastService));
super();
effect(() => {
const v = this.visibleSignal();
if (!v) this.form.reset(this.defaultValues);
});
this.submit.bind(() => {
this.close();
});
}
override onSuccess(response: Response): void {
this.close();
}
override close() {
this.visibleSignal.set(false);
this.onClose.emit();
this.visibleChange.emit(false);
}
}
@@ -0,0 +1,77 @@
import { ToastService } from '@/core/services/toast.service';
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { finalize, Observable } from 'rxjs';
@Component({
selector: 'abstract-form-dialog',
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractForm<
FormInterface,
Response,
InitialValue = Response,
RequestPayload extends FormInterface = FormInterface,
> {
@Input() initialValues?: InitialValue;
@Input() editMode?: boolean;
@Output() onSubmit = new EventEmitter<Response>();
@Output() onClose = new EventEmitter<void>();
protected readonly fb = inject(FormBuilder);
private readonly toastService = inject(ToastService);
constructor() {}
abstract form: ReturnType<FormBuilder['group']>;
showSuccessMessage: boolean = true;
successMessage = 'عملیات با موفقیت انجام شد';
defaultValues: Partial<FormInterface> = {};
abstract submitForm(payload: FormInterface): Observable<Response> | void;
prepareRequestPayload(payload: FormInterface): RequestPayload {
// @ts-ignore
return payload;
}
ngOnChanges() {
this.form.patchValue(this.initialValues ?? {});
}
onSuccess(response: Response): void {}
submitLoading = signal(false);
submit() {
this.form.markAllAsTouched();
if (this.form.valid) {
// this.form.disable();
this.submitLoading.set(true);
this.submitForm(this.prepareRequestPayload(this.form.value))
?.pipe(
finalize(() => {
this.submitLoading.set(false);
// this.form.enable();
}),
)
.subscribe((res) => {
this.onSuccess(res);
if (this.showSuccessMessage) {
this.toastService.success({
text: this.successMessage,
});
}
this.onSubmit.emit(res);
// this.form.reset();
});
}
}
close() {
this.onClose.emit();
}
}
@@ -0,0 +1,65 @@
import { Maybe } from '@/core';
import { IPaginatedResponse } from '@/core/models/service.model';
import { Component, signal } from '@angular/core';
import { Observable } from 'rxjs';
import { IColumn } from '../components/pageDataList/page-data-list.component';
@Component({
selector: 'abstract-list',
template: '',
})
export abstract class AbstractList<ResponseModel, RequestParams = null> {
columns = [] as IColumn[];
loading = signal(false);
items = signal<ResponseModel[]>([]);
visibleForm = signal(false);
requestPayload = signal<Maybe<RequestParams>>(null);
selectedItemForEdit = signal<Maybe<ResponseModel>>(null);
editMode = signal(false);
ngOnInit() {
this.setColumns();
this.getData();
}
refresh() {
this.getData();
}
getData() {
this.loading.set(true);
this.getDataRequest(this.requestPayload())?.subscribe((res) => {
this.loading.set(false);
this.items.set(res.data);
});
}
openAddForm() {
this.selectedItemForEdit.set(null);
this.editMode.set(false);
this.visibleForm.set(true);
}
abstract getDataRequest(
payload: Maybe<RequestParams>,
): Observable<IPaginatedResponse<ResponseModel>> | void;
abstract setColumns(): void;
// constructor() {
// if (this.editable) {
// if (!this.ed) {
// throw new Error('onEdit output must be bound when editable is true');
// }
// }
// }
onEditClick(selectedItem: ResponseModel) {
this.selectedItemForEdit.set(selectedItem);
this.editMode.set(true);
this.visibleForm.set(true);
}
onCloseEdit() {
this.visibleForm.set(false);
}
}
@@ -89,8 +89,6 @@ export abstract class AbstractSelectComponent<T = ISelectItem, serviceResponse =
}
change(value: Maybe<T>) {
console.log('first');
if (typeof value !== 'object') {
// @ts-ignore
this.onChange.emit(this.items()?.find((item) => item[this.optionValue] === value));
+4
View File
@@ -0,0 +1,4 @@
export * from './abstract-form';
export * from './abstract-form-dialog.component';
export * from './abstract-list';
export * from './abstract-select.component';
@@ -1,2 +0,0 @@
export * from './select/select.component';
export * from './tag/tag.component';
@@ -1,14 +0,0 @@
<uikit-field label="بانک" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
<p-select
[loading]="loading()"
[options]="items()"
optionLabel="name"
[optionValue]="selectOptionValue"
placeholder="انتخاب بانک"
[formControl]="control"
[showClear]="true"
[filter]="true"
appendTo="body"
>
</p-select>
</uikit-field>
@@ -1,23 +0,0 @@
import { AbstractSelectComponent } from '@/shared/components';
import { UikitFieldComponent } from '@/uikit';
import { Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select';
import { IBankResponse } from '../../models';
import { BanksStore } from '../../store/main.store';
@Component({
selector: 'banks-select-field',
templateUrl: './select.component.html',
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
})
export class BanksSelectComponent extends AbstractSelectComponent<IBankResponse, IBankResponse[]> {
constructor(private store: BanksStore) {
super();
this.getData();
}
getDataService() {
return this.store.getItems();
}
}
@@ -1,3 +0,0 @@
<span>
{{ selectedBank?.name }}
</span>
@@ -1,16 +0,0 @@
import { Component, inject, Input } from '@angular/core';
import { BanksStore } from '../../store/main.store';
@Component({
selector: 'app-catalog-banks-tag',
templateUrl: './tag.component.html',
})
export class CatalogBanksComponent {
@Input() bankId!: number;
private readonly store = inject(BanksStore);
banks = this.store.banks;
selectedBank = this.banks().find((bank) => bank.id === this.bankId);
}
@@ -1,5 +0,0 @@
const baseUrl = '/api/v1/banks';
export const BANKS_API_ROUTES = {
list: () => `${baseUrl}`,
};
@@ -1 +0,0 @@
export * from './apiRoutes';
-1
View File
@@ -1 +0,0 @@
export * from './components';
@@ -1 +0,0 @@
export * from './io';
-19
View File
@@ -1,19 +0,0 @@
export interface IBankRawResponse {
id: number;
name: string;
shortName: string;
}
export interface IBankResponse extends IBankRawResponse {}
export interface IBankRequest {
firstName: string;
lastName: string;
email: string;
mobileNumber: string;
address: string;
city: string;
state: string;
country: string;
isActive: boolean;
}
@@ -1,17 +0,0 @@
import { IPaginatedResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { BANKS_API_ROUTES } from '../constants';
import { IBankRawResponse, IBankResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class BanksService {
constructor(private http: HttpClient) {}
private apiRoutes = BANKS_API_ROUTES;
getAll(): Observable<IPaginatedResponse<IBankResponse>> {
return this.http.get<IPaginatedResponse<IBankRawResponse>>(this.apiRoutes.list());
}
}
@@ -1,65 +0,0 @@
import { BaseState, BaseStore } from '@/core/state';
import { computed, Injectable } from '@angular/core';
import { of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { IBankResponse } from '../models';
import { BanksService } from '../services/main.service';
export interface banksState extends BaseState<IBankResponse[]> {
items: IBankResponse[];
}
@Injectable({
providedIn: 'root',
})
export class BanksStore extends BaseStore<banksState> {
constructor(private service: BanksService) {
super({
loading: false,
initialized: false,
error: null,
isRefreshing: false,
items: [],
});
this.initial();
}
readonly banks = computed(() => this.getCurrentState().items || []);
reset(): void {
this.setState({
loading: false,
initialized: false,
error: null,
isRefreshing: false,
items: [],
});
}
initial() {
this.getItems().subscribe();
}
getItems() {
if (this.getCurrentState().items?.length === 0) {
this.setLoading(true);
return this.service.getAll().pipe(
map((res: { data: IBankResponse[] }) => {
this.patchState({
items: res.data,
initialized: true,
});
this.setLoading(false);
return res.data;
}),
catchError((err: any) => {
this.setError(err.message || 'Failed to load banks');
this.setLoading(false);
return of([] as IBankResponse[]);
}),
);
} else {
return of(this.getCurrentState().items);
}
}
}
@@ -1,16 +0,0 @@
<uikit-field label="جنسیت" name="gender" [control]="control">
<ng-container *ngIf="formControl as fc">
<p-select
[options]="[
{ label: 'مرد', id: true },
{ label: 'زن', id: false },
]"
[size]="size"
[id]="'gender'"
[optionValue]="'id'"
[attr.name]="'gender'"
[formControl]="fc"
[invalid]="fc.invalid && (fc.dirty || fc.touched)"
></p-select>
</ng-container>
</uikit-field>
@@ -1,20 +0,0 @@
import { Maybe } from '@/core';
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { AbstractControl, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select';
@Component({
selector: 'app-gender-select',
templateUrl: './gender-select.component.html',
imports: [CommonModule, UikitFieldComponent, Select, ReactiveFormsModule],
})
export class GenderSelectComponent {
@Input() control!: Maybe<AbstractControl<any, any>>;
@Input() size?: 'small' | 'large';
get formControl(): FormControl | undefined {
return this.control as FormControl | undefined;
}
}
@@ -1 +0,0 @@
{{ genderTitle() }}
@@ -1,13 +0,0 @@
import { Component, computed, Input } from '@angular/core';
@Component({
selector: 'catalog-gender-tag',
templateUrl: './gender-tag.component.html',
})
export class CatalogGenderTagComponent {
@Input() gender!: boolean;
constructor() {}
genderTitle = computed(() => (this.gender ? 'مرد' : 'زن'));
}
-2
View File
@@ -1,2 +0,0 @@
export * from './gender-select.component';
export * from './gender-tag.component';
-3
View File
@@ -1,3 +0,0 @@
export * from './gender';
export * from './movementTypes';
export * from './purchaseReceiptStatus';
@@ -1,3 +0,0 @@
export * from './tag.component';
export * from './types';
export * from './utils';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,24 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { MovementType } from '../movementTypes';
import { MovementReferenceType } from './types';
import { getMovementReferenceTypeColor, getMovementReferenceTypeText } from './utils';
@Component({
selector: 'catalog-movement-reference-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogMovementReferenceTagComponent {
@Input() type!: MovementReferenceType;
@Input() movementType?: MovementType;
@Input() counterName?: string;
constructor() {}
get text() {
return getMovementReferenceTypeText(this.type, this.movementType, this.counterName);
}
get color() {
return getMovementReferenceTypeColor(this.type);
}
}
@@ -1,9 +0,0 @@
export const MovementReferenceType = {
PURCHASE: 'PURCHASE',
SALES: 'SALES',
ADJUSTMENT: 'ADJUSTMENT',
INVENTORY_TRANSFER: 'INVENTORY_TRANSFER',
} as const;
export type MovementReferenceType =
(typeof MovementReferenceType)[keyof typeof MovementReferenceType];
@@ -1,39 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { MovementType } from '../movementTypes';
import { MovementReferenceType } from './types';
export const getMovementReferenceTypeText = (
movementType: MovementReferenceType,
type?: MovementType,
counterName?: string,
): string => {
switch (movementType) {
case MovementReferenceType.PURCHASE:
return 'خرید';
case MovementReferenceType.SALES:
return 'فروش';
case MovementReferenceType.ADJUSTMENT:
return 'تعدیل';
case MovementReferenceType.INVENTORY_TRANSFER:
if (type === MovementType.IN) {
return `دریافت از انبار ${counterName ?? 'دیگر'}`;
} else {
return `ارسال به انبار ${counterName ?? 'دیگر'}`;
}
default:
return 'انتقال بین انبارها';
}
};
export const getMovementReferenceTypeColor = (movementType: MovementReferenceType): TagSeverity => {
switch (movementType) {
case MovementReferenceType.PURCHASE:
return 'success';
case MovementReferenceType.SALES:
return 'danger';
case MovementReferenceType.ADJUSTMENT:
return 'warn';
case MovementReferenceType.INVENTORY_TRANSFER:
return 'info';
}
};
@@ -1,3 +0,0 @@
export * from './tag.component';
export * from './types';
export * from './utils';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,21 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { MovementType } from './types';
import { getMovementTypeColor, getMovementTypeText } from './utils';
@Component({
selector: 'catalog-movement-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogMovementTagComponent {
@Input() type!: MovementType;
constructor() {}
get text() {
return getMovementTypeText(this.type);
}
get color() {
return getMovementTypeColor(this.type);
}
}
@@ -1,7 +0,0 @@
export const MovementType = {
IN: 'IN',
OUT: 'OUT',
ADJUST: 'ADJUST',
} as const;
export type MovementType = (typeof MovementType)[keyof typeof MovementType];
@@ -1,24 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { MovementType } from './types';
export const getMovementTypeText = (movementType: MovementType): string => {
switch (movementType) {
case MovementType.IN:
return 'ورودی';
case MovementType.OUT:
return 'خروجی';
case MovementType.ADJUST:
return 'تعدیل';
}
};
export const getMovementTypeColor = (movementType: MovementType): TagSeverity => {
switch (movementType) {
case MovementType.IN:
return 'success';
case MovementType.OUT:
return 'danger';
case MovementType.ADJUST:
return 'warn';
}
};
@@ -1,2 +0,0 @@
export * from './select/select.component';
export * from './tag/tag.component';
@@ -1,13 +0,0 @@
<uikit-field label="نوع پرداخت" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
<p-select
[options]="items"
optionLabel="label"
optionValue="value"
placeholder="انتخاب نوع پرداخت"
[formControl]="control"
[showClear]="true"
[filter]="true"
appendTo="body"
>
</p-select>
</uikit-field>
@@ -1,28 +0,0 @@
import { Maybe } from '@/core';
import { UikitFieldComponent } from '@/uikit';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select';
import { PaymentMethodType } from '../../types';
@Component({
selector: 'app-catalog-payment-method-type-select-field',
templateUrl: './select.component.html',
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
})
export class PaymentMethodTypeSelectComponent {
@Input() control = new FormControl<Maybe<PaymentMethodType>>(null);
@Input() showLabel: boolean = true;
@Input() showErrors: boolean = true;
@Input() showClear: boolean = false;
@Input() isFullDataOptionValue: boolean = false;
@Output() onChange = new EventEmitter<Maybe<PaymentMethodType>>();
items = [
{ label: 'نقدی', value: 'CASH' },
{ label: 'کارت بانکی', value: 'CARD' },
{ label: 'حساب', value: 'BANK' },
{ label: 'چک', value: 'CHECK' },
{ label: 'سایر', value: 'OTHER' },
] as { label: string; value: PaymentMethodType }[];
}
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,21 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { PaymentMethodType } from '../../types';
import { getPaymentMethodTypeColor, getPaymentMethodTypeText } from '../../utils';
@Component({
selector: 'app-catalog-payment-method-type-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogPaymentMethodTypeTagComponent {
@Input() type!: PaymentMethodType;
constructor() {}
get text() {
return getPaymentMethodTypeText(this.type);
}
get color() {
return getPaymentMethodTypeColor(this.type);
}
}
@@ -1,3 +0,0 @@
export * from './components/tag/tag.component';
export * from './types';
export * from './utils';
@@ -1,9 +0,0 @@
export const PaymentMethodType = {
CASH: 'CASH',
CARD: 'CARD',
BANK: 'BANK',
CHECK: 'CHECK',
OTHER: 'OTHER',
} as const;
export type PaymentMethodType = (typeof PaymentMethodType)[keyof typeof PaymentMethodType];
@@ -1,32 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { PaymentMethodType } from './types';
export const getPaymentMethodTypeText = (value: PaymentMethodType): string => {
switch (value) {
case PaymentMethodType.CASH:
return 'نقدی';
case PaymentMethodType.CARD:
return 'کارت';
case PaymentMethodType.BANK:
return 'بانک';
case PaymentMethodType.CHECK:
return 'چک';
case PaymentMethodType.OTHER:
return 'سایر';
}
};
export const getPaymentMethodTypeColor = (value: PaymentMethodType): TagSeverity => {
switch (value) {
case PaymentMethodType.CASH:
return 'danger';
case PaymentMethodType.CARD:
return 'info';
case PaymentMethodType.BANK:
return 'success';
case PaymentMethodType.CHECK:
return 'warn';
case PaymentMethodType.OTHER:
return 'secondary';
}
};
@@ -1 +0,0 @@
export * from './tag/tag.component';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,21 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { PaymentType } from '../../types';
import { getPaymentTypeColor, getPaymentTypeText } from '../../utils';
@Component({
selector: 'app-catalog-payment-type-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogPaymentTypeTagComponent {
@Input() type!: PaymentType;
constructor() {}
get text() {
return getPaymentTypeText(this.type);
}
get color() {
return getPaymentTypeColor(this.type);
}
}
@@ -1,3 +0,0 @@
export * from './components/tag/tag.component';
export * from './types';
export * from './utils';
@@ -1,6 +0,0 @@
export const PaymentType = {
PAYMENT: 'PAYMENT',
REFUND: 'REFUND',
} as const;
export type PaymentType = (typeof PaymentType)[keyof typeof PaymentType];
@@ -1,20 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { PaymentType } from './types';
export const getPaymentTypeText = (value: PaymentType): string => {
switch (value) {
case PaymentType.PAYMENT:
return 'پرداخت';
case PaymentType.REFUND:
return 'بازگشت وجه';
}
};
export const getPaymentTypeColor = (value: PaymentType): TagSeverity => {
switch (value) {
case PaymentType.PAYMENT:
return 'success';
case PaymentType.REFUND:
return 'warn';
}
};
@@ -1,3 +0,0 @@
export * from './tag.component';
export * from './types';
export * from './utils';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,21 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { PurchaseReceiptStatus } from './types';
import { getPurchaseReceiptStatusColor, getPurchaseReceiptStatusText } from './utils';
@Component({
selector: 'app-catalog-purchase-receipt-status-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogPurchaseReceiptStatusTagComponent {
@Input() type!: PurchaseReceiptStatus;
constructor() {}
get text() {
return getPurchaseReceiptStatusText(this.type);
}
get color() {
return getPurchaseReceiptStatusColor(this.type);
}
}
@@ -1,8 +0,0 @@
export const PurchaseReceiptStatus = {
UNPAID: 'UNPAID',
PARTIALLY_PAID: 'PARTIALLY_PAID',
PAID: 'PAID',
} as const;
export type PurchaseReceiptStatus =
(typeof PurchaseReceiptStatus)[keyof typeof PurchaseReceiptStatus];
@@ -1,24 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { PurchaseReceiptStatus } from './types';
export const getPurchaseReceiptStatusText = (value: PurchaseReceiptStatus): string => {
switch (value) {
case PurchaseReceiptStatus.UNPAID:
return 'پرداخت نشده';
case PurchaseReceiptStatus.PARTIALLY_PAID:
return 'پرداخت شده جزئی';
case PurchaseReceiptStatus.PAID:
return 'تسویه شده';
}
};
export const getPurchaseReceiptStatusColor = (value: PurchaseReceiptStatus): TagSeverity => {
switch (value) {
case PurchaseReceiptStatus.UNPAID:
return 'danger';
case PurchaseReceiptStatus.PARTIALLY_PAID:
return 'warn';
case PurchaseReceiptStatus.PAID:
return 'success';
}
};
@@ -1,2 +0,0 @@
export * from './select.component';
export * from './tag.component';
@@ -1,12 +0,0 @@
<uikit-field label="نقش" name="role" [control]="control">
<p-select
[options]="roles()"
[id]="'role'"
[optionValue]="'id'"
optionLabel="name"
[attr.name]="'role'"
[formControl]="control"
[loading]="loading()"
[invalid]="control.invalid && (control.dirty || control.touched)"
></p-select>
</uikit-field>
@@ -1,38 +0,0 @@
import { Maybe } from '@/core';
import { UikitFieldComponent } from '@/uikit';
import { Component, Input, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Select } from 'primeng/select';
import { IRoleResponse } from '../models';
import { RolesService } from '../services';
@Component({
selector: 'catalog-roles-select',
templateUrl: './select.component.html',
imports: [UikitFieldComponent, Select, ReactiveFormsModule],
})
export class CatalogRolesComponent {
@Input() control!: FormControl<Maybe<number>>;
constructor(private service: RolesService) {
this.getData();
}
roles = signal<IRoleResponse[]>([]);
loading = signal<boolean>(false);
private getData() {
this.loading.set(true);
return this.service.getAll().subscribe({
next: (res) => {
this.roles.set(res);
},
error: () => {
this.roles.set([]);
},
complete: () => {
this.loading.set(false);
},
});
}
}
@@ -1 +0,0 @@
{{ roleTitle() }}
@@ -1,16 +0,0 @@
import { Component, Input } from '@angular/core';
import { IRoleResponse } from '../models';
@Component({
selector: 'catalog-role-tag',
templateUrl: './tag.component.html',
})
export class CatalogRoleTagComponent {
@Input() role!: IRoleResponse;
constructor() {}
roleTitle() {
return this.role.name;
}
}
@@ -1,5 +0,0 @@
const baseUrl = '/api/v1/roles';
export const ROLES_API_ROUTES = {
list: () => `${baseUrl}`,
};
@@ -1 +0,0 @@
export * from './apiRoutes';
-4
View File
@@ -1,4 +0,0 @@
export * from './components';
export * from './constants';
export * from './models';
export * from './services';
@@ -1 +0,0 @@
export * from './io';
@@ -1,6 +0,0 @@
export interface IRoleRawResponse {
id: number;
name: string;
description: string;
}
export interface IRoleResponse extends IRoleRawResponse {}
@@ -1 +0,0 @@
export * from './main.service';
@@ -1,19 +0,0 @@
import { IPaginatedResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
import { ROLES_API_ROUTES } from '../constants';
import { IRoleRawResponse, IRoleResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class RolesService {
constructor(private http: HttpClient) {}
private apiRoutes = ROLES_API_ROUTES;
getAll(): Observable<IRoleResponse[]> {
return this.http
.get<IPaginatedResponse<IRoleRawResponse>>(this.apiRoutes.list())
.pipe(map((response) => response.data as IRoleResponse[]));
}
}
@@ -1,3 +0,0 @@
export * from './tag.component';
export * from './types';
export * from './utils';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,24 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { BankTransactionReferenceType } from './types';
import {
getBankAccountTransactionReferenceTypeColor,
getBankAccountTransactionReferenceTypeText,
} from './utils';
@Component({
selector: 'catalog-bank-account-transaction-reference-type-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogBankAccountTransactionReferenceTypeTagComponent {
@Input() type!: BankTransactionReferenceType;
constructor() {}
get text() {
return getBankAccountTransactionReferenceTypeText(this.type);
}
get color() {
return getBankAccountTransactionReferenceTypeColor(this.type);
}
}
@@ -1,11 +0,0 @@
export const BankTransactionReferenceType = {
PURCHASE_PAYMENT: 'PURCHASE_PAYMENT',
PURCHASE_REFUND: 'PURCHASE_REFUND',
POS_SALE: 'POS_SALE',
POS_REFUND: 'POS_REFUND',
BANK_TRANSFER: 'BANK_TRANSFER',
MANUAL_ADJUSTMENT: 'MANUAL_ADJUSTMENT',
} as const;
export type BankTransactionReferenceType =
(typeof BankTransactionReferenceType)[keyof typeof BankTransactionReferenceType];
@@ -1,40 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { BankTransactionReferenceType } from './types';
export const getBankAccountTransactionReferenceTypeText = (
type: BankTransactionReferenceType,
): string => {
switch (type) {
case BankTransactionReferenceType.BANK_TRANSFER:
return 'انتقال بانکی';
case BankTransactionReferenceType.MANUAL_ADJUSTMENT:
return 'تعدیل دستی';
case BankTransactionReferenceType.POS_REFUND:
return 'بازپرداخت فروشگاه';
case BankTransactionReferenceType.POS_SALE:
return 'فروش فروشگاه';
case BankTransactionReferenceType.PURCHASE_REFUND:
return 'بازپرداخت خرید';
case BankTransactionReferenceType.PURCHASE_PAYMENT:
return 'پرداخت خرید';
}
};
export const getBankAccountTransactionReferenceTypeColor = (
type: BankTransactionReferenceType,
): TagSeverity => {
switch (type) {
case BankTransactionReferenceType.BANK_TRANSFER:
return 'secondary';
case BankTransactionReferenceType.MANUAL_ADJUSTMENT:
return 'secondary';
case BankTransactionReferenceType.POS_REFUND:
return 'warn';
case BankTransactionReferenceType.POS_SALE:
return 'info';
case BankTransactionReferenceType.PURCHASE_REFUND:
return 'warn';
case BankTransactionReferenceType.PURCHASE_PAYMENT:
return 'info';
}
};
@@ -1,3 +0,0 @@
export * from './tag.component';
export * from './types';
export * from './utils';
@@ -1,3 +0,0 @@
<p-tag [severity]="color">
{{ text }}
</p-tag>
@@ -1,21 +0,0 @@
import { Component, Input } from '@angular/core';
import { Tag } from 'primeng/tag';
import { BankAccountTransactionType } from './types';
import { getBankAccountTransactionTypeColor, getBankAccountTransactionTypeText } from './utils';
@Component({
selector: 'catalog-bank-account-transaction-type-tag',
templateUrl: './tag.component.html',
imports: [Tag],
})
export class CatalogBankAccountTransactionTypeTagComponent {
@Input() type!: BankAccountTransactionType;
constructor() {}
get text() {
return getBankAccountTransactionTypeText(this.type);
}
get color() {
return getBankAccountTransactionTypeColor(this.type);
}
}
@@ -1,7 +0,0 @@
export const BankAccountTransactionType = {
DEPOSIT: 'DEPOSIT',
WITHDRAWAL: 'WITHDRAWAL',
} as const;
export type BankAccountTransactionType =
(typeof BankAccountTransactionType)[keyof typeof BankAccountTransactionType];
@@ -1,22 +0,0 @@
import { TagSeverity } from '@/core/models/tag';
import { BankAccountTransactionType } from './types';
export const getBankAccountTransactionTypeText = (type: BankAccountTransactionType): string => {
switch (type) {
case BankAccountTransactionType.DEPOSIT:
return 'واریز';
case BankAccountTransactionType.WITHDRAWAL:
return 'برداشت';
}
};
export const getBankAccountTransactionTypeColor = (
type: BankAccountTransactionType,
): TagSeverity => {
switch (type) {
case BankAccountTransactionType.DEPOSIT:
return 'success';
case BankAccountTransactionType.WITHDRAWAL:
return 'danger';
}
};
@@ -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();
}
}
@@ -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);
// }
// }