浏览代码

project initialization

onunez 6 年之前
父节点
当前提交
710d81f205
共有 100 个文件被更改,包括 5470 次插入154 次删除
  1. 31 4
      angular.json
  2. 290 113
      package-lock.json
  3. 31 3
      package.json
  4. 0 11
      src/app/app-routing.module.ts
  5. 0 19
      src/app/app.component.html
  6. 17 0
      src/app/app.component.ts
  7. 48 4
      src/app/app.module.ts
  8. 43 0
      src/app/app.routing.ts
  9. 156 0
      src/app/components/dashboard/dashboard.component.html
  10. 85 0
      src/app/components/dashboard/dashboard.component.scss
  11. 25 0
      src/app/components/dashboard/dashboard.component.spec.ts
  12. 37 0
      src/app/components/dashboard/dashboard.component.ts
  13. 39 0
      src/app/components/login/login.component.html
  14. 69 0
      src/app/components/login/login.component.scss
  15. 25 0
      src/app/components/login/login.component.spec.ts
  16. 67 0
      src/app/components/login/login.component.ts
  17. 23 0
      src/app/components/plugins/maps/maps.component.html
  18. 0 0
      src/app/components/plugins/maps/maps.component.scss
  19. 25 0
      src/app/components/plugins/maps/maps.component.spec.ts
  20. 87 0
      src/app/components/plugins/maps/maps.component.ts
  21. 12 0
      src/app/components/plugins/plant-detail/plant-detail.component.html
  22. 10 0
      src/app/components/plugins/plant-detail/plant-detail.component.scss
  23. 25 0
      src/app/components/plugins/plant-detail/plant-detail.component.spec.ts
  24. 29 0
      src/app/components/plugins/plant-detail/plant-detail.component.ts
  25. 51 0
      src/app/components/plugins/plants-datatables/plants-datatables.component.html
  26. 39 0
      src/app/components/plugins/plants-datatables/plants-datatables.component.scss
  27. 25 0
      src/app/components/plugins/plants-datatables/plants-datatables.component.spec.ts
  28. 53 0
      src/app/components/plugins/plants-datatables/plants-datatables.component.ts
  29. 36 0
      src/app/components/plugins/plants-status/plants-status.component.html
  30. 3 0
      src/app/components/plugins/plants-status/plants-status.component.scss
  31. 25 0
      src/app/components/plugins/plants-status/plants-status.component.spec.ts
  32. 28 0
      src/app/components/plugins/plants-status/plants-status.component.ts
  33. 66 0
      src/app/components/plugins/plugins.module.ts
  34. 1 0
      src/app/components/profile/profile.component.html
  35. 0 0
      src/app/components/profile/profile.component.scss
  36. 25 0
      src/app/components/profile/profile.component.spec.ts
  37. 19 0
      src/app/components/profile/profile.component.ts
  38. 1 0
      src/app/components/shared/footer/footer.component.html
  39. 0 0
      src/app/components/shared/footer/footer.component.scss
  40. 25 0
      src/app/components/shared/footer/footer.component.spec.ts
  41. 15 0
      src/app/components/shared/footer/footer.component.ts
  42. 46 0
      src/app/components/shared/navbar/navbar.component.html
  43. 0 0
      src/app/components/shared/navbar/navbar.component.scss
  44. 25 0
      src/app/components/shared/navbar/navbar.component.spec.ts
  45. 165 0
      src/app/components/shared/navbar/navbar.component.ts
  46. 25 0
      src/app/components/shared/shared.module.ts
  47. 83 0
      src/app/components/shared/sidebar/sidebar.component.html
  48. 5 0
      src/app/components/shared/sidebar/sidebar.component.scss
  49. 25 0
      src/app/components/shared/sidebar/sidebar.component.spec.ts
  50. 40 0
      src/app/components/shared/sidebar/sidebar.component.ts
  51. 1156 0
      src/app/data/measures.data.ts
  52. 18 0
      src/app/data/measures.ts
  53. 191 0
      src/app/data/plants.data.ts
  54. 31 0
      src/app/helpers/auth.guard.ts
  55. 24 0
      src/app/helpers/error.interceptor.ts
  56. 112 0
      src/app/helpers/fake-backend.ts
  57. 4 0
      src/app/helpers/index.ts
  58. 27 0
      src/app/helpers/jwt.interceptor.ts
  59. 53 0
      src/app/layouts/admin/admin.component.html
  60. 0 0
      src/app/layouts/admin/admin.component.scss
  61. 25 0
      src/app/layouts/admin/admin.component.spec.ts
  62. 161 0
      src/app/layouts/admin/admin.component.ts
  63. 42 0
      src/app/layouts/admin/admin.module.ts
  64. 29 0
      src/app/layouts/admin/admin.routing.ts
  65. 3 0
      src/app/models/index.ts
  66. 18 0
      src/app/models/plant.ts
  67. 4 0
      src/app/models/role.ts
  68. 18 0
      src/app/models/user.ts
  69. 43 0
      src/app/services/authentication.service.ts
  70. 12 0
      src/app/services/plants.service.spec.ts
  71. 24 0
      src/app/services/plants.service.ts
  72. 16 0
      src/app/services/services.module.ts
  73. 18 0
      src/app/services/user.service.ts
  74. 35 0
      src/assets/css/demo.css
  75. 二进制
      src/assets/img/angular.png
  76. 二进制
      src/assets/img/angular2-logo-red.png
  77. 二进制
      src/assets/img/angular2-logo.png
  78. 二进制
      src/assets/img/apple-icon.png
  79. 二进制
      src/assets/img/cover.jpeg
  80. 二进制
      src/assets/img/faces/marc.jpg
  81. 二进制
      src/assets/img/favicon.png
  82. 二进制
      src/assets/img/gears.gif
  83. 二进制
      src/assets/img/html.png
  84. 二进制
      src/assets/img/logo-inverlec.png
  85. 二进制
      src/assets/img/logo-inverlec2.png
  86. 二进制
      src/assets/img/logo.png
  87. 二进制
      src/assets/img/marker-icon.png
  88. 二进制
      src/assets/img/mask.png
  89. 二进制
      src/assets/img/me-logo.png
  90. 二进制
      src/assets/img/new_logo.png
  91. 二进制
      src/assets/img/sidebar-1.jpg
  92. 二进制
      src/assets/img/sidebar-2.jpg
  93. 二进制
      src/assets/img/sidebar-3.jpg
  94. 二进制
      src/assets/img/sidebar-4.jpg
  95. 二进制
      src/assets/img/tim_80x80.png
  96. 161 0
      src/assets/scss/core/_alerts.scss
  97. 120 0
      src/assets/scss/core/_angular-modal.scss
  98. 257 0
      src/assets/scss/core/_buttons.scss
  99. 658 0
      src/assets/scss/core/_cards.scss
  100. 210 0
      src/assets/scss/core/_checkboxes.scss

+ 31 - 4
angular.json

@@ -28,9 +28,22 @@
               "src/assets"
             ],
             "styles": [
+              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
+              "node_modules/font-awesome/scss/font-awesome.scss",
+              "node_modules/perfect-scrollbar/css/perfect-scrollbar.css",
+              "src/assets/scss/material-dashboard.scss",
+              "node_modules/leaflet/dist/leaflet.css",
               "src/styles.scss"
             ],
-            "scripts": []
+            "scripts": [
+              "node_modules/jquery/dist/jquery.js",
+              "node_modules/popper.js/dist/umd/popper.js",
+              "node_modules/bootstrap-material-design/dist/js/bootstrap-material-design.min.js",
+              "node_modules/arrive/src/arrive.js",
+              "node_modules/moment/moment.js",
+              "node_modules/perfect-scrollbar/dist/perfect-scrollbar.min.js",
+              "node_modules/datatables.net/js/jquery.dataTables.js"
+            ]
           },
           "configurations": {
             "production": {
@@ -93,9 +106,22 @@
               "src/assets"
             ],
             "styles": [
+              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
+              "node_modules/font-awesome/scss/font-awesome.scss",
+              "node_modules/perfect-scrollbar/css/perfect-scrollbar.css",
+              "src/assets/scss/material-dashboard.scss",
+              "node_modules/leaflet/dist/leaflet.css",
               "src/styles.scss"
             ],
-            "scripts": []
+            "scripts": [
+              "node_modules/jquery/dist/jquery.js",
+              "node_modules/popper.js/dist/umd/popper.js",
+              "node_modules/bootstrap-material-design/dist/js/bootstrap-material-design.min.js",
+              "node_modules/arrive/src/arrive.js",
+              "node_modules/moment/moment.js",
+              "node_modules/perfect-scrollbar/dist/perfect-scrollbar.min.js",
+              "node_modules/datatables.net/js/jquery.dataTables.js"
+            ]
           }
         },
         "lint": {
@@ -124,6 +150,7 @@
           }
         }
       }
-    }},
+    }
+  },
   "defaultProject": "plant-viewer"
-}
+}

文件差异内容过多而无法显示
+ 290 - 113
package-lock.json


+ 31 - 3
package.json

@@ -11,15 +11,41 @@
   },
   "private": true,
   "dependencies": {
-    "@angular/animations": "~8.2.0",
+    "@agm/core": "^1.0.0-beta.7",
+    "@angular/animations": "^8.2.3",
+    "@angular/cdk": "^8.1.3",
     "@angular/common": "~8.2.0",
     "@angular/compiler": "~8.2.0",
     "@angular/core": "~8.2.0",
     "@angular/forms": "~8.2.0",
+    "@angular/material": "^8.1.3",
     "@angular/platform-browser": "~8.2.0",
     "@angular/platform-browser-dynamic": "~8.2.0",
     "@angular/router": "~8.2.0",
-    "rxjs": "~6.4.0",
+    "@asymmetrik/ngx-leaflet": "^6.0.1",
+    "@ng-bootstrap/ng-bootstrap": "^5.1.0",
+    "@swimlane/ngx-datatable": "^15.0.2",
+    "@types/googlemaps": "^3.37.4",
+    "angular-bootstrap-md": "^8.1.1",
+    "angular-datatables": "^8.0.0",
+    "arrive": "^2.4.1",
+    "bootstrap": "^4.3.1",
+    "bootstrap-material-design": "^4.1.2",
+    "chart.js": "^2.8.0",
+    "datatables.net": "^1.10.19",
+    "datatables.net-dt": "^1.10.19",
+    "font-awesome": "^4.7.0",
+    "hammerjs": "^2.0.8",
+    "jquery": "^3.4.1",
+    "leaflet": "^1.5.1",
+    "leaflet-routing-machine": "^3.2.12",
+    "moment": "^2.24.0",
+    "ng2-charts": "^2.2.4",
+    "ngx-bootstrap": "^5.1.1",
+    "perfect-scrollbar": "^1.4.0",
+    "popper.js": "^1.15.0",
+    "rxjs": "6.5.2",
+    "rxjs-compat": "^6.5.2",
     "tslib": "^1.10.0",
     "zone.js": "~0.9.1"
   },
@@ -28,9 +54,11 @@
     "@angular/cli": "~8.2.2",
     "@angular/compiler-cli": "~8.2.0",
     "@angular/language-service": "~8.2.0",
-    "@types/node": "~8.9.4",
+    "@types/datatables.net": "^1.10.17",
     "@types/jasmine": "~3.3.8",
     "@types/jasminewd2": "~2.0.3",
+    "@types/jquery": "^3.3.31",
+    "@types/node": "~8.9.4",
     "codelyzer": "^5.0.0",
     "jasmine-core": "~3.4.0",
     "jasmine-spec-reporter": "~4.2.1",

+ 0 - 11
src/app/app-routing.module.ts

@@ -1,11 +0,0 @@
-import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-
-
-const routes: Routes = [];
-
-@NgModule({
-  imports: [RouterModule.forRoot(routes)],
-  exports: [RouterModule]
-})
-export class AppRoutingModule { }

+ 0 - 19
src/app/app.component.html

@@ -1,21 +1,2 @@
-<!--The content below is only a placeholder and can be replaced.-->
-<div style="text-align:center">
-  <h1>
-    Welcome to {{ title }}!
-  </h1>
-  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
-</div>
-<h2>Here are some links to help you start: </h2>
-<ul>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
-  </li>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
-  </li>
-  <li>
-    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
-  </li>
-</ul>
 
 <router-outlet></router-outlet>

+ 17 - 0
src/app/app.component.ts

@@ -1,4 +1,8 @@
 import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { AuthenticationService } from '@app/services/authentication.service';
+import { User, Role } from './models';
 
 @Component({
   selector: 'app-root',
@@ -7,4 +11,17 @@ import { Component } from '@angular/core';
 })
 export class AppComponent {
   title = 'plant-viewer';
+  currentUser: User;
+
+  constructor(
+    private router: Router,
+    private authenticationService: AuthenticationService
+  ) {
+    this.authenticationService.currentUser.subscribe(x => this.currentUser = x);
+  }
+
+  get isAdmin() {
+    return this.currentUser && this.currentUser.role === Role.Admin;
+  }
+
 }

+ 48 - 4
src/app/app.module.ts

@@ -1,18 +1,62 @@
 import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+
 import { NgModule } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+import { RouterModule } from '@angular/router';
+
+// used to create fake backend
+import { fakeBackendProvider } from './helpers';
+import { JwtInterceptor, ErrorInterceptor } from './helpers';
+
 
-import { AppRoutingModule } from './app-routing.module';
+import { AppRoutingModule } from './app.routing';
 import { AppComponent } from './app.component';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { SharedModule } from './components/shared/shared.module';
