diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html index 62db0a46f..acf7e6f99 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html @@ -8,35 +8,36 @@ > - Customer ID - Name - Options + Customer ID + Name + Options + Visibility - {{ customer.id }} - {{ customer.name }} - - - - @@ -68,4 +69,4 @@ [body]="message" (closeModalEvent)="closeModal()" > - \ No newline at end of file + diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts index 8ab919874..b38a604a8 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts @@ -23,20 +23,39 @@ describe('CustomerTableListComponent', () => { const actionSub: ActionsSubject = new ActionsSubject(); const state = { - data: [{ tenant_id: 'id', name: 'name', description: 'description' }], + data: [{ tenant_id: 'id', name: 'name', description: 'description', status: 'inactive' }], isLoading: false, message: '', customerIdToEdit: '', customerId: '', }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [NgxPaginationModule, DataTablesModule], - declarations: [CustomerListComponent], - providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }], - }).compileComponents(); - })); + const btnProps = [ + { + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }, + { + key: 'inactive', + _status: true, + btnColor: 'btn-primary', + btnIcon: 'fa-arrow-circle-up', + btnName: 'Active', + }, + ]; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NgxPaginationModule, DataTablesModule], + declarations: [CustomerListComponent], + providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }], + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(CustomerListComponent); @@ -175,7 +194,12 @@ describe('CustomerTableListComponent', () => { actionSubject.next(action); - expect(component.customers).toEqual(state.data); + const StateWithBtnProperties = state.data.map((customer) => { + const addProps = btnProps.find((prop) => prop.key === component.setActive(customer.status)); + return { ...customer, ...addProps }; + }); + + expect(component.customers).toEqual(StateWithBtnProperties); }); it('on success load customer, the datatable should be reloaded', async () => { @@ -191,6 +215,60 @@ describe('CustomerTableListComponent', () => { expect(component.dtElement.dtInstance.then).toHaveBeenCalled(); }); + it('openModal should set on true and display "Are you sure you want to archive customer"', () => { + const message = 'Are you sure you want to archive name?'; + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'active', + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }; + + component.openModal(itemData); + expect(component.showModal).toBeTrue(); + expect(component.message).toBe(message); + }); + + it('switchStatus should call openModal() on item.status = activate', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'activate', + key: 'activate', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }; + + spyOn(component, 'openModal'); + component.switchStatus(itemData); + expect(component.openModal).toHaveBeenCalled(); + }); + + it('switchStatus should set showModal false when item.status = inactive', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'inactive', + key: 'inactive', + _status: true, + btnColor: 'btn-primary', + btnIcon: 'fa-arrow-circle-up', + btnName: 'Active', + }; + + component.switchStatus(itemData); + expect(component.showModal).toBeFalse(); + }); + afterEach(() => { component.dtTrigger.unsubscribe(); component.changeCustomerSubscription.unsubscribe(); diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts index 45c8495ad..a3d431e82 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts @@ -5,9 +5,9 @@ import { Observable, Subject, Subscription } from 'rxjs'; import { delay, filter } from 'rxjs/operators'; import { customerIdtoEdit, - getIsLoading + getIsLoading, } from 'src/app/modules/customer-management/store/customer-management.selectors'; -import { Customer } from './../../../../../shared/models/customer.model'; +import { Customer, CustomerUI } from './../../../../../shared/models/customer.model'; import { CustomerManagementActionTypes, DeleteCustomer, @@ -17,6 +17,7 @@ import { } from './../../../../store/customer-management.actions'; import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/components/store/project.actions'; import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store'; +import { UnarchiveCustomer } from '../../../../store/customer-management.actions'; @Component({ selector: 'app-customer-list', @@ -28,7 +29,7 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { @Input() hasChange: boolean; @Output() changeValueShowCustomerForm = new EventEmitter(); @Input() - customers: Customer[] = []; + customers: CustomerUI[] = []; dtOptions: any = {}; dtTrigger: Subject = new Subject(); @ViewChild(DataTableDirective, { static: false }) @@ -48,6 +49,23 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { } ngOnInit(): void { + const btnProps = [ + { + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }, + { + key: 'inactive', + _status: true, + btnColor: 'btn-primary', + btnIcon: 'fa-arrow-circle-up', + btnName: 'Active', + }, + ]; + this.dtOptions = { scrollY: '325px', paging: false, @@ -62,7 +80,10 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { this.loadCustomersSubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === CustomerManagementActionTypes.LOAD_CUSTOMERS_SUCCESS)) .subscribe((action) => { - this.customers = action.payload; + this.customers = action.payload.map((customer: CustomerUI) => { + const addProps = btnProps.find((prop) => prop.key === this.setActive(customer.status)); + return { ...customer, ...addProps }; + }); this.rerenderDataTable(); }); this.changeCustomerSubscription = this.actionsSubject$ @@ -151,7 +172,20 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { openModal(item: Customer) { this.idToDelete = item.id; - this.message = `Are you sure you want to delete ${item.name}?`; + this.message = `Are you sure you want to archive ${item.name}?`; this.showModal = true; } + + switchStatus(item: CustomerUI): void { + if (item.key !== 'inactive') { + this.openModal(item); + } else { + this.showModal = false; + this.store.dispatch(new UnarchiveCustomer(item.id)); + } + } + + setActive(status: any): string { + return status === 'inactive' ? 'inactive' : 'active'; + } } diff --git a/src/app/modules/customer-management/store/customer-management.actions.spec.ts b/src/app/modules/customer-management/store/customer-management.actions.spec.ts index 5e38228ea..ebb71ed91 100644 --- a/src/app/modules/customer-management/store/customer-management.actions.spec.ts +++ b/src/app/modules/customer-management/store/customer-management.actions.spec.ts @@ -77,4 +77,22 @@ describe('CustomerManagmentActions', () => { const resetCustomerIdToEdit = new actions.ResetCustomerToEdit(); expect(resetCustomerIdToEdit.type).toEqual(actions.CustomerManagementActionTypes.RESET_CUSTOMER_ID_TO_EDIT); }); + + it('UnarchiveCustomer type is CustomerManagementActionTypes.UNARCHIVE_CUSTOMER', () => { + const unArchiveCustomer = new actions.UnarchiveCustomer('id_test'); + expect(unArchiveCustomer.type).toEqual(actions.CustomerManagementActionTypes.UNARCHIVE_CUSTOMER); + }); + + it('UnarchiveCustomerSuccess type is CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_SUCCESS', () => { + const unArchiveCustomerSuccess = new actions.UnarchiveCustomerSuccess({ + id: 'id_test', + status: 'active', + }); + expect(unArchiveCustomerSuccess.type).toEqual(actions.CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_SUCCESS); + }); + + it('UnarchiveCustomerFail type is CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_FAIL', () => { + const unArchiveCustomerFail = new actions.UnarchiveCustomerFail('error'); + expect(unArchiveCustomerFail.type).toEqual(actions.CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_FAIL); + }); }); diff --git a/src/app/modules/customer-management/store/customer-management.actions.ts b/src/app/modules/customer-management/store/customer-management.actions.ts index e58e71758..1eac29dbe 100644 --- a/src/app/modules/customer-management/store/customer-management.actions.ts +++ b/src/app/modules/customer-management/store/customer-management.actions.ts @@ -1,5 +1,5 @@ import { Action } from '@ngrx/store'; -import { Customer } from '../../shared/models/customer.model'; +import { Customer, Status } from '../../shared/models/customer.model'; export enum CustomerManagementActionTypes { CREATE_CUSTOMER = '[CustomerManagement] CREATE_CUSTOMER', @@ -16,6 +16,9 @@ export enum CustomerManagementActionTypes { UPDATE_CUSTOMER_FAIL = '[CustomerManagement] UPDATE_CUSTOMER_FAIL', SET_CUSTOMER_ID_TO_EDIT = '[CustomerManagement] SET_CUSTOMER_ID_TO_EDIT', RESET_CUSTOMER_ID_TO_EDIT = '[CustomerManagement] RESET_CUSTOMER_ID_TO_EDIT', + UNARCHIVE_CUSTOMER = '[CustomerManagement] UNARCHIVE_CUSTOMER', + UNARCHIVE_CUSTOMER_SUCCESS = '[CustomerManagement] UNARCHIVE_CUSTOMER_SUCCESS', + UNARCHIVE_CUSTOMER_FAIL = '[CustomerManagement] UNARCHIVE_CUSTOMER_FAIL', } export class LoadCustomers implements Action { @@ -97,6 +100,24 @@ export class ResetCustomerToEdit implements Action { public readonly type = CustomerManagementActionTypes.RESET_CUSTOMER_ID_TO_EDIT; } +export class UnarchiveCustomer implements Action { + public readonly type = CustomerManagementActionTypes.UNARCHIVE_CUSTOMER; + + constructor(public payload: string) {} +} + +export class UnarchiveCustomerSuccess implements Action { + public readonly type = CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_SUCCESS; + + constructor(public payload: Status) {} +} + +export class UnarchiveCustomerFail implements Action { + public readonly type = CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_FAIL; + + constructor(public error: string) {} +} + export type CustomerManagementActions = | CreateCustomer | CreateCustomerSuccess @@ -111,4 +132,7 @@ export type CustomerManagementActions = | UpdateCustomerSuccess | UpdateCustomerFail | SetCustomerToEdit - | ResetCustomerToEdit; + | ResetCustomerToEdit + | UnarchiveCustomer + | UnarchiveCustomerSuccess + | UnarchiveCustomerFail; diff --git a/src/app/modules/customer-management/store/customer-management.effects.spec.ts b/src/app/modules/customer-management/store/customer-management.effects.spec.ts index bd1167996..c88d4a2d4 100644 --- a/src/app/modules/customer-management/store/customer-management.effects.spec.ts +++ b/src/app/modules/customer-management/store/customer-management.effects.spec.ts @@ -121,4 +121,27 @@ describe('CustomerEffects', () => { expect(action.type).toEqual(CustomerManagementActionTypes.DELETE_CUSTOMER_FAIL); }); }); + + it('action type is UNARCHIVE_CUSTOMER_SUCCESS when service is executed sucessfully', async () => { + const customerId = 'customerId'; + actions$ = of({ type: CustomerManagementActionTypes.UNARCHIVE_CUSTOMER, customerId }); + spyOn(toastrService, 'success'); + spyOn(service, 'updateCustomer').and.returnValue(of(customer)); + + effects.updateCustomer$.subscribe((action) => { + expect(toastrService.success).toHaveBeenCalledWith(INFO_SAVED_SUCCESSFULLY); + expect(action.type).toEqual(CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_SUCCESS); + }); + }); + + it('action type is UNARCHIVE_CUSTOMER_FAIL when service fail in execution', async () => { + actions$ = of({ type: CustomerManagementActionTypes.UNARCHIVE_CUSTOMER, customer }); + spyOn(toastrService, 'error'); + spyOn(service, 'updateCustomer').and.returnValue(throwError({ error: { message: 'fail!' } })); + + effects.updateCustomer$.subscribe((action) => { + expect(toastrService.error).toHaveBeenCalled(); + expect(action.type).toEqual(CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_FAIL); + }); + }); }); diff --git a/src/app/modules/customer-management/store/customer-management.effects.ts b/src/app/modules/customer-management/store/customer-management.effects.ts index 5ee838869..72e93540b 100644 --- a/src/app/modules/customer-management/store/customer-management.effects.ts +++ b/src/app/modules/customer-management/store/customer-management.effects.ts @@ -8,6 +8,7 @@ import { map, catchError, mergeMap } from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; import { CustomerService } from '../services/customer.service'; import * as actions from './customer-management.actions'; +import { Status } from '../../shared/models/customer.model'; @Injectable() export class CustomerEffects { @@ -87,4 +88,25 @@ export class CustomerEffects { ) ) ); + + @Effect() + unarchiveCustomer$: Observable = this.actions$.pipe( + ofType(actions.CustomerManagementActionTypes.UNARCHIVE_CUSTOMER), + map((action: actions.UnarchiveCustomer) => ({ + id: action.payload, + status: 'active', + })), + mergeMap((customer: Status) => + this.customerService.updateCustomer(customer).pipe( + map((customerData) => { + this.toastrService.success(INFO_SAVED_SUCCESSFULLY); + return new actions.UpdateCustomerSuccess(customerData); + }), + catchError((error) => { + this.toastrService.error(error.error.message); + return of(new actions.UpdateCustomerFail(error)); + }) + ) + ) + ); } diff --git a/src/app/modules/customer-management/store/customer-management.reducers.spec.ts b/src/app/modules/customer-management/store/customer-management.reducers.spec.ts index f69256259..da8caf6cc 100644 --- a/src/app/modules/customer-management/store/customer-management.reducers.spec.ts +++ b/src/app/modules/customer-management/store/customer-management.reducers.spec.ts @@ -1,10 +1,10 @@ import { CustomerState, customerManagementReducer } from './customer-management.reducers'; -import { Customer } from '../../shared/models/index'; +import { Customer, Status } from '../../shared/models/index'; import * as actions from './customer-management.actions'; describe('customerManagementReducer', () => { const initialState: CustomerState = { data: [], isLoading: false, message: '', customerIdToEdit: '', customerId: '' }; - const customer: Customer = { name: 'aa', description: 'bb' }; + const customer: Customer = { name: 'aa', description: 'bb', status: 'inactive' }; it('on LoadCustomer, isLoading is true ', () => { const action = new actions.LoadCustomers(); @@ -63,11 +63,11 @@ describe('customerManagementReducer', () => { it('on DeleteCustomerSuccess, message equal to Customer removed successfully!', () => { const currentState = { - data: [{ name: 'aa', description: 'bb', tenant_id: 'cc', id: '1' }], + data: [{ name: 'aa', description: 'bb', tenant_id: 'cc', id: '1', status: 'inactive' }], isLoading: false, message: '', customerIdToEdit: '', - customerId: '' + customerId: '', }; const customerToDeleteId = '1'; const action = new actions.DeleteCustomerSuccesss(customerToDeleteId); @@ -75,7 +75,7 @@ describe('customerManagementReducer', () => { expect(state.isLoading).toEqual(false); expect(state.data.length).toEqual(0); - expect(state.message).toEqual('Customer removed successfully!'); + expect(state.message).toEqual('Customer archived successfully!'); }); it('on DeleteCustomeryFail, message equal to Something went wrong deleting customer!', () => { @@ -100,7 +100,7 @@ describe('customerManagementReducer', () => { isLoading: false, message: '', customerIdToEdit: '1', - customerId: '' + customerId: '', }; const customerEdited = { name: 'xx', description: 'yy', tenant_id: 'cc', id: '1' }; const action = new actions.UpdateCustomerSuccess(customerEdited); @@ -133,4 +133,37 @@ describe('customerManagementReducer', () => { expect(state.customerIdToEdit).toEqual(''); }); + + it('on UnarchiveCustomer, isLoading is true', () => { + const action = new actions.UnarchiveCustomer('1'); + const state = customerManagementReducer(initialState, action); + + expect(state.isLoading).toEqual(true); + }); + + it('on UnarchiveCustomerSuccess, status customer is change to "active" in the store', () => { + const currentState = { + data: [{ name: 'aa', description: 'bb', tenant_id: 'cc', id: '1', status: 'inactive' }], + isLoading: false, + message: '', + customerIdToEdit: '1', + customerId: '', + }; + const customerEdited: Status = { id: '1', status: 'active' }; + const expectedCustomer = { name: 'aa', description: 'bb', tenant_id: 'cc', id: '1', status: 'active' }; + const action = new actions.UnarchiveCustomerSuccess(customerEdited); + const state = customerManagementReducer(currentState, action); + + expect(state.data).toEqual([expectedCustomer]); + expect(state.isLoading).toEqual(false); + expect(state.message).toEqual('Customer unarchive successfully!'); + }); + + it('on UnarchiveCustomerFail, message equal to Something went wrong unarchiving customer!', () => { + const action = new actions.UnarchiveCustomerFail('error'); + const state = customerManagementReducer(initialState, action); + + expect(state.message).toEqual('Something went wrong unarchiving customer!'); + expect(state.isLoading).toEqual(false); + }); }); diff --git a/src/app/modules/customer-management/store/customer-management.reducers.ts b/src/app/modules/customer-management/store/customer-management.reducers.ts index 189f9e31c..8238761ba 100644 --- a/src/app/modules/customer-management/store/customer-management.reducers.ts +++ b/src/app/modules/customer-management/store/customer-management.reducers.ts @@ -81,7 +81,7 @@ export const customerManagementReducer = (state: CustomerState = initialState, a ...state, data: customers, isLoading: false, - message: 'Customer removed successfully!', + message: 'Customer archived successfully!', }; } @@ -137,6 +137,33 @@ export const customerManagementReducer = (state: CustomerState = initialState, a }; } + case CustomerManagementActionTypes.UNARCHIVE_CUSTOMER: { + return { + ...state, + isLoading: true, + }; + } + + case CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_SUCCESS: { + const index = customersList.findIndex((customer) => customer.id === action.payload.id); + customersList[index] = { ...customersList[index], ...action.payload }; + return { + ...state, + data: customersList, + isLoading: false, + message: 'Customer unarchive successfully!', + }; + } + + case CustomerManagementActionTypes.UNARCHIVE_CUSTOMER_FAIL: { + return { + ...state, + data: [], + isLoading: false, + message: 'Something went wrong unarchiving customer!', + }; + } + default: return state; } diff --git a/src/app/modules/shared/models/customer.model.ts b/src/app/modules/shared/models/customer.model.ts index 67337e5d9..42107c878 100644 --- a/src/app/modules/shared/models/customer.model.ts +++ b/src/app/modules/shared/models/customer.model.ts @@ -1,5 +1,20 @@ export interface Customer { id?: string; - name: string; + name?: string; description?: string; + status?: any; +} +export interface CustomerUI { + id?: string; + name?: string; + description?: string; + status?: any; + key?: string; + btnColor?: string; + btnIcon?: string; + btnName?: string; +} +export interface Status { + id: string; + status: any; }