feat: add bank accounts and branches management
- Implemented bank accounts management with form and list components. - Added bank branches management with form and list components. - Created services for bank accounts and branches to handle API interactions. - Updated routing to include bank accounts and branches. - Enhanced UI with new select fields for banks and branches. - Added necessary models and constants for bank accounts and branches. - Improved state management for banks using a store pattern. - Updated menu items to include links for bank accounts and branches.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
export * from './select/select.component';
|
||||
export * from './tag/tag.component';
|
||||
@@ -0,0 +1,14 @@
|
||||
<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>
|
||||
@@ -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<IBankResponse> {
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<span>
|
||||
{{ selectedBank?.name }}
|
||||
</span>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const baseUrl = '/api/v1/banks';
|
||||
|
||||
export const BANKS_API_ROUTES = {
|
||||
list: () => `${baseUrl}`,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './apiRoutes';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './components';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './io';
|
||||
+19
@@ -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;
|
||||
}
|
||||
@@ -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<IPaginatedResponse<IBankResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IBankRawResponse>>(this.apiRoutes.list());
|
||||
}
|
||||
}
|
||||
@@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ export interface IColumn<T = any> {
|
||||
width?: string;
|
||||
minWidth?: string;
|
||||
canCopy?: boolean;
|
||||
type?: 'text' | 'price' | 'boolean' | 'date';
|
||||
type?: 'text' | 'price' | 'boolean' | 'date' | 'nested';
|
||||
nestedPath?: string;
|
||||
customDataModel?: TemplateRef<any> | ((item: T) => string | number | boolean);
|
||||
}
|
||||
|
||||
@@ -130,6 +131,18 @@ export class PageDataListComponent<I> {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user