+import { AdminComponent } from './layouts/admin/admin.component';
+
+
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+
+import { MDBBootstrapModule } from 'angular-bootstrap-md';
+//import { DashboardComponent } from './dashboard/dashboard.component';
+//import { AgmCoreModule } from '@agm/core';
+import { PluginsModule } from './components/plugins/plugins.module';
+import { LoginComponent } from './components/login/login.component';
+import { AuthenticationService } from './services/authentication.service';
+
+
+//import { DashboardComponent } from './dashboard/dashboard.component';
 
 @NgModule({
   declarations: [
-    AppComponent
+    AppComponent,
+    AdminComponent,
+    LoginComponent,
   ],
   imports: [
     BrowserModule,
-    AppRoutingModule
+    NgbModule,
+    BrowserAnimationsModule,
+    FormsModule,
+    ReactiveFormsModule,
+    HttpClientModule,
+    SharedModule,
+    RouterModule,
+    AppRoutingModule,
+    PluginsModule,
+    NgxDatatableModule,
+    MDBBootstrapModule.forRoot(),
+  ],
+  providers: [
+    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
+    { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
+    AuthenticationService,
+    // provider used to create fake backend
+    fakeBackendProvider
   ],
-  providers: [],
   bootstrap: [AppComponent]
 })
 export class AppModule { }

+ 43 - 0
src/app/app.routing.ts

@@ -0,0 +1,43 @@
+import { NgModule } from '@angular/core';
+import { CommonModule, } from '@angular/common';
+import { BrowserModule  } from '@angular/platform-browser';
+import { Routes, RouterModule } from '@angular/router';
+import { AdminModule } from './layouts/admin/admin.module';
+
+import { AdminComponent } from './layouts/admin/admin.component';
+import { LoginComponent } from './components/login/login.component';
+import { AuthGuard } from './helpers/auth.guard';
+import { Role } from './models/role';
+
+const routes: Routes =[
+  {
+    path: '',
+    redirectTo: 'dashboard',
+    pathMatch: 'full',
+  }, {
+    path: '',
+    component: AdminComponent,
+    canActivate: [AuthGuard],
+    children: [{
+      path: '',
+      loadChildren : () => AdminModule
+    }]
+  }, {
+    path: 'login',
+    component: LoginComponent
+  },
+  { path: '**', redirectTo: '' }
+];
+
+@NgModule({
+  imports: [
+    CommonModule,
+    BrowserModule,
+    RouterModule.forRoot(routes,{
+       useHash: true
+    })
+  ],
+  exports: [
+  ],
+})
+export class AppRoutingModule { }

+ 156 - 0
src/app/components/dashboard/dashboard.component.html

@@ -0,0 +1,156 @@
+<div class="main-content">
+  <div class="container-fluid">
+    <div class="row">
+      <div class="col-lg-6 col-md-6 col-sm-12">
+        <div class="row">
+
+          <div class="col-lg-6 col-md-6 col-sm-6">
+            <div class="widget">
+							<div class="widget-controls">
+								<span class="refresh-content"><i class="fa fa-bolt"></i></span>
+							</div><!-- Widget Controls -->
+							<div class="mini-stats ">
+								<span class="yellow-skin"><i class="fa fa-bolt"></i></span>
+								<p>Capacidad instalada</p>
+								<h3>
+                  369.6
+                  <small>kW</small>
+                </h3>
+							</div>
+						</div>
+          </div>
+
+          <div class="col-lg-6 col-md-6 col-sm-6">
+            <div class="widget">
+							<div class="widget-controls">
+								<span class="refresh-content"><i class="fa fa-bolt"></i></span>
+							</div><!-- Widget Controls -->
+							<div class="mini-stats ">
+								<span class="yellow-skin"><i class="fa fa-bolt"></i></span>
+								<p>Ultimos 365 días</p>
+                <h3>
+                  270.83
+                  <small>MWh</small>
+                </h3>
+							</div>
+						</div>
+          </div>
+
+
+
+          <div class="col-lg-6 col-md-6 col-sm-6">
+            <div class="widget">
+							<div class="widget-controls">
+								<span class="refresh-content"><i class="fa fa-bolt"></i></span>
+							</div><!-- Widget Controls -->
+							<div class="mini-stats ">
+								<span class="yellow-skin"><i class="fa fa-bolt"></i></span>
+								<p>Ultimos 30 días</p>
+								<h3>
+                  56.24
+                  <small>MWh</small>
+                </h3>
+							</div>
+						</div>
+          </div>
+
+      </div>
+      <div class="col-lg-6 col-md-6 col-sm-12">
+        Grafico
+      </div>
+    </div>
+  </div>
+
+  <div class="d-none d-md-block">
+    <app-plants-datatables></app-plants-datatables>
+  </div>
+
+  <div class="d-block d-md-none">
+    <app-plants-status></app-plants-status>
+  </div>
+
+
+  <app-maps></app-maps>
+
+
+</div>
+
+
+<!--
+<div class="col-lg-3 col-md-6 col-sm-6">
+  <div class="card card-stats">
+    <div class="card-header card-header-warning card-header-icon">
+      <div class="card-icon">
+        <i class="material-icons">offline_bolt</i>
+      </div>
+      <p class="card-category">Capacidad instalada</p>
+      <h3 class="card-title">369.6
+        <small>kW</small>
+      </h3>
+    </div>
+    <div class="card-footer">
+      <div class="stats">
+        <i class="material-icons">business</i>
+        <span>5 plantas</span>
+      </div>
+    </div>
+  </div>
+</div>
+<div class="col-lg-3 col-md-6 col-sm-6">
+  <div class="card card-stats">
+    <div class="card-header card-header-success card-header-icon">
+      <div class="card-icon">
+        <i class="material-icons">date_range</i>
+      </div>
+      <p class="card-category">Ultimos 365 días</p>
+      <h3 class="card-title">270.83
+        <small>MWh</small>
+      </h3>
+    </div>
+    <div class="card-footer">
+      <div class="stats">
+        <i class="material-icons">offline_bolt</i>
+        Energía
+      </div>
+    </div>
+  </div>
+</div>
+<div class="col-lg-3 col-md-6 col-sm-6">
+  <div class="card card-stats">
+    <div class="card-header card-header-danger card-header-icon">
+      <div class="card-icon">
+        <i class="material-icons">date_range</i>
+      </div>
+      <p class="card-category">Ultimos 30 días</p>
+      <h3 class="card-title">56.24
+        <small>MWh</small>
+      </h3>
+    </div>
+    <div class="card-footer">
+      <div class="stats">
+        <i class="material-icons">offline_bolt</i>
+        Energía
+      </div>
+    </div>
+  </div>
+</div>
+<div class="col-lg-3 col-md-6 col-sm-6">
+  <div class="card card-stats">
+    <div class="card-header card-header-danger card-header-icon">
+      <div class="card-icon">
+          <i class="material-icons">date_range</i>
+      </div>
+      <p class="card-category">Ultimas 24 horas</p>
+      <h3 class="card-title">1.55
+        <small>MWh</small>
+      </h3>
+    </div>
+    <div class="card-footer">
+      <div class="stats">
+        <i class="material-icons">offline_bolt</i>
+        Energía
+      </div>
+    </div>
+  </div>
+</div>
+-->

+ 85 - 0
src/app/components/dashboard/dashboard.component.scss

@@ -0,0 +1,85 @@
+agm-map {
+  height: 300px;
+}
+
+.widget {
+  background: #ffffff none repeat scroll 0 0;
+  float: left;
+  margin-top: 14px;
+  position: relative;
+  width: 100%;
+  border-radius: 5px;
+
+  .widget-controls {
+    padding-right: 10px;
+    padding-top: 10px;
+    position: absolute;
+    right: 0;
+    top: 0;
+    z-index: 1;
+
+    span {
+      border: 1px solid;
+      border-radius: 50%;
+      color: #fff;
+      float: left;
+      font-size: 20px;
+      height: 57px;
+      line-height: 56px;
+      margin-right: 15px;
+      text-align: center;
+      width: 57px;
+    }
+  }
+
+  .mini-stats {
+    background: #ffffff none repeat scroll 0 0;
+    border-radius: 5px;
+    float: left;
+    padding: 25px 30px;
+    position: relative;
+    width: 100%;
+
+    p {
+      color: #878888;
+      display: block;
+      font-size: 11px;
+      line-height: 20px;
+      margin: 6px 0 0;
+      text-transform: uppercase;
+    }
+
+    span {
+      border: 1px solid;
+      border-radius: 50%;
+      color: #fff;
+      float: left;
+      font-size: 20px;
+      height: 57px;
+      line-height: 56px;
+      margin-right: 15px;
+      text-align: center;
+      width: 57px;
+    }
+
+    h3 {
+      margin: 0;
+    }
+
+    .sky-skin {
+      background-color: #63d6ff;
+      border-color: #28c4fc !important;
+    }
+
+    .yellow-skin {
+      background-color: #fea11d;
+      border-color: #e58d12;
+    }
+
+    .red-skin {
+      background-color: #ff6b6b;
+      border-color: #ff6262 !important;
+    }
+
+  }
+}

+ 25 - 0
src/app/components/dashboard/dashboard.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DashboardComponent } from './dashboard.component';
+
+describe('DashboardComponent', () => {
+  let component: DashboardComponent;
+  let fixture: ComponentFixture<DashboardComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ DashboardComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DashboardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 37 - 0
src/app/components/dashboard/dashboard.component.ts

@@ -0,0 +1,37 @@
+import { Component, OnInit } from '@angular/core';
+import { Plant } from 'src/app/models/plant';
+import { PlantsService } from 'src/app/services/plants.service';
+
+@Component({
+  selector: 'app-dashboard',
+  templateUrl: './dashboard.component.html',
+  styleUrls: ['./dashboard.component.scss']
+})
+export class DashboardComponent implements OnInit {
+  listData: Plant[];
+  rows = [];
+
+  constructor(private plantsService: PlantsService) {}
+  ngOnInit(): void {
+
+    this.plantsService.getPlants().subscribe(res => {
+      this.listData = res;
+    });
+
+    this.rows = [JSON.stringify(this.listData)];
+
+
+    var responsiveOptions: any[] = [
+      ['screen and (max-width: 640px)', {
+        seriesBarDistance: 5,
+        axisX: {
+          labelInterpolationFnc: function (value) {
+            return value[0];
+          }
+        }
+      }]
+    ];
+
+  }
+
+}

+ 39 - 0
src/app/components/login/login.component.html

@@ -0,0 +1,39 @@
+<div id="wrapper">
+  <div class="vertical-align-wrap">
+    <div class="vertical-align-middle auth-main">
+      <div class="auth-box">
+        <div class="top">
+          <img alt="Inverlec" src="./assets/img/logo-inverlec.png">
+        </div>
+        <div class="card">
+          <div class="header">
+            <p class="lead">Login to your account</p>
+          </div>
+          <div class="body">
+            <form class="form-auth-small ng-untouched ng-pristine ng-valid" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
+              <div class="form-group">
+                <label for="username">Username</label>
+                <input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
+                <div *ngIf="submitted && f.username.errors" class="invalid-feedback">
+                    <div *ngIf="f.username.errors.required">Username is required</div>
+                </div>
+              </div>
+              <div class="form-group">
+                <label for="password">Password</label>
+                <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
+                <div *ngIf="submitted && f.password.errors" class="invalid-feedback">
+                    <div *ngIf="f.password.errors.required">Password is required</div>
+                </div>
+              </div>
+              <button [disabled]="loading" class="btn btn-primary">
+                <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
+                Login
+              </button>
+              <div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{error}}</div>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 69 - 0
src/app/components/login/login.component.scss

@@ -0,0 +1,69 @@
+.vertical-align-wrap {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: table;
+}
+
+.vertical-align-middle {
+  display: table-cell;
+  vertical-align: middle;
+}
+
+.auth-box {
+  width: 400px;
+  height: auto;
+  margin-left: 130px;
+
+  .card {
+    padding: 25px;
+  }
+
+}
+
+.auth-main::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 400px;
+  height: 100%;
+  z-index: -1;
+  background: #173f80;
+}
+
+.auth-main:after {
+  content: '';
+  position: absolute;
+  right: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: -2;
+  background: #f0f0f0;
+  //background: url(../../assets/images/auth_bg.jpg) no-repeat top left fixed;
+}
+
+.card {
+  background: #fff;
+  transition: .5s;
+  border: 0;
+  margin-bottom: 30px;
+  border-radius: .55rem;
+  position: relative;
+  width: 100%;
+  box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1);
+}
+
+@media screen and (max-width: 640px) {
+  .auth-box {
+      width: 90%;
+  }
+}
+
+@media screen and (max-width: 992px) {
+  .auth-box {
+      width: 80%;
+      margin: 0 auto;
+  }
+}

+ 25 - 0
src/app/components/login/login.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+  let component: LoginComponent;
+  let fixture: ComponentFixture<LoginComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LoginComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 67 - 0
src/app/components/login/login.component.ts

@@ -0,0 +1,67 @@
+import { Component, OnInit } from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { first } from 'rxjs/operators';
+
+import { AuthenticationService } from '@app/services/authentication.service';
+
+@Component({
+  selector: 'app-login',
+  templateUrl: './login.component.html',
+  styleUrls: ['./login.component.scss']
+})
+
+export class LoginComponent implements OnInit {
+    loginForm: FormGroup;
+    loading = false;
+    submitted = false;
+    returnUrl: string;
+    error = '';
+
+    constructor(
+        private formBuilder: FormBuilder,
+        private route: ActivatedRoute,
+        private router: Router,
+        private authenticationService: AuthenticationService
+    ) {
+        // redirect to home if already logged in
+        if (this.authenticationService.currentUserValue) {
+            this.router.navigate(['/']);
+        }
+    }
+
+    ngOnInit() {
+        this.loginForm = this.formBuilder.group({
+            username: ['', Validators.required],
+            password: ['', Validators.required]
+        });
+
+        // get return url from route parameters or default to '/'
+        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
+    }
+
+    // convenience getter for easy access to form fields
+    get f() { return this.loginForm.controls; }
+
+    onSubmit() {
+        this.submitted = true;
+
+        // stop here if form is invalid
+        if (this.loginForm.invalid) {
+            return;
+        }
+
+        this.loading = true;
+        this.authenticationService.login(this.f.username.value, this.f.password.value)
+            .pipe(first())
+            .subscribe(
+                data => {
+                    this.router.navigate([this.returnUrl]);
+                },
+                error => {
+                    console.log(error);
+                    this.error = error;
+                    this.loading = false;
+                });
+    }
+}

+ 23 - 0
src/app/components/plugins/maps/maps.component.html

@@ -0,0 +1,23 @@
+<div class="align-container">
+  <mat-expansion-panel>
+    <mat-expansion-panel-header>
+      <mat-panel-title>
+        Ubicaciones
+      </mat-panel-title>
+    </mat-expansion-panel-header>
+
+    <div class="container-fluid">
+      <div class="row">
+        <div class="col-md-8">
+          <div style="height: 350px;" leaflet [leafletOptions]="options" [leafletLayers]="markers"
+            (leafletMapReady)='onMapReady($event)'>
+          </div>
+        </div>
+        <div class="col-md-4">
+          <app-plant-detail [plantId]="plantId"></app-plant-detail>
+        </div>
+      </div>
+    </div>
+  </mat-expansion-panel>
+</div>
+<br>

+ 0 - 0
src/app/components/plugins/maps/maps.component.scss


