From 9eb5c3528532b1f7429405cf1a1eda98e472aa5f Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Mon, 13 Apr 2026 15:47:50 +0330 Subject: [PATCH] init to partner panel --- .../consumer/constants/menuItems.const.ts | 2 +- .../partner/constants/apiRoutes/index.ts | 5 ++ src/app/domains/partner/constants/index.ts | 1 + .../partner/constants/menuItems.const.ts | 12 ++- .../partner/layouts/layout.component.html | 6 +- .../partner/layouts/layout.component.ts | 12 ++- src/app/domains/partner/models/index.ts | 1 + src/app/domains/partner/models/io.d.ts | 5 ++ .../accounts/components/form.component.html | 41 ++++++++++ .../accounts/components/form.component.ts | 40 ++++++++++ .../accounts/components/layout.component.html | 5 ++ .../accounts/components/layout.component.ts | 26 +++++++ .../accounts/constants/apiRoutes/index.ts | 6 ++ .../modules/accounts/constants/index.ts | 2 + .../accounts/constants/routes/index.ts | 40 ++++++++++ .../partner/modules/accounts/models/index.ts | 1 + .../partner/modules/accounts/models/io.d.ts | 20 +++++ .../modules/accounts/services/main.service.ts | 24 ++++++ .../modules/accounts/store/account.store.ts | 67 +++++++++++++++++ .../partner/modules/accounts/views/index.ts | 2 + .../accounts/views/list.component.html | 19 +++++ .../modules/accounts/views/list.component.ts | 41 ++++++++++ .../accounts/views/single.component.html | 11 +++ .../accounts/views/single.component.ts | 39 ++++++++++ .../components/layout.component.html | 5 ++ .../customers/components/layout.component.ts | 26 +++++++ .../customers/components/list.component.html | 11 +++ .../customers/components/list.component.ts | 48 ++++++++++++ .../customers/constants/apiRoutes/index.ts | 6 ++ .../modules/customers/constants/index.ts | 2 + .../customers/constants/routes/index.ts | 40 ++++++++++ .../partner/modules/customers/models/index.ts | 1 + .../partner/modules/customers/models/io.d.ts | 8 ++ .../customers/services/main.service.ts | 20 +++++ .../modules/customers/store/customer.store.ts | 75 +++++++++++++++++++ .../partner/modules/customers/views/index.ts | 2 + .../customers/views/list.component.html | 1 + .../modules/customers/views/list.component.ts | 10 +++ .../customers/views/single.component.html | 9 +++ .../customers/views/single.component.ts | 39 ++++++++++ .../dashboard/views/index.component.html | 4 +- .../dashboard/views/index.component.ts | 4 +- src/app/domains/partner/routes.ts | 6 +- .../domains/partner/services/main.service.ts | 16 ++++ src/app/domains/partner/store/main.store.ts | 57 ++++++++++++++ 45 files changed, 806 insertions(+), 12 deletions(-) create mode 100644 src/app/domains/partner/constants/apiRoutes/index.ts create mode 100644 src/app/domains/partner/models/index.ts create mode 100644 src/app/domains/partner/models/io.d.ts create mode 100644 src/app/domains/partner/modules/accounts/components/form.component.html create mode 100644 src/app/domains/partner/modules/accounts/components/form.component.ts create mode 100644 src/app/domains/partner/modules/accounts/components/layout.component.html create mode 100644 src/app/domains/partner/modules/accounts/components/layout.component.ts create mode 100644 src/app/domains/partner/modules/accounts/constants/apiRoutes/index.ts create mode 100644 src/app/domains/partner/modules/accounts/constants/index.ts create mode 100644 src/app/domains/partner/modules/accounts/constants/routes/index.ts create mode 100644 src/app/domains/partner/modules/accounts/models/index.ts create mode 100644 src/app/domains/partner/modules/accounts/models/io.d.ts create mode 100644 src/app/domains/partner/modules/accounts/services/main.service.ts create mode 100644 src/app/domains/partner/modules/accounts/store/account.store.ts create mode 100644 src/app/domains/partner/modules/accounts/views/index.ts create mode 100644 src/app/domains/partner/modules/accounts/views/list.component.html create mode 100644 src/app/domains/partner/modules/accounts/views/list.component.ts create mode 100644 src/app/domains/partner/modules/accounts/views/single.component.html create mode 100644 src/app/domains/partner/modules/accounts/views/single.component.ts create mode 100644 src/app/domains/partner/modules/customers/components/layout.component.html create mode 100644 src/app/domains/partner/modules/customers/components/layout.component.ts create mode 100644 src/app/domains/partner/modules/customers/components/list.component.html create mode 100644 src/app/domains/partner/modules/customers/components/list.component.ts create mode 100644 src/app/domains/partner/modules/customers/constants/apiRoutes/index.ts create mode 100644 src/app/domains/partner/modules/customers/constants/index.ts create mode 100644 src/app/domains/partner/modules/customers/constants/routes/index.ts create mode 100644 src/app/domains/partner/modules/customers/models/index.ts create mode 100644 src/app/domains/partner/modules/customers/models/io.d.ts create mode 100644 src/app/domains/partner/modules/customers/services/main.service.ts create mode 100644 src/app/domains/partner/modules/customers/store/customer.store.ts create mode 100644 src/app/domains/partner/modules/customers/views/index.ts create mode 100644 src/app/domains/partner/modules/customers/views/list.component.html create mode 100644 src/app/domains/partner/modules/customers/views/list.component.ts create mode 100644 src/app/domains/partner/modules/customers/views/single.component.html create mode 100644 src/app/domains/partner/modules/customers/views/single.component.ts create mode 100644 src/app/domains/partner/services/main.service.ts create mode 100644 src/app/domains/partner/store/main.store.ts diff --git a/src/app/domains/consumer/constants/menuItems.const.ts b/src/app/domains/consumer/constants/menuItems.const.ts index e6cdfbf..044ac6e 100644 --- a/src/app/domains/consumer/constants/menuItems.const.ts +++ b/src/app/domains/consumer/constants/menuItems.const.ts @@ -5,7 +5,7 @@ export const CONSUMER_MENU_ITEMS = [ { label: 'داشبورد', icon: 'pi pi-fw pi-home', - routerLink: ['/'], + routerLink: ['/consumer'], }, { label: 'حساب‌های کاربری', diff --git a/src/app/domains/partner/constants/apiRoutes/index.ts b/src/app/domains/partner/constants/apiRoutes/index.ts new file mode 100644 index 0000000..8e4bf96 --- /dev/null +++ b/src/app/domains/partner/constants/apiRoutes/index.ts @@ -0,0 +1,5 @@ +const baseUrl = '/api/v1/partner'; + +export const PARTNER_API_ROUTES = { + info: () => `${baseUrl}`, +}; diff --git a/src/app/domains/partner/constants/index.ts b/src/app/domains/partner/constants/index.ts index 0e07b28..0c680cb 100644 --- a/src/app/domains/partner/constants/index.ts +++ b/src/app/domains/partner/constants/index.ts @@ -1 +1,2 @@ +export * from './apiRoutes'; export * from './menuItems.const'; diff --git a/src/app/domains/partner/constants/menuItems.const.ts b/src/app/domains/partner/constants/menuItems.const.ts index 3f875c7..7e76051 100644 --- a/src/app/domains/partner/constants/menuItems.const.ts +++ b/src/app/domains/partner/constants/menuItems.const.ts @@ -5,7 +5,17 @@ export const PARTNER_MENU_ITEMS = [ { label: 'داشبورد', icon: 'pi pi-fw pi-home', - routerLink: ['/'], + routerLink: ['/partner'], + }, + { + label: 'حساب‌های کاربری', + icon: 'pi pi-fw pi-home', + routerLink: ['/partner/accounts'], + }, + { + label: 'مشتریان', + icon: 'pi pi-fw pi-home', + routerLink: ['/partner/customers'], }, ], }, diff --git a/src/app/domains/partner/layouts/layout.component.html b/src/app/domains/partner/layouts/layout.component.html index 0680b43..5416fe1 100644 --- a/src/app/domains/partner/layouts/layout.component.html +++ b/src/app/domains/partner/layouts/layout.component.html @@ -1 +1,5 @@ - +@if (loading()) { + +} @else { + +} diff --git a/src/app/domains/partner/layouts/layout.component.ts b/src/app/domains/partner/layouts/layout.component.ts index f64bf3a..fb3f8c5 100644 --- a/src/app/domains/partner/layouts/layout.component.ts +++ b/src/app/domains/partner/layouts/layout.component.ts @@ -1,17 +1,25 @@ import { LayoutService } from '@/layout/service/layout.service'; -import { Component, inject } from '@angular/core'; +import { PageLoadingComponent } from '@/shared/components/page-loading.component'; +import { Component, computed, inject, TemplateRef, ViewChild } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { PARTNER_MENU_ITEMS } from '../constants'; +import { PartnerStore } from '../store/main.store'; @Component({ selector: 'partner-layout', templateUrl: './layout.component.html', - imports: [RouterOutlet], + imports: [RouterOutlet, PageLoadingComponent], }) export class LayoutComponent { + @ViewChild('topBarMoreAction') topBarMoreAction!: TemplateRef; + private readonly layoutService = inject(LayoutService); + private readonly store = inject(PartnerStore); + + readonly loading = computed(() => this.store.loading()); ngOnInit() { this.layoutService.setMenuItems(PARTNER_MENU_ITEMS); + this.store.getData(); } } diff --git a/src/app/domains/partner/models/index.ts b/src/app/domains/partner/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/partner/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/domains/partner/models/io.d.ts b/src/app/domains/partner/models/io.d.ts new file mode 100644 index 0000000..c4d9f58 --- /dev/null +++ b/src/app/domains/partner/models/io.d.ts @@ -0,0 +1,5 @@ +export interface IPartnerInfoRawResponse { + id: string; + name: string; +} +export interface IPartnerInfoResponse extends IPartnerInfoRawResponse {} diff --git a/src/app/domains/partner/modules/accounts/components/form.component.html b/src/app/domains/partner/modules/accounts/components/form.component.html new file mode 100644 index 0000000..79a0dc2 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/components/form.component.html @@ -0,0 +1,41 @@ + +
+ + + + رمز باید حداقل ۸ کاراکتر بوده و شامل حداقل یک حرف بزرگ، یک حرف کوچک، یک عدد و یک کاراکتر ویژه باشد. + + + + + + + + + +
diff --git a/src/app/domains/partner/modules/accounts/components/form.component.ts b/src/app/domains/partner/modules/accounts/components/form.component.ts new file mode 100644 index 0000000..495d941 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/components/form.component.ts @@ -0,0 +1,40 @@ +import { MustMatch, password } from '@/core/validators'; +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { Password } from 'primeng/password'; +import { IAccountRequest, IAccountResponse } from '../models'; +import { AccountsService } from '../services/main.service'; + +@Component({ + selector: 'account-password-form', + templateUrl: './form.component.html', + imports: [ReactiveFormsModule, Dialog, FormFooterActionsComponent, UikitFieldComponent, Password], +}) +export class PartnerAccountPasswordFormComponent extends AbstractFormDialog< + IAccountRequest, + IAccountResponse +> { + @Input({ required: true }) accountId!: string; + private service = inject(AccountsService); + + form = this.fb.group( + { + password: ['', [Validators.required, password()]], + confirmPassword: ['', [Validators.required]], + }, + { + validators: [MustMatch('password', 'confirmPassword')], + }, + ); + + override submitForm(payload: IAccountRequest) { + return this.service.update(this.accountId, payload); + // @ts-ignore + // const { confirmPassword, ...rest } = payload; + // return this.service.create(rest); + } +} diff --git a/src/app/domains/partner/modules/accounts/components/layout.component.html b/src/app/domains/partner/modules/accounts/components/layout.component.html new file mode 100644 index 0000000..5416fe1 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/components/layout.component.html @@ -0,0 +1,5 @@ +@if (loading()) { + +} @else { + +} diff --git a/src/app/domains/partner/modules/accounts/components/layout.component.ts b/src/app/domains/partner/modules/accounts/components/layout.component.ts new file mode 100644 index 0000000..c0916c9 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/components/layout.component.ts @@ -0,0 +1,26 @@ +import { PageLoadingComponent } from '@/shared/components/page-loading.component'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ActivatedRoute, RouterOutlet } from '@angular/router'; +import { AccountStore } from '../store/account.store'; + +@Component({ + selector: 'partner-account-layout', + templateUrl: './layout.component.html', + imports: [PageLoadingComponent, RouterOutlet], +}) +export class PartnerAccountLayoutComponent { + private readonly route = inject(ActivatedRoute); + private readonly store = inject(AccountStore); + + readonly accountId = signal(this.route.snapshot.paramMap.get('accountId')!); + + readonly loading = computed(() => this.store.loading()); + + getData() { + this.store.getData(this.accountId()); + } + + ngOnInit() { + this.getData(); + } +} diff --git a/src/app/domains/partner/modules/accounts/constants/apiRoutes/index.ts b/src/app/domains/partner/modules/accounts/constants/apiRoutes/index.ts new file mode 100644 index 0000000..085848a --- /dev/null +++ b/src/app/domains/partner/modules/accounts/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/partner/accounts'; + +export const PARTNER_ACCOUNTS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/domains/partner/modules/accounts/constants/index.ts b/src/app/domains/partner/modules/accounts/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/partner/modules/accounts/constants/routes/index.ts b/src/app/domains/partner/modules/accounts/constants/routes/index.ts new file mode 100644 index 0000000..0bc545a --- /dev/null +++ b/src/app/domains/partner/modules/accounts/constants/routes/index.ts @@ -0,0 +1,40 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPartnerAccountsRouteNames = 'accounts' | 'account'; + +export const partnerAccountsNamedRoutes: NamedRoutes = { + accounts: { + path: 'accounts', + loadComponent: () => + import('../../views/list.component').then((m) => m.PartnerAccountsComponent), + meta: { + title: 'حساب‌های کاربری', + pagePath: () => '/partner/accounts', + }, + }, + account: { + path: 'accounts/:accountId', + loadComponent: () => + import('../../views/single.component').then((m) => m.PartnerAccountComponent), + meta: { + title: 'حساب کاربری', + pagePath: (accountId: string) => `/partner/accounts/${accountId}`, + }, + }, +}; + +export const PARTNER_ACCOUNTS_ROUTES: Routes = [ + partnerAccountsNamedRoutes.accounts, + { + path: partnerAccountsNamedRoutes.account.path, + loadComponent: () => + import('../../components/layout.component').then((m) => m.PartnerAccountLayoutComponent), + children: [ + { + ...partnerAccountsNamedRoutes.account, + path: '', + }, + ], + }, +]; diff --git a/src/app/domains/partner/modules/accounts/models/index.ts b/src/app/domains/partner/modules/accounts/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/partner/modules/accounts/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/domains/partner/modules/accounts/models/io.d.ts b/src/app/domains/partner/modules/accounts/models/io.d.ts new file mode 100644 index 0000000..06ff6de --- /dev/null +++ b/src/app/domains/partner/modules/accounts/models/io.d.ts @@ -0,0 +1,20 @@ +export interface IAccountRawResponse { + id: string; + role: string; + created_at: string; + account: Account; +} +export interface IAccountResponse extends IAccountRawResponse {} + +export interface IAccountRequest { + name: string; +} + +export interface IAccountPasswordRequest { + password: string; +} + +interface Account { + username: string; + status: string; +} diff --git a/src/app/domains/partner/modules/accounts/services/main.service.ts b/src/app/domains/partner/modules/accounts/services/main.service.ts new file mode 100644 index 0000000..1e4d52c --- /dev/null +++ b/src/app/domains/partner/modules/accounts/services/main.service.ts @@ -0,0 +1,24 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PARTNER_ACCOUNTS_API_ROUTES } from '../constants'; +import { IAccountRawResponse, IAccountRequest, IAccountResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class AccountsService { + constructor(private http: HttpClient) {} + + private apiRoutes = PARTNER_ACCOUNTS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(accountId: string): Observable { + return this.http.get(this.apiRoutes.single(accountId)); + } + + update(accountId: string, userData: IAccountRequest): Observable { + return this.http.patch(this.apiRoutes.single(accountId), userData); + } +} diff --git a/src/app/domains/partner/modules/accounts/store/account.store.ts b/src/app/domains/partner/modules/accounts/store/account.store.ts new file mode 100644 index 0000000..50ed0ab --- /dev/null +++ b/src/app/domains/partner/modules/accounts/store/account.store.ts @@ -0,0 +1,67 @@ +import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state'; +import { computed, inject, Injectable } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { catchError, finalize } from 'rxjs'; +import { partnerAccountsNamedRoutes } from '../constants'; +import { IAccountResponse } from '../models'; +import { AccountsService } from '../services/main.service'; + +interface AccountState extends EntityState { + breadcrumbItems: MenuItem[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class AccountStore extends EntityStore { + private readonly service = inject(AccountsService); + constructor() { + super({ + ...defaultBaseStateData, + breadcrumbItems: [], + }); + } + + breadcrumbItems = computed(() => this._state().breadcrumbItems); + + private setBreadcrumb(accountId: string) { + this.patchState({ + breadcrumbItems: [ + { + ...partnerAccountsNamedRoutes.accounts.meta, + routerLink: partnerAccountsNamedRoutes.accounts.meta.pagePath!(), + }, + { + title: this.entity()?.account.username, + routerLink: partnerAccountsNamedRoutes.account.meta.pagePath!(accountId), + }, + ], + }); + } + + getData(accountId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(accountId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError((error) => { + this.setError(error); + throw error; + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + this.setBreadcrumb(accountId); + }); + } + + override reset(): void { + this.setState({ + ...defaultBaseStateData, + breadcrumbItems: [], + }); + } +} diff --git a/src/app/domains/partner/modules/accounts/views/index.ts b/src/app/domains/partner/modules/accounts/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/partner/modules/accounts/views/list.component.html b/src/app/domains/partner/modules/accounts/views/list.component.html new file mode 100644 index 0000000..e41a2a1 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/views/list.component.html @@ -0,0 +1,19 @@ + + +@if (selectedItemForEdit()) { + +} diff --git a/src/app/domains/partner/modules/accounts/views/list.component.ts b/src/app/domains/partner/modules/accounts/views/list.component.ts new file mode 100644 index 0000000..25d4c39 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/views/list.component.ts @@ -0,0 +1,41 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { PartnerAccountPasswordFormComponent } from '../components/form.component'; +import { partnerAccountsNamedRoutes } from '../constants'; +import { IAccountResponse } from '../models'; +import { AccountsService } from '../services/main.service'; + +@Component({ + selector: 'partner-accounts', + templateUrl: './list.component.html', + imports: [PageDataListComponent, PartnerAccountPasswordFormComponent], +}) +export class PartnerAccountsComponent extends AbstractList { + private readonly service = inject(AccountsService); + private readonly router = inject(Router); + + override setColumns(): void { + this.columns = [ + { field: 'id', header: 'شناسه', type: 'id' }, + { field: 'username', header: 'نام کاربری', type: 'nested', nestedPath: 'account.username' }, + { field: 'role', header: 'نقش' }, + { field: 'status', header: 'وضعیت', type: 'nested', nestedPath: 'account.status' }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } + + toSinglePage(account: IAccountResponse) { + this.router.navigateByUrl(partnerAccountsNamedRoutes.account.meta.pagePath!(account.id)); + } +} diff --git a/src/app/domains/partner/modules/accounts/views/single.component.html b/src/app/domains/partner/modules/accounts/views/single.component.html new file mode 100644 index 0000000..fcac674 --- /dev/null +++ b/src/app/domains/partner/modules/accounts/views/single.component.html @@ -0,0 +1,11 @@ +
+ +
+
+ + + +
+
+
+
diff --git a/src/app/domains/partner/modules/accounts/views/single.component.ts b/src/app/domains/partner/modules/accounts/views/single.component.ts new file mode 100644 index 0000000..0daa12d --- /dev/null +++ b/src/app/domains/partner/modules/accounts/views/single.component.ts @@ -0,0 +1,39 @@ +import { BreadcrumbService } from '@/core/services'; +import { AppCardComponent, KeyValueComponent } from '@/shared/components'; +import { Component, computed, effect, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { AccountStore } from '../store/account.store'; + +@Component({ + selector: 'partner-account', + templateUrl: './single.component.html', + imports: [AppCardComponent, KeyValueComponent], +}) +export class PartnerAccountComponent { + private readonly store = inject(AccountStore); + private readonly route = inject(ActivatedRoute); + private readonly breadcrumbService = inject(BreadcrumbService); + + readonly accountId = signal(this.route.snapshot.paramMap.get('accountId')!); + editMode = signal(false); + + readonly loading = computed(() => this.store.loading()); + readonly account = computed(() => this.store.entity()); + readonly accountBreadcrumb = computed(() => this.store.breadcrumbItems()); + + constructor() { + effect(() => { + if (this.account()?.id) { + this.setBreadcrumb(); + } + }); + } + + getData() { + this.store.getData(this.accountId()); + } + + setBreadcrumb() { + this.breadcrumbService.setItems(this.accountBreadcrumb()); + } +} diff --git a/src/app/domains/partner/modules/customers/components/layout.component.html b/src/app/domains/partner/modules/customers/components/layout.component.html new file mode 100644 index 0000000..5416fe1 --- /dev/null +++ b/src/app/domains/partner/modules/customers/components/layout.component.html @@ -0,0 +1,5 @@ +@if (loading()) { + +} @else { + +} diff --git a/src/app/domains/partner/modules/customers/components/layout.component.ts b/src/app/domains/partner/modules/customers/components/layout.component.ts new file mode 100644 index 0000000..e232128 --- /dev/null +++ b/src/app/domains/partner/modules/customers/components/layout.component.ts @@ -0,0 +1,26 @@ +import { PageLoadingComponent } from '@/shared/components/page-loading.component'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ActivatedRoute, RouterOutlet } from '@angular/router'; +import { PartnerCustomerStore } from '../store/customer.store'; + +@Component({ + selector: 'partner-customer-layout', + templateUrl: './layout.component.html', + imports: [PageLoadingComponent, RouterOutlet], +}) +export class PartnerCustomerLayoutComponent { + private readonly route = inject(ActivatedRoute); + private readonly store = inject(PartnerCustomerStore); + + readonly customerId = signal(this.route.snapshot.paramMap.get('customerId')!); + + readonly loading = computed(() => this.store.loading()); + + getData() { + this.store.getData(this.customerId()); + } + + ngOnInit() { + this.getData(); + } +} diff --git a/src/app/domains/partner/modules/customers/components/list.component.html b/src/app/domains/partner/modules/customers/components/list.component.html new file mode 100644 index 0000000..db56c99 --- /dev/null +++ b/src/app/domains/partner/modules/customers/components/list.component.html @@ -0,0 +1,11 @@ + diff --git a/src/app/domains/partner/modules/customers/components/list.component.ts b/src/app/domains/partner/modules/customers/components/list.component.ts new file mode 100644 index 0000000..855b9e5 --- /dev/null +++ b/src/app/domains/partner/modules/customers/components/list.component.ts @@ -0,0 +1,48 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { partnerCustomersNamedRoutes } from '../constants'; +import { ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'partner-customer-list', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class PartnerCustomerListComponent extends AbstractList { + @Input() fullHeight?: boolean; + @Input() header: IColumn[] = [ + { field: 'id', header: 'شناسه', type: 'id' }, + + { + field: 'first_name', + header: 'عنوان', + }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + + private readonly service = inject(CustomersService); + private readonly router = inject(Router); + + override setColumns(): void { + this.columns = this.header; + } + + override getDataRequest() { + return this.service.getAll(); + } + + toSinglePage(item: ICustomerResponse) { + this.router.navigateByUrl(partnerCustomersNamedRoutes.customer.meta.pagePath!(item.id)); + } +} diff --git a/src/app/domains/partner/modules/customers/constants/apiRoutes/index.ts b/src/app/domains/partner/modules/customers/constants/apiRoutes/index.ts new file mode 100644 index 0000000..94e7146 --- /dev/null +++ b/src/app/domains/partner/modules/customers/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/partner/customers'; + +export const PARTNER_CUSTOMERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/domains/partner/modules/customers/constants/index.ts b/src/app/domains/partner/modules/customers/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/partner/modules/customers/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/partner/modules/customers/constants/routes/index.ts b/src/app/domains/partner/modules/customers/constants/routes/index.ts new file mode 100644 index 0000000..c095e6f --- /dev/null +++ b/src/app/domains/partner/modules/customers/constants/routes/index.ts @@ -0,0 +1,40 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPartnerCustomersRouteNames = 'customers' | 'customer'; + +export const partnerCustomersNamedRoutes: NamedRoutes = { + customers: { + path: 'customers', + loadComponent: () => + import('../../views/list.component').then((m) => m.PartnerCustomersComponent), + meta: { + title: 'مشتریان', + pagePath: () => 'partner/customers', + }, + }, + customer: { + path: 'customers/:customerId', + loadComponent: () => + import('../../views/single.component').then((m) => m.PartnerCustomerComponent), + meta: { + title: 'مشتری', + pagePath: (customerId: string) => `partner/customers/${customerId}`, + }, + }, +}; + +export const PARTNER_CUSTOMERS_ROUTES: Routes = [ + partnerCustomersNamedRoutes.customers, + { + path: partnerCustomersNamedRoutes.customer.path, + loadComponent: () => + import('../../components/layout.component').then((m) => m.PartnerCustomerLayoutComponent), + children: [ + { + ...partnerCustomersNamedRoutes.customer, + path: '', + }, + ], + }, +]; diff --git a/src/app/domains/partner/modules/customers/models/index.ts b/src/app/domains/partner/modules/customers/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/partner/modules/customers/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/domains/partner/modules/customers/models/io.d.ts b/src/app/domains/partner/modules/customers/models/io.d.ts new file mode 100644 index 0000000..6e53dd5 --- /dev/null +++ b/src/app/domains/partner/modules/customers/models/io.d.ts @@ -0,0 +1,8 @@ +export interface ICustomerRawResponse { + id: string; + first_name: string; + last_name: string; +} +export interface ICustomerResponse extends ICustomerRawResponse {} + +export interface ICustomerRequest {} diff --git a/src/app/domains/partner/modules/customers/services/main.service.ts b/src/app/domains/partner/modules/customers/services/main.service.ts new file mode 100644 index 0000000..542b97c --- /dev/null +++ b/src/app/domains/partner/modules/customers/services/main.service.ts @@ -0,0 +1,20 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PARTNER_CUSTOMERS_API_ROUTES } from '../constants'; +import { ICustomerRawResponse, ICustomerResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class CustomersService { + constructor(private http: HttpClient) {} + + private apiRoutes = PARTNER_CUSTOMERS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(customerId: string): Observable { + return this.http.get(this.apiRoutes.single(customerId)); + } +} diff --git a/src/app/domains/partner/modules/customers/store/customer.store.ts b/src/app/domains/partner/modules/customers/store/customer.store.ts new file mode 100644 index 0000000..a903fdb --- /dev/null +++ b/src/app/domains/partner/modules/customers/store/customer.store.ts @@ -0,0 +1,75 @@ +import { EntityState, EntityStore } from '@/core/state'; +import { computed, inject, Injectable } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { catchError, finalize } from 'rxjs'; +import { partnerCustomersNamedRoutes } from '../constants'; +import { ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +interface PartnerCustomerState extends EntityState { + breadcrumbItems: MenuItem[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class PartnerCustomerStore extends EntityStore { + private readonly service = inject(CustomersService); + constructor() { + super({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } + + breadcrumbItems = computed(() => this._state().breadcrumbItems); + + private setBreadcrumb(customerId: string) { + this.patchState({ + breadcrumbItems: [ + { + ...partnerCustomersNamedRoutes.customers.meta, + routerLink: partnerCustomersNamedRoutes.customers.meta.pagePath!(), + }, + { + title: this.entity()?.first_name, + routerLink: partnerCustomersNamedRoutes.customer.meta.pagePath!(customerId), + }, + ], + }); + } + + getData(customerId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(customerId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError((error) => { + this.setError(error); + throw error; + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + this.setBreadcrumb(customerId); + }); + } + + override reset(): void { + this.setState({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } +} diff --git a/src/app/domains/partner/modules/customers/views/index.ts b/src/app/domains/partner/modules/customers/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/partner/modules/customers/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/partner/modules/customers/views/list.component.html b/src/app/domains/partner/modules/customers/views/list.component.html new file mode 100644 index 0000000..04a1035 --- /dev/null +++ b/src/app/domains/partner/modules/customers/views/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/partner/modules/customers/views/list.component.ts b/src/app/domains/partner/modules/customers/views/list.component.ts new file mode 100644 index 0000000..181a34d --- /dev/null +++ b/src/app/domains/partner/modules/customers/views/list.component.ts @@ -0,0 +1,10 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { Component } from '@angular/core'; +import { PartnerCustomerListComponent } from '../components/list.component'; + +@Component({ + selector: 'partner-customers', + templateUrl: './list.component.html', + imports: [PartnerCustomerListComponent], +}) +export class PartnerCustomersComponent {} diff --git a/src/app/domains/partner/modules/customers/views/single.component.html b/src/app/domains/partner/modules/customers/views/single.component.html new file mode 100644 index 0000000..14824d7 --- /dev/null +++ b/src/app/domains/partner/modules/customers/views/single.component.html @@ -0,0 +1,9 @@ +
+ +
+
+ +
+
+
+
diff --git a/src/app/domains/partner/modules/customers/views/single.component.ts b/src/app/domains/partner/modules/customers/views/single.component.ts new file mode 100644 index 0000000..71eb7b6 --- /dev/null +++ b/src/app/domains/partner/modules/customers/views/single.component.ts @@ -0,0 +1,39 @@ +import { BreadcrumbService } from '@/core/services'; +import { AppCardComponent, KeyValueComponent } from '@/shared/components'; +import { Component, computed, effect, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { PartnerCustomerStore } from '../store/customer.store'; + +@Component({ + selector: 'partner-customer', + templateUrl: './single.component.html', + imports: [AppCardComponent, KeyValueComponent], +}) +export class PartnerCustomerComponent { + private readonly store = inject(PartnerCustomerStore); + private readonly route = inject(ActivatedRoute); + private readonly breadcrumbService = inject(BreadcrumbService); + + readonly customerId = signal(this.route.snapshot.paramMap.get('customerId')!); + editMode = signal(false); + + readonly loading = computed(() => this.store.loading()); + readonly customer = computed(() => this.store.entity()); + readonly customerBreadcrumb = computed(() => this.store.breadcrumbItems()); + + constructor() { + effect(() => { + if (this.customer()?.id) { + this.setBreadcrumb(); + } + }); + } + + getData() { + this.store.getData(this.customerId()); + } + + setBreadcrumb() { + this.breadcrumbService.setItems(this.customerBreadcrumb()); + } +} diff --git a/src/app/domains/partner/modules/dashboard/views/index.component.html b/src/app/domains/partner/modules/dashboard/views/index.component.html index bcac5c5..541972f 100644 --- a/src/app/domains/partner/modules/dashboard/views/index.component.html +++ b/src/app/domains/partner/modules/dashboard/views/index.component.html @@ -1,3 +1 @@ -
- به پنل کسب و کار خوش آمدید -
+
به پنل مدیریتی شریک تجاری خوش آمدید.
diff --git a/src/app/domains/partner/modules/dashboard/views/index.component.ts b/src/app/domains/partner/modules/dashboard/views/index.component.ts index 4ad28f7..8428f5a 100644 --- a/src/app/domains/partner/modules/dashboard/views/index.component.ts +++ b/src/app/domains/partner/modules/dashboard/views/index.component.ts @@ -4,6 +4,4 @@ import { Component } from '@angular/core'; selector: 'partner-dashboard', templateUrl: './index.component.html', }) -export class DashboardComponent { - constructor() {} -} +export class DashboardComponent {} diff --git a/src/app/domains/partner/routes.ts b/src/app/domains/partner/routes.ts index 94a6969..72120ea 100644 --- a/src/app/domains/partner/routes.ts +++ b/src/app/domains/partner/routes.ts @@ -1,13 +1,17 @@ import { Route } from '@angular/router'; +import { PARTNER_ACCOUNTS_ROUTES } from './modules/accounts/constants'; +import { PARTNER_CUSTOMERS_ROUTES } from './modules/customers/constants'; export const PARTNER_ROUTES = { path: 'partner', - component: undefined, + loadComponent: () => import('./layouts/layout.component').then((m) => m.LayoutComponent), children: [ { path: '', loadComponent: () => import('./modules/dashboard/views/index.component').then((m) => m.DashboardComponent), }, + ...PARTNER_ACCOUNTS_ROUTES, + ...PARTNER_CUSTOMERS_ROUTES, ], } as Route; diff --git a/src/app/domains/partner/services/main.service.ts b/src/app/domains/partner/services/main.service.ts new file mode 100644 index 0000000..5e91166 --- /dev/null +++ b/src/app/domains/partner/services/main.service.ts @@ -0,0 +1,16 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PARTNER_API_ROUTES } from '../constants'; +import { IPartnerInfoRawResponse, IPartnerInfoResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class PartnerService { + constructor(private http: HttpClient) {} + + private apiRoutes = PARTNER_API_ROUTES; + + getInfo(): Observable { + return this.http.get(this.apiRoutes.info()); + } +} diff --git a/src/app/domains/partner/store/main.store.ts b/src/app/domains/partner/store/main.store.ts new file mode 100644 index 0000000..0a41df2 --- /dev/null +++ b/src/app/domains/partner/store/main.store.ts @@ -0,0 +1,57 @@ +import { EntityState, EntityStore } from '@/core/state'; +import { LayoutService } from '@/layout/service/layout.service'; +import { inject, Injectable } from '@angular/core'; +import { catchError, finalize } from 'rxjs'; +import { IPartnerInfoResponse } from '../models'; +import { PartnerService } from '../services/main.service'; + +interface PartnerState extends EntityState {} + +@Injectable({ + providedIn: 'root', +}) +export class PartnerStore extends EntityStore { + private readonly service = inject(PartnerService); + private readonly layoutService = inject(LayoutService); + + constructor() { + super({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + }); + } + + getData() { + this.patchState({ loading: true }); + this.service + .getInfo() + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError((error) => { + this.setError(error); + throw error; + }), + ) + .subscribe((entity) => { + this.layoutService.setPanelInfo({ + title: `پنل مدیریتی ${entity.name}`, + }); + this.setEntity(entity); + }); + } + + override reset(): void { + this.setState({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + }); + } +}