diff --git a/Phonebook.Frontend/src/app/app-routing.module.ts b/Phonebook.Frontend/src/app/app-routing.module.ts index 55446a0f2..9076c6b50 100644 --- a/Phonebook.Frontend/src/app/app-routing.module.ts +++ b/Phonebook.Frontend/src/app/app-routing.module.ts @@ -1,11 +1,18 @@ import { NgModule } from '@angular/core'; import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; -import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component'; +import { DashboardComponent } from 'src/app/pages/dashboard/components/dashboard/dashboard.component'; import { SettingsComponent } from 'src/app/pages/settings/settings.component'; import { environment } from 'src/environments/environment'; +import { TeamComponent } from './pages/dashboard/components/team/team.component'; +import { BookmarkedComponent } from './pages/dashboard/components/bookmarked/bookmarked.component'; const routes: Routes = [ - { path: '', component: DashboardComponent, pathMatch: 'full' }, + { path: '', redirectTo: 'dashboard/bookmarks', pathMatch: 'full' }, + { + path: 'dashboard', + loadChildren: () => + import('src/app/pages/dashboard/dashboard.module').then((m) => m.DashboardModule), + }, { path: 'search', loadChildren: () => import('src/app/modules/table/table.module').then((m) => m.TableModule), diff --git a/Phonebook.Frontend/src/app/app.module.ts b/Phonebook.Frontend/src/app/app.module.ts index 92cc82d68..ba59f69f2 100644 --- a/Phonebook.Frontend/src/app/app.module.ts +++ b/Phonebook.Frontend/src/app/app.module.ts @@ -20,7 +20,7 @@ import { FeatureFlagModule } from 'src/app/modules/feature-flag/feature-flag.mod import { NotImplementedModule } from 'src/app/modules/not-implemented/not-implemented.module'; import { ProfilePictureModule } from 'src/app/modules/profile-picture/profile-picture.module'; import { TableModule } from 'src/app/modules/table/table.module'; -import { DashboardComponent } from 'src/app/pages/dashboard/dashboard.component'; +import { DashboardComponent } from 'src/app/pages/dashboard/components/dashboard/dashboard.component'; import { SettingsModule } from 'src/app/pages/settings/settings.module'; import { UserPagesModule } from 'src/app/pages/users/user-pages.module'; import { ApiModule } from 'src/app/services/api/api.module'; @@ -58,13 +58,7 @@ import { FormsModule } from '@angular/forms'; declare const require; @NgModule({ - declarations: [ - AppComponent, - SearchComponent, - DashboardComponent, - NavigationComponent, - OnlineBarComponent, - ], + declarations: [AppComponent, SearchComponent, NavigationComponent, OnlineBarComponent], imports: [ BrowserModule, AppRoutingModule, diff --git a/Phonebook.Frontend/src/app/modules/organigram/pages/organigram/organigram.component.ts b/Phonebook.Frontend/src/app/modules/organigram/pages/organigram/organigram.component.ts index 453a8a0ca..156f597fc 100644 --- a/Phonebook.Frontend/src/app/modules/organigram/pages/organigram/organigram.component.ts +++ b/Phonebook.Frontend/src/app/modules/organigram/pages/organigram/organigram.component.ts @@ -13,7 +13,7 @@ export class OrganigramComponent implements OnInit { constructor(private organigramService: OrganigramService) {} public ngOnInit() { - this.organigramService.getOrganigram().subscribe((organigram) => { + this.organigramService.getOrganigramTree().subscribe((organigram) => { this.nodes = organigram; }); } diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.html b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.html new file mode 100644 index 000000000..258f105ae --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.html @@ -0,0 +1,106 @@ +
+
+

+ bookmark + + Bookmarked People + +

+ + + + Custom Order + + Alphabetical asc + + Alphabetical desc + + +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+ You haven't bookmarked anybody yet, look for the button + + on a workmates page. +
+
+
diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.scss b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.scss new file mode 100644 index 000000000..eec5ed661 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.scss @@ -0,0 +1,50 @@ +.pb-bookmarked { + height: 100%; + display: flex; + flex-direction: column; + flex: 1; + padding-right: 10px; +} + +.pb-small-card { + width: 320px; + height: 275px; +} + +.card-container { + display: flex; +} + +.pb-card:active { + cursor: -webkit-grabbing; + cursor: grabbing; +} + +.cdk-drop-dragging .cdk-drag, +.pb-card:not(.cdk-drag-placeholder) { + transition: transform 150ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-animating { + transition: transform 200ms cubic-bezier(0, 0, 0.2, 1); +} + +.pb-bookmarked-title { + display: flex; + flex-direction: row; + mat-icon { + display: inline-flex; + vertical-align: middle; + height: 29px; + width: 29px; + } + h1 { + margin: 0; + } + mat-form-field { + margin-left: 16px; + } + ::ng-deep .mat-form-field-infix { + border: none; + } +} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.ts b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.ts new file mode 100644 index 000000000..4b6661a24 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/bookmarked/bookmarked.component.ts @@ -0,0 +1,101 @@ +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { CdkDragEnd, CdkDragEnter, moveItemInArray } from '@angular/cdk/drag-drop'; +import { ChangeDetectorRef, Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Person, PhonebookSortDirection } from 'src/app/shared/models'; +import { + BookmarksState, + ToggleBookmark, + UpdateBookmarkOrder, + AppState, +} from 'src/app/shared/states'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; +import { untilComponentDestroyed } from 'ng2-rx-componentdestroyed'; +import { Router } from '@angular/router'; +import { Layout } from 'src/app/shared/models/enumerables/Layout'; + +@Component({ + selector: 'app-bookmarked', + templateUrl: './bookmarked.component.html', + styleUrls: ['./bookmarked.component.scss'], + host: { class: 'pb-dashboard-component' }, +}) +export class BookmarkedComponent implements OnInit, OnDestroy { + public bookmarkedPersons: Person[]; + public bookmarkedPersonsSubscriptions: Subscription | null = null; + public favoriteSort: PhonebookSortDirection = PhonebookSortDirection.none; + public lastFrom: number; + public lastTo: number; + @Select(BookmarksState) + public bookmarkedPersons$: Observable; + public currentUser: Person | null = null; + @Select(AppState.activeLayout) + public activeLayout$: Observable; + public layouts: string[] = Object.values(Layout); + public layout: typeof Layout = Layout; + constructor( + private store: Store, + private cd: ChangeDetectorRef, + private breakpointObserver: BreakpointObserver, + public router: Router, + private currentUserService: CurrentUserService + ) {} + + public ngOnInit() { + this.changeOrder(); + this.currentUserService + .getCurrentUser() + .pipe(untilComponentDestroyed(this)) + .subscribe( + (user) => { + if (user != null) { + this.currentUser = user; + } + }, + (error) => { + this.currentUser = null; + } + ); + } + + public changeOrder() { + if (this.bookmarkedPersonsSubscriptions) { + this.bookmarkedPersonsSubscriptions.unsubscribe(); + } + this.bookmarkedPersonsSubscriptions = this.store + .select(BookmarksState.sortedBookmarks) + .pipe(map((filterFn) => filterFn(this.favoriteSort))) + .subscribe((persons) => { + this.bookmarkedPersons = persons; + }); + } + + public entered(e: CdkDragEnter) { + if (this.bookmarkedPersons && this.bookmarkedPersons.length === 1) { + return; + } + + this.lastFrom = e.item.data; + this.lastTo = e.container.data; + } + + public ended(e: CdkDragEnd) { + if (this.bookmarkedPersons && this.bookmarkedPersons.length === 1) { + return; + } + if (this.lastFrom === undefined || this.lastTo === undefined) { + return; + } + moveItemInArray(this.bookmarkedPersons, this.lastFrom, this.lastTo); + this.store.dispatch(new UpdateBookmarkOrder(this.bookmarkedPersons)); + this.cd.detectChanges(); + } + + public removeFromBookmarkedPersons(person: Person) { + this.store.dispatch(new ToggleBookmark(person)); + } + + public ngOnDestroy(): void {} +} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.html b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.html new file mode 100644 index 000000000..ed73d32d9 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.html @@ -0,0 +1,154 @@ + + +
+

+ + Recent People + + + +

+ +
+ + + +
+ Looks like you haven't searched for any colleagues yet. Search for somebody to see how + it works! The most recent ones will be displayed here. +
+
+
+
+
+ + +
+ +
+
+ +
+ +
+ You haven't bookmarked anybody yet, look for the button + + on a workmates page. +
+
+
+ +
+
+
+
diff --git a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.scss b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.scss similarity index 51% rename from Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.scss rename to Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.scss index e509985b6..f6652dd3a 100644 --- a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.scss +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.scss @@ -9,32 +9,6 @@ mat-drawer-container { } } -.pb-bookmarked { - height: 100%; - margin-right: 50px; - display: flex; - flex-direction: column; - flex: 1; - - .pb-bookmarked-title { - display: flex; - flex-direction: row; - h1 { - margin: 0; - } - mat-form-field { - margin-left: 16px; - } - ::ng-deep .mat-form-field-infix { - border: none; - } - } - - .pb-bookmarks-list { - overflow-y: auto; - } -} - .pb-recent-people-drawer { width: 370px; background-color: unset; @@ -57,7 +31,7 @@ mat-drawer-container { z-index: 1; right: 0; top: 46%; - padding-right: 10px; + padding-right: 20px; button { align-self: center; @@ -67,32 +41,14 @@ mat-drawer-container { } } -.pb-small-card { - width: 320px; - height: 275px; -} - -.pb-card { - margin: 5px; - display: flex; -} - -.card-container { +.bottom-button { + bottom: 0%; + margin: 10px 0; display: flex; } -.pb-card:active { - cursor: -webkit-grabbing; - cursor: grabbing; -} - -.cdk-drop-dragging .cdk-drag, -.pb-card:not(.cdk-drag-placeholder) { - transition: transform 150ms cubic-bezier(0, 0, 0.2, 1); -} - -.cdk-drag-animating { - transition: transform 200ms cubic-bezier(0, 0, 0.2, 1); +.pb-expand { + flex-direction: column; } ::ng-deep .selectLayout div.mat-select-arrow-wrapper { diff --git a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.ts b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.ts similarity index 70% rename from Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.ts rename to Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.ts index 6b57e304c..5b071fa6c 100644 --- a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.ts +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/dashboard/dashboard.component.ts @@ -1,6 +1,7 @@ import { CdkDragEnd, CdkDragEnter, moveItemInArray } from '@angular/cdk/drag-drop'; -import { BreakpointObserver } from '@angular/cdk/layout'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { Router } from '@angular/router'; +import { ChangeDetectorRef, Component, OnInit, OnDestroy } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { untilComponentDestroyed } from 'ng2-rx-componentdestroyed'; import { Observable, Subscription } from 'rxjs'; @@ -21,6 +22,9 @@ import { ResetLastPersons, SetLastPersons, } from 'src/app/shared/states/LastPersons.state'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; +import { MatDrawerMode } from '@angular/material/sidenav'; +import { BookmarkedComponent } from 'src/app/pages/dashboard/components/bookmarked/bookmarked.component'; @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html', @@ -30,11 +34,6 @@ import { export class DashboardComponent implements OnInit, OnDestroy { @Select(LastPersonsState) public lastPersons$: Observable; - public bookmarkedPersons: Person[]; - public bookmarkedPersonsSubscriptions: Subscription | null = null; - public favoriteSort: PhonebookSortDirection = PhonebookSortDirection.none; - public lastFrom: number; - public lastTo: number; @Select(BookmarksState) public bookmarkedPersons$: Observable; public removedLastPersons: Person[] | null = null; @@ -44,15 +43,18 @@ export class DashboardComponent implements OnInit, OnDestroy { public layout: typeof Layout = Layout; public drawerOpen: boolean = false; + public drawerMode: MatDrawerMode = 'side'; public smallScreen: boolean = false; + public currentUser: Person | null = null; constructor( private store: Store, private cd: ChangeDetectorRef, - private breakpointObserver: BreakpointObserver + private breakpointObserver: BreakpointObserver, + public router: Router, + private currentUserService: CurrentUserService ) {} public ngOnInit() { - this.changeOrder(); this.store .select(AppState.recentPeopleDrawer) .pipe(untilComponentDestroyed(this)) @@ -70,39 +72,19 @@ export class DashboardComponent implements OnInit, OnDestroy { this.drawerOpen = this.store.selectSnapshot(AppState.recentPeopleDrawer); } }); - } - - public changeOrder() { - if (this.bookmarkedPersonsSubscriptions) { - this.bookmarkedPersonsSubscriptions.unsubscribe(); - } - this.bookmarkedPersonsSubscriptions = this.store - .select(BookmarksState.sortedBookmarks) - .pipe(map((filterFn) => filterFn(this.favoriteSort))) - .subscribe((persons) => { - this.bookmarkedPersons = persons; - }); - } - - public entered(e: CdkDragEnter) { - if (this.bookmarkedPersons && this.bookmarkedPersons.length === 1) { - return; - } - - this.lastFrom = e.item.data; - this.lastTo = e.container.data; - } - - public ended(e: CdkDragEnd) { - if (this.bookmarkedPersons && this.bookmarkedPersons.length === 1) { - return; - } - if (this.lastFrom === undefined || this.lastTo === undefined) { - return; - } - moveItemInArray(this.bookmarkedPersons, this.lastFrom, this.lastTo); - this.store.dispatch(new UpdateBookmarkOrder(this.bookmarkedPersons)); - this.cd.detectChanges(); + this.currentUserService + .getCurrentUser() + .pipe(untilComponentDestroyed(this)) + .subscribe( + (user) => { + if (user != null) { + this.currentUser = user; + } + }, + (error) => { + this.currentUser = null; + } + ); } public resetLastPersons() { diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.html b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.html new file mode 100644 index 000000000..f372074f5 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.html @@ -0,0 +1,49 @@ +
+
+

+ people + + My Team + +

+
+
+ +
+ +
+
+ +
+ +
+
+
+
diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.scss b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.scss new file mode 100644 index 000000000..a060c7bbb --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.scss @@ -0,0 +1,35 @@ +.pb-team { + height: 100%; + display: flex; + flex-direction: column; + flex: 1; + padding-right: 10px; +} +.pb-small-card { + width: 320px; + height: 275px; +} + +.card-container { + display: flex; +} + +.pb-team-title { + display: flex; + flex-direction: row; + mat-icon { + display: inline-flex; + vertical-align: middle; + height: 29px; + width: 29px; + } + h1 { + margin: 0; + } + mat-form-field { + margin-left: 16px; + } + ::ng-deep .mat-form-field-infix { + border: none; + } +} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.ts b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.ts new file mode 100644 index 000000000..88bb046b9 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/components/team/team.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { Person } from 'src/app/shared/models'; +import { AppState } from 'src/app/shared/states'; +import { OrganigramService } from 'src/app/services/api/organigram.service'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; +import { Router } from '@angular/router'; +import { Layout } from 'src/app/shared/models/enumerables/Layout'; + +@Component({ + selector: 'app-team', + templateUrl: './team.component.html', + styleUrls: ['./team.component.scss'], + host: { class: 'pb-dashboard-component' }, +}) +export class TeamComponent implements OnInit, OnDestroy { + public currentUser: Person | null = null; + public teamPersons: Person[]; + public person: Person; + @Select(AppState.activeLayout) + public activeLayout$: Observable; + public layouts: string[] = Object.values(Layout); + public layout: typeof Layout = Layout; + + constructor(private organigramService: OrganigramService) {} + + public ngOnInit() { + this.organigramService.getNodeForCurrentUser().subscribe((orgUnitOfUser) => { + if (orgUnitOfUser != null) { + this.teamPersons = [ + ...orgUnitOfUser.supervisors, + ...orgUnitOfUser.assistents, + ...orgUnitOfUser.employees, + ...orgUnitOfUser.learners, + ]; + } + }); + } + + public ngOnDestroy(): void {} +} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/dashboard-routing.module.ts b/Phonebook.Frontend/src/app/pages/dashboard/dashboard-routing.module.ts new file mode 100644 index 000000000..fde49e4ba --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/dashboard-routing.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { DashboardComponent } from 'src/app/pages/dashboard/components/dashboard/dashboard.component'; +import { BookmarkedComponent } from 'src/app/pages/dashboard/components/bookmarked/bookmarked.component'; +import { TeamComponent } from 'src/app/pages/dashboard/components/team/team.component'; +import { HasBookmarksGuard } from 'src/app/pages/dashboard/has-bookmarks.guard'; +import { IsAuthenticatedGuard } from 'src/app/shared/guards/is-authenticated.guard'; + +const routes: Routes = [ + { + path: '', + component: DashboardComponent, + children: [ + { + path: '', + pathMatch: 'full', + redirectTo: '/dashboard/bookmarks', + }, + { + path: 'bookmarks', + component: BookmarkedComponent, + pathMatch: 'full', + canActivate: [HasBookmarksGuard], + }, + { + path: 'team', + component: TeamComponent, + canActivate: [IsAuthenticatedGuard], + data: { guard: { redirectTo: '/dashboard/bookmarks' } }, + }, + ], + }, + { path: '**', redirectTo: 'bookmarks' }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class DashboardRoutingModule {} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.html b/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.html deleted file mode 100644 index b24fa48e3..000000000 --- a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.component.html +++ /dev/null @@ -1,202 +0,0 @@ - - -
-

- - Recent People - - - -

- -
- - - -
- Looks like you haven't searched for any colleagues yet. Search for somebody to see how - it works! The most recent ones will be displayed here. -
-
-
-
-
- -
-
-

- Bookmarked People -

- - - - Custom Order - - Alphabetical asc - - Alphabetical desc - - - -
- -
- -
- -
-
- -
- -
-
-
-
- You haven't bookmarked anybody yet, look for the button - - on a workmates page. -
-
-
- -
-
-
diff --git a/Phonebook.Frontend/src/app/pages/dashboard/dashboard.module.ts b/Phonebook.Frontend/src/app/pages/dashboard/dashboard.module.ts new file mode 100644 index 000000000..b41d75256 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/dashboard.module.ts @@ -0,0 +1,25 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { NgModule } from '@angular/core'; +import { DashboardComponent } from 'src/app/pages/dashboard/components/dashboard/dashboard.component'; +import { UserModule } from 'src/app/shared/components/user/user.module'; +import { MaterialModule } from 'src/app/shared/material.module'; +import { BookmarkedComponent } from 'src/app/pages/dashboard/components/bookmarked/bookmarked.component'; +import { TeamComponent } from 'src/app/pages/dashboard/components/team/team.component'; +import { DashboardRoutingModule } from 'src/app/pages/dashboard/dashboard-routing.module'; +import { PipesModule } from 'src/app/shared/pipes/pipes.module'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + declarations: [DashboardComponent, TeamComponent, BookmarkedComponent], + imports: [ + CommonModule, + DashboardRoutingModule, + MaterialModule, + DragDropModule, + PipesModule, + UserModule, + FormsModule, + ], +}) +export class DashboardModule {} diff --git a/Phonebook.Frontend/src/app/pages/dashboard/has-bookmarks.guard.ts b/Phonebook.Frontend/src/app/pages/dashboard/has-bookmarks.guard.ts new file mode 100644 index 000000000..3e7e76836 --- /dev/null +++ b/Phonebook.Frontend/src/app/pages/dashboard/has-bookmarks.guard.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + UrlTree, + Router, +} from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { Store } from '@ngxs/store'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; +import { BookmarksState } from 'src/app/shared/states'; +import { map, catchError } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class HasBookmarksGuard implements CanActivate { + constructor( + private store: Store, + private currentUserService: CurrentUserService, + private router: Router + ) {} + canActivate(): Observable | Promise | boolean | UrlTree { + return this.currentUserService.getCurrentUser().pipe( + map((user) => { + if (user != null && !this.store.selectSnapshot(BookmarksState.hasBookmarks)) { + return this.router.parseUrl('/dashboard/team'); + } + return true; + }), + catchError((error) => { + return of(true); + }) + ); + } +} diff --git a/Phonebook.Frontend/src/app/services/api/organigram.service.spec.ts b/Phonebook.Frontend/src/app/services/api/organigram.service.spec.ts index ff6b21923..05e708433 100644 --- a/Phonebook.Frontend/src/app/services/api/organigram.service.spec.ts +++ b/Phonebook.Frontend/src/app/services/api/organigram.service.spec.ts @@ -1,11 +1,14 @@ import { inject, TestBed } from '@angular/core/testing'; -import { OrganigramService } from './organigram.service'; +import { OrganigramService, UnitTreeNode } from './organigram.service'; import { PersonService } from './person.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Person, Business } from 'src/app/shared/models'; import { HttpClient } from '@angular/common/http'; describe('OrganigramService', () => { beforeEach(() => { TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], providers: [ OrganigramService, { provide: PersonService, useValue: null }, @@ -13,8 +16,105 @@ describe('OrganigramService', () => { ], }); }); - it('should be created', inject([OrganigramService], (service: OrganigramService) => { expect(service).toBeTruthy(); })); + + const ORG_UNIT_ID: string = 'targetOrgUnit'; + const nodeOfUser: UnitTreeNode = { + id: ORG_UNIT_ID, + name: 'bla', + depth: 0, + children: [], + supervisors: [], + assistents: [], + employees: [], + learners: [], + }; + it('should return users OrgUnit (first level)', inject( + [OrganigramService], + (service: OrganigramService) => { + const user: Partial = { + Business: { + ShortOrgUnit: [ORG_UNIT_ID], + OrgUnit: [ORG_UNIT_ID], + } as Business, + }; + + const organigram: UnitTreeNode[] = [nodeOfUser]; + + expect(service.getNodeForUser(user as Person, organigram, 0)).toEqual(nodeOfUser); + } + )); + + it('should return users OrgUnit (second level)', inject( + [OrganigramService], + (service: OrganigramService) => { + const user: Partial = { + Business: { + ShortOrgUnit: ['firstLevel', ORG_UNIT_ID], + OrgUnit: ['firstLevelName', ORG_UNIT_ID], + } as Business, + }; + const organigram: UnitTreeNode[] = [ + { + id: 'firstLevel', + name: 'firstLevelName', + children: [{ ...nodeOfUser, depth: 1 }], + } as UnitTreeNode, + ]; + + expect(service.getNodeForUser(user as Person, organigram, 0)).toEqual({ + ...nodeOfUser, + depth: 1, + }); + } + )); + + it('should return users OrgUnit (third level)', inject( + [OrganigramService], + (service: OrganigramService) => { + const user: Partial = { + Business: { + ShortOrgUnit: ['firstLevel', 'secondLevel', ORG_UNIT_ID], + OrgUnit: ['firstLevelName', 'secondLevelName', ORG_UNIT_ID], + } as Business, + }; + + const organigram: UnitTreeNode[] = [ + { + id: 'firstLevel', + name: 'firstLevelName', + children: [ + { + id: 'secondLevel', + name: 'secondLevelName', + children: [{ ...nodeOfUser, depth: 2 }], + }, + ], + } as UnitTreeNode, + ]; + + expect(service.getNodeForUser(user as Person, organigram, 0)).toEqual({ + ...nodeOfUser, + depth: 2, + }); + } + )); + + it('should return null if OrgUnit not found', inject( + [OrganigramService], + (service: OrganigramService) => { + const user: Partial = { + Business: { + ShortOrgUnit: [ORG_UNIT_ID], + OrgUnit: [ORG_UNIT_ID], + } as Business, + }; + + const organigram: UnitTreeNode[] = []; + + expect(service.getNodeForUser(user as Person, organigram, 0)).toEqual(null); + } + )); }); diff --git a/Phonebook.Frontend/src/app/services/api/organigram.service.ts b/Phonebook.Frontend/src/app/services/api/organigram.service.ts index c81ef15de..253fd8a11 100644 --- a/Phonebook.Frontend/src/app/services/api/organigram.service.ts +++ b/Phonebook.Frontend/src/app/services/api/organigram.service.ts @@ -1,32 +1,64 @@ import { Injectable } from '@angular/core'; -import { Observable, forkJoin, of } from 'rxjs'; +import { Observable, forkJoin, of, combineLatest } from 'rxjs'; import { map, flatMap, publishReplay, refCount } from 'rxjs/operators'; import { Person } from 'src/app/shared/models'; import { PersonService } from './person.service'; import { HttpClient } from '@angular/common/http'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; @Injectable() export class OrganigramService { - constructor(private http: HttpClient, private personService: PersonService) {} + constructor( + private http: HttpClient, + private personService: PersonService, + private currentUserService: CurrentUserService + ) {} public organigram: Observable; - public getOrganigram(): Observable { + public orgUnits: Observable; + public team: UnitTreeNode; + + public getOrgUnits(): Observable { + if (this.orgUnits != null) { + return this.orgUnits; + } + this.orgUnits = this.http.get('/api/OrgUnit').pipe( + publishReplay(1), // this tells Rx to cache the latest emitted + refCount() + ); + return this.orgUnits; + } + + public getOrganigramTree(): Observable { if (this.organigram != null) { return this.organigram; } - this.organigram = this.http.get('/api/OrgUnit').pipe( + this.organigram = this.getOrgUnits().pipe( flatMap((d) => this.ConvertOrgUnitsToUnitTree(d)), publishReplay(1), // this tells Rx to cache the latest emitted refCount() ); return this.organigram; } + public getOrganigramById(id: string): Observable { + return this.getOrganigramTree().pipe( + map((orgUnitArray) => { + const orgUnit = orgUnitArray.find((x) => { + return x.id === id; + }); + if (orgUnit === undefined) { + return null; + } + return orgUnit; + }) + ); + } private ConvertOrgUnitsToUnitTree( orgUnits: OrgUnit[], depth: number = 0 ): Observable { return forkJoin( orgUnits.map((o) => { - var TaShortNames = o.OrgUnitToFunctions.filter((f) => f.RoleName == 'TA').map( + let TaShortNames = o.OrgUnitToFunctions.filter((f) => f.RoleName == 'TA').map( (t) => t.Person.ShortName ); return forkJoin( @@ -42,7 +74,7 @@ export class OrganigramService { o.ShortName == null ? of([]) : this.personService.getByOrgUnit(o.ShortName) ).pipe( map(([childs, headofOrgUnit, assistents, members]) => { - var tree = new UnitTreeNode( + let tree = new UnitTreeNode( o.ShortName == null ? '' : o.ShortName, o.Name == null ? '' : o.Name, depth, @@ -58,6 +90,40 @@ export class OrganigramService { }) ); } + + public getNodeForCurrentUser(): Observable { + return combineLatest([this.currentUserService.getCurrentUser(), this.getOrganigramTree()]).pipe( + map(([user, organigram]) => { + if (user === null) { + return null; + } + return this.getNodeForUser(user, organigram, 0); + }) + ); + } + + public getNodeForUser( + user: Person, + nodeChilds: UnitTreeNode[], + depth: number + ): UnitTreeNode | null { + if (user.Business.ShortOrgUnit.length > depth) { + for (const node of nodeChilds) { + if (node.id === user.Business.ShortOrgUnit[depth] && node.depth === depth) { + if (user.Business.ShortOrgUnit.length == depth + 1) { + return node; + } + return this.getNodeForUser(user, node.children, depth + 1); + } + if (node.id !== user.Business.ShortOrgUnit[depth] && node.depth === depth) { + continue; + } else { + return this.getNodeForUser(user, node.children, depth + 1); + } + } + } + return null; + } } export class UnitTreeNode { diff --git a/Phonebook.Frontend/src/app/shared/guards/is-authenticated.guard.ts b/Phonebook.Frontend/src/app/shared/guards/is-authenticated.guard.ts new file mode 100644 index 000000000..6ac2385b0 --- /dev/null +++ b/Phonebook.Frontend/src/app/shared/guards/is-authenticated.guard.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + UrlTree, + Router, +} from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { CurrentUserService } from 'src/app/services/api/current-user.service'; +import { Store } from '@ngxs/store'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class IsAuthenticatedGuard implements CanActivate { + public defaultPath: string = '/'; + + constructor( + private store: Store, + private currentUserService: CurrentUserService, + private router: Router + ) {} + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean | UrlTree { + return this.currentUserService.getCurrentUser().pipe( + map((user) => { + if (user === null) { + return this.router.parseUrl(next.data.guard.redirectTo || this.defaultPath); + } + return true; + }), + catchError((error) => { + return of(this.router.parseUrl(next.data.guard.redirectTo || this.defaultPath)); + }) + ); + } +} diff --git a/Phonebook.Frontend/src/app/shared/states/Bookmarks.state.ts b/Phonebook.Frontend/src/app/shared/states/Bookmarks.state.ts index 3d72366ee..be5ce35a3 100644 --- a/Phonebook.Frontend/src/app/shared/states/Bookmarks.state.ts +++ b/Phonebook.Frontend/src/app/shared/states/Bookmarks.state.ts @@ -33,6 +33,11 @@ export class BookmarksState { }; } + @Selector() + public static hasBookmarks(state: Person[]) { + return state.length > 0; + } + @Action(ToggleBookmark) public toggleBookmark(ctx: StateContext, action: ToggleBookmark) { const state = ctx.getState(); diff --git a/Phonebook.Frontend/src/i18n/messages.de.xlf b/Phonebook.Frontend/src/i18n/messages.de.xlf index 57fd64de7..45613908a 100644 --- a/Phonebook.Frontend/src/i18n/messages.de.xlf +++ b/Phonebook.Frontend/src/i18n/messages.de.xlf @@ -401,6 +401,10 @@ Title of the Bookmarked people section DashboardComponent + + My Team Mein Team + Title of the Team people section + DashboardComponent Custom Order Eigene Reihenfolge diff --git a/Phonebook.Frontend/src/i18n/messages.en.xlf b/Phonebook.Frontend/src/i18n/messages.en.xlf index db3394a93..b5cbca910 100644 --- a/Phonebook.Frontend/src/i18n/messages.en.xlf +++ b/Phonebook.Frontend/src/i18n/messages.en.xlf @@ -327,6 +327,10 @@ Title of the Bookmarked people section DashboardComponent + + My Team My Team + Title of the Team people section + DashboardComponent Custom Order Custom Order The standard (customizable) order of the favorite diff --git a/Phonebook.Frontend/src/i18n/messages.xlf b/Phonebook.Frontend/src/i18n/messages.xlf index 78871a898..b850e16de 100644 --- a/Phonebook.Frontend/src/i18n/messages.xlf +++ b/Phonebook.Frontend/src/i18n/messages.xlf @@ -199,6 +199,11 @@ section DashboardComponent + + My Team + Title of the Team people section + DashboardComponent + Custom Order The standard (customizable) order of the favorite diff --git a/Phonebook.Frontend/src/styles.scss b/Phonebook.Frontend/src/styles.scss index 0b60f0167..9690a1d7c 100644 --- a/Phonebook.Frontend/src/styles.scss +++ b/Phonebook.Frontend/src/styles.scss @@ -20,6 +20,13 @@ app-root { display: flex; } +.pb-dashboard-component { + width: 100%; + height: 100%; + display: flex; + overflow: auto; +} + .circle { border-radius: 50%; overflow: hidden; @@ -52,6 +59,10 @@ app-root { flex-grow: 1; } +.pb-margin-0 { + margin: 0 !important; +} + .pb-margin-20 { margin: 20px; } @@ -150,6 +161,11 @@ app-root { } } +.pb-card { + margin: 5px; + display: flex; +} + .pb-margin-left-right-responsive { margin: auto 15%; diff --git a/Phonebook.Frontend/src/styles/overrides.scss b/Phonebook.Frontend/src/styles/overrides.scss index 5274047bf..3e7eba1a5 100644 --- a/Phonebook.Frontend/src/styles/overrides.scss +++ b/Phonebook.Frontend/src/styles/overrides.scss @@ -47,3 +47,14 @@ mat-card.mat-card { } } } + +// Centers icons in mat-button + +button[mat-button] { + div { + display: flex; + mat-icon { + margin: auto 15px auto 0; + } + } +}