+ 25 - 0
src/app/components/plugins/maps/maps.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MapsComponent } from './maps.component';
+
+describe('MapsComponent', () => {
+  let component: MapsComponent;
+  let fixture: ComponentFixture<MapsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ MapsComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MapsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 87 - 0
src/app/components/plugins/maps/maps.component.ts

@@ -0,0 +1,87 @@
+import { Component, OnInit, NgZone } from '@angular/core';
+import { latLng, tileLayer, marker, Layer, icon, Map, latLngBounds, LatLng, LatLngBounds, point } from 'leaflet';
+import { Plant } from 'src/app/models/plant';
+import { PlantsService } from 'src/app/services/plants.service';
+
+@Component({
+  selector: 'app-maps',
+  templateUrl: './maps.component.html',
+  styleUrls: ['./maps.component.scss']
+})
+export class MapsComponent implements OnInit {
+
+  listData: Plant[];
+  markers: Layer[] = [];
+  points: LatLng[] = [];
+  plantId: number;
+
+  icon = icon({
+    iconSize: [25, 41],
+    iconAnchor: [13, 41],
+    iconUrl: 'marker-icon.png',
+    //shadowUrl: 'marker-shadow.png'
+  });
+
+
+  constructor(private plantsService: PlantsService, private zone: NgZone) {
+  }
+
+  // Open Street Map definitions
+  LAYER_OSM = tileLayer(
+    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+    { maxZoom: 18,
+      attribution: '&copy; OpenStreetMap contributors'
+    });
+
+  // Values to bind to Leaflet Directive
+  options = {
+    layers: [ this.LAYER_OSM ],
+    zoom: 8,
+    center: latLng([13.661714, -89.251530])
+  };
+
+  ngOnInit() {
+    this.plantsService.getPlants().subscribe(res => {
+      this.listData = res;
+    });
+    this.addMarkers();
+  }
+
+  addMarkers() {
+    for (const plant of this.listData) {
+      const name = plant.name;
+      const lat = plant.latitude;
+      const long = plant.longitude;
+      const newMarker = marker(
+        [lat, long],
+        {icon: this.icon})
+        .bindPopup('<b>' + plant.name + '</b><br>Fecha de Instalación: ' + plant.installDate)
+        .on('click', () => {
+          this.zone.run(() => {
+            this.sendPlantId(plant.id);
+          });
+        });
+      this.points.push(latLng([lat, long]));
+      this.markers.push(newMarker);
+    }
+  }
+
+  sendPlantId(id: number) {
+    this.plantId = id;
+  }
+
+  onMapReady(map: Map) {
+    setTimeout(() => {
+      map.invalidateSize();
+    }, 0);
+
+    const bounds = latLngBounds(this.points);
+    map.fitBounds(bounds, {
+      padding: point(24, 24),
+      maxZoom: 10.5,
+      animate: true
+    });
+  }
+
+
+}

+ 12 - 0
src/app/components/plugins/plant-detail/plant-detail.component.html

@@ -0,0 +1,12 @@
+<div class='plant-detail' *ngIf=plant>
+  <h2><i class="fa fa-bolt"></i> {{plant.name}} </h2>
+
+  <p class='h4'>País: {{plant.country}}</p>
+  <p class='h4'>Ciudad: {{plant.city}}</p>
+  <p class='h4'>Fecha de Instalación: {{plant.installDate}}</p>
+  <p class='h4'>Potencia Instalada: {{plant.installedCapacity}} kW</p>
+  <p class='h4'>Generación Total: kWh</p>
+  <p class='h4'>Generación 24-h: kWh</p>
+
+  <button class="btn bg-yellow btn-flat">Ir a Planta</button>
+</div>

+ 10 - 0
src/app/components/plugins/plant-detail/plant-detail.component.scss

@@ -0,0 +1,10 @@
+.detail-box {
+  border: none;
+  border-radius: 0;
+  box-shadow: none;
+}
+
+button {
+  position: absolute;
+  bottom: 0;
+}

+ 25 - 0
src/app/components/plugins/plant-detail/plant-detail.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PlantDetailComponent } from './plant-detail.component';
+
+describe('PlantDetailComponent', () => {
+  let component: PlantDetailComponent;
+  let fixture: ComponentFixture<PlantDetailComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ PlantDetailComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PlantDetailComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 29 - 0
src/app/components/plugins/plant-detail/plant-detail.component.ts

@@ -0,0 +1,29 @@
+import { Component, OnInit, Input, OnChanges } from '@angular/core';
+import { PlantsService } from 'src/app/services/plants.service';
+import { Plant } from 'src/app/models/plant';
+
+@Component({
+  selector: 'app-plant-detail',
+  templateUrl: './plant-detail.component.html',
+  styleUrls: ['./plant-detail.component.scss']
+})
+export class PlantDetailComponent implements OnInit, OnChanges {
+
+  @Input() plantId: number;
+  plant: Plant;
+
+  id: number;
+
+  constructor(private plantsService: PlantsService) { }
+
+  ngOnInit() {
+
+  }
+
+  ngOnChanges() {
+    this.plantsService.getPlant(this.plantId).subscribe(res => {
+      this.plant = res;
+    });
+  }
+
+}

+ 51 - 0
src/app/components/plugins/plants-datatables/plants-datatables.component.html

@@ -0,0 +1,51 @@
+<div class="align-container">
+  <h4><b>Status de las plantas</b></h4>
+  <div class="example-container mat-elevation-z8">
+    <div class="example-table-container">
+
+      <table mat-table [dataSource]="dataSource" class="example-table">
+
+        <!-- Name Column -->
+        <ng-container matColumnDef="name">
+          <th mat-header-cell *matHeaderCellDef>Name</th>
+          <td mat-cell *matCellDef="let row">{{row.name}}</td>
+        </ng-container>
+
+        <!-- State Column -->
+        <ng-container matColumnDef="state">
+          <th mat-header-cell *matHeaderCellDef>State</th>
+          <td mat-cell *matCellDef="let row">{{row.state}}</td>
+        </ng-container>
+
+        <!--  Column -->
+        <ng-container matColumnDef="installDate">
+          <th mat-header-cell *matHeaderCellDef>Date</th>
+          <td mat-cell *matCellDef="let row">{{row.installDate}}</td>
+        </ng-container>
+
+        <!-- Country Column -->
+        <ng-container matColumnDef="country">
+          <th mat-header-cell *matHeaderCellDef>Country</th>
+          <td mat-cell *matCellDef="let row">{{row.country}}</td>
+        </ng-container>
+
+        <!-- City Column -->
+        <ng-container matColumnDef="city">
+          <th mat-header-cell *matHeaderCellDef>City</th>
+          <td mat-cell *matCellDef="let row">{{row.city}}</td>
+        </ng-container>
+
+        <!--  Column -->
+        <ng-container matColumnDef="installedCapacity">
+          <th mat-header-cell *matHeaderCellDef>Capacity</th>
+          <td mat-cell *matCellDef="let row">{{row.installedCapacity}}</td>
+        </ng-container>
+
+        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+      </table>
+      <mat-paginator [pageSizeOptions]="[5, 10, 25]"></mat-paginator>
+    </div>
+  </div>
+</div>
+<br>

+ 39 - 0
src/app/components/plugins/plants-datatables/plants-datatables.component.scss

@@ -0,0 +1,39 @@
+table {
+  width: 100%;
+}
+
+.mat-form-field {
+  font-size: 14px;
+  width: 100%;
+}
+
+.example-loading-shade {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 56px;
+  right: 0;
+  background: rgba(0, 0, 0, 0.15);
+  z-index: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.example-rate-limit-reached {
+  color: #980000;
+  max-width: 360px;
+  text-align: center;
+}
+
+/* Structure */
+.example-container {
+  position: relative;
+  min-height: 200px;
+}
+
+.example-table-container {
+  position: relative;
+  //max-height: 400px;
+  overflow: auto;
+}

+ 25 - 0
src/app/components/plugins/plants-datatables/plants-datatables.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PlantsDatatablesComponent } from './plants-datatables.component';
+
+describe('PlantsDatatablesComponent', () => {
+  let component: PlantsDatatablesComponent;
+  let fixture: ComponentFixture<PlantsDatatablesComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ PlantsDatatablesComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PlantsDatatablesComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 53 - 0
src/app/components/plugins/plants-datatables/plants-datatables.component.ts

@@ -0,0 +1,53 @@
+import {HttpClient} from '@angular/common/http';
+import { Component, ViewChild, OnInit } from '@angular/core';
+import { Plant } from 'src/app/models/plant';
+import { PlantsService } from 'src/app/services/plants.service';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatSort} from '@angular/material/sort';
+import {merge, Observable, of as observableOf} from 'rxjs';
+import {catchError, map, startWith, switchMap} from 'rxjs/operators';
+import {MatTableDataSource} from '@angular/material/table';
+
+
+@Component({
+  selector: 'app-plants-datatables',
+  templateUrl: './plants-datatables.component.html',
+  styleUrls: ['./plants-datatables.component.scss']
+})
+
+export class PlantsDatatablesComponent implements OnInit {
+  displayedColumns: string[] = ['name','state', 'installDate', 'country', 'city', 'installedCapacity'];
+  //displayedColumns: string[] = ['state'];
+
+
+  listData: Plant[] = [];
+  dataSource = new MatTableDataSource(this.listData);
+
+  resultsLength = 0;
+  isLoadingResults = true;
+  isRateLimitReached = false;
+
+  @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
+  @ViewChild(MatSort, {static: true}) sort: MatSort;
+
+  constructor(private plantsService: PlantsService) {}
+
+  ngOnInit() {
+    this.plantsService.getPlants().subscribe(res =>
+      this.listData = res
+    );
+    this.dataSource.data = this.listData;
+    this.dataSource.paginator = this.paginator;
+    this.dataSource.sort = this.sort;
+  }
+
+
+
+  applyFilter(filterValue: string) {
+    this.dataSource.filter = filterValue.trim().toLowerCase();
+    if (this.dataSource.paginator) {
+      this.dataSource.paginator.firstPage();
+    }
+  }
+
+}

+ 36 - 0
src/app/components/plugins/plants-status/plants-status.component.html

@@ -0,0 +1,36 @@
+<div class="align-container">
+  <mat-expansion-panel>
+    <mat-expansion-panel-header>
+      <mat-panel-title>
+        Status de Plantas
+      </mat-panel-title>
+    </mat-expansion-panel-header>
+
+    <div class="container-fluid">
+      <div class="row">
+        <div *ngFor="let item of listData; index as i;" class="col-lg-3 col-md-4 col-sm-6">
+          <div [ngClass]="item.state == true ? 'card border-success': 'card border-danger'">
+            <div class="card-body">
+              <h5 class="card-title">{{item.name}}</h5>
+              <h6 class="card-subtitle mb-2 text-muted">{{item.city}}, {{item.country}}</h6>
+              <div class="card-text">
+                Status:
+                <div *ngIf="item.state; then thenBlock else elseBlock"></div>
+                <ng-template #thenBlock><span class="badge badge-pill badge-success">&nbsp;</span></ng-template>
+                <ng-template #elseBlock><span class="badge badge-pill badge-danger">&nbsp;</span></ng-template>
+              </div>
+              <br>
+              <p class="card-text">
+                Capacidad instalada: <b>{{item.installedCapacity}} kw</b>
+                <br>
+                Fecha instalación: <b>{{item.installDate}}</b>
+              </p>
+              <a href="#" class="btn btn-sm btn-primary float-right">Ir a planta</a>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </mat-expansion-panel>
+</div>
+<br>

+ 3 - 0
src/app/components/plugins/plants-status/plants-status.component.scss

@@ -0,0 +1,3 @@
+.card {
+  border: 1px solid;
+}

+ 25 - 0
src/app/components/plugins/plants-status/plants-status.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PlantsStatusComponent } from './plants-status.component';
+
+describe('PlantsStatusComponent', () => {
+  let component: PlantsStatusComponent;
+  let fixture: ComponentFixture<PlantsStatusComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ PlantsStatusComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PlantsStatusComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 28 - 0
src/app/components/plugins/plants-status/plants-status.component.ts

@@ -0,0 +1,28 @@
+import { Component, OnInit } from '@angular/core';
+import { Plant } from 'src/app/models/plant';
+import { PlantsService } from 'src/app/services/plants.service';
+
+@Component({
+  selector: 'app-plants-status',
+  templateUrl: './plants-status.component.html',
+  styleUrls: ['./plants-status.component.scss']
+})
+export class PlantsStatusComponent implements OnInit {
+
+  listData: Plant[];
+  plantId: number;
+
+  constructor(private plantsService: PlantsService) {
+  }
+
+  ngOnInit() {
+    this.plantsService.getPlants().subscribe(res => {
+      this.listData = res;
+    });
+  }
+
+  sendPlantId(id: number) {
+    this.plantId = id;
+  }
+
+}

+ 66 - 0
src/app/components/plugins/plugins.module.ts

@@ -0,0 +1,66 @@
+import { NgModule } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { LeafletModule } from "@asymmetrik/ngx-leaflet";
+import { MapsComponent } from './maps/maps.component';
+import { PlantsStatusComponent } from './plants-status/plants-status.component';
+import { PlantDetailComponent } from "./plant-detail/plant-detail.component";
+import {
+  MatButtonModule,
+  MatInputModule,
+  MatRippleModule,
+  MatFormFieldModule,
+  MatTooltipModule,
+  MatSelectModule,
+  MatExpansionModule,
+  MatTableModule,
+  MatPaginatorModule,
+  MatProgressSpinnerModule,
+  MatSortModule
+
+} from '@angular/material';
+import {  } from '@angular/material/table';
+import { PlantsDatatablesComponent } from './plants-datatables/plants-datatables.component';
+
+/*import { BoxModule } from "angular-admin-lte";
+import { LineChartComponent } from "./line-chart/line-chart.component";
+import { ChartsModule } from "ng2-charts";
+import { RealTimeComponent } from "./real-time/real-time.component";
+
+import { IMqttMessage, MqttModule, IMqttServiceOptions } from "ngx-mqtt";
+
+export const MQTT_SERVICE_OPTIONS: IMqttServiceOptions = {
+  hostname: "192.168.100.5",
+  port: 9001,
+  path: "/mqtt"
+};
+*/
+@NgModule({
+  declarations: [
+    MapsComponent,
+    PlantDetailComponent,
+    PlantsStatusComponent,
+    PlantsDatatablesComponent,
+  ],
+  imports: [
+    CommonModule,
+    LeafletModule,
+    MatButtonModule,
+    MatRippleModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatTooltipModule,
+    MatExpansionModule,
+    MatTableModule,
+    MatPaginatorModule,
+    MatProgressSpinnerModule,
+    MatSortModule
+  ],
+  exports: [
+    MapsComponent,
+    PlantDetailComponent,
+    PlantsStatusComponent,
+    PlantsDatatablesComponent,
+  ]
+})
+export class PluginsModule {}

+ 1 - 0
src/app/components/profile/profile.component.html

@@ -0,0 +1 @@
+<p>profile works!</p>

+ 0 - 0
src/app/components/profile/profile.component.scss


+ 25 - 0
src/app/components/profile/profile.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProfileComponent } from './profile.component';
+
+describe('ProfileComponent', () => {
+  let component: ProfileComponent;
+  let fixture: ComponentFixture<ProfileComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ProfileComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ProfileComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 19 - 0
src/app/components/profile/profile.component.ts

@@ -0,0 +1,19 @@
+import { Component, OnInit } from '@angular/core';
+import { Title } from '@angular/platform-browser';
+
+@Component({
+  selector: 'app-profile',
+  templateUrl: './profile.component.html',
+  styleUrls: ['./profile.component.scss']
+})
+export class ProfileComponent implements OnInit {
+
+  title = "Perfil del usuario";
+
+  constructor(private titleService: Title) { }
+
+  ngOnInit() {
+    this.titleService.setTitle(this.title);
+  }
+
+}

+ 1 - 0
src/app/components/shared/footer/footer.component.html

@@ -0,0 +1 @@
+<p>footer works!</p>

+ 0 - 0
src/app/components/shared/footer/footer.component.scss


+ 25 - 0
src/app/components/shared/footer/footer.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+  let component: FooterComponent;
+  let fixture: ComponentFixture<FooterComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ FooterComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(FooterComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 15 - 0
src/app/components/shared/footer/footer.component.ts

@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-footer',
+  templateUrl: './footer.component.html',
+  styleUrls: ['./footer.component.scss']
+})
+export class FooterComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}

+ 46 - 0
src/app/components/shared/navbar/navbar.component.html

@@ -0,0 +1,46 @@
+<nav class="navbar navbar-expand-lg navbar-transparent  navbar-absolute fixed-top">
+  <div class="container-fluid">
+    <div class="navbar-wrapper">
+      <a class="navbar-brand d-none d-lg-block" title="Logout" (click)="menuToggle()">
+
+        <i class="material-icons text_align-center visible-on-sidebar-regular">more_vert</i>
+        <i class="material-icons design_bullet-list-67 visible-on-sidebar-mini">view_list</i>
+      </a>
+      <a class="navbar-brand">{{getTitle()}}</a>
+    </div>
+    <button mat-raised-button class="navbar-toggler" type="button" (click)="sidebarToggle()">
+      <span class="sr-only">Toggle navigation</span>
+      <span class="navbar-toggler-icon icon-bar"></span>
+      <span class="navbar-toggler-icon icon-bar"></span>
+      <span class="navbar-toggler-icon icon-bar"></span>
+    </button>
+    <div class="collapse navbar-collapse justify-content-end" id="navigation">
+      <ul class="navbar-nav">
+        <li class="nav-item">
+          <a class="nav-link" href="javascript:void(0)">
+            <i class="material-icons">dashboard</i>
+            <p>
+              <span class="d-lg-none d-md-block">Stats</span>
+            </p>
+          </a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" title="Account" href="#/profile">
+            <i class="material-icons">person</i>
+            <p>
+              <span class="d-lg-none d-md-block">Account</span>
+            </p>
+          </a>
+        </li>
+        <li class="nav-item">
+          <a class="nav-link" title="Logout" (click)="logout()" href="#">
+            <i class="material-icons">exit_to_app</i>
+            <p>
+              <span class="d-lg-none d-md-block">Logout</span>
+            </p>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+</nav>

+ 0 - 0
src/app/components/shared/navbar/navbar.component.scss


+ 25 - 0
src/app/components/shared/navbar/navbar.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NavbarComponent } from './navbar.component';
+
+describe('NavbarComponent', () => {
+  let component: NavbarComponent;
+  let fixture: ComponentFixture<NavbarComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ NavbarComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NavbarComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 165 - 0
src/app/components/shared/navbar/navbar.component.ts

@@ -0,0 +1,165 @@
+import { Component, OnInit, ElementRef } from '@angular/core';
+import { ROUTES } from '../sidebar/sidebar.component';
+import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
+import { Router } from '@angular/router';
+import { AuthenticationService } from 'src/app/services/authentication.service';
+import { AdminLayoutRoutes } from 'src/app/layouts/admin/admin.routing';
+
+@Component({
+  selector: 'app-navbar',
+  templateUrl: './navbar.component.html',
+  styleUrls: ['./navbar.component.scss']
+})
+export class NavbarComponent implements OnInit {
+    private listTitles: any[];
+
+    private leTitles: any[];
+
+    location: Location;
+      mobile_menu_visible: any = 0;
+    private toggleButton: any;
+    private sidebarVisible: boolean;
+    private sidebarMini: boolean;
+    constructor(location: Location,  private element: ElementRef, private router: Router, private authenticationService: AuthenticationService) {
+      this.location = location;
+      this.sidebarVisible = false;
+      this.sidebarMini = false;
+    }
+
+    ngOnInit(){
+      this.listTitles = ROUTES.filter(listTitle => listTitle);
+      this.leTitles = AdminLayoutRoutes.filter(listTitle => listTitle);
+      const navbar: HTMLElement = this.element.nativeElement;
+      this.toggleButton = navbar.getElementsByClassName('navbar-toggler')[0];
+      this.router.events.subscribe((event) => {
+        this.sidebarClose();
+         var $layer: any = document.getElementsByClassName('close-layer')[0];
+         if ($layer) {
+           $layer.remove();
+           this.mobile_menu_visible = 0;
+         }
+     });
+    }
+
+    sidebarMiniOn() {
+      const toggleButton = this.toggleButton;
+      const body = document.getElementsByTagName('body')[0];
+      setTimeout(function(){
+        toggleButton.classList.add('sidebar-mini');
+      }, 500);
+      body.classList.add('sidebar-mini');
+
+      this.sidebarMini = true;
+    };
+
+    sidebarMiniOff() {
+      const body = document.getElementsByTagName('body')[0];
+      //this.toggleButton.classList.remove('');
+      this.sidebarMini = false;
+      body.classList.remove('sidebar-mini');
+    };
+
+    menuToggle(){
+      const body = document.getElementsByTagName('body')[0];
+      //body.classList.add('sidebar-mini');
+      if (this.sidebarMini === false) {
+          this.sidebarMiniOn();
+      } else {
+          this.sidebarMiniOff();
+      }
+    }
+
+    sidebarOpen() {
+      const toggleButton = this.toggleButton;
+      const body = document.getElementsByTagName('body')[0];
+      setTimeout(function(){
+        toggleButton.classList.add('toggled');
+      }, 500);
+
+      body.classList.add('nav-open');
+
+      this.sidebarVisible = true;
+    };
+    sidebarClose() {
+      const body = document.getElementsByTagName('body')[0];
+      this.toggleButton.classList.remove('toggled');
+      this.sidebarVisible = false;
+      body.classList.remove('nav-open');
+    };
+    sidebarToggle() {
+      // const toggleButton = this.toggleButton;
+      // const body = document.getElementsByTagName('body')[0];
+      var $toggle = document.getElementsByClassName('navbar-toggler')[0];
+
+      if (this.sidebarVisible === false) {
+          this.sidebarOpen();
+      } else {
+          this.sidebarClose();
+      }
+      const body = document.getElementsByTagName('body')[0];
+
+      if (this.mobile_menu_visible == 1) {
+          // $('html').removeClass('nav-open');
+          body.classList.remove('nav-open');
+          if ($layer) {
+              $layer.remove();
+          }
+          setTimeout(function() {
+              $toggle.classList.remove('toggled');
+          }, 400);
+
+          this.mobile_menu_visible = 0;
+      } else {
+        setTimeout(function() {
+            $toggle.classList.add('toggled');
+        }, 430);
+
+        var $layer = document.createElement('div');
+        $layer.setAttribute('class', 'close-layer');
+
+        if (body.querySelectorAll('.main-panel')) {
+          document.getElementsByClassName('main-panel')[0].appendChild($layer);
+        }else if (body.classList.contains('off-canvas-sidebar')) {
+          document.getElementsByClassName('wrapper-full-page')[0].appendChild($layer);
+        }
+
+        setTimeout(function() {
+            $layer.classList.add('visible');
+        }, 100);
+
+        $layer.onclick = function() { //asign a function
+          body.classList.remove('nav-open');
+          this.mobile_menu_visible = 0;
+          $layer.classList.remove('visible');
+          setTimeout(function() {
+            $layer.remove();
+            $toggle.classList.remove('toggled');
+          }, 400);
+        }.bind(this);
+
+        body.classList.add('nav-open');
+        this.mobile_menu_visible = 1;
+      }
+    };
+
+    logout() {
+      this.authenticationService.logout();
+      this.router.navigate(['/login']);
+    }
+
+    getTitle(){
+      var titlee = this.location.prepareExternalUrl(this.location.path());
+      if(titlee.charAt(0) === '#'){
+        titlee = titlee.slice( 1 );
+      }
+
+      for(var item = 0; item < this.leTitles.length; item++){
+        console.log(this.leTitles[1].path);
+
+        if("/"+this.leTitles[item].path === titlee){
+          return this.leTitles[item].data['title'];
+        }
+      }
+      return 'Dashboard';
+    }
+}

+ 25 - 0
src/app/components/shared/shared.module.ts

@@ -0,0 +1,25 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+
+import { FooterComponent } from './footer/footer.component';
+import { NavbarComponent } from './navbar/navbar.component';
+import { SidebarComponent } from './sidebar/sidebar.component';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    RouterModule,
+  ],
+  declarations: [
+    FooterComponent,
+    NavbarComponent,
+    SidebarComponent
+  ],
+  exports: [
+    FooterComponent,
+    NavbarComponent,
+    SidebarComponent
+  ]
+})
+export class SharedModule { }

+ 83 - 0
src/app/components/shared/sidebar/sidebar.component.html

@@ -0,0 +1,83 @@
+<div class="logo">
+  <a class="simple-text logo-mini">
+    <div class="logo-img">
+      <img src="./assets/img/logo-inverlec2.png">
+    </div>
+  </a>
+  <a class="simple-text logo-normal" href="/">Plant viewer</a>
+</div>
+
+<div class="sidebar-wrapper">
+  <div *ngIf="isMobileMenu()">
+    <!--
+    <form class="navbar-form">
+      <span class="bmd-form-group">
+        <div class="input-group no-border">
+          <input type="text" value="" class="form-control" placeholder="Search...">
+          <button mat-raised-button type="submit" class="btn btn-white btn-round btn-just-icon">
+            <i class="material-icons">search</i>
+            <div class="ripple-container"></div>
+          </button>
+        </div>
+      </span>
+    </form> -->
+    <ul class="nav navbar-nav nav-mobile-menu">
+      <li class="nav-item">
+          <a class="nav-link" href="javascript:void(0)">
+              <i class="material-icons">person</i>
+              <p>
+                  <span class="d-lg-none d-md-block">Account</span>
+              </p>
+          </a>
+      </li>
+      <hr>
+    </ul>
+  </div>
+    <ul class="nav">
+      <li routerLinkActive="active" *ngFor="let menuItem of menuItems" class="{{menuItem.class}} nav-item">
+        <a class="nav-link" [routerLink]="[menuItem.path]">
+          <i class="material-icons">{{menuItem.icon}}</i>
+          <p>{{menuItem.title}}</p>
+        </a>
+      </li>
+      <li class="nav-item" routerlinkactive="active">
+        <!----><!---->
+        <a class="nav-link collapsed" data-toggle="collapse" href="#components" aria-expanded="false">
+          <i class="material-icons">business</i>
+          <p>Plantas<b class="caret"></b></p>
+        </a><!---->
+        <div class="collapse" id="components" style="">
+          <ul class="nav"><!---->
+            <li class="nav-item" routerlinkactive="active">
+              <a class="nav-link" href="#/components/buttons"><span class="sidebar-mini">B</span><span class="sidebar-normal">COCESNA ESTACION<br> EL SALVADOR</span></a>
+            </li>
+            <li class="nav-item" routerlinkactive="active">
+              <a class="nav-link" href="#/components/grid"><span class="sidebar-mini">GS</span><span class="sidebar-normal">COCESNA ICCAE</span></a>
+            </li>
+            <li class="nav-item" routerlinkactive="active">
+              <a class="nav-link" href="#/components/panels"><span class="sidebar-mini">P</span><span class="sidebar-normal">CONDUSAL</span></a>
+            </li>
+            <li class="nav-item" routerlinkactive="active">
+              <a class="nav-link" href="#/components/sweet-alert"><span class="sidebar-mini">SA</span><span class="sidebar-normal">NZEB</span></a>
+            </li>
+            <li class="nav-item" routerlinkactive="active">
+              <a class="nav-link" href="#/components/notifications"><span class="sidebar-mini">N</span><span class="sidebar-normal">SIGET GERENCIA<br> ELECTRICIDAD</span></a>
+            </li>
+          </ul>
+        </div>
+      </li>
+      <hr>
+    </ul>
+  <div *ngIf="isMobileMenu()">
+    <ul class="nav navbar-nav nav-mobile-menu-bottom">
+      <li class="nav-item">
+          <a class="nav-link" title="Logout" (click)="logout()" href="#">
+              <i class="material-icons">exit_to_app</i>
+              <p>
+                  <span class="d-lg-none d-md-block">Logout</span>
+              </p>
+          </a>
+      </li>
+    </ul>
+  </div>
+</div>

+ 5 - 0
src/app/components/shared/sidebar/sidebar.component.scss

@@ -0,0 +1,5 @@
+ul.nav.navbar-nav.nav-mobile-menu-bottom {
+  bottom: 0;
+  position: fixed;
+  width: 100%;
+}

+ 25 - 0
src/app/components/shared/sidebar/sidebar.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SidebarComponent } from './sidebar.component';
+
+describe('SidebarComponent', () => {
+  let component: SidebarComponent;
+  let fixture: ComponentFixture<SidebarComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ SidebarComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(SidebarComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 40 - 0
src/app/components/shared/sidebar/sidebar.component.ts

@@ -0,0 +1,40 @@
+import { Component, OnInit } from '@angular/core';
+
+declare const $: any;
+declare interface RouteInfo {
+    path: string;
+    title: string;
+    icon: string;
+    class: string;
+}
+export const ROUTES: RouteInfo[] = [
+    { path: '/dashboard', title: 'Dashboard',  icon: 'dashboard', class: '' },
+    //{ path: '/profile', title: 'Perfil',  icon:'person', class: '' },
+    /*{ path: '/table-list', title: 'Table List',  icon:'content_paste', class: '' },
+    { path: '/typography', title: 'Typography',  icon:'library_books', class: '' },
+    { path: '/icons', title: 'Icons',  icon:'bubble_chart', class: '' },
+    { path: '/maps', title: 'Maps',  icon:'location_on', class: '' },
+    { path: '/notifications', title: 'Notifications',  icon:'notifications', class: '' },
+    { path: '/upgrade', title: 'Upgrade to PRO',  icon:'unarchive', class: 'active-pro' },*/
+];
+
+@Component({
+  selector: 'app-sidebar',
+  templateUrl: './sidebar.component.html',
+  styleUrls: ['./sidebar.component.scss']
+})
+export class SidebarComponent implements OnInit {
+  menuItems: any[];
+
+  constructor() { }
+
+  ngOnInit() {
+    this.menuItems = ROUTES.filter(menuItem => menuItem);
+  }
+  isMobileMenu() {
+      if ($(window).width() > 991) {
+          return false;
+      }
+      return true;
+  };
+}

+ 1156 - 0
src/app/data/measures.data.ts

@@ -0,0 +1,1156 @@
+
+export const measures =
+[
+  {
+    timeStamp: '01/07/2019 08:00',
+    voltA: 119.7390807,
+    voltB: 120.2765039,
+    voltC: 119.960822,
+    ampA: 8.298610002,
+    ampB: 7.945849673,
+    ampC: 8.514370702,
+    potA: 0.993667933,
+    potB: 0.955699019,
+    potC: 1.021390908
+  },
+  {
+    timeStamp: '01/07/2019 08:15',
+    voltA: 119.9870123,
+    voltB: 119.7309436,
+    voltC: 119.6583242,
+    ampA: 8.369239625,
+    ampB: 8.444681313,
+    ampC: 8.067437668,
+    potA: 1.004200058,
+    potB: 1.011089662,
+    potC: 0.965336072
+  },
+  {
+    timeStamp: '01/07/2019 08:30',
+    voltA: 120.1684774,
+    voltB: 119.5073759,
+    voltC: 119.5590522,
+    ampA: 8.041248594,
+    ampB: 7.692842473,
+    ampC: 8.093224654,
+    potA: 0.9663046,
+    potB: 0.919351417,
+    potC: 0.967618269
+  },
+  {
+    timeStamp: '01/07/2019 08:45',
+    voltA: 119.8309905,
+    voltB: 120.1889803,
+    voltC: 119.7625979,
+    ampA: 7.981196995,
+    ampB: 8.376915,
+    ampC: 8.024123368,
+    potA: 0.956394741,
+    potB: 1.006812872,
+    potC: 0.960989861
+  },
+  {
+    timeStamp: '01/07/2019 09:00',
+    voltA: 120.1087781,
+    voltB: 120.1676465,
+    voltC: 120.3920205,
+    ampA: 8.433257739,
+    ampB: 8.290556786,
+    ampC: 8.191510693,
+    potA: 1.012908282,
+    potB: 0.996256697,
+    potC: 0.986192524
+  },
+  {
+    timeStamp: '01/07/2019 09:15',
+    voltA: 120.2371546,
+    voltB: 120.1609551,
+    voltC: 120.4252212,
+    ampA: 8.135410692,
+    ampB: 7.723540203,
+    ampC: 7.846855577,
+    potA: 0.978178633,
+    potB: 0.928067967,
+    potC: 0.944959319
+  },
+  {
+    timeStamp: '01/07/2019 09:30',
+    voltA: 119.8499476,
+    voltB: 120.2740738,
+    voltC: 119.797522,
+    ampA: 7.713774221,
+    ampB: 7.654371921,
+    ampC: 8.111018412,
+    potA: 0.924495436,
+    potB: 0.920622493,
+    potC: 0.971679906
+  },
+  {
+    timeStamp: '01/07/2019 09:45',
+    voltA: 119.5168781,
+    voltB: 119.8598256,
+    voltC: 119.9314394,
+    ampA: 7.675037889,
+    ampB: 8.485398627,
+    ampC: 8.225002463,
+    potA: 0.917296568,
+    potB: 1.0170584,
+    potC: 0.986436385
+  },
+  {
+    timeStamp: '01/07/2019 10:00',
+    voltA: 119.6556857,
+    voltB: 120.2986436,
+    voltC: 119.6104392,
+    ampA: 8.557403428,
+    ampB: 8.39883774,
+    ampC: 8.000328557,
+    potA: 1.023941975,
+    potB: 1.010368788,
+    potC: 0.956922813
+  },
+  {
+    timeStamp: '01/07/2019 10:15',
+    voltA: 119.7905877,
+    voltB: 119.7643394,
+    voltC: 119.7122252,
+    ampA: 7.858166508,
+    ampB: 8.369670833,
+    ampC: 8.364538688,
+    potA: 0.941334384,
+    potB: 1.002388099,
+    potC: 1.001337539
+  },
+  {
+    timeStamp: '01/07/2019 10:30',
+    voltA: 119.7423439,
+    voltB: 119.5924109,
+    voltC: 119.7263251,
+    ampA: 8.002674058,
+    ampB: 8.1536277,
+    ampC: 8.04358054,
+    potA: 0.958258949,
+    potB: 0.975111994,
+    potC: 0.963028339
+  },
+  {
+    timeStamp: '01/07/2019 10:45',
+    voltA: 120.1211981,
+    voltB: 120.2143863,
+    voltC: 119.7599291,
+    ampA: 7.829443029,
+    ampB: 8.469659215,
+    ampC: 8.517541186,
+    potA: 0.940482077,
+    potB: 1.018174885,
+    potC: 1.020060128
+  },
+  {
+    timeStamp: '01/07/2019 11:00',
+    voltA: 119.6293836,
+    voltB: 120.4784154,
+    voltC: 119.5578997,
+    ampA: 8.280388294,
+    ampB: 7.686330711,
+    ampC: 8.345001204,
+    potA: 0.990577747,
+    potB: 0.926036945,
+    potC: 0.997710817
+  },
+  {
+    timeStamp: '01/07/2019 11:15',
+    voltA: 120.1414517,
+    voltB: 119.8457783,
+    voltC: 119.6722689,
+    ampA: 8.203384532,
+    ampB: 8.037544484,
+    ampC: 8.426445173,
+    potA: 0.985566527,
+    potB: 0.963265774,
+    potC: 1.008411813
+  },
+  {
+    timeStamp: '01/07/2019 11:30',
+    voltA: 120.3417341,
+    voltB: 120.2905463,
+    voltC: 120.0251942,
+    ampA: 8.371859475,
+    ampB: 8.297537277,
+    ampC: 7.785112314,
+    potA: 1.007484087,
+    potB: 0.998115292,
+    potC: 0.934409617
+  },
+  {
+    timeStamp: '01/07/2019 11:45',
+    voltA: 119.6305675,
+    voltB: 120.1426996,
+    voltC: 119.6496737,
+    ampA: 8.270770793,
+    ampB: 8.414931024,
+    ampC: 7.788571005,
+    potA: 0.989437004,
+    potB: 1.01099253,
+    potC: 0.93189998
+  },
+  {
+    timeStamp: '01/07/2019 12:00',
+    voltA: 120.0877362,
+    voltB: 120.3293686,
+    voltC: 119.7721854,
+    ampA: 8.580149179,
+    ampB: 8.032269914,
+    ampC: 8.138617673,
+    potA: 1.030370692,
+    potB: 0.966517967,
+    potC: 0.974780025
+  },
+  {
+    timeStamp: '01/07/2019 12:15',
+    voltA: 120.1402415,
+    voltB: 120.2035906,
+    voltC: 119.6292385,
+    ampA: 8.113992716,
+    ampB: 8.098853831,
+    ampC: 8.191165431,
+    potA: 0.974817045,
+    potB: 0.97351131,
+    potC: 0.979902883
+  },
+  {
+    timeStamp: '01/07/2019 12:30',
+    voltA: 119.6964384,
+    voltB: 119.6170746,
+    voltC: 119.7926902,
+    ampA: 7.735932076,
+    ampB: 8.322381222,
+    ampC: 7.857772813,
+    potA: 0.925963518,
+    potB: 0.995498895,
+    potC: 0.941303744
+  },
+  {
+    timeStamp: '01/07/2019 12:45',
+    voltA: 120.4978232,
+    voltB: 120.2358185,
+    voltC: 119.8522057,
+    ampA: 8.003097774,
+    ampB: 8.177814035,
+    ampC: 8.369515341,
+    potA: 0.964355861,
+    potB: 0.983266164,
+    potC: 1.003104874
+  },
+  {
+    timeStamp: '01/07/2019 13:00',
+    voltA: 119.6558108,
+    voltB: 120.096985,
+    voltC: 120.494494,
+    ampA: 7.680847096,
+    ampB: 8.310328339,
+    ampC: 8.565364332,
+    potA: 0.919057987,
+    potB: 0.998045378,
+    potC: 1.032079241
+  },
+  {
+    timeStamp: '01/07/2019 13:15',
+    voltA: 119.7079672,
+    voltB: 119.5716794,
+    voltC: 120.2204705,
+    ampA: 7.78396386,
+    ampB: 7.857896232,
+    ampC: 8.03359088,
+    potA: 0.931802491,
+    potB: 0.939581849,
+    potC: 0.965802076
+  },
+  {
+    timeStamp: '01/07/2019 13:30',
+    voltA: 119.9845481,
+    voltB: 120.2431784,
+    voltC: 119.7680622,
+    ampA: 8.304702507,
+    ampB: 7.755741718,
+    ampC: 7.730635515,
+    potA: 0.996435978,
+    potB: 0.932575035,
+    potC: 0.925883235
+  },
+  {
+    timeStamp: '01/07/2019 13:45',
+    voltA: 120.2669305,
+    voltB: 120.057963,
+    voltC: 119.9687296,
+    ampA: 8.324985122,
+    ampB: 7.606838425,
+    ampC: 7.973678993,
+    potA: 1.001220407,
+    potB: 0.913261526,
+    potC: 0.956592139
+  },
+  {
+    timeStamp: '01/07/2019 14:00',
+    voltA: 119.74827,
+    voltB: 119.5780725,
+    voltC: 120.1737561,
+    ampA: 8.172607618,
+    ampB: 7.737252352,
+    ampC: 8.360145185,
+    potA: 0.978655623,
+    potB: 0.925205723,
+    potC: 1.004670049
+  },
+  {
+    timeStamp: '01/07/2019 14:15',
+    voltA: 119.5168391,
+    voltB: 119.5903676,
+    voltC: 119.7756315,
+    ampA: 8.304771558,
+    ampB: 8.157928242,
+    ampC: 7.70578271,
+    potA: 0.992560046,
+    potB: 0.975609638,
+    potC: 0.92296499
+  },
+  {
+    timeStamp: '01/07/2019 14:30',
+    voltA: 120.3800435,
+    voltB: 120.3412344,
+    voltC: 119.7808908,
+    ampA: 8.25552905,
+    ampB: 8.586068937,
+    ampC: 7.652397213,
+    potA: 0.993800947,
+    potB: 1.033258135,
+    potC: 0.916610955
+  },
+  {
+    timeStamp: '01/07/2019 14:45',
+    voltA: 119.8277738,
+    voltB: 119.6087349,
+    voltC: 119.8098312,
+    ampA: 8.181705373,
+    ampB: 7.746852766,
+    ampC: 7.957378645,
+    potA: 0.980395541,
+    potB: 0.926591259,
+    potC: 0.953372192
+  },
+  {
+    timeStamp: '01/07/2019 15:00',
+    voltA: 120.1085436,
+    voltB: 119.5570275,
+    voltC: 120.2540712,
+    ampA: 8.149618043,
+    ampB: 7.679350509,
+    ampC: 7.796298791,
+    potA: 0.978838754,
+    potB: 0.91812032,
+    potC: 0.93753667
+  },
+  {
+    timeStamp: '01/07/2019 15:15',
+    voltA: 119.5412424,
+    voltB: 119.6394596,
+    voltC: 120.4123223,
+    ampA: 8.497046208,
+    ampB: 7.747009824,
+    ampC: 7.823219791,
+    potA: 1.015747461,
+    potB: 0.926848069,
+    potC: 0.942012063
+  },
+  {
+    timeStamp: '01/07/2019 15:30',
+    voltA: 120.3314413,
+    voltB: 120.1931628,
+    voltC: 120.4664532,
+    ampA: 8.31850107,
+    ampB: 7.787912612,
+    ampC: 7.876764621,
+    potA: 1.000977223,
+    potB: 0.936053848,
+    potC: 0.948885896
+  },
+  {
+    timeStamp: '01/07/2019 15:45',
+    voltA: 119.793045,
+    voltB: 119.6661138,
+    voltC: 119.9124023,
+    ampA: 7.84004361,
+    ampB: 7.763854055,
+    ampC: 7.970842013,
+    potA: 0.939182697,
+    potB: 0.929070243,
+    potC: 0.955802814
+  },
+  {
+    timeStamp: '01/07/2019 16:00',
+    voltA: 119.7323653,
+    voltB: 120.4193121,
+    voltC: 119.5280832,
+    ampA: 7.906334399,
+    ampB: 8.450726561,
+    ampC: 8.038403734,
+    potA: 0.946644118,
+    potB: 1.017630679,
+    potC: 0.96081499
+  },
+  {
+    timeStamp: '01/07/2019 16:15',
+    voltA: 120.2798582,
+    voltB: 119.918841,
+    voltC: 120.2472121,
+    ampA: 8.064620583,
+    ampB: 8.174804458,
+    ampC: 8.050648786,
+    potA: 0.97001142,
+    potB: 0.980313076,
+    potC: 0.968068072
+  },
+  {
+    timeStamp: '01/07/2019 16:30',
+    voltA: 119.5601301,
+    voltB: 119.9967716,
+    voltC: 120.3808259,
+    ampA: 8.561746312,
+    ampB: 7.87218038,
+    ampC: 7.690536773,
+    potA: 1.023643503,
+    potB: 0.944636231,
+    potC: 0.925793168
+  },
+  {
+    timeStamp: '01/07/2019 16:45',
+    voltA: 120.465634,
+    voltB: 119.8649406,
+    voltC: 120.2474182,
+    ampA: 8.2111473,
+    ampB: 7.69115041,
+    ampC: 8.437320922,
+    potA: 0.989161065,
+    potB: 0.921899287,
+    potC: 1.014566057
+  },
+  {
+    timeStamp: '01/07/2019 17:00',
+    voltA: 120.0743654,
+    voltB: 119.7873669,
+    voltC: 119.7470981,
+    ampA: 7.739799479,
+    ampB: 7.935529215,
+    ampC: 8.487115976,
+    potA: 0.929351511,
+    potB: 0.95057615,
+    potC: 1.016307509
+  },
+  {
+    timeStamp: '01/07/2019 17:15',
+    voltA: 120.4065283,
+    voltB: 120.2442517,
+    voltC: 119.6994581,
+    ampA: 7.615118974,
+    ampB: 7.801620989,
+    ampC: 8.140134559,
+    potA: 0.916910038,
+    potB: 0.938100078,
+    potC: 0.974369695
+  },
+  {
+    timeStamp: '01/07/2019 17:30',
+    voltA: 119.5377785,
+    voltB: 120.2138092,
+    voltC: 119.6129066,
+    ampA: 8.280217707,
+    ampB: 7.787435098,
+    ampC: 8.350187185,
+    potA: 0.98979883,
+    potB: 0.936157237,
+    potC: 0.99879016
+  },
+  {
+    timeStamp: '01/07/2019 17:45',
+    voltA: 120.1424669,
+    voltB: 119.5803089,
+    voltC: 119.7494841,
+    ampA: 7.99929817,
+    ampB: 8.563020538,
+    ampC: 8.103274852,
+    potA: 0.961055415,
+    potB: 1.023968641,
+    potC: 0.970362983
+  },
+  {
+    timeStamp: '01/07/2019 18:00',
+    voltA: 120.0084693,
+    voltB: 119.9645292,
+    voltC: 120.464352,
+    ampA: 8.558594079,
+    ampB: 7.719110333,
+    ampC: 8.213837334,
+    potA: 1.027103775,
+    potB: 0.926019437,
+    potC: 0.989474592
+  },
+  {
+    timeStamp: '01/07/2019 18:15',
+    voltA: 119.6157037,
+    voltB: 119.5548642,
+    voltC: 119.619822,
+    ampA: 8.047110082,
+    ampB: 8.383712732,
+    ampC: 8.015683245,
+    potA: 0.962560735,
+    potB: 1.002313637,
+    potC: 0.958834603
+  },
+  {
+    timeStamp: '01/07/2019 18:30',
+    voltA: 119.8592425,
+    voltB: 120.0794147,
+    voltC: 119.654828,
+    ampA: 8.29583171,
+    ampB: 8.232853951,
+    ampC: 7.6640015,
+    potA: 0.994332104,
+    potB: 0.988596284,
+    potC: 0.917034782
+  },
+  {
+    timeStamp: '01/07/2019 18:45',
+    voltA: 119.9155712,
+    voltB: 119.762406,
+    voltC: 120.3287262,
+    ampA: 8.363807146,
+    ampB: 8.043935002,
+    ampC: 8.542191207,
+    potA: 1.002950712,
+    potB: 0.963361009,
+    potC: 1.027870987
+  },
+  {
+    timeStamp: '01/07/2019 19:00',
+    voltA: 120.1321289,
+    voltB: 119.5632268,
+    voltC: 119.7514064,
+    ampA: 8.214858448,
+    ampB: 7.990141864,
+    ampC: 8.544631476,
+    potA: 0.986868434,
+    potB: 0.955327144,
+    potC: 1.023231637
+  },
+  {
+    timeStamp: '01/07/2019 19:15',
+    voltA: 119.9932168,
+    voltB: 120.380272,
+    voltC: 120.0635866,
+    ampA: 7.69965709,
+    ampB: 7.714840009,
+    ampC: 7.829990995,
+    potA: 0.923906622,
+    potB: 0.928714539,
+    potC: 0.940096802
+  },
+  {
+    timeStamp: '01/07/2019 19:30',
+    voltA: 119.546786,
+    voltB: 119.6778098,
+    voltC: 120.4133033,
+    ampA: 8.522908796,
+    ampB: 7.62940879,
+    ampC: 7.929995714,
+    potA: 1.018886354,
+    potB: 0.913070934,
+    potC: 0.954876979
+  },
+  {
+    timeStamp: '01/07/2019 19:45',
+    voltA: 120.4743747,
+    voltB: 120.1097096,
+    voltC: 120.1209818,
+    ampA: 8.513101445,
+    ampB: 7.68981119,
+    ampC: 7.759634562,
+    potA: 1.025610573,
+    potB: 0.923620989,
+    potC: 0.932094922
+  },
+  {
+    timeStamp: '01/07/2019 20:00',
+    voltA: 120.0741533,
+    voltB: 120.3153217,
+    voltC: 119.8730588,
+    ampA: 7.942247038,
+    ampB: 7.736034,
+    ampC: 7.759456731,
+    potA: 0.953658589,
+    potB: 0.930763419,
+    potC: 0.930149813
+  },
+  {
+    timeStamp: '01/07/2019 20:15',
+    voltA: 120.2239739,
+    voltB: 120.3235503,
+    voltC: 120.3734834,
+    ampA: 8.244394025,
+    ampB: 8.441339818,
+    ampC: 8.415803384,
+    potA: 0.991173812,
+    potB: 1.015691976,
+    potC: 1.013039569
+  },
+  {
+    timeStamp: '01/07/2019 20:30',
+    voltA: 119.8046092,
+    voltB: 119.8652766,
+    voltC: 119.8544465,
+    ampA: 7.679419234,
+    ampB: 8.38384035,
+    ampC: 8.372880134,
+    potA: 0.92002982,
+    potB: 1.004931342,
+    potC: 1.003526914
+  },
+  {
+    timeStamp: '01/07/2019 20:45',
+    voltA: 120.2170294,
+    voltB: 120.1605695,
+    voltC: 120.4301739,
+    ampA: 8.256348165,
+    ampB: 7.617517054,
+    ampC: 8.414940111,
+    potA: 0.99255365,
+    potB: 0.915325188,
+    potC: 1.013412701
+  },
+  {
+    timeStamp: '01/07/2019 21:00',
+    voltA: 119.5622114,
+    voltB: 120.4035675,
+    voltC: 120.4107742,
+    ampA: 8.028183553,
+    ampB: 8.175181191,
+    ampC: 8.467189294,
+    potA: 0.959867379,
+    potB: 0.98432098,
+    potC: 1.019540818
+  },
+  {
+    timeStamp: '01/07/2019 21:15',
+    voltA: 120.1419284,
+    voltB: 119.7953607,
+    voltC: 120.0110183,
+    ampA: 7.63716167,
+    ampB: 8.008278579,
+    ampC: 7.8798718,
+    potA: 0.91754333,
+    potB: 0.959354621,
+    potC: 0.945671439
+  },
+  {
+    timeStamp: '01/07/2019 21:30',
+    voltA: 120.1664074,
+    voltB: 120.3783845,
+    voltC: 120.4645713,
+    ampA: 8.563912836,
+    ampB: 7.942652571,
+    ampC: 7.950675349,
+    potA: 1.029094639,
+    potB: 0.956123685,
+    potC: 0.957774698
+  },
+  {
+    timeStamp: '01/07/2019 21:45',
+    voltA: 120.0695568,
+    voltB: 119.5949386,
+    voltC: 119.6322366,
+    ampA: 7.668242582,
+    ampB: 8.036504037,
+    ampC: 7.607549069,
+    potA: 0.920722489,
+    potB: 0.961125207,
+    potC: 0.91010811
+  },
+  {
+    timeStamp: '01/07/2019 22:00',
+    voltA: 120.2190663,
+    voltB: 120.2175821,
+    voltC: 120.2133955,
+    ampA: 7.940432487,
+    ampB: 8.001693419,
+    ampC: 7.869310602,
+    potA: 0.95459138,
+    potB: 0.961944236,
+    potC: 0.945996548
+  },
+  {
+    timeStamp: '01/07/2019 22:15',
+    voltA: 120.2119085,
+    voltB: 120.2023055,
+    voltC: 120.4605715,
+    ampA: 8.261095237,
+    ampB: 8.057871181,
+    ampC: 7.779037787,
+    potA: 0.993082025,
+    potB: 0.968574694,
+    potC: 0.937067337
+  },
+  {
+    timeStamp: '01/07/2019 22:30',
+    voltA: 119.9383541,
+    voltB: 119.7352387,
+    voltC: 120.0657684,
+    ampA: 8.239228244,
+    ampB: 8.185380492,
+    ampC: 8.070631069,
+    potA: 0.988199475,
+    potB: 0.980078487,
+    potC: 0.969006521
+  },
+  {
+    timeStamp: '01/07/2019 22:45',
+    voltA: 120.1855991,
+    voltB: 120.1655025,
+    voltC: 119.8572007,
+    ampA: 7.787723902,
+    ampB: 8.558258213,
+    ampC: 8.17080926,
+    potA: 0.935972263,
+    potB: 1.028407399,
+    potC: 0.979330325
+  },
+  {
+    timeStamp: '01/07/2019 23:00',
+    voltA: 119.7116243,
+    voltB: 120.1499067,
+    voltC: 120.3592458,
+    ampA: 8.372104676,
+    ampB: 8.450115482,
+    ampC: 8.326996848,
+    potA: 1.00223825,
+    potB: 1.015280587,
+    potC: 1.00223106
+  },
+  {
+    timeStamp: '01/07/2019 23:15',
+    voltA: 120.2248038,
+    voltB: 119.9488418,
+    voltC: 120.1121545,
+    ampA: 7.737300451,
+    ampB: 7.834887726,
+    ampC: 7.675338368,
+    potA: 0.930215428,
+    potB: 0.939785708,
+    potC: 0.921901428
+  },
+  {
+    timeStamp: '01/07/2019 23:30',
+    voltA: 120.4210381,
+    voltB: 119.5459634,
+    voltC: 120.3472818,
+    ampA: 8.09559559,
+    ampB: 7.895474264,
+    ampC: 7.707666542,
+    potA: 0.974880025,
+    potB: 0.943872077,
+    potC: 0.927596717
+  },
+  {
+    timeStamp: '01/07/2019 23:45',
+    voltA: 119.9086012,
+    voltB: 120.0287604,
+    voltC: 120.4677751,
+    ampA: 8.300230186,
+    ampB: 8.530651577,
+    ampC: 7.764114409,
+    potA: 0.995268991,
+    potB: 1.023923534,
+    potC: 0.935325589
+  },
+  {
+    timeStamp: '02/07/2019 00:00',
+    voltA: 119.6758343,
+    voltB: 119.9839163,
+    voltC: 120.4083294,
+    ampA: 8.573380938,
+    ampB: 7.933739968,
+    ampC: 7.99173348,
+    potA: 1.026026516,
+    potB: 0.951921192,
+    potC: 0.962271277
+  },
+  {
+    timeStamp: '02/07/2019 00:15',
+    voltA: 119.7449095,
+    voltB: 120.1842537,
+    voltC: 120.2569011,
+    ampA: 8.513941365,
+    ampB: 8.400479457,
+    ampC: 8.061198514,
+    potA: 1.019501138,
+    potB: 1.009605354,
+    potC: 0.969414752
+  },
+  {
+    timeStamp: '02/07/2019 00:30',
+    voltA: 119.5051228,
+    voltB: 120.4200298,
+    voltC: 119.5875464,
+    ampA: 7.764791736,
+    ampB: 8.452846555,
+    ampC: 8.306463229,
+    potA: 0.92793239,
+    potB: 1.017892034,
+    potC: 0.993349557
+  },
+  {
+    timeStamp: '02/07/2019 00:45',
+    voltA: 119.6434441,
+    voltB: 120.289076,
+    voltC: 119.6089338,
+    ampA: 8.153641305,
+    ampB: 8.044816795,
+    ampC: 8.506909817,
+    potA: 0.975529728,
+    potB: 0.967703579,
+    potC: 1.017502413
+  },
+  {
+    timeStamp: '02/07/2019 01:00',
+    voltA: 119.6638822,
+    voltB: 120.110746,
+    voltC: 120.0281535,
+    ampA: 8.039641803,
+    ampB: 7.706499016,
+    ampC: 7.919660232,
+    potA: 0.96205475,
+    potB: 0.925633346,
+    potC: 0.950582194
+  },
+  {
+    timeStamp: '02/07/2019 01:15',
+    voltA: 119.8429521,
+    voltB: 120.1533916,
+    voltC: 119.5648204,
+    ampA: 7.745299173,
+    ampB: 7.937762076,
+    ampC: 7.843898644,
+    potA: 0.928219518,
+    potB: 0.953749036,
+    potC: 0.937854333
+  },
+  {
+    timeStamp: '02/07/2019 01:30',
+    voltA: 119.5951497,
+    voltB: 119.7396335,
+    voltC: 120.2410349,
+    ampA: 7.601370931,
+    ampB: 7.800460459,
+    ampC: 7.963549521,
+    potA: 0.909087094,
+    potB: 0.934024276,
+    potC: 0.957545435
+  },
+  {
+    timeStamp: '02/07/2019 01:45',
+    voltA: 120.2401594,
+    voltB: 119.5503519,
+    voltC: 120.4010556,
+    ampA: 8.183164676,
+    ampB: 8.073688439,
+    ampC: 8.033608607,
+    potA: 0.983945025,
+    potB: 0.965212294,
+    potC: 0.967254956
+  },
+  {
+    timeStamp: '02/07/2019 02:00',
+    voltA: 119.8789107,
+    voltB: 120.2530453,
+    voltC: 119.9720496,
+    ampA: 7.880575408,
+    ampB: 8.354172888,
+    ampC: 7.793969664,
+    potA: 0.944714795,
+    potB: 1.004614731,
+    potC: 0.935058515
+  },
+  {
+    timeStamp: '02/07/2019 02:15',
+    voltA: 119.5586519,
+    voltB: 119.7683466,
+    voltC: 120.0008385,
+    ampA: 8.309846521,
+    ampB: 7.738186359,
+    ampC: 8.345211091,
+    potA: 0.993514047,
+    potB: 0.926789786,
+    potC: 1.001432328
+  },
+  {
+    timeStamp: '02/07/2019 02:30',
+    voltA: 120.4412887,
+    voltB: 119.906235,
+    voltC: 120.3912186,
+    ampA: 7.93325543,
+    ampB: 8.379327194,
+    ampC: 7.601621946,
+    potA: 0.955491508,
+    potB: 1.004733576,
+    potC: 0.915168529
+  },
+  {
+    timeStamp: '02/07/2019 02:45',
+    voltA: 119.7571574,
+    voltB: 119.7804651,
+    voltC: 119.6224323,
+    ampA: 7.694975102,
+    ampB: 7.966682409,
+    ampC: 8.435552012,
+    potA: 0.921528345,
+    potB: 0.954252924,
+    potC: 1.009081249
+  },
+  {
+    timeStamp: '02/07/2019 03:00',
+    voltA: 119.9961163,
+    voltB: 119.7916406,
+    voltC: 119.9872402,
+    ampA: 7.879171501,
+    ampB: 7.676266882,
+    ampC: 7.714848256,
+    potA: 0.94546998,
+    potB: 0.919552603,
+    potC: 0.925683351
+  },
+  {
+    timeStamp: '02/07/2019 03:15',
+    voltA: 120.151551,
+    voltB: 119.6276784,
+    voltC: 119.9071604,
+    ampA: 7.644491918,
+    ampB: 8.344710406,
+    ampC: 8.589926186,
+    potA: 0.91849756,
+    potB: 0.998258333,
+    potC: 1.029993657
+  },
+  {
+    timeStamp: '02/07/2019 03:30',
+    voltA: 120.0232041,
+    voltB: 119.6672949,
+    voltC: 119.5549,
+    ampA: 8.542615829,
+    ampB: 7.686881329,
+    ampC: 8.249087975,
+    potA: 1.025312123,
+    potB: 0.919868295,
+    potC: 0.986218888
+  },
+  {
+    timeStamp: '02/07/2019 03:45',
+    voltA: 120.1373904,
+    voltB: 119.9299102,
+    voltC: 120.3509784,
+    ampA: 7.765469826,
+    ampB: 8.355058778,
+    ampC: 8.45540511,
+    potA: 0.93292328,
+    potB: 1.002021449,
+    potC: 1.017616278
+  },
+  {
+    timeStamp: '02/07/2019 04:00',
+    voltA: 120.2129395,
+    voltB: 119.7667682,
+    voltC: 119.5022041,
+    ampA: 8.524914602,
+    ampB: 8.479344976,
+    ampC: 8.396855305,
+    potA: 1.024805043,
+    potB: 1.015543744,
+    potC: 1.003442716
+  },
+  {
+    timeStamp: '02/07/2019 04:15',
+    voltA: 119.6584152,
+    voltB: 119.7092319,
+    voltC: 120.2214419,
+    ampA: 8.280739598,
+    ampB: 8.513057001,
+    ampC: 8.193140578,
+    potA: 0.990860177,
+    potB: 1.019091515,
+    potC: 0.984991174
+  },
+  {
+    timeStamp: '02/07/2019 04:30',
+    voltA: 120.3256618,
+    voltB: 120.0883054,
+    voltC: 120.4980127,
+    ampA: 8.109035111,
+    ampB: 8.232462075,
+    ampC: 8.204938038,
+    potA: 0.975725016,
+    potB: 0.98862242,
+    potC: 0.988678728
+  },
+  {
+    timeStamp: '02/07/2019 04:45',
+    voltA: 120.3067336,
+    voltB: 120.3909391,
+    voltC: 120.356172,
+    ampA: 8.461708613,
+    ampB: 8.475638895,
+    ampC: 7.812800745,
+    potA: 1.018000524,
+    potB: 1.020390126,
+    potC: 0.94031879
+  },
+  {
+    timeStamp: '02/07/2019 05:00',
+    voltA: 120.3196001,
+    voltB: 120.2967644,
+    voltC: 119.8183658,
+    ampA: 7.819017311,
+    ampB: 8.173920405,
+    ampC: 8.069328008,
+    potA: 0.940781036,
+    potB: 0.983296177,
+    potC: 0.966853695
+  },
+  {
+    timeStamp: '02/07/2019 05:15',
+    voltA: 119.9844861,
+    voltB: 120.0431692,
+    voltC: 119.6379268,
+    ampA: 7.715763066,
+    ampB: 7.767284867,
+    ampC: 8.282056011,
+    potA: 0.925771866,
+    potB: 0.932409492,
+    potC: 0.990848011
+  },
+  {
+    timeStamp: '02/07/2019 05:30',
+    voltA: 119.5105623,
+    voltB: 119.7881525,
+    voltC: 120.4030919,
+    ampA: 7.754281902,
+    ampB: 7.852347053,
+    ampC: 7.831325082,
+    potA: 0.92671859,
+    potB: 0.940618147,
+    potC: 0.942915754
+  },
+  {
+    timeStamp: '02/07/2019 05:45',
+    voltA: 119.7613177,
+    voltB: 119.6791026,
+    voltC: 120.0354846,
+    ampA: 8.433906156,
+    ampB: 7.728545582,
+    ampC: 7.779055461,
+    potA: 1.010055715,
+    potB: 0.9249454,
+    potC: 0.933762692
+  },
+  {
+    timeStamp: '02/07/2019 06:00',
+    voltA: 119.6791608,
+    voltB: 119.5906486,
+    voltC: 119.8744636,
+    ampA: 7.86035021,
+    ampB: 8.270915426,
+    ampC: 8.045577386,
+    potA: 0.940720117,
+    potB: 0.98912414,
+    potC: 0.964459273
+  },
+  {
+    timeStamp: '02/07/2019 06:15',
+    voltA: 119.8033928,
+    voltB: 119.7537999,
+    voltC: 120.4641419,
+    ampA: 8.293526208,
+    ampB: 7.784105873,
+    ampC: 7.661766165,
+    potA: 0.993592578,
+    potB: 0.932176257,
+    potC: 0.922968087
+  },
+  {
+    timeStamp: '02/07/2019 06:30',
+    voltA: 120.4167142,
+    voltB: 119.5334602,
+    voltC: 119.6298029,
+    ampA: 8.007350561,
+    ampB: 8.218779801,
+    ampC: 8.283234293,
+    potA: 0.964218844,
+    potB: 0.982419188,
+    potC: 0.990921686
+  },
+  {
+    timeStamp: '02/07/2019 06:45',
+    voltA: 119.6263509,
+    voltB: 119.5398697,
+    voltC: 119.5134235,
+    ampA: 7.819281731,
+    ampB: 7.706564762,
+    ampC: 8.536939607,
+    potA: 0.93539214,
+    potB: 0.921241748,
+    potC: 1.020278879
+  },
+  {
+    timeStamp: '02/07/2019 07:00',
+    voltA: 119.502882,
+    voltB: 119.549509,
+    voltC: 120.3265325,
+    ampA: 7.832379946,
+    ampB: 8.302481128,
+    ampC: 8.511183399,
+    potA: 0.935991977,
+    potB: 0.992557543,
+    potC: 1.024121186
+  },
+  {
+    timeStamp: '02/07/2019 07:15',
+    voltA: 120.4348818,
+    voltB: 120.274579,
+    voltC: 120.0106768,
+    ampA: 7.96762779,
+    ampB: 8.481516371,
+    ampC: 8.325235963,
+    potA: 0.959580311,
+    potB: 1.020110811,
+    potC: 0.999117203
+  },
+  {
+    timeStamp: '02/07/2019 07:30',
+    voltA: 120.3938575,
+    voltB: 119.6237839,
+    voltC: 119.6513059,
+    ampA: 8.45993605,
+    ampB: 7.822786688,
+    ampC: 7.968046502,
+    potA: 1.018524335,
+    potB: 0.935791344,
+    potC: 0.95338717
+  },
+  {
+    timeStamp: '02/07/2019 07:45',
+    voltA: 120.3998004,
+    voltB: 119.7877279,
+    voltC: 119.7792491,
+    ampA: 8.314715293,
+    ampB: 7.868113425,
+    ampC: 8.215350092,
+    potA: 1.001090062,
+    potB: 0.94250343,
+    potC: 0.984028465
+  }
+]

+ 18 - 0
src/app/data/measures.ts

@@ -0,0 +1,18 @@
+import { Observable } from 'rxjs';
+
+export interface Measure {
+  timeStamp: string;
+  voltA: number;
+  voltB: number;
+  voltC: number;
+  ampA: number;
+  ampB: number;
+  ampC: number;
+  potA: number;
+  potB: number;
+  potC: number;
+}
+
+export abstract class MeasureData {
+  abstract getMeasures(): Observable<Measure[]>;
+}

+ 191 - 0
src/app/data/plants.data.ts

@@ -0,0 +1,191 @@
+import { Plant } from 'src/app/models/plant';
+
+export const plantsData: Plant[] = [
+  {
+    id: 0,
+    name: 'Exozent',
+    state: true,
+    installDate: '2018-06-06',
+    country: 'El Salvador',
+    city: 'Zacatecoluca',
+    latitude: 13.49,
+    longitude: -88.95,
+    installedCapacity: 60
+  },
+  {
+    id: 1,
+    name: 'Norsup',
+    state: true,
+    installDate: '2019-01-13',
+    country: 'El Salvador',
+    city: 'San Miguel',
+    latitude: 13.5,
+    longitude: -88.93,
+    installedCapacity: 239
+  },
+  {
+    id: 2,
+    name: 'Digique',
+    state: true,
+    installDate: '2018-08-20',
+    country: 'El Salvador',
+    city: 'San Salvador',
+    latitude: 13.66,
+    longitude: -88.93,
+    installedCapacity: 269
+  },
+  {
+    id: 3,
+    name: 'Zeam',
+    state: false,
+    installDate: '2018-05-14',
+    country: 'El Salvador',
+    city: 'La Union',
+    latitude: 13.44,
+    longitude: -88.99,
+    installedCapacity: 21
+  },
+  {
+    id: 4,
+    name: 'Bitrex',
+    state: true,
+    installDate: '2019-05-31',
+    country: 'El Salvador',
+    city: 'Zacatecoluca',
+    latitude: 13.47,
+    longitude: -88.85,
+    installedCapacity: 77
+  },
+  {
+    id: 5,
+    name: 'Polaria',
+    state: false,
+    installDate: '2018-11-27',
+    country: 'El Salvador',
+    city: 'San Miguel',
+    latitude: 13.54,
+    longitude: -88.95,
+    installedCapacity: 56
+  },
+  {
+    id: 6,
+    name: 'Velity',
+    state: false,
+    installDate: '2018-09-11',
+    country: 'El Salvador',
+    city: 'La Union',
+    latitude: 13.63,
+    longitude: -89.03,
+    installedCapacity: 111
+  },
+  {
+    id: 7,
+    name: 'Farmex',
+    state: true,
+    installDate: '2018-11-05',
+    country: 'El Salvador',
+    city: 'San Vicente',
+    latitude: 13.62,
+    longitude: -89.01,
+    installedCapacity: 140
+  },
+  {
+    id: 8,
+    name: 'Wazzu',
+    state: false,
+    installDate: '2019-05-04',
+    country: 'El Salvador',
+    city: 'San Vicente',
+    latitude: 13.64,
+    longitude: -88.86,
+    installedCapacity: 4
+  },
+  {
+    id: 9,
+    name: 'Olympix',
+    state: true,
+    installDate: '2018-06-10',
+    country: 'El Salvador',
+    city: 'San Miguel',
+    latitude: 13.6,
+    longitude: -88.82,
+    installedCapacity: 131
+  },
+  {
+    id: 10,
+    name: 'Fossiel',
+    state: false,
+    installDate: '2018-02-19',
+    country: 'El Salvador',
+    city: 'Zacatecoluca',
+    latitude: 13.62,
+    longitude: -88.92,
+    installedCapacity: 69
+  },
+  {
+    id: 11,
+    name: 'Immunics',
+    state: true,
+    installDate: '2018-10-17',
+    country: 'El Salvador',
+    city: 'Zacatecoluca',
+    latitude: 13.62,
+    longitude: -88.88,
+    installedCapacity: 136
+  },
+  {
+    id: 12,
+    name: 'Uberlux',
+    state: false,
+    installDate: '2019-02-28',
+    country: 'El Salvador',
+    city: 'San Vicente',
+    latitude: 13.52,
+    longitude: -88.94,
+    installedCapacity: 228
+  },
+  {
+    id: 13,
+    name: 'Vitricomp',
+    state: false,
+    installDate: '2018-05-11',
+    country: 'El Salvador',
+    city: 'La Union',
+    latitude: 13.48,
+    longitude: -88.76,
+    installedCapacity: 118
+  },
+  {
+    id: 14,
+    name: 'Cormoran',
+    state: false,
+    installDate: '2018-07-10',
+    country: 'El Salvador',
+    city: 'San Salvador',
+    latitude: 13.45,
+    longitude: -88.89,
+    installedCapacity: 14
+  },
+  {
+    id: 15,
+    name: 'Quarmony',
+    state: false,
+    installDate: '2019-03-27',
+    country: 'El Salvador',
+    city: 'San Miguel',
+    latitude: 13.64,
+    longitude: -88.74,
+    installedCapacity: 263
+  },
+  {
+    id: 16,
+    name: 'Bostonic',
+    state: false,
+    installDate: '2019-02-17',
+    country: 'El Salvador',
+    city: 'Zacatecoluca',
+    latitude: 13.47,
+    longitude: -88.82,
+    installedCapacity: 204
+  }
+];

+ 31 - 0
src/app/helpers/auth.guard.ts

@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
+
+import { AuthenticationService } from '@app/services/authentication.service';
+
+@Injectable({ providedIn: 'root' })
+export class AuthGuard implements CanActivate {
+    constructor(
+        private router: Router,
+        private authenticationService: AuthenticationService
+    ) { }
+
+    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+        const currentUser = this.authenticationService.currentUserValue;
+        if (currentUser) {
+            // check if route is restricted by role
+            if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
+                // role not authorised so redirect to home page
+                this.router.navigate(['/']);
+                return false;
+            }
+
+            // authorised so return true
+            return true;
+        }
+
+        // not logged in so redirect to login page with the return url
+        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
+        return false;
+    }
+}

+ 24 - 0
src/app/helpers/error.interceptor.ts

@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable, throwError } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+
+import { AuthenticationService } from '../services/authentication.service';
+
+@Injectable()
+export class ErrorInterceptor implements HttpInterceptor {
+    constructor(private authenticationService: AuthenticationService) { }
+
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
+        return next.handle(request).pipe(catchError(err => {
+            if ([401, 403].indexOf(err.status) !== -1) {
+                // auto logout if 401 Unauthorized or 403 Forbidden response returned from api
+                this.authenticationService.logout();
+                location.reload(true);
+            }
+
+            const error = err.error.message || err.statusText;
+            return throwError(error);
+        }))
+    }
+}

