diff --git a/Backend/src/Trackable.Services/DataClasses/DispatchingResults.cs b/Backend/src/Trackable.Services/DataClasses/DispatchingResults.cs index 4c2b760..b4bc61f 100644 --- a/Backend/src/Trackable.Services/DataClasses/DispatchingResults.cs +++ b/Backend/src/Trackable.Services/DataClasses/DispatchingResults.cs @@ -16,6 +16,6 @@ public class DispatchingResults public IEnumerable RoutePoints { get; set; } - public IEnumerable AlternativeCarRoutePoints { get; set; } + public IEnumerable AlternativeCarRoutePoints { get; set; } } } diff --git a/Backend/src/Trackable.Services/Services/DispatchingService.cs b/Backend/src/Trackable.Services/Services/DispatchingService.cs index a2b5703..8d8feea 100644 --- a/Backend/src/Trackable.Services/Services/DispatchingService.cs +++ b/Backend/src/Trackable.Services/Services/DispatchingService.cs @@ -67,7 +67,7 @@ public async Task> CallRoutingAPI(DispatchingPar content = await response.Content.ReadAsStringAsync(); apiResult = JObject.Parse(content); - var alternativePath = ExtractAlternativeDispatchingResult(apiResult); + var alternativePath = ExtractDispatchingResults(apiResult); foreach (var dispatchingResult in result) { diff --git a/Frontend/src/app/app.component.css b/Frontend/src/app/app.component.css index a3bbb22..6f9d561 100644 --- a/Frontend/src/app/app.component.css +++ b/Frontend/src/app/app.component.css @@ -50,6 +50,7 @@ h1 { .spinner { vertical-align: text-bottom; width: 25px; + margin-left: 10px; } .mat-raised-button{ diff --git a/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.css b/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.html b/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.html new file mode 100644 index 0000000..0f756c8 --- /dev/null +++ b/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.html @@ -0,0 +1,25 @@ + + +

Assets

+ + +

Create an asset by clicking on the + button. Give your asset a name, + choose its type (car, truck, etc.), link it to the corresponding device, and click submit.

+ + +

In this page, you will see the current location of all assets in real time. + You can click on the name of any asset to zoom in and follow that asset around.

+ +

You can click on the points button (Bullseye icon) and view the entire location history of + that asset. Additionally, you can filter to show a specific time period only.

+ +

You can click on the trip button (Rising trend icon) and view the trips this asset has made. + Trips are summarized versions of the location history of an asset. They include information + about periods of motion of the asset, the start and ending locations, and any minor stops + along the way. You can click on a single trip and view detailed information about it. When in + single trip mode, you can click on the map and it will show you information like instantaneous + speed, acceleration and other detailed information.

+ +
+
diff --git a/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.ts b/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.ts new file mode 100644 index 0000000..7d14dce --- /dev/null +++ b/Frontend/src/app/assets/asset-info-dialog/asset-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-asset-info-dialog', + templateUrl: './asset-info-dialog.component.html', + styleUrls: ['./asset-info-dialog.component.css'] +}) +export class AssetInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/assets/asset-list/asset-list.component.html b/Frontend/src/app/assets/asset-list/asset-list.component.html index 4ec1dc5..f7098f3 100644 --- a/Frontend/src/app/assets/asset-list/asset-list.component.html +++ b/Frontend/src/app/assets/asset-list/asset-list.component.html @@ -7,7 +7,7 @@ add_circle - diff --git a/Frontend/src/app/assets/asset-list/asset-list.component.ts b/Frontend/src/app/assets/asset-list/asset-list.component.ts index d40a707..c51c8b6 100644 --- a/Frontend/src/app/assets/asset-list/asset-list.component.ts +++ b/Frontend/src/app/assets/asset-list/asset-list.component.ts @@ -2,7 +2,9 @@ // Licensed under the MIT License. import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; +import { Observable } from 'rxjs'; +import { Subscription } from 'rxjs'; import { ToasterService } from 'angular2-toaster'; import { Asset } from '../asset'; @@ -16,8 +18,7 @@ import { Trip } from '../../shared/trip'; import { Roles } from '../../shared/role'; import { takeWhile, skipWhile } from 'rxjs/operators'; - - +import { AssetInfoDialogComponent } from '../asset-info-dialog/asset-info-dialog.component'; enum SelectedAssetState { @@ -52,7 +53,8 @@ export class AssetListComponent implements OnInit, OnDestroy { private assetService: AssetService, private locationService: LocationService, private mapsService: MapsService, - private toasterService: ToasterService) { + private toasterService: ToasterService, + public dialog: MatDialog) { this.isAlive = true; } @@ -163,6 +165,12 @@ export class AssetListComponent implements OnInit, OnDestroy { } } + openInfoDialog(): void { + this.dialog.open(AssetInfoDialogComponent, { + width: '600px', + }); + } + isAssetListSeleceted() { return this.selectedAssetState === SelectedAssetState.ListSelected; } diff --git a/Frontend/src/app/assets/assets.module.ts b/Frontend/src/app/assets/assets.module.ts index dccf886..8a20c4b 100644 --- a/Frontend/src/app/assets/assets.module.ts +++ b/Frontend/src/app/assets/assets.module.ts @@ -6,15 +6,22 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { AssetsRoutingModule, assetsRoutedComponents } from './assets-routing.module'; import { AssetService } from './asset.service'; +import { MatDialogModule } from '@angular/material'; +import { AssetInfoDialogComponent } from './asset-info-dialog/asset-info-dialog.component'; @NgModule({ declarations: [ - assetsRoutedComponents + assetsRoutedComponents, + AssetInfoDialogComponent ], imports: [ AssetsRoutingModule, - SharedModule + SharedModule, + MatDialogModule + ], + entryComponents: [ + AssetInfoDialogComponent ], providers: [ AssetService diff --git a/Frontend/src/app/core/data.service.ts b/Frontend/src/app/core/data.service.ts index da25a58..71c5c7e 100644 --- a/Frontend/src/app/core/data.service.ts +++ b/Frontend/src/app/core/data.service.ts @@ -28,10 +28,12 @@ export class DataService { const cache = this.getCache(path); const url = this.getUrl(path); - this.authHttpService.get(url).subscribe(data => { - cache.set(data); - this.spinnerService.stop(); - }, + this.authHttpService.get(url).pipe( + map(response => response as unknown as T[])) + .subscribe(data => { + cache.set(data); + this.spinnerService.stop(); + }, error => this.spinnerService.stop()); return cache.getItems(); diff --git a/Frontend/src/app/devices/device-list/device-list.component.html b/Frontend/src/app/devices/device-list/device-list.component.html index a1f425c..060e2f4 100644 --- a/Frontend/src/app/devices/device-list/device-list.component.html +++ b/Frontend/src/app/devices/device-list/device-list.component.html @@ -8,7 +8,7 @@ add_circle diff --git a/Frontend/src/app/devices/device-list/device-list.component.ts b/Frontend/src/app/devices/device-list/device-list.component.ts index c4136da..885a5c8 100644 --- a/Frontend/src/app/devices/device-list/device-list.component.ts +++ b/Frontend/src/app/devices/device-list/device-list.component.ts @@ -11,6 +11,8 @@ import { Device } from '../device'; import { DeviceService } from '../device.service'; import { MapsService } from '../../maps/maps.service'; import { Roles } from '../../shared/role'; +import { MatDialog } from '@angular/material/dialog'; +import { DevicesInfoDialogComponent } from '../devices-info-dialog/devices-info-dialog.component'; enum SelectedDeviceState { ListSelected, @@ -36,7 +38,8 @@ export class DeviceListComponent implements OnInit, OnDestroy { constructor( private deviceService: DeviceService, private mapsService: MapsService, - private toasterService: ToasterService) { } + private toasterService: ToasterService, + public dialog: MatDialog) { } ngOnInit() { this.devices = this.deviceService.getDevices(); @@ -95,6 +98,12 @@ export class DeviceListComponent implements OnInit, OnDestroy { } } + openDevicesDialog(): void { + this.dialog.open(DevicesInfoDialogComponent, { + width: '600px', + }); + } + timeFilterChange(range) { this.selectedDateRange = range; this.showPoints(this.selectedDevice, true); diff --git a/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.css b/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.html b/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.html new file mode 100644 index 0000000..014a392 --- /dev/null +++ b/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.html @@ -0,0 +1,15 @@ + + +

Devices

+ + +

Create a device by clicking on the + button. Download the Android apk or set up iOS mobile client on your + phone. Open the mobile application from your phone and scan the QR code to finish provisioning the device. + Alternatively, if you will be using your own background tracking client, dismiss the popup and give your + device a unique ID and a unique name. You can find the Linked Asset property and select the asset to + link it with the device. Click submit once you are done. You can still click on the edit (pencil icon) + button on each device to make modification after your submission.

+ +
+
diff --git a/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.ts b/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.ts new file mode 100644 index 0000000..2031e8e --- /dev/null +++ b/Frontend/src/app/devices/devices-info-dialog/devices-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-devices-info-dialog', + templateUrl: './devices-info-dialog.component.html', + styleUrls: ['./devices-info-dialog.component.css'] +}) +export class DevicesInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/devices/devices.module.ts b/Frontend/src/app/devices/devices.module.ts index 265de3b..0f764c2 100644 --- a/Frontend/src/app/devices/devices.module.ts +++ b/Frontend/src/app/devices/devices.module.ts @@ -7,15 +7,18 @@ import { SharedModule } from '../shared/shared.module'; import { DevicesRoutingModule, routedComponents } from './devices-routing.module'; import { DeviceService } from './device.service'; import { DeviceRegisterComponent } from './device-register/device-register.component'; +import { DevicesInfoDialogComponent } from './devices-info-dialog/devices-info-dialog.component'; @NgModule({ declarations: [ routedComponents, - DeviceRegisterComponent + DeviceRegisterComponent, + DevicesInfoDialogComponent ], entryComponents: [ - DeviceRegisterComponent + DeviceRegisterComponent, + DevicesInfoDialogComponent ], imports: [ DevicesRoutingModule, diff --git a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.css b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.css index a5751f3..1ebc7dd 100644 --- a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.css +++ b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.css @@ -46,3 +46,57 @@ h1 { height: 28px; padding: 10px 16px; } + +.mat-button { + width: 100%; + padding: 0; +} + +.pins-list { + width: 500px; + max-width: 100%; + border: solid 1px #ccc; + min-height: 60px; + display: block; + background: white; + border-radius: 4px; + overflow: hidden; +} + +.pins-list-box { + padding: 10px; + border-bottom: solid 1px #ccc; + color: rgba(0, 0, 0, 0.87); + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + cursor: move; + background: white; + font-size: 14px; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.pins-list-box:last-child { + border: none; +} + +.pins-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} \ No newline at end of file diff --git a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.html b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.html index 289c3e5..918fa3a 100644 --- a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.html +++ b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.html @@ -3,21 +3,36 @@ Add Route/Asset Info + +
-
- - - - - +
+ + + +
+
+
+ + {{pin.name}} + + + + delete_forever + + +
+
@@ -39,13 +54,15 @@

Avoid

- {{option.name}} + {{option.name}} Cross Winds - Grounding Risk + Grounding Risk + @@ -57,7 +74,8 @@

Minimize

- {{option.name}} + {{option.name}} @@ -67,7 +85,8 @@

Optimize

{{showOptimizeList ? "keyboard_arrow_up" : "keyboard_arrow_down"}} - + {{option.name}} @@ -82,29 +101,29 @@

Loaded Asset Properties

- + - + - + - + @@ -117,8 +136,8 @@

Departure Timing

- + @@ -157,7 +176,8 @@

Results Options

Exclude Directions - Show Alternative Car Route + Show Alternative Car + Route @@ -170,7 +190,8 @@

Results Options

- + Kilometer Mile @@ -188,7 +209,8 @@

Results Options

- + Meter Foot diff --git a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.ts b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.ts index 38b9c97..ad200be 100644 --- a/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.ts +++ b/Frontend/src/app/dispatching/dispatching-editor/dispatching-editor.component.ts @@ -12,7 +12,7 @@ import { DispatchingService } from '../dispatching.service'; import { LocationService } from '../../locations/location.service'; import { MapsService } from '../../maps/maps.service'; import { ToasterService } from 'angular2-toaster'; - +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { Location } from '../../shared/location'; import { Asset, AssetType } from '../../assets/asset'; @@ -35,6 +35,7 @@ import { HazardousMaterialOptions, HazardousPermitOptions } from '../dispatching-editor-options'; +import { DispatchingInfoDialogComponent } from '../dispatching-info-dialog/dispatching-info-dialog.component'; @Component({ selector: 'app-dispatching-editor', @@ -72,6 +73,7 @@ export class DispatchingEditorComponent implements OnInit, OnDestroy { showHazardMaterialList: boolean; showHazardPermitList: boolean; showResultsList: boolean; + showPinsList: boolean; private isAlive: boolean; @@ -81,7 +83,8 @@ export class DispatchingEditorComponent implements OnInit, OnDestroy { private dispatchingService: DispatchingService, private locationService: LocationService, private mapsService: MapsService, - private toasterService: ToasterService) { + private toasterService: ToasterService, + public dialog: MatDialog) { this.resetAllData(); } @@ -203,4 +206,23 @@ export class DispatchingEditorComponent implements OnInit, OnDestroy { this.dispatchingService.savePinsAdded(this.pinsAdded); this.mapsService.setRouteColor(this.colorSelected); } + + delete_pin(name: string) { + this.pinsAdded = this.pinsAdded.filter(item => item.name !== name); + this.mapsService.resetDispatchingDraw(this.pinsAdded); + } + + drop(event: CdkDragDrop) { + moveItemInArray(this.pinsAdded, event.previousIndex, event.currentIndex); + } + + togglePinsList() { + this.showPinsList = this.showPinsList ? false : true; + } + + openDispatchingInfoDialog(): void { + this.dialog.open(DispatchingInfoDialogComponent, { + width: '600px', + }); + } } diff --git a/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.css b/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.html b/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.html new file mode 100644 index 0000000..5eab823 --- /dev/null +++ b/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.html @@ -0,0 +1,14 @@ + + +

Dispatching

+ + +

+ Click the + button to add points to create the route. Select the asset from the assets list. Set up additional options according to the asset type. Click “Dispatch!” button to see the direction information and the corresponding path on the map. +

+ +
+
+ + diff --git a/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.ts b/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.ts new file mode 100644 index 0000000..f268ad7 --- /dev/null +++ b/Frontend/src/app/dispatching/dispatching-info-dialog/dispatching-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-dispatching-info-dialog', + templateUrl: './dispatching-info-dialog.component.html', + styleUrls: ['./dispatching-info-dialog.component.css'] +}) +export class DispatchingInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/dispatching/dispatching-results.ts b/Frontend/src/app/dispatching/dispatching-results.ts index 0e74b85..ac27f7a 100644 --- a/Frontend/src/app/dispatching/dispatching-results.ts +++ b/Frontend/src/app/dispatching/dispatching-results.ts @@ -8,5 +8,5 @@ export class DispatchingResults { itineraryPoints: Point[]; itineraryDistance: string[]; routePoints: Point[]; - alternativeCarRoutePoints: Point[]; + alternativeCarRoutePoints: DispatchingResults[]; } diff --git a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.css b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.css index 68dc28b..a95605c 100644 --- a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.css +++ b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.css @@ -3,6 +3,8 @@ mat-list-item { border-bottom: 1px solid #d9d9d9; + height: auto!important; + padding: 5px!important; } .mat-list-text h3{ @@ -24,3 +26,12 @@ mat-list-item { font-size: 0.9em; padding: 0 0 0 10px; } + +:host ::ng-deep .mat-tab-label, :host ::ng-deep.mat-tab-label-active{ + min-width: 100px!important; + padding: 0 10px!important; +} + +:host ::ng-deep .mat-tab-body-content { + overflow: hidden!important; +} \ No newline at end of file diff --git a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.html b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.html index 065d557..1687b77 100644 --- a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.html +++ b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.html @@ -8,15 +8,42 @@ Dispatch Directions -
-

No Directions Available

-
+
+ + - - -
-

{{direction}}

- Distance: {{distances[i]}} -
-
-
\ No newline at end of file +
+

No Directions Available

+
+ + + +
+

{{direction}}

+ Distance: {{mainRoute.distances[i]}} +
+
+
+ +
+ + + + +
+

No Directions Available

+
+ + + +
+

{{direction}}

+ Distance: {{altRoute.distances[i]}} +
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.ts b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.ts index 2995946..c4d64b9 100644 --- a/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.ts +++ b/Frontend/src/app/dispatching/dispatching-show/dispatching-show.component.ts @@ -9,6 +9,14 @@ import { SpinnerService } from '../../core/spinner.service'; import { ToasterService } from 'angular2-toaster'; import { Point } from '../../shared/point'; +import { Location } from '../../shared/location'; + +interface Route { + directions: string[] + distances: string[] + directionPoints: Point[] + routePoint: Point[] +} @Component({ selector: 'app-dispatching-show', @@ -17,35 +25,115 @@ import { Point } from '../../shared/point'; }) export class DispatchingShowComponent implements OnInit { - directions: string[]; - distances: string[]; - directionPoints: Point[]; + mainRoute: Route = { + directions: [], + distances: [], + directionPoints: [], + routePoint: [] + }; + altRoute: Route = { + directions: [], + distances: [], + directionPoints: [], + routePoint: [] + }; + viewAltRoute: boolean; noDirectionsAvailable: boolean; + location: Location; constructor( private dispatchingService: DispatchingService, private mapService: MapsService, private spinnerService: SpinnerService, private toasterService: ToasterService) { - this.spinnerService.start(); - } + this.spinnerService.start(); + } ngOnInit() { + this.location = new Location(); this.dispatchingService.getDispatchingResults() .subscribe(results => { - this.directions = results[0].itineraryText; - this.directionPoints = results[0].itineraryPoints; - this.distances = results[0].itineraryDistance; - this.noDirectionsAvailable = this.directions.length === 0; - this.mapService.showAlternativeResults(results[0].alternativeCarRoutePoints); - this.mapService.showDispatchingResults(results[0].routePoints, this.dispatchingService.getPinsAdded()); - this.spinnerService.stop(); }, error => { + this.viewAltRoute = this.dispatchingService.getDispatchingParameters().getAlternativeCarRoute; + + this.mainRoute = { + directions: results[0].itineraryText, + distances: results[0].itineraryDistance, + directionPoints: results[0].itineraryPoints, + routePoint: results[0].routePoints, + } + if (results[0].alternativeCarRoutePoints != null) { + this.altRoute = { + directions: results[0].alternativeCarRoutePoints[0].itineraryText, + distances: results[0].alternativeCarRoutePoints[0].itineraryDistance, + directionPoints: results[0].alternativeCarRoutePoints[0].itineraryPoints, + routePoint: results[0].alternativeCarRoutePoints[0].routePoints, + } + this.renameDestinationsInAltDirections(); + } + + this.noDirectionsAvailable = this.mainRoute.directions.length === 0; + + this.renameDestinationsInDirections(); + + this.mapService.showAlternativeResults(this.altRoute.routePoint); + this.mapService.showDispatchingResults(this.mainRoute.routePoint, this.dispatchingService.getPinsAdded()); + + this.spinnerService.stop(); + }, error => { this.toasterService.pop('error', 'Error routing', - 'An error has occured. Please make sure that the locations you are trying to route to are in the supported regions'); + 'An error has occured. Please make sure that the locations you are trying to route to are in the supported regions'); + }); + + + } + + toggleRoutes(item) { + if (item.tab.textLabel === "Main Route") { + this.mapService.showAlternativeResults(this.altRoute.routePoint); + this.mapService.showDispatchingResults(this.mainRoute.routePoint, this.dispatchingService.getPinsAdded()); + + } else { + this.mapService.showAlternativeResults(this.mainRoute.routePoint); + this.mapService.showDispatchingResults(this.altRoute.routePoint, this.dispatchingService.getPinsAdded()); + + } + } + + private renameDestinationsInDirections() { + this.dispatchingService.getDispatchingPinsResult() + .subscribe(location => { + var directions = this.mainRoute.directions; + var pinIndex = 1; + for (var i = 0; i < directions.length && location.length > 0; i++) { + var direction = directions[i]; + if (direction.startsWith("Arrive at Stop: Y")) { + directions[i] = "Arrive at Stop " + pinIndex + ": " + location[pinIndex].address + pinIndex += 1; + } + } + this.mainRoute.directions = directions; + }); + } + + private renameDestinationsInAltDirections() { + this.dispatchingService.getDispatchingPinsResult() + .subscribe(location => { + var directions = this.altRoute.directions; + var pinIndex = 1; + for (var i = 0; i < directions.length && location.length > 0; i++) { + var direction = directions[i]; + if (direction.startsWith("Arrive at Stop: Y")) { + directions[i] = "Arrive at Stop " + pinIndex + ": " + location[pinIndex].address + pinIndex += 1; + } + } + this.altRoute.directions = directions; }); + + } - showItineraryPoint(index: number) { - this.mapService.showItineraryPosition(this.directionPoints[index]); + showItineraryPoint(p) { + this.mapService.showItineraryPosition(p); } } diff --git a/Frontend/src/app/dispatching/dispatching.module.ts b/Frontend/src/app/dispatching/dispatching.module.ts index f0d2427..61c8aa1 100644 --- a/Frontend/src/app/dispatching/dispatching.module.ts +++ b/Frontend/src/app/dispatching/dispatching.module.ts @@ -10,24 +10,32 @@ import { DispatchingService } from './dispatching.service'; import { DialogService } from './dialog.service'; import { DispatchingShowComponent } from './dispatching-show/dispatching-show.component'; import { LocationDialogComponent } from './location-dialog/location-dialog.component'; +import { MatMenuModule, MatTabsModule} from '@angular/material'; +import { DispatchingInfoDialogComponent } from './dispatching-info-dialog/dispatching-info-dialog.component'; +import { DragDropModule } from '@angular/cdk/drag-drop'; @NgModule({ declarations: [ routedComponenets, DispatchingShowComponent, - LocationDialogComponent + LocationDialogComponent, + DispatchingInfoDialogComponent ], imports: [ CommonModule, SharedModule, - DispatchingRoutingModule + DispatchingRoutingModule, + MatMenuModule, + MatTabsModule, + DragDropModule ], providers: [ DispatchingService, DialogService ], entryComponents: [ - LocationDialogComponent + LocationDialogComponent, + DispatchingInfoDialogComponent ], exports: [ LocationDialogComponent diff --git a/Frontend/src/app/dispatching/dispatching.service.ts b/Frontend/src/app/dispatching/dispatching.service.ts index 5d3f03e..6afb64e 100644 --- a/Frontend/src/app/dispatching/dispatching.service.ts +++ b/Frontend/src/app/dispatching/dispatching.service.ts @@ -9,18 +9,22 @@ import { DispatchingParameters } from './dispatching-parameters'; import { DispatchingResults } from './dispatching-results'; import { DataService } from '../core/data.service'; import { Location } from '../shared/location'; +import { MapsService } from '../maps/maps.service'; @Injectable() export class DispatchingService { private dispatchingResults: Observable; + private dispatchingParameters: DispatchingParameters; private pinsAdded: Location[]; constructor( private dataService: DataService, - private router: Router) { } + private mapService: MapsService, + private router: Router) {} callDisaptchingAPI(dispatchingParameters: DispatchingParameters) { + this.dispatchingParameters = dispatchingParameters; this.dispatchingResults = this.dataService.post('dispatching', dispatchingParameters); this.router.navigate(['/dispatching/show']); } @@ -29,10 +33,18 @@ export class DispatchingService { return this.dispatchingResults; } + getDispatchingPinsResult(): Observable { + return this.mapService.getDispatchingPinsResult() + } + getPinsAdded(): Location[] { return this.pinsAdded; } + getDispatchingParameters(): DispatchingParameters{ + return this.dispatchingParameters; + } + savePinsAdded(pins: Location[]) { this.pinsAdded = pins; } diff --git a/Frontend/src/app/geofences/geofence-list/geofence-list.component.html b/Frontend/src/app/geofences/geofence-list/geofence-list.component.html index f052709..13b46cf 100644 --- a/Frontend/src/app/geofences/geofence-list/geofence-list.component.html +++ b/Frontend/src/app/geofences/geofence-list/geofence-list.component.html @@ -7,9 +7,9 @@ add_circle - + diff --git a/Frontend/src/app/geofences/geofence-list/geofence-list.component.ts b/Frontend/src/app/geofences/geofence-list/geofence-list.component.ts index e27f1a1..886a6c8 100644 --- a/Frontend/src/app/geofences/geofence-list/geofence-list.component.ts +++ b/Frontend/src/app/geofences/geofence-list/geofence-list.component.ts @@ -11,6 +11,8 @@ import { AssetService } from '../../assets/asset.service'; import { GeofenceService } from '../geofence.service'; import { MapsService } from '../../maps/maps.service'; import { takeWhile } from 'rxjs/operators'; +import { GeofencesInfoDialogComponent } from '../geofences-info-dialog/geofences-info-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-geofence-list', @@ -28,7 +30,8 @@ export class GeofenceListComponent implements OnInit, OnDestroy { constructor( private geofenceService: GeofenceService, private assetService: AssetService, - private mapsService: MapsService) { } + private mapsService: MapsService, + public dialog: MatDialog) { } ngOnInit() { this.isAlive = true; @@ -62,4 +65,10 @@ export class GeofenceListComponent implements OnInit, OnDestroy { deleteGeofence(geofence: Geofence) { this.geofenceService.remove(geofence); } + + openGeoDialog():void { + this.dialog.open(GeofencesInfoDialogComponent, { + width: '600px', + }); + } } diff --git a/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.css b/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.html b/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.html new file mode 100644 index 0000000..83fdbae --- /dev/null +++ b/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.html @@ -0,0 +1,29 @@ + + +

Geofence

+ + +

+ If you wish to be alerted when a specific group of assets enters or exits an area, you can use the geofence feature. You define a geofence as a polygon surrounding an area. Triggered geofences have a cooldown (in minutes) which controls how much time must at least pass before it gets triggered again. Inbound geofences trigger when an asset that was outside the geofenced area enters it. Outbound geofences trigger when an asset that was inside the geofenced area leaves it. +

+
    +
  • + Go to the geofences tab (the dotted square icon) and click the + icon. +
  • +
  • + Use the map to specify a polygon surrounding the area you want to geofence. +
  • +
  • + Fill in the geofence information including the name, list of emails to notify, cooldown in minutes and the type: inbound or outbound. +
  • +
  • + Select the assets you want to subscribe to this geofence. +
  • +
  • + Click submit to finish creating the geofence. +
  • +
+ +
+
diff --git a/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.ts b/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.ts new file mode 100644 index 0000000..c509a65 --- /dev/null +++ b/Frontend/src/app/geofences/geofences-info-dialog/geofences-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-geofences-info-dialog', + templateUrl: './geofences-info-dialog.component.html', + styleUrls: ['./geofences-info-dialog.component.css'] +}) +export class GeofencesInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/geofences/geofences.module.ts b/Frontend/src/app/geofences/geofences.module.ts index 8c421d9..799ed45 100644 --- a/Frontend/src/app/geofences/geofences.module.ts +++ b/Frontend/src/app/geofences/geofences.module.ts @@ -6,15 +6,20 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { GeofencesRoutingModule, geofencesRoutedComponents } from './geofences-routing.module'; import { GeofenceService } from './geofence.service'; +import { GeofencesInfoDialogComponent } from './geofences-info-dialog/geofences-info-dialog.component'; @NgModule({ declarations: [ geofencesRoutedComponents, + GeofencesInfoDialogComponent, ], imports: [ GeofencesRoutingModule, SharedModule ], + entryComponents: [ + GeofencesInfoDialogComponent + ], providers: [GeofenceService], exports: [] }) diff --git a/Frontend/src/app/locations/location-list/location-list.component.html b/Frontend/src/app/locations/location-list/location-list.component.html index bf23c13..2ac774a 100644 --- a/Frontend/src/app/locations/location-list/location-list.component.html +++ b/Frontend/src/app/locations/location-list/location-list.component.html @@ -7,9 +7,9 @@ add_circle - + @@ -22,6 +22,9 @@

{{location.name}}

address: {{location.address}} + mode_edit diff --git a/Frontend/src/app/locations/location-list/location-list.component.ts b/Frontend/src/app/locations/location-list/location-list.component.ts index b7e6665..976b5fd 100644 --- a/Frontend/src/app/locations/location-list/location-list.component.ts +++ b/Frontend/src/app/locations/location-list/location-list.component.ts @@ -11,6 +11,8 @@ import { Point } from '../../shared/point'; import { Roles } from '../../shared/role'; import { AssetService } from '../../assets/asset.service'; import { takeWhile } from 'rxjs/operators'; +import { LocationsInfoDialogComponent } from '../locations-info-dialog/locations-info-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-location-list', @@ -32,7 +34,8 @@ export class LocationListComponent implements OnInit, OnDestroy { constructor( private locationService: LocationService, private assetService: AssetService, - private mapsService: MapsService) { } + private mapsService: MapsService, + public dialog: MatDialog) { } ngOnInit() { this.isAlive = true; @@ -89,4 +92,14 @@ export class LocationListComponent implements OnInit, OnDestroy { this.showTable = (this.assetsCount.length >= 1); }); } + + deleteLocation(location: Location) { + this.locationService.deleteLocation(location); + } + + openLocationDialog(): void { + this.dialog.open(LocationsInfoDialogComponent, { + width: '600px', + }); + } } diff --git a/Frontend/src/app/locations/location.service.ts b/Frontend/src/app/locations/location.service.ts index 3c285f4..2b18966 100644 --- a/Frontend/src/app/locations/location.service.ts +++ b/Frontend/src/app/locations/location.service.ts @@ -10,25 +10,29 @@ import { Point } from '../shared/point'; @Injectable() export class LocationService { - constructor(private dataSevrice: DataService) { } + constructor(private dataService: DataService) { } addLocation(location: Location): Observable { - return this.dataSevrice.post('locations', location); + return this.dataService.post('locations', location); } getLocations(): Observable { - return this.dataSevrice.get('locations'); + return this.dataService.get('locations'); } getLocation(id: number): Observable { - return this.dataSevrice.getSingle('locations', id); + return this.dataService.getSingle('locations', id); } updateLocation(location: Location): Observable { - return this.dataSevrice.put('locations', location.id, location, true); + return this.dataService.put('locations', location.id, location, true); } getLocationAssetsCount(location: Location): Observable> { - return this.dataSevrice.getSingleNoCache>(`locations/${location.id}/assetsCount`); + return this.dataService.getSingleNoCache>(`locations/${location.id}/assetsCount`); + } + + deleteLocation(location: Location): Observable { + return this.dataService.delete('locations', location.id); } } diff --git a/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.css b/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.html b/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.html new file mode 100644 index 0000000..cbea68a --- /dev/null +++ b/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.html @@ -0,0 +1,10 @@ + + +

Locations

+ + +

Create a location by clicking on the + button. Drop a pin on the map and fill out the name of the new location. Click submit once you are done. Those locations will be used in Add Route page.

+ + +
diff --git a/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.ts b/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.ts new file mode 100644 index 0000000..01d8bbd --- /dev/null +++ b/Frontend/src/app/locations/locations-info-dialog/locations-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-locations-info-dialog', + templateUrl: './locations-info-dialog.component.html', + styleUrls: ['./locations-info-dialog.component.css'] +}) +export class LocationsInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/locations/locations.module.ts b/Frontend/src/app/locations/locations.module.ts index ff0e624..e9b6e65 100644 --- a/Frontend/src/app/locations/locations.module.ts +++ b/Frontend/src/app/locations/locations.module.ts @@ -7,15 +7,20 @@ import { SharedModule } from '../shared/shared.module'; import { LocationsRoutingModule, locationsRoutedComponents } from './locations-routing.module'; import { LocationService } from './location.service'; import { LocationEditorComponent } from './location-editor/location-editor.component'; +import { LocationsInfoDialogComponent } from './locations-info-dialog/locations-info-dialog.component'; @NgModule({ declarations: [ - locationsRoutedComponents + locationsRoutedComponents, + LocationsInfoDialogComponent ], imports: [ SharedModule, LocationsRoutingModule ], + entryComponents: [ + LocationsInfoDialogComponent + ], providers: [ LocationService ] diff --git a/Frontend/src/app/maps/bing-maps.service.ts b/Frontend/src/app/maps/bing-maps.service.ts index 2292c20..18cfd25 100644 --- a/Frontend/src/app/maps/bing-maps.service.ts +++ b/Frontend/src/app/maps/bing-maps.service.ts @@ -19,6 +19,8 @@ import { LocationService } from '../locations/location.service'; import { TripLeg } from '../shared/trip-leg'; import { takeWhile } from 'rxjs/operators'; + + @Injectable() export class BingMapsService { private readonly genericColors = ['#56A6B6', '#464E50', '#56B691', '#BF8B8A', '#E95794']; @@ -27,6 +29,7 @@ export class BingMapsService { private readonly greenSpeed = 13.4; private readonly geofenceGreenBlue = 'rgba(86,182,145,0.4)'; private readonly geofencePink = 'rgba(233,87,148,0.4)'; + pinCount = 1; private map: Microsoft.Maps.Map; private loadPromise: Promise; @@ -178,7 +181,7 @@ export class BingMapsService { this.geofencesLayer.setVisible(true); this.geofencesLayer.clear(); - let lastGeofencePolygon; + let lastGeofencePolygon = new Array(); for (const geofence of geofences) { lastGeofencePolygon = this.showGeofencePolygon(geofence, this.geofenceGreenBlue, this.genericColors[0]); } @@ -389,6 +392,8 @@ export class BingMapsService { }); } + + drawDispatchingRoute(subject: Subject, initialLocations: Location[]) { this.load().then(() => { const tempRoutePoints = initialLocations || new Array(); @@ -400,25 +405,33 @@ export class BingMapsService { if (initialLocations) { this.showDispatchingRoutePins(tempRoutePoints); + } else { + this.pinCount = 1; } this.drawHandlerId = Microsoft.Maps.Events.addHandler(this.map, 'click', e => { const event = e as Microsoft.Maps.IMouseEventArgs; const newLocation = new Location(); - newLocation.name = 'Pin' + '(' + (tempRoutePoints.length + 1) + ')'; + newLocation.name = 'Pin' + '(' + (this.pinCount) + ')'; + this.pinCount += 1; newLocation.latitude = event.location.latitude; newLocation.longitude = event.location.longitude; - - this.showLocation(newLocation); - tempRoutePoints.push(newLocation); - subject.next(tempRoutePoints); + this.searchManager.reverseGeocode({ + location: event.location, + callback: (placeResult: Microsoft.Maps.Search.IPlaceResult) => { + newLocation.address = placeResult.address.formattedAddress; + this.showLocation(newLocation); + tempRoutePoints.push(newLocation); + subject.next(tempRoutePoints); + } + }); } ); }); } - showDispatchingRoute(points: Point[], clearMap: boolean, colorIndex: number): void { + showDispatchingRoute(points: Point[], clearMap: boolean, colorIndex: number, Thickness: number): void { this.load().then(() => { if (clearMap) { this.locationsLayer.clear(); @@ -434,7 +447,7 @@ export class BingMapsService { const polyline = new Microsoft.Maps.Polyline(locations, { strokeColor: this.tripColors[colorIndex % this.tripColors.length], - strokeThickness: 2 + strokeThickness: Thickness }); this.locationsLayer.add(polyline); diff --git a/Frontend/src/app/maps/maps/maps.component.ts b/Frontend/src/app/maps/maps/maps.component.ts index 5891467..d423bd9 100644 --- a/Frontend/src/app/maps/maps/maps.component.ts +++ b/Frontend/src/app/maps/maps/maps.component.ts @@ -64,7 +64,8 @@ export class MapsComponent implements OnInit { this.bingMapsService.showDispatchingRoute( points, true, - this.mapsService.getRouteColor() + 1 + this.mapsService.getRouteColor() + 1, + 2 ) ); this.mapsService @@ -73,7 +74,8 @@ export class MapsComponent implements OnInit { this.bingMapsService.showDispatchingRoute( points, false, - this.mapsService.getRouteColor() + this.mapsService.getRouteColor(), + 4 ) ); this.mapsService diff --git a/Frontend/src/app/reports/report-list/report-list.component.html b/Frontend/src/app/reports/report-list/report-list.component.html index 969eee8..a459943 100644 --- a/Frontend/src/app/reports/report-list/report-list.component.html +++ b/Frontend/src/app/reports/report-list/report-list.component.html @@ -4,7 +4,7 @@ Reports - diff --git a/Frontend/src/app/reports/report-list/report-list.component.ts b/Frontend/src/app/reports/report-list/report-list.component.ts index c4d12a5..3141ee7 100644 --- a/Frontend/src/app/reports/report-list/report-list.component.ts +++ b/Frontend/src/app/reports/report-list/report-list.component.ts @@ -6,6 +6,8 @@ import { Observable, Subscription } from 'rxjs'; import { Metric } from '../metric'; import { ReportService } from '../report.service'; +import { MatDialog } from '@angular/material/dialog'; +import { ReportsInfoDialogComponent } from '../reports-info-dialog/reports-info-dialog.component'; @Component({ @@ -21,7 +23,8 @@ export class ReportListComponent implements OnInit, OnDestroy { selectedMetric: Metric; - constructor(private reportService: ReportService) { } + constructor(private reportService: ReportService, + public dialog: MatDialog) { } ngOnInit() { this.metrics = this.reportService.getAllMetrics(); @@ -40,4 +43,10 @@ export class ReportListComponent implements OnInit, OnDestroy { this.selectedMetric = metric; this.reportService.setSelectedMetric(metric); } + + openReportsDialog(): void { + this.dialog.open(ReportsInfoDialogComponent, { + width: '600px', + }); + } } diff --git a/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.css b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.html b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.html new file mode 100644 index 0000000..eedc9e4 --- /dev/null +++ b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.html @@ -0,0 +1,12 @@ + + +

Reports

+ + +

+ Show reports of trips, collected points, geofences for all assets. +

+ +
+
diff --git a/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.spec.ts b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.spec.ts new file mode 100644 index 0000000..8ae37d2 --- /dev/null +++ b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.spec.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ReportsInfoDialogComponent } from './reports-info-dialog.component'; + +describe('ReportsInfoDialogComponent', () => { + let component: ReportsInfoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ReportsInfoDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReportsInfoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.ts b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.ts new file mode 100644 index 0000000..5a1b85c --- /dev/null +++ b/Frontend/src/app/reports/reports-info-dialog/reports-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-reports-info-dialog', + templateUrl: './reports-info-dialog.component.html', + styleUrls: ['./reports-info-dialog.component.css'] +}) +export class ReportsInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/reports/reports.module.ts b/Frontend/src/app/reports/reports.module.ts index 9daee53..cf7e7df 100644 --- a/Frontend/src/app/reports/reports.module.ts +++ b/Frontend/src/app/reports/reports.module.ts @@ -8,18 +8,23 @@ import { ChartsModule } from 'ng2-charts'; import { ReportsRoutingModule, reportsRoutedComponents } from './reports-routing.module'; import { ReportService } from './report.service'; import { ReportChartComponent } from './report-chart/report-chart.component'; +import { ReportsInfoDialogComponent } from './reports-info-dialog/reports-info-dialog.component'; @NgModule({ declarations: [ reportsRoutedComponents, - ReportChartComponent + ReportChartComponent, + ReportsInfoDialogComponent ], imports: [ ReportsRoutingModule, SharedModule, ChartsModule ], + entryComponents: [ + ReportsInfoDialogComponent + ], providers: [ ReportService ], diff --git a/Frontend/src/app/users/login/login.component.ts b/Frontend/src/app/users/login/login.component.ts index c44f690..497677b 100644 --- a/Frontend/src/app/users/login/login.component.ts +++ b/Frontend/src/app/users/login/login.component.ts @@ -89,4 +89,4 @@ export class LoginComponent implements OnInit, OnDestroy { ngOnDestroy() { this.userSubscription.unsubscribe(); } -} +} \ No newline at end of file diff --git a/Frontend/src/app/users/user-list/user-list.component.html b/Frontend/src/app/users/user-list/user-list.component.html index 571e372..dffe4dc 100644 --- a/Frontend/src/app/users/user-list/user-list.component.html +++ b/Frontend/src/app/users/user-list/user-list.component.html @@ -7,7 +7,7 @@ - diff --git a/Frontend/src/app/users/user-list/user-list.component.ts b/Frontend/src/app/users/user-list/user-list.component.ts index c21fc9c..92a7096 100644 --- a/Frontend/src/app/users/user-list/user-list.component.ts +++ b/Frontend/src/app/users/user-list/user-list.component.ts @@ -6,6 +6,8 @@ import { Observable, Subscription } from 'rxjs'; import { User } from '../../shared/user'; import { UserService } from '../user.service'; +import { MatDialog } from '@angular/material/dialog'; +import { UsersInfoDialogComponent } from '../users-info-dialog/users-info-dialog.component'; @Component({ @@ -18,7 +20,9 @@ export class UserListComponent implements OnInit, OnDestroy { users: Observable; filter: string; - constructor(private userService: UserService) { } + constructor( + private userService: UserService, + public dialog: MatDialog) { } ngOnInit() { this.users = this.userService.getAll(); @@ -32,4 +36,10 @@ export class UserListComponent implements OnInit, OnDestroy { this.userService.delete(user); } } + + openUsersDialog(): void{ + this.dialog.open(UsersInfoDialogComponent, { + width: '600px', + }); + } } diff --git a/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.css b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.html b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.html new file mode 100644 index 0000000..23a1e46 --- /dev/null +++ b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.html @@ -0,0 +1,34 @@ + + +

Reports

+ +

On a fresh deployment, only the owner can access the portal. If another user tries to sign in to the portal, they will be marked as "pending" and blocked from accessing resources, until an administrator grants them access. +

+

On the user page, you will find all the users with their assigned roles. Click the edit (pencil icon) button to edit a user. You can change a user's role to any of the following:

+
    +
  • + Blocked: User will not be able to access the portal + +
  • +
  • + Pending: User will not be able to access the portal until an administrator grants him access +
  • +
  • + Viewer: User will be able to view assets, devices, and reports but will not be able to edit anything +
  • +
  • + Administrator: User will have full access to the portal's features +
  • +
+ +

+ Click submit once you choose a role to assign to a user. +

+ +

+ You can also add a user who has not logged in before by clicking the + icon and entering his/her email. +

+ +
+
diff --git a/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.spec.ts b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.spec.ts new file mode 100644 index 0000000..a8b3f48 --- /dev/null +++ b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.spec.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UsersInfoDialogComponent } from './users-info-dialog.component'; + +describe('UsersInfoDialogComponent', () => { + let component: UsersInfoDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UsersInfoDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UsersInfoDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.ts b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.ts new file mode 100644 index 0000000..486c351 --- /dev/null +++ b/Frontend/src/app/users/users-info-dialog/users-info-dialog.component.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-users-info-dialog', + templateUrl: './users-info-dialog.component.html', + styleUrls: ['./users-info-dialog.component.css'] +}) +export class UsersInfoDialogComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Frontend/src/app/users/users.module.ts b/Frontend/src/app/users/users.module.ts index a456543..68aacc6 100644 --- a/Frontend/src/app/users/users.module.ts +++ b/Frontend/src/app/users/users.module.ts @@ -9,14 +9,19 @@ import { InstrumentationApprovalComponent } from './instrumentation-approval/ins import { LoginComponent } from './login/login.component'; import { UserService } from './user.service'; import { AuthService } from './auth.service'; +import { UsersInfoDialogComponent } from './users-info-dialog/users-info-dialog.component'; @NgModule({ declarations: [ usersRoutedComponents, LoginComponent, - InstrumentationApprovalComponent + InstrumentationApprovalComponent, + UsersInfoDialogComponent + ], + entryComponents: [ + InstrumentationApprovalComponent, + UsersInfoDialogComponent ], - entryComponents: [InstrumentationApprovalComponent], imports: [ UsersRoutingModule, SharedModule