diff --git a/.github/workflows/CI-time-tracker-ui.yml b/.github/workflows/CI-time-tracker-ui.yml index 41d115b9c..1c4a7fbd6 100644 --- a/.github/workflows/CI-time-tracker-ui.yml +++ b/.github/workflows/CI-time-tracker-ui.yml @@ -1,13 +1,6 @@ name: CI process for time-tracker app on: - # Trigger the workflow on push or pull request - push: - branches: - - '*' - - '*/*' - - '!master' - pull_request: types: [opened, edited, reopened, synchronize] branches: diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 65da222c8..1d6ff648a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,25 +1,21 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { GettingStartedComponent } from './components/options-sidebar/getting-started/getting-started.component'; import { ReportsComponent } from './components/options-sidebar/reports/reports.component'; import { TimeClockComponent } from './components/options-sidebar/time-clock/time-clock.component'; import { TimeEntriesComponent } from './components/options-sidebar/time-entries/time-entries.component'; -import { TimeOffComponent } from './components/options-sidebar/time-off/time-off.component'; import { ProjectManagementComponent } from './components/options-sidebar/project-management/project-management.component'; const routes: Routes = [ - {path: 'getting-started', component: GettingStartedComponent}, {path: 'reports', component: ReportsComponent}, {path: 'time-clock', component: TimeClockComponent}, {path: 'time-entries', component: TimeEntriesComponent}, - {path: 'time-off', component: TimeOffComponent}, {path: 'project-management', component: ProjectManagementComponent}, - {path: '', pathMatch: 'full', redirectTo: 'getting-started'}, - {path: '**', pathMatch: 'full', redirectTo: 'getting-started'}, + {path: '', pathMatch: 'full', redirectTo: 'time-clock'}, + {path: '**', pathMatch: 'full', redirectTo: 'time-clock'}, ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true })], + imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b611dc7bc..b39b40bbd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -5,7 +5,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; - import { AppComponent } from './app.component'; import { NavbarComponent } from './components/shared/navbar/navbar.component'; import { UserComponent } from './components/shared/user/user.component'; @@ -13,11 +12,17 @@ import { SidebarComponent } from './components/shared/sidebar/sidebar.component' import { ClockComponent } from './components/shared/clock/clock.component'; import { TimeClockComponent } from './components/options-sidebar/time-clock/time-clock.component'; import { ProjectManagementComponent } from './components/options-sidebar/project-management/project-management.component'; +import { TimeEntriesComponent } from './components/options-sidebar/time-entries/time-entries.component'; import { ProjectListComponent } from './components/shared/project-list/project-list.component'; import { CreateProjectComponent } from './components/shared/create-project/create-project.component'; import { DetailsFieldsComponent } from './components/shared/details-fields/details-fields.component'; import { ProjectListHoverComponent } from './components/shared/project-list-hover/project-list-hover.component'; import { ModalComponent } from './components/shared/modal/modal.component'; +import { MonthPickerComponent } from './components/shared/month-picker/month-picker.component'; +import { EmptyStateComponent } from './components/shared/empty-state/empty-state.component'; +import { GroupByDatePipe } from './components/shared/pipes/group-by-date/group-by-date.pipe'; + + @NgModule({ declarations: [ AppComponent, @@ -32,7 +37,11 @@ import { ModalComponent } from './components/shared/modal/modal.component'; TimeClockComponent, DetailsFieldsComponent, ProjectListHoverComponent, - ModalComponent + ModalComponent, + TimeEntriesComponent, + MonthPickerComponent, + EmptyStateComponent, + GroupByDatePipe ], imports: [ CommonModule, @@ -46,4 +55,3 @@ import { ModalComponent } from './components/shared/modal/modal.component'; bootstrap: [AppComponent] }) export class AppModule { } - diff --git a/src/app/components/options-sidebar/getting-started/getting-started.component.css b/src/app/components/options-sidebar/getting-started/getting-started.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/components/options-sidebar/getting-started/getting-started.component.html b/src/app/components/options-sidebar/getting-started/getting-started.component.html deleted file mode 100644 index 1dcdc1e99..000000000 --- a/src/app/components/options-sidebar/getting-started/getting-started.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -

getting-started works!