+ 112 - 0
src/app/helpers/fake-backend.ts

@@ -0,0 +1,112 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
+import { Observable, of, throwError } from 'rxjs';
+import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
+
+import { User, Role } from '../models';
+
+const users: User[] = [
+    { id: 1, username: 'admin', password: 'admin', firstName: 'Admin', lastName: 'User', role: Role.Admin },
+    { id: 2, username: 'user', password: 'user', firstName: 'Normal', lastName: 'User', role: Role.User }
+];
+
+@Injectable()
+export class FakeBackendInterceptor implements HttpInterceptor {
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
+        const { url, method, headers, body } = request;
+
+        // wrap in delayed observable to simulate server api call
+        return of(null)
+            .pipe(mergeMap(handleRoute))
+            .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
+            .pipe(delay(500))
+            .pipe(dematerialize());
+
+        function handleRoute() {
+            switch (true) {
+                case url.endsWith('/users/authenticate') && method === 'POST':
+                    return authenticate();
+                case url.endsWith('/users') && method === 'GET':
+                    return getUsers();
+                case url.match(/\/users\/\d+$/) && method === 'GET':
+                    return getUserById();
+                default:
+                    // pass through any requests not handled above
+                    return next.handle(request);
+            }
+
+        }
+
+        // route functions
+
+        function authenticate() {
+            const { username, password } = body;
+            const user = users.find(x => x.username === username && x.password === password);
+            if (!user) return error('Username or password is incorrect');
+            return ok({
+                id: user.id,
+                username: user.username,
+                firstName: user.firstName,
+                lastName: user.lastName,
+                role: user.role,
+                token: `fake-jwt-token.${user.id}`
+            });
+        }
+
+        function getUsers() {
+            if (!isAdmin()) return unauthorized();
+            return ok(users);
+        }
+
+        function getUserById() {
+            if (!isLoggedIn()) return unauthorized();
+
+            // only admins can access other user records
+            if (!isAdmin() && currentUser().id !== idFromUrl()) return unauthorized();
+
+            const user = users.find(x => x.id === idFromUrl());
+            return ok(user);
+        }
+
+        // helper functions
+
+        function ok(body) {
+            return of(new HttpResponse({ status: 200, body }));
+        }
+
+        function unauthorized() {
+            return throwError({ status: 401, error: { message: 'unauthorized' } });
+        }
+
+        function error(message) {
+            return throwError({ status: 400, error: { message } });
+        }
+
+        function isLoggedIn() {
+            const authHeader = headers.get('Authorization') || '';
+            return authHeader.startsWith('Bearer fake-jwt-token');
+        }
+
+        function isAdmin() {
+            return isLoggedIn() && currentUser().role === Role.Admin;
+        }
+
+        function currentUser() {
+            if (!isLoggedIn()) return;
+            const id = parseInt(headers.get('Authorization').split('.')[1]);
+            return users.find(x => x.id === id);
+        }
+
+        function idFromUrl() {
+            const urlParts = url.split('/');
+            return parseInt(urlParts[urlParts.length - 1]);
+        }
+    }
+}
+
+export const fakeBackendProvider = {
+    // use fake backend in place of Http service for backend-less development
+    provide: HTTP_INTERCEPTORS,
+    useClass: FakeBackendInterceptor,
+    multi: true
+};

