diff --git a/src/app.routes.ts b/src/app.routes.ts index bf3429f..e7bb518 100644 --- a/src/app.routes.ts +++ b/src/app.routes.ts @@ -1,4 +1,6 @@ import { AuthComponent } from '@/modules/auth/pages/auth.component'; +import { BANK_ACCOUNTS_ROUTES } from '@/modules/bankAccounts/constants'; +import { BANK_BRANCHES_ROUTES } from '@/modules/bankBranches/constants'; import { CARDEX_ROUTES } from '@/modules/cardex/constants'; import { CUSTOMERS_ROUTES } from '@/modules/customers/constants'; import { INVENTORIES_ROUTES } from '@/modules/inventories/constants'; @@ -30,6 +32,8 @@ export const appRoutes: Routes = [ ...INVENTORIES_ROUTES, ...PRODUCTS_ROUTES, ...CARDEX_ROUTES, + ...BANK_BRANCHES_ROUTES, + ...BANK_ACCOUNTS_ROUTES, { path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') }, { path: 'documentation', component: Documentation }, { path: 'pages', loadChildren: () => import('./app/pages/pages.routes') }, diff --git a/src/app/core/constants/menuItems.const.ts b/src/app/core/constants/menuItems.const.ts index 95694a8..ce982a3 100644 --- a/src/app/core/constants/menuItems.const.ts +++ b/src/app/core/constants/menuItems.const.ts @@ -57,9 +57,14 @@ export const MENU_ITEMS = [ icon: 'pi pi-fw pi-cog', items: [ { - label: 'فروشگاه‌ها', - icon: 'pi pi-fw pi-store', - routerLink: ['/stores'], + label: 'شعب بانک‌ها', + icon: 'pi pi-fw pi-building', + routerLink: ['/bankBranches'], + }, + { + label: 'حساب‌های بانکی', + icon: 'pi pi-fw pi-credit-card', + routerLink: ['/bankAccounts'], }, { label: 'کاربران', diff --git a/src/app/modules/bankAccounts/components/form.component.html b/src/app/modules/bankAccounts/components/form.component.html new file mode 100644 index 0000000..208367e --- /dev/null +++ b/src/app/modules/bankAccounts/components/form.component.html @@ -0,0 +1,18 @@ + +
+ + + + + + + + +
diff --git a/src/app/modules/bankAccounts/components/form.component.ts b/src/app/modules/bankAccounts/components/form.component.ts new file mode 100644 index 0000000..59e0068 --- /dev/null +++ b/src/app/modules/bankAccounts/components/form.component.ts @@ -0,0 +1,102 @@ +import { ToastService } from '@/core/services/toast.service'; +import { BankBranchesSelectComponent } from '@/modules/bankBranches/components/select/select.component'; +import { BanksSelectComponent } from '@/shared/catalog/banks'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IBankAccountsRequest, IBankAccountsResponse } from '../models'; +import { BankAccountsService } from '../services/main.service'; + +@Component({ + selector: 'bank-account-form', + templateUrl: './form.component.html', + imports: [ + ReactiveFormsModule, + Dialog, + InputComponent, + FormFooterActionsComponent, + BanksSelectComponent, + BankBranchesSelectComponent, + ], +}) +export class BankAccountFormComponent { + @Input() initialValues?: IBankAccountsResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor( + private service: BankAccountsService, + private toastService: ToastService, + ) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: this.fb.control(this.initialValues?.name || '', { + nonNullable: true, + validators: Validators.required, + }), + branchId: this.fb.control(this.initialValues?.branchId || 0, { + nonNullable: true, + validators: Validators.required, + }), + cardNumber: this.fb.control(this.initialValues?.cardNumber || '', { + nonNullable: true, + validators: Validators.required, + }), + accountNumber: this.fb.control(this.initialValues?.accountNumber || '', { + nonNullable: true, + validators: Validators.required, + }), + iban: this.fb.control(this.initialValues?.iban || '', { + nonNullable: true, + validators: Validators.required, + }), + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IBankAccountsRequest).subscribe({ + next: (res) => { + this.toastService.success({ + text: `حساب بانکی ${this.form.value.name} با موفقیت ایجاد شد`, + }); + this.close(); + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/bankAccounts/components/select/select.component.html b/src/app/modules/bankAccounts/components/select/select.component.html new file mode 100644 index 0000000..d6bc026 --- /dev/null +++ b/src/app/modules/bankAccounts/components/select/select.component.html @@ -0,0 +1,30 @@ + + + @if (canInsert) { + +
+ +
+
+ } +
+ +
diff --git a/src/app/modules/bankAccounts/components/select/select.component.ts b/src/app/modules/bankAccounts/components/select/select.component.ts new file mode 100644 index 0000000..76f20d9 --- /dev/null +++ b/src/app/modules/bankAccounts/components/select/select.component.ts @@ -0,0 +1,34 @@ +import { AbstractSelectComponent } from '@/shared/components'; +import { UikitFieldComponent } from '@/uikit'; +import { Component } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Button } from 'primeng/button'; +import { Select } from 'primeng/select'; +import { IBankAccountsResponse } from '../../models'; +import { BankAccountsService } from '../../services/main.service'; +import { BankAccountFormComponent } from '../form.component'; + +@Component({ + selector: 'bank-accounts-select-field', + templateUrl: './select.component.html', + imports: [ReactiveFormsModule, Select, UikitFieldComponent, Button, BankAccountFormComponent], +}) +export class BankAccountsSelectComponent extends AbstractSelectComponent { + constructor(private service: BankAccountsService) { + super(); + this.getData(); + } + + getData() { + this.loading.set(true); + this.service.getAll().subscribe({ + next: (res) => { + this.items.set(res.data); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } +} diff --git a/src/app/modules/bankAccounts/constants/apiRoutes/index.ts b/src/app/modules/bankAccounts/constants/apiRoutes/index.ts new file mode 100644 index 0000000..547e525 --- /dev/null +++ b/src/app/modules/bankAccounts/constants/apiRoutes/index.ts @@ -0,0 +1,5 @@ +const baseUrl = '/api/v1/bank-accounts'; + +export const BANK_ACCOUNTS_API_ROUTES = { + list: () => `${baseUrl}`, +}; diff --git a/src/app/modules/bankAccounts/constants/index.ts b/src/app/modules/bankAccounts/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/bankAccounts/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/bankAccounts/constants/routes/index.ts b/src/app/modules/bankAccounts/constants/routes/index.ts new file mode 100644 index 0000000..fca6d2d --- /dev/null +++ b/src/app/modules/bankAccounts/constants/routes/index.ts @@ -0,0 +1,25 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TBankAccountsRouteNames = 'bankAccounts' | 'bankAccount'; + +export const bankAccountsNamedRoutes: NamedRoutes = { + bankAccounts: { + path: 'bankAccounts', + loadComponent: () => import('../../views/list.component').then((m) => m.BankAccountsComponent), + meta: { + title: 'حساب‌های بانکی', + pagePath: () => '/bankAccounts', + }, + }, + bankAccount: { + path: 'bankAccount', + loadComponent: () => import('../../views/single.component').then((m) => m.BankAccountComponent), + meta: { + title: 'حساب‌ بانکی', + pagePath: () => '/bankAccount', + }, + }, +}; + +export const BANK_ACCOUNTS_ROUTES: Routes = Object.values(bankAccountsNamedRoutes); diff --git a/src/app/modules/bankAccounts/models/index.ts b/src/app/modules/bankAccounts/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/bankAccounts/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/bankAccounts/models/io.d.ts b/src/app/modules/bankAccounts/models/io.d.ts new file mode 100644 index 0000000..3c2596f --- /dev/null +++ b/src/app/modules/bankAccounts/models/io.d.ts @@ -0,0 +1,21 @@ +export interface IBankAccountsRawResponse { + id: number; + name: string; + branchId: number; + accountNumber?: string; + iban?: string; + cardNumber?: string; + createdAt?: string; + updatedAt?: string; + deletedAt?: string; +} + +export interface IBankAccountsResponse extends IBankAccountsRawResponse {} + +export interface IBankAccountsRequest { + name: string; + branchId: number; + accountNumber?: string; + iban?: string; + cardNumber?: string; +} diff --git a/src/app/modules/bankAccounts/models/types.ts b/src/app/modules/bankAccounts/models/types.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/modules/bankAccounts/services/main.service.ts b/src/app/modules/bankAccounts/services/main.service.ts new file mode 100644 index 0000000..3b32da0 --- /dev/null +++ b/src/app/modules/bankAccounts/services/main.service.ts @@ -0,0 +1,21 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { BANK_ACCOUNTS_API_ROUTES } from '../constants'; +import { IBankAccountsRawResponse, IBankAccountsRequest, IBankAccountsResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class BankAccountsService { + constructor(private http: HttpClient) {} + + private apiRoutes = BANK_ACCOUNTS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + + create(data: IBankAccountsRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/bankAccounts/views/index.ts b/src/app/modules/bankAccounts/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/bankAccounts/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/bankAccounts/views/list.component.html b/src/app/modules/bankAccounts/views/list.component.html new file mode 100644 index 0000000..8014e55 --- /dev/null +++ b/src/app/modules/bankAccounts/views/list.component.html @@ -0,0 +1,14 @@ + + + diff --git a/src/app/modules/bankAccounts/views/list.component.ts b/src/app/modules/bankAccounts/views/list.component.ts new file mode 100644 index 0000000..68d0664 --- /dev/null +++ b/src/app/modules/bankAccounts/views/list.component.ts @@ -0,0 +1,76 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { BankAccountFormComponent } from '../components/form.component'; +import { IBankAccountsResponse } from '../models'; +import { BankAccountsService } from '../services/main.service'; + +@Component({ + selector: 'app-bank-accounts', + templateUrl: './list.component.html', + imports: [PageDataListComponent, BankAccountFormComponent], +}) +export class BankAccountsComponent { + constructor(private service: BankAccountsService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه', width: '60px' }, + { + field: 'name', + header: 'نام', + minWidth: '100px', + }, + { + field: 'bank', + header: 'شعبه', + customDataModel(item) { + return `شعبه‌ی ${item.branch?.name} بانک ${item.branch?.bank?.name}`; + }, + minWidth: '200px', + }, + { + field: 'accountNumber', + header: 'شماره حساب بانکی', + canCopy: true, + minWidth: '160px', + }, + { + field: 'iban', + header: 'شماره شبا', + canCopy: true, + minWidth: '200px', + }, + { + field: 'cardNumber', + header: 'شماره کارت بانکی', + canCopy: true, + minWidth: '160px', + }, + { field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی', type: 'date' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + visibleForm = signal(false); + + refresh() { + this.getData(); + } + + getData() { + this.loading.set(true); + this.service.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res.data); + }); + } + + openAddForm() { + this.visibleForm.set(true); + } +} diff --git a/src/app/modules/bankAccounts/views/single.component.html b/src/app/modules/bankAccounts/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/bankAccounts/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/bankAccounts/views/single.component.ts b/src/app/modules/bankAccounts/views/single.component.ts new file mode 100644 index 0000000..689be65 --- /dev/null +++ b/src/app/modules/bankAccounts/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-bank-account', + templateUrl: './single.component.html', +}) +export class BankAccountComponent { + constructor() {} +} diff --git a/src/app/modules/bankBranches/components/form.component.html b/src/app/modules/bankBranches/components/form.component.html new file mode 100644 index 0000000..fbe06a1 --- /dev/null +++ b/src/app/modules/bankBranches/components/form.component.html @@ -0,0 +1,15 @@ + +
+ + + + + +
diff --git a/src/app/modules/bankBranches/components/form.component.ts b/src/app/modules/bankBranches/components/form.component.ts new file mode 100644 index 0000000..7c3e2f3 --- /dev/null +++ b/src/app/modules/bankBranches/components/form.component.ts @@ -0,0 +1,92 @@ +import { ToastService } from '@/core/services/toast.service'; +import { BanksSelectComponent } from '@/shared/catalog/banks'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { IBankBranchRequest, IBankBranchResponse } from '../models'; +import { BankBranchesService } from '../services/main.service'; + +@Component({ + selector: 'bank-branch-form', + templateUrl: './form.component.html', + imports: [ + ReactiveFormsModule, + Dialog, + InputComponent, + FormFooterActionsComponent, + BanksSelectComponent, + ], +}) +export class BankBranchFormComponent { + @Input() initialValues?: IBankBranchResponse; + + @Input() + set visible(v: boolean) { + this.visibleSignal.set(!!v); + } + get visible() { + return this.visibleSignal(); + } + private visibleSignal = signal(false); + + @Output() visibleChange = new EventEmitter(); + @Output() onSubmit = new EventEmitter(); + + private fb = inject(FormBuilder); + constructor( + private service: BankBranchesService, + private toastService: ToastService, + ) { + effect(() => { + const v = this.visibleSignal(); + if (!v) this.form.reset(); + }); + } + + form = this.fb.group({ + name: this.fb.control(this.initialValues?.name || '', { + nonNullable: true, + validators: Validators.required, + }), + code: this.fb.control(this.initialValues?.code || '', { + nonNullable: true, + validators: Validators.required, + }), + bankId: this.fb.control(this.initialValues?.bank?.id || 0, { + nonNullable: true, + validators: Validators.required, + }), + }); + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.service.create(this.form.value as IBankBranchRequest).subscribe({ + next: (res) => { + this.toastService.success({ + text: `شعبه بانک ${this.form.value.name} با موفقیت ایجاد شد`, + }); + this.close(); + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.visibleChange.emit(false); + } +} diff --git a/src/app/modules/bankBranches/components/select/select.component.html b/src/app/modules/bankBranches/components/select/select.component.html new file mode 100644 index 0000000..f327d6e --- /dev/null +++ b/src/app/modules/bankBranches/components/select/select.component.html @@ -0,0 +1,30 @@ + + + @if (canInsert) { + +
+ +
+
+ } +
+ +
diff --git a/src/app/modules/bankBranches/components/select/select.component.ts b/src/app/modules/bankBranches/components/select/select.component.ts new file mode 100644 index 0000000..cf717c2 --- /dev/null +++ b/src/app/modules/bankBranches/components/select/select.component.ts @@ -0,0 +1,34 @@ +import { AbstractSelectComponent } from '@/shared/components'; +import { UikitFieldComponent } from '@/uikit'; +import { Component } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Button } from 'primeng/button'; +import { Select } from 'primeng/select'; +import { IBankBranchResponse } from '../../models'; +import { BankBranchesService } from '../../services/main.service'; +import { BankBranchFormComponent } from '../form.component'; + +@Component({ + selector: 'bank-branches-select-field', + templateUrl: './select.component.html', + imports: [ReactiveFormsModule, Select, UikitFieldComponent, Button, BankBranchFormComponent], +}) +export class BankBranchesSelectComponent extends AbstractSelectComponent { + constructor(private service: BankBranchesService) { + super(); + this.getData(); + } + + getData() { + this.loading.set(true); + this.service.getAll().subscribe({ + next: (res) => { + this.items.set(res.data); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } +} diff --git a/src/app/modules/bankBranches/constants/apiRoutes/index.ts b/src/app/modules/bankBranches/constants/apiRoutes/index.ts new file mode 100644 index 0000000..15c7647 --- /dev/null +++ b/src/app/modules/bankBranches/constants/apiRoutes/index.ts @@ -0,0 +1,5 @@ +const baseUrl = '/api/v1/bank-branches'; + +export const BANK_BRANCHES_API_ROUTES = { + list: () => `${baseUrl}`, +}; diff --git a/src/app/modules/bankBranches/constants/index.ts b/src/app/modules/bankBranches/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/modules/bankBranches/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/modules/bankBranches/constants/routes/index.ts b/src/app/modules/bankBranches/constants/routes/index.ts new file mode 100644 index 0000000..84efeac --- /dev/null +++ b/src/app/modules/bankBranches/constants/routes/index.ts @@ -0,0 +1,17 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TBankBranchesRouteNames = 'bankBranches'; + +export const bankBranchesNamedRoutes: NamedRoutes = { + bankBranches: { + path: 'bankBranches', + loadComponent: () => import('../../views/list.component').then((m) => m.BankBranchesComponent), + meta: { + title: 'شعب بانک', + pagePath: () => '/bankBranches', + }, + }, +}; + +export const BANK_BRANCHES_ROUTES: Routes = Object.values(bankBranchesNamedRoutes); diff --git a/src/app/modules/bankBranches/models/index.ts b/src/app/modules/bankBranches/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/modules/bankBranches/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/modules/bankBranches/models/io.d.ts b/src/app/modules/bankBranches/models/io.d.ts new file mode 100644 index 0000000..d9917d9 --- /dev/null +++ b/src/app/modules/bankBranches/models/io.d.ts @@ -0,0 +1,19 @@ +import { IBankResponse } from '@/shared/catalog/banks/models'; + +export interface IBankBranchRawResponse { + id: number; + name: string; + code: string; + bank: IBankResponse; + createdAt: Date; + updatedAt: Date; + deletedAt: Date; +} + +export interface IBankBranchResponse extends IBankBranchRawResponse {} + +export interface IBankBranchRequest { + name: string; + code: string; + bankId: number; +} diff --git a/src/app/modules/bankBranches/models/types.ts b/src/app/modules/bankBranches/models/types.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/modules/bankBranches/services/main.service.ts b/src/app/modules/bankBranches/services/main.service.ts new file mode 100644 index 0000000..42fe134 --- /dev/null +++ b/src/app/modules/bankBranches/services/main.service.ts @@ -0,0 +1,21 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { BANK_BRANCHES_API_ROUTES } from '../constants'; +import { IBankBranchRawResponse, IBankBranchRequest, IBankBranchResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class BankBranchesService { + constructor(private http: HttpClient) {} + + private apiRoutes = BANK_BRANCHES_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + + create(data: IBankBranchRequest): Observable { + return this.http.post(this.apiRoutes.list(), data); + } +} diff --git a/src/app/modules/bankBranches/views/index.ts b/src/app/modules/bankBranches/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/modules/bankBranches/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/modules/bankBranches/views/list.component.html b/src/app/modules/bankBranches/views/list.component.html new file mode 100644 index 0000000..92c1fcb --- /dev/null +++ b/src/app/modules/bankBranches/views/list.component.html @@ -0,0 +1,14 @@ + + + diff --git a/src/app/modules/bankBranches/views/list.component.ts b/src/app/modules/bankBranches/views/list.component.ts new file mode 100644 index 0000000..4047ed4 --- /dev/null +++ b/src/app/modules/bankBranches/views/list.component.ts @@ -0,0 +1,48 @@ +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, signal } from '@angular/core'; +import { BankBranchFormComponent } from '../components/form.component'; +import { IBankBranchResponse } from '../models'; +import { BankBranchesService } from '../services/main.service'; + +@Component({ + selector: 'app-bank-branches', + templateUrl: './list.component.html', + imports: [PageDataListComponent, BankBranchFormComponent], +}) +export class BankBranchesComponent { + constructor(private service: BankBranchesService) { + this.getData(); + } + + columns = [ + { field: 'id', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { field: 'code', header: 'کد شعبه' }, + { field: 'bank', header: 'بانک', type: 'nested', nestedPath: 'name' }, + { field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی', type: 'date' }, + ] as IColumn[]; + + loading = signal(false); + items = signal([]); + visibleForm = signal(false); + + refresh() { + this.getData(); + } + + getData() { + this.loading.set(true); + this.service.getAll().subscribe((res) => { + this.loading.set(false); + this.items.set(res.data); + }); + } + + openAddForm() { + this.visibleForm.set(true); + } +} diff --git a/src/app/modules/bankBranches/views/single.component.html b/src/app/modules/bankBranches/views/single.component.html new file mode 100644 index 0000000..7144e26 --- /dev/null +++ b/src/app/modules/bankBranches/views/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/modules/bankBranches/views/single.component.ts b/src/app/modules/bankBranches/views/single.component.ts new file mode 100644 index 0000000..77d906c --- /dev/null +++ b/src/app/modules/bankBranches/views/single.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-customer', + templateUrl: './single.component.html', +}) +export class CustomerComponent { + constructor() {} +} diff --git a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html index 4055caa..8b15fc6 100644 --- a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html +++ b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html @@ -60,7 +60,7 @@
- تاریخ رسید: + تاریخ خرید: diff --git a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts index 08aafe3..427d4e7 100644 --- a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts +++ b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts @@ -94,7 +94,8 @@ export class PurchaseReceiptTemplateComponent { form = this.fb.group({ totalAmount: [0, [Validators.required, Validators.min(0)]], code: ['', [Validators.required]], - isSettled: [false], + isSettled: this.fb.control(false, { validators: [Validators.required] }), + paidAmount: this.fb.control(0, { validators: [Validators.required, Validators.min(0)] }), buyAt: [dayjs().calendar('jalali').format('YYYY/MM/DD'), Validators.required], description: [''], products: this.fb.array([ @@ -115,14 +116,20 @@ export class PurchaseReceiptTemplateComponent { private toastService: ToastService, ) { this.form.controls.products.valueChanges.subscribe((products) => { - console.log('first'); - let totalAmount = 0; products.forEach((p: any) => { totalAmount += p.total || 0; }); this.form.controls.totalAmount.setValue(totalAmount); }); + + this.form.controls.isSettled.valueChanges.subscribe((isSettled) => { + if (isSettled) { + this.form.controls.paidAmount.setValue(this.totalAmount); + } else { + this.form.controls.paidAmount.setValue(0); + } + }); } formIsSubmitted = signal(false); diff --git a/src/app/shared/catalog/banks/components/index.ts b/src/app/shared/catalog/banks/components/index.ts new file mode 100644 index 0000000..162984a --- /dev/null +++ b/src/app/shared/catalog/banks/components/index.ts @@ -0,0 +1,2 @@ +export * from './select/select.component'; +export * from './tag/tag.component'; diff --git a/src/app/shared/catalog/banks/components/select/select.component.html b/src/app/shared/catalog/banks/components/select/select.component.html new file mode 100644 index 0000000..fef0b94 --- /dev/null +++ b/src/app/shared/catalog/banks/components/select/select.component.html @@ -0,0 +1,14 @@ + + + + diff --git a/src/app/shared/catalog/banks/components/select/select.component.ts b/src/app/shared/catalog/banks/components/select/select.component.ts new file mode 100644 index 0000000..7313c26 --- /dev/null +++ b/src/app/shared/catalog/banks/components/select/select.component.ts @@ -0,0 +1,34 @@ +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 { + constructor(private store: BanksStore) { + super(); + this.getData(); + } + + getData() { + this.loading.set(true); + this.store.getItems().subscribe({ + next: (res) => { + console.log(res); + + this.items.set(res || []); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } +} diff --git a/src/app/shared/catalog/banks/components/tag/tag.component.html b/src/app/shared/catalog/banks/components/tag/tag.component.html new file mode 100644 index 0000000..d694fca --- /dev/null +++ b/src/app/shared/catalog/banks/components/tag/tag.component.html @@ -0,0 +1,3 @@ + + {{ selectedBank?.name }} + diff --git a/src/app/shared/catalog/banks/components/tag/tag.component.ts b/src/app/shared/catalog/banks/components/tag/tag.component.ts new file mode 100644 index 0000000..6fea533 --- /dev/null +++ b/src/app/shared/catalog/banks/components/tag/tag.component.ts @@ -0,0 +1,16 @@ +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); +} diff --git a/src/app/shared/catalog/banks/constants/apiRoutes/index.ts b/src/app/shared/catalog/banks/constants/apiRoutes/index.ts new file mode 100644 index 0000000..0cd6e36 --- /dev/null +++ b/src/app/shared/catalog/banks/constants/apiRoutes/index.ts @@ -0,0 +1,5 @@ +const baseUrl = '/api/v1/banks'; + +export const BANKS_API_ROUTES = { + list: () => `${baseUrl}`, +}; diff --git a/src/app/shared/catalog/banks/constants/index.ts b/src/app/shared/catalog/banks/constants/index.ts new file mode 100644 index 0000000..b853783 --- /dev/null +++ b/src/app/shared/catalog/banks/constants/index.ts @@ -0,0 +1 @@ +export * from './apiRoutes'; diff --git a/src/app/shared/catalog/banks/index.ts b/src/app/shared/catalog/banks/index.ts new file mode 100644 index 0000000..07635cb --- /dev/null +++ b/src/app/shared/catalog/banks/index.ts @@ -0,0 +1 @@ +export * from './components'; diff --git a/src/app/shared/catalog/banks/models/index.ts b/src/app/shared/catalog/banks/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/shared/catalog/banks/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/shared/catalog/banks/models/io.d.ts b/src/app/shared/catalog/banks/models/io.d.ts new file mode 100644 index 0000000..37da4e1 --- /dev/null +++ b/src/app/shared/catalog/banks/models/io.d.ts @@ -0,0 +1,19 @@ +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; +} diff --git a/src/app/shared/catalog/banks/services/main.service.ts b/src/app/shared/catalog/banks/services/main.service.ts new file mode 100644 index 0000000..5619c79 --- /dev/null +++ b/src/app/shared/catalog/banks/services/main.service.ts @@ -0,0 +1,17 @@ +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> { + return this.http.get>(this.apiRoutes.list()); + } +} diff --git a/src/app/shared/catalog/banks/store/main.store.ts b/src/app/shared/catalog/banks/store/main.store.ts new file mode 100644 index 0000000..abf3e6b --- /dev/null +++ b/src/app/shared/catalog/banks/store/main.store.ts @@ -0,0 +1,63 @@ +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 {} + +@Injectable({ + providedIn: 'root', +}) +export class BanksStore extends BaseStore { + 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); + } + } +} diff --git a/src/app/shared/components/pageDataList/page-data-list.component.ts b/src/app/shared/components/pageDataList/page-data-list.component.ts index 802e52c..4f9c6f9 100644 --- a/src/app/shared/components/pageDataList/page-data-list.component.ts +++ b/src/app/shared/components/pageDataList/page-data-list.component.ts @@ -24,7 +24,8 @@ export interface IColumn { width?: string; minWidth?: string; canCopy?: boolean; - type?: 'text' | 'price' | 'boolean' | 'date'; + type?: 'text' | 'price' | 'boolean' | 'date' | 'nested'; + nestedPath?: string; customDataModel?: TemplateRef | ((item: T) => string | number | boolean); } @@ -130,6 +131,18 @@ export class PageDataListComponent { return data ? 'بله' : 'خیر'; case 'price': return formatWithCurrency(data, false, 'ریال'); + case 'nested': { + const path = column.nestedPath; + if (!path) return '-'; + const nestedData = path + .split('.') + .reduce( + (obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), + item[String(field)], + ); + + return nestedData || '-'; + } default: break; } diff --git a/src/app/uikit/copy/copy.component.html b/src/app/uikit/copy/copy.component.html index ff71734..6e4740a 100644 --- a/src/app/uikit/copy/copy.component.html +++ b/src/app/uikit/copy/copy.component.html @@ -1,5 +1,5 @@