| @@ -9,6 +9,8 @@ import {SharedModule} from "../../shared/shared.module"; | |||
| import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | |||
| import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | |||
| import {FormsModule} from "@angular/forms"; | |||
| import {SecurityAtmDetailsComponent} from "./security-atm-details/security-atm-details.component"; | |||
| import {MatTableModule} from "@angular/material/table"; | |||
| @NgModule({ | |||
| @@ -16,6 +18,7 @@ import {FormsModule} from "@angular/forms"; | |||
| HomePageComponent, | |||
| CentralizedSecurityManagementComponent, | |||
| SecuritySystemDetailsComponent, | |||
| SecurityAtmDetailsComponent | |||
| ], | |||
| imports: [ | |||
| CommonModule, | |||
| @@ -23,6 +26,7 @@ import {FormsModule} from "@angular/forms"; | |||
| SharedMaterialModule, | |||
| SharedModule, | |||
| FormsModule, | |||
| MatTableModule, | |||
| ], | |||
| }) | |||
| export class HomePageModule {} | |||
| @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; | |||
| import { HomePageComponent } from './homepage/home-page.component'; | |||
| import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component'; | |||
| import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component'; | |||
| import {SecurityAtmDetailsComponent} from "./security-atm-details/security-atm-details.component"; | |||
| export const homePageRoutes: Routes = [ | |||
| { | |||
| @@ -15,5 +16,9 @@ export const homePageRoutes: Routes = [ | |||
| { | |||
| path: 'security-system-details', | |||
| component: SecuritySystemDetailsComponent, | |||
| }, | |||
| { | |||
| path: 'atm-monitoring', | |||
| component: SecurityAtmDetailsComponent, | |||
| } | |||
| ]; | |||
| @@ -5,6 +5,9 @@ | |||
| <button mat-stroked-button routerLink="./security-system-details"> | |||
| Security System Details | |||
| </button> | |||
| <button mat-stroked-button routerLink="./atm-monitoring"> | |||
| ATM monitoring | |||
| </button> | |||
| </div> | |||
| <mat-card class="sound-group"> | |||
| @@ -0,0 +1,65 @@ | |||
| <div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px"> | |||
| <div fxFlex="70" class="map-image"> | |||
| <div class="card-state"> | |||
| <img src="assets/images/map-quan5.jpg"> | |||
| <div class="state t2" id="State2"> | |||
| <div fxLayout="row"> | |||
| <div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)"> | |||
| <img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px"> | |||
| </div> | |||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||
| </div> | |||
| <div *ngIf="(state1 && state5 && status1)" class="alarm-text" | |||
| [ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div> | |||
| </div> | |||
| <div class="state t3" id="State3"> | |||
| <div fxLayout="row"> | |||
| <div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)"> | |||
| <img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px"> | |||
| </div> | |||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||
| </div> | |||
| <div *ngIf="(state2 && state5 && status2)" class="alarm-text" | |||
| [ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">VIBRATION ALARM</div> | |||
| </div> | |||
| <div class="state t4" id="State4" fxLayout="row" > | |||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||
| </div> | |||
| <div class="state t5 tooltip" id="State5" fxLayout="row"> | |||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||
| </div> | |||
| <div class="state t6 tooltip" id="State6" fxLayout="row"> | |||
| <img [src]="getImageSource()" style="width: 30px; height: 30px"> | |||
| <ng-container [ngTemplateOutlet]="camera"></ng-container> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div fxFlex="30" fxLayout="column" fxLayoutGap="50px"> | |||
| <table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> | |||
| <!-- Position Column --> | |||
| <ng-container matColumnDef="title"> | |||
| <th mat-header-cell *matHeaderCellDef > | |||
| Title | |||
| </th> | |||
| <td mat-cell *matCellDef="let element"> {{element.title}} </td> | |||
| </ng-container> | |||
| <ng-container matColumnDef="date"> | |||
| <th mat-header-cell *matHeaderCellDef > | |||
| Date | |||
| </th> | |||
| <td mat-cell *matCellDef="let element"> {{element.date}} </td> | |||
| </ng-container> | |||
| <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | |||
| <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> | |||
| </table> | |||
| </div> | |||
| </div> | |||
| <ng-template #camera> | |||
| <div (click)="openDialog()"> | |||
| <img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png"> | |||
| </div> | |||
| </ng-template> | |||
| @@ -0,0 +1,231 @@ | |||
| button { | |||
| padding: 30px 50px; | |||
| } | |||
| .red-bg { | |||
| background-color: red !important; | |||
| color: white !important; | |||
| } | |||
| .green-bg { | |||
| background-color: green !important; | |||
| color: white !important; | |||
| } | |||
| h1 { | |||
| font-size: 1.8rem; | |||
| color: white; | |||
| } | |||
| .topnav { | |||
| overflow: hidden; | |||
| background-color: #0a1128; | |||
| } | |||
| body { | |||
| margin: 0; | |||
| } | |||
| p { | |||
| text-align: center; | |||
| } | |||
| .content { | |||
| padding: 50px; | |||
| } | |||
| .card-grid { | |||
| max-width: 800px; | |||
| margin: 0 auto; | |||
| display: grid; | |||
| grid-gap: 2rem; | |||
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |||
| } | |||
| .card { | |||
| background-color: white; | |||
| box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, 0.5); | |||
| } | |||
| .card-title { | |||
| font-size: 1.2rem; | |||
| font-weight: bold; | |||
| color: #034078; | |||
| } | |||
| .reading { | |||
| font-size: 1.2rem; | |||
| color: #1282a2; | |||
| } | |||
| .button { | |||
| padding: 15px 50px; | |||
| font-size: 24px; | |||
| text-align: center; | |||
| outline: none; | |||
| color: #fff; | |||
| background-color: #0f8b8d; | |||
| border: none; | |||
| border-radius: 5px; | |||
| -webkit-touch-callout: none; | |||
| -webkit-user-select: none; | |||
| -khtml-user-select: none; | |||
| -moz-user-select: none; | |||
| -ms-user-select: none; | |||
| user-select: none; | |||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||
| } | |||
| /*.button:hover {background-color: #0f8b8d}*/ | |||
| .button:active { | |||
| background-color: #0f8b8d; | |||
| box-shadow: 2px 2px #cdcdcd; | |||
| transform: translateY(2px); | |||
| } | |||
| .state { | |||
| font-size: 1.5rem; | |||
| color: #8c8c8c; | |||
| font-weight: bold; | |||
| text-align: center; | |||
| } | |||
| .content { | |||
| padding: 30px; | |||
| max-width: 600px; | |||
| margin: 0 auto; | |||
| } | |||
| .map-image { | |||
| img { | |||
| height: 100%; | |||
| width: 100%; | |||
| } | |||
| .card-state { | |||
| width: 100%; | |||
| height: 100%; | |||
| position: relative; | |||
| } | |||
| .state { | |||
| position: absolute; | |||
| color: red; | |||
| &.t1 { | |||
| top: 5%; | |||
| left: 10%; | |||
| } | |||
| &.t2 { | |||
| top: 58%; | |||
| left: 31%; | |||
| transform: translate(-50%, -50%); | |||
| } | |||
| &.t3 { | |||
| top: 35%; | |||
| left: 50%; | |||
| transform: translate(-50%); | |||
| } | |||
| &.t4 { | |||
| top: 41%; | |||
| right: 12%; | |||
| } | |||
| &.t5 { | |||
| top: 54%; | |||
| left: 86%; | |||
| transform: translate(-50%); | |||
| } | |||
| &.t6 { | |||
| top: 48%; | |||
| left: 5%; | |||
| width: 100px; | |||
| .alarm-text-off { | |||
| width: 100px !important; | |||
| } | |||
| } | |||
| } | |||
| .sensor-on { | |||
| width: 30px; | |||
| height: 30px; | |||
| position: relative; | |||
| background: linear-gradient(#ff0000, #c70039); | |||
| display: flex !important; | |||
| justify-content: center; | |||
| align-items: center; | |||
| border-radius: 50%; | |||
| img { | |||
| z-index: 9; | |||
| } | |||
| &:before, | |||
| &:after { | |||
| position: absolute; | |||
| content: ""; | |||
| width: 100%; | |||
| height: 100%; | |||
| background: #ff0000; | |||
| border-radius: 50%; | |||
| z-index: 1; | |||
| } | |||
| &:before { | |||
| animation: sensor-on 2s ease-out infinite; | |||
| } | |||
| &:after { | |||
| animation: sensor-on 2s 1s ease-out infinite; | |||
| } | |||
| } | |||
| .sensor-off { | |||
| display: inline-block; | |||
| } | |||
| @keyframes sensor-on { | |||
| 100% { | |||
| transform: scale(2); | |||
| opacity: 0; | |||
| } | |||
| } | |||
| } | |||
| .alarm-text { | |||
| position: absolute; | |||
| font-size: 10px; | |||
| padding: 2px 4px; | |||
| width: 100px; | |||
| margin-top: 10px; | |||
| margin-left: -22px; | |||
| border-radius: 2px; | |||
| &-off { | |||
| background: #bfe9f4; | |||
| color: #004aad; | |||
| } | |||
| &-on { | |||
| background: #f11e1e; | |||
| color: #fff; | |||
| } | |||
| } | |||
| .tooltip { | |||
| position: relative; | |||
| display: inline-block; | |||
| } | |||
| .tooltip .tooltiptext { | |||
| text-align: start; | |||
| font-size: 10px; | |||
| visibility: hidden; | |||
| width: 300px; | |||
| padding: 10px; | |||
| background-color: #eee; | |||
| color: black; | |||
| border-radius: 6px; | |||
| position: absolute; | |||
| z-index: 1; | |||
| bottom: 100%; | |||
| left: -50%; | |||
| } | |||
| .tooltip:hover .tooltiptext { | |||
| visibility: visible; | |||
| cursor: pointer; | |||
| } | |||
| a { | |||
| color: blue; | |||
| } | |||
| table { | |||
| width: 100%; | |||
| } | |||
| th.mat-sort-header-sorted { | |||
| color: black; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { SecurityAtmDetailsComponent } from './security-atm-details.component'; | |||
| describe('SecuritySystemDetailsComponent', () => { | |||
| let component: SecurityAtmDetailsComponent; | |||
| let fixture: ComponentFixture<SecurityAtmDetailsComponent>; | |||
| beforeEach(() => { | |||
| TestBed.configureTestingModule({ | |||
| declarations: [SecurityAtmDetailsComponent] | |||
| }); | |||
| fixture = TestBed.createComponent(SecurityAtmDetailsComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,96 @@ | |||
| import {Component, OnDestroy, OnInit} from '@angular/core'; | |||
| import {debounceTime, filter, Subject, Subscription} from "rxjs"; | |||
| import {SocketService} from "../../../shared/services/socket.service"; | |||
| import {CameraDialogComponent} from "../../../shared/component/camera-dialog/camera-dialog.component"; | |||
| import {MatDialog} from "@angular/material/dialog"; | |||
| import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service'; | |||
| import { AlarmSoundService } from "../../../shared/services/alarm-sound.service"; | |||
| import {MatTableDataSource} from "@angular/material/table"; | |||
| const ELEMENT_DATA: any[] = [ | |||
| {title: 'Vibration warning Phường 15', date: '26/08/2024 12:14'}, | |||
| {title: 'Fire warning Phường 11', date: '26/08/2024 10:14'}, | |||
| {title: 'Fire warning Phường 9', date: '25/08/2024 12:14'}, | |||
| {title: 'Vibration warning Phường 1', date: '25/08/2024 12:14'}, | |||
| {title: 'Vibration warning Phường 2', date: '25/08/2024 12:14'}, | |||
| ]; | |||
| @Component({ | |||
| selector: 'app-security-atm-details', | |||
| templateUrl: './security-atm-details.component.html', | |||
| styleUrls: ['./security-atm-details.component.scss'] | |||
| }) | |||
| export class SecurityAtmDetailsComponent implements OnInit, OnDestroy { | |||
| isConnected = false; | |||
| state1 = false; | |||
| status1 = false; | |||
| state2 = false; | |||
| status2 = false; | |||
| state3 = ''; | |||
| state4 = ''; | |||
| state5 = false; | |||
| state6 = false; | |||
| switchArm = false; | |||
| isReady = true; | |||
| private unsubscribeAll = new Subject(); | |||
| constructor( | |||
| private socketService$: SocketService, | |||
| private dialog: MatDialog, | |||
| private confirm$: ConfirmDialogService, | |||
| private alarmSoundService$: AlarmSoundService, | |||
| ) {} | |||
| ngOnInit() { | |||
| const fakeData = { | |||
| state5: '0', | |||
| state6: '1', | |||
| status1: '1', | |||
| state1: '0', | |||
| status2: '1', | |||
| state2: '0', | |||
| }; | |||
| this.onMessage(fakeData); | |||
| setTimeout(() => {{ | |||
| const fakeData = { | |||
| state5: '1', | |||
| state6: '1', | |||
| status1: '1', | |||
| state1: '0', | |||
| status2: '1', | |||
| state2: '0', | |||
| }; | |||
| this.onMessage(fakeData); | |||
| }}, 3000); | |||
| } | |||
| ngOnDestroy(): void { | |||
| this.unsubscribeAll.next(''); | |||
| this.unsubscribeAll.complete(); | |||
| this.alarmSoundService$.stopAlarm(); | |||
| } | |||
| getImageSource(): string { | |||
| return (this.state5 && this.state6) || this.state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'; | |||
| } | |||
| onMessage(message: any) { | |||
| this.state1 = message.state1 == '0'; // 1 OFF // alarm 12h | |||
| this.status1 = message.status1 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||
| this.state2 = message.state2 == '0'; // 1 OFF // alarm 1h | |||
| this.status2 = message.status2 == '1'; // 0 not, 1 ready, 2 error, 3 bypass | |||
| this.state3 = message.state3 == '1' ? 'ON' : 'OFF'; | |||
| this.state4 = message.state4 == '1' ? 'ON' : 'OFF'; | |||
| this.state5 = message.state5 == '1'; // alarm 9h && 6h // 1 ON, 0 OFF | |||
| this.state6 = message.state6 == '1'; // ? 'ON' : 'OFF'; | |||
| this.switchArm = message.state5 == '1';// alarm 9h && 6h // 1 ON, 0 OFF | |||
| this.isReady = message.ready == '1'; | |||
| } | |||
| openDialog(): void { | |||
| this.dialog.open(CameraDialogComponent, { | |||
| width: '80vw', | |||
| data: '', | |||
| }); | |||
| } | |||
| displayedColumns: string[] = ['title', 'date']; | |||
| dataSource = new MatTableDataSource(ELEMENT_DATA); | |||
| } | |||