committed by
khalil-bot
parent
ad911b7479
commit
781959240b
61
.github/workflows/README.md
vendored
Normal file
61
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# GitHub Actions Workflows
|
||||||
|
|
||||||
|
## Angular CI Pipeline
|
||||||
|
|
||||||
|
### Triggers
|
||||||
|
- **Push**: Runs on all branches when changes are made to `ui/` folder
|
||||||
|
- **Pull Request**: Runs when PR targets `main` branch
|
||||||
|
|
||||||
|
### Pipeline Stages
|
||||||
|
|
||||||
|
1. **Checkout** - Clone repository
|
||||||
|
2. **Setup Node.js** - Install Node 20.x with npm cache
|
||||||
|
3. **Install Dependencies** - Run `npm ci`
|
||||||
|
4. **Type Check** - Run `npx tsc --noEmit`
|
||||||
|
5. **Lint** - Run `npm run lint`
|
||||||
|
6. **Format Check** - Run `npm run format:check`
|
||||||
|
7. **Build Dev** - Build development configuration
|
||||||
|
8. **Build Prod** - Build production configuration
|
||||||
|
9. **Test** - Run unit tests with coverage
|
||||||
|
10. **Upload Artifacts** - Save build outputs and coverage reports
|
||||||
|
|
||||||
|
|
||||||
|
### Local Testing
|
||||||
|
|
||||||
|
Before pushing, run locally:
|
||||||
|
```bash
|
||||||
|
cd ui
|
||||||
|
npm ci
|
||||||
|
npx tsc --noEmit
|
||||||
|
npm run lint
|
||||||
|
npm run format:check
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
**Node version mismatch:**
|
||||||
|
```bash
|
||||||
|
# Use Node 20.x locally
|
||||||
|
nvm use 20
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cache issues:**
|
||||||
|
```bash
|
||||||
|
# Clear npm cache
|
||||||
|
npm cache clean --force
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lint errors:**
|
||||||
|
```bash
|
||||||
|
# Auto-fix
|
||||||
|
npm run lint -- --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format errors:**
|
||||||
|
```bash
|
||||||
|
# Auto-format
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
100
.github/workflows/angular-ci.yml
vendored
Normal file
100
.github/workflows/angular-ci.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: Angular CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**' # Run on all branches
|
||||||
|
paths:
|
||||||
|
- 'ui/**'
|
||||||
|
- '.github/workflows/angular-ci.yml'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'ui/**'
|
||||||
|
- '.github/workflows/angular-ci.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
name: Build and Test Angular App
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./ui
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Checkout code
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Setup Node.js
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: './ui/package-lock.json'
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
# Check TypeScript compilation
|
||||||
|
- name: TypeScript compilation check
|
||||||
|
run: npx tsc --noEmit
|
||||||
|
|
||||||
|
# Run linting
|
||||||
|
- name: Run ESLint
|
||||||
|
run: npm run lint
|
||||||
|
continue-on-error: true # Don't fail build on lint warnings
|
||||||
|
|
||||||
|
# Check code formatting
|
||||||
|
- name: Check Prettier formatting
|
||||||
|
run: npm run format:check
|
||||||
|
|
||||||
|
# Build development
|
||||||
|
- name: Build (Development)
|
||||||
|
run: npm run build -- --configuration=development
|
||||||
|
|
||||||
|
# Build production
|
||||||
|
- name: Build (Production)
|
||||||
|
run: npm run build -- --configuration=production
|
||||||
|
|
||||||
|
# Run unit tests (when available)
|
||||||
|
- name: Run unit tests
|
||||||
|
run: npm run test -- --watch=false --browsers=ChromeHeadless --code-coverage
|
||||||
|
continue-on-error: true # Don't fail yet (no tests implemented)
|
||||||
|
|
||||||
|
# Upload coverage (when tests exist)
|
||||||
|
- name: Upload coverage reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: coverage-report
|
||||||
|
path: ui/coverage/
|
||||||
|
retention-days: 7
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
# Upload build artifacts
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: angular-build
|
||||||
|
path: ui/dist/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
- name: Build summary
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
echo "Build successful!" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Build Details" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Node version: ${{ matrix.node-version }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Angular version: 18.x" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Build time: $(date)" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"ignorePatterns": ["projects/**/*"],
|
"ignorePatterns": [
|
||||||
|
"projects/**/*"
|
||||||
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.ts"],
|
"files": [
|
||||||
"parserOptions": {
|
"*.ts"
|
||||||
"project": ["tsconfig.json"],
|
],
|
||||||
"createDefaultProgram": true
|
|
||||||
},
|
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
@@ -30,15 +30,16 @@
|
|||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"style": "kebab-case"
|
"style": "kebab-case"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "off"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": ["*.html"],
|
"files": [
|
||||||
|
"*.html"
|
||||||
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@angular-eslint/template/recommended"
|
"plugin:@angular-eslint/template/recommended",
|
||||||
|
"plugin:@angular-eslint/template/accessibility"
|
||||||
],
|
],
|
||||||
"rules": {}
|
"rules": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
"cli": {
|
||||||
"packageManager": "npm",
|
"packageManager": "npm",
|
||||||
"analytics": false
|
"analytics": false,
|
||||||
|
"schematicCollections": [
|
||||||
|
"@angular-eslint/schematics"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
@@ -119,6 +122,15 @@
|
|||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1614
ui/package-lock.json
generated
1614
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,10 @@
|
|||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test"
|
"test": "ng test",
|
||||||
|
"lint": "ng lint",
|
||||||
|
"format": "prettier --write \"src/**/*.{ts,html,scss,json}\"",
|
||||||
|
"format:check": "prettier --check \"src/**/*.{ts,html,scss,json}\""
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -28,13 +31,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^18.0.2",
|
"@angular-devkit/build-angular": "^18.0.2",
|
||||||
|
"@angular-eslint/builder": "21.0.1",
|
||||||
|
"@angular-eslint/eslint-plugin": "21.0.1",
|
||||||
|
"@angular-eslint/eslint-plugin-template": "21.0.1",
|
||||||
|
"@angular-eslint/schematics": "21.0.1",
|
||||||
|
"@angular-eslint/template-parser": "21.0.1",
|
||||||
"@angular/cli": "^18.0.2",
|
"@angular/cli": "^18.0.2",
|
||||||
"@angular/compiler-cli": "^18.0.0",
|
"@angular/compiler-cli": "^18.0.0",
|
||||||
"@types/chart.js": "^4.0.1",
|
"@types/chart.js": "^4.0.1",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
"@typescript-eslint/eslint-plugin": "7.11.0",
|
||||||
"@typescript-eslint/parser": "^8.57.2",
|
"@typescript-eslint/parser": "7.11.0",
|
||||||
"eslint": "^10.1.0",
|
"eslint": "8.57.1",
|
||||||
"jasmine-core": "~5.1.0",
|
"jasmine-core": "~5.1.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
<main>
|
<main>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
main {
|
main {
|
||||||
min-height: calc(100vh - 64px);
|
min-height: calc(100vh - 64px);
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [AppComponent],
|
imports: [AppComponent],
|
||||||
|
providers: [provideRouter(routes)],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -14,16 +17,16 @@ describe('AppComponent', () => {
|
|||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should have the 'dashboard' title`, () => {
|
it(`should have the 'PI_E2EEDA Dashboard' title`, () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
const app = fixture.componentInstance;
|
const app = fixture.componentInstance;
|
||||||
expect(app.title).toEqual('dashboard');
|
expect(app.title).toEqual('PI_E2EEDA Dashboard');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render title', () => {
|
it('should render header', () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const compiled = fixture.nativeElement as HTMLElement;
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, dashboard');
|
expect(compiled.querySelector('app-header')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import { HeaderComponent } from './components/header/header.component';
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, HeaderComponent],
|
imports: [RouterOutlet, HeaderComponent],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'PI_E2EEDA Dashboard';
|
title = 'PI_E2EEDA Dashboard';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,9 @@ import { routes } from './app.routes';
|
|||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideAnimationsAsync()]
|
providers: [
|
||||||
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
|
provideRouter(routes),
|
||||||
|
provideAnimationsAsync(),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,22 +4,24 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: '/dashboard',
|
redirectTo: '/dashboard',
|
||||||
pathMatch: 'full'
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./components/room-map/room-map.component').then(m => m.RoomMapComponent),
|
import('./components/room-map/room-map.component').then(m => m.RoomMapComponent),
|
||||||
title: 'Dashboard - PI_E2EEDA'
|
title: 'Dashboard - PI_E2EEDA',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'room/:id',
|
path: 'room/:id',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./components/room-details-panel/room-details-panel.component').then(m => m.RoomDetailsPanelComponent),
|
import('./components/room-details-panel/room-details-panel.component').then(
|
||||||
title: 'Room Details - PI_E2EEDA'
|
m => m.RoomDetailsPanelComponent
|
||||||
|
),
|
||||||
|
title: 'Room Details - PI_E2EEDA',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: '/dashboard'
|
redirectTo: '/dashboard',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<h1 routerLink="/dashboard">🌡️ PI_E2EEDA</h1>
|
<h1 routerLink="/dashboard">🌡️ PI_E2EEDA</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
|
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
header {
|
header {
|
||||||
background: #00bcd4;
|
background: #00bcd4;
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background 0.3s;
|
transition: background 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255,255,255,0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: rgba(255,255,255,0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { RouterModule } from '@angular/router';
|
|||||||
imports: [CommonModule, RouterModule],
|
imports: [CommonModule, RouterModule],
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrl: './header.component.scss',
|
styleUrl: './header.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<div class="legend">
|
<div class="legend">
|
||||||
<h3>Air Quality (CO2)</h3>
|
<h3>Air Quality (CO2)</h3>
|
||||||
<div class="legend-items">
|
<div class="legend-items">
|
||||||
<div class="legend-item" *ngFor="let level of levels">
|
@for (level of levels; track level.label) {
|
||||||
<div class="color-box" [style.background-color]="level.color"></div>
|
<div class="legend-item">
|
||||||
<span class="label">{{ level.label }}</span>
|
<div class="color-box" [style.background-color]="level.color"></div>
|
||||||
<span class="range">{{ level.range }}</span>
|
<span class="label">{{ level.label }}</span>
|
||||||
</div>
|
<span class="range">{{ level.range }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>>
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
.legend {
|
.legend {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: rgba(0,0,0,0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-items {
|
.legend-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-item {
|
.legend-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-box {
|
.color-box {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.range {
|
.range {
|
||||||
color: rgba(0,0,0,0.6);
|
color: rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
|
|
||||||
interface CO2Level {
|
interface CO2Level {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -10,19 +10,18 @@ interface CO2Level {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-legend',
|
selector: 'app-legend',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [],
|
imports: [CommonModule],
|
||||||
templateUrl: './legend.component.html',
|
templateUrl: './legend.component.html',
|
||||||
styleUrl: './legend.component.scss',
|
styleUrl: './legend.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class LegendComponent {
|
export class LegendComponent {
|
||||||
levels: CO2Level[] = [
|
levels: CO2Level[] = [
|
||||||
{ label: 'Excellent', range: '< 800 ppm', color: '#4caf50' },
|
{ label: 'Excellent', range: '< 800 ppm', color: '#4caf50' },
|
||||||
{ label: 'Good', range: '800-1000 ppm', color: '#8bc34a' },
|
{ label: 'Good', range: '800-1000 ppm', color: '#8bc34a' },
|
||||||
{ label: 'Moderate', range: '1000-1200 ppm', color: '#ffc107' },
|
{ label: 'Moderate', range: '1000-1200 ppm', color: '#ffc107' },
|
||||||
{ label: 'Poor', range: '1200-1500 ppm', color: '#ff9800' },
|
{ label: 'Poor', range: '1200-1500 ppm', color: '#ff9800' },
|
||||||
{ label: 'Very Poor', range: '1500-2000 ppm', color: '#ff5722' },
|
{ label: 'Very Poor', range: '1500-2000 ppm', color: '#ff5722' },
|
||||||
{ label: 'Critical', range: '> 2000 ppm', color: '#f44336' }
|
{ label: 'Critical', range: '> 2000 ppm', color: '#f44336' },
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="room-details-container">
|
<div class="room-details-container">
|
||||||
<h1>Room Details - {{ roomId }}</h1>
|
<h1>Room Details - {{ roomId }}</h1>
|
||||||
<p>Detailed room information coming soon.</p>
|
<p>Detailed room information coming soon.</p>
|
||||||
<a routerLink="/dashboard">← Back to Dashboard</a>
|
<a routerLink="/dashboard">← Back to Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
.room-details-container {
|
.room-details-container {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #00bcd4;
|
color: #00bcd4;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||||
@Component({
|
@Component({
|
||||||
@@ -7,13 +7,9 @@ import { ActivatedRoute, RouterModule } from '@angular/router';
|
|||||||
imports: [CommonModule, RouterModule],
|
imports: [CommonModule, RouterModule],
|
||||||
templateUrl: './room-details-panel.component.html',
|
templateUrl: './room-details-panel.component.html',
|
||||||
styleUrl: './room-details-panel.component.scss',
|
styleUrl: './room-details-panel.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class RoomDetailsPanelComponent {
|
export class RoomDetailsPanelComponent {
|
||||||
roomId: string | null = null;
|
private route = inject(ActivatedRoute);
|
||||||
|
roomId: string | null = this.route.snapshot.paramMap.get('id');
|
||||||
constructor(private route: ActivatedRoute) {
|
|
||||||
this.roomId = this.route.snapshot.paramMap.get('id');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="room-map-container">
|
<div class="room-map-container">
|
||||||
<h1>Room Map</h1>
|
<h1>Room Map</h1>
|
||||||
<p>Interactive 2D SVG map will be displayed here.</p>
|
<p>Interactive 2D SVG map will be displayed here.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
.room-map-container {
|
.room-map-container {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #00bcd4;
|
color: #00bcd4;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
templateUrl: './room-map.component.html',
|
templateUrl: './room-map.component.html',
|
||||||
styleUrl: './room-map.component.scss',
|
styleUrl: './room-map.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class RoomMapComponent {
|
export class RoomMapComponent {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ export const environment = {
|
|||||||
apiUrl: 'https://YOUR_DOMAIN/api',
|
apiUrl: 'https://YOUR_DOMAIN/api',
|
||||||
wsUrl: 'wss://YOUR_DOMAIN/ws',
|
wsUrl: 'wss://YOUR_DOMAIN/ws',
|
||||||
influxUrl: '',
|
influxUrl: '',
|
||||||
logLevel: 'error'
|
logLevel: 'error',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export const environment = {
|
|||||||
apiUrl: 'http://localhost:8080',
|
apiUrl: 'http://localhost:8080',
|
||||||
wsUrl: 'ws://localhost:8080',
|
wsUrl: 'ws://localhost:8080',
|
||||||
influxUrl: '',
|
influxUrl: '',
|
||||||
logLevel: 'debug'
|
logLevel: 'debug',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>Dashboard</title>
|
<title>Dashboard</title>
|
||||||
<base href="/">
|
<base href="/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
<link
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
|
||||||
</head>
|
rel="stylesheet"
|
||||||
<body class="mat-typography">
|
/>
|
||||||
<app-root></app-root>
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
</body>
|
</head>
|
||||||
|
<body class="mat-typography">
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
|||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
import { AppComponent } from './app/app.component';
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig)
|
bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
|
||||||
.catch((err) => console.error(err));
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
html, body { height: 100%; }
|
html,
|
||||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user