Skip to content
2,905 changes: 2,003 additions & 902 deletions package-lock.json → --package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
Contact
</div>
<div class="card-body">
<p>Name: <span class="badge badge-primary">{{contact.name}}</span></p>
<p>Name: <span class="badge badge-primary">{{contact.first_name}} {{contact.last_name}}</span></p>
<p>Email: <span class="badge badge-primary">{{contact.email}}</span></p>
<p>Phone: <span class="badge badge-primary">{{contact.phone}}</span></p>
<p>Avatar: <span class="badge badge-primary"> <img [src]="contact.avatar"> </span></p>
</div>
<div class="card-footer text-muted">
<span class="float-md-right ">
Expand Down
15 changes: 10 additions & 5 deletions src/app/core/components/contact-form/contact-form.component.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<form (submit)="submit()" class="form" [formGroup]="form">

<div class="form-group">
<label for="name-input">Name:</label>
<input id="name-input" type="text" class="form-control" formControlName="name">
<label for="first-name-input">First Name:</label>
<input id="first-name-input" type="text" class="form-control" formControlName="firstName">
</div>

<div class="form-group">
<label for="last-name-input">Last Name:</label>
<input id="last-name-input" type="text" class="form-control" formControlName="lastName">
</div>

<div class="form-group">
Expand All @@ -13,8 +18,8 @@


<div class="form-group">
<label for="phone-input">Phone:</label>
<input id="phone-input" type="tel" class="form-control" formControlName="phone">
<label for="phone-input"> avatar:</label>
<input id="phone-input" type="tel" class="form-control" formControlName="avatar">
</div>


Expand All @@ -27,4 +32,4 @@
</button>
</div>

</form>
</form>
22 changes: 16 additions & 6 deletions src/app/core/components/contact-form/contact-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms';
export class ContactFormComponent implements OnInit, OnChanges {

@Input() contact: Contact = {
id: undefined,
name: '',
id: null,
first_name: '',
last_name: '',
email: '',
phone: ''
avatar: ''
};

@Output() save = new EventEmitter<Contact>();
Expand All @@ -25,9 +26,10 @@ export class ContactFormComponent implements OnInit, OnChanges {
constructor(public formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
id: [this.contact.id],
name: [this.contact.name, Validators.required],
firstName: [this.contact.first_name, Validators.required],
lastName: [this.contact.last_name],
email: [this.contact.email, Validators.required],
phone: [this.contact.phone]
avatar: [this.contact.avatar]
});
}

Expand All @@ -37,7 +39,15 @@ export class ContactFormComponent implements OnInit, OnChanges {

ngOnChanges() {
if (this.contact) {
this.form.patchValue({...this.contact});
this.form.patchValue(
{
id: this.contact.id,
firstName: this.contact.first_name,
lastName: this.contact.last_name,
email: this.contact.email,
avatar: this.contact.avatar
}
);
}
}

Expand Down
15 changes: 11 additions & 4 deletions src/app/core/components/contact-list/contact-list.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
<th>#</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Avatar</th>
<th></th>
</tr>
</thead>
<tbody>
<tr class="contact-row" *ngFor="let c of contacts; trackBy: contactsTrackByFn">
<th scope="row">{{c.id}}</th>
<td>{{c.name}}</td>
<td>{{c.email}}</td>
<td>{{c.phone}}</td>
<td>
<span [innerHtml]="c.first_name | highLightText:keyword"></span>
<span>&nbsp; </span>
<span [innerHtml]="c.last_name | highLightText:keyword"> </span>
</td>


<td [innerHtml]="c.email | highLightText:keyword">
</td>
<td class="avatar"><img [src]="c.avatar"></td>
<td>

<span class="float-right">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@

.header
width: 100%

.avatar img
display: block
height: 30px
width: 30px

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class ContactListComponent implements OnInit {
@Output() show = new EventEmitter<Contact>();
@Output() remove = new EventEmitter<Contact>();

@Input() keyword = '';

contactsTrackByFn = (index: number, contact: Contact) => contact.id;

constructor() {}
Expand Down
25 changes: 23 additions & 2 deletions src/app/core/models/contact.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@


export interface Contact {
id?: number;
name: string;
first_name: string;
last_name?: string;
email: string;
phone?: string;
avatar?: string;
}

export interface SupportUrl {
url: string;
text: string;
}


export interface ContactPage {
data: Contact[];
page: number;
per_page: number;
supportUrl: SupportUrl;
total: number;
total_pages: number;
}



7 changes: 5 additions & 2 deletions src/app/core/modules/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {CommonModule} from '@angular/common';
import {ReactiveFormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {FooterComponent} from '@app/core/components/footer/footer.component';
import {HighLightTextPipe} from "@app/core/pipes/high-light-text.pipe";

@NgModule({
imports: [
Expand All @@ -19,14 +20,16 @@ import {FooterComponent} from '@app/core/components/footer/footer.component';
ContactDetailsContainerComponent,
ContactFormComponent,
ToolbarComponent,
FooterComponent
FooterComponent,
HighLightTextPipe
],
exports: [
ContactListComponent,
ContactDetailsContainerComponent,
ContactFormComponent,
ToolbarComponent,
FooterComponent
FooterComponent,
HighLightTextPipe
]
})
export class SharedModule { }
8 changes: 8 additions & 0 deletions src/app/core/pipes/high-light-text.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HighLightTextPipe } from './high-light-text.pipe';

describe('HighLightTextPipe', () => {
it('create an instance', () => {
const pipe = new HighLightTextPipe();
expect(pipe).toBeTruthy();
});
});
15 changes: 15 additions & 0 deletions src/app/core/pipes/high-light-text.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'highLightText'
})
export class HighLightTextPipe implements PipeTransform {

transform(value: any, args: any): unknown {
if(!args) return value;
const re = new RegExp(args, 'igm');
value= value.replace(re, '<strong >$&</strong>');

return value;
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div [formGroup]="searchFormGroup" class="form-group">
<input class="form-control" type="text" formControlName="search">
<span *ngIf="searchKeyword$ | async as searchKeyword"> {{searchKeyword}} </span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { ContactSearchComponent } from './contact-search.component';

describe('ContactSearchComponent', () => {
let component: ContactSearchComponent;
let fixture: ComponentFixture<ContactSearchComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ContactSearchComponent ]
})
.compileComponents();
}));

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
45 changes: 45 additions & 0 deletions src/app/views/contacts/contact-search/contact-search.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Component, OnInit, ChangeDetectionStrategy, Output, EventEmitter} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';
import {Observable, of} from 'rxjs';
import {debounceTime, filter, map, switchMap, tap} from 'rxjs/operators';


