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: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { CustomerEffects } from './modules/customer-management/store/customer-ma
import { EntryEffects } from './modules/time-clock/store/entry.effects';
import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor';
import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe';
import {TechnologiesComponent} from './modules/shared/components/technologies/technologies.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -92,6 +93,7 @@ import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substra
CreateProjectTypeComponent,
EntryFieldsComponent,
SubstractDatePipe,
TechnologiesComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text span-width">Technology</span>
</div>
<input
(input)="queryTechnologies($event)"
id="technologies"
type="text"
class="form-control"
aria-label="Small"
aria-describedby="inputGroup-sizing-sm"
[(ngModel)]="query"
/>
</div>

<div *ngIf="isLoading">LOADING...</div>
<div #technologiesDropdown *ngIf="technology && showList" class="d-flex flex-column technologies-dropdown-container">
<div
*ngFor="let item of technology.items"
(click)="addTechnology(item.name)"
class="technologies-dropdown-item">
{{ item.name }}
</div>
</div>
<div class="selected-technologies-container d-flex flex-wrap" *ngIf="selectedTechnologies.length">
<div *ngFor="let technology of selectedTechnologies; let tagIndex = index" class="selected-technology">
<span class="mr-3">{{ technology }}</span>
<i class="fas fa-times text-white" (click)="removeTechnology(tagIndex)"></i>
</div>
</div>
<div class="form-group" *ngIf="selectedTechnologies.length === 0">
<!-- empty-space -->
</div>



Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@import '../../../../../styles/colors.scss';

.technologies-dropdown-container {
box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08);
margin: 0 0 2rem 6rem;
max-height: 7.5rem;
overflow-y: auto;

.technologies-dropdown-item {
cursor: pointer;
font-size: 0.8rem;
margin-bottom: 0.1rem;
padding: 0.2rem 0.5rem;

&:hover {
opacity: 0.7;
background-color: #efefef;
}
}
}

.selected-technologies-container {
margin: 0.5rem 0 0.5rem 6rem;
}

.selected-technology {
background-color: $modal-button-secondary;
color: #ffffff;
border-radius: 0.2rem;
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.75);
font-size: 0.8rem;
padding: 0.1rem 1rem 0.2rem 1.5rem;
position: relative;
margin: 0 0.5rem 0.5rem 0;

&:hover {
opacity: 0.8;
}

i {
cursor: pointer;
}
}

.span-width {
width: 6rem;
background-image: $background-pantone;
color: white;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MockStore, provideMockStore} from '@ngrx/store/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';

import {TechnologyState} from '../../store/technology.reducers';
import {allTechnologies} from '../../store/technology.selectors';
import {TechnologiesComponent} from './technologies.component';
import * as actions from '../../store/technology.actions';
import {ProjectState} from '../../../customer-management/components/projects/components/store/project.reducer';

describe('Technologies component', () => {
let component: TechnologiesComponent;
let fixture: ComponentFixture<TechnologiesComponent>;
let store: MockStore<TechnologyState>;
let mockTechnologySelector;

const state = {
technologies: {
technologyList: {items: [{name: 'java'}]},
isLoading: false,
}
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TechnologiesComponent],
providers: [provideMockStore({initialState: state})],
imports: [FormsModule, ReactiveFormsModule],
}).compileComponents();
store = TestBed.inject(MockStore);
mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies);
}));

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

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

it('when a new technology is added, it should be added to the selectedTechnologies list', () => {
const name = 'ngrx';
component.selectedTechnologies = ['java', 'javascript'];
component.selectedTechnologies.indexOf(name);
length = component.selectedTechnologies.length;
component.addTechnology(name);
expect(component.selectedTechnologies.length).toBe(3);
});

it('when the max number of technologies is reached, then adding technologies is not allowed', () => {
const name = 'ngrx';
component.selectedTechnologies = [
'java',
'javascript',
'angular',
'angular-ui',
'typescript',
'scss',
'bootstrap',
'jasmine',
'karma',
'github',
];
length = component.selectedTechnologies.length;
component.addTechnology(name);
expect(component.selectedTechnologies.length).toBe(10);
});

it('when a technology is removed, then it should be removed from the technologies list', () => {
const index = 1;
component.selectedTechnologies = ['java', 'angular'];
component.removeTechnology(index);
expect(component.selectedTechnologies.length).toBe(1);
});