+ 4 - 0
src/app/helpers/index.ts

@@ -0,0 +1,4 @@
+export * from './auth.guard';
+export * from './error.interceptor';
+export * from './fake-backend';
+export * from './jwt.interceptor';

+ 27 - 0
src/app/helpers/jwt.interceptor.ts

@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+import { environment } from '@environments/environment';
+import { AuthenticationService } from '@app/services/authentication.service';
+
+@Injectable()
+export class JwtInterceptor implements HttpInterceptor {
+    constructor(private authenticationService: AuthenticationService) { }
+
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
+        // add auth header with jwt if user is logged in and request is to api url
+        const currentUser = this.authenticationService.currentUserValue;
+        const isLoggedIn = currentUser && currentUser.token;
+        const isApiUrl = request.url.startsWith(environment.apiUrl);
+        if (isLoggedIn && isApiUrl) {
+            request = request.clone({
+                setHeaders: {
+                    Authorization: `Bearer ${currentUser.token}`
+                }
+            });
+        }
+
+        return next.handle(request);
+    }
+}

+ 53 - 0
src/app/layouts/admin/admin.component.html

@@ -0,0 +1,53 @@
+<div class="wrapper">
+    <div class="sidebar" data-color="blue" data-background-color="white" >
+        <app-sidebar></app-sidebar>
+        <div class="sidebar-background" ></div>
+    </div>
+    <div class="main-panel">
+        <app-navbar></app-navbar>
+        <router-outlet></router-outlet>
+    </div>
+    <div class="fixed-plugin">
+        <div class="dropdown show-dropdown">
+            <a href="#" data-toggle="dropdown" aria-expanded="true">
+                <i class="fa fa-cog fa-2x"> </i>
+            </a>
+            <ul class="dropdown-menu" x-placement="bottom-start">
+                <li class="header-title"> Sidebar Filters</li>
+                <li class="adjustments-line">
+                    <a href="javascript:void(0)" class="switch-trigger active-color">
+                        <div class="ml-auto mr-auto">
+                            <span class="badge filter badge-white" data-color="white"></span>
+                            <span class="badge filter badge-yellow" data-color="yellow"></span>
+                            <span class="badge filter badge-blue active" data-color="blue"></span>
+                            <span class="badge filter badge-green" data-color="green"></span>
+                            <span class="badge filter badge-black" data-color="black"></span>
+                        </div>
+                        <div class="clearfix"></div>
+                    <div class="ripple-container"></div></a>
+                </li>
+                <li class="header-title">Images</li>
+                <li>
+                    <a class="img-holder switch-trigger" href="javascript:void(0)">
+                        <img src="./assets/img/sidebar-1.jpg" alt="">
+                    </a>
+                </li>
+                <li>
+                    <a class="img-holder switch-trigger" href="javascript:void(0)">
+                        <img src="./assets/img/sidebar-2.jpg" alt="">
+                    <div class="ripple-container"></div></a>
+                </li>
+                <li>
+                    <a class="img-holder switch-trigger" href="javascript:void(0)">
+                        <img src="./assets/img/sidebar-3.jpg" alt="">
+                    </a>
+                </li>
+                <li class="active">
+                    <a class="img-holder switch-trigger" href="javascript:void(0)">
+                        <img src="./assets/img/sidebar-4.jpg" alt="">
+                    </a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>