@Component({
selector: 'app-contact-search',
templateUrl: './contact-search.component.html',
styleUrls: ['./contact-search.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ContactSearchComponent implements OnInit {

@Output() searchText = new EventEmitter<string>();

constructor() {
}

searchKeyword$: Observable<string>;
search = new FormControl();

searchFormGroup = new FormGroup({
search: this.search
});

ngOnInit(): void {
this.searchKeyword();
}


searchKeyword(): void {
this.searchKeyword$ = this.search.valueChanges.pipe(
debounceTime(500),
filter(num => num.length >= 2 || num.length === 0 ),
tap( word => {
console.log(word);
this.searchText.emit(word);
} ),
);
}



}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<h1> Contact list</h1>
<h3>Search Contact</h3>
<app-contact-search (searchText)="filterContact($event)"></app-contact-search>




<app-contact-list [contacts]="contacts$ | async"
<h3>Contact list</h3>
<app-contact-list [contacts]="newContacts$ | async"
[keyword] ="hightLightText"
(show)="showContact($event)"
(remove)="deleteContact($event)"
(edit)="editContact($event)"
></app-contact-list>



49 changes: 43 additions & 6 deletions src/app/views/contacts/contacts-index/contacts-index.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Contact } from '@app/core/models';
import { Router } from '@angular/router';
import { ContactsStoreFacade } from '@app/contacts-store/contacts.store-facade';
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import {Contact} from '@app/core/models';
import {Router} from '@angular/router';
import {ContactsStoreFacade} from '@app/contacts-store/contacts.store-facade';
import {Observable, of} from 'rxjs';
import {catchError, map, shareReplay, tap} from 'rxjs/operators';


@Component({
Expand All @@ -12,11 +14,46 @@ import { ContactsStoreFacade } from '@app/contacts-store/contacts.store-facade';
})
export class ContactsIndexComponent implements OnInit {

hightLightText: string = null;
contacts$ = this.contactsFacade.contacts$;
newContacts$: Observable<Contact[]>;

constructor(private contactsFacade: ContactsStoreFacade, private router: Router) { }
constructor(private contactsFacade: ContactsStoreFacade, private router: Router) {
}

ngOnInit() {
this.filterContact(null);
}


//filter contact logic
filterContact(keyword: string): void {
if (!keyword) {
this.hightLightText = '';
this.newContacts$ = this.contacts$.pipe(
map(results => {
results.pop();
return results;
} )
);
return;
}

this.hightLightText = keyword;
const keywordFormatted = keyword.toLowerCase();
this.newContacts$ = this.contacts$.pipe(
map(results => results.filter(
item => item.first_name.toLowerCase().includes(keywordFormatted)
|| item.last_name.toLowerCase().includes(keywordFormatted)
|| item.email.toLowerCase().includes(keywordFormatted)
)),
catchError(e => {
return of([]);
})
);

}

ngOnInit() {}

editContact(contact: Contact) {
this.router.navigate(['/contacts', contact.id, 'edit']);
Expand Down
Loading