it('when querying technologies, then a FindTechnology action should be dispatched', () => {
const query = 'react';
const target = {value: query};
const event = new InputEvent('input');
spyOnProperty(event, 'target').and.returnValue(target);
spyOn(store, 'dispatch');
component.queryTechnologies(event);

expect(store.dispatch).toHaveBeenCalledWith(new actions.FindTechnology(query));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2, ViewChild} from '@angular/core';
import * as actions from '../../store/technology.actions';
import {select, Store} from '@ngrx/store';
import {TechnologyState} from '../../store/technology.reducers';
import {allTechnologies} from '../../store/technology.selectors';
import {Technology} from '../../models';


@Component({
selector: 'app-technologies',
templateUrl: './technologies.component.html',
styleUrls: ['./technologies.component.scss']
})
export class TechnologiesComponent implements OnInit {
private readonly MAX_NUM_TECHNOLOGIES = 10;
private readonly MIN_QUERY_LENGTH = 2;
public query = '';
showList = false;
isLoading = false;
technology: Technology;

@Input()
selectedTechnologies: string[] = [];
@ViewChild('technologiesDropdown') list: ElementRef;
@Output()
technologyRemoved: EventEmitter<string[]> = new EventEmitter<string[]>();
@Output()
technologyAdded: EventEmitter<string[]> = new EventEmitter<string[]>();

constructor(private store: Store<TechnologyState>, private renderer: Renderer2) {
this.renderer.listen('window', 'click', (e: Event) => {
if (this.showList && !this.list.nativeElement.contains(e.target)) {
this.showList = false;
}
});
}

queryTechnologies(event: Event) {
const inputElement: HTMLInputElement = (event.target) as HTMLInputElement;
const query: string = inputElement.value;
if (query.length >= this.MIN_QUERY_LENGTH) {
this.showList = true;
this.store.dispatch(new actions.FindTechnology(query));
}
}

ngOnInit(): void {
const technologies$ = this.store.pipe(select(allTechnologies));
technologies$.subscribe((response) => {
this.isLoading = response.isLoading;
const filteredItems = response.technologyList.items.filter(item => !this.selectedTechnologies.includes(item.name));
this.technology = {items: filteredItems};
});
}

addTechnology(name: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't want to display already selected technologies in the component @juanultimate. Only not selected technologies should be displayed in the component.

const index = this.selectedTechnologies.indexOf(name);
if (index > -1) {
this.removeTechnology(index);
} else if (this.selectedTechnologies.length < this.MAX_NUM_TECHNOLOGIES) {
this.selectedTechnologies = [...this.selectedTechnologies, name];
this.technologyAdded.emit(this.selectedTechnologies);
this.showList = false;
this.query = '';
}
}

removeTechnology(index: number) {
this.selectedTechnologies = this.selectedTechnologies.filter((item) => item !== this.selectedTechnologies[index]);
this.technologyRemoved.emit(this.selectedTechnologies);
}
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,23 @@
<form [formGroup]="entryForm" (ngSubmit)="onSubmit()">
<div class="input-group input-group-sm mb-3">
<div class="input-group-prepend">
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Activity</span>
<span class="input-group-text span-width">Activity</span>
</div>
<select id="activitiesSelect" (blur)="onSubmit()" class="form-control">
<option *ngFor="let activity of activities">{{ activity.name }}</option>
</select>
</div>
<div class="input-group input-group-sm mb-3">
<div class="input-group-prepend">
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Ticket URI</span>
<span class="input-group-text span-width">Ticket URI</span>
</div>
<input (blur)="onSubmit()" type="text" id="uri" formControlName="uri" class="form-control" aria-label="Small" aria-describedby="inputGroup-sizing-sm" />
</div>

<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text span-width" id="inputGroup-sizing-sm">Technology</span>
</div>
<input
(keyup)="getTechnologies($event.target.value)"
id="technology"
formControlName="technology"
type="text"
class="form-control"
aria-label="Small"
aria-describedby="inputGroup-sizing-sm"
/>
</div>

<div *ngIf="isLoading">LOADING...</div>
<div #list *ngIf="technology && showlist" class="d-flex flex-column technology-content">
<div
*ngFor="let item of technology.items"
(click)="setTechnology(item.name)"
class="technology-list"
[ngClass]="{ active: selectedTechnology && selectedTechnology.includes(item.name) }"
>
{{ item.name }}
</div>
</div>
<div class="tags-content d-flex flex-wrap" *ngIf="selectedTechnology.length">
<div *ngFor="let technology of selectedTechnology; let tagIndex = index" class="tag">
<span class="mr-3">{{ technology }}</span>
<i class="fas fa-times text-white" (click)="removeTag(tagIndex)"></i>
</div>
</div>

<div class="form-group" *ngIf="selectedTechnology.length === 0">
<!-- empty-space -->
<input (blur)="onSubmit()" type="text" id="uri" formControlName="uri" class="form-control" aria-label="Small"
aria-describedby="inputGroup-sizing-sm"/>
</div>
<app-technologies (technologyAdded)="onTechnologyAdded($event)"
(technologyRemoved)="onTechnologyRemoved($event)"
[selectedTechnologies]="selectedTechnologies">
</app-technologies>

<div class="form-group text-left">
<label for="NotesTextarea">Description</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
@import '../../../../../styles/colors.scss';

@mixin tagTechnology() {
background-color: $modal-button-secondary;
color: #ffffff;

&:hover {
opacity: 0.8;
}
}

.span-width {
width: 6rem;
background-image: $background-pantone;
Expand All @@ -29,47 +20,6 @@
color: white;
}

.technology-content {
box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.08);
margin: 0 0 2rem 6rem;
max-height: 7.5rem;
overflow-y: auto;

.technology-list {
cursor: pointer;
font-size: 0.8rem;
margin-bottom: 0.1rem;
padding: 0.2rem 0.5rem;

&:hover {
opacity: 0.7;
}
}

.active {
background-color: #efefef;
}
}

.tags-content {
margin: 2rem 0;

div {
@include tagTechnology();

border-radius: 0.2rem;
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.75);
font-size: 0.8rem;
padding: 0.1rem 1rem 0.2rem 1.5rem;
position: relative;
margin: 0 0.5rem 0.5rem 0;

i {
cursor: pointer;
}
}
}

.ng-autocomplete {
width: 100%;
}
Expand Down
Loading