Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TimeEntriesComponent } from './modules/time-entries/pages/time-entries.
import { ActivitiesManagementComponent } from './modules/activities-management/pages/activities-management.component';
import { HomeComponent } from './modules/home/home.component';
import { LoginComponent } from './modules/login/login.component';
import { CustomerComponent } from './modules/customer-managment/pages/customer.component';
import { CustomerComponent } from './modules/customer-management/pages/customer.component';

const routes: Routes = [
{
Expand Down
21 changes: 10 additions & 11 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ import { ProjectEffects } from './modules/project-management/store/project.effec
import { TechnologyEffects } from './modules/shared/store/technology.effects';
import { reducers, metaReducers } from './reducers';
import { environment } from '../environments/environment';
import { CustomerComponent } from './modules/customer-managment/pages/customer.component';
import { CustomerComponent } from './modules/customer-management/pages/customer.component';
// tslint:disable-next-line: max-line-length
import { CustomerListComponent } from './modules/customer-managment/components/customer-info/components/customer-list/customer-list.component';
import { SearchComponent } from './modules/customer-managment/components/search/search.component';
import { CustomerListComponent } from './modules/customer-management/components/customer-info/components/customer-list/customer-list.component';
// tslint:disable-next-line: max-line-length
import { ManagementCustomerProjectsComponent } from './modules/customer-managment/components/management-customer-projects/management-customer-projects.component';
import { CreateCustomerComponent } from './modules/customer-managment/components/customer-info/components/create-customer/create-customer';
import { ManagementCustomerProjectsComponent } from './modules/customer-management/components/management-customer-projects/management-customer-projects.component';
import { CreateCustomerComponent } from './modules/customer-management/components/customer-info/components/create-customer/create-customer';
// tslint:disable-next-line: max-line-length
import { CreateProjectComponent } from './modules/customer-managment/components/projects/components/create-project/create-project.component';
import { ProjectListComponent } from './modules/customer-managment/components/projects/components/project-list/project-list.component';
import { CreateProjectComponent } from './modules/customer-management/components/projects/components/create-project/create-project.component';
import { ProjectListComponent } from './modules/customer-management/components/projects/components/project-list/project-list.component';
// tslint:disable-next-line: max-line-length
import { ProjectTypeListComponent } from './modules/customer-managment/components/projects-type/components/project-type-list/project-type-list.component';
import { ProjectTypeListComponent } from './modules/customer-management/components/projects-type/components/project-type-list/project-type-list.component';
// tslint:disable-next-line: max-line-length
import { CreateProjectTypeComponent } from './modules/customer-managment/components/projects-type/components/create-project-type/create-project-type.component';
import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component';
import { CustomerEffects } from './modules/customer-management/store/customer-management.effects';

@NgModule({
declarations: [
Expand Down Expand Up @@ -77,7 +77,6 @@ import { CreateProjectTypeComponent } from './modules/customer-managment/compone
SearchProjectComponent,
CustomerComponent,
CustomerListComponent,
SearchComponent,
ManagementCustomerProjectsComponent,
CreateCustomerComponent,
CreateProjectComponent,
Expand All @@ -102,7 +101,7 @@ import { CreateProjectTypeComponent } from './modules/customer-managment/compone
maxAge: 15, // Retains last 15 states
})
: [],
EffectsModule.forRoot([ProjectEffects, ActivityEffects, TechnologyEffects]),
EffectsModule.forRoot([ProjectEffects, ActivityEffects, CustomerEffects, TechnologyEffects]),
],
providers: [],
bootstrap: [AppComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class="container">
<form style="width: 600px;" [formGroup]="customerForm" (ngSubmit)="onSubmit(customerForm.value)">
<div class="form-group">
<div
*ngIf="showAlert && messageToShow !== ''"
[ngClass]="{'bg-secondary': messageToShow == 'Customer created successfully!', 'bg-primary': messageToShow != 'Customer created successfully!'}"
class="alert alert-dismissible fade fade-in show text-white"
role="alert"
>
<strong>{{ messageToShow }}</strong>
</div>

<input
class="form-control form-control-sm"
id="name"
type="text"
formControlName="name"
placeholder="Customer name"
[class.is-invalid]="customerForm.invalid && customerForm.touched"
required
/>
<span
class="badge badge-pill badge-light text-danger"
*ngIf="(customerForm.dirty || customerForm.touched) && customerForm.invalid"
>Activity name is required</span
>
<textarea
class="form-control form-control-sm mt-2"
id="description"
rows="3"
formControlName="description"
placeholder="Customer description"
></textarea>
<button type="submit" class="btn btn-sm btn-primary" [disabled]="!customerForm.valid">Save</button>
<button (click)="resetCustomerForm()" id="cancel" type="button" class="btn btn-sm btn-secondary mb-2 ml-2 mt-2">
Cancel
</button>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder } from '@angular/forms';
import { MockStore, provideMockStore } from '@ngrx/store/testing';

import { CreateCustomerComponent } from './create-customer';
import { CustomerState, CreateCustomer } from 'src/app/modules/customer-management/store';
import * as models from 'src/app/modules/shared/models/index';

describe('CreateCustomerComponent', () => {
let component: CreateCustomerComponent;
let fixture: ComponentFixture<CreateCustomerComponent>;
let store: MockStore<CustomerState>;

const state = {
data: [],
isLoading: false,
message: '',
};

const customerData: models.Customer = {
name: 'aa',
description: 'bb',
tenant_id: 'cc',
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CreateCustomerComponent],
providers: [FormBuilder, provideMockStore({ initialState: state })],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CreateCustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();

store = TestBed.inject(MockStore);
store.setState(state);
});

afterEach(() => {
fixture.destroy();
});

it('component should be created', () => {
expect(component).toBeTruthy();
});

it('should call resetCustomerForm', () => {
spyOn(component.customerForm, 'reset');

component.resetCustomerForm();

expect(component.customerForm.reset).toHaveBeenCalled();
});

it('onSubmit and dispatch CreateCustomer action', () => {
spyOn(store, 'dispatch');

component.onSubmit(customerData);

expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith(new CreateCustomer(customerData));
});

it('should call resetCustomerForm', () => {
spyOn(component.customerForm, 'reset');

component.resetCustomerForm();

expect(component.customerForm.reset).toHaveBeenCalled();
});

it('should be enable tabs and show message Customer created successfully! ', () => {
component.areTabsActive = false;
component.messageToShow = '';

spyOn(store, 'dispatch');

component.ngOnInit();

component.onSubmit(customerData);

component.setStatusOnScreen('Customer created successfully!');

expect(component.messageToShow).toEqual('Customer created successfully!');
expect(component.areTabsActive).toBeTrue();
});

it('should be disabled tabs and show message An error occurred, try again later. ', () => {
component.areTabsActive = false;
component.messageToShow = '';

spyOn(store, 'dispatch');

component.ngOnInit();

component.onSubmit(customerData);

component.setStatusOnScreen('An error occurred, try again later.');

expect(component.messageToShow).toEqual('An error occurred, try again later.');
expect(component.areTabsActive).toBeFalse();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Component, Input, Output, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sort imports by module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

import { Store, select } from '@ngrx/store';

import { Subscription } from 'rxjs';
import { CustomerState, CreateCustomer } from 'src/app/modules/customer-management/store';
import { getStatusMessage } from 'src/app/modules/customer-management/store/customer-management.selectors';

@Component({
selector: 'app-create-customer',
templateUrl: './create-customer.html',
styleUrls: ['./create-customer.scss'],
})
export class CreateCustomerComponent implements OnInit, OnDestroy {
customerForm: FormGroup;
@Input() areTabsActive: boolean;
@Output() changeValueAreTabsActives = new EventEmitter<boolean>();
showAlert = false;
messageToShow = '';
saveSubscription: Subscription;

constructor(private formBuilder: FormBuilder, private store: Store<CustomerState>) {
this.customerForm = this.formBuilder.group({
name: ['', Validators.required],
description: [''],
});
}

ngOnInit() {
const messages$ = this.store.pipe(select(getStatusMessage));
this.saveSubscription = messages$.subscribe((valueMessage) => {
this.setStatusOnScreen(valueMessage);
});
}

ngOnDestroy() {
this.areTabsActive = false;
this.saveSubscription.unsubscribe();
}

onSubmit(customerData) {
this.store.dispatch(new CreateCustomer(customerData));
this.showAlert = true;
}

setStatusOnScreen(message: string) {
if (message === 'Customer created successfully!') {
this.messageToShow = message;
this.areTabsActive = true;
this.changeValueAreTabsActives.emit(this.areTabsActive);
} else if (message === 'An error occurred, try again later.') {
this.messageToShow = message;
this.areTabsActive = false;
this.changeValueAreTabsActives.emit(this.areTabsActive);
}
}

resetCustomerForm() {
this.showAlert = false;
this.customerForm.reset();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
</li>
<li class="nav-item">
<a
class="nav-link nav-active text-black font-weight-bold"
class="nav-link text-black font-weight-bold"
[ngClass]="{ 'nav-active': areTabsActive, disabled: !areTabsActive }"
id="projects-type-tab"
data-toggle="tab"
href="#projectsType"
Expand All @@ -24,7 +25,8 @@
</li>
<li class="nav-item">
<a
class="nav-link nav-active text-black font-weight-bold"
class="nav-link text-black font-weight-bold"
[ngClass]="{ 'nav-active': areTabsActive, disabled: !areTabsActive }"
id="projects-tab"
data-toggle="tab"
href="#projects"
Expand All @@ -41,7 +43,10 @@
role="tabpanel"
aria-labelledby="customer-information-tab"
>
<app-create-customer></app-create-customer>
<app-create-customer
[areTabsActive]="areTabsActive"
(changeValueAreTabsActives)="areTabsActive = $event"
></app-create-customer>
</div>
<div class="tab-pane fade mt-3" id="projects" role="tabpanel" aria-labelledby="projects-tab">
<app-create-project></app-create-project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ import { Component } from '@angular/core';
styleUrls: ['./management-customer-projects.component.scss'],
})
export class ManagementCustomerProjectsComponent {
areTabsActive = false;

constructor() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
<hr />
<div class="row text-right">
<hr />
<app-search></app-search>
<div class="col-5 text-right">
<app-search-project></app-search-project>
</div>
</div>
<app-project-list></app-project-list>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ <h4>Customers</h4>
<div class="col">
<button (click)="activateCustomerForm()" type="button" class="btn btn-primary">Add new customer</button>
</div>
<app-search></app-search>
<div class="col-5 text-right">
<app-search-project></app-search-project>
</div>
</div>
<app-customer-list></app-customer-list>
<div class="row" *ngIf="showCustomerForm">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed, inject } from '@angular/core/testing';

import { CustomerService } from './customer.service';
import { Customer } from '../../shared/models/customer.model';

describe('CustomerService', () => {
let service: CustomerService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
service = TestBed.inject(CustomerService);
httpMock = TestBed.inject(HttpTestingController);
});

afterEach(() => {
httpMock.verify();
});

it('services are ready to be used', inject(
[HttpClientTestingModule, CustomerService],
(httpClient: HttpClientTestingModule, customerService: CustomerService) => {
expect(customerService).toBeTruthy();
expect(httpClient).toBeTruthy();
}
));

it('create customer using POST from baseUrl', () => {
const customer: Customer[] = [{ name: 'aa', description: 'bb', tenant_id: 'cc' }];

service.baseUrl = 'customers';

service.createCustomer(customer).subscribe((response) => {
expect(response.length).toBe(1);
});

const createCustomerRequest = httpMock.expectOne(service.baseUrl);
expect(createCustomerRequest.request.method).toBe('POST');
createCustomerRequest.flush(customer);
});
});
23 changes: 23 additions & 0 deletions src/app/modules/customer-management/services/customer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import { environment } from './../../../../environments/environment';
import { AzureAdB2CService } from 'src/app/modules/login/services/azure.ad.b2c.service';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class CustomerService {
baseUrl = `${environment.timeTrackerApiUrl}/customers`;

constructor(private http: HttpClient, private service: AzureAdB2CService) {}

createCustomer(customerData): Observable<any> {
const body = {
...customerData,
tenant_id: this.service.getTenantId(),
};
return this.http.post(this.baseUrl, body);
}
}
Loading