+ 0 - 0
src/app/layouts/admin/admin.component.scss


+ 25 - 0
src/app/layouts/admin/admin.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AdminComponent } from './admin.component';
+
+describe('AdminComponent', () => {
+  let component: AdminComponent;
+  let fixture: ComponentFixture<AdminComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ AdminComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(AdminComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 161 - 0
src/app/layouts/admin/admin.component.ts

@@ -0,0 +1,161 @@
+import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
+import { Location, LocationStrategy, PathLocationStrategy, PopStateEvent } from '@angular/common';
+import { filter, map } from 'rxjs/operators';
+import { Subscription } from 'rxjs/Subscription';
+
+//import { NavbarComponent } from '../../components/navbar/navbar.component';
+import { Router, NavigationEnd, NavigationStart } from '@angular/router';
+import PerfectScrollbar from 'perfect-scrollbar';
+import * as $ from "jquery";
+
+@Component({
+  selector: 'app-admin',
+  templateUrl: './admin.component.html',
+  styleUrls: ['./admin.component.scss']
+})
+export class AdminComponent implements OnInit {
+  private _router: Subscription;
+  private lastPoppedUrl: string;
+  private yScrollStack: number[] = [];
+
+  constructor( public location: Location, private router: Router) {}
+
+  ngOnInit() {
+      const isWindows = navigator.platform.indexOf('Win') > -1 ? true : false;
+
+      if (isWindows && !document.getElementsByTagName('body')[0].classList.contains('sidebar-mini')) {
+          // if we are on windows OS we activate the perfectScrollbar function
+
+          document.getElementsByTagName('body')[0].classList.add('perfect-scrollbar-on');
+      } else {
+          document.getElementsByTagName('body')[0].classList.remove('perfect-scrollbar-off');
+      }
+      const elemMainPanel = <HTMLElement>document.querySelector('.main-panel');
+      const elemSidebar = <HTMLElement>document.querySelector('.sidebar .sidebar-wrapper');
+
+      this.location.subscribe((ev:PopStateEvent) => {
+          this.lastPoppedUrl = ev.url;
+      });
+       this.router.events.subscribe((event:any) => {
+          if (event instanceof NavigationStart) {
+             if (event.url != this.lastPoppedUrl)
+                 this.yScrollStack.push(window.scrollY);
+         } else if (event instanceof NavigationEnd) {
+             if (event.url == this.lastPoppedUrl) {
+                 this.lastPoppedUrl = undefined;
+                 window.scrollTo(0, this.yScrollStack.pop());
+             } else
+                 window.scrollTo(0, 0);
+         }
+      });
+
+      this._router = this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
+           elemMainPanel.scrollTop = 0;
+           elemSidebar.scrollTop = 0;
+      });
+      if (window.matchMedia(`(min-width: 960px)`).matches && !this.isMac()) {
+          let ps = new PerfectScrollbar(elemMainPanel);
+          ps = new PerfectScrollbar(elemSidebar);
+      }
+
+      const window_width = $(window).width();
+      let $sidebar = $('.sidebar');
+      let $sidebar_responsive = $('body > .navbar-collapse');
+      let $sidebar_img_container = $sidebar.find('.sidebar-background');
+
+
+      if(window_width > 767){
+          if($('.fixed-plugin .dropdown').hasClass('show-dropdown')){
+              $('.fixed-plugin .dropdown').addClass('open');
+          }
+
+      }
+
+      $('.fixed-plugin a').click(function(event){
+        // Alex if we click on switch, stop propagation of the event, so the dropdown will not be hide, otherwise we set the  section active
+          if($(this).hasClass('switch-trigger')){
+              if(event.stopPropagation){
+                  event.stopPropagation();
+              }
+              else if(window.event){
+                 window.event.cancelBubble = true;
+              }
+          }
+      });
+
+      $('.fixed-plugin .badge').click(function(){
+          let $full_page_background = $('.full-page-background');
+
+
+          $(this).siblings().removeClass('active');
+          $(this).addClass('active');
+
+          var new_color = $(this).data('color');
+
+          if($sidebar.length !== 0){
+              $sidebar.attr('data-color', new_color);
+          }
+
+          if($sidebar_responsive.length != 0){
+              $sidebar_responsive.attr('data-color',new_color);
+          }
+      });
+
+      $('.fixed-plugin .img-holder').click(function(){
+          let $full_page_background = $('.full-page-background');
+
+          $(this).parent('li').siblings().removeClass('active');
+          $(this).parent('li').addClass('active');
+
+
+          //var new_image = $(this).find("img").attr('src');
+
+          if($sidebar_img_container.length !=0 ){
+              $sidebar_img_container.fadeOut('fast', function(){
+                 //$sidebar_img_container.css('background-image','url("' + new_image + '")');
+                 $sidebar_img_container.fadeIn('fast');
+              });
+          }
+
+          if($full_page_background.length != 0){
+
+              $full_page_background.fadeOut('fast', function(){
+                 //$full_page_background.css('background-image','url("' + new_image + '")');
+                 $full_page_background.fadeIn('fast');
+              });
+          }
+
+          if($sidebar_responsive.length != 0){
+              //$sidebar_responsive.css('background-image','url("' + new_image + '")');
+          }
+      });
+  }
+  ngAfterViewInit() {
+      this.runOnRouteChange();
+  }
+  isMaps(path){
+      var titlee = this.location.prepareExternalUrl(this.location.path());
+      titlee = titlee.slice( 1 );
+      if(path == titlee){
+          return false;
+      }
+      else {
+          return true;
+      }
+  }
+  runOnRouteChange(): void {
+    if (window.matchMedia(`(min-width: 960px)`).matches && !this.isMac()) {
+      const elemMainPanel = <HTMLElement>document.querySelector('.main-panel');
+      const ps = new PerfectScrollbar(elemMainPanel);
+      ps.update();
+    }
+  }
+  isMac(): boolean {
+      let bool = false;
+      if (navigator.platform.toUpperCase().indexOf('MAC') >= 0 || navigator.platform.toUpperCase().indexOf('IPAD') >= 0) {
+          bool = true;
+      }
+      return bool;
+  }
+
+}

+ 42 - 0
src/app/layouts/admin/admin.module.ts

@@ -0,0 +1,42 @@
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { AdminLayoutRoutes } from './admin.routing';
+import { DashboardComponent } from '../../components/dashboard/dashboard.component';
+import { ProfileComponent } from '../../components/profile/profile.component';
+
+import { PluginsModule } from '../../components/plugins/plugins.module';
+
+import {
+  MatButtonModule,
+  MatInputModule,
+  MatRippleModule,
+  MatFormFieldModule,
+  MatTooltipModule,
+  MatSelectModule,
+  MatExpansionModule
+} from '@angular/material';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    RouterModule.forChild(AdminLayoutRoutes),
+    FormsModule,
+    ReactiveFormsModule,
+    MatButtonModule,
+    MatRippleModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatTooltipModule,
+    PluginsModule,
+  ],
+  declarations: [
+    DashboardComponent,
+    ProfileComponent
+  ]
+})
+
+export class AdminModule {}

+ 29 - 0
src/app/layouts/admin/admin.routing.ts

@@ -0,0 +1,29 @@
+import { Routes } from '@angular/router';
+
+import { DashboardComponent } from '../../components/dashboard/dashboard.component';
+import { ProfileComponent } from '../../components/profile/profile.component';
+/*
+import { TableListComponent } from '../../table-list/table-list.component';
+import { TypographyComponent } from '../../typography/typography.component';
+import { IconsComponent } from '../../icons/icons.component';
+import { MapsComponent } from '../../maps/maps.component';
+import { NotificationsComponent } from '../../notifications/notifications.component';
+import { UpgradeComponent } from '../../upgrade/upgrade.component';*/
+
+export const AdminLayoutRoutes: Routes = [
+  { path: 'dashboard',
+    component: DashboardComponent,
+    data: {title: "Dashboard"}
+  },
+  { path: 'profile',
+    component: ProfileComponent,
+    data: {title: "Perfile de usuario"}
+  }
+  /*
+  { path: 'table-list',     component: TableListComponent },
+  { path: 'typography',     component: TypographyComponent },
+  { path: 'icons',          component: IconsComponent },
+  { path: 'maps',           component: MapsComponent },
+  { path: 'notifications',  component: NotificationsComponent },
+  { path: 'upgrade',        component: UpgradeComponent },*/
+];

+ 3 - 0
src/app/models/index.ts

@@ -0,0 +1,3 @@
+export * from './role';
+export * from './user';
+export * from './plant';

+ 18 - 0
src/app/models/plant.ts

@@ -0,0 +1,18 @@
+import { Observable } from 'rxjs';
+
+export interface Plant {
+    id: number;
+    name: string;
+    state: boolean;
+    installDate: string;
+    country: string;
+    city: string;
+    latitude: number;
+    longitude: number;
+    installedCapacity: number;
+}
+
+export abstract class PlantData {
+    abstract getPlants(): Observable<Plant[]>;
+    abstract getPlant(id: number): Observable<Plant>;
+}

+ 4 - 0
src/app/models/role.ts

@@ -0,0 +1,4 @@
+export enum Role {
+    User = 'User',
+    Admin = 'Admin'
+}

+ 18 - 0
src/app/models/user.ts

@@ -0,0 +1,18 @@
+import { Role } from "./role";
+
+export class User {
+    id: number;
+    username: string;
+    password: string;
+    firstName: string;
+    lastName: string;
+    role: Role;
+    token?: string;
+    birthdate?: Date;
+    email?: string;
+    address?: string;
+    city?: string;
+    country?: string;
+    phone?: string;
+    gender?: string;
+}

+ 43 - 0
src/app/services/authentication.service.ts

@@ -0,0 +1,43 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { environment } from '@environments/environment';
+import { User } from '@app/models';
+
+@Injectable({ providedIn: 'root' })
+export class AuthenticationService {
+    private currentUserSubject: BehaviorSubject<User>;
+    public currentUser: Observable<User>;
+
+    constructor(private http: HttpClient) {
+        this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
+        this.currentUser = this.currentUserSubject.asObservable();
+    }
+
+    public get currentUserValue(): User {
+        return this.currentUserSubject.value;
+    }
+
+    login(username: string, password: string) {
+        return this.http.post<any>(`${environment.apiUrl}/users/authenticate`, { username, password })
+            .pipe(map(user => {
+                // login successful if there's a jwt token in the response
+                if (user && user.token) {
+                    // store user details and jwt token in local storage to keep user logged in between page refreshes
+                    localStorage.setItem('currentUser', JSON.stringify(user));
+                    this.currentUserSubject.next(user);
+                }
+
+                return user;
+            }));
+    }
+
+    logout() {
+        // remove user from local storage to log user out
+        localStorage.removeItem('currentUser');
+        this.currentUserSubject.next(null);
+    }
+    
+}