- -
\ No newline at end of file diff --git a/src/app/components/options-sidebar/getting-started/getting-started.component.spec.ts b/src/app/components/options-sidebar/getting-started/getting-started.component.spec.ts deleted file mode 100644 index bff58d1a2..000000000 --- a/src/app/components/options-sidebar/getting-started/getting-started.component.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { GettingStartedComponent } from './getting-started.component'; - -describe('GettingStartedComponent', () => { - let component: GettingStartedComponent; - let fixture: ComponentFixture; - - function setup() { - // tslint:disable-next-line: no-shadowed-variable - const fixture = TestBed.createComponent(GettingStartedComponent); - const app = fixture.debugElement.componentInstance; - return { fixture, app }; - } - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ GettingStartedComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(GettingStartedComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should be created', () => { - expect(component).toBeTruthy(); - }); - - it('should have p tag as \'getting-started works!\'', async(() => { - // tslint:disable-next-line: no-shadowed-variable - const { app, fixture } = setup(); - fixture.detectChanges(); - const compile = fixture.debugElement.nativeElement; - const ptag = compile.querySelector('p'); - expect(ptag.textContent).toBe('getting-started works!'); - })); - - -}); diff --git a/src/app/components/options-sidebar/getting-started/getting-started.component.ts b/src/app/components/options-sidebar/getting-started/getting-started.component.ts deleted file mode 100644 index 91d9ed409..000000000 --- a/src/app/components/options-sidebar/getting-started/getting-started.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-getting-started', - templateUrl: './getting-started.component.html', - styleUrls: ['./getting-started.component.css'] -}) -export class GettingStartedComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/src/app/components/options-sidebar/project-management/project-management.component.spec.ts b/src/app/components/options-sidebar/project-management/project-management.component.spec.ts index 1674699b8..642bcb2fa 100644 --- a/src/app/components/options-sidebar/project-management/project-management.component.spec.ts +++ b/src/app/components/options-sidebar/project-management/project-management.component.spec.ts @@ -1,11 +1,12 @@ import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + import { ProjectManagementComponent } from './project-management.component'; -import { Project } from '../../../interfaces/project'; +import { Project } from '../../../interfaces'; import { ProjectService } from '../../../services/project.service'; import { of } from 'rxjs'; import { CreateProjectComponent } from '../../../components/shared/create-project/create-project.component'; import { ProjectListComponent } from '../../../components/shared/project-list/project-list.component'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; describe('ProjectManagementComponent', () => { let component: ProjectManagementComponent; @@ -13,21 +14,21 @@ describe('ProjectManagementComponent', () => { let projectService: ProjectService; const projects: Project[] = [{ - id: 1, + id: '1', name: 'app 1', details: 'It is a good app', status: 'inactive', completed: true }, { - id: 2, + id: '2', name: 'app 2', details: 'It is a good app', status: 'inactive', completed: false }, { - id: 3, + id: '3', name: 'app 3', details: 'It is a good app', status: 'active', @@ -89,14 +90,14 @@ describe('ProjectManagementComponent', () => { it('should edit a project #updateProject', () => { const project = { - id: 1, + id: '1', name: 'app test', details: 'It is a excelent app', status: 'inactive', completed: true }; - component.editedProjectId = 1; + component.editedProjectId = '1'; component.updateProject(project); expect(component.projects.length).toEqual(3); expect(component.projects[0].name).toBe('app test'); @@ -106,14 +107,14 @@ describe('ProjectManagementComponent', () => { }); it('should filter the project to edit #editProject', () => { - const editProjectId = 2; + const editProjectId = '2'; component.editProject(editProjectId); expect(component.project.name).toBe('app 2'); }); it('should clean the project #cancelForm', () => { const project = { - id: 1, + id: '1', name: 'app 1', details: 'It is a good app', status: 'inactive', @@ -149,7 +150,7 @@ describe('ProjectManagementComponent', () => { })); it('should delete a project #deleteProject', () => { - const projectId = 1; + const projectId = '1'; component.deleteProject(projectId); expect(component.projects.length).toEqual(2); diff --git a/src/app/components/options-sidebar/project-management/project-management.component.ts b/src/app/components/options-sidebar/project-management/project-management.component.ts index f11ec63fb..bb8051352 100644 --- a/src/app/components/options-sidebar/project-management/project-management.component.ts +++ b/src/app/components/options-sidebar/project-management/project-management.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { Project } from '../../../interfaces/project'; +import { Project } from '../../../interfaces'; import { ProjectService } from '../../../services/project.service'; @Component({ @@ -30,7 +30,7 @@ export class ProjectManagementComponent implements OnInit { this.projects[projectIndex].status = projectData.status; this.projects[projectIndex].completed = projectData.completed; } else { - const newProject: Project = { id: this.projects.length + 1, name: projectData.name, + const newProject: Project = { id: (this.projects.length + 1).toString(), name: projectData.name, details: projectData.details, status: projectData.status, completed: false }; this.projects = this.projects.concat(newProject); diff --git a/src/app/components/options-sidebar/time-clock/time-clock.component.css b/src/app/components/options-sidebar/time-clock/time-clock.component.css index 0e808f74e..a3d8d5059 100644 --- a/src/app/components/options-sidebar/time-clock/time-clock.component.css +++ b/src/app/components/options-sidebar/time-clock/time-clock.component.css @@ -1,10 +1,10 @@ .content-ClockIn { - padding: 2.1rem 1rem; + padding: 2.1rem 1rem; } .timer { - align-items: center; - display: flex; - height: 100%; - justify-content: center; -} + align-items: center; + display: flex; + height: 100%; + justify-content: center; +} \ No newline at end of file diff --git a/src/app/components/options-sidebar/time-clock/time-clock.component.html b/src/app/components/options-sidebar/time-clock/time-clock.component.html index 86009b891..e3a306492 100644 --- a/src/app/components/options-sidebar/time-clock/time-clock.component.html +++ b/src/app/components/options-sidebar/time-clock/time-clock.component.html @@ -1,85 +1,91 @@
- + -
-
-
-
- Time clock -
-
- -
-
+
+
+
+
+ Time clock +
+
+
+
+
-
-

{{ username }} clocked in at {{ clockInUsername }}

-

{{ username }} clocked out at {{ clockOutUsername }}

-
Totals
-
-
-
-
Current
-

{{ hour | number: '2.0-2' }}:{{ minute | number: '2.0-2' }}:{{ seconds | number: '2.0-2' }}

-
-
-
Day
-

4:22

-
-
-
Week
-

14:00

-
-
-
Projects
-
-
- -
-
-

Top

-
    - -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
-
-
- -
-
- - -
-
-
+
+

{{ username }} clocked in at {{ clockInUsername }}

+

{{ username }} clocked out at {{ clockOutUsername }}

+
Totals
+
+
+
+
Current
+

{{ hour | number: '2.0-2' }}:{{ minute | number: '2.0-2' }}:{{ seconds | number: '2.0-2' }}

+
+
+
Day
+

4:22

+
+
+
Week
+

14:00

+
+
+
Projects
+
+
+ +
+
+

Top

+
    + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
-
\ No newline at end of file +
+
diff --git a/src/app/components/options-sidebar/time-clock/time-clock.component.spec.ts b/src/app/components/options-sidebar/time-clock/time-clock.component.spec.ts index 4927e4433..0d045af13 100644 --- a/src/app/components/options-sidebar/time-clock/time-clock.component.spec.ts +++ b/src/app/components/options-sidebar/time-clock/time-clock.component.spec.ts @@ -1,6 +1,6 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { DebugElement } from '@angular/core'; +import { DebugElement, Component } from '@angular/core'; import { TimeClockComponent } from './time-clock.component'; import { ProjectListHoverComponent } from '../../shared/project-list-hover/project-list-hover.component'; @@ -10,7 +10,6 @@ describe('TimeClockComponent', () => { let de: DebugElement; function setup() { - // tslint:disable-next-line: no-shadowed-variable const fixture = TestBed.createComponent(TimeClockComponent); const app = fixture.debugElement.componentInstance; return { fixture, app }; @@ -33,13 +32,12 @@ describe('TimeClockComponent', () => { expect(component).toBeTruthy(); }); - it('should have p tag as \'Dario clocked out at hh:mm:ss\'', async(() => { - // tslint:disable-next-line: no-shadowed-variable - const { app, fixture } = setup(); + it('should have p tag as \'Dario clocked out at 00:00:00\'', async(() => { + const { fixture } = setup(); fixture.detectChanges(); const compile = fixture.debugElement.nativeElement; const ptag = compile.querySelector('p'); - expect(ptag.textContent).toBe('Dario clocked out at hh:mm:ss'); + expect(ptag.textContent).toBe('Dario clocked out at 00:00:00'); })); it('should set showfileds as true', () => { @@ -63,4 +61,45 @@ describe('TimeClockComponent', () => { li.nativeElement.click(); expect(component.setShowFields).toHaveBeenCalledWith(true); }); + + it('should have button text as Options', async(() => { + const { fixture } = setup(); + fixture.detectChanges(); + const x = document.getElementById('optionsContainer'); + const ptag = x.querySelector('button'); + expect(ptag.textContent).toBe(' Options '); + })); + + it('should set Clock In', () => { + const { fixture } = setup(); + fixture.detectChanges(); + const x = document.getElementById('clockInOutContainer'); + const ptag = x.querySelector('button'); + expect(ptag.textContent).toBe('Clock In'); + }); + + it('should setVartToEmpty called', () => { + spyOn(component, 'setDefaultValuesToFields'); + component.setDefaultValuesToFields(); + expect(component.setDefaultValuesToFields).toHaveBeenCalled(); + }); + + it('should employeClockIn called', () => { + spyOn(component, 'employeClockIn'); + component.employeClockIn(); + expect(component.employeClockIn).toHaveBeenCalled(); + }); + + it('should employeClockOut called', () => { + spyOn(component, 'employeClockOut'); + component.employeClockOut(); + expect(component.employeClockOut).toHaveBeenCalled(); + }); + + it('should enterTechnolofy called', () => { + spyOn(component, 'enterTechnology'); + component.enterTechnology(''); + expect(component.enterTechnology).toHaveBeenCalled(); + }); + }); diff --git a/src/app/components/options-sidebar/time-clock/time-clock.component.ts b/src/app/components/options-sidebar/time-clock/time-clock.component.ts index 674c774e8..a38b5aa42 100644 --- a/src/app/components/options-sidebar/time-clock/time-clock.component.ts +++ b/src/app/components/options-sidebar/time-clock/time-clock.component.ts @@ -15,29 +15,42 @@ export class TimeClockComponent implements OnInit { { id: 'P4', name: 'Project 4' } ]; + currentDate: Date = new Date(); username = 'Dario'; - clockInUsername = 'hh:mm:ss'; - clockOutUsername = 'hh:mm:ss'; isClockIn: boolean; isEnterTechnology: boolean; showAlertEnterTecnology: boolean; - + showFields: boolean; + hourCounterRealTime: number; + minuteCounterRealTime: number; + secondsCounterRealTime: number; hour: number; minute: number; seconds: number; - - showFields: boolean; + interval; + dataTechnology: string[] = new Array(); + execOnlyOneTimeCounter = false; + execOnlyOneTimeClockIn = false; + isClockInEnable = false; + isHidenForm = true; constructor() { this.isClockIn = true; this.isEnterTechnology = false; + this.hourCounterRealTime = 0; + this.minuteCounterRealTime = 0; + this.secondsCounterRealTime = 0; this.hour = 0; this.minute = 0; this.seconds = 0; } employeClockIn(): boolean { + this.isClockInEnable = true; this.isClockIn = !this.isClockIn; + this.isHidenForm = false; + this.startTimer(); + this.setArrivalAndDepartureTimes(); return this.isClockIn; } @@ -46,9 +59,9 @@ export class TimeClockComponent implements OnInit { this.isClockIn = false; this.showAlertEnterTecnology = true; } else { - this.isClockIn = true; - this.isEnterTechnology = false; - this.showAlertEnterTecnology = false; + this.setDefaultValuesToFields(); + this.pauseTimer(); + this.setArrivalAndDepartureTimes(); } } @@ -61,10 +74,62 @@ export class TimeClockComponent implements OnInit { } setShowFields(show: boolean) { - this.isClockIn = false; - this.showFields = show; + this.isHidenForm = false; + if ( this.isClockInEnable !== true ) { + this.isClockIn = false; + this.showFields = show; + if ( !this.execOnlyOneTimeCounter ) { + this.startTimer(); + this.execOnlyOneTimeCounter = true; + } + this.setArrivalAndDepartureTimes(); + } + } + + startTimer() { + this.interval = setInterval(() => { + this.timer(); + }, 1000 ); + } + + pauseTimer() { + clearInterval(this.interval); + } + + timer() { + this.secondsCounterRealTime += 1; + if ( this.secondsCounterRealTime === 59 ) { + this.minuteCounterRealTime += 1; + this.secondsCounterRealTime = 0; + if ( this.minuteCounterRealTime === 59 ) { + this.hourCounterRealTime += 1; + this.minuteCounterRealTime = 0; + } + } + } + + setArrivalAndDepartureTimes() { + if ( !this.execOnlyOneTimeClockIn ) { + this.currentDate = new Date(); + this.hour = this.currentDate.getHours(); + this.minute = this.currentDate.getMinutes(); + this.seconds = this.currentDate.getSeconds(); + this.execOnlyOneTimeClockIn = true; + } + } - ngOnInit(): void {} + setDefaultValuesToFields() { + this.isHidenForm = true; + this.isClockIn = true; + this.isEnterTechnology = false; + this.showAlertEnterTecnology = false; + this.execOnlyOneTimeClockIn = false; + this.execOnlyOneTimeCounter = false; + this.isClockInEnable = false; + } + + ngOnInit(): void { + } } diff --git a/src/app/components/options-sidebar/time-entries/time-entries.component.css b/src/app/components/options-sidebar/time-entries/time-entries.component.css index e69de29bb..4b6e05eb5 100644 --- a/src/app/components/options-sidebar/time-entries/time-entries.component.css +++ b/src/app/components/options-sidebar/time-entries/time-entries.component.css @@ -0,0 +1,46 @@ +.container-time-entries { + padding: 1rem; +} + +.header-entries { + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.accordion-container { + max-height: 25rem; + overflow-y: auto; +} + +.accordion-container::-webkit-scrollbar { + display: none; +} + +.date-header { + background-color: #e6e6e6; + border-radius: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); + font-size: small; + padding: 0 0.9rem; +} + +.date-header > a { + color: #000000; +} + +.btn-small > i { + font-size: 0.8rem; + opacity: 0.7; +} + +.entries { + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); + display: flex; + font-size: small; +} + +.entries:hover { + background-color: #d5edf0; + cursor: pointer; +} diff --git a/src/app/components/options-sidebar/time-entries/time-entries.component.html b/src/app/components/options-sidebar/time-entries/time-entries.component.html index f4ee1f815..401cb29e0 100644 --- a/src/app/components/options-sidebar/time-entries/time-entries.component.html +++ b/src/app/components/options-sidebar/time-entries/time-entries.component.html @@ -1,5 +1,66 @@ -
+
+
+
Time Entries
+ +
+
Project
+
Duration
+
Actions
+
+ +
+
+ + -

time-entries works!

- -
\ No newline at end of file + +
+
+
+ {{ item.project }} +
+
+ {{ item.duration }} +
+
+ + +
+
+
+
+
+ + + +
+
+ + diff --git a/src/app/components/options-sidebar/time-entries/time-entries.component.spec.ts b/src/app/components/options-sidebar/time-entries/time-entries.component.spec.ts index 304646888..fddcd9cac 100644 --- a/src/app/components/options-sidebar/time-entries/time-entries.component.spec.ts +++ b/src/app/components/options-sidebar/time-entries/time-entries.component.spec.ts @@ -1,10 +1,26 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TimeEntriesComponent } from './time-entries.component'; +import { GroupByDatePipe } from '../../shared/pipes/group-by-date/group-by-date.pipe'; +import { MonthPickerComponent } from '../../shared/month-picker/month-picker.component'; +import { DetailsFieldsComponent } from '../../shared/details-fields/details-fields.component'; +import { EmptyStateComponent } from '../../shared/empty-state/empty-state.component'; +import { ModalComponent } from '../../shared/modal/modal.component'; describe('TimeEntriesComponent', () => { let component: TimeEntriesComponent; let fixture: ComponentFixture; + const entry = { + id: 'entry_1', + project: 'Mido - 05 de Febrero', + startDate: '2020-02-05T15:36:15.887Z', + endDate: '2020-02-05T18:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-25' + }; function setup() { // tslint:disable-next-line: no-shadowed-variable @@ -15,7 +31,18 @@ describe('TimeEntriesComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ TimeEntriesComponent ] + declarations: [ + EmptyStateComponent, + DetailsFieldsComponent, + GroupByDatePipe, + ModalComponent, + MonthPickerComponent, + TimeEntriesComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule + ] }) .compileComponents(); })); @@ -39,4 +66,46 @@ describe('TimeEntriesComponent', () => { expect(ptag.textContent).toBe('time-entries works!'); })); -}); \ No newline at end of file + it('should call dataByMonth in ngOnInit()', async(() => { + component.ngOnInit(); + expect(component.dataByMonth.length).toEqual(3); + })); + + it('should open Delete Modal', () => { + component.openModal(entry); + expect(component.entryToDelete).toBe(entry); + expect(component.showModal).toBe(true); + }); + + it('should filter the Entry to edit', () => { + const entryId = "entry_1" + component.editEntry(entryId); + expect(component.entry.project).toBe(entry.project); + expect(component.entry.startDate).toBe(entry.startDate); + expect(component.entry.endDate).toBe(entry.endDate); + expect(component.entry.activity).toBe(entry.activity); + expect(component.entry.technology).toBe(entry.technology); + }); + + it('should save an Entry', () => { + component.entryId = 'entry_1'; + component.saveEntry(entry); + expect(component.entryList[0].project).toBe('Mido - 05 de Febrero'); + expect(component.entryList[0].startDate).toBe('2020-02-05T15:36:15.887Z'); + expect(component.entryList[0].endDate).toBe('2020-02-05T18:36:15.887Z'); + expect(component.entryList[0].activity).toBe('development'); + expect(component.entryList[0].technology).toBe('Angular, TypeScript'); + }); + + it('should delete a Entry', () => { + const entryId = "entry_2"; + component.removeEntry(entryId); + expect(component.dataByMonth.length).toBe(2); + }); + + it('should get the entry List by Month', () => { + const month = 3; + component.getMonth(month); + expect(component.dataByMonth.length).toEqual(1); + }); +}); diff --git a/src/app/components/options-sidebar/time-entries/time-entries.component.ts b/src/app/components/options-sidebar/time-entries/time-entries.component.ts index 17ea41840..55b231c1c 100644 --- a/src/app/components/options-sidebar/time-entries/time-entries.component.ts +++ b/src/app/components/options-sidebar/time-entries/time-entries.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Entry } from '../../../interfaces'; @Component({ selector: 'app-time-entries', @@ -7,9 +8,95 @@ import { Component, OnInit } from '@angular/core'; }) export class TimeEntriesComponent implements OnInit { + showModal: boolean = false; + entryId: string; + entry: Entry; + entryToDelete: Entry; + dataByMonth: Entry[]; + + entryList = [ + { + id: 'entry_1', + project: 'Mido - 05 de Febrero', + startDate: '2020-02-05T15:36:15.887Z', + endDate: '2020-02-05T18:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-25' + }, + { + id: 'entry_2', + project: 'Mido 15 de Marzo', + startDate: '2020-03-15T20:36:15.887Z', + endDate: '2020-03-15T23:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-38' + }, + { + id: 'entry_3', + project: 'GoSpace 15 y 16 de Marzo', + startDate: '2020-03-15T23:36:15.887Z', + endDate: '2020-03-16T05:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-225' + }, + { + id: 'entry_4', + project: 'Mido 16 de Marzo', + startDate: '2020-03-16T15:36:15.887Z', + endDate: '2020-03-16T18:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-89' + }, + { + id: 'entry_5', + project: 'Ernst&Young 01 de Abril', + startDate: '2020-04-01T09:36:15.887Z', + endDate: '2020-04-01T15:36:15.887Z', + activity: 'development', + technology: 'Angular, TypeScript', + comments: 'No comments', + ticket: 'EY-59' + } + ]; + constructor() { } ngOnInit(): void { + this.dataByMonth = this.entryList.filter(entry => new Date(entry.startDate).getMonth() === new Date().getMonth()); + } + + openModal(itemToDelete: Entry) { + this.entryToDelete = itemToDelete; + this.showModal = true; + } + + editEntry(entryId: string) { + this.entryId = entryId; + this.entry = this.entryList.find((entry) => entry.id === entryId); } + saveEntry(newData): void { + const entryIndex = this.entryList.findIndex((entry => entry.id === this.entryId)); + this.entryList[entryIndex].project = newData.project; + this.entryList[entryIndex].activity = newData.activity; + this.entryList[entryIndex].technology = newData.technology; + this.entryList[entryIndex].ticket = newData.jiraTicket; + this.entryList[entryIndex].comments = newData.notes; + } + + removeEntry(entryId: string) { + this.dataByMonth = this.dataByMonth.filter((entry: Entry) => entry.id !== entryId); + } + + getMonth(month: number) { + this.dataByMonth = this.entryList.filter(entry => new Date(entry.startDate).getMonth() === month); + } } diff --git a/src/app/components/options-sidebar/time-off/time-off.component.css b/src/app/components/options-sidebar/time-off/time-off.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/components/options-sidebar/time-off/time-off.component.html b/src/app/components/options-sidebar/time-off/time-off.component.html deleted file mode 100644 index 50aa714a8..000000000 --- a/src/app/components/options-sidebar/time-off/time-off.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -

time-off works!

- -
\ No newline at end of file diff --git a/src/app/components/options-sidebar/time-off/time-off.component.spec.ts b/src/app/components/options-sidebar/time-off/time-off.component.spec.ts deleted file mode 100644 index 8aa6ed544..000000000 --- a/src/app/components/options-sidebar/time-off/time-off.component.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { TimeOffComponent } from './time-off.component'; - -describe('TimeOffComponent', () => { - let component: TimeOffComponent; - let fixture: ComponentFixture; - - function setup() { - // tslint:disable-next-line: no-shadowed-variable - const fixture = TestBed.createComponent(TimeOffComponent); - const app = fixture.debugElement.componentInstance; - return { fixture, app }; - } - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ TimeOffComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TimeOffComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should be created', () => { - expect(component).toBeTruthy(); - }); - - it('should have p tag as \'time-off works!\'', async(() => { - // tslint:disable-next-line: no-shadowed-variable - const { app, fixture } = setup(); - fixture.detectChanges(); - const compile = fixture.debugElement.nativeElement; - const ptag = compile.querySelector('p'); - expect(ptag.textContent).toBe('time-off works!'); - })); -}); diff --git a/src/app/components/options-sidebar/time-off/time-off.component.ts b/src/app/components/options-sidebar/time-off/time-off.component.ts deleted file mode 100644 index e41c61bdc..000000000 --- a/src/app/components/options-sidebar/time-off/time-off.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-time-off', - templateUrl: './time-off.component.html', - styleUrls: ['./time-off.component.css'] -}) -export class TimeOffComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/src/app/components/shared/clock/clock.component.spec.ts b/src/app/components/shared/clock/clock.component.spec.ts index 761908de4..9566bfa6b 100644 --- a/src/app/components/shared/clock/clock.component.spec.ts +++ b/src/app/components/shared/clock/clock.component.spec.ts @@ -1,11 +1,18 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ClockComponent } from './clock.component'; +import { interval, timer } from 'rxjs'; describe('ClockComponent', () => { let component: ClockComponent; let fixture: ComponentFixture; + function setup() { + // tslint:disable-next-line: no-shadowed-variable + const fixture = TestBed.createComponent(ClockComponent); + const app = fixture.debugElement.componentInstance; + return { fixture, app }; + } beforeEach(async(() => { TestBed.configureTestingModule({ @@ -33,5 +40,4 @@ describe('ClockComponent', () => { const currenMinutes = 5; expect(component.currentDate.getMinutes()).toEqual(currenMinutes); }); - }); diff --git a/src/app/components/shared/clock/clock.component.ts b/src/app/components/shared/clock/clock.component.ts index 0eea6b977..ec3ddad30 100644 --- a/src/app/components/shared/clock/clock.component.ts +++ b/src/app/components/shared/clock/clock.component.ts @@ -15,14 +15,14 @@ export class ClockComponent implements OnInit { displayTime: boolean; constructor() { - this.showClcok(); + this.showClock(); this.displayTime = false; setTimeout(() => { this.displayTime = true; }, 3000); } - showClcok() { + showClock() { const timenInterval = interval(1000); timenInterval.subscribe( (data) => { this.currentDate = new Date(); diff --git a/src/app/components/shared/create-project/create-project.component.ts b/src/app/components/shared/create-project/create-project.component.ts index c31de6dbc..389a9a41d 100644 --- a/src/app/components/shared/create-project/create-project.component.ts +++ b/src/app/components/shared/create-project/create-project.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, SimpleChanges } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; -import { Project } from '../../../interfaces/project'; +import { Project } from '../../../interfaces'; import { Output, EventEmitter } from '@angular/core'; import { ProjectListComponent } from '../project-list/project-list.component'; @@ -34,8 +34,10 @@ export class CreateProjectComponent implements OnInit { ngOnChanges(): void { if (this.projectToEdit) { this.editProjectId = this.projectToEdit.id; - this.projectForm.setValue({name: this.projectToEdit.name, details: this.projectToEdit.details, - status: this.projectToEdit.status, completed: this.projectToEdit.completed}); + this.projectForm.setValue({ + name: this.projectToEdit.name, details: this.projectToEdit.details, + status: this.projectToEdit.status, completed: this.projectToEdit.completed + }); } } diff --git a/src/app/components/shared/details-fields/details-fields.component.css b/src/app/components/shared/details-fields/details-fields.component.css index 82869783c..06b6e01f5 100644 --- a/src/app/components/shared/details-fields/details-fields.component.css +++ b/src/app/components/shared/details-fields/details-fields.component.css @@ -1,3 +1,7 @@ .span-width { width: 6rem; } + +.hidden { + display: none; +} diff --git a/src/app/components/shared/details-fields/details-fields.component.html b/src/app/components/shared/details-fields/details-fields.component.html index 7365bc192..fd89befb5 100644 --- a/src/app/components/shared/details-fields/details-fields.component.html +++ b/src/app/components/shared/details-fields/details-fields.component.html @@ -1,44 +1,40 @@ -
+ +
+
+ Project +
+ +
- + Activity
- +
- Jira Ticket + Jira Ticket
- +
Technology
- +
- + +
+
- diff --git a/src/app/components/shared/details-fields/details-fields.component.spec.ts b/src/app/components/shared/details-fields/details-fields.component.spec.ts index 18266ca45..7d7c2019d 100644 --- a/src/app/components/shared/details-fields/details-fields.component.spec.ts +++ b/src/app/components/shared/details-fields/details-fields.component.spec.ts @@ -1,16 +1,35 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DetailsFieldsComponent } from './details-fields.component'; describe('DetailsFieldsComponent', () => { let component: DetailsFieldsComponent; let fixture: ComponentFixture; + const initialData = { + project: '', + activity: '', + ticket: '', + technology: '', + comments: '' + } + + const newData = { + project: 'Ernst&Young', + activity: 'development', + ticket: 'WA-15', + technology: 'Angular', + comments: 'No notes' + } beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ DetailsFieldsComponent ] - }) - .compileComponents(); + declarations: [DetailsFieldsComponent], + imports: [ + FormsModule, + ReactiveFormsModule + ], + }).compileComponents(); })); beforeEach(() => { @@ -22,4 +41,22 @@ describe('DetailsFieldsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should emit saveEntry event', () => { + spyOn(component.saveEntry, 'emit'); + component.onSubmit(); + expect(component.saveEntry.emit).toHaveBeenCalledWith(initialData); + }); + + it('should emit ngOnChange without data', () => { + component.entryToEdit = null; + component.ngOnChanges(); + expect(component.entryForm.value).toEqual(initialData); + }); + + it('should emit ngOnChange with new data', () => { + component.entryToEdit = newData; + component.ngOnChanges(); + expect(component.entryForm.value).toEqual(newData); + }); }); diff --git a/src/app/components/shared/details-fields/details-fields.component.ts b/src/app/components/shared/details-fields/details-fields.component.ts index eea5de70b..93547a015 100644 --- a/src/app/components/shared/details-fields/details-fields.component.ts +++ b/src/app/components/shared/details-fields/details-fields.component.ts @@ -1,4 +1,5 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; @Component({ selector: 'app-details-fields', @@ -6,10 +7,38 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./details-fields.component.css'] }) export class DetailsFieldsComponent implements OnInit { + @Input() entryToEdit; + @Input() formType: string; + @Output() saveEntry = new EventEmitter(); + @ViewChild('closeModal') closeModal: ElementRef; + entryForm: FormGroup; - constructor() { } + constructor(private formBuilder: FormBuilder) { + this.entryForm = this.formBuilder.group({ + project: '', + activity: '', + ticket: '', + technology: '', + comments: '' + }); + } + + ngOnInit(): void { } - ngOnInit(): void { + ngOnChanges(): void { + if (this.entryToEdit) { + this.entryForm.setValue({ + project: this.entryToEdit.project, + activity: this.entryToEdit.activity, + ticket: this.entryToEdit.ticket, + technology: this.entryToEdit.technology, + comments: this.entryToEdit.comments + }); + } } + onSubmit() { + this.saveEntry.emit(this.entryForm.value); + this.closeModal.nativeElement.click(); + } } diff --git a/src/app/components/shared/empty-state/empty-state.component.css b/src/app/components/shared/empty-state/empty-state.component.css new file mode 100644 index 000000000..597443547 --- /dev/null +++ b/src/app/components/shared/empty-state/empty-state.component.css @@ -0,0 +1,8 @@ +.container-empty-state { + opacity: 0.3; + padding: 5rem; +} + +.container-empty-state i { + font-size: 5rem; +} diff --git a/src/app/components/shared/empty-state/empty-state.component.html b/src/app/components/shared/empty-state/empty-state.component.html new file mode 100644 index 000000000..1c536d61e --- /dev/null +++ b/src/app/components/shared/empty-state/empty-state.component.html @@ -0,0 +1,4 @@ +
+ +

no data to display

+
diff --git a/src/app/components/shared/empty-state/empty-state.component.spec.ts b/src/app/components/shared/empty-state/empty-state.component.spec.ts new file mode 100644 index 000000000..c5cd9ed4e --- /dev/null +++ b/src/app/components/shared/empty-state/empty-state.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EmptyStateComponent } from './empty-state.component'; + +describe('EmptyStateComponent', () => { + let component: EmptyStateComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EmptyStateComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EmptyStateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/shared/empty-state/empty-state.component.ts b/src/app/components/shared/empty-state/empty-state.component.ts new file mode 100644 index 000000000..0e58e14a1 --- /dev/null +++ b/src/app/components/shared/empty-state/empty-state.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-empty-state', + templateUrl: './empty-state.component.html', + styleUrls: ['./empty-state.component.css'] +}) +export class EmptyStateComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/components/shared/modal/modal.component.html b/src/app/components/shared/modal/modal.component.html index 8e07addaa..05e9a29e1 100644 --- a/src/app/components/shared/modal/modal.component.html +++ b/src/app/components/shared/modal/modal.component.html @@ -1,5 +1,5 @@ diff --git a/src/app/components/shared/project-list/project-list.component.spec.ts b/src/app/components/shared/project-list/project-list.component.spec.ts index 438257e77..71168eb8a 100644 --- a/src/app/components/shared/project-list/project-list.component.spec.ts +++ b/src/app/components/shared/project-list/project-list.component.spec.ts @@ -25,7 +25,7 @@ describe('ProjectListComponent', () => { it('should emit deleteProject event #removeProject', () => { const project = { - id: 1, + id: '1', name: 'app 4', details: 'It is a good app', status: 'inactive', @@ -41,7 +41,7 @@ describe('ProjectListComponent', () => { it('should open delete modal #openModal', () => { const project = { - id: 1, + id: '1', name: 'app 4', details: 'It is a good app', status: 'inactive', @@ -49,7 +49,7 @@ describe('ProjectListComponent', () => { }; component.openModal(project); - expect(component.projectToDelete.id).toBe(1); + expect(component.projectToDelete.id).toBe('1'); expect(component.projectToDelete.name).toBe('app 4'); expect(component.projectToDelete.details).toBe('It is a good app'); expect(component.projectToDelete.status).toBe('inactive'); diff --git a/src/app/components/shared/project-list/project-list.component.ts b/src/app/components/shared/project-list/project-list.component.ts index 6fc799c95..3c696ccfe 100644 --- a/src/app/components/shared/project-list/project-list.component.ts +++ b/src/app/components/shared/project-list/project-list.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Input } from '@angular/core'; import { Output, EventEmitter } from '@angular/core'; -import { Project } from '../../../interfaces/project'; +import { Project } from '../../../interfaces'; @Component({ selector: 'app-project-list', diff --git a/src/app/components/shared/sidebar/sidebar.component.html b/src/app/components/shared/sidebar/sidebar.component.html index 1a75e8002..c99cc85af 100644 --- a/src/app/components/shared/sidebar/sidebar.component.html +++ b/src/app/components/shared/sidebar/sidebar.component.html @@ -2,12 +2,10 @@ diff --git a/src/app/interfaces/entry.ts b/src/app/interfaces/entry.ts new file mode 100644 index 000000000..62904c0b3 --- /dev/null +++ b/src/app/interfaces/entry.ts @@ -0,0 +1,11 @@ +export interface Entry { + id: string, + project: string, + startDate: string, + endDate: string, + activity: string, + technology: string, + comments?: string, + ticket?: string +} + diff --git a/src/app/interfaces/index.ts b/src/app/interfaces/index.ts new file mode 100644 index 000000000..e41e3fb3d --- /dev/null +++ b/src/app/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './project'; +export * from './entry'; diff --git a/src/app/interfaces/project.ts b/src/app/interfaces/project.ts index 1c7f6c435..d5eb3b8ed 100644 --- a/src/app/interfaces/project.ts +++ b/src/app/interfaces/project.ts @@ -1,7 +1,8 @@ export interface Project { - id: number; + id: string; name: string; details: string; status: string; completed: boolean; } + diff --git a/src/app/services/project.service.spec.ts b/src/app/services/project.service.spec.ts index 3648b5b41..6e36e632b 100644 --- a/src/app/services/project.service.spec.ts +++ b/src/app/services/project.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, inject, async } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { Project } from '../interfaces/project'; +import { Project } from '../interfaces'; import { ProjectService } from './project.service'; describe('ProjectService', () => { @@ -8,21 +8,21 @@ describe('ProjectService', () => { let httpMock: HttpTestingController; const projects: Project[] = [{ - id: 1, + id: '1', name: 'app 1', details: 'It is a good app', status: 'inactive', completed: true }, { - id: 2, + id: '2', name: 'app 2', details: 'It is a good app', status: 'inactive', completed: false }, { - id: 3, + id: '3', name: 'app 3', details: 'It is a good app', status: 'active', diff --git a/src/app/services/project.service.ts b/src/app/services/project.service.ts index 33a2395b9..3e04f2862 100644 --- a/src/app/services/project.service.ts +++ b/src/app/services/project.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Project } from '../interfaces/project'; +import { Project } from '../interfaces'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs';