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:
2025-12-24 21:25:13 +03:30
parent 1373cc046d
commit f671e37b14
50 changed files with 885 additions and 9 deletions
@@ -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';
+1
View File
@@ -0,0 +1 @@
export * from './components';
@@ -0,0 +1 @@
export * from './io';
+19
View File
@@ -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;
}