+ 12 - 0
src/app/services/plants.service.spec.ts

@@ -0,0 +1,12 @@
+import { TestBed } from '@angular/core/testing';
+
+import { PlantsService } from './plants.service';
+
+describe('PlantsService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: PlantsService = TestBed.get(PlantsService);
+    expect(service).toBeTruthy();
+  });
+});

+ 24 - 0
src/app/services/plants.service.ts

@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import { PlantData, Plant } from '../models/plant';
+import { of as observableOf, Observable } from 'rxjs';
+import { plantsData } from '../data/plants.data';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class PlantsService extends PlantData {
+
+  private listData: Plant[] = plantsData;
+
+  constructor() {
+    super();
+   }
+
+  getPlants(): Observable<Plant[]> {
+    return observableOf(this.listData);
+  }
+
+  getPlant(id: number): Observable<Plant> {
+    return observableOf(this.listData.find(e => e.id === id));
+  }
+}

+ 16 - 0
src/app/services/services.module.ts

@@ -0,0 +1,16 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { PlantsService } from './plants.service';
+//import { RealTimeMeasuresService } from './real-time-measures.service';
+
+@NgModule({
+  declarations: [],
+  imports: [
+    CommonModule
+  ],
+  providers: [
+    PlantsService,
+  //RealTimeMeasuresService
+  ]
+})
+export class ServicesModule { }

+ 18 - 0
src/app/services/user.service.ts

@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { environment } from '@environments/environment';
+import { User } from '@app/models';
+
+@Injectable({ providedIn: 'root' })
+export class UserService {
+    constructor(private http: HttpClient) { }
+
+    getAll() {
+        return this.http.get<User[]>(`${environment.apiUrl}/users`);
+    }
+
+    getById(id: number) {
+        return this.http.get<User>(`${environment.apiUrl}/users/${id}`);
+    }
+}

+ 35 - 0
src/assets/css/demo.css

@@ -0,0 +1,35 @@
+/*!
+
+=========================================================
+* Material Dashboard Angular - v2.3.0
+=========================================================
+
+* Product Page: https://www.creative-tim.com/product/material-dashboard-angular2
+* Copyright 2019 Creative Tim (https://www.creative-tim.com)
+* Licensed under MIT (https://github.com/creativetimofficial/material-dashboard-angular2/blob/master/LICENSE.md)
+
+* Coded by Creative Tim
+
+=========================================================
+
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+*/
+.tim-typo{
+  padding-left: 25%;
+  margin-bottom: 40px;
+  position: relative;
+  width: 100%;
+}
+.tim-typo .tim-note{
+  bottom: 5px;
+  color: #c0c1c2;
+  display: block;
+  font-weight: 400;
+  font-size: 13px;
+  line-height: 15px;
+  left: 0;
+  margin-left: 20px;
+  position: absolute;
+  width: 260px;
+}

二进制
src/assets/img/angular.png


二进制
src/assets/img/angular2-logo-red.png


二进制
src/assets/img/angular2-logo.png


二进制
src/assets/img/apple-icon.png


二进制
src/assets/img/cover.jpeg


二进制
src/assets/img/faces/marc.jpg


二进制
src/assets/img/favicon.png


二进制
src/assets/img/gears.gif


二进制
src/assets/img/html.png


二进制
src/assets/img/logo-inverlec.png


二进制
src/assets/img/logo-inverlec2.png


二进制
src/assets/img/logo.png


二进制
src/assets/img/marker-icon.png


二进制
src/assets/img/mask.png


二进制
src/assets/img/me-logo.png


二进制
src/assets/img/new_logo.png


二进制
src/assets/img/sidebar-1.jpg


二进制
src/assets/img/sidebar-2.jpg


二进制
src/assets/img/sidebar-3.jpg


二进制
src/assets/img/sidebar-4.jpg


二进制
src/assets/img/tim_80x80.png


+ 161 - 0
src/assets/scss/core/_alerts.scss

@@ -0,0 +1,161 @@
+.alert {
+    border: 0;
+    border-radius: 3px;
+    position: relative;
+    padding: 20px 15px;
+    line-height: 20px;
+
+    b{
+        font-weight: $font-weight-bold;
+        text-transform: uppercase;
+        font-size: $font-size-small;
+    }
+    // SASS conversion note: please mirror any content change in _mixins-shared.scss alert-variations-content
+    @include alert-variations(unquote(".alert"), unquote(""), $mdb-text-color-light);
+
+    &-info, &-danger, &-warning, &-success, &-rose {
+        color: $mdb-text-color-light;
+    }
+
+    &-default {
+        a, .alert-link {
+            color: $mdb-text-color-primary;
+        }
+    }
+
+    span{
+        display: block;
+        max-width: 89%;
+    }
+
+    &.alert-danger{
+        @include shadow-alert-color($brand-danger);
+        @include alert-icon-color($brand-danger);
+    }
+    &.alert-warning{
+        @include shadow-alert-color($brand-warning);
+        @include alert-icon-color($brand-warning);
+    }
+    &.alert-success{
+        @include shadow-alert-color($brand-success);
+        @include alert-icon-color($brand-success);
+    }
+    &.alert-info{
+        @include shadow-alert-color($brand-info);
+        @include alert-icon-color($brand-info);
+    }
+    &.alert-primary{
+        @include shadow-alert-color($brand-primary);
+        @include alert-icon-color($brand-primary);
+    }
+    &.alert-rose{
+        @include shadow-alert-color($brand-rose);
+        @include alert-icon-color($brand-rose);
+    }
+
+    &.alert-with-icon{
+      padding-left: 66px;
+
+      i[data-notify="icon"] {
+        font-size: 30px;
+        display: block;
+        left: 15px;
+        position: absolute;
+        top: 50%;
+        margin-top: -15px;
+        color: #fff;
+      }
+    }
+
+    .mat-button.close{
+      min-width: auto;
+      line-height: .5;
+        i{
+          color: $white-color;
+          font-size: 11px;
+        }
+    }
+
+    i[data-notify="icon"]{
+        display: none;
+    }
+
+    .alert-icon{
+        display: block;
+        float: left;
+        margin-right: $margin-base;
+
+        i{
+            margin-top: -7px;
+            top: 5px;
+            position: relative;
+        }
+    }
+
+    [data-notify="dismiss"]{
+        margin-right: 5px;
+    }
+}
+
+.places-buttons .btn {
+    margin-bottom: 30px;
+}
+//
+// .alert {
+//     border: 0;
+//     border-radius: 3px;
+//
+//     padding: 20px 15px;
+//     line-height: 20px;
+//
+//     //@include shadow-z-2();
+//
+//     b{
+//         font-weight: $font-weight-bold;
+//         text-transform: uppercase;
+//         font-size: $font-size-small;
+//     }
+//     // SASS conversion note: please mirror any content change in _mixins-shared.scss alert-variations-content
+//     @include alert-variations(unquote(".alert"), unquote(""), $mdb-text-color-light);
+//
+//     &-info, &-danger, &-warning, &-success {
+//         color: $mdb-text-color-light;
+//     }
+//
+//     &-default {
+//         a, .alert-link {
+//             color: $mdb-text-color-primary;
+//         }
+//     }
+//
+//     .alert-icon{
+//         display: block;
+//         float: left;
+//         margin-right: $margin-base;
+//
+//         i{
+//             margin-top: -7px;
+//             top: 5px;
+//             position: relative;
+//         }
+//     }
+//     .mat-button.close,
+//     .close{
+//         min-width: auto;
+//         color: $white-color;
+//         text-shadow: none;
+//         opacity: .9;
+//
+//         i{
+//             font-size: 11px;
+//         }
+//
+//         &:hover,
+//         &:focus{
+//             opacity: 1;
+//         }
+//     }
+// }
+// .alert .close {
+//     line-height: .5;
+// }

+ 120 - 0
src/assets/scss/core/_angular-modal.scss

@@ -0,0 +1,120 @@
+.modal.modal-angular{
+    .modal-header{
+        border-bottom: none;
+        position: relative;
+    }
+
+    .modal-footer{
+        border-top: none;
+    }
+
+    .modal-body{
+        .separator{
+            border-bottom: 1px solid rgba(220,220,220, .2);
+            width: 100%;
+            display: block;
+            margin: 11px 0;
+        }
+
+        .image-container{
+            width: 35px;
+            overflow: hidden;
+            margin: 0px auto;
+            margin-bottom: 17px;
+
+            img{
+                width: 100%;
+                vertical-align: top;
+            }
+
+            &.image-angular-cli{
+                width: 44px;
+                position: relative;
+                top: 4px;
+                margin-top: -9px;
+            }
+        }
+
+        h4{
+            padding-left: 0px;
+            font-size: 16px;
+            font-weight: 400;
+
+            &.margin-top{
+                margin-top: 15px;
+            }
+
+            i{
+                color: #c5a47e;
+            }
+        }
+
+        a.modal-button{
+            display: inline-block;
+            padding: 20px 15px;
+            background-color: #fdfdfd;
+            font-size: 14px;
+            text-align: center;
+            color: inherit;
+            border-radius: 6px;
+            transition: box-shadow 150ms ease-in;
+            width: 130px;
+            max-width: 130px;
+            margin-bottom: 10px;
+            font-weight: 100;
+            border: 1px solid rgba(220,220,220, .4);
+
+            &:nth-child(2n+1){
+                margin-left: 10px;
+                margin-right: 10px;
+            }
+
+            &.disabled{
+                opacity: .5;
+                pointer-events: none;
+            }
+
+            &:hover,
+            &:focus{
+                box-shadow: 0px 3px 25px 0px rgba(0, 0, 0, 0.2);
+            }
+
+            .product-type{
+                font-size: 12px;
+                font-weight: 600;
+            }
+
+            .price{
+                font-size: 11px;
+                font-weight: 100;
+
+                span{
+                    display: block;
+                    font-size: 27px;
+                    position: relative;
+                    font-weight: 500;
+                    margin-top: 3px;
+
+                    i{
+                        font-size: 10px;
+                        position: absolute;
+                        width: 8px;
+                        top: 4px;
+                    }
+                }
+            }
+
+            .wrapper-card{
+                position: relative;
+            }
+        }
+    }
+}
+
+@media (min-width: 768px){
+    .modal.modal-angular{
+        .modal-dialog{
+            width: 570px;
+        }
+    }
+}

+ 257 - 0
src/assets/scss/core/_buttons.scss

@@ -0,0 +1,257 @@
+.mat-button.btn,.mat-raised-button.btn,.mat-raised-button.btn:not([class*=mat-elevation-z]),
+.btn{
+  position: relative;
+  padding: 12px 30px;
+  margin: $bmd-btn-margin-bottom 1px;
+  min-width: auto;
+  font-size: .75rem; //  12px
+  font-weight: 400;
+  line-height: $bmd-line-height;
+  text-decoration: none;
+  text-transform: uppercase;
+  vertical-align: middle;
+  letter-spacing: 0;
+  cursor: pointer;
+  background-color: transparent;
+  border: 0;
+  border-radius: $border-radius-sm;
+  outline: 0;
+  transition: box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+  will-change: box-shadow, transform;
+  @include undo-bs-tab-focus();
+
+  //--
+  // Colors
+  @include bmd-raised-button-color();
+  &.btn-white {
+      &,
+      &:focus,
+      &:hover {
+          background-color: $white-color;
+          color: $gray-color;
+      }
+      &.btn-link {
+          color: $white-color;
+          background: transparent;
+          box-shadow: none;
+      }
+  }
+  &.btn-link:hover,
+  &.btn-link:focus,
+  &.btn-link:active {
+      text-decoration: none !important;
+  }
+
+  @include hover-focus();
+
+  //---
+  // btn-raised
+  &.btn-raised,
+  .btn-group-raised & {
+    // baseline shadow
+    // @include box-shadow($bmd-shadow-2dp);
+
+    // reverse any of the above for links
+    &.btn-link {
+      box-shadow: none;
+      @include bmd-hover-focus-active() {
+        box-shadow: none;
+      }
+    }
+
+    @include bmd-disabled() {
+      box-shadow: none;
+    }
+  }
+
+  //---
+  // btn-outline
+  @include bmd-outline-button-color();
+
+  // Size variations
+  &.btn-lg,
+  .btn-group-lg & {
+    @include button-size($input-btn-padding-y-lg, $input-btn-padding-x-lg, $bmd-btn-font-size, $btn-lg-line-height, $border-radius-sm);
+  }
+  &.btn-sm,
+  .btn-group-sm & {
+    @include button-size($input-btn-padding-y-sm, $input-btn-padding-x-sm, $bmd-btn-font-size-sm, $line-height-sm, $border-radius-sm);
+  }
+
+  &.btn-round {
+    border-radius: $border-radius-extreme;
+
+    > .mat-button-focus-overlay, .mat-button-ripple{
+      border-radius: $border-radius-extreme;
+    }
+  }
+
+  &.btn-fab,
+  &.btn-just-icon {
+      // see above for color variations
+      font-size: $mdb-btn-fab-font-size;
+      height: $mdb-btn-fab-size;
+      min-width: $mdb-btn-fab-size;
+      width: $mdb-btn-fab-size;
+      // margin: auto;
+      padding: 0;
+      overflow: hidden;
+      position: relative;
+      line-height: $mdb-btn-fab-size;
+
+        &.btn-round{
+            border-radius: 50%;
+        }
+
+      .btn-group-sm &,
+      &.btn-sm,
+      &.btn-fab-mini{
+          height: $mdb-btn-fab-size-mini + 1;
+          min-width: $mdb-btn-fab-size-mini + 1;
+          width: $mdb-btn-fab-size-mini + 1;
+
+          .material-icons,
+          .fa{
+              font-size: $mdb-btn-icon-size-mini;
+              line-height: $mdb-btn-fab-size-mini;
+          }
+      }
+
+      .btn-group-lg &,
+      &.btn-lg{
+          height: $mdb-btn-fab-size-lg + 1;
+          min-width: $mdb-btn-fab-size-lg + 1;
+          width: $mdb-btn-fab-size-lg + 1;
+          line-height: $mdb-btn-fab-size-lg;
+
+          .material-icons,
+          .fa{
+              font-size: $mdb-btn-icon-size;
+              line-height: $mdb-btn-fab-size-lg;
+          }
+      }
+
+      .material-icons,
+      .fa {
+        margin-top: 0;
+        position: absolute;
+        width: 100%;
+        transform: none;
+        left: 0;
+        top: 0;
+        height: 100%;
+
+        line-height: $mdb-btn-fab-size;
+        font-size: $mdb-btn-just-icon-font-size;
+      }
+  }
+}
+
+.btn-just-icon{
+    &.btn-lg{
+        font-size: $mdb-btn-fab-font-size;
+        height: $mdb-btn-fab-size;
+        min-width: $mdb-btn-fab-size;
+        width: $mdb-btn-fab-size;
+    }
+}
+
+.input-group-btn > .btn{
+    border: 0;
+}
+
+
+// Align icons inside buttons with text
+.btn .material-icons,
+.btn:not(.btn-just-icon):not(.btn-fab) .fa{
+      position: relative;
+      display: inline-block;
+      top: 0;
+      margin-top: -1em;
+      margin-bottom: -1em;
+      font-size: 1.1rem;
+      vertical-align: middle;
+    }
+
+// Disabled buttons and button groups
+.mat-raised-button.btn,
+.input-group-btn .mat-raised-button.btn,
+.btn-group,
+.btn-group-vertical {
+  // have to ratchet up the specificity to kill drop shadows on disabled raised buttons
+  @include bmd-disabled() {
+    .bg-inverse & {
+      color: $bmd-inverse-btn-disabled;
+    }
+
+    // flat buttons shouldn't lose transparency on disabled hover/focus
+  }
+}
+
+// btn-group variations
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  margin: 10px 1px;
+
+  // spec: https://www.google.com/design/spec/components/buttons.html#buttons-toggle-buttons
+  //&.open {
+  //  .dropdown-toggle {
+  //  }
+  //
+  //  > .dropdown-toggle.btn {
+  //    @include bmd-raised-button-color-bg();
+  //  }
+  //}
+
+  .dropdown-menu {
+    border-radius: 0 0 $border-radius $border-radius;
+  }
+
+  &.btn-group-raised {
+    @include box-shadow($bmd-shadow-2dp);
+  }
+
+  .mat-raised-button.btn + .mat-raised-button.btn,
+  .mat-raised-button.btn,
+  .mat-raised-button.btn:active,
+  .btn-group {
+    margin: 0;
+  }
+
+  // remove margin from nested btn-group(s) to properly align them with the outer buttons
+  > .btn-group {
+    margin: 0;
+  }
+
+}
+.btn-group > .mat-raised-button.btn:not(:first-child), .btn-group > .btn-group:not(:first-child) > .mat-raised-button.btn,
+
+.btn-group > .mat-raised-button.btn:not(:first-child) .mat-button-ripple, .btn-group > .btn-group:not(:first-child) > .mat-raised-button.btn .mat-button-ripple,
+
+.btn-group > .mat-raised-button.btn:not(:first-child) .mat-button-focus-overlay, .btn-group > .btn-group:not(:first-child) > .mat-raised-button.btn .mat-button-focus-overlay{
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+}
+.btn-group > .mat-raised-button.btn:not(:last-child):not(.dropdown-toggle), .btn-group > .btn-group:not(:last-child) > .mat-raised-button.btn,
+.btn-group > .mat-raised-button.btn:not(:last-child):not(.dropdown-toggle) .mat-button-ripple, .btn-group > .btn-group:not(:last-child) > .mat-raised-button.btn .mat-button-ripple,
+.btn-group > .mat-raised-button.btn:not(:last-child):not(.dropdown-toggle) .mat-button-focus-overlay, .btn-group > .btn-group:not(:last-child) > .mat-raised-button.btn .mat-button-focus-overlay {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+}
+.btn-no-ripple .mat-button-ripple{
+  display: none;
+}
+.mat-button, .mat-icon-button {
+    background: transparent;
+}
+.mat-button:hover .mat-button-focus-overlay, .mat-stroked-button:hover .mat-button-focus-overlay{
+  opacity: 0;
+  background-color: transparent!important;
+}
+button:focus {
+  outline: none;
+}
+.mat-button .mat-button-ripple{
+  border-radius: inherit;
+}

+ 658 - 0
src/assets/scss/core/_cards.scss

