feat: add Master Admin application functionality
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import { buildApiUrl } from '../../../../core/config/api-url.util';
|
||||
|
||||
|
||||
export const STATE_ENDPOINTS = {
|
||||
dataTable: buildApiUrl(
|
||||
'masterAdmin',
|
||||
'/v1/states/datatable'
|
||||
),
|
||||
|
||||
create: buildApiUrl(
|
||||
'masterAdmin',
|
||||
'/v1/states'
|
||||
),
|
||||
|
||||
getById: (id: string) =>
|
||||
buildApiUrl(
|
||||
'masterAdmin',
|
||||
`/v1/states/${encodeURIComponent(id)}`
|
||||
),
|
||||
|
||||
autocomplete: buildApiUrl(
|
||||
'masterAdmin',
|
||||
'/v1/states/autocomplete'
|
||||
),
|
||||
|
||||
update: (id: string) =>
|
||||
buildApiUrl(
|
||||
'masterAdmin',
|
||||
`/v1/states/${encodeURIComponent(id)}`
|
||||
),
|
||||
|
||||
delete: (id: string) =>
|
||||
buildApiUrl(
|
||||
'masterAdmin',
|
||||
`/v1/states/${encodeURIComponent(id)}`
|
||||
),
|
||||
|
||||
changeStatus: (id: string) =>
|
||||
buildApiUrl(
|
||||
'masterAdmin',
|
||||
`/v1/states/${encodeURIComponent(id)}/status`
|
||||
),
|
||||
} as const;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { HttpClient, HttpParams } from "@angular/common/http";
|
||||
import { Injectable, inject } from "@angular/core";
|
||||
import { STATE_ENDPOINTS } from "./state.endpoints"
|
||||
import { Observable } from "rxjs";
|
||||
import { DataTableQuery, DataTableResult } from "../../../../shared/components/data-table/data-table.types";
|
||||
import {
|
||||
CreateStateRequest,
|
||||
StateDto,
|
||||
StateLookupDto,
|
||||
UpdateStateRequest
|
||||
} from "../models/state.model";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StateService {
|
||||
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
getStateDataTable(query: DataTableQuery, countryId: string | null = null): Observable<DataTableResult<StateDto>> {
|
||||
const params = countryId ? new HttpParams().set('countryId', countryId) : undefined;
|
||||
return this.http.post<DataTableResult<StateDto>>(`${STATE_ENDPOINTS.dataTable}`, query, { params });
|
||||
}
|
||||
|
||||
createState(request: CreateStateRequest): Observable<StateDto> {
|
||||
return this.http.post<StateDto>(STATE_ENDPOINTS.create, request);
|
||||
}
|
||||
|
||||
updateState(id: string, request: UpdateStateRequest): Observable<StateDto> {
|
||||
return this.http.put<StateDto>(STATE_ENDPOINTS.update(id), request);
|
||||
}
|
||||
|
||||
getStateById(id: string): Observable<StateDto> {
|
||||
return this.http.get<StateDto>(STATE_ENDPOINTS.getById(id));
|
||||
}
|
||||
|
||||
autocomplete(countryId: string, term = '', limit = 50): Observable<StateLookupDto[]> {
|
||||
return this.http.get<StateLookupDto[]>(STATE_ENDPOINTS.autocomplete, {
|
||||
params: new HttpParams()
|
||||
.set('countryId', countryId)
|
||||
.set('term', term)
|
||||
.set('limit', limit)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface StateDto {
|
||||
id: string;
|
||||
countryId: string;
|
||||
name: string;
|
||||
code: string | null;
|
||||
isActive: boolean;
|
||||
createdOn?: string;
|
||||
modifiedOn?: string | null;
|
||||
}
|
||||
|
||||
export interface StateLookupDto {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface CreateStateRequest {
|
||||
countryId: string;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface UpdateStateRequest {
|
||||
countryId: null;
|
||||
name: string;
|
||||
code: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export type StateModalMode = 'create' | 'edit';
|
||||
@@ -1,19 +1,104 @@
|
||||
<app-data-table
|
||||
[columns]="columns()"
|
||||
[rows]="states()"
|
||||
[actions]="actions()"
|
||||
[loading]="loading()"
|
||||
[totalRecords]="totalRecords()"
|
||||
[pageIndex]="queryState.pageIndex()"
|
||||
[pageSize]="queryState.pageSize()"
|
||||
[pageSizeOptions]="[5, 10, 20, 50]"
|
||||
title="States"
|
||||
[showSearch]="true"
|
||||
searchPlaceholder="Search states..."
|
||||
[searchDebounceTime]="300"
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<div class="xl:col-span-12 col-span-12">
|
||||
<app-filter-card title="Filter" titleIcon="ti ti-filter" headerClass="!py-2" bodyClass="!px-4 !py-2.5">
|
||||
<form [formGroup]="countryFilterForm" autocomplete="off" class="grid w-full grid-cols-12 items-end gap-3">
|
||||
<div class="col-span-12 sm:col-span-5 lg:col-span-2">
|
||||
<app-autocomplete
|
||||
formControlName="countryId"
|
||||
inputId="state-country-filter"
|
||||
variant="floating"
|
||||
size="sm"
|
||||
label="Country"
|
||||
placeholder="Search"
|
||||
[searchFn]="searchCountries"
|
||||
[displayWith]="displayCountry"
|
||||
[valueWith]="countryValue"
|
||||
[selectedItem]="selectedCountryLookup()"
|
||||
[minSearchLength]="1"
|
||||
[debounceTime]="300"
|
||||
[limit]="50"
|
||||
[clearable]="true"
|
||||
[hideValidation]="true"
|
||||
wrapperClass="!mb-0 w-full"
|
||||
(itemSelected)="onCountryLookupSelected($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-12 sm:col-span-3 lg:col-span-1">
|
||||
<button type="button" class="ti-btn ti-btn-sm ti-btn-primary-full !mb-0 flex min-h-8 w-full items-center justify-center gap-1.5 !px-3 md:!w-auto" (click)="applyCountryFilter()">
|
||||
<i class="ti ti-filter" aria-hidden="true"></i>
|
||||
<span>Filter</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</app-filter-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
(searchChanged)="onSearch($event)"
|
||||
(pageChanged)="onPageChange($event)"
|
||||
(sortChanged)="onSortChange($event)"
|
||||
(actionClicked)="onActionClick($event)">
|
||||
</app-data-table>
|
||||
<app-data-table [columns]="columns()" [rows]="states()" [actions]="actions()"
|
||||
[totalRecords]="totalRecords()" [pageIndex]="queryState.pageIndex()" [pageSize]="queryState.pageSize()"
|
||||
tableTitle="States" buttonTitle="Add" [showSearch]="true"
|
||||
[showAddButton]="true" searchPlaceholder="Search..." [searchDebounceTime]="300"
|
||||
[emptyMessage]="emptyMessage()" [emptyDescription]="emptyDescription()"
|
||||
(addClicked)="onAddState()" (searchChanged)="onSearch($event)" (pageChanged)="onPageChange($event)"
|
||||
(sortChanged)="onSortChange($event)" (actionClicked)="onActionClick($event)" toolTip="Add State" />
|
||||
|
||||
<app-confirm-dialog
|
||||
title="Delete State"
|
||||
text="Do you really want to delete this state?"
|
||||
confirmButtonText="Delete"
|
||||
cancelButtonText="Cancel"
|
||||
(confirmed)="onDeleteConfirmed()"
|
||||
(cancelled)="onDeleteCancelled()"
|
||||
/>
|
||||
|
||||
<modal [open]="showStateModal()" [title]="stateModalTitle()" size="md" [submitAction]="stateSubmitAction()"
|
||||
[submitLabel]="stateSubmitLabel()" [loadingLabel]="stateLoadingLabel()" [loading]="saving()"
|
||||
(closed)="closeStateModal()" (submitted)="saveState()">
|
||||
<form [formGroup]="stateForm" (ngSubmit)="saveState()" autocomplete="off">
|
||||
<div class="grid grid-cols-12 gap-x-5 gap-y-5">
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<app-autocomplete
|
||||
formControlName="countryId"
|
||||
inputId="state-country"
|
||||
variant="floating"
|
||||
label="Country"
|
||||
placeholder="Search"
|
||||
[searchFn]="searchCountries"
|
||||
[displayWith]="displayCountry"
|
||||
[valueWith]="countryValue"
|
||||
[resolveValueFn]="resolveCountry"
|
||||
[selectedItem]="selectedFormCountry()"
|
||||
[minSearchLength]="1"
|
||||
[debounceTime]="300"
|
||||
[limit]="50"
|
||||
[clearable]="stateModalMode() === 'create'"
|
||||
[required]="true"
|
||||
[clearable]="true"
|
||||
[readonly]="stateModalMode() !== 'create'"
|
||||
[validationMessages]="{ required: 'Country is required.' }"
|
||||
[submitAttempted]="stateSubmitAttempted()"
|
||||
wrapperClass="w-full"
|
||||
(itemSelected)="onFormCountrySelected($event)"
|
||||
(cleared)="onFormCountryCleared()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<app-form-input formControlName="name" inputId="state-name" label="State Name" placeholder="Name" variant="floating"
|
||||
autocomplete="off" [required]="true" [maxLength]="150" [validationMessages]="{
|
||||
required: 'State Name is required.',
|
||||
maxlength: 'State Name cannot exceed 150 characters.'
|
||||
}" [submitAttempted]="stateSubmitAttempted()" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<app-form-input formControlName="code" inputId="state-code" label="State Code" placeholder="e.g.: CA" variant="floating"
|
||||
autocomplete="off" [required]="true" [maxLength]="16" [validationMessages]="{
|
||||
required: 'State Code is required.',
|
||||
maxlength: 'State Code cannot exceed 16 characters.',
|
||||
pattern: 'State Code can contain letters, numbers, hyphens, and underscores only.'
|
||||
}" [submitAttempted]="stateSubmitAttempted()" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</modal>
|
||||
|
||||
@@ -1,50 +1,187 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { inject, signal } from '@angular/core';
|
||||
import { StateService } from '../../../../../core/services/state/state.service';
|
||||
import { Component, DestroyRef, ElementRef, computed, inject, signal, viewChild } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import {
|
||||
FormBuilder,
|
||||
ReactiveFormsModule,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Subject, catchError, distinctUntilChanged, finalize, map, of, switchMap } from 'rxjs';
|
||||
|
||||
import {
|
||||
CountryLookupDto
|
||||
} from '../../../countries/public-api';
|
||||
import {
|
||||
CreateStateRequest,
|
||||
StateDto,
|
||||
StateModalMode,
|
||||
UpdateStateRequest
|
||||
} from '../../models/state.model';
|
||||
import { CountryService } from '../../../countries/public-api';
|
||||
import { StateService } from '../../data-access/state.service';
|
||||
import { DataTableQueryState } from '../../../../../shared/components/data-table/data-table-query.state';
|
||||
import { DataTablePageEvent, DataTableSortEvent, DataTableQuery, DataTableColumn, DataTableAction, DataTableActionEvent } from '../../../../../shared/components/data-table/data-table.types';
|
||||
import {
|
||||
DataTableAction,
|
||||
DataTableActionEvent,
|
||||
DataTableColumn,
|
||||
DataTablePageEvent,
|
||||
DataTableQuery,
|
||||
DataTableRecord,
|
||||
DataTableSortEvent
|
||||
} from '../../../../../shared/components/data-table/data-table.types';
|
||||
import { DataTable } from '../../../../../shared/components/data-table/data-table';
|
||||
import { finalize} from 'rxjs/operators';
|
||||
import { Autocomplete } from '../../../../../shared/components/form/autocomplete/autocomplete';
|
||||
import {
|
||||
AutocompleteDisplayFn,
|
||||
AutocompleteResolveValueFn,
|
||||
AutocompleteSearchFn,
|
||||
AutocompleteValueFn
|
||||
} from '../../../../../shared/components/form/autocomplete/autocomplete.types';
|
||||
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
|
||||
import { Modal } from '../../../../../shared/components/modal/modal';
|
||||
import { ConfirmDialog } from '../../../../../shared/components/confirm-dialog/confirm-dialog';
|
||||
import { FilterCard } from '../../../../../shared/components/filter-card/filter-card';
|
||||
|
||||
interface StateTableRow extends DataTableRecord {
|
||||
id: string;
|
||||
countryId: string;
|
||||
name: string;
|
||||
code: string | null;
|
||||
isActive: boolean;
|
||||
serialNumber: number;
|
||||
createdOn?: string;
|
||||
modifiedOn?: string | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'state-list',
|
||||
imports: [DataTable],
|
||||
standalone: true,
|
||||
imports: [DataTable, Modal, ReactiveFormsModule, FormInput, Autocomplete, ConfirmDialog, FilterCard],
|
||||
templateUrl: './state-list.html',
|
||||
styleUrl: './state-list.scss',
|
||||
})
|
||||
export class StateList {
|
||||
|
||||
|
||||
private readonly stateApi: StateService = inject(StateService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly stateApi = inject(StateService);
|
||||
private readonly countryApi = inject(CountryService);
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly stateQueryRequests$ = new Subject<DataTableQuery>();
|
||||
|
||||
readonly queryState = new DataTableQueryState();
|
||||
|
||||
readonly states = signal<any[]>([]);
|
||||
readonly states = signal<StateTableRow[]>([]);
|
||||
readonly selectedCountryLookup = signal<CountryLookupDto | null>(null);
|
||||
readonly selectedFormCountry = signal<CountryLookupDto | null>(null);
|
||||
readonly selectedCountryId = signal<string | null>(null);
|
||||
readonly appliedCountryId = signal<string | null>(null);
|
||||
readonly totalRecords = signal(0);
|
||||
readonly filteredRecords = signal(0);
|
||||
readonly loading = signal(false);
|
||||
readonly saving = signal(false);
|
||||
|
||||
readonly showStateModal = signal(false);
|
||||
readonly stateModalMode = signal<StateModalMode>('create');
|
||||
readonly selectedStateId = signal<string | null>(null);
|
||||
readonly selectedState = signal<StateDto | null>(null);
|
||||
readonly stateSubmitAttempted = signal(false);
|
||||
readonly pendingDeleteState = signal<StateDto | null>(null);
|
||||
readonly deleteConfirmDialog = viewChild(ConfirmDialog);
|
||||
|
||||
readonly columns = signal<DataTableColumn[]>([
|
||||
{ key: 'serialNumber', label: 'Sr.No.', header: 'Sr.No.', sortable: false, width: '60px' },
|
||||
{ key: 'name', header: 'Name', label: 'Name', sortable: true },
|
||||
readonly countryFilterForm = this.formBuilder.nonNullable.group({
|
||||
countryId: ['']
|
||||
});
|
||||
|
||||
readonly searchCountries: AutocompleteSearchFn<CountryLookupDto> =
|
||||
(term, limit) => this.countryApi.autocomplete(term, limit);
|
||||
readonly displayCountry: AutocompleteDisplayFn<CountryLookupDto> = country => country.name;
|
||||
readonly countryValue: AutocompleteValueFn<CountryLookupDto, string> = country => country.id;
|
||||
readonly resolveCountry: AutocompleteResolveValueFn<CountryLookupDto, string> =
|
||||
value => this.countryApi.getCountryById(value).pipe(
|
||||
map(country => ({ id: country.id, iso2: country.iso2, name: country.name }))
|
||||
);
|
||||
|
||||
readonly stateForm = this.formBuilder.nonNullable.group({
|
||||
countryId: [
|
||||
'',
|
||||
[
|
||||
Validators.required
|
||||
]
|
||||
],
|
||||
name: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.maxLength(150)
|
||||
]
|
||||
],
|
||||
code: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.maxLength(16),
|
||||
Validators.pattern(/^[A-Za-z0-9_-]+$/)
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
readonly emptyMessage = computed(() =>
|
||||
this.appliedCountryId()
|
||||
? 'No states found'
|
||||
: 'No records found'
|
||||
);
|
||||
|
||||
readonly emptyDescription = computed(() =>
|
||||
this.appliedCountryId()
|
||||
? 'There are no states available for the selected country.'
|
||||
: 'There is currently no data to display.'
|
||||
);
|
||||
|
||||
readonly stateModalTitle = computed(() =>
|
||||
this.stateModalMode() === 'create'
|
||||
? 'Add State'
|
||||
: 'Edit State'
|
||||
);
|
||||
|
||||
readonly stateSubmitLabel = computed(() =>
|
||||
this.stateModalMode() === 'create'
|
||||
? 'Save'
|
||||
: 'Update'
|
||||
);
|
||||
|
||||
readonly stateLoadingLabel = computed(() =>
|
||||
this.stateModalMode() === 'create'
|
||||
? 'Saving...'
|
||||
: 'Updating...'
|
||||
);
|
||||
|
||||
readonly stateSubmitAction = computed<'save' | 'update'>(() =>
|
||||
this.stateModalMode() === 'create'
|
||||
? 'save'
|
||||
: 'update'
|
||||
);
|
||||
|
||||
readonly columns = signal<DataTableColumn<StateTableRow>[]>([
|
||||
{ key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '60px' },
|
||||
{ key: 'name', header: 'Name', label: 'Name', sortable: true, align: 'left' },
|
||||
{ key: 'code', header: 'Code', label: 'Code', sortable: true },
|
||||
{ key: 'isActive', label: 'Status', header: 'Status', sortable: true, badge: true, badgeClass: value =>
|
||||
value === true
|
||||
? 'badge bg-success/10 text-success'
|
||||
: 'badge bg-danger/10 text-danger',
|
||||
{
|
||||
key: 'isActive',
|
||||
label: 'Status',
|
||||
header: 'Status',
|
||||
sortable: true,
|
||||
badge: true,
|
||||
badgeClass: value =>
|
||||
value === true
|
||||
? 'badge bg-success/10 text-success'
|
||||
: 'badge bg-danger/10 text-danger',
|
||||
width: '100px',
|
||||
formatter: (value) => value ? 'Active' : 'Inactive'
|
||||
formatter: value => value ? 'Active' : 'Inactive'
|
||||
}
|
||||
]);
|
||||
|
||||
readonly actions = signal<DataTableAction[]>([
|
||||
{
|
||||
type: 'view',
|
||||
label: 'View',
|
||||
icon: 'ti ti-eye',
|
||||
className: 'text-info'
|
||||
},
|
||||
readonly actions = signal<DataTableAction<StateTableRow>[]>([
|
||||
{
|
||||
type: 'edit',
|
||||
label: 'Edit',
|
||||
@@ -56,70 +193,107 @@ export class StateList {
|
||||
label: 'Delete',
|
||||
icon: 'ti ti-trash',
|
||||
className: 'text-danger',
|
||||
visible: (row: any) => row.isActive
|
||||
visible: row => row.isActive
|
||||
},
|
||||
{
|
||||
type: 'activate',
|
||||
label: 'Activate',
|
||||
icon: 'ti ti-check',
|
||||
className: 'text-success',
|
||||
visible: (row: any) => !row.isActive
|
||||
visible: row => !row.isActive
|
||||
}
|
||||
]);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStates(this.queryState.getQuery(), 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
}
|
||||
loadStates(query: DataTableQuery, countryId: any): void {
|
||||
this.loading.set(true);
|
||||
|
||||
this.stateApi
|
||||
.getStateDataTable(query, countryId)
|
||||
constructor() {
|
||||
this.countryFilterForm.controls.countryId.valueChanges
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading.set(false);
|
||||
})
|
||||
distinctUntilChanged(),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe({
|
||||
next: (response: any) => {
|
||||
console.log('State data loaded:', response);
|
||||
if (response.draw !== this.queryState.getQuery().draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add serial numbers to states
|
||||
const statesWithSerialNumbers = response.rows.map((state: any, index: number) => ({
|
||||
...state,
|
||||
serialNumber: (query.page - 1) * query.pageSize + index + 1
|
||||
}));
|
||||
|
||||
this.states.set(statesWithSerialNumbers);
|
||||
this.totalRecords.set(response.total);
|
||||
this.filteredRecords.set(response.filtered);
|
||||
},
|
||||
error: (error: any) => {
|
||||
console.error('Unable to load states.', error);
|
||||
|
||||
this.states.set([]);
|
||||
this.totalRecords.set(0);
|
||||
this.filteredRecords.set(0);
|
||||
}
|
||||
.subscribe(countryId => {
|
||||
this.onCountrySelected(countryId || null);
|
||||
});
|
||||
|
||||
this.stateQueryRequests$
|
||||
.pipe(
|
||||
switchMap(query => {
|
||||
const countryId = this.appliedCountryId();
|
||||
|
||||
return this.stateApi.getStateDataTable(query, countryId).pipe(
|
||||
catchError(() => {
|
||||
this.toastr.error('Unable to load states.');
|
||||
this.clearStateGrid();
|
||||
return of(null);
|
||||
})
|
||||
);
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe(response => {
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
const query = this.queryState.getQuery();
|
||||
|
||||
if (response.draw !== query.draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statesWithSerialNumbers: StateTableRow[] = response.rows.map((state, index) => ({
|
||||
...state,
|
||||
serialNumber: (query.page - 1) * query.pageSize + index + 1
|
||||
}));
|
||||
|
||||
this.states.set(statesWithSerialNumbers);
|
||||
this.totalRecords.set(response.total);
|
||||
this.filteredRecords.set(response.filtered);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadStates(this.queryState.getQuery());
|
||||
}
|
||||
|
||||
loadStates(query: DataTableQuery): void {
|
||||
this.stateQueryRequests$.next(query);
|
||||
}
|
||||
|
||||
onCountryLookupSelected(country: CountryLookupDto): void {
|
||||
this.selectedCountryLookup.set(country);
|
||||
}
|
||||
|
||||
applyCountryFilter(): void {
|
||||
const countryId = this.countryFilterForm.controls.countryId.value || null;
|
||||
this.appliedCountryId.set(countryId);
|
||||
|
||||
this.loadStates(this.queryState.setPage({
|
||||
pageIndex: 1,
|
||||
pageSize: this.queryState.pageSize()
|
||||
}));
|
||||
}
|
||||
|
||||
onFormCountrySelected(country: CountryLookupDto): void {
|
||||
this.selectedFormCountry.set(country);
|
||||
}
|
||||
|
||||
onFormCountryCleared(): void {
|
||||
this.selectedFormCountry.set(null);
|
||||
}
|
||||
|
||||
onSearch(value: string): void {
|
||||
const query = this.queryState.setSearch(value.trim());
|
||||
this.loadStates(query, 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
this.loadStates(query);
|
||||
}
|
||||
|
||||
onPageChange(event: DataTablePageEvent): void {
|
||||
const query = this.queryState.setPage(event);
|
||||
this.loadStates(query, 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
this.loadStates(query);
|
||||
}
|
||||
|
||||
onSortChange(event: DataTableSortEvent): void {
|
||||
const query = this.queryState.setSort(event);
|
||||
this.loadStates(query, 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
this.loadStates(query);
|
||||
}
|
||||
|
||||
onRefresh(): void {
|
||||
@@ -130,27 +304,42 @@ export class StateList {
|
||||
draw: currentQuery.draw + 1
|
||||
};
|
||||
|
||||
this.loadStates(query, 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
this.loadStates(query);
|
||||
}
|
||||
|
||||
onReset(): void {
|
||||
const query = this.queryState.reset();
|
||||
this.loadStates(query, 'a9d18090-f76a-489f-bfc7-9d97d3d2d7da');
|
||||
this.loadStates(query);
|
||||
}
|
||||
|
||||
onActionClick(event: DataTableActionEvent): void {
|
||||
onDeleteConfirmed(): void {
|
||||
const state = this.pendingDeleteState();
|
||||
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingDeleteState.set(null);
|
||||
this.deleteState(state);
|
||||
}
|
||||
|
||||
onDeleteCancelled(): void {
|
||||
this.pendingDeleteState.set(null);
|
||||
}
|
||||
|
||||
onActionClick(event: DataTableActionEvent<StateTableRow>): void {
|
||||
const action = event.action.type;
|
||||
const state = event.row;
|
||||
const state = this.toStateDto(event.row);
|
||||
|
||||
switch (action) {
|
||||
case 'view':
|
||||
this.viewState(state);
|
||||
break;
|
||||
case 'edit':
|
||||
this.editState(state);
|
||||
this.openEditState(state);
|
||||
break;
|
||||
case 'delete':
|
||||
this.deleteState(state);
|
||||
this.requestDeleteState(state);
|
||||
break;
|
||||
case 'activate':
|
||||
this.activateState(state);
|
||||
@@ -158,24 +347,224 @@ export class StateList {
|
||||
}
|
||||
}
|
||||
|
||||
private viewState(state: any): void {
|
||||
console.log('Viewing state:', state);
|
||||
// TODO: Implement view logic (open modal, navigate to details page, etc.)
|
||||
onAddState(): void {
|
||||
this.stateModalMode.set('create');
|
||||
this.selectedStateId.set(null);
|
||||
this.selectedState.set(null);
|
||||
this.stateSubmitAttempted.set(false);
|
||||
|
||||
this.selectedFormCountry.set(null);
|
||||
this.stateForm.reset({
|
||||
countryId: '',
|
||||
name: '',
|
||||
code: ''
|
||||
});
|
||||
this.resetStateFormState();
|
||||
|
||||
this.showStateModal.set(true);
|
||||
}
|
||||
|
||||
private editState(state: any): void {
|
||||
console.log('Editing state:', state);
|
||||
// TODO: Implement edit logic (open modal, navigate to edit page, etc.)
|
||||
closeStateModal(): void {
|
||||
if (this.saving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showStateModal.set(false);
|
||||
this.selectedStateId.set(null);
|
||||
this.selectedState.set(null);
|
||||
this.selectedFormCountry.set(null);
|
||||
this.stateSubmitAttempted.set(false);
|
||||
}
|
||||
|
||||
private deleteState(state: any): void {
|
||||
console.log('Deleting state:', state);
|
||||
// TODO: Implement delete logic (API call to delete state)
|
||||
saveState(): void {
|
||||
if (this.stateForm.invalid) {
|
||||
this.stateSubmitAttempted.set(true);
|
||||
this.stateForm.markAllAsTouched();
|
||||
this.focusFirstInvalidStateControl();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.saving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving.set(true);
|
||||
|
||||
if (this.stateModalMode() === 'create') {
|
||||
this.stateApi
|
||||
.createState(this.buildCreateStateRequest())
|
||||
.pipe(finalize(() => this.saving.set(false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastr.success('State saved successfully.');
|
||||
this.finishStateSave();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const stateId = this.selectedStateId();
|
||||
|
||||
if (!stateId) {
|
||||
this.saving.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.stateApi
|
||||
.updateState(
|
||||
stateId,
|
||||
this.buildUpdateStateRequest(this.selectedState()?.isActive ?? true)
|
||||
)
|
||||
.pipe(finalize(() => this.saving.set(false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastr.success('State updated successfully.');
|
||||
this.finishStateSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private activateState(state: any): void {
|
||||
console.log('Activating state:', state);
|
||||
// TODO: Implement activate logic (API call to activate state)
|
||||
private onCountrySelected(countryId: string | null): void {
|
||||
this.selectedCountryId.set(countryId);
|
||||
|
||||
if (!countryId || this.selectedCountryLookup()?.id !== countryId) {
|
||||
this.selectedCountryLookup.set(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private clearStateGrid(): void {
|
||||
this.states.set([]);
|
||||
this.totalRecords.set(0);
|
||||
this.filteredRecords.set(0);
|
||||
}
|
||||
|
||||
private viewState(state: StateDto): void {
|
||||
this.openEditState(state);
|
||||
}
|
||||
|
||||
private openEditState(state: StateDto): void {
|
||||
this.stateModalMode.set('edit');
|
||||
this.selectedStateId.set(state.id);
|
||||
this.selectedState.set(state);
|
||||
this.stateSubmitAttempted.set(false);
|
||||
|
||||
this.stateApi
|
||||
.getStateById(state.id)
|
||||
.subscribe({
|
||||
next: stateDetails => {
|
||||
this.selectedState.set(stateDetails);
|
||||
this.selectedFormCountry.set(null);
|
||||
this.stateForm.reset({
|
||||
countryId: stateDetails.countryId ?? '',
|
||||
name: stateDetails.name ?? '',
|
||||
code: stateDetails.code ?? ''
|
||||
});
|
||||
this.resetStateFormState();
|
||||
|
||||
this.showStateModal.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private requestDeleteState(state: StateDto): void {
|
||||
this.pendingDeleteState.set(state);
|
||||
this.deleteConfirmDialog()?.open();
|
||||
}
|
||||
|
||||
private deleteState(state: StateDto): void {
|
||||
this.updateStateStatus(state, false);
|
||||
}
|
||||
|
||||
private activateState(state: StateDto): void {
|
||||
this.updateStateStatus(state, true);
|
||||
}
|
||||
|
||||
private buildCreateStateRequest(): CreateStateRequest {
|
||||
const value = this.stateForm.getRawValue();
|
||||
|
||||
return {
|
||||
countryId: value.countryId,
|
||||
name: value.name.trim(),
|
||||
code: value.code.trim().toUpperCase()
|
||||
};
|
||||
}
|
||||
|
||||
private buildUpdateStateRequest(isActive: boolean): UpdateStateRequest {
|
||||
const value = this.stateForm.getRawValue();
|
||||
|
||||
return {
|
||||
countryId: null,
|
||||
name: value.name.trim(),
|
||||
code: value.code.trim().toUpperCase(),
|
||||
isActive
|
||||
};
|
||||
}
|
||||
|
||||
private stateToUpdateRequest(state: StateDto, isActive: boolean): UpdateStateRequest {
|
||||
return {
|
||||
countryId: null,
|
||||
name: state.name?.trim() ?? '',
|
||||
code: state.code?.trim().toUpperCase() ?? '',
|
||||
isActive
|
||||
};
|
||||
}
|
||||
|
||||
private updateStateStatus(state: StateDto, isActive: boolean): void {
|
||||
this.stateApi
|
||||
.updateState(state.id, this.stateToUpdateRequest(state, isActive))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastr.success(
|
||||
isActive
|
||||
? 'State activated successfully.'
|
||||
: 'State deactivated successfully.'
|
||||
);
|
||||
this.loadStates(this.queryState.getQuery());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private resetStateFormState(): void {
|
||||
this.stateForm.markAsPristine();
|
||||
this.stateForm.markAsUntouched();
|
||||
this.stateForm.updateValueAndValidity();
|
||||
}
|
||||
|
||||
private finishStateSave(): void {
|
||||
this.showStateModal.set(false);
|
||||
this.selectedStateId.set(null);
|
||||
this.selectedState.set(null);
|
||||
this.selectedFormCountry.set(null);
|
||||
this.stateSubmitAttempted.set(false);
|
||||
this.loadStates(this.queryState.getQuery());
|
||||
}
|
||||
|
||||
private focusFirstInvalidStateControl(): void {
|
||||
queueMicrotask(() => {
|
||||
const firstInvalidControl =
|
||||
this.elementRef.nativeElement.querySelector<HTMLElement>(
|
||||
'modal [data-form-control][aria-invalid="true"]'
|
||||
);
|
||||
|
||||
firstInvalidControl?.focus();
|
||||
firstInvalidControl?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private toStateDto(row: StateTableRow): StateDto {
|
||||
return {
|
||||
id: row.id,
|
||||
countryId: row.countryId,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
isActive: row.isActive,
|
||||
createdOn: row.createdOn,
|
||||
modifiedOn: row.modifiedOn
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { StateService } from './data-access/state.service';
|
||||
export type { StateLookupDto } from './models/state.model';
|
||||
Reference in New Issue
Block a user