feat: add licenses management module with filtering and listing functionality

- Introduced new constants and routes for licenses management.
- Created components for licenses filter form and list display.
- Implemented services for fetching licenses data from the API.
- Added dialog components for creating and editing licenses.
- Updated partner menu to include a link to licenses.
- Refactored existing components to improve code structure and maintainability.
This commit is contained in:
2026-04-25 23:59:23 +03:30
parent 095ae31249
commit baa9409e9e
36 changed files with 454 additions and 127 deletions
@@ -17,6 +17,11 @@ export const PARTNER_MENU_ITEMS = [
icon: 'pi pi-fw pi-home',
routerLink: ['/partner/consumers'],
},
{
label: 'لایسنس‌ها',
icon: 'pi pi-fw pi-home',
routerLink: ['/partner/licenses'],
},
],
},
] as MenuItem[];
@@ -0,0 +1,17 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<partner-consumer-businessActivities-form-content
[consumerId]="consumerId"
[businessActivityId]="businessActivityId"
[initialValues]="initialValues"
[editMode]="editMode"
(onSubmit)="onFormSubmit($event)"
(onClose)="close()"
/>
</p-dialog>
@@ -0,0 +1,28 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Dialog } from 'primeng/dialog';
import { IBusinessActivityResponse } from '../../models';
import { ConsumerBusinessActivitiesFormComponent } from './form.component';
@Component({
selector: 'partner-consumer-businessActivities-form',
templateUrl: './form-dialog.component.html',
imports: [Dialog, ConsumerBusinessActivitiesFormComponent],
})
export class ConsumerBusinessActivitiesFormDialogComponent extends AbstractDialog {
@Output() onSubmit = new EventEmitter<IBusinessActivityResponse>();
@Input() initialValues?: IBusinessActivityResponse;
@Input() editMode?: boolean;
@Input({ required: true }) consumerId!: string;
@Input() businessActivityId!: string;
get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'ایجاد'} فعالیت اقتصادی`;
}
onFormSubmit(response: IBusinessActivityResponse) {
this.onSubmit.emit(response);
this.close();
}
}
@@ -1,21 +1,13 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<app-input label="کد اقتصادی" [control]="form.controls.economic_code" name="economic_code" />
<catalog-guild-select label="صنف" [control]="form.controls.guild_id" name="guild_id" />
<app-input label="عنوان" [control]="$any(form).controls.name" name="name" />
<app-input label="کد اقتصادی" [control]="$any(form).controls.economic_code" name="economic_code" />
<catalog-guild-select label="صنف" [control]="$any(form).controls.guild_id" name="guild_id" />
@if (!editMode) {
<p-divider align="center"> اطلاعات لایسنس </p-divider>
<uikit-datepicker
label="تاریخ شروع لایسنس"
[control]="form.controls.license_starts_at"
[control]="$any(form).controls.license_starts_at"
name="license_starts_at"
hint="مدت زمان لایسنس‌ها ۱ ساله هستند."
/>
@@ -23,4 +15,3 @@
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</p-dialog>
@@ -1,23 +1,19 @@
// import { CatalogRolesComponent } from '@/shared/catalog/roles';
import { MustMatch } from '@/core/validators';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { AbstractForm } from '@/shared/abstractClasses';
import { CatalogGuildSelectComponent } from '@/shared/catalog/guild/components/select.component';
import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Dialog } from 'primeng/dialog';
import { Divider } from 'primeng/divider';
import { IBusinessActivityRequest, IBusinessActivityResponse } from '../../models';
import { PartnerConsumerBusinessActivitiesService } from '../../services/businessActivities.service';
@Component({
selector: 'partner-consumer-businessActivities-form',
selector: 'partner-consumer-businessActivities-form-content',
templateUrl: './form.component.html',
imports: [
ReactiveFormsModule,
Dialog,
InputComponent,
FormFooterActionsComponent,
CatalogGuildSelectComponent,
@@ -25,7 +21,7 @@ import { PartnerConsumerBusinessActivitiesService } from '../../services/busines
UikitFlatpickrJalaliComponent,
],
})
export class ConsumerBusinessActivitiesFormComponent extends AbstractFormDialog<
export class ConsumerBusinessActivitiesFormComponent extends AbstractForm<
IBusinessActivityRequest,
IBusinessActivityResponse
> {
@@ -53,7 +49,6 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractFormDialog<
validators: [Validators.required],
}),
);
form.addValidators([MustMatch('password', 'confirmPassword')]);
}
return form;
@@ -61,20 +56,15 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractFormDialog<
form = this.initForm();
get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'ایجاد'} فعالیت اقتصادی`;
}
override ngOnChanges(): void {
this.form = this.initForm();
}
submitForm() {
const formValue = this.form.value as IBusinessActivityRequest & { confirmPassword?: string };
const { confirmPassword, ...rest } = formValue;
const formValue = this.form.value as IBusinessActivityRequest;
if (this.editMode) {
return this.service.update(this.consumerId, this.businessActivityId, rest);
return this.service.update(this.consumerId, this.businessActivityId, formValue);
}
return this.service.create(this.consumerId, rest);
return this.service.create(this.consumerId, formValue);
}
}
@@ -9,12 +9,12 @@ import { Router } from '@angular/router';
import { partnerConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { IBusinessActivityResponse } from '../../models';
import { PartnerConsumerBusinessActivitiesService } from '../../services/businessActivities.service';
import { ConsumerBusinessActivitiesFormComponent } from './form.component';
import { ConsumerBusinessActivitiesFormDialogComponent } from './form-dialog.component';
@Component({
selector: 'partner-consumer-businessActivities-list',
templateUrl: './list.component.html',
imports: [PageDataListComponent, ConsumerBusinessActivitiesFormComponent],
imports: [PageDataListComponent, ConsumerBusinessActivitiesFormDialogComponent],
})
export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessActivityResponse> {
@Input({ required: true }) consumerId!: string;
@@ -23,6 +23,7 @@ export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessA
{ field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'guild', header: 'صنف', type: 'nested', nestedOption: { path: 'guild.name' } },
{ field: 'economic_code', header: 'کد اقتصادی' },
{
field: 'license_info',
header: 'محدودیت کاربر',
@@ -0,0 +1,18 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<partner-consumer-complex-form-content
[consumerId]="consumerId"
[businessActivityId]="businessActivityId"
[complexId]="complexId"
[initialValues]="initialValues"
[editMode]="editMode"
(onSubmit)="onFormSubmit($event)"
(onClose)="close()"
/>
</p-dialog>
@@ -0,0 +1,29 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Dialog } from 'primeng/dialog';
import { IComplexResponse } from '../../models';
import { ConsumerComplexFormContentComponent } from './form.component';
@Component({
selector: 'partner-consumer-complex-form',
templateUrl: './form-dialog.component.html',
imports: [Dialog, ConsumerComplexFormContentComponent],
})
export class ConsumerComplexFormDialogComponent extends AbstractDialog {
@Output() onSubmit = new EventEmitter<IComplexResponse>();
@Input() initialValues?: IComplexResponse;
@Input() editMode?: boolean;
@Input({ required: true }) consumerId!: string;
@Input() businessActivityId!: string;
@Input() complexId!: string;
get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'ایجاد'} شعبه`;
}
onFormSubmit(response: IComplexResponse) {
this.onSubmit.emit(response);
this.close();
}
}
@@ -1,15 +1,6 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<app-input label="کد شعبه" [control]="form.controls.branch_code" name="branch_code" />
<app-input label="آدرس" [control]="form.controls.address" name="address" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</p-dialog>
@@ -1,19 +1,18 @@
// import { CatalogRolesComponent } from '@/shared/catalog/roles';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { AbstractForm } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Dialog } from 'primeng/dialog';
import { IComplexRequest, IComplexResponse } from '../../models';
import { PartnerComplexesService } from '../../services/complexes.service';
@Component({
selector: 'partner-consumer-complex-form',
selector: 'partner-consumer-complex-form-content',
templateUrl: './form.component.html',
imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent],
imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent],
})
export class ConsumerComplexFormComponent extends AbstractFormDialog<
export class ConsumerComplexFormContentComponent extends AbstractForm<
IComplexRequest,
IComplexResponse
> {
@@ -9,12 +9,12 @@ import { Router } from '@angular/router';
import { partnerConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { IComplexResponse } from '../../models';
import { PartnerComplexesService } from '../../services/complexes.service';
import { ConsumerComplexFormComponent } from './form.component';
import { ConsumerComplexFormDialogComponent } from './form-dialog.component';
@Component({
selector: 'partner-consumer-complexes-list',
templateUrl: './list.component.html',
imports: [PageDataListComponent, ConsumerComplexFormComponent],
imports: [PageDataListComponent, ConsumerComplexFormDialogComponent],
})
export class ConsumerComplexesComponent extends AbstractList<IComplexResponse> {
@Input() consumerId!: string;
@@ -0,0 +1,19 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<partner-consumer-pos-form-content
[consumerId]="consumerId"
[businessActivityId]="businessActivityId"
[complexId]="complexId"
[posId]="posId"
[initialValues]="initialValues"
[editMode]="editMode"
(onSubmit)="onFormSubmit($event)"
(onClose)="close()"
/>
</p-dialog>
@@ -0,0 +1,30 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Dialog } from 'primeng/dialog';
import { IPosResponse } from '../../models';
import { ConsumerPosFormContentComponent } from './form.component';
@Component({
selector: 'partner-consumer-pos-form',
templateUrl: './form-dialog.component.html',
imports: [Dialog, ConsumerPosFormContentComponent],
})
export class ConsumerPosFormDialogComponent extends AbstractDialog {
@Output() onSubmit = new EventEmitter<IPosResponse>();
@Input() initialValues?: IPosResponse;
@Input() editMode?: boolean;
@Input({ required: true }) consumerId!: string;
@Input({ required: true }) businessActivityId!: string;
@Input({ required: true }) complexId!: string;
@Input() posId!: string;
get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'ایجاد'} پایانه فروش`;
}
onFormSubmit(response: IPosResponse) {
this.onSubmit.emit(response);
this.close();
}
}
@@ -1,11 +1,3 @@
<p-dialog
[header]="preparedTitle"
[(visible)]="visible"
[modal]="true"
[style]="{ width: '500px' }"
[closable]="true"
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<app-enum-select [control]="form.controls.pos_type" type="posType" name="pos_type" />
@@ -48,4 +40,3 @@
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</p-dialog>
@@ -1,6 +1,6 @@
// import { CatalogRolesComponent } from '@/shared/catalog/roles';
import { MustMatch } from '@/core/validators';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { AbstractForm } from '@/shared/abstractClasses';
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
import { CatalogProviderSelectComponent } from '@/shared/catalog';
import { CatalogDeviceSelectComponent } from '@/shared/catalog/devices';
@@ -9,18 +9,16 @@ import { FormFooterActionsComponent } from '@/shared/components/formFooterAction
import { UikitFieldComponent } from '@/uikit';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Dialog } from 'primeng/dialog';
import { Divider } from 'primeng/divider';
import { Password } from 'primeng/password';
import { IPosRequest, IPosResponse } from '../../models';
import { PartnerPosesService } from '../../services/poses.service';
@Component({
selector: 'partner-consumer-pos-form',
selector: 'partner-consumer-pos-form-content',
templateUrl: './form.component.html',
imports: [
ReactiveFormsModule,
Dialog,
InputComponent,
FormFooterActionsComponent,
CatalogDeviceSelectComponent,
@@ -31,7 +29,7 @@ import { PartnerPosesService } from '../../services/poses.service';
Divider,
],
})
export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IPosResponse> {
export class ConsumerPosFormContentComponent extends AbstractForm<IPosRequest, IPosResponse> {
private readonly service = inject(PartnerPosesService);
@Input({ required: true }) consumerId!: string;
@@ -9,12 +9,12 @@ import { Router } from '@angular/router';
import { partnerConsumerPosesNamedRoutes } from '../../constants/routes/poses';
import { IPosResponse } from '../../models';
import { PartnerPosesService } from '../../services/poses.service';
import { ConsumerPosFormComponent } from './form.component';
import { ConsumerPosFormDialogComponent } from './form-dialog.component';
@Component({
selector: 'partner-consumer-poses-list',
templateUrl: './list.component.html',
imports: [PageDataListComponent, ConsumerPosFormComponent],
imports: [PageDataListComponent, ConsumerPosFormDialogComponent],
})
export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
@Input({ required: true }) consumerId!: string;
@@ -5,7 +5,7 @@ import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ButtonDirective } from 'primeng/button';
import { ConsumerBusinessActivitiesFormComponent } from '../../components/businessActivities/form.component';
import { ConsumerBusinessActivitiesFormDialogComponent } from '../../components/businessActivities/form-dialog.component';
import { PartnerChargeAccountFormDialogComponent } from '../../components/chargeAccount/charge-account-form-dialog.component';
import { ConsumerComplexesComponent } from '../../components/complexes/list.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
@@ -17,7 +17,7 @@ import { ConsumerStore } from '../../store/consumer.store';
imports: [
AppCardComponent,
KeyValueComponent,
ConsumerBusinessActivitiesFormComponent,
ConsumerBusinessActivitiesFormDialogComponent,
ConsumerComplexesComponent,
ButtonDirective,
PartnerChargeAccountFormDialogComponent,
@@ -3,7 +3,7 @@ import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerComplexFormComponent } from '../../components/complexes/form.component';
import { ConsumerComplexFormDialogComponent } from '../../components/complexes/form-dialog.component';
import { ConsumerPosesComponent } from '../../components/poses/list.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store';
@@ -15,7 +15,7 @@ import { ConsumerStore } from '../../store/consumer.store';
imports: [
AppCardComponent,
KeyValueComponent,
ConsumerComplexFormComponent,
ConsumerComplexFormDialogComponent,
ConsumerPosesComponent,
],
})
@@ -3,7 +3,7 @@ import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerPosFormComponent } from '../../components/poses/form.component';
import { ConsumerPosFormDialogComponent } from '../../components/poses/form-dialog.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store';
@@ -12,7 +12,7 @@ import { PosStore } from '../../store/pos.store';
@Component({
selector: 'partner-user-pos',
templateUrl: './single.component.html',
imports: [AppCardComponent, KeyValueComponent, ConsumerPosFormComponent],
imports: [AppCardComponent, KeyValueComponent, ConsumerPosFormDialogComponent],
})
export class PartnerUserPosComponent {
private readonly consumerStore = inject(ConsumerStore);
@@ -0,0 +1,10 @@
<div class="flex flex-col gap-4 w-full">
<uikit-field label="بر اساس نام مشتری" name="search" class="w-full">
<p-inputgroup class="w-full">
<input pInputText [(ngModel)]="search" placeholder="جستجوی نام مشتری" (ngModelChange)="onFilterChange($event)" />
<p-inputgroup-addon>
<p-button icon="pi pi-times" severity="secondary" (onClick)="clearSearch()"></p-button>
</p-inputgroup-addon>
</p-inputgroup>
</uikit-field>
</div>
@@ -0,0 +1,49 @@
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
import { Component, EventEmitter, Output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Button } from 'primeng/button';
import { InputGroupModule } from 'primeng/inputgroup';
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
import { InputText, InputTextModule } from 'primeng/inputtext';
import { MessageModule } from 'primeng/message';
@Component({
selector: 'partner-licenses-filter',
templateUrl: './filter-form.component.html',
imports: [
InputText,
FormsModule,
InputTextModule,
InputGroupModule,
InputGroupAddonModule,
Button,
UikitFieldComponent,
MessageModule,
],
})
export class AdminAgentsFilterComponent {
@Output() onSearch = new EventEmitter<string>();
search = signal<string>('');
private debounceTimer: any;
onFilterChange = (search: string) => {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.onSearch.emit(search);
}, 300);
};
clearSearch = () => {
clearTimeout(this.debounceTimer);
this.search.update(() => '');
this.onSearch.emit('');
};
resetFilters = () => {
this.search.set('');
};
}
@@ -0,0 +1,16 @@
<app-page-data-list
[pageTitle]="'مدیریت لایسنس‌ها'"
[columns]="columns"
emptyPlaceholderTitle="تا به حال لایسنسی نفروخته‌اید."
[currentPage]="responseMetaData()?.page"
[totalRecords]="responseMetaData()?.totalRecords || items().length"
[perPage]="1"
[items]="items()"
[loading]="loading()"
(onRefresh)="refresh()"
(onChangePage)="changePage($event)"
>
<ng-template #filter>
<partner-licenses-filter (onSearch)="refresh()" />
</ng-template>
</app-page-data-list>
@@ -0,0 +1,44 @@
import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject } from '@angular/core';
import { ILicenseRawResponse, ILicenseResponse } from '../models';
import { PartnerLicensesService } from '../services/main.service';
import { AdminAgentsFilterComponent } from './filter-form.component';
@Component({
selector: 'partner-license-list',
templateUrl: './list.component.html',
imports: [PageDataListComponent, AdminAgentsFilterComponent],
})
export class PartnerLicenseListComponent extends AbstractList<ILicenseResponse> {
private readonly service = inject(PartnerLicensesService);
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
{
field: 'consumer',
header: 'مشتری',
customDataModel(item: ILicenseRawResponse) {
return `${item.consumer.first_name} ${item.consumer.last_name}`;
},
},
{
field: 'accounts_limit',
header: 'تعداد کاربر قابل تعریف',
customDataModel(item: ILicenseRawResponse) {
return item.license.accounts_limit || '-';
},
},
{ field: 'expires_at', header: 'تاریخ انقضا', type: 'date' },
{ field: 'created_at', header: 'تاریخ ایجاد', type: 'date' },
];
}
override getDataRequest() {
return this.service.getAll({
page: this.responseMetaData()?.page || 1,
pageSize: this.responseMetaData()?.perPage || 1,
});
}
}
@@ -0,0 +1,6 @@
const baseUrl = '/api/v1/partner/licenses';
export const PARTNER_LICENSES_API_ROUTES = {
list: () => `${baseUrl}`,
single: (id: string) => `${baseUrl}/${id}`,
};
@@ -0,0 +1,2 @@
export * from './apiRoutes';
export * from './routes';
@@ -0,0 +1,18 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
export type TPartnerLicensesRouteNames = 'licenses';
export const partnerLicensesNamedRoutes: NamedRoutes<TPartnerLicensesRouteNames> = {
licenses: {
path: 'licenses',
loadComponent: () =>
import('../../views/list.component').then((m) => m.PartnerLicensesComponent),
meta: {
title: 'لایسنس‌ها',
pagePath: () => '/partner/licenses',
},
},
};
export const PARTNER_LICENSES_ROUTES: Routes = Object.values(partnerLicensesNamedRoutes);
@@ -0,0 +1 @@
export * from './io';
+21
View File
@@ -0,0 +1,21 @@
export interface ILicenseRawResponse {
id: string;
starts_at: string;
expires_at: string;
created_at: string;
consumer: Consumer;
license: License;
}
export interface ILicenseResponse extends ILicenseRawResponse {}
interface License {
id: string;
accounts_limit?: number;
}
interface Consumer {
first_name: string;
last_name: string;
mobile_number: string;
status: string;
}
@@ -0,0 +1,26 @@
import { IPaginatedQuery, IPaginatedResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { PARTNER_LICENSES_API_ROUTES } from '../constants';
import { ILicenseRawResponse, ILicenseResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class PartnerLicensesService {
constructor(private http: HttpClient) {}
private apiRoutes = PARTNER_LICENSES_API_ROUTES;
getAll(paginationQuery?: IPaginatedQuery): Observable<IPaginatedResponse<ILicenseResponse>> {
return this.http.get<IPaginatedResponse<ILicenseRawResponse>>(this.apiRoutes.list(), {
params: {
page: paginationQuery?.page || '1',
pageSize: paginationQuery?.pageSize || '50',
},
});
}
getSingle(licenseId: string): Observable<ILicenseResponse> {
return this.http.get<ILicenseRawResponse>(this.apiRoutes.single(licenseId));
}
}
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<partner-license-list />
@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
import { PartnerLicenseListComponent } from '../components/list.component';
@Component({
selector: 'partner-licenses',
templateUrl: './list.component.html',
imports: [PartnerLicenseListComponent],
})
export class PartnerLicensesComponent {}
+2
View File
@@ -1,6 +1,7 @@
import { Route } from '@angular/router';
import { PARTNER_ACCOUNTS_ROUTES } from './modules/accounts/constants';
import { PARTNER_CONSUMERS_ROUTES } from './modules/consumers/constants';
import { PARTNER_LICENSES_ROUTES } from './modules/licenses/constants';
export const PARTNER_ROUTES = {
path: 'partner',
@@ -13,5 +14,6 @@ export const PARTNER_ROUTES = {
},
...PARTNER_ACCOUNTS_ROUTES,
...PARTNER_CONSUMERS_ROUTES,
...PARTNER_LICENSES_ROUTES,
],
} as Route;
@@ -1,5 +1,9 @@
import { Maybe } from '@/core';
import { IListingResponse, IPaginatedResponse } from '@/core/models/service.model';
import {
IListingResponse,
IPaginatedResponse,
IResponseMetadata,
} from '@/core/models/service.model';
import { Component, signal } from '@angular/core';
import { catchError, finalize, Observable } from 'rxjs';
import { IColumn } from '../components/pageDataList/page-data-list.component';
@@ -16,6 +20,8 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
requestPayload = signal<Maybe<RequestParams>>(null);
selectedItemForEdit = signal<Maybe<ResponseModel>>(null);
editMode = signal(false);
withPagination = signal(false);
responseMetaData = signal<Maybe<IResponseMetadata>>(null);
ngOnInit() {
this.setColumns();
@@ -39,6 +45,9 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
}),
)
.subscribe((res) => {
if ('meta' in res) {
this.responseMetaData.set(res.meta);
}
this.items.set(res.data ?? []);
});
}
@@ -71,4 +80,8 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
onCloseEdit() {
this.visibleForm.set(false);
}
changePage(page: number) {
// this.
}
}
@@ -271,6 +271,7 @@
class="contnet"
styleClass="!w-[80vw] md:!w-96 md:!max-w-96 !max-w-sm"
>
<hr class="mt-0!" />
<div class="pt-2">
<ng-container [ngTemplateOutlet]="filter" [ngTemplateOutletContext]="{ $implicit: columns }"> </ng-container>
</div>
@@ -107,7 +107,7 @@ export class PageDataListComponent<I> {
@Output() onDelete = new EventEmitter<I>();
@Output() onDetails = new EventEmitter<I>();
@Output() onAdd = new EventEmitter<void>();
@Output() pageChange = new EventEmitter<any>();
@Output() onChangePage = new EventEmitter<any>();
@Output() onThumbnailClick = new EventEmitter<I>();
@Output() onRefresh = new EventEmitter();
@@ -150,7 +150,7 @@ export class PageDataListComponent<I> {
};
onPage = ($event: any) => {
this.pageChange.emit($event);
this.onChangePage.emit($event);
};
openThumbnailModal = (item: I) => {