@@ -0,0 +1,658 @@
+//https://www.google.com/design/spec/components/cards.html#cards-content-blocks
+// Card resting elevation: 2dp
+.card {
+  border: 0;
+  margin-bottom: 30px;
+  margin-top: 30px;
+  border-radius: $border-radius-large;
+  color: $gray-dark;
+  background: $white-color;
+  width: 100%;
+
+    .card-category:not([class*="text-"]) {
+        color: $gray-color;
+    }
+    .card-category{
+        margin-top: 10px;
+
+        .material-icons{
+            position: relative;
+            top: 8px;
+            line-height: 0;
+          }
+  }
+
+  .form-check {
+      margin-top: 15px;
+  }
+
+    .card-title{
+        margin-top: 0.625rem;
+
+        &:last-child{
+            margin-bottom: 0;
+        }
+    }
+
+  // Cards have a default elevation of 2dp.
+  @include box-shadow($bmd-shadow-2dp);
+  @extend %std-font;
+
+
+  &.no-shadow {
+    .card-header-image,
+    .card-header-image img {
+      box-shadow: none !important;
+      }
+  }
+
+  .card-body,
+  .card-footer {
+    padding: $padding-card-body-y $padding-card-body-x;
+  }
+
+  .card-body {
+      & + .card-footer{
+        padding-top: 0rem;
+        border: 0;
+        border-radius: $border-radius-large;
+      }
+  }
+
+  .card-footer {
+    display: flex;
+    align-items: center;
+    background-color: transparent;
+    border: 0;
+
+    .author,
+    .stats {
+        display: inline-flex;
+    }
+
+    .stats {
+        color: $gray-color;
+
+        .material-icons {
+            position: relative;
+            top: -10px;
+            margin-right: 3px;
+            margin-left: 3px;
+            font-size: 18px;
+        }
+    }
+  }
+
+  &.bmd-card-raised {
+    // Card raised elevation: 8dp
+    @include box-shadow($bmd-shadow-8dp);
+  }
+
+  @include media-breakpoint-up(lg) {
+    // On desktop, cards can have a resting elevation of 0dp and gain an elevation of 8dp on hover.
+    &.bmd-card-flat {
+      box-shadow: none;
+    }
+  }
+
+    .card-header {
+      border-bottom: none;
+      background: transparent;
+        .title{
+            color: $white-color;
+        }
+
+        &:not([class*="card-header-"]){
+            // @include shadow-big();
+        }
+
+        .nav-tabs {
+            padding: 0;
+        }
+
+        &.card-header-image {
+            position: relative;
+            padding: 0;
+            z-index: 1;
+            margin-left: 15px;
+            margin-right: 15px;
+            margin-top: -30px;
+            border-radius: $border-radius-large;
+
+            img {
+                width: 100%;
+                border-radius: $border-radius-large;
+                pointer-events: none;
+                @include shadow-big-image();
+            }
+            .card-title {
+                position: absolute;
+                bottom: 15px;
+                left: 15px;
+                color: $white-color;
+                font-size: $font-size-h4;
+                text-shadow: 0 2px 5px rgba(33, 33, 33, 0.5);
+            }
+
+            .colored-shadow{
+                transform: scale(0.94);
+                top: 12px;
+                filter: blur(12px);
+                position: absolute;
+                width: 100%;
+                height: 100%;
+                background-size: cover;
+                z-index: -1;
+                transition: opacity .45s;
+                opacity: 0;
+            }
+
+            &.no-shadow{
+                box-shadow: none;
+
+                &.shadow-normal{
+                    @include shadow-big();
+                }
+
+                .colored-shadow{
+                    display: none !important;
+                }
+            }
+        }
+    }
+
+    .card-header-primary .card-icon,
+    .card-header-primary .card-text,
+    .card-header-primary:not(.card-header-icon):not(.card-header-text),
+    &.bg-primary,
+    &.card-rotate.bg-primary .front,
+    &.card-rotate.bg-primary .back{
+      background: linear-gradient(60deg, $purple-400, $purple-600);
+    }
+    .card-header-info .card-icon,
+    .card-header-info .card-text,
+    .card-header-info:not(.card-header-icon):not(.card-header-text),
+    &.bg-info,
+    &.card-rotate.bg-info .front,
+    &.card-rotate.bg-info .back{
+      background: linear-gradient(60deg, $cyan-400, $cyan-600);
+    }
+    .card-header-success .card-icon,
+    .card-header-success .card-text,
+    .card-header-success:not(.card-header-icon):not(.card-header-text),
+    &.bg-success,
+    &.card-rotate.bg-success .front,
+    &.card-rotate.bg-success .back{
+      background: linear-gradient(60deg, $green-400, $green-600);
+    }
+    .card-header-warning .card-icon,
+    .card-header-warning .card-text,
+    .card-header-warning:not(.card-header-icon):not(.card-header-text),
+    &.bg-warning,
+    &.card-rotate.bg-warning .front,
+    &.card-rotate.bg-warning .back{
+      background: linear-gradient(60deg, $orange-400, $orange-600);
+    }
+    .card-header-danger .card-icon,
+    .card-header-danger .card-text,
+    .card-header-danger:not(.card-header-icon):not(.card-header-text),
+    &.bg-danger,
+    &.card-rotate.bg-danger .front,
+    &.card-rotate.bg-danger .back{
+      background: linear-gradient(60deg, $red-400, $red-600);
+    }
+
+    .card-header-rose .card-icon,
+    .card-header-rose .card-text,
+    .card-header-rose:not(.card-header-icon):not(.card-header-text),
+    &.bg-rose,
+    &.card-rotate.bg-rose .front,
+    &.card-rotate.bg-rose .back{
+      background: linear-gradient(60deg, $pink-400, $pink-600);
+    }
+
+    .card-header-primary .card-icon,
+    .card-header-primary:not(.card-header-icon):not(.card-header-text),
+    .card-header-primary .card-text{
+        @include shadow-big-color($brand-primary);
+
+        //@include shadow-8dp-color($brand-primary);
+        //@include shadow-16dp-color($brand-primary);
+    }
+    .card-header-danger .card-icon,
+    .card-header-danger:not(.card-header-icon):not(.card-header-text),
+    .card-header-danger .card-text{
+        @include shadow-big-color($brand-danger);
+    }
+
+    .card-header-rose .card-icon,
+    .card-header-rose:not(.card-header-icon):not(.card-header-text),
+    .card-header-rose .card-text{
+        @include shadow-big-color($brand-rose);
+    }
+
+    .card-header-warning .card-icon,
+    .card-header-warning:not(.card-header-icon):not(.card-header-text),
+    .card-header-warning .card-text{
+        @include shadow-big-color($brand-warning);
+    }
+
+    .card-header-info .card-icon,
+    .card-header-info:not(.card-header-icon):not(.card-header-text),
+    .card-header-info .card-text{
+        @include shadow-big-color($brand-info);
+    }
+
+    .card-header-success .card-icon,
+    .card-header-success:not(.card-header-icon):not(.card-header-text),
+    .card-header-success .card-text{
+        @include shadow-big-color($brand-success);
+    }
+
+    [class*="card-header-"],
+    &[class*="bg-"]{
+        color: $white-color;
+
+        .card-title a,
+        .card-title,
+        .icon i{
+            color: $white-color;
+        }
+
+        .icon i{
+            border-color: rgba(255, 255, 255, 0.25);
+        }
+        .author a,
+        .stats,
+        .card-category,
+        .card-description{
+            color: $white-transparent;
+        }
+
+        .author a{
+            &:hover,
+            &:focus,
+            &:active{
+                color: $white-color;
+            }
+        }
+    }
+
+    .author{
+        .avatar{
+            width: 30px;
+            height: 30px;
+            overflow: hidden;
+            border-radius: 50%;
+            margin-right: 5px;
+        }
+
+        a{
+            color: $black-color;
+            text-decoration: none;
+
+            .ripple-container{
+                display: none;
+            }
+        }
+    }
+
+    .card-category-social{
+        .fa{
+            font-size: 24px;
+            position: relative;
+            margin-top: -4px;
+            top: 2px;
+            margin-right: 5px;
+        }
+
+        .material-icons{
+            position: relative;
+            top: 5px;
+        }
+    }
+
+    &[class*="bg-"],
+    &[class*="bg-"] .card-body{
+        border-radius: $border-radius-large;
+
+        h1,
+        h2,
+        h3{
+          small{
+              color: $white-transparent;
+          }
+        }
+    }
+
+    .card-stats{
+        background: transparent;
+        display: flex;
+
+        .author,
+        .stats{
+            display: inline-flex;
+        }
+    }
+}
+
+.card {
+  box-shadow: 0 1px 4px 0 rgba(0,0,0,0.14);
+
+  .table tr:first-child td{
+    border-top: none;
+  }
+
+  .card-title{
+    margin-top: 0;
+    margin-bottom: 15px;
+  }
+
+  .card-body{
+    padding: $padding-card-body-y 20px;
+    position: relative;
+
+  }
+
+  .card-header {
+    z-index: 3 !important;
+
+    .card-title{
+      margin-bottom: 3px;
+    }
+
+    .card-category{
+      margin: 0;
+    }
+
+    &.card-header-text {
+        display: inline-block;
+
+        &:after {
+            content: "";
+            display: table;
+        }
+    }
+
+    &.card-header-icon,
+    &.card-header-text {
+        i {
+            width: 33px;
+            height: 33px;
+            text-align: center;
+            line-height: 33px;
+        }
+        .card-title{
+          margin-top: 15px;
+          color: $black-color;
+        }
+        h4{
+          font-weight: 300;
+        }
+    }
+
+    &.card-header-tabs {
+        .nav-tabs {
+            background: transparent;
+            padding: 0;
+        }
+        .nav-tabs-title {
+            float: left;
+            padding: 10px 10px 10px 0;
+            line-height: 24px;
+        }
+    }
+  }
+
+  &.card-plain {
+    .card-header {
+      &.card-header-icon + .card-body .card-title,
+      &.card-header-icon + .card-body .card-category {
+          margin-top: -20px;
+      }
+    }
+  }
+
+  .card-actions {
+      position: absolute;
+      z-index: 1;
+      top: -50px;
+      width: calc(100% - 30px);
+      left: 17px;
+      right: 17px;
+      text-align: center;
+
+      .card-header{
+        padding: 0;
+        min-height: 160px;
+      }
+
+      .btn {
+          padding-left: 12px;
+          padding-right: 12px;
+      }
+      .fix-broken-card {
+          position: absolute;
+          top: -65px;
+      }
+  }
+
+  &.card-chart {
+    .card-footer i:nth-child(1n+2){
+      width: 18px;
+      text-align: center;
+    }
+
+    .card-category{
+      margin: 0;
+    }
+  }
+
+  .card-body + .card-footer,
+  .card-footer{
+    padding: 0;
+    padding-top: 10px;
+    margin: 0 15px 10px;
+    border-radius: 0;
+    justify-content: space-between;
+    align-items: center;
+
+    h6 {
+      width: 100%;
+    }
+
+    .stats{
+      color: #999999;
+      font-size: 12px;
+      line-height: 22px;
+
+      .card-category{
+        padding-top: 7px;
+        padding-bottom: 7px;
+        margin: 0;
+      }
+
+      .material-icons{
+        position: relative;
+        top: 4px;
+        font-size: 16px;
+      }
+    }
+  }
+  [class*="card-header-"] {
+      margin: 0px 15px 0;
+      padding: 0;
+
+      .card-title + .card-category{
+        color: rgba(255, 255, 255, 0.62);
+        a {
+          color: $white-color;
+        }
+      }
+
+      &:not(.card-header-icon):not(.card-header-text):not(.card-header-image){
+        border-radius: $border-radius-base;
+        margin-top: -20px;
+        padding: 15px;
+      }
+
+      .card-icon,
+      .card-text{
+        border-radius: $border-radius-base;
+        background-color: $gray-color;
+        padding: 15px;
+        margin-top: -20px;
+        margin-right: 15px;
+        float: left;
+      }
+
+      .card-text{
+        float: none;
+        display: inline-block;
+        margin-right: 0;
+
+        .card-title{
+          color: $white-color;
+          margin-top: 0;
+        }
+      }
+
+      position: relative;
+
+      .ct-chart{
+        .card-title{
+            color: $white-color;
+        }
+        .card-category{
+            margin-bottom: 0;
+            color: rgba($white-color, .62);
+        }
+
+        .ct-label{
+            color: rgba($white-color, .7);
+        }
+        .ct-grid{
+            stroke: rgba(255, 255, 255, 0.2);
+        }
+        .ct-series-a .ct-point,
+        .ct-series-a .ct-line,
+        .ct-series-a .ct-bar,
+        .ct-series-a .ct-slice-donut{
+            stroke: rgba(255,255,255,.8);
+        }
+        .ct-series-a .ct-slice-pie,
+        .ct-series-a .ct-area{
+            fill: rgba(255,255,255,.4);
+        }
+        .ct-series-a .ct-bar{
+          stroke-width: 10px;
+        }
+        .ct-point{
+          stroke-width: 10px;
+          stroke-linecap: round;
+        }
+        .ct-line{
+          fill: none;
+          stroke-width: 4px;
+        }
+      }
+  }
+
+  [data-header-animation="true"] {
+      @include transform-translate-y(0);
+      -webkit-transition: all 300ms cubic-bezier(0.34, 1.61, 0.7, 1);
+      -moz-transition: all 300ms cubic-bezier(0.34, 1.61, 0.7, 1);
+      -o-transition: all 300ms cubic-bezier(0.34, 1.61, 0.7, 1);
+      -ms-transition: all 300ms cubic-bezier(0.34, 1.61, 0.7, 1);
+      transition: all 300ms cubic-bezier(0.34, 1.61, 0.7, 1);
+  }
+
+  &:hover {
+      [data-header-animation="true"]{
+          @include transform-translate-y(-50px);
+      }
+  }
+
+  .map {
+    height: 280px;
+    border-radius: $border-radius-large;
+    margin-top: 15px;
+
+    &.map-big{
+      height: 420px;
+    }
+  }
+
+  .card-body.table-full-width{
+    padding: 0;
+  }
+
+  .card-plain .card-header-icon {
+    margin-right: 15px !important;
+  }
+}
+
+.table-sales{
+    margin-top: 40px;
+}
+
+.iframe-container {
+    width: 100%;
+
+    iframe {
+        width: 100%;
+        height: 500px;
+        border: 0;
+        @include shadow-big();
+    }
+}
+
+.card-wizard {
+  .nav.nav-pills {
+    .nav-item {
+      margin: 0;
+
+      .nav-link {
+        padding: 6px 15px !important;
+      }
+    }
+  }
+  .nav-pills:not(.flex-column) .nav-item + .nav-item:not(:first-child) {
+    margin-left: 0;
+  }
+
+  .nav-item .nav-link.active,
+  .nav-item .nav-link:hover,
+  .nav-item .nav-link:focus {
+    background-color: inherit !important;
+    box-shadow: none !important;
+  }
+
+  .input-group-text {
+    padding: 6px 15px 0px !important;
+  }
+  .card-footer {
+    border-top: none !important;
+  }
+}
+
+.card-chart,
+.card-product {
+  .card-body + .card-footer {
+    border-top: 1px solid #eee;
+  }
+}
+
+.card-product{
+  .price{
+    color: inherit;
+  }
+}
+
+.card-collapse {
+  margin-bottom: 15px;
+
+  .card .card-header a[aria-expanded="true"]{
+    color: #e91e63;
+  }
+}

+ 210 - 0
src/assets/scss/core/_checkboxes.scss

@@ -0,0 +1,210 @@
+.form-check {
+  margin-bottom: .5rem;
+  padding-left: 0;
+
+    .form-check-label {
+        cursor: pointer;
+        padding-left: 0; // Reset for Bootstrap rule
+        // color: $mdb-checkbox-label-color;
+        @include mdb-label-color-toggle-focus();
+    }
+
+  // Hide native checkbox
+    .form-check-input {
+        opacity: 0;
+        position: absolute;
+        margin: 0;
+        z-index: -1;
+        width: 0;
+        height: 0;
+        overflow: hidden;
+        left: 0;
+        pointer-events: none;
+    }
+
+    .form-check-sign {
+        vertical-align: middle;
+        position: relative;
+        top: -2px;
+        float: left;
+        padding-right: 10px;
+        display: inline-block;
+
+        &:before {
+            display: block;
+            position: absolute;
+            left: 0;
+            content: "";
+            background-color: rgba(0,0,0,.84);
+            height: $mdb-checkbox-size;
+            width: $mdb-checkbox-size;
+            border-radius: 100%;
+            z-index: 1;
+            opacity: 0;
+            margin: 0;
+            top: 0;
+            @include transform-scale3d(unquote('2.3,2.3,1'));
+        }
+
+        .check {
+            position: relative;
+            display: inline-block;
+            width: $mdb-checkbox-size;
+            height: $mdb-checkbox-size;
+            border: 1px solid $mdb-checkbox-border-color;
+            overflow: hidden;
+            z-index: 1;
+            border-radius: $border-radius-base;
+
+            &:before {
+                position: absolute;
+                content: "";
+                transform: rotate(45deg);
+                display: block;
+                margin-top: -3px;
+                margin-left: 7px;
+                width: 0;
+                color: $white-color;
+                height: 0;
+                box-shadow:
+                0 0 0 0,
+                0 0 0 0,
+                0 0 0 0,
+                0 0 0 0,
+                0 0 0 0,
+                0 0 0 0,
+                0 0 0 0 inset;
+                @include animation(checkbox-off $mdb-checkbox-animation-check forwards);
+            }
+        }
+
+    }
+
+    .form-check-input{
+
+        &:focus + .form-check-sign .check:after {
+            opacity: 0.2;
+        }
+
+        &:checked {
+            & + .form-check-sign .check {
+                background: $mdb-checkbox-checked-color;
+            }
+
+            & + .form-check-sign .check:before {
+                color: #FFFFFF;
+                box-shadow: 0 0 0 10px,
+                            10px -10px 0 10px,
+                            32px 0 0 20px,
+                            0px 32px 0 20px,
+                            -5px 5px 0 10px,
+                            20px -12px 0 11px;
+                @include animation(checkbox-on $mdb-checkbox-animation-check forwards);
+            }
+
+            & + .form-check-sign:before {
+            @include animation(rippleOn $mdb-checkbox-animation-ripple);
+            }
+
+            & + .form-check-sign .check:after {
+            //background-color: $brand-success; // FIXME: seems like tho wrong color, test and make sure it can be removed
+            @include animation(rippleOn $mdb-checkbox-animation-ripple forwards);
+            }
+        }
+
+        &:not(:checked) {
+          & + .form-check-sign:before {
+            @include animation(rippleOff $mdb-checkbox-animation-ripple);
+          }
+
+          & + .form-check-sign .check:after {
+            @include animation(rippleOff $mdb-checkbox-animation-ripple); // Ripple effect on uncheck
+
+          }
+        }
+  }
+.rtl {
+  .form-check {
+    .form-check-sign {
+      .check::before{
+        margin-right: 10px;
+      }
+    }
+  }
+}
+
+  // Style for disabled inputs
+  fieldset[disabled] &,
+  fieldset[disabled] & .form-check-input,
+  .form-check-input[disabled] ~ .form-check-sign .check,
+  .form-check-input[disabled] + .circle {
+    opacity: 0.5;
+  }
+
+  .form-check-input[disabled] ~ .form-check-sign .check{
+      border-color: #000000;
+      opacity: .26;
+  }
+
+  .form-check-input[disabled] + .form-check-sign .check:after {
+    background-color: $mdb-text-color-primary;
+    transform: rotate(-45deg);
+  }
+
+  .form-check-input[disabled][checked] + .form-check-sign .check{
+      background-color: $black;
+  }
+}
+
+@keyframes checkbox-on {
+  0% {
+    box-shadow:
+      0 0 0 10px,
+      10px -10px 0 10px,
+      32px 0 0 20px,
+      0px 32px 0 20px,
+      -5px 5px 0 10px,
+      15px 2px 0 11px;
+  }
+  50% {
+    box-shadow:
+      0 0 0 10px,
+      10px -10px 0 10px,
+      32px 0 0 20px,
+      0px 32px 0 20px,
+      -5px 5px 0 10px,
+      20px 2px 0 11px;
+  }
+  100% {
+    box-shadow:
+      0 0 0 10px,
+      10px -10px 0 10px,
+      32px 0 0 20px,
+      0px 32px 0 20px,
+      -5px 5px 0 10px,
+      20px -12px 0 11px;
+  }
+}
+
+@keyframes rippleOn {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 0.2;
+  }
+  100% {
+    opacity: 0;
+  }
+}
+@keyframes rippleOff {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 0.2;
+  }
+  100% {
+    opacity: 0;
+  }
+}

部分文件因为文件数量过多而无法显示