├── src ├── assets │ ├── .gitkeep │ ├── images │ │ ├── bkg.png │ │ ├── graph.png │ │ ├── geospatial_large.png │ │ └── geospatial_small.png │ ├── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png │ ├── community.json │ ├── changelog.json │ ├── statistici_pe_zile.json │ ├── _puncte_verificare.geojson │ └── puncte_verificare.geojson ├── app │ ├── app.component.css │ ├── components │ │ ├── login │ │ │ ├── login.component.scss │ │ │ ├── login.component.ts │ │ │ └── login.component.html │ │ ├── main │ │ │ ├── logs │ │ │ │ ├── logs.component.scss │ │ │ │ ├── logs.component.html │ │ │ │ └── logs.component.ts │ │ │ ├── community │ │ │ │ ├── community.component.css │ │ │ │ ├── community.component.html │ │ │ │ └── community.component.ts │ │ │ ├── administration │ │ │ │ ├── administration.component.scss │ │ │ │ ├── user-list │ │ │ │ │ ├── user-list.component.scss │ │ │ │ │ ├── user-list.component.html │ │ │ │ │ └── user-list.component.ts │ │ │ │ ├── patients-list │ │ │ │ │ ├── patients-list.component.scss │ │ │ │ │ ├── add-new-patient │ │ │ │ │ │ ├── add-new-patient.component.scss │ │ │ │ │ │ └── add-new-patient.component.html │ │ │ │ │ └── patients-list.component.ts │ │ │ │ ├── administration.component.html │ │ │ │ └── administration.component.ts │ │ │ ├── statistics │ │ │ │ ├── mobility │ │ │ │ │ ├── mobility.component.scss │ │ │ │ │ ├── mobility.component.html │ │ │ │ │ └── mobility.component.ts │ │ │ │ ├── air-quality │ │ │ │ │ ├── air-quality.component.scss │ │ │ │ │ ├── air-quality.component.html │ │ │ │ │ └── air-quality.component.ts │ │ │ │ ├── relation-cases │ │ │ │ │ ├── relation-cases.component.scss │ │ │ │ │ └── relation-cases.component.html │ │ │ │ ├── daily-tests │ │ │ │ │ ├── daily-tests.component.html │ │ │ │ │ ├── daily-tests.component.ts │ │ │ │ │ └── daily-tests.component.css │ │ │ │ ├── coronavirus-europe │ │ │ │ │ ├── coronavirus-europe.component.scss │ │ │ │ │ ├── coronavirus-europe.component.html │ │ │ │ │ └── coronavirus-europe.component.ts │ │ │ │ ├── europa-cases-graph │ │ │ │ │ ├── europa-cases-graph.component.html │ │ │ │ │ ├── europa-cases-graph.component.ts │ │ │ │ │ └── europa-cases-graph.component.css │ │ │ │ ├── counties-cases │ │ │ │ │ ├── counties-cases.component.html │ │ │ │ │ ├── counties-cases.component.scss │ │ │ │ │ └── counties-cases.component.ts │ │ │ │ ├── deaths │ │ │ │ │ ├── deaths.component.css │ │ │ │ │ ├── deaths.component.html │ │ │ │ │ └── deaths.component.ts │ │ │ │ ├── europe-situation │ │ │ │ │ ├── europe-situation.component.html │ │ │ │ │ ├── europe-situation.component.css │ │ │ │ │ └── europe-situation.component.ts │ │ │ │ ├── statistics.component.scss │ │ │ │ ├── statistics.component.html │ │ │ │ ├── general-statistics │ │ │ │ │ ├── general-statistics.component.scss │ │ │ │ │ └── general-statistics.component.html │ │ │ │ └── statistics.component.ts │ │ │ ├── about │ │ │ │ ├── about.component.scss │ │ │ │ └── about.component.ts │ │ │ ├── main.component.html │ │ │ ├── maps │ │ │ │ ├── maps.component.css │ │ │ │ ├── no2-emission │ │ │ │ │ ├── no2-emission.component.css │ │ │ │ │ ├── no2-emission.component.html │ │ │ │ │ └── no2-emission.component.ts │ │ │ │ ├── communities │ │ │ │ │ ├── communities.component.css │ │ │ │ │ ├── communities.component.html │ │ │ │ │ └── communities.component.ts │ │ │ │ ├── frontier-situation │ │ │ │ │ ├── frontier-situation.component.html │ │ │ │ │ ├── frontier-situation.component.css │ │ │ │ │ └── frontier-situation.component.ts │ │ │ │ ├── european-context │ │ │ │ │ ├── european-context.component.css │ │ │ │ │ ├── european-context.component.html │ │ │ │ │ └── european-context.component.ts │ │ │ │ ├── hospital-infrastructure │ │ │ │ │ ├── hospital-infrastructure.component.html │ │ │ │ │ ├── hospital-infrastructure.component.css │ │ │ │ │ └── hospital-infrastructure.component.ts │ │ │ │ ├── social-interest-points │ │ │ │ │ ├── social-interest-points.component.css │ │ │ │ │ ├── social-interest-points.component.html │ │ │ │ │ └── social-interest-points.component.ts │ │ │ │ ├── maps.component.html │ │ │ │ └── maps.component.ts │ │ │ ├── home │ │ │ │ ├── graphics │ │ │ │ │ ├── graphics.component.html │ │ │ │ │ └── graphics.component.scss │ │ │ │ ├── right-menu │ │ │ │ │ ├── right-menu.component.scss │ │ │ │ │ ├── right-menu.component.ts │ │ │ │ │ └── right-menu.component.html │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.ts │ │ │ │ ├── left-menu │ │ │ │ │ ├── left-menu.component.scss │ │ │ │ │ ├── left-menu.component.ts │ │ │ │ │ └── left-menu.component.html │ │ │ │ ├── home.component.scss │ │ │ │ └── map │ │ │ │ │ ├── map.component.scss │ │ │ │ │ └── map.component.html │ │ │ ├── main.component.ts │ │ │ └── manifest │ │ │ │ ├── manifest.component.ts │ │ │ │ ├── manifest.component.css │ │ │ │ └── manifest.component.html │ │ ├── layout │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ └── footer.component.ts │ │ │ └── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.ts │ │ │ │ └── header.component.scss │ │ └── install-prompt │ │ │ ├── install-prompt.component.scss │ │ │ ├── install-prompt.component.html │ │ │ └── install-prompt.component.ts │ ├── models │ │ ├── index.ts │ │ └── user.ts │ ├── app.component.html │ ├── interceptors │ │ ├── index.ts │ │ ├── error.interceptor.ts │ │ └── jwt.interceptor.ts │ ├── services │ │ ├── index.ts │ │ ├── statistics.service.ts │ │ ├── storage.service.ts │ │ ├── shared.service.ts │ │ ├── administration.service.ts │ │ ├── authentication.service.ts │ │ ├── notifications.service.ts │ │ ├── pwa.service.ts │ │ └── dashboard.service.ts │ ├── interfaces │ │ └── all-cases-by-county-response.ts │ ├── app.component.scss │ ├── guards │ │ ├── skipIfLoggedIn.guard.ts │ │ └── auth.guard.service.ts │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app-routing.module.ts ├── favicon.ico ├── main.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── test.ts ├── index.html ├── manifest.webmanifest └── polyfills.ts ├── proxy.conf.json ├── api ├── pgpool.js ├── config.js ├── server.js └── routes │ ├── _statistics.js │ ├── _authentication.js │ ├── _dashboard.v2.js │ └── index.js ├── e2e ├── tsconfig.json ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts └── protractor.conf.js ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── browserslist ├── tsconfig.json ├── ngsw-config.json ├── .gitignore ├── karma.conf.js ├── README.md ├── tslint.json ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/login/login.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/logs/logs.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; -------------------------------------------------------------------------------- /src/app/components/main/community/community.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/components/main/administration/administration.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/mobility/mobility.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/administration/user-list/user-list.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/air-quality/air-quality.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/administration/patients-list/patients-list.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/components/main/about/about.component.scss: -------------------------------------------------------------------------------- 1 | ul { 2 | overflow-wrap: break-word; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/main/community/community.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/app/components/main/administration/patients-list/add-new-patient/add-new-patient.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/main/administration/user-list/user-list.component.html: -------------------------------------------------------------------------------- 1 |

user-list works!

2 | -------------------------------------------------------------------------------- /src/app/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error.interceptor'; 2 | export * from './jwt.interceptor'; -------------------------------------------------------------------------------- /src/assets/images/bkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/images/bkg.png -------------------------------------------------------------------------------- /src/assets/images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/images/graph.png -------------------------------------------------------------------------------- /src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/images/geospatial_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/images/geospatial_large.png -------------------------------------------------------------------------------- /src/assets/images/geospatial_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geospatialorg/covid19/HEAD/src/assets/images/geospatial_small.png -------------------------------------------------------------------------------- /src/app/components/layout/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "/api/*": { 4 | "target": "http://127.0.0.1:6200", 5 | "secure": false, 6 | "logLevel": "debug", 7 | "changeOrigin": true 8 | } 9 | } -------------------------------------------------------------------------------- /src/assets/community.json: -------------------------------------------------------------------------------- 1 | { 2 | "html": "

Proiecte derulate de comunitate


Sectiune in dezvoltare

" 3 | } -------------------------------------------------------------------------------- /src/app/models/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | id: number; 3 | username: string; 4 | password: string; 5 | firstName: string; 6 | lastName: string; 7 | group_id: string; 8 | token: string; 9 | } -------------------------------------------------------------------------------- /src/app/components/install-prompt/install-prompt.component.scss: -------------------------------------------------------------------------------- 1 | .prompt-install { 2 | width: 100%; 3 | text-align: center; 4 | padding-top: 20px; 5 | padding-bottom: 20px; 6 | background-color: rgba(162, 161, 0, 0.59); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authentication.service'; 2 | export * from './dashboard.service'; 3 | export * from './administration.service'; 4 | export * from './statistics.service'; 5 | export * from './shared.service'; 6 | -------------------------------------------------------------------------------- /src/app/components/main/administration/administration.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/logs/logs.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Jurnal actualizări aplicație

3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/components/main/main.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /src/app/components/main/maps/maps.component.css: -------------------------------------------------------------------------------- 1 | .new-row { 2 | margin-left: 5px !important; 3 | } 4 | 5 | @media (max-width: 550px) { 6 | .new-row { 7 | margin-left: unset !important; 8 | margin-top: 5px !important; 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /api/pgpool.js: -------------------------------------------------------------------------------- 1 | 2 | const pg = require('pg'); 3 | const config = require('./config'); 4 | 5 | let pool; 6 | 7 | module.exports = { 8 | getPool: () => { 9 | if(pool) return pool; 10 | 11 | pool = new pg.Pool(config.db); 12 | return pool; 13 | } 14 | } -------------------------------------------------------------------------------- /src/app/components/install-prompt/install-prompt.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/relation-cases/relation-cases.component.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | #statistics iframe { 4 | width: 100%; 5 | height: 1000px; 6 | } 7 | 8 | @media (max-width: 600px) { 9 | #statistics iframe { 10 | width: 90%; 11 | height: 100%; 12 | } 13 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/interfaces/all-cases-by-county-response.ts: -------------------------------------------------------------------------------- 1 | export interface AllCasesByCountyResponse { 2 | confirmed: { 3 | total: number, 4 | data: Array 5 | }; 6 | deaths: { 7 | total: number, 8 | data: Array 9 | }; 10 | healed: { 11 | total: number, 12 | data: Array 13 | }; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/daily-tests/daily-tests.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /api/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | auth: { 3 | jwtTokenSecret: '', 4 | tokenExpireTime: '' 5 | }, 6 | app: { 7 | port: '', 8 | apiPath: '/api' 9 | }, 10 | db: { 11 | user: '', 12 | password: '', 13 | host: '', 14 | database: '', 15 | port: '' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/components/main/home/graphics/graphics.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 |
7 |
8 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/coronavirus-europe/coronavirus-europe.component.scss: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | 5 | } 6 | .europe-statistics { 7 | background-color: rgb(51, 51, 51); 8 | } 9 | 10 | @media (max-width: 550px) { 11 | iframe { 12 | width: 100%; 13 | height: 100%; 14 | /* max-width: 344px; */ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/main/home/right-menu/right-menu.component.scss: -------------------------------------------------------------------------------- 1 | .right-menu { 2 | width: 100%; 3 | .borderlessTable { 4 | overflow-y: auto; 5 | height: 100%; 6 | max-height: 180px; 7 | } 8 | 9 | @media screen and (max-width: 991px) { 10 | .borderlessTable { 11 | overflow-y: hidden; 12 | max-height: 100%; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europa-cases-graph/europa-cases-graph.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/components/main/maps/no2-emission/no2-emission.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/no2-emission/no2-emission.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/counties-cases/counties-cases.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/administration/user-list/user-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-user-list', 5 | templateUrl: './user-list.component.html', 6 | styleUrls: ['./user-list.component.scss'] 7 | }) 8 | export class UserListComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/daily-tests/daily-tests.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-daily-tests', 5 | templateUrl: './daily-tests.component.html', 6 | styleUrls: ['./daily-tests.component.css'] 7 | }) 8 | export class DailyTestsComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/app/components/main/administration/administration.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-administration', 5 | templateUrl: './administration.component.html', 6 | styleUrls: ['./administration.component.scss'] 7 | }) 8 | export class AdministrationComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/components/main/maps/communities/communities.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | min-height: 550px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/communities/communities.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/maps/frontier-situation/frontier-situation.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
-------------------------------------------------------------------------------- /src/app/components/main/maps/european-context/european-context.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | min-height: 550px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/european-context/european-context.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/maps/frontier-situation/frontier-situation.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | min-height: 550px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/hospital-infrastructure/hospital-infrastructure.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/maps/hospital-infrastructure/hospital-infrastructure.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | min-height: 550px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/social-interest-points/social-interest-points.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .map-content { 7 | margin: 0; 8 | height: calc(100% - 45px); 9 | overflow-y: auto; 10 | } 11 | 12 | 13 | @media (max-width: 550px) { 14 | .map-content { 15 | margin: 0; 16 | height: calc(100% - 83px); 17 | overflow-y: unset; 18 | min-height: 550px; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/maps/social-interest-points/social-interest-points.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /src/app/components/main/maps/communities/communities.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-communities', 5 | templateUrl: './communities.component.html', 6 | styleUrls: ['./communities.component.css'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | export class CommunitiesComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit(): void { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | ::ng-deep ::-webkit-scrollbar-track 2 | { 3 | -webkit-box-shadow: inset 0 0 12px rgba(0,0,0,0.3); 4 | border-radius: 3px; 5 | background-color: #F5F5F5; 6 | } 7 | 8 | ::ng-deep ::-webkit-scrollbar 9 | { 10 | width: 10px; 11 | background-color: #ececec; 12 | } 13 | 14 | ::ng-deep ::-webkit-scrollbar-thumb 15 | { 16 | border-radius: 5px; 17 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 18 | background-color: rgba(45, 110, 127, 0.72); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/components/main/maps/maps.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiUrl: '/api', 3 | production: false, 4 | data_refresh: 30 * 60 * 1000, 5 | enable_service_worker: false, // enable service worker 6 | enable_notifications: true, // enable / disable notification request 7 | enable_pwa_mobile_install: false, // enable pwa prompt install install 8 | notification_prompt_delay: 7000, // Allow notification request delay 9 | pwa_install_prompt_delay: 7000, // Prompt install delay 10 | }; 11 | -------------------------------------------------------------------------------- /src/app/services/statistics.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {environment} from 'src/environments/environment'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class StatisticsService { 9 | 10 | constructor( 11 | private http: HttpClient 12 | ) { 13 | } 14 | 15 | getCaseRelations(params?: any) { 16 | return this.http.get(`${environment.apiUrl}/statistics/getCaseRelations`, {params}); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/main/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 |
9 | 10 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europa-cases-graph/europa-cases-graph.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-europa-cases-graph', 5 | templateUrl: './europa-cases-graph.component.html', 6 | styleUrls: ['./europa-cases-graph.component.css'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | export class EuropaCasesGraphComponent implements OnInit { 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/components/layout/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | // .footer { 2 | // padding: 10px 5px; 3 | // background-color: #1976d2; 4 | // color: #ffffff; 5 | // font-size: 13px; 6 | // text-align: center; 7 | // } 8 | 9 | .layout-footer { 10 | padding: 10px 5px; 11 | color: #ffffff; 12 | font-weight: bold; 13 | background-color: #1976d2; 14 | font-size: 14px; 15 | text-align: center; 16 | } 17 | 18 | @media screen and (max-width: 600px) { 19 | .layout-footer { 20 | padding: 10px 5px; 21 | font-size: 12px; 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/components/main/statistics/coronavirus-europe/coronavirus-europe.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/deaths/deaths.component.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | margin: 0 auto; 3 | } 4 | 5 | #deaths-list .ui-dropdown { 6 | width: 30%; 7 | } 8 | 9 | @media(max-width: 1000px) { 10 | #deaths-list .ui-dropdown { 11 | width: 90% !important; 12 | } 13 | } 14 | 15 | @media(min-width: 1001px) and (max-width: 1200px) { 16 | #deaths-list .ui-dropdown { 17 | width: 50%; 18 | } 19 | } 20 | 21 | @media(min-width: 1201px) and (max-width: 1368px) { 22 | #deaths-list .ui-dropdown { 23 | width: 40%; 24 | } 25 | } -------------------------------------------------------------------------------- /src/app/components/main/statistics/counties-cases/counties-cases.component.scss: -------------------------------------------------------------------------------- 1 | #counties-cases { 2 | margin: 0; 3 | height: calc(100% - 55px); 4 | overflow-y: auto; 5 | text-align: center; 6 | } 7 | 8 | #counties-cases iframe { 9 | width: 100%; 10 | height: calc(100% - 83px); 11 | max-width: 1000px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | } 15 | 16 | @media (max-width: 600px) { 17 | #counties-cases { 18 | height: calc(100% - 83px); 19 | } 20 | 21 | #counties-cases iframe { 22 | width: 100%; 23 | height: 100%; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/mobility/mobility.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/daily-tests/daily-tests.component.css: -------------------------------------------------------------------------------- 1 | #tests-graph iframe { 2 | width: 44%; 3 | height: 100%; 4 | margin: 0 auto; 5 | } 6 | 7 | @media(max-width: 1000px) { 8 | #tests-graph iframe { 9 | width: 100%; 10 | } 11 | } 12 | 13 | @media(min-width: 1001px) and (max-width: 1200px) { 14 | #tests-graph iframe { 15 | width: 60%; 16 | } 17 | } 18 | 19 | @media(min-width: 1201px) and (max-width: 1368px) { 20 | #tests-graph iframe { 21 | width: 55%; 22 | } 23 | } 24 | /* 25 | @media (min-width: 1369px) { 26 | #europe-confirmed-graph iframe { 27 | width: 54%; 28 | } 29 | } */ -------------------------------------------------------------------------------- /src/app/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class StorageService { 7 | 8 | constructor() { 9 | } 10 | 11 | set(key, value): void { 12 | if (typeof value !== 'string' && typeof value !== 'number') { 13 | value = JSON.stringify(value); 14 | } 15 | localStorage.setItem(key, value); 16 | } 17 | 18 | get(key): any { 19 | const val = localStorage.getItem(key); 20 | try { 21 | return JSON.parse(val); 22 | } catch (e) { 23 | return val; 24 | } 25 | } 26 | remove(key) { 27 | localStorage.removeItem(key); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/layout/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {DashboardService} from 'src/app/services'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-footer', 7 | templateUrl: './footer.component.html', 8 | styleUrls: ['./footer.component.scss'] 9 | }) 10 | export class FooterComponent implements OnInit { 11 | lastUpdate: any; 12 | 13 | constructor( 14 | private DashboardSvc: DashboardService 15 | ) { 16 | } 17 | 18 | ngOnInit(): void { 19 | this.DashboardSvc.getGlobalStat().subscribe(res => { 20 | if (res && res.data && res.data.data) { 21 | this.lastUpdate = res.data.data[0].to_char; 22 | } 23 | }); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/main/community/community.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | selector: 'app-community', 6 | templateUrl: './community.component.html', 7 | styleUrls: ['./community.component.css'] 8 | }) 9 | export class CommunityComponent implements OnInit { 10 | pageData: any = null; 11 | 12 | constructor( 13 | private domSanitizer: DomSanitizer 14 | ) { } 15 | 16 | ngOnInit(): void { 17 | fetch('assets/community.json').then((response) => response.json()).then((json) => { 18 | this.pageData = this.domSanitizer.bypassSecurityTrustHtml(json.html); 19 | }); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europa-cases-graph/europa-cases-graph.component.css: -------------------------------------------------------------------------------- 1 | #europe-confirmed-graph iframe { 2 | width: 54%; 3 | height: 100%; 4 | margin: 0 auto; 5 | } 6 | 7 | @media(max-width: 1000px) { 8 | #europe-confirmed-graph iframe { 9 | width: 100%; 10 | } 11 | } 12 | 13 | @media(min-width: 1001px) and (max-width: 1200px) { 14 | #europe-confirmed-graph iframe { 15 | width: 70%; 16 | } 17 | } 18 | 19 | @media(min-width: 1201px) and (max-width: 1368px) { 20 | #europe-confirmed-graph iframe { 21 | width: 65%; 22 | } 23 | } 24 | /* 25 | @media (min-width: 1369px) { 26 | #europe-confirmed-graph iframe { 27 | width: 54%; 28 | } 29 | } */ 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('covid app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/favicon.ico", 11 | "/index.html", 12 | "/manifest.webmanifest", 13 | "/*.css", 14 | "/*.js" 15 | ] 16 | } 17 | }, { 18 | "name": "assets", 19 | "installMode": "lazy", 20 | "updateMode": "prefetch", 21 | "resources": { 22 | "files": [ 23 | "/assets/**", 24 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/counties-cases/counties-cases.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import {SharedService} from '../../../../services/shared.service'; 3 | 4 | @Component({ 5 | selector: 'app-counties-cases', 6 | templateUrl: './counties-cases.component.html', 7 | styleUrls: ['./counties-cases.component.scss'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class CountiesCasesComponent implements OnInit { 11 | 12 | constructor(private sharedService: SharedService) { 13 | this.sharedService.setMeta( 14 | 'Cazuri pe judete', 15 | 'covid, romania', 16 | `Cazuri pe judete` 17 | ); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europe-situation/europe-situation.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 |
9 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/components/install-prompt/install-prompt.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit } from '@angular/core'; 2 | import {PwaService} from '../../services/pwa.service'; 3 | 4 | @Component({ 5 | selector: 'app-install-prompt', 6 | templateUrl: './install-prompt.component.html', 7 | styleUrls: ['./install-prompt.component.scss'] 8 | }) 9 | export class InstallPromptComponent implements OnInit { 10 | 11 | constructor(private pwaService: PwaService) { 12 | } 13 | 14 | showInstall = false; 15 | 16 | ngOnInit(): void { 17 | this.pwaService.showPrompt.subscribe((status) => { 18 | this.showInstall = status; 19 | }); 20 | } 21 | 22 | install(e) { 23 | e.preventDefault(); 24 | this.pwaService.promptInstall(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/guards/skipIfLoggedIn.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Resolve, Router, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class SkipIfLoggedIn implements Resolve { 7 | constructor( private router: Router ){} 8 | 9 | resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable|Promise { 10 | const promise = new Promise((resolve, reject) => { 11 | if(localStorage.getItem('currentUser')){ 12 | resolve(this.router.navigate(['/'])); 13 | } 14 | 15 | resolve(true); 16 | }); 17 | 18 | return promise; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/components/main/statistics/coronavirus-europe/coronavirus-europe.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {SharedService} from '../../../../services/shared.service'; 3 | 4 | @Component({ 5 | selector: 'app-coronavirus-europe', 6 | templateUrl: './coronavirus-europe.component.html', 7 | styleUrls: ['./coronavirus-europe.component.scss'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class CoronavirusEuropeComponent implements OnInit { 11 | 12 | constructor(private sharedService: SharedService) { 13 | this.sharedService.setMeta( 14 | 'Corona Virus Europa', 15 | 'covid, europa', 16 | `Corona virus în Europa` 17 | ); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/main/main.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {MenuItem} from 'primeng/api'; 3 | import {SharedService} from '../../services/shared.service'; 4 | import {DashboardService} from '../../services'; 5 | 6 | @Component({ 7 | selector: 'app-dashboard', 8 | templateUrl: './main.component.html', 9 | styleUrls: ['./main.component.scss'], 10 | encapsulation: ViewEncapsulation.None 11 | }) 12 | export class MainComponent implements OnInit { 13 | constructor(private sharedService: SharedService, private dashboardService: DashboardService) { 14 | this.sharedService.setMeta( 15 | null, 16 | 'main, covid, românia', 17 | `Dashboard COVID` 18 | ); 19 | } 20 | 21 | ngOnInit(): void { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/relation-cases/relation-cases.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 | 7 | 10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/components/main/maps/no2-emission/no2-emission.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import {StatisticsService} from '../../../../services'; 3 | import {SharedService} from '../../../../services/shared.service'; 4 | 5 | @Component({ 6 | selector: 'app-no2-emission', 7 | templateUrl: './no2-emission.component.html', 8 | styleUrls: ['./no2-emission.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class No2EmissionComponent implements OnInit { 12 | 13 | constructor(private StatisticsSvc: StatisticsService, private sharedService: SharedService) { 14 | this.sharedService.setMeta( 15 | 'Emisii NO2', 16 | 'harta emisii', 17 | `Harta emisii NO2` 18 | ); 19 | } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/components/main/logs/logs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | selector: 'app-logs', 6 | templateUrl: './logs.component.html', 7 | styleUrls: ['./logs.component.scss'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class LogsComponent implements OnInit { 11 | jsonData : any[]; 12 | 13 | constructor( 14 | private domSanitizer: DomSanitizer 15 | ) { } 16 | 17 | ngOnInit(): void { 18 | fetch('assets/changelog.json').then((response) => response.json()).then((json) => { 19 | this.jsonData = json.map(e => { 20 | e['Anun\u021b'] = this.domSanitizer.bypassSecurityTrustHtml(e['Anun\u021b']); 21 | return e; 22 | }); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/main/maps/hospital-infrastructure/hospital-infrastructure.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {StatisticsService} from '../../../../services'; 3 | import {SharedService} from '../../../../services/shared.service'; 4 | 5 | @Component({ 6 | selector: 'app-hospital-infrastructure', 7 | templateUrl: './hospital-infrastructure.component.html', 8 | styleUrls: ['./hospital-infrastructure.component.css'] 9 | }) 10 | export class HospitalInfrastructureComponent implements OnInit { 11 | 12 | constructor(private StatisticsSvc: StatisticsService, private sharedService: SharedService) { 13 | this.sharedService.setMeta( 14 | 'Infrastructura spitale', 15 | 'infrastructura spitale romania', 16 | `Infrastructura spitale` 17 | ); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/air-quality/air-quality.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/components/main/maps/european-context/european-context.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import {StatisticsService} from '../../../../services'; 3 | import {SharedService} from '../../../../services/shared.service'; 4 | 5 | @Component({ 6 | selector: 'app-european-context', 7 | templateUrl: './european-context.component.html', 8 | styleUrls: ['./european-context.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class EuropeanContextComponent implements OnInit { 12 | 13 | constructor(private StatisticsSvc: StatisticsService, private sharedService: SharedService) { 14 | this.sharedService.setMeta( 15 | 'Context european', 16 | 'harta context european', 17 | `Harta context european` 18 | ); 19 | } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Coronavirus COVID-19 România 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 |

Aplicația se încarcă...

20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/app/components/main/maps/frontier-situation/frontier-situation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import {StatisticsService} from '../../../../services'; 3 | import {SharedService} from '../../../../services/shared.service'; 4 | 5 | @Component({ 6 | selector: 'app-frontier-situation', 7 | templateUrl: './frontier-situation.component.html', 8 | styleUrls: ['./frontier-situation.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class FrontierSituationComponent implements OnInit { 12 | 13 | constructor(private StatisticsSvc: StatisticsService, private sharedService: SharedService) { 14 | this.sharedService.setMeta( 15 | 'Situatie la frontiere', 16 | 'harta context european', 17 | `Situatie la frontiere` 18 | ); 19 | } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/components/main/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {Meta, Title} from '@angular/platform-browser'; 3 | import {SharedService} from '../../../services/shared.service'; 4 | import {DashboardService} from '../../../services'; 5 | import {BehaviorSubject} from 'rxjs'; 6 | 7 | @Component({ 8 | selector: 'app-home', 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.scss'], 11 | encapsulation: ViewEncapsulation.None 12 | }) 13 | export class HomeComponent implements OnInit { 14 | 15 | constructor(private sharedService: SharedService, private dashboardService: DashboardService) { 16 | this.sharedService.setMeta( 17 | null, 18 | 'main, covid, românia, cazuri confirmate', 19 | 'Dashboard interactiv despre cazurile COVID19 confirmate în România' 20 | ); 21 | } 22 | 23 | ngOnInit() { } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/deaths/deaths.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 | 10 | 11 |
12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/app/interceptors/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | import { AuthenticationService } from '../services'; 7 | 8 | @Injectable() 9 | 10 | export class ErrorInterceptor implements HttpInterceptor { 11 | constructor(private authSvc: AuthenticationService) {} 12 | 13 | intercept( request: HttpRequest, next: HttpHandler ) : Observable> { 14 | return next.handle(request).pipe(catchError(err => { 15 | if(err.status === 401 || err.status === 403){ 16 | this.authSvc.clearSession(); 17 | } 18 | 19 | const error = err.error.message || err.statusText; 20 | return throwError(error); 21 | })) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/main/maps/social-interest-points/social-interest-points.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import {StatisticsService} from '../../../../services'; 3 | import {SharedService} from '../../../../services/shared.service'; 4 | 5 | @Component({ 6 | selector: 'app-social-interest-points', 7 | templateUrl: './social-interest-points.component.html', 8 | styleUrls: ['./social-interest-points.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class SocialInterestPointsComponent implements OnInit { 12 | 13 | constructor(private StatisticsSvc: StatisticsService, private sharedService: SharedService) { 14 | this.sharedService.setMeta( 15 | 'Puncte de interes social', 16 | 'puncte de interes, bancomate, farmacii, cabinate veterinare', 17 | `Puncte de interes social` 18 | ); 19 | } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | /api/config.js 13 | api/config.js 14 | config.js 15 | /api/public 16 | 17 | # profiling files 18 | chrome-profiler-events*.json 19 | speed-measure-plugin*.json 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | /public 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | .history/* 38 | 39 | # misc 40 | /.sass-cache 41 | /connect.lock 42 | /coverage 43 | /libpeerconnection.log 44 | npm-debug.log 45 | yarn-error.log 46 | testem.log 47 | /typings 48 | final-index.html 49 | 50 | # System Files 51 | .DS_Store 52 | Thumbs.db 53 | config.js 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europe-situation/europe-situation.component.css: -------------------------------------------------------------------------------- 1 | #europe-situation-graph iframe { 2 | width: 50%; 3 | height: 100%; 4 | margin: 0 auto; 5 | } 6 | 7 | #europe-situation-graph iframe#full { 8 | width: 100%; 9 | height: 100%; 10 | margin: 0 auto; 11 | } 12 | 13 | #graph-list .ui-dropdown { 14 | width: 30%; 15 | } 16 | 17 | @media(max-width: 1000px) { 18 | .ui-dropdown { 19 | width: 90% !important; 20 | } 21 | #europe-situation-graph iframe { 22 | width: 100%; 23 | } 24 | } 25 | 26 | @media(min-width: 1001px) and (max-width: 1200px) { 27 | #graph-list .ui-dropdown { 28 | width: 50%; 29 | } 30 | #europe-situation-graph iframe { 31 | width: 70%; 32 | } 33 | } 34 | 35 | @media(min-width: 1201px) and (max-width: 1368px) { 36 | #graph-list .ui-dropdown { 37 | width: 40%; 38 | } 39 | #europe-situation-graph iframe { 40 | width: 65%; 41 | } 42 | } -------------------------------------------------------------------------------- /api/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const morgan = require('morgan'); 4 | const path = require('path'); 5 | 6 | const config = require('./config'); 7 | const port = process.env.PORT || config.app.port; 8 | 9 | const router = express.Router(); 10 | 11 | let app = express(); 12 | 13 | app 14 | .use(bodyParser.urlencoded({ extended: true })) 15 | .use(bodyParser.json({ limit: '10mb' })) 16 | .use(morgan('dev')) 17 | .use(express.static(path.join(__dirname, 'public'))); 18 | 19 | app.use(router); 20 | 21 | app.use((req, res, next)=>{ 22 | res.append("COVID19-Dupa-CloudFront", "DA"); 23 | next(); 24 | }); 25 | 26 | let routerLogic = require('./routes')(app); 27 | 28 | app.use('/', (req, res) => { 29 | res.sendFile('./public/index.html', { 'root': __dirname }); 30 | }); 31 | 32 | const server = require('http').createServer(app); 33 | 34 | server.listen(port, () =>{ 35 | console.log(`Server listening on port ${port}`); 36 | }); -------------------------------------------------------------------------------- /api/routes/_statistics.js: -------------------------------------------------------------------------------- 1 | const pool = require('../pgpool').getPool(); 2 | 3 | let router = require('express').Router(); 4 | const cors = require('cors'); 5 | 6 | router 7 | .route('/getCaseRelations') 8 | .get(cors(), (req, res) => { 9 | let params_no = 0; 10 | let qp = Array.from(Array(params_no).keys(), x => `$${x+1}`).join(','); 11 | let query = `select COVID.GET_CASES_RELATIONS_REPORT(${qp}) as data`; 12 | 13 | pool.query(query, 14 | [ ], 15 | function (err, result) { 16 | if (err) { 17 | res.status(400).send({ 18 | success: false 19 | }); 20 | 21 | return console.error('error running query', err); 22 | } 23 | 24 | res.status(200).send({ 25 | data: result.rows[0].data || null 26 | }); 27 | } 28 | ); 29 | 30 | }); 31 | 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /src/app/components/main/home/left-menu/left-menu.component.scss: -------------------------------------------------------------------------------- 1 | .disclaimer { 2 | max-width: 450px; 3 | 4 | p { 5 | text-indent: 15px; 6 | padding: 5px; 7 | } 8 | 9 | .ui-dialog-titlebar { 10 | background-color: #ff7f00 !important; 11 | text-align: center; 12 | font-size: 18px; 13 | } 14 | 15 | .ui-widget-content { 16 | text-align: justify; 17 | padding: 1em 0.75em !important; 18 | } 19 | } 20 | 21 | .left-menu { 22 | .borderlessTable { 23 | overflow-y: auto; 24 | max-height: calc(100vh - 360px); 25 | 26 | @media screen and (max-width: 991px) { 27 | max-height: 100%; 28 | } 29 | } 30 | } 31 | 32 | @media screen and (max-width: 600px) { 33 | .disclaimer { 34 | max-width: 95vw; 35 | 36 | p { 37 | font-size: 12px; 38 | } 39 | 40 | .ui-dialog-titlebar { 41 | font-size: 14px; 42 | padding: 0.75em !important; 43 | } 44 | 45 | .ui-widget-content { 46 | padding: 0.5em 1em !important; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/components/main/manifest/manifest.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {SharedService} from '../../../services/shared.service'; 3 | 4 | @Component({ 5 | selector: 'app-manifest', 6 | templateUrl: './manifest.component.html', 7 | styleUrls: ['./manifest.component.css'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class ManifestComponent implements OnInit { 11 | 12 | constructor(private sharedService: SharedService) { 13 | this.sharedService.setMeta( 14 | 'Manifest', 15 | 'manifest, covid, românia', 16 | `Asociația geo-spatial.org solicită autorităților 17 | să ofere public o prezentare completă, structurată a 18 | tuturor cazurilor oficiale (confirmate, vindecări, decese) actualizată cât mai des posibil, 19 | pentru a disemina aceste informații vitale și a evita apariția informațiilor 20 | false, potențial alarmiste.` 21 | ); 22 | } 23 | 24 | ngOnInit(): void { 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/app/components/main/home/home.component.scss: -------------------------------------------------------------------------------- 1 | #main-container { 2 | overflow: auto; 3 | margin: 0; 4 | width: 100%; 5 | height: calc(100% - 2px); 6 | padding: 0; 7 | } 8 | 9 | #right-sidebar, 10 | #left-sidebar, 11 | #map-container { 12 | height: calc(100% - 5px); 13 | overflow-y: auto; 14 | } 15 | 16 | @media screen and (max-width: 600px) { 17 | #main-content, #right-sidebar, #left-sidebar, #map-container{ 18 | height: 100%; 19 | overflow-y: unset; 20 | } 21 | 22 | #map { 23 | height: 340px; 24 | position: relative; 25 | } 26 | 27 | #map-container { 28 | width: calc(100% - 3px); 29 | height: auto !important; 30 | } 31 | 32 | #custom-grid { 33 | display: block; 34 | flex-wrap: nowrap; 35 | } 36 | } 37 | 38 | 39 | @media screen and (max-width: 991px) { 40 | #right-sidebar, 41 | #left-sidebar, 42 | #map-container { 43 | height: auto; 44 | overflow-y: hidden; 45 | } 46 | 47 | #map-container { 48 | height: 100%; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/services/shared.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Meta, Title} from '@angular/platform-browser'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class SharedService { 8 | private readonly defaultTitle: string; 9 | 10 | constructor(private titleService: Title, private metaService: Meta) { 11 | this.defaultTitle = this.titleService.getTitle(); 12 | } 13 | 14 | setMeta(title: string | null, keywords: string | null = null, description: string | null = null) { 15 | this.titleService.setTitle((title ? title + ' | ' : '') + this.defaultTitle); 16 | if (keywords) { 17 | this.metaService.addTag({name: 'keywords', content: keywords}); 18 | } 19 | if (keywords) { 20 | this.metaService.addTag({name: 'description', content: description}); 21 | } 22 | } 23 | 24 | getGeojsonData() { 25 | return fetch('assets/counties.geojson').then((response) => response.json()).then((json) => json); 26 | } 27 | 28 | getGeojsonQuarantineData() { 29 | return fetch('assets/uat_q.json').then((response) => response.json()).then((json) => json); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/covid'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | apiUrl: '/api', 7 | production: false, 8 | data_refresh: 30 * 60 * 1000, 9 | enable_service_worker: false, // enable service worker 10 | enable_notifications: true, // enable / disable notification request 11 | notification_prompt_delay: 7000, // Allow notification request delay 12 | enable_pwa_mobile_install: false, // enable pwa prompt install install 13 | pwa_install_prompt_delay: 7000, // Prompt install delay 14 | }; 15 | 16 | /* 17 | * For easier debugging in development mode, you can import the following file 18 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 19 | * 20 | * This import should be commented out in production mode because it will have a negative impact 21 | * on performance if an error is thrown. 22 | */ 23 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 24 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/statistics.component.scss: -------------------------------------------------------------------------------- 1 | //.sub-menu { 2 | // background-color: rgba(25, 118, 210, 0.4); 3 | // border-top: 1px solid #eee; 4 | // border-bottom: 1px solid #eee; 5 | // padding: 5px; 6 | //} 7 | // 8 | //.sub-menu button { 9 | // margin-right: 5px; 10 | //} 11 | 12 | // .new-row { 13 | // margin-left: 5px !important; 14 | // } 15 | ::ng-deep .statistics-container { 16 | height: calc(100% - 51px); 17 | overflow-y: auto; 18 | } 19 | 20 | @media (max-width: 550px) { 21 | #maps-btn { 22 | margin-left: 0 !important; 23 | display: block; 24 | margin-top: 5px; 25 | } 26 | 27 | .new-row { 28 | margin-right: unset !important; 29 | margin-top: 5px !important; 30 | } 31 | } 32 | 33 | .s-m-container { 34 | overflow-x: hidden; 35 | width: 100%; 36 | white-space: nowrap; 37 | } 38 | 39 | .sub-menu { 40 | position: relative; 41 | 42 | .arrow-btn { 43 | position: absolute; 44 | z-index: 1; 45 | } 46 | 47 | .arrow-btn.left { 48 | left: 0; 49 | } 50 | 51 | .arrow-btn.right { 52 | right: 0; 53 | } 54 | } 55 | 56 | .sub-menu.with-arrows { 57 | padding-left: 42px; 58 | padding-right: 42px; 59 | } -------------------------------------------------------------------------------- /src/app/components/main/home/map/map.component.scss: -------------------------------------------------------------------------------- 1 | #map { 2 | width: 100%; 3 | height: calc(100% - 65px); 4 | position: relative; 5 | margin-top: 10px; 6 | } 7 | 8 | .map-legend { 9 | position: absolute; 10 | z-index: 100; 11 | background-color: #fff; 12 | bottom: 10px; 13 | left: 10px; 14 | padding: 0 15px; 15 | box-shadow: 0 0 3px 3px rgba(75, 74, 74, 0.25); 16 | } 17 | 18 | .show-legend { 19 | top: 65px; 20 | left: .5em; 21 | } 22 | 23 | .show-legend button { 24 | cursor: pointer; 25 | } 26 | 27 | .show-legend.show-legend-active button { 28 | background-color: rgba(0,60,136,0.8); 29 | } 30 | 31 | .ol-touch .show-legend { 32 | top: 80px; 33 | } 34 | 35 | #layers { 36 | margin:5px 0; 37 | padding: 0; 38 | list-style-type: none; 39 | } 40 | 41 | #layers li { 42 | display: inline-block; 43 | margin-right: 5px; 44 | } 45 | 46 | #layers li .ui-button-secondary { 47 | background-color: #ddd; 48 | } 49 | 50 | @media screen and (max-width: 600px) { 51 | #map { 52 | height: 340px; 53 | position: relative; 54 | } 55 | 56 | #map-container { 57 | width: calc(100% - 3px); 58 | height: auto !important; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'covid'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('covid'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('.content span').textContent).toContain('covid app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/statistics.component.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Covid19 România", 3 | "short_name": "Covid19 România", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "./", 8 | "start_url": "./", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coronavirus COVID-19 România 2 | 3 | Project home: [https://covid19.geo-spatial.org/](https://covid19.geo-spatial.org/) 4 | 5 | About: [https://covid19.geo-spatial.org/despre](https://covid19.geo-spatial.org/despre) 6 | 7 | ---- 8 | 9 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.25. 10 | 11 | ## Development server 12 | 13 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 14 | 15 | ## Code scaffolding 16 | 17 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 18 | 19 | ## Build 20 | 21 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 22 | 23 | ## Running unit tests 24 | 25 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 26 | 27 | ## Running end-to-end tests 28 | 29 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 30 | 31 | ## Further help 32 | 33 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 34 | -------------------------------------------------------------------------------- /src/app/services/administration.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {environment} from 'src/environments/environment'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class AdministrationService { 9 | 10 | constructor( 11 | private http: HttpClient 12 | ) { 13 | } 14 | 15 | getCaseList(params?: any) { 16 | return this.http.get(`${environment.apiUrl}/administration/getCaseList`, {params}); 17 | } 18 | 19 | getGlobalStat(params?: any) { 20 | return this.http.get(`${environment.apiUrl}/dashboard/getGlobalStat`, {params}); 21 | } 22 | 23 | setCase(params: any) { 24 | return this.http.post(`${environment.apiUrl}/administration/setCase`, params); 25 | } 26 | 27 | deleteCase(params: any) { 28 | return this.http.post(`${environment.apiUrl}/administration/deleteCase`, params); 29 | } 30 | 31 | getUatCombo(params?: any) { 32 | return this.http.get(`${environment.apiUrl}/administration/getUatCombo`, {params}); 33 | } 34 | 35 | getCountyCombo(params?: any) { 36 | return this.http.get(`${environment.apiUrl}/administration/getCountyCombo`, {params}); 37 | } 38 | 39 | getCaseDetails(params?: any) { 40 | return this.http.get(`${environment.apiUrl}/administration/getCaseDetails`, {params}); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/guards/auth.guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | import { AuthenticationService } from '../services'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | 9 | export class AuthGuard implements CanActivate { 10 | constructor( 11 | private router: Router, 12 | private authSvc: AuthenticationService 13 | ) { } 14 | 15 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean { 16 | if (route.queryParams.token) { 17 | return new Promise((resolve, reject) => { 18 | this.authSvc.checkToken({ token: route.queryParams.token }).subscribe(res => { 19 | 20 | if (!res.success) { 21 | reject(this.router.navigate([''], {})); 22 | return false; 23 | } 24 | 25 | if (res.user) { 26 | return this.authSvc.setToken(res.user).subscribe(r => { 27 | resolve(true); 28 | }); 29 | } 30 | 31 | reject(this.router.navigate([''], {})); 32 | return false; 33 | }); 34 | }); 35 | } 36 | else { 37 | const currentUser = this.authSvc.currentUserValue; 38 | 39 | if (currentUser) { 40 | return true; 41 | } 42 | 43 | this.router.navigate([''], {}); 44 | return false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/components/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { AuthenticationService } from '../../services'; 4 | import * as CryptoJS from 'crypto-js'; 5 | 6 | @Component({ 7 | selector: 'app-login', 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.scss'] 10 | }) 11 | export class LoginComponent implements OnInit { 12 | username: string; 13 | password: string; 14 | 15 | loginFormHasErrors: boolean; 16 | loginFormErrors: any; 17 | invalidLoginMessage: string; 18 | 19 | constructor( 20 | private authSvc: AuthenticationService, 21 | public router: Router 22 | ) { } 23 | 24 | ngOnInit(): void { 25 | } 26 | 27 | doLogin() { 28 | this.loginFormHasErrors = false; 29 | 30 | this.loginFormErrors = { 31 | noPassword: !this.password 32 | }; 33 | 34 | if(this.loginFormErrors.noPassword){ 35 | this.loginFormHasErrors = true; 36 | return false; 37 | } 38 | 39 | // de pus in functiune cand se modifica in baza de date 40 | let pass = CryptoJS.SHA256(this.password).toString(CryptoJS.enc.Hex); 41 | 42 | this.authSvc.login(this.username, pass).subscribe(async (res) => { 43 | if(!res.success){ 44 | this.loginFormErrors.invalidLogin = true; 45 | this.loginFormHasErrors = true; 46 | this.invalidLoginMessage = res.message; 47 | this.password = ''; 48 | return; 49 | } 50 | 51 | 52 | this.router.navigate(['/administration']); 53 | }); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/components/main/maps/maps.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {SharedService} from '../../../services/shared.service'; 3 | 4 | @Component({ 5 | selector: 'app-maps', 6 | templateUrl: './maps.component.html', 7 | styleUrls: ['./maps.component.css'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class MapsComponent implements OnInit { 11 | 12 | submenuItems = [ 13 | { 14 | routerLink: '/harti/hospital-infrastructure', 15 | label: 'Infrastructura spitalicească', 16 | }, 17 | { 18 | routerLink: '/harti/comunitati-marginalizate', 19 | label: 'Comunități marginalizate', 20 | }, 21 | { 22 | routerLink: '/harti/europe', 23 | label: 'Context european', 24 | }, 25 | { 26 | routerLink: '/harti/social-interest-points', 27 | routerLinkActive: 'ui-button-info', 28 | routerLinkActiveOptions: {exact: true}, 29 | label: 'Puncte de interes social', 30 | }, 31 | { 32 | routerLink: '/harti/frontier-situation', 33 | label: 'Situația la frontieră', 34 | }, 35 | { 36 | routerLink: '/harti/no2-emission', 37 | label: 'Concentrații NO2', 38 | } 39 | ]; 40 | 41 | constructor(private sharedService: SharedService) { 42 | this.sharedService.setMeta( 43 | 'Hărți', 44 | 'hărți, covid, românia', 45 | `Hărți informative despre diferite aspecte privind COVID, noxe, puncte de interes social, infrastructura spitaliceasca` 46 | ); 47 | } 48 | 49 | ngOnInit(): void { 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/assets/changelog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Anun\u021b": "

03.04.2020 13:00: A fost actualizat\u0103 harta cu num\u0103rul de cazuri \u0219i cea cu num\u0103rul de vindec\u0103ri pe baza datelor oficiale.

" 4 | }, 5 | { 6 | "Anun\u021b": "

02.04.2020 13:00: A fost actualizat\u0103 harta cu num\u0103rul de cazuri \u0219i cea cu num\u0103rul de vindec\u0103ri. Autorit\u0103\u021bile au revenit la raportarea cazurilor la nivel de jude\u021b.

" 7 | }, 8 | { 9 | "Anun\u021b": "

01.04.2020 15:00: Au fost ad\u0103ugate trei vizualiz\u0103ri noi: Harta zonelor carantinate, Harta infrastructurii spitalice\u0219ti suprapus\u0103 peste harta densit\u0103\u021bii popula\u021biei, Graficul situa\u021bie cazurilor \u00een Europa

" 10 | } 11 | ] -------------------------------------------------------------------------------- /src/app/components/layout/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 16 |
17 | 18 | 19 | 22 | 36 | 39 | 40 | -------------------------------------------------------------------------------- /src/app/components/main/home/right-menu/right-menu.component.ts: -------------------------------------------------------------------------------- 1 | import {AfterViewInit, Component, OnDestroy, OnInit} from '@angular/core'; 2 | import {DashboardService} from 'src/app/services'; 3 | import {environment as appConfig} from '../../../../../environments/environment'; 4 | import {combineLatest, forkJoin} from 'rxjs'; 5 | import {map} from 'rxjs/operators'; 6 | import {SharedService} from '../../../../services/shared.service'; 7 | import {AllCasesByCountyResponse} from '../../../../interfaces/all-cases-by-county-response'; 8 | 9 | @Component({ 10 | selector: 'app-right-menu', 11 | templateUrl: './right-menu.component.html', 12 | styleUrls: ['./right-menu.component.scss'] 13 | }) 14 | export class RightMenuComponent implements OnInit, OnDestroy, AfterViewInit { 15 | tableDataDeath: any[] = []; 16 | tableDataRecovered: any[] = []; 17 | totalDeaths = 0; 18 | totalHealed = 0; 19 | 20 | dataReady = false; 21 | 22 | constructor(private dashboardService: DashboardService) {} 23 | 24 | 25 | ngOnInit(): void { 26 | setTimeout(() => { 27 | this.dashboardService.currentCases.subscribe((response: AllCasesByCountyResponse) => { 28 | if (response.deaths && response.deaths.data) { 29 | this.tableDataDeath = response.deaths.data; 30 | this.totalDeaths = response.deaths.total; 31 | } 32 | if (response.healed && response.healed.data) { 33 | this.tableDataRecovered = response.healed.data; 34 | this.totalHealed = response.healed.total; 35 | } 36 | 37 | this.dataReady = true; 38 | }); 39 | }, 10); 40 | } 41 | 42 | ngOnDestroy(): void {} 43 | 44 | ngAfterViewInit(): void {} 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/app/components/main/home/left-menu/left-menu.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation, ViewChild, OnDestroy, Input} from '@angular/core'; 2 | import {DashboardService} from 'src/app/services'; 3 | import {environment as appConfig} from '../../../../../environments/environment'; 4 | import {Dialog} from 'primeng/dialog'; 5 | import {BehaviorSubject} from 'rxjs'; 6 | import {AllCasesByCountyResponse} from '../../../../interfaces/all-cases-by-county-response'; 7 | 8 | @Component({ 9 | selector: 'app-left-menu', 10 | templateUrl: './left-menu.component.html', 11 | styleUrls: ['./left-menu.component.scss'], 12 | encapsulation: ViewEncapsulation.None 13 | }) 14 | export class LeftMenuComponent implements OnInit, OnDestroy { 15 | @ViewChild(Dialog, {static: true}) dialog; 16 | 17 | tableData: any[] = []; 18 | totalCases = 0; 19 | displayDisclaimer = false; 20 | 21 | dataReady = false; 22 | 23 | constructor( 24 | private dashboardService: DashboardService 25 | ) { 26 | } 27 | 28 | ngOnInit() { 29 | this.dashboardService.currentCases.subscribe((response: AllCasesByCountyResponse) => { 30 | this.tableData = response.confirmed.data; 31 | this.totalCases = response.confirmed.total; 32 | this.dataReady = true; 33 | }); 34 | } 35 | 36 | 37 | ngOnDestroy(): void { } 38 | 39 | showDisclaimer(val) { 40 | this.displayDisclaimer = val; 41 | if (val) { 42 | this.centerModal(); 43 | } 44 | } 45 | 46 | centerModal() { 47 | const modal = this.dialog; 48 | setTimeout(() => { 49 | modal.center(); 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/components/main/manifest/manifest.component.css: -------------------------------------------------------------------------------- 1 | #manifest-antet div{ 2 | width: 50%; 3 | display: inline-block; 4 | } 5 | 6 | #manifest-date { 7 | vertical-align: top; 8 | text-align: right; 9 | padding-top: 30px; 10 | } 11 | 12 | #manifest-to span{ 13 | width: 50px; 14 | display: inline-block; 15 | } 16 | 17 | @media (max-width: 550px) { 18 | #manifest-area { 19 | font-size: 12px; 20 | width: 100%; 21 | margin: unset; 22 | } 23 | 24 | #manifest-antet div { 25 | display: block; 26 | width: 100%; 27 | } 28 | 29 | #manifest-date { 30 | vertical-align: unset; 31 | text-align: right; 32 | padding-top: 10px; 33 | } 34 | 35 | #manifest-logo { 36 | text-align: center; 37 | } 38 | } 39 | 40 | 41 | /* #manifest-area { 42 | text-align: center; 43 | height: 100%; 44 | } 45 | 46 | #manifest-area iframe { 47 | width: 60%; 48 | height: 99%; 49 | } 50 | 51 | @media (max-width: 360px) { 52 | #manifest-area{ 53 | overflow: hidden; 54 | } 55 | #manifest-area iframe { 56 | width: 150%; 57 | margin-left: -85px; 58 | margin-top: -60px; 59 | height: 130%; 60 | 61 | height: calc(100% + 50px); 62 | } 63 | } 64 | 65 | @media (min-width: 361px) and (max-width: 550px) { 66 | #manifest-area{ 67 | overflow: hidden; 68 | } 69 | #manifest-area iframe { 70 | width: 145%; 71 | margin-left: -85px; 72 | margin-top: -60px; 73 | height: 130%; 74 | 75 | height: calc(100% + 50px); 76 | } 77 | } 78 | 79 | @media (min-width: 551px) and (max-width: 900px) { 80 | #manifest-area iframe { 81 | width: 100%; 82 | } 83 | } */ 84 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/general-statistics/general-statistics.component.scss: -------------------------------------------------------------------------------- 1 | /* iframe { 2 | width: 100%; 3 | height: 371px; 4 | margin-bottom: 20px; 5 | } 6 | 7 | @media (max-width: 550px) { 8 | iframe { 9 | width: 100%; 10 | height: 100%; 11 | max-width: 344px; 12 | } 13 | } */ 14 | 15 | .only-xs { 16 | display: none; 17 | } 18 | 19 | .only-large { 20 | display: block; 21 | } 22 | 23 | .statistics-container iframe { 24 | height: 700px; 25 | width: 100%; 26 | } 27 | 28 | // .statistics-container #canvasDaily iframe { 29 | // // height: 925px; 30 | // // width: 70%; 31 | // } 32 | 33 | .statistics-container #canvasByAge iframe, .statistics-container #canvasByGender iframe { 34 | height: 550px; 35 | width: 100%; 36 | } 37 | 38 | @media (max-width: 550px) { 39 | .full-width { 40 | width: 100%; 41 | } 42 | 43 | .only-xs { 44 | display: block; 45 | } 46 | 47 | .only-large { 48 | display: none; 49 | } 50 | 51 | .statistics-container iframe, .statistics-container #canvasDaily iframe, .statistics-container #canvasByGender iframe { 52 | height: 320px; 53 | } 54 | 55 | .statistics-container #canvasByAge iframe { 56 | height: 200px; 57 | } 58 | } 59 | 60 | #statistics-list .ui-dropdown { 61 | width: 30%; 62 | } 63 | 64 | @media(max-width: 1000px) { 65 | #statistics-list .ui-dropdown { 66 | width: 90% !important; 67 | } 68 | } 69 | 70 | @media(min-width: 1001px) and (max-width: 1200px) { 71 | #statistics-list .ui-dropdown { 72 | width: 50%; 73 | } 74 | } 75 | 76 | @media(min-width: 1201px) and (max-width: 1368px) { 77 | #statistics-list .ui-dropdown { 78 | width: 40%; 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, HostListener} from '@angular/core'; 2 | import {PwaService} from './services/pwa.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent { 10 | title = 'covid'; 11 | // 12 | // @HostListener('window:load', ['$event']) 13 | // isPwaAppUsed(e) { 14 | // this.showInstallPrompt = true; 15 | // if (navigator.standalone) { 16 | // console.log('Launched: Installed (iOS)'); 17 | // } else if (matchMedia('(display-mode: standalone)').matches) { 18 | // console.log('Launched: Installed'); 19 | // } else { 20 | // console.log('Launched: Browser Tab'); 21 | // } 22 | // } 23 | // 24 | // @HostListener('window:appinstalled', ['$event']) 25 | // isAppInstalled(e) { 26 | // console.log(e); 27 | // // Prevent Chrome 67 and earlier from automatically showing the prompt 28 | // e.preventDefault(); 29 | // // Stash the event so it can be triggered later. 30 | // this.deferredPrompt = e; 31 | // this.showButton = true; 32 | // } 33 | 34 | @HostListener('window:beforeinstallprompt', ['$event']) onBeforeInstallPrompt(e) { 35 | this.pwaService.onBeforeInstallPrompt(e); 36 | } 37 | @HostListener('window:appinstalled', ['$event']) onAppInstalled(e) { 38 | this.pwaService.onAppInstalled(e); 39 | } 40 | @HostListener('window:load', ['$event']) 41 | isPwaAppUsedIn(e) { 42 | let used = 'tab'; 43 | if (navigator.hasOwnProperty('standalone')) { 44 | used = 'standalone'; // iOS 45 | } else if (matchMedia('(display-mode: standalone)').matches) { 46 | used = 'standalone'; 47 | } 48 | this.pwaService.changeUsedIn(used); 49 | } 50 | 51 | constructor(private pwaService: PwaService) {} 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/components/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 39 |
40 | -------------------------------------------------------------------------------- /src/app/interceptors/jwt.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpEventType, HttpResponse } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import {tap} from 'rxjs/operators'; 5 | 6 | import { AuthenticationService } from '../services'; 7 | 8 | 9 | @Injectable() 10 | 11 | export class JwtInterceptor implements HttpInterceptor { 12 | constructor(private authSvc: AuthenticationService) {} 13 | 14 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 15 | let currentUser = this.authSvc.currentUserValue; 16 | 17 | if(currentUser && currentUser.token){ 18 | request = request.clone({ 19 | setHeaders: { 20 | Authorization: `Bearer ${currentUser.token}`, 21 | 'Cache-Control': 'no-cache, no-store, must-revalidate, post-check=0, pre-check=0', 22 | 'Pragma': 'no-cache', 23 | 'Expires': '0' 24 | } 25 | }); 26 | } 27 | 28 | return next.handle(request).pipe(tap((event: HttpEvent) => { 29 | 30 | if (event instanceof HttpResponse) { 31 | if(event.body.data && !event.body.data.success && event.body.data.code === 'E1'){ 32 | // sesiune expirata 33 | this.authSvc.clearSession(); 34 | return; 35 | } 36 | 37 | let renewToken = event.headers.get('new-access-token'); 38 | 39 | if(renewToken){ 40 | this.authSvc.updateToken(renewToken); 41 | } 42 | } 43 | 44 | return event; 45 | })); 46 | 47 | // if(currentUser && currentUser.token){ 48 | // request = request.clone({ 49 | // setHeaders: { 50 | // Authorization: `Bearer ${currentUser.token}` 51 | // } 52 | // }); 53 | // } 54 | 55 | // return next.handle(request); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/routes/_authentication.js: -------------------------------------------------------------------------------- 1 | const express = require('express'), 2 | router = express.Router(), 3 | jwt = require('jsonwebtoken'), 4 | pg = require('pg'), 5 | config = require('../config'); 6 | 7 | var pool = new pg.Pool(config.db); 8 | 9 | router 10 | .route('/login') 11 | .post(function(req, res){ 12 | 13 | let query = "select COVID.LOGIN($1, $2, $3) as data"; 14 | 15 | let params = req.body; 16 | 17 | params.ip_address = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress; 18 | 19 | if(!params.user_id || !params.password) { 20 | res.status(400).json({success: false, message:"Bad request"}); 21 | return false; 22 | } 23 | 24 | pool.query(query, 25 | [params.user_id, params.password, params.ip_address], 26 | function(err, result){ 27 | if(err){ 28 | res.status(400).send({ 29 | success: false, 30 | user: null 31 | }); 32 | 33 | return console.error('error running query', err); 34 | } 35 | 36 | if(result.rows[0].data.success){ 37 | let user = result.rows[0].data; 38 | 39 | delete user.success; 40 | delete user.message; 41 | delete user.code; 42 | 43 | let token = jwt.sign(user, config.auth.jwtTokenSecret, { 44 | expiresIn: config.auth.tokenExpireTime 45 | }); 46 | 47 | delete user.login_id; 48 | 49 | user.token = token; 50 | 51 | res.status(200).send({ 52 | success: true, 53 | user: user 54 | }); 55 | } else { 56 | res.status(200).send({ 57 | success: false, 58 | user: null, 59 | message: result.rows[0].data.message, 60 | code: result.rows[0].data.code 61 | }); 62 | } 63 | } 64 | ) 65 | }); 66 | 67 | module.exports = router; -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-var-requires": false, 64 | "object-literal-key-quotes": [ 65 | true, 66 | "as-needed" 67 | ], 68 | "object-literal-sort-keys": false, 69 | "ordered-imports": false, 70 | "quotemark": [ 71 | true, 72 | "single" 73 | ], 74 | "trailing-comma": false, 75 | "no-conflicting-lifecycle": true, 76 | "no-host-metadata-property": true, 77 | "no-input-rename": true, 78 | "no-inputs-metadata-property": true, 79 | "no-output-native": true, 80 | "no-output-on-prefix": true, 81 | "no-output-rename": true, 82 | "no-outputs-metadata-property": true, 83 | "template-banana-in-box": true, 84 | "template-no-negated-async": true, 85 | "use-lifecycle-interface": true, 86 | "use-pipe-transform-interface": true 87 | }, 88 | "rulesDirectory": [ 89 | "codelyzer" 90 | ] 91 | } -------------------------------------------------------------------------------- /src/app/components/main/home/graphics/graphics.component.scss: -------------------------------------------------------------------------------- 1 | 2 | :root { 3 | --home-confirmate:steelblue ; 4 | --home-recuperari: limegreen; 5 | --home-decese: #b2beb5; 6 | } 7 | 8 | .wrapper { 9 | margin: 0 20; 10 | max-width: 1200px; 11 | } 12 | 13 | svg.chart-group { 14 | width: 100%; 15 | max-width: 900px; 16 | font: 12px sans-serif; 17 | } 18 | 19 | path { 20 | stroke: grey; 21 | stroke-width: 2; 22 | fill: none; 23 | } 24 | 25 | .axis path, line { 26 | fill: none; 27 | stroke: grey; 28 | stroke-width: 1; 29 | shape-rendering: crispEdges; 30 | } 31 | 32 | .line_total { 33 | stroke: var(--home-confirmate); 34 | } 35 | .line_healed { 36 | stroke: var(--home-recuperari); 37 | } 38 | .line_dead { 39 | stroke: var(--home-decese); 40 | } 41 | 42 | .dot_total { 43 | fill: var(--home-confirmate); 44 | stroke: #333; 45 | } 46 | .dot_healed { 47 | fill: var(--home-recuperari); 48 | stroke: #333; 49 | } 50 | .dot_dead { 51 | fill: var(--home-decese); 52 | stroke: #333; 53 | } 54 | 55 | .overlay { 56 | fill: none; 57 | pointer-events: all; 58 | } 59 | 60 | .focus circle { 61 | fill: #F1F3F3; 62 | stroke: #777; 63 | stroke-width: 3px; 64 | } 65 | 66 | .hover-line { 67 | stroke: #777; 68 | stroke-width: 2px; 69 | stroke-dasharray: 3,3; 70 | } 71 | 72 | tooltip_div.tooltip { 73 | position: absolute; 74 | /* top: 100px; 75 | left: 100px; */ 76 | -moz-border-radius: 5px; 77 | border-radius: 5px; 78 | background: white; 79 | border: 1px solid #222; 80 | opacity: .9; 81 | color: #222; 82 | padding: 10px; 83 | min-width: 16.75vmin; 84 | font-size: 0.7em; 85 | line-height: 12pt; 86 | font-family: "EB Garamond", serif; 87 | /* font-weight: lighter; */ 88 | /* visibility: visible; */ 89 | pointer-events: none; 90 | /* width: 120px; 91 | height: 110px; */ 92 | } 93 | 94 | /* Small devices (tablets, 768px and up) */ 95 | @media (min-width: 550px) { 96 | .wrapper { 97 | width: 97%; 98 | max-width: 767px; 99 | } 100 | } 101 | 102 | /* Large devices (large desktops, 1200px and up) */ 103 | @media (min-width: 850px) { 104 | .wrapper { 105 | width: 97%; 106 | max-width: 1200px; 107 | float: none; 108 | } 109 | } -------------------------------------------------------------------------------- /src/app/components/layout/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation} from '@angular/core'; 2 | import {MenuItem} from 'primeng/api'; 3 | import {AuthenticationService} from 'src/app/services'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.scss'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class HeaderComponent implements OnInit { 12 | currentUser: any = null; 13 | menuItems: MenuItem[]; 14 | 15 | displaySidebar = false; 16 | 17 | constructor( 18 | private AuthSvc: AuthenticationService 19 | ) { 20 | } 21 | 22 | ngOnInit(): void { 23 | this.currentUser = this.AuthSvc.currentUserValue; 24 | 25 | this.menuItems = [ 26 | { 27 | label: 'COVID-19', 28 | icon: 'pi pi-home', 29 | routerLink: '', 30 | visible: true 31 | }, 32 | 33 | { 34 | label: 'Hărți', 35 | icon: 'pi pi-compass', 36 | routerLink: '/harti', 37 | visible: true 38 | }, 39 | { 40 | label: 'Statistici', 41 | icon: 'pi pi-chart-line', 42 | routerLink: '/statistici', 43 | visible: true 44 | }, 45 | { 46 | label: 'Jurnal aplicație', 47 | icon: 'pi pi-file', 48 | routerLink: '/jurnal-aplicatie', 49 | visible: true 50 | }, 51 | { 52 | label: 'Manifest', 53 | icon: 'fa fa-fist-raised', 54 | routerLink: '/manifest', 55 | visible: true 56 | }, 57 | { 58 | label: 'Comunitate', 59 | icon: 'pi pi-users', 60 | routerLink: '/comunitate', 61 | visible: true 62 | }, 63 | { 64 | label: 'Despre', 65 | icon: 'pi pi-info-circle', 66 | routerLink: '/despre', 67 | visible: true 68 | }, 69 | { 70 | label: 'Login', 71 | icon: 'pi pi-sign-in', 72 | routerLink: '/login', 73 | // visible: !this.currentUser 74 | visible: false 75 | }, 76 | { 77 | label: 'Logout', 78 | icon: 'pi pi-sign-in', 79 | routerLink: '/logout', 80 | visible: this.currentUser 81 | } 82 | 83 | ]; 84 | } 85 | 86 | doLogout(ev) { 87 | ev.preventDefault(); 88 | this.AuthSvc.logout().subscribe(res => { 89 | this.AuthSvc.clearSession(); 90 | this.displaySidebar = false; 91 | }); 92 | } 93 | 94 | showSidebar(val) { 95 | this.displaySidebar = val; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /api/routes/_dashboard.v2.js: -------------------------------------------------------------------------------- 1 | const pool = require('../pgpool').getPool(); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | let router = require('express').Router(); 6 | 7 | router 8 | .route('/getDeadCasesByCounty') 9 | .get((req, res) => { 10 | 11 | let params_no = 0; 12 | let qp = Array.from(Array(params_no).keys(), x => `$${x+1}`).join(','); 13 | let query = `select COVID.GET_COUNTY_CASE_DEAD(${qp}) as data`; 14 | 15 | 16 | pool.query(query, 17 | [ ], 18 | function (err, result) { 19 | if (err) { 20 | res.status(400).send({ 21 | success: false 22 | }); 23 | 24 | return console.error('error running query', err); 25 | } 26 | 27 | res.status(200).send({ 28 | data: result.rows[0].data || null 29 | }); 30 | } 31 | ); 32 | 33 | }); 34 | 35 | 36 | router 37 | .route('/getHealthCasesByCounty') 38 | .get((req, res) => { 39 | 40 | let params_no = 0; 41 | let qp = Array.from(Array(params_no).keys(), x => `$${x+1}`).join(','); 42 | let query = `select COVID.GET_COUNTY_CASE_HEALED(${qp}) as data`; 43 | 44 | 45 | pool.query(query, 46 | [ ], 47 | function (err, result) { 48 | if (err) { 49 | res.status(400).send({ 50 | success: false 51 | }); 52 | 53 | return console.error('error running query', err); 54 | } 55 | 56 | res.status(200).send({ 57 | data: result.rows[0].data || null 58 | }); 59 | } 60 | ); 61 | 62 | }); 63 | 64 | router 65 | .route('/getCasesByCounty') 66 | .get((req, res) => { 67 | 68 | let params_no = 0; 69 | let qp = Array.from(Array(params_no).keys(), x => `$${x+1}`).join(','); 70 | let query = `select COVID.GET_COUNTY_CASE(${qp}) as data`; 71 | 72 | 73 | pool.query(query, 74 | [ ], 75 | function (err, result) { 76 | if (err) { 77 | res.status(400).send({ 78 | success: false 79 | }); 80 | 81 | return console.error('error running query', err); 82 | } 83 | 84 | res.status(200).send({ 85 | data: result.rows[0].data || null 86 | }); 87 | } 88 | ); 89 | }); 90 | 91 | module.exports = router; -------------------------------------------------------------------------------- /src/app/components/main/home/left-menu/left-menu.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | (după locul confirmării) 6 |

7 | {{totalCases}} 8 | 9 |

10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | Cazuri confirmate pe județ 22 | 23 | 24 | 25 | 26 | 27 | {{item.total_case + " "}} 28 | {{item.county}} 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 |
48 |

Începând cu data de 02.04.2020, autoritățile au reluat comunicarea cazurilor le nivel de județ. Cazurile raportate pe hartă și în tabel sunt extrase din comunicatul oficial.

49 |

Pentru mai multe informații vă rugăm să consultați secțiunea "Despre".

50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /api/routes/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../config'); 2 | const jwt = require('jsonwebtoken'); 3 | const cors = require('cors'); 4 | 5 | function verifyToken (req, res, next){ 6 | var token = req.headers.authorization.split(' ')[1] || null; 7 | 8 | if(token) { 9 | jwt.verify(token, config.auth.jwtTokenSecret, function(err, decoded){ 10 | if(err){ 11 | console.log(err); 12 | 13 | if(err.name === 'TokenExpiredError'){ 14 | return res.status(401).json({ 15 | success: false, 16 | message: 'TokenExpiredError' 17 | }); 18 | } 19 | 20 | return res.status(401).json({ 21 | success: false, 22 | message: 'Failed to authenticate token.' 23 | }); 24 | 25 | } else { 26 | req.user = decoded; 27 | 28 | var now = new Date(); 29 | var nowSeconds = parseInt(now.getTime()/1000); 30 | 31 | var newToken = null; 32 | var tokenTtl = decoded.exp - nowSeconds; 33 | 34 | if(tokenTtl < 600 && tokenTtl > 0) { 35 | let user = Object.assign({}, decoded); 36 | delete user.token; 37 | delete user.iat; 38 | delete user.exp; 39 | 40 | newToken = jwt.sign(user, config.auth.jwtTokenSecret, { 41 | expiresIn: config.auth.tokenExpireTime 42 | }); 43 | 44 | res.append('New-Access-Token', newToken); 45 | } 46 | 47 | next(); 48 | } 49 | }); 50 | } else { 51 | return res.status(403).json({ 52 | success: false, 53 | message: 'No token provided.' 54 | }); 55 | } 56 | } 57 | 58 | function setCacheControl(req, res, next){ 59 | res.set('Cache-Control', 'public, max-age=600, s-maxage=600'); 60 | next(); 61 | } 62 | 63 | module.exports = app => { 64 | app.use(`${config.app.apiPath}/`, cors()); 65 | app.use(`${config.app.apiPath}/`, setCacheControl); 66 | app.use(`${config.app.apiPath}/authentication`, require('./_authentication')); 67 | app.use(`${config.app.apiPath}/dashboard`, require('./_dashboard')); 68 | app.use(`${config.app.apiPath}/dashboard/v2`, require('./_dashboard.v2')); 69 | app.use(`${config.app.apiPath}/statistics`, cors(), require('./_statistics')); 70 | app.use(`${config.app.apiPath}/*`,verifyToken); 71 | app.use(`${config.app.apiPath}/administration`, require('./_administration')); 72 | } -------------------------------------------------------------------------------- /src/app/components/layout/header/header.component.scss: -------------------------------------------------------------------------------- 1 | .layout-topbar { 2 | padding: 15px; 3 | background-color: #1976d2; 4 | color: #ffffff; 5 | 6 | .logo { 7 | width: 30%; 8 | cursor: pointer; 9 | text-align: unset; 10 | } 11 | 12 | .topbar-menu { 13 | margin: 0; 14 | width: 70%; 15 | text-align: right; 16 | } 17 | 18 | .topbar-menu li, * { 19 | display: inline-block; 20 | } 21 | 22 | .topbar-menu li a { 23 | color: #fff; 24 | padding: 15px; 25 | display: block; 26 | } 27 | 28 | .topbar-menu li a:hover { 29 | background-color: rgba(255, 255, 255, 0.1); 30 | } 31 | 32 | .topbar-menu li a.active { 33 | background-color: rgba(255, 255, 255, 0.2); 34 | } 35 | 36 | .logo h2 { 37 | text-align: center; 38 | font-size: 18px; 39 | } 40 | } 41 | 42 | 43 | @media screen and (max-width: 600px) { 44 | .layout-topbar { 45 | .topbar-menu { 46 | display: none; 47 | } 48 | 49 | .logo { 50 | margin-top: 10px; 51 | text-align: center; 52 | width: 100%; 53 | } 54 | 55 | .logo h2 { 56 | font-size: 14px; 57 | width: calc(100% - 40px); 58 | text-align: center; 59 | } 60 | } 61 | } 62 | 63 | @media screen and (min-width: 601px) and (max-width: 1055px){ 64 | .layout-topbar { 65 | .logo { 66 | width: 100%; 67 | text-align: center; 68 | } 69 | 70 | .logo h2 { 71 | font-size: 18px; 72 | } 73 | 74 | .topbar-menu { 75 | padding-left: 0; 76 | width: 100%; 77 | display: flex; 78 | justify-content: space-around; 79 | } 80 | 81 | // .topbar-menu li { 82 | // min-width: 100px; 83 | // } 84 | 85 | .topbar-menu li a { 86 | text-align: center; 87 | font-size: 14px; 88 | padding: 5px; 89 | } 90 | 91 | .topbar-menu li a span.pi { 92 | display: block; 93 | } 94 | } 95 | } 96 | 97 | 98 | /**** SIDEBAR MENU ********/ 99 | 100 | .sidebar-title { 101 | font-size: 16px; 102 | padding: 10px; 103 | color: #007ad9; 104 | text-transform: uppercase; 105 | margin: 0; 106 | border-bottom: 1px solid #ccc; 107 | } 108 | 109 | #sidebar-menu { 110 | list-style-type: none; 111 | padding-left: 0; 112 | } 113 | 114 | #sidebar-menu li a { 115 | padding: 3px 0; 116 | color: unset; 117 | font-size: 16px; 118 | display: block; 119 | } 120 | 121 | #sidebar-menu li a i { 122 | margin-right: 10px; 123 | color: #007ad9; 124 | } 125 | 126 | .sidebar-footer { 127 | text-align: center; 128 | border-top: 1px solid #ccc; 129 | padding: 10px; 130 | } 131 | 132 | .sidebar-content { 133 | padding: 5px 15px; 134 | padding-top: 15px; 135 | height: calc(100% - 125px); 136 | } 137 | 138 | .sidebar-content h3 { 139 | font-size: 12px; 140 | } 141 | -------------------------------------------------------------------------------- /src/app/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {BehaviorSubject, Observable, Subject} from 'rxjs'; 4 | import {Router} from '@angular/router'; 5 | import {map} from 'rxjs/operators'; 6 | 7 | import {User} from '../models'; 8 | import {environment} from 'src/environments/environment'; 9 | 10 | @Injectable({providedIn: 'root'}) 11 | 12 | export class AuthenticationService { 13 | private currentUserSubject: BehaviorSubject; 14 | public currentUser: Observable; 15 | 16 | 17 | constructor(private http: HttpClient, public router: Router) { 18 | this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser'))); 19 | this.currentUser = this.currentUserSubject.asObservable(); 20 | } 21 | 22 | public get currentUserValue(): User { 23 | return this.currentUserSubject.value; 24 | // return JSON.parse(localStorage.getItem('currentUser')); 25 | } 26 | 27 | login(userId: string, password: string) { 28 | return this.http.post(`${environment.apiUrl}/authentication/login`, {user_id: userId, password}).pipe(map(data => { 29 | if (data.user && data.user.token) { 30 | localStorage.setItem('currentUser', JSON.stringify(data.user)); 31 | this.currentUserSubject.next(data.user); 32 | } 33 | 34 | return data; 35 | })); 36 | } 37 | 38 | updateToken(token: string) { 39 | const currentUser = JSON.parse(localStorage.getItem('currentUser')); 40 | currentUser.token = token; 41 | 42 | localStorage.setItem('currentUser', JSON.stringify(currentUser)); 43 | 44 | this.currentUserSubject.next(currentUser); 45 | } 46 | 47 | getToken() { 48 | return JSON.parse(localStorage.getItem('currentUser')).token; 49 | } 50 | 51 | setToken(user: any): Observable { 52 | localStorage.setItem('currentUser', JSON.stringify(user)); 53 | this.currentUserSubject.next(user); 54 | 55 | return this.currentUserSubject.asObservable(); 56 | } 57 | 58 | get token() { 59 | return JSON.parse(localStorage.getItem('currentUser')).token; 60 | } 61 | 62 | logout() { 63 | return this.http.get(`${environment.apiUrl}/administration/setLogout`, {}); 64 | } 65 | 66 | clearSession() { 67 | 68 | 69 | localStorage.removeItem('currentUser'); 70 | this.currentUserSubject.next(null); 71 | // return this.router.navigateByUrl('/'); 72 | this.router.routeReuseStrategy.shouldReuseRoute = () => false; 73 | return this.router.navigate([this.router.url]); 74 | } 75 | 76 | checkSession(params: any) { 77 | return this.http.post(`${environment.apiUrl}/authentication/checkSession`, params); 78 | } 79 | 80 | checkToken(params: any) { 81 | return this.http.post(`${environment.apiUrl}/authentication/checkToken`, params); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/europe-situation/europe-situation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-europe-situation', 7 | templateUrl: './europe-situation.component.html', 8 | styleUrls: ['./europe-situation.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class EuropeSituationComponent implements OnInit { 12 | list: any[]; 13 | activeChart: any; 14 | 15 | constructor( 16 | private sanitizer: DomSanitizer, 17 | private route: ActivatedRoute, 18 | private router: Router, 19 | ) { 20 | this.list = [ 21 | { 22 | id: 1, 23 | title: 'Evoluția cazurilor în Europa (heatmap)', 24 | url: this.getSafeUrl('https://covid19.geo-spatial.org/charts/heatmap/index.html'), 25 | path: 'evolutie-cazuri-confirmate-europa-heatmap' 26 | }, 27 | { 28 | id: 2, 29 | title: 'Evoluția cazurilor în Europa', 30 | url: this.getSafeUrl('https://covid19.geo-spatial.org/charts/heatmap/europe_ft_confirmed.html'), 31 | path: 'evolutie-cazuri-confirmate-europa' 32 | }, 33 | { 34 | id: 3, 35 | title: 'Evoluția cazurilor în Europa (raportate la 100k locuitori)', 36 | url: this.getSafeUrl('https://covid19.geo-spatial.org/charts/heatmap/europe_ft_confirmed_100k.html'), 37 | path: 'evolutie-cazuri-confirmate-europa-100k' 38 | }, 39 | { 40 | id: 4, 41 | title: 'Evoluția deceselor în Europa', 42 | url: this.getSafeUrl('https://covid19.geo-spatial.org/charts/heatmap/europe_ft_deaths.html'), 43 | path: 'evolutie-decese-europa' 44 | }, 45 | { 46 | id: 5, 47 | title: 'Evoluția deceselor în Europa (raportate la 100k locuitori)', 48 | url: this.getSafeUrl('https://covid19.geo-spatial.org/charts/heatmap/europe_ft_deaths_100k.html'), 49 | path: 'evolutie-decese-europa-100k' 50 | } 51 | ] 52 | }; 53 | 54 | ngOnInit(): void { 55 | this.route.queryParams.subscribe(params => { 56 | let entry = params.chart ? this.list.find(e => e.path === params.chart) : undefined; 57 | 58 | if (!entry) entry = this.list[0]; 59 | 60 | this.activeChart = entry; 61 | this.updateQueryParams(); 62 | }); 63 | } 64 | 65 | getSafeUrl(url) { 66 | return this.sanitizer.bypassSecurityTrustResourceUrl(url) 67 | } 68 | 69 | updateQueryParams(){ 70 | this.router.navigate([], { 71 | relativeTo: this.route, 72 | queryParams: { 73 | chart: this.activeChart.path 74 | }, 75 | queryParamsHandling: 'merge', 76 | skipLocationChange: true 77 | }); 78 | } 79 | 80 | onGraphChange(){ 81 | this.updateQueryParams(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/general-statistics/general-statistics.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "covid", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "app": "ng serve --host 0.0.0.0 --proxy-config proxy.conf.json --disable-host-check", 7 | "api": "nodemon api/server.js", 8 | "build": "ng build && npm run copy-404 && npm run copy-500", 9 | "build-prod": "ng build --prod --aot && npm run copy-404 && npm run copy-500", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e", 13 | "copy-404": "cpy ./dist/covid/index.html ./dist/covid/ --rename=404.html", 14 | "copy-500": "cpy ./dist/covid/index.html ./dist/covid/ --rename=500.html", 15 | "serve-https": "http-server ./dist/covid/ -c-1 --ssl --cert localhost.crt --key localhost.key -p 8080", 16 | "build-and-serve": "npm run build-prod && npm run serve-https" 17 | }, 18 | "private": true, 19 | "dependencies": { 20 | "@angular/animations": "~8.2.14", 21 | "@angular/cdk": "^8.2.3", 22 | "@angular/common": "~8.2.14", 23 | "@angular/compiler": "~8.2.14", 24 | "@angular/core": "~8.2.14", 25 | "@angular/forms": "~8.2.14", 26 | "@angular/platform-browser": "~8.2.14", 27 | "@angular/platform-browser-dynamic": "~8.2.14", 28 | "@angular/pwa": "^0.900.7", 29 | "@angular/router": "~8.2.14", 30 | "@angular/service-worker": "~8.2.14", 31 | "@fortawesome/fontawesome-free": "^5.12.1", 32 | "@schematics/angular": "^9.0.5", 33 | "chart.js": "^2.9.3", 34 | "chartjs-plugin-annotation": "^0.5.7", 35 | "chartjs-plugin-piechart-outlabels": "^0.1.4", 36 | "cors": "^2.8.5", 37 | "crypto-js": "^4.0.0", 38 | "d3": "^5.15.0", 39 | "express": "^4.17.1", 40 | "google-palette": "^1.1.0", 41 | "jquery": "^3.4.1", 42 | "jsonwebtoken": "^8.5.1", 43 | "morgan": "^1.9.1", 44 | "ol": "^6.2.1", 45 | "pg": "^7.18.2", 46 | "primeflex": "^1.0.0", 47 | "primeicons": "^2.0.0", 48 | "primeng": "^8.1.1", 49 | "regression": "^2.0.1", 50 | "rxjs": "~6.4.0", 51 | "tslib": "^1.10.0", 52 | "zone.js": "~0.9.1" 53 | }, 54 | "devDependencies": { 55 | "@angular-devkit/build-angular": "~0.803.25", 56 | "@angular/cli": "~8.3.25", 57 | "@angular/compiler-cli": "~8.2.14", 58 | "@angular/language-service": "~8.2.14", 59 | "@types/d3": "^5.7.2", 60 | "@types/jasmine": "~3.3.8", 61 | "@types/jasminewd2": "~2.0.3", 62 | "@types/node": "~8.9.4", 63 | "codelyzer": "^5.0.0", 64 | "cpy-cli": "^3.1.0", 65 | "jasmine-core": "~3.4.0", 66 | "jasmine-spec-reporter": "~4.2.1", 67 | "karma": "~4.1.0", 68 | "karma-chrome-launcher": "~2.2.0", 69 | "karma-coverage-istanbul-reporter": "~2.0.1", 70 | "karma-jasmine": "~2.0.1", 71 | "karma-jasmine-html-reporter": "^1.4.0", 72 | "nodemon": "^2.0.2", 73 | "protractor": "~5.4.0", 74 | "ts-node": "~7.0.0", 75 | "tslint": "~5.15.0", 76 | "typescript": "~3.5.3" 77 | }, 78 | "browser": { 79 | "crypto": false 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/assets/statistici_pe_zile.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [{ 3 | "day_case": "2020-02-26", 4 | "day_no": 1, 5 | "new_case_no": 1, 6 | "total_case": 1, 7 | "new_healed_no": 0, 8 | "total_healed": 0, 9 | "new_dead_no": 0, 10 | "total_dead": 0 11 | }, 12 | { 13 | "day_case": "2020-02-27", 14 | "day_no": 2, 15 | "new_case_no": 0, 16 | "total_case": 1, 17 | "new_healed_no": 0, 18 | "total_healed": 0, 19 | "new_dead_no": 0, 20 | "total_dead": 0 21 | }, 22 | { 23 | "day_case": "2020-02-28", 24 | "day_no": 3, 25 | "new_case_no": 2, 26 | "total_case": 3, 27 | "new_healed_no": 0, 28 | "total_healed": 0, 29 | "new_dead_no": 0, 30 | "total_dead": 0 31 | }, 32 | { 33 | "day_case": "2020-02-29", 34 | "day_no": 4, 35 | "new_case_no": 0, 36 | "total_case": 3, 37 | "new_healed_no": 0, 38 | "total_healed": 0, 39 | "new_dead_no": 0, 40 | "total_dead": 0 41 | }, 42 | { 43 | "day_case": "2020-03-01", 44 | "day_no": 5, 45 | "new_case_no": 0, 46 | "total_case": 3, 47 | "new_healed_no": 1, 48 | "total_healed": 1, 49 | "new_dead_no": 0, 50 | "total_dead": 0 51 | }, 52 | { 53 | "day_case": "2020-03-02", 54 | "day_no": 6, 55 | "new_case_no": 0, 56 | "total_case": 3, 57 | "new_healed_no": 0, 58 | "total_healed": 1, 59 | "new_dead_no": 0, 60 | "total_dead": 0 61 | }, 62 | { 63 | "day_case": "2020-03-03", 64 | "day_no": 7, 65 | "new_case_no": 1, 66 | "total_case": 4, 67 | "new_healed_no": 0, 68 | "total_healed": 1, 69 | "new_dead_no": 0, 70 | "total_dead": 0 71 | }, 72 | { 73 | "day_case": "2020-03-04", 74 | "day_no": 8, 75 | "new_case_no": 2, 76 | "total_case": 6, 77 | "new_healed_no": 0, 78 | "total_healed": 1, 79 | "new_dead_no": 0, 80 | "total_dead": 0 81 | }, 82 | { 83 | "day_case": "2020-03-05", 84 | "day_no": 9, 85 | "new_case_no": 1, 86 | "total_case": 7, 87 | "new_healed_no": 2, 88 | "total_healed": 3, 89 | "new_dead_no": 0, 90 | "total_dead": 0 91 | }, 92 | { 93 | "day_case": "2020-03-06", 94 | "day_no": 10, 95 | "new_case_no": 2, 96 | "total_case": 9, 97 | "new_healed_no": 0, 98 | "total_healed": 3, 99 | "new_dead_no": 0, 100 | "total_dead": 0 101 | }, 102 | { 103 | "day_case": "2020-03-07", 104 | "day_no": 11, 105 | "new_case_no": 4, 106 | "total_case": 13, 107 | "new_healed_no": 1, 108 | "total_healed": 4, 109 | "new_dead_no": 0, 110 | "total_dead": 0 111 | }, 112 | { 113 | "day_case": "2020-03-08", 114 | "day_no": 12, 115 | "new_case_no": 2, 116 | "total_case": 15, 117 | "new_healed_no": 0, 118 | "total_healed": 4, 119 | "new_dead_no": 0, 120 | "total_dead": 0 121 | }, 122 | { 123 | "day_case": "2020-03-09", 124 | "day_no": 13, 125 | "new_case_no": 2, 126 | "total_case": 17, 127 | "new_healed_no": 1, 128 | "total_healed": 5, 129 | "new_dead_no": 0, 130 | "total_dead": 0 131 | }, 132 | { 133 | "day_case": "2020-03-10", 134 | "day_no": 14, 135 | "new_case_no": 12, 136 | "total_case": 29, 137 | "new_healed_no": 0, 138 | "total_healed": 5, 139 | "new_dead_no": 0, 140 | "total_dead": 0 141 | } 142 | ] 143 | } -------------------------------------------------------------------------------- /src/app/services/notifications.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {environment as appConfig, environment} from '../../environments/environment'; 3 | import {timer} from 'rxjs'; 4 | import {take} from 'rxjs/operators'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class NotificationsService { 10 | 11 | private notificationTitle = 'COVID19 România'; 12 | private notificationIcon = '/favicon.ico'; 13 | 14 | // notification options 15 | private notificationDefaultOptions = { 16 | // "Visual Options", 17 | body: '', 18 | icon: this.notificationIcon, 19 | // image: '', 20 | // badge: '', 21 | // vibrate: '', 22 | // sound: '', 23 | // dir: '', 24 | // 25 | // // "Behavioral Options", 26 | // tag: '', 27 | // data: '', 28 | // requireInteraction: '', 29 | // renotify: '', 30 | // silent: '', 31 | // 32 | // // "Both visual & behavioral options", 33 | // actions: '', 34 | // 35 | // // "Information Option. No visual affect.", 36 | // timestamp: '' 37 | }; 38 | 39 | constructor() { 40 | } 41 | 42 | init() { 43 | if (this.enabled()) { 44 | // do magic 45 | this.promptPermissionGrant(); 46 | } 47 | } 48 | 49 | promptPermissionGrant() { 50 | if (this.canRequestPermission()) { 51 | timer(appConfig.notification_prompt_delay) 52 | .pipe(take(1)) 53 | .subscribe(() => { 54 | this.requestPermission(); 55 | }); 56 | } 57 | } 58 | 59 | isNotificationApiAvailable(): boolean { 60 | return 'Notification' in window; 61 | } 62 | 63 | isNotificationPermissionGranted(): boolean { 64 | if (this.isNotificationApiAvailable()) { 65 | return Notification.permission === 'granted'; 66 | } 67 | return false; 68 | } 69 | 70 | isNotificationPermissionDenied(): boolean { 71 | if (this.isNotificationApiAvailable()) { 72 | return Notification.permission === 'denied'; 73 | } 74 | return false; 75 | } 76 | 77 | /** 78 | * Prompt user for permission 79 | */ 80 | requestPermission(): void { 81 | if (this.canRequestPermission()) { 82 | Notification.requestPermission((status) => { 83 | console.log('Notification permission status:', status); 84 | }); 85 | } 86 | } 87 | 88 | canRequestPermission(): boolean { 89 | return ( 90 | this.enabled() 91 | && this.isNotificationApiAvailable() 92 | && !this.isNotificationPermissionDenied() 93 | ); 94 | } 95 | 96 | enabled() { 97 | return environment.enable_notifications; 98 | } 99 | 100 | showNotification(message) { 101 | if (!this.enabled() || !this.isNotificationPermissionGranted()) { 102 | return; 103 | } 104 | // delay the trigger a little bit to allow browser to change ui before the sending the notification 105 | setTimeout(() => { 106 | this.notificationDefaultOptions.body = message; 107 | return new Notification(this.notificationTitle, this.notificationDefaultOptions); 108 | }, 5000); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/mobility/mobility.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild, ElementRef, HostListener } from '@angular/core'; 2 | import * as $ from 'jquery'; 3 | import { Chart } from 'chart.js'; 4 | 5 | @Component({ 6 | selector: 'app-mobility', 7 | templateUrl: './mobility.component.html', 8 | styleUrls: ['./mobility.component.scss'] 9 | }) 10 | export class MobilityComponent implements OnInit { 11 | @ViewChild('canvasMobility', {static: true}) canvasMobility: ElementRef; 12 | @ViewChild('mainGrid', {static: true}) mainGrid: ElementRef; 13 | @HostListener('window:resize', ['$event']) 14 | onResize(event?) { 15 | this.isMobile = window.innerWidth < 450; 16 | 17 | this.screenHeight = window.innerHeight; 18 | this.screenWidth = window.innerWidth; 19 | } 20 | 21 | isMobile: boolean = window.innerWidth < 450; 22 | 23 | screenHeight: number; 24 | screenWidth: number; 25 | 26 | constructor() { } 27 | enableFeature : true = true; 28 | 29 | ngOnInit(): void { 30 | if (this.enableFeature) { 31 | // this.drawChart3(); 32 | } 33 | } 34 | 35 | drawChart3() { 36 | let self = this; 37 | let cfgWazers = { 38 | type: 'line', 39 | data: { 40 | datasets: [{ 41 | label: 'Medie NO2', 42 | data: [], 43 | fill: false 44 | } 45 | ] 46 | }, 47 | options: { 48 | responsive: true, 49 | title: { 50 | display: true, 51 | text: 'Utilizatori Waze București', 52 | fontSize: 18 53 | }, 54 | tooltips: { 55 | mode: 'index', 56 | intersect: false, 57 | }, 58 | hover: { 59 | mode: 'nearest', 60 | intersect: true 61 | }, 62 | scales: { 63 | xAxes: [{ 64 | type: 'time', 65 | time: { 66 | format: 'DD-MM-YYYY', 67 | tooltipFormat: 'll', 68 | unit: 'day', 69 | unitStepSize: 5 70 | }, 71 | display: true, 72 | scaleLabel: { 73 | display: true, 74 | labelString: 'Data' 75 | } 76 | }], 77 | yAxes: [{ 78 | // display: true, 79 | scaleLabel: { 80 | display: true, 81 | labelString: 'Valoare indice' 82 | } 83 | }] 84 | } 85 | } 86 | }; 87 | 88 | $.getJSON({ 89 | url: 'https://covid19.geo-spatial.org/external/wazero/bu.2.json', 90 | success(data) { 91 | const sensorDataServerY = []; 92 | const sensorDataServerX = []; 93 | for (var d in data) { 94 | sensorDataServerY.push(data[d].value); 95 | sensorDataServerX.push(data[d].time); 96 | }; 97 | 98 | const sensorData = { 99 | label: 'Numar wazers', 100 | data: sensorDataServerY, 101 | backgroundColor: '#9999ff0', 102 | borderColor: '#9999ff', 103 | fill: false 104 | }; 105 | 106 | cfgWazers['data']['labels'] = sensorDataServerX; 107 | cfgWazers.data.datasets = [sensorData, ]; 108 | const ctxMobility = self.canvasMobility.nativeElement.getContext('2d'); 109 | const myLine = new Chart(ctxMobility, cfgWazers); 110 | } 111 | }); 112 | 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/statistics.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, ViewEncapsulation, ViewChild, ElementRef} from '@angular/core'; 2 | import {SharedService} from '../../../services/shared.service'; 3 | 4 | @Component({ 5 | selector: 'app-statistics', 6 | templateUrl: './statistics.component.html', 7 | styleUrls: ['./statistics.component.scss'], 8 | encapsulation: ViewEncapsulation.None 9 | }) 10 | export class StatisticsComponent implements OnInit { 11 | @ViewChild('container', { static: true }) container: ElementRef 12 | @ViewChild('containerContent', { static: true }) containerContent: ElementRef 13 | links: any[]; 14 | 15 | showArrows: boolean = false 16 | 17 | constructor(private sharedService: SharedService) { 18 | this.sharedService.setMeta( 19 | 'Statistici', 20 | 'statistici, covid, românia', 21 | `Staistici informative despre diferite aspecte privind COVID` 22 | ); 23 | } 24 | 25 | ngOnInit(): void { 26 | // console.log(this.container.nativeElement.offsetWidth, this.containerContent.nativeElement.scrollWidth) 27 | // console.log(this.containerContent.nativeElement.scrollLeft) 28 | this.links = [ 29 | { 30 | routerLink: '/statistici/statistici-generale', 31 | label: 'Statistici generale', 32 | classes: 'ui-button-secondary' 33 | }, 34 | { 35 | routerLink: '/statistici/decese', 36 | label: 'Decese', 37 | classes: 'ui-button-secondary' 38 | }, 39 | { 40 | routerLink: '/statistici/relationare-cazuri', 41 | label: 'Relaționare cazuri', 42 | classes: 'ui-button-secondary' 43 | }, 44 | { 45 | routerLink: '/statistici/repartitie-cazuri-judete', 46 | label: 'Repartiția cazurilor pe județe', 47 | classes: 'ui-button-secondary' 48 | }, 49 | // { 50 | // routerLink: '/statistici/situatie-europa', 51 | // label: 'Cazuri în Europa (heatmap)', 52 | // classes: 'ui-button-secondary' 53 | // }, 54 | // { 55 | // routerLink: '/statistici/situatie-europa-grafic', 56 | // label: 'Cazuri în Europa (grafic)', 57 | // classes: 'ui-button-secondary' 58 | // }, 59 | 60 | { 61 | routerLink: '/statistici/teste-efectuate', 62 | label: 'Teste efectuate', 63 | classes: 'ui-button-secondary' 64 | }, 65 | { 66 | routerLink: '/statistici/mobilitate', 67 | label: 'Mobilitate', 68 | classes: 'ui-button-secondary' 69 | }, 70 | { 71 | routerLink: '/statistici/calitate-aer', 72 | label: 'Calitate aer', 73 | classes: 'ui-button-secondary' 74 | }, 75 | { 76 | routerLink: '/statistici/situatie-europa', 77 | label: 'Europa', 78 | classes: 'ui-button-secondary' 79 | } 80 | ]; 81 | } 82 | 83 | moveScroll(direction){ 84 | if(direction === 'r'){ 85 | this.containerContent.nativeElement.scrollLeft += 100; 86 | } else { 87 | this.containerContent.nativeElement.scrollLeft -= 100; 88 | } 89 | 90 | console.log(this.containerContent.nativeElement.scrollLeft) 91 | } 92 | 93 | // $('.scrollleft').click(function () { 94 | // console.log('ok') 95 | // $('#scrollbar').animate({ 96 | // scrollLeft: '-=153' 97 | // }, 1000, 'easeOutQuad'); 98 | // }); 99 | // $('.scrollright').click(function () { 100 | // console.log('ok') 101 | // $('#scrollbar').animate({ 102 | // scrollLeft: '+=153' 103 | // }, 1000, 'easeOutQuad'); 104 | // }); 105 | } 106 | -------------------------------------------------------------------------------- /src/app/components/main/home/map/map.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
    5 |
  • 6 | 11 |
  • 12 | 13 |
14 |
15 |
16 |

Hartă cazuri

17 | 18 | 22 | 23 |
24 |
25 |
26 |
27 |
28 |

{{selectedFeature.get('county')}}

29 |

30 | Cazuri confirmate: {{selectedFeature.get('total_case')}} 31 | (după locul confirmării) 32 |

33 |

34 | Total vindecari: {{selectedFeature.get('total_healed')}} 35 | (după locul externării) 36 |

37 |

Total decese: {{selectedFeature.get('total_dead')}}

38 | 39 |
40 | 41 |
42 |

Legenda

43 |
44 |
45 | 46 |
47 |

{{selectedQuarantineZone.get('uat')}}

48 |

Populatie: {{selectedQuarantineZone.get('population')}}

49 |
50 | 51 |
52 |

{{selectedRoad.get('ref')}}

53 |
54 | 55 |
56 |

Punct de control

57 |

{{selectedCheckpoint.get('Descriere')}}

58 |
59 | 60 |
61 |

{{selectedMetropolitan.get('uat')}}

62 |

Zona: {{selectedMetropolitan.get('metropolitan_zone')}}

63 | 64 |

Populatie: {{selectedMetropolitan.get('population')}}

65 |
66 |
67 | 68 | 69 |
70 |

71 | Începând cu data de 02.04.2020, autoritățile nu mai comunică 72 | numărul de vindecări la nivel de județ, prezentând doar numărul total 73 | al pacienților declarați vindecați. Din această cauză, etichetăm 74 | vindecările noi cu cu locația "JUDEȚ NECUNOSCUT". 75 |

76 | 77 |

78 | Echipa de voluntari 79 | verifică sistematic presa locală și anunțurile autorităților publice 80 | locale (DSP-uri, spitale, etc.) și, acolo unde găsește informații 81 | verificabile, mută cazurile din "JUDEȚ NECUNOSCUT" în județul 82 | corect de vindecare. 83 |

84 |
85 | 86 | 87 |
88 | 89 |
90 |
91 |
92 | -------------------------------------------------------------------------------- /src/assets/_puncte_verificare.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "name": "puncte_verificare", 4 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 5 | "features": [ 6 | { "type": "Feature", "properties": { "fid": 1, "Descriere": "DJ 178 Centrul localității Mihoveni" }, "geometry": { "type": "Point", "coordinates": [ 26.183583455379626, 47.678989841693763 ] } }, 7 | { "type": "Feature", "properties": { "fid": 2, "Descriere": "DN 17 Trei Movile" }, "geometry": { "type": "Point", "coordinates": [ 26.145372465201188, 47.635490190825898 ] } }, 8 | { "type": "Feature", "properties": { "fid": 3, "Descriere": "DJ 209 C Moara" }, "geometry": { "type": "Point", "coordinates": [ 26.208827128774889, 47.618649430826132 ] } }, 9 | { "type": "Feature", "properties": { "fid": 4, "Descriere": "DN 2 cu DJ 74 C Cumpărătura" }, "geometry": { "type": "Point", "coordinates": [ 26.268469923668739, 47.581237276746066 ] } }, 10 | { "type": "Feature", "properties": { "fid": 5, "Descriere": "DJ 208 A Bosanci spre Luncușoara" }, "geometry": { "type": "Point", "coordinates": [ 26.3395012580593, 47.594491917471061 ] } }, 11 | { "type": "Feature", "properties": { "fid": 6, "Descriere": "DJ 290 Văratec" }, "geometry": { "type": "Point", "coordinates": [ 26.38037681268127, 47.644594704731524 ] } }, 12 | { "type": "Feature", "properties": { "fid": 7, "Descriere": "DN 29 Salcea" }, "geometry": { "type": "Point", "coordinates": [ 26.382376503874124, 47.652635465118287 ] } }, 13 | { "type": "Feature", "properties": { "fid": 8, "Descriere": "DN 2 Pătrăuți" }, "geometry": { "type": "Point", "coordinates": [ 26.187103041918739, 47.702430561192294 ] } }, 14 | { "type": "Feature", "properties": { "fid": 9, "Descriere": "DC 59 Fetești" }, "geometry": { "type": "Point", "coordinates": [ 26.338824550224889, 47.706145015708678 ] } }, 15 | { "type": "Feature", "properties": { "fid": 10, "Descriere": "DN 29 A cu DJ 208D Adâncata" }, "geometry": { "type": "Point", "coordinates": [ 26.295512551971289, 47.740512366030593 ] } }, 16 | { "type": "Feature", "properties": { "fid": 11, "Descriere": "DN 2 Drăgușeni" }, "geometry": { "type": "Point", "coordinates": [ 26.489484121712582, 47.296072077071749 ] } }, 17 | { "type": "Feature", "properties": { "fid": 12, "Descriere": "DN 17 B Broșteni" }, "geometry": { "type": "Point", "coordinates": [ 25.692493487705587, 47.238454128884577 ] } }, 18 | { "type": "Feature", "properties": { "fid": 13, "Descriere": "DN 17 Poiana Stampei" }, "geometry": { "type": "Point", "coordinates": [ 25.139031097198888, 47.322932581151122 ] } }, 19 | { "type": "Feature", "properties": { "fid": 14, "Descriere": "DN 18 Cârlibaba" }, "geometry": { "type": "Point", "coordinates": [ 25.128231047064116, 47.571037399536216 ] } }, 20 | { "type": "Feature", "properties": { "fid": 15, "Descriere": "DJ 208 A Dolhasca" }, "geometry": { "type": "Point", "coordinates": [ 26.605993818764489, 47.429063025122701 ] } }, 21 | { "type": "Feature", "properties": { "fid": 16, "Descriere": "DN 2\/DN 29C Siret" }, "geometry": { "type": "Point", "coordinates": [ 26.070998956531746, 47.961637656179164 ] } }, 22 | { "type": "Feature", "properties": { "fid": 17, "Descriere": "DJ 208 C – Vorona (Botoșani)" }, "geometry": { "type": "Point", "coordinates": [ 26.643758941433177, 47.597184783030123 ] } }, 23 | { "type": "Feature", "properties": { "fid": 18, "Descriere": "DN 15 C – Oglinzi (Neamț)" }, "geometry": { "type": "Point", "coordinates": [ 26.373177764032054, 47.250860144215011 ] } }, 24 | { "type": "Feature", "properties": { "fid": 19, "Descriere": "DN 2A Est" }, "geometry": { "type": "Point", "coordinates": [ 27.680317193263345, 44.643457624125908 ] } }, 25 | { "type": "Feature", "properties": { "fid": 20, "Descriere": "DN 2A Vest" }, "geometry": { "type": "Point", "coordinates": [ 27.630684666440068, 44.640294302555994 ] } }, 26 | { "type": "Feature", "properties": { "fid": 21, "Descriere": "DN 21A" }, "geometry": { "type": "Point", "coordinates": [ 27.651540027873953, 44.656454189545471 ] } }, 27 | { "type": "Feature", "properties": { "fid": 22, "Descriere": "DJ 201\/2012" }, "geometry": { "type": "Point", "coordinates": [ 27.66027017917186, 44.63390961917537 ] } } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/puncte_verificare.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "name": "puncte_verificare", 4 | "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, 5 | "features": [ 6 | { "type": "Feature", "properties": { "fid": 1, "Descriere": "DJ 178 Centrul localității Mihoveni" }, "geometry": { "type": "Point", "coordinates": [ 26.183583455379626, 47.678989841693763 ] } }, 7 | { "type": "Feature", "properties": { "fid": 2, "Descriere": "DN 17 Trei Movile" }, "geometry": { "type": "Point", "coordinates": [ 26.145372465201188, 47.635490190825898 ] } }, 8 | { "type": "Feature", "properties": { "fid": 3, "Descriere": "DJ 209 C Moara" }, "geometry": { "type": "Point", "coordinates": [ 26.208827128774889, 47.618649430826132 ] } }, 9 | { "type": "Feature", "properties": { "fid": 4, "Descriere": "DN 2 cu DJ 74 C Cumpărătura" }, "geometry": { "type": "Point", "coordinates": [ 26.268469923668739, 47.581237276746066 ] } }, 10 | { "type": "Feature", "properties": { "fid": 5, "Descriere": "DJ 208 A Bosanci spre Luncușoara" }, "geometry": { "type": "Point", "coordinates": [ 26.3395012580593, 47.594491917471061 ] } }, 11 | { "type": "Feature", "properties": { "fid": 6, "Descriere": "DJ 290 Văratec" }, "geometry": { "type": "Point", "coordinates": [ 26.38037681268127, 47.644594704731524 ] } }, 12 | { "type": "Feature", "properties": { "fid": 7, "Descriere": "DN 29 Salcea" }, "geometry": { "type": "Point", "coordinates": [ 26.382376503874124, 47.652635465118287 ] } }, 13 | { "type": "Feature", "properties": { "fid": 8, "Descriere": "DN 2 Pătrăuți" }, "geometry": { "type": "Point", "coordinates": [ 26.187103041918739, 47.702430561192294 ] } }, 14 | { "type": "Feature", "properties": { "fid": 9, "Descriere": "DC 59 Fetești" }, "geometry": { "type": "Point", "coordinates": [ 26.338824550224889, 47.706145015708678 ] } }, 15 | { "type": "Feature", "properties": { "fid": 10, "Descriere": "DN 29 A cu DJ 208D Adâncata" }, "geometry": { "type": "Point", "coordinates": [ 26.295512551971289, 47.740512366030593 ] } }, 16 | { "type": "Feature", "properties": { "fid": 11, "Descriere": "DN 2 Drăgușeni" }, "geometry": { "type": "Point", "coordinates": [ 26.489484121712582, 47.296072077071749 ] } }, 17 | { "type": "Feature", "properties": { "fid": 12, "Descriere": "DN 17 B Broșteni" }, "geometry": { "type": "Point", "coordinates": [ 25.692493487705587, 47.238454128884577 ] } }, 18 | { "type": "Feature", "properties": { "fid": 13, "Descriere": "DN 17 Poiana Stampei" }, "geometry": { "type": "Point", "coordinates": [ 25.139031097198888, 47.322932581151122 ] } }, 19 | { "type": "Feature", "properties": { "fid": 14, "Descriere": "DN 18 Cârlibaba" }, "geometry": { "type": "Point", "coordinates": [ 25.128231047064116, 47.571037399536216 ] } }, 20 | { "type": "Feature", "properties": { "fid": 15, "Descriere": "DJ 208 A Dolhasca" }, "geometry": { "type": "Point", "coordinates": [ 26.605993818764489, 47.429063025122701 ] } }, 21 | { "type": "Feature", "properties": { "fid": 16, "Descriere": "DN 2\/DN 29C Siret" }, "geometry": { "type": "Point", "coordinates": [ 26.070998956531746, 47.961637656179164 ] } }, 22 | { "type": "Feature", "properties": { "fid": 17, "Descriere": "DJ 208 C – Vorona (Botoșani)" }, "geometry": { "type": "Point", "coordinates": [ 26.594532734745986, 47.542989516970742 ] } }, 23 | { "type": "Feature", "properties": { "fid": 18, "Descriere": "DN 15 C – Oglinzi (Neamț)" }, "geometry": { "type": "Point", "coordinates": [ 26.373177764032054, 47.250860144215011 ] } }, 24 | { "type": "Feature", "properties": { "fid": 19, "Descriere": "DN 2A Est" }, "geometry": { "type": "Point", "coordinates": [ 27.680317193263345, 44.643457624125908 ] } }, 25 | { "type": "Feature", "properties": { "fid": 20, "Descriere": "DN 2A Vest" }, "geometry": { "type": "Point", "coordinates": [ 27.630684666440068, 44.640294302555994 ] } }, 26 | { "type": "Feature", "properties": { "fid": 21, "Descriere": "DN 21A" }, "geometry": { "type": "Point", "coordinates": [ 27.651540027873953, 44.656454189545471 ] } }, 27 | { "type": "Feature", "properties": { "fid": 22, "Descriere": "DJ 201\/2012" }, "geometry": { "type": "Point", "coordinates": [ 27.66027017917186, 44.63390961917537 ] } } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/main/home/right-menu/right-menu.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | (după locul externării) 7 |

{{totalHealed}}

8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | Vindecări înregistrate pe județ 22 | 23 | 24 | 25 | 26 | 27 | {{item.total_healed + " "}} 28 | {{item.county}} 29 | 30 | 31 | 32 | 33 | 34 | Nu au fost înregistrate vindecări. 35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |     51 |

{{totalDeaths}}

52 |
53 |
54 |
55 | 56 | 57 |
58 |
59 |
60 |
61 |
62 | 63 | 64 | 65 | Decese înregistrate pe județ 66 | 67 | 68 | 69 | 70 | 71 | 72 | {{item.total_dead + " "}} 73 | {{item.county}} 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Nu au fost înregistrate decese. 87 | 88 | 89 | 90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 | 99 | 100 | 101 | 102 |
103 |
104 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/air-quality/air-quality.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild, ElementRef, HostListener } from '@angular/core'; 2 | import * as $ from 'jquery'; 3 | import { Chart } from 'chart.js'; 4 | 5 | @Component({ 6 | selector: 'app-air-quality', 7 | templateUrl: './air-quality.component.html', 8 | styleUrls: ['./air-quality.component.scss'] 9 | }) 10 | export class AirQualityComponent implements OnInit { 11 | @ViewChild('canvasAirQ', {static: true}) canvasAirQ: ElementRef; 12 | @ViewChild('canvasNO2', {static: true}) canvasNO2: ElementRef; 13 | @ViewChild('mainGrid', {static: true}) mainGrid: ElementRef; 14 | 15 | @HostListener('window:resize', ['$event']) 16 | onResize(event?) { 17 | this.isMobile = window.innerWidth < 450; 18 | 19 | this.screenHeight = window.innerHeight; 20 | this.screenWidth = window.innerWidth; 21 | } 22 | 23 | isMobile: boolean = window.innerWidth < 450; 24 | 25 | screenHeight: number; 26 | screenWidth: number; 27 | 28 | constructor() { } 29 | 30 | ngOnInit(): void { 31 | // this.drawChart4(); 32 | } 33 | 34 | drawChart4(){ 35 | let self = this; 36 | let diagrame = { 37 | 'ica_data': { 38 | titlu: 'Calitate Aer', 39 | label: 'Aerlive.ro Medie Indice Calitate Aer - Bucuresti', 40 | canvas: self.canvasAirQ.nativeElement.getContext('2d') 41 | }, 42 | 'no2_data': { 43 | titlu: 'Dioxid de azot', 44 | label: 'Aerlive.ro Medie NO2 - Bucuresti', 45 | canvas: self.canvasNO2.nativeElement.getContext('2d') 46 | } 47 | }; 48 | 49 | let myLine; 50 | 51 | $.getJSON({ 52 | url: 'https://covid19.apps.sage.ieat.ro/aerlive.avg.json', 53 | success: function(data) { 54 | for (var key in diagrame) { 55 | var cfg_aer_live = { 56 | type: 'line', 57 | data: { 58 | datasets: [{ 59 | label: 'Medie NO2', 60 | data: [], 61 | fill: false 62 | } 63 | ] 64 | }, 65 | options: { 66 | annotation: { 67 | drawTime: 'beforeDatasetsDraw', 68 | events: ['click', 'mouseover'], 69 | //annotations: this.chartAnnotations 70 | }, 71 | responsive: true, 72 | title: { 73 | display: true, 74 | text: "Informații Poluare", 75 | fontSize: 18 76 | }, 77 | tooltips: { 78 | mode: 'index', 79 | intersect: false, 80 | }, 81 | hover: { 82 | mode: 'nearest', 83 | intersect: true 84 | }, 85 | scales: { 86 | xAxes: [{ 87 | type: 'time', 88 | time: { 89 | format: 'DD-MM-YYYY', 90 | tooltipFormat: 'll', 91 | unit: 'day', 92 | unitStepSize: 5 93 | }, 94 | display: true, 95 | scaleLabel: { 96 | display: true, 97 | labelString: 'Data' 98 | } 99 | }], 100 | yAxes: [{ 101 | //display: true, 102 | scaleLabel: { 103 | display: true, 104 | labelString: 'Valoare indice' 105 | } 106 | }] 107 | } 108 | } 109 | }; 110 | cfg_aer_live.options.title.text = diagrame[key].titlu; 111 | var sensor_data_server = data[key]; 112 | var sensor_data = { 113 | label: diagrame[key].label, 114 | data: sensor_data_server, 115 | backgroundColor: '#9999ff', 116 | borderColor: '#9999ff', 117 | fill: false 118 | } 119 | cfg_aer_live.data.datasets = [sensor_data, ]; 120 | var ctxSensor = diagrame[key].canvas; 121 | myLine = new Chart(ctxSensor, cfg_aer_live); 122 | console.log(cfg_aer_live) 123 | } 124 | } 125 | }); 126 | }; 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/app/components/main/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import { DashboardService, SharedService } from 'src/app/services'; 3 | import {DomSanitizer} from '@angular/platform-browser'; 4 | 5 | @Component({ 6 | selector: 'app-about', 7 | templateUrl: './about.component.html', 8 | styleUrls: ['./about.component.scss'] 9 | }) 10 | export class AboutComponent implements OnInit { 11 | colaborators: any[] = []; 12 | projects: any[] = []; 13 | apiLinks: any[] = []; 14 | 15 | pageData: any; 16 | 17 | constructor(private sharedService: SharedService, private dashboardService: DashboardService, private domSanitizer: DomSanitizer) { 18 | this.sharedService.setMeta( 19 | 'Despre proiect', 20 | 'despre proiect, covid, românia', 21 | `Detalii despre proiectul COVID 19 România` 22 | ); 23 | } 24 | 25 | ngOnInit(): void { 26 | // this.dashboardService.getJsonData({ file: 'despre.json' }).toPromise().then(res => { 27 | // this.pageData = this.domSanitizer.bypassSecurityTrustHtml((res.data[0].data)); 28 | // }); 29 | 30 | fetch('assets/despre.json').then((response) => response.json()).then((json) => { 31 | this.pageData = this.domSanitizer.bypassSecurityTrustHtml(json[0].data); 32 | }); 33 | 34 | this.colaborators = [ 35 | 'Cristina Alexa', 36 | 'Bogdan Antonescu', 37 | 'Cristi Boboc', 38 | 'Octavian Borcan', 39 | 'Marius Budileanu', 40 | 'Andrei Cipu', 41 | 'Mihai Dumitru', 42 | 'Bogdan Grama', 43 | 'Claudia Ifrim', 44 | 'Codrina Ilie', 45 | 'Robert Ille', 46 | 'Iulian Iuga', 47 | 'Gabriel Iuhasz', 48 | 'Răzvan Moldovan', 49 | 'Marian Neagul', 50 | 'Ion Nedelcu', 51 | 'Silviu Panica', 52 | 'Gabriela Stancu', 53 | 'Daniel Urdă', 54 | 'Ruxandra Vâlcu', 55 | 'Cristina Vrînceanu', 56 | 'Ionelia Drăgoi', 57 | 'Vasile Crăciunescu' 58 | ].sort((a, b) => { 59 | let nameA: any = a.split(' '); 60 | nameA = nameA[nameA.length - 1].toLowerCase(); 61 | 62 | let nameB: any = b.split(' '); 63 | nameB = nameB[nameB.length - 1].toLowerCase(); 64 | 65 | return nameA.localeCompare(nameB); 66 | }); 67 | 68 | this.projects = [ 69 | { 70 | url: 'https://coronavirus.casajurnalistului.ro/', 71 | title: 'Monitorizare Coronavirus Casa Jurnalistului' 72 | }, 73 | { 74 | url: 'https://instnsp.maps.arcgis.com/apps/opsdashboard/index.html#/5eced796595b4ee585bcdba03e30c127', 75 | title: 'Harta INSP' 76 | }, 77 | { 78 | url: 'https://www.hartacoronavirus.ro', 79 | title: 'https://www.hartacoronavirus.ro' 80 | }, 81 | { 82 | url: 'https://covid19-romania.appspot.com', 83 | title: 'https://covid19-romania.appspot.com' 84 | }, 85 | { 86 | url: 'https://covid19ro.org', 87 | title: 'https://covid19ro.org' 88 | }, 89 | { 90 | url: 'https://coronavirus-esriro.hub.arcgis.com', 91 | title: 'Esri Romania COVID - 19 Hub' 92 | }, 93 | { 94 | url: 'https://datelazi.ro', 95 | title: 'COVID-19: Date la Zi' 96 | } 97 | ]; 98 | 99 | 100 | this.apiLinks = [ 101 | { 102 | url: 'https://covid19.geo-spatial.org/api/dashboard/getCasesByCounty', 103 | title: 'https://covid19.geo-spatial.org/api/dashboard/getCasesByCounty' 104 | }, 105 | { 106 | url: 'https://covid19.geo-spatial.org/api/dashboard/getDeadCasesByCounty', 107 | title: 'https://covid19.geo-spatial.org/api/dashboard/getDeadCasesByCounty' 108 | }, 109 | { 110 | url: 'https://covid19.geo-spatial.org/api/dashboard/getHealthCasesByCounty', 111 | title: 'https://covid19.geo-spatial.org/api/dashboard/getHealthCasesByCounty' 112 | }, 113 | { 114 | url: 'https://covid19.geo-spatial.org/api/dashboard/getDailyCaseReport', 115 | title: 'https://covid19.geo-spatial.org/api/dashboard/getDailyCaseReport' 116 | }, 117 | { 118 | url: 'https://covid19.geo-spatial.org/api/dashboard/getCaseRelations', 119 | title: 'https://covid19.geo-spatial.org/api/dashboard/getCaseRelations' 120 | }, 121 | { 122 | url: 'https://covid19.geo-spatial.org/api/dashboard/getPercentageByGender', 123 | title: 'https://covid19.geo-spatial.org/api/dashboard/getPercentageByGender' 124 | }, 125 | { 126 | url: 'https://covid19.geo-spatial.org/api/dashboard/getCasesByAgeGroup', 127 | title: 'https://covid19.geo-spatial.org/api/dashboard/getCasesByAgeGroup' 128 | }, 129 | { 130 | url: 'https://covid19.geo-spatial.org/api/dashboard/getDailyCases', 131 | title: 'https://covid19.geo-spatial.org/api/dashboard/getDailyCases' 132 | } 133 | ]; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "covid": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/covid", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": false, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets", 25 | "src/manifest.webmanifest" 26 | ], 27 | "styles": [ 28 | "node_modules/ol/ol.css", 29 | "node_modules/primeicons/primeicons.css", 30 | "node_modules/primeng/resources/themes/nova-light/theme.css", 31 | "node_modules/primeng/resources/primeng.min.css", 32 | "node_modules/primeflex/primeflex.css", 33 | "node_modules/@fortawesome/fontawesome-free/css/all.min.css", 34 | "node_modules/chart.js/dist/Chart.min.css", 35 | "src/styles.scss" 36 | ], 37 | "scripts": [ 38 | "node_modules/jquery/dist/jquery.min.js", 39 | "node_modules/chart.js/dist/Chart.js", 40 | "node_modules/regression/dist/regression.min.js", 41 | "node_modules/chartjs-plugin-piechart-outlabels/dist/chartjs-plugin-piechart-outlabels.min.js" 42 | ] 43 | }, 44 | "configurations": { 45 | "production": { 46 | "fileReplacements": [ 47 | { 48 | "replace": "src/environments/environment.ts", 49 | "with": "src/environments/environment.prod.ts" 50 | } 51 | ], 52 | "optimization": true, 53 | "outputHashing": "all", 54 | "sourceMap": false, 55 | "extractCss": true, 56 | "namedChunks": false, 57 | "aot": true, 58 | "extractLicenses": true, 59 | "vendorChunk": false, 60 | "buildOptimizer": true, 61 | "budgets": [ 62 | { 63 | "type": "initial", 64 | "maximumWarning": "2mb", 65 | "maximumError": "5mb" 66 | }, 67 | { 68 | "type": "anyComponentStyle", 69 | "maximumWarning": "6kb", 70 | "maximumError": "10kb" 71 | } 72 | ], 73 | "serviceWorker": true, 74 | "ngswConfigPath": "ngsw-config.json" 75 | } 76 | } 77 | }, 78 | "serve": { 79 | "builder": "@angular-devkit/build-angular:dev-server", 80 | "options": { 81 | "browserTarget": "covid:build" 82 | }, 83 | "configurations": { 84 | "production": { 85 | "browserTarget": "covid:build:production" 86 | } 87 | } 88 | }, 89 | "extract-i18n": { 90 | "builder": "@angular-devkit/build-angular:extract-i18n", 91 | "options": { 92 | "browserTarget": "covid:build" 93 | } 94 | }, 95 | "test": { 96 | "builder": "@angular-devkit/build-angular:karma", 97 | "options": { 98 | "main": "src/test.ts", 99 | "polyfills": "src/polyfills.ts", 100 | "tsConfig": "tsconfig.spec.json", 101 | "karmaConfig": "karma.conf.js", 102 | "assets": [ 103 | "src/favicon.ico", 104 | "src/assets", 105 | "src/manifest.webmanifest" 106 | ], 107 | "styles": [ 108 | "src/styles.scss" 109 | ], 110 | "scripts": [] 111 | } 112 | }, 113 | "lint": { 114 | "builder": "@angular-devkit/build-angular:tslint", 115 | "options": { 116 | "tsConfig": [ 117 | "tsconfig.app.json", 118 | "tsconfig.spec.json", 119 | "e2e/tsconfig.json" 120 | ], 121 | "exclude": [ 122 | "**/node_modules/**" 123 | ] 124 | } 125 | }, 126 | "e2e": { 127 | "builder": "@angular-devkit/build-angular:protractor", 128 | "options": { 129 | "protractorConfig": "e2e/protractor.conf.js", 130 | "devServerTarget": "covid:serve" 131 | }, 132 | "configurations": { 133 | "production": { 134 | "devServerTarget": "covid:serve:production" 135 | } 136 | } 137 | } 138 | } 139 | } 140 | }, 141 | "defaultProject": "covid", 142 | "schematics": { 143 | "@schematics/angular:component": { 144 | "styleext": "scss" 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/app/services/pwa.service.ts: -------------------------------------------------------------------------------- 1 | import {HostListener, Injectable} from '@angular/core'; 2 | 3 | import {Platform} from '@angular/cdk/platform'; 4 | import {environment, environment as appConfig} from '../../environments/environment'; 5 | import {SwUpdate} from '@angular/service-worker'; 6 | import {BehaviorSubject} from 'rxjs'; 7 | import {AllCasesByCountyResponse} from '../interfaces/all-cases-by-county-response'; 8 | 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class PwaService { 14 | private isInstalledSubject = new BehaviorSubject(false); 15 | isInstalled = this.isInstalledSubject.asObservable(); 16 | 17 | private showPromptSubject = new BehaviorSubject(false); 18 | showPrompt = this.showPromptSubject.asObservable(); 19 | 20 | private usedInSubject = new BehaviorSubject('tab'); 21 | usedIn = this.showPromptSubject.asObservable(); 22 | 23 | public deferredPrompt: any; 24 | 25 | constructor( 26 | private platform: Platform, 27 | private swUpdate: SwUpdate 28 | ) { 29 | } 30 | 31 | init() {} 32 | 33 | enabled() { 34 | return environment.enable_service_worker && environment.enable_pwa_mobile_install; 35 | } 36 | 37 | 38 | promptInstall(): void { 39 | if (this.enabled() && this.deferredPrompt) { 40 | this.deferredPrompt.prompt(); 41 | this.deferredPrompt.userChoice 42 | .then((choiceResult) => { 43 | if (choiceResult.outcome === 'accepted') { 44 | console.log('User accepted the A2HS prompt'); 45 | } else { 46 | console.log('User dismissed the A2HS prompt'); 47 | } 48 | this.deferredPrompt = null; 49 | }); 50 | } 51 | } 52 | 53 | promptInstallTest() { 54 | 55 | // disable install for the moment 56 | // if (!this.enabled()) { 57 | // window.addEventListener('beforeinstallprompt', (event: any) => { 58 | // event.preventDefault(); 59 | // return; 60 | // }); 61 | // } 62 | return; 63 | // window.addEventListener('appinstalled', (evt) => { 64 | // console.log('a2hs installed'); 65 | // }); 66 | // navigator.serviceWorker.addEventListener('controllerchange', () => { 67 | // // This fires when the service worker controlling this page 68 | // // changes, eg a new worker has skipped waiting and become 69 | // // the new active worker. 70 | // }); 71 | 72 | // console.log(this.platform); 73 | // if (this.platform.ANDROID) { 74 | // window.addEventListener('beforeinstallprompt', (event: any) => { 75 | // event.preventDefault(); 76 | // this.promptEvent = event; 77 | // this.openPromptComponent('android'); 78 | // }); 79 | // } 80 | // if (this.platform.IOS) { 81 | // const isInStandaloneMode = ('standalone' in window.navigator) && (window.navigator['standalone']); 82 | // if (!isInStandaloneMode) { 83 | // this.openPromptComponent('ios'); 84 | // } 85 | // } 86 | // 87 | // 88 | // this.swUpdate.available.subscribe(event => { 89 | // console.log('current version is', event.current); 90 | // console.log('available version is', event.available); 91 | // }); 92 | // this.swUpdate.activated.subscribe(event => { 93 | // console.log('old version was', event.previous); 94 | // console.log('new version is', event.current); 95 | // }); 96 | // 97 | // this.swUpdate.available.subscribe(event => { 98 | // this.swUpdate.activateUpdate().then(() => this.updateApp()); 99 | // }); 100 | 101 | 102 | // check if app is installed 103 | // window.addEventListener('appinstalled', (evt) => { 104 | // console.log('a2hs installed'); 105 | // }); 106 | 107 | // detect if started as app or tab 108 | // window.addEventListener('load', () => { 109 | // if (navigator.standalone) { 110 | // console.log('Launched: Installed (iOS)'); 111 | // } else if (matchMedia('(display-mode: standalone)').matches) { 112 | // console.log('Launched: Installed'); 113 | // } else { 114 | // console.log('Launched: Browser Tab'); 115 | // } 116 | // }); 117 | } 118 | 119 | private updateApp() { 120 | console.log('app update'); 121 | // found some cases when path is 500, not sure why, maybe because sw updates 122 | document.location.href = document.location.origin; 123 | console.log('The app is updating right now'); 124 | 125 | } 126 | 127 | onBeforeInstallPrompt(e) { 128 | // Prevent Chrome 67 and earlier from automatically showing the prompt 129 | e.preventDefault(); 130 | 131 | if (this.enabled()) { 132 | // Stash the event so it can be triggered later. 133 | this.deferredPrompt = e; 134 | this.changeShowPromptStatus(true); 135 | } 136 | } 137 | 138 | // triggered when app is installed 139 | onAppInstalled(e) { 140 | console.log('a2hs installed'); 141 | } 142 | 143 | public changeInstalledStatus(status: boolean) { 144 | this.isInstalledSubject.next(status); 145 | } 146 | 147 | public changeShowPromptStatus(status: boolean) { 148 | this.showPromptSubject.next(this.enabled() && status); 149 | } 150 | 151 | public changeUsedIn(status: string) { 152 | this.usedInSubject.next(status); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/app/services/dashboard.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HttpClient} from '@angular/common/http'; 3 | import {environment} from 'src/environments/environment'; 4 | import {BehaviorSubject, combineLatest, interval} from 'rxjs'; 5 | import {delay, map, startWith, switchMap, tap} from 'rxjs/operators'; 6 | import {AllCasesByCountyResponse} from '../interfaces/all-cases-by-county-response'; 7 | import {StorageService} from './storage.service'; 8 | import {NotificationsService} from './notifications.service'; 9 | 10 | const KEY_PREFIX = 'COVID_'; 11 | const TOTAL_CASES_KEY = KEY_PREFIX + 'TOTALS'; 12 | 13 | @Injectable({ 14 | providedIn: 'root' 15 | }) 16 | export class DashboardService { 17 | 18 | /* set to true to see random totals and notification */ 19 | private testNotificationEnableRandomTotals = false; 20 | 21 | public cases: AllCasesByCountyResponse = { 22 | confirmed: {total: 0, data: []}, 23 | deaths: {total: 0, data: []}, 24 | healed: {total: 0, data: []}, 25 | }; 26 | 27 | private casesSourceSubject = new BehaviorSubject({ 28 | confirmed: {total: 0, data: []}, 29 | deaths: {total: 0, data: []}, 30 | healed: {total: 0, data: []}, 31 | }); 32 | currentCases = this.casesSourceSubject.asObservable(); 33 | 34 | 35 | constructor( 36 | private http: HttpClient, 37 | private storageService: StorageService, 38 | private notificationsService: NotificationsService 39 | ) { 40 | this.getAllCases(); 41 | } 42 | 43 | getCasesByCounty(params?: any) { 44 | return this.http.get(`${environment.apiUrl}/dashboard/v2/getCasesByCounty`, {params}); 45 | } 46 | 47 | getHealthCasesByCounty(params?: any) { 48 | return this.http.get(`${environment.apiUrl}/dashboard/v2/getHealthCasesByCounty`, {params}); 49 | } 50 | 51 | getDeadCasesByCounty(params?: any) { 52 | return this.http.get(`${environment.apiUrl}/dashboard/v2/getDeadCasesByCounty`, {params}); 53 | } 54 | 55 | getDailyCaseReport(params?: any) { 56 | return this.http.get(`${environment.apiUrl}/dashboard/getDailyCaseReport`, {params}); 57 | } 58 | 59 | getGlobalStat(params?: any) { 60 | return this.http.get(`${environment.apiUrl}/dashboard/getGlobalStat`, {params}); 61 | } 62 | 63 | getPercentageByGender(params?: any) { 64 | return this.http.get(`${environment.apiUrl}/dashboard/getPercentageByGender`, {params}); 65 | } 66 | 67 | getCasesByAgeGroup(params?: any) { 68 | return this.http.get(`${environment.apiUrl}/dashboard/getCasesByAgeGroup`, {params}); 69 | } 70 | 71 | getGeojsonData(params?: any) { 72 | return this.http.get(`${environment.apiUrl}/dashboard/getGeojsonData`, {params}); 73 | } 74 | 75 | getAllCases() { 76 | return interval(environment.data_refresh).pipe( 77 | startWith(0), 78 | switchMap(() => combineLatest([ 79 | this.getCasesByCounty(), 80 | this.getDeadCasesByCounty(), 81 | this.getHealthCasesByCounty() 82 | ]).pipe( 83 | map((response): AllCasesByCountyResponse => this.mapResponse(response)) 84 | ) 85 | ) 86 | ).subscribe((response: AllCasesByCountyResponse) => { 87 | this.changeCases(response); 88 | this.computeTotals(response); 89 | }); 90 | } 91 | 92 | private computeTotals(response: AllCasesByCountyResponse) { 93 | const newCases = { 94 | confirmed: response.confirmed.total, 95 | deaths: response.deaths.total, 96 | healed: response.healed.total, 97 | }; 98 | 99 | 100 | const storedCases = this.storageService.get(TOTAL_CASES_KEY); 101 | if (storedCases) { 102 | const newTotals = newCases.confirmed + newCases.deaths + newCases.healed; 103 | const oldTotals = storedCases.confirmed + storedCases.deaths + storedCases.healed; 104 | if (newTotals !== oldTotals) { 105 | let message = ''; 106 | message += 'Cazuri confirmate: ' + newCases.confirmed + '. '; 107 | message += 'Cazuri vindecate: ' + newCases.healed + '. '; 108 | message += 'Decese: ' + newCases.deaths + '. '; 109 | 110 | this.notificationsService.showNotification(message); 111 | } 112 | } 113 | this.storageService.set(TOTAL_CASES_KEY, newCases); 114 | } 115 | 116 | private mapResponse([casesByCounty, deadCasesByCounty, healthCasesByCounty]): AllCasesByCountyResponse { 117 | // force data changes to test notifications 118 | if (this.testNotificationEnableRandomTotals) { 119 | const min = 10; 120 | const max = 10000; 121 | 122 | casesByCounty.data.total = casesByCounty.data.total + Math.floor(Math.random() * (max - min + 1)) + min; 123 | deadCasesByCounty.data.total = deadCasesByCounty.data.total + Math.floor(Math.random() * (max - min + 1)) + min; 124 | healthCasesByCounty.data.total = healthCasesByCounty.data.total + Math.floor(Math.random() * (max - min + 1)) + min; 125 | } 126 | 127 | return { 128 | confirmed: casesByCounty.data ? casesByCounty.data : null, 129 | deaths: deadCasesByCounty.data ? deadCasesByCounty.data : null, 130 | healed: healthCasesByCounty.data ? healthCasesByCounty.data : null 131 | }; 132 | } 133 | 134 | private changeCases(currentCases: AllCasesByCountyResponse) { 135 | this.casesSourceSubject.next(currentCases); 136 | } 137 | 138 | getJsonData(params: any) { 139 | return this.http.get(`${environment.apiUrl}/dashboard/getJsonData`, {params}); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/app/components/main/administration/patients-list/patients-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AdministrationService } from 'src/app/services'; 3 | import { FormGroup, FormBuilder } from '@angular/forms'; 4 | import { ConfirmationService } from 'primeng/api'; 5 | 6 | @Component({ 7 | selector: 'app-patients-list', 8 | templateUrl: './patients-list.component.html', 9 | styleUrls: ['./patients-list.component.scss'], 10 | providers: [ConfirmationService] 11 | }) 12 | export class PatientsListComponent implements OnInit { 13 | tableData: any[] = []; 14 | tableParams: any; 15 | display: boolean = false; 16 | idPatient: number; 17 | modalTitle: string; 18 | countyList: any[] = []; 19 | statusList: any[] = []; 20 | myForm: FormGroup; 21 | 22 | constructor( 23 | private AdministrationSvc: AdministrationService, 24 | private fb: FormBuilder, 25 | private ConfirmationSvc: ConfirmationService 26 | ) { } 27 | 28 | ngOnInit(): void { 29 | this.tableParams = { 30 | limit: 10, 31 | offset: 0, 32 | page: 1, 33 | lastPage: 1, 34 | total: 0, 35 | sort_column: '', 36 | order_type: '', 37 | filter: '' 38 | } 39 | 40 | this.myForm = this.fb.group({ 41 | 'county_code': [null], 42 | 'case_no': [''], 43 | 'status': [null], 44 | 'source_no': [''] 45 | }); 46 | 47 | this.statusList = [ 48 | { 49 | status: 1, 50 | status_name: 'Confirmat' 51 | }, 52 | { 53 | status: 2, 54 | status_name: 'Vindecat' 55 | }, 56 | { 57 | status: 3, 58 | status_name: 'Decedat' 59 | } 60 | ] 61 | 62 | this.AdministrationSvc.getCountyCombo().subscribe(res => { 63 | if(res && res.data && res.data.data) { 64 | this.countyList = res.data.data; 65 | } 66 | }) 67 | 68 | this.loadData(); 69 | } 70 | 71 | filterTable() { 72 | let appliedFilters = {}; 73 | 74 | if(this.myForm.value['county_code']) { 75 | appliedFilters['county_code'] = this.myForm.value['county_code'].county_code; 76 | } 77 | if(this.myForm.value['status']) { 78 | appliedFilters['status'] = this.myForm.value['status'].status; 79 | } 80 | if(this.myForm.value['case_no']) { 81 | appliedFilters['case_no'] = this.myForm.value['case_no']; 82 | } 83 | if(this.myForm.value['source_no']) { 84 | appliedFilters['source_no'] = this.myForm.value['source_no']; 85 | } 86 | 87 | this.tableParams.filter = JSON.stringify(appliedFilters); 88 | this.tableParams.page = 1; 89 | this.loadData(); 90 | } 91 | 92 | clearfilterTable(): void{ 93 | this.tableParams.filter = ''; 94 | this.myForm.reset(); 95 | this.tableParams.page = 1; 96 | 97 | this.loadData(); 98 | } 99 | 100 | loadData() { 101 | this.tableParams.offset = (this.tableParams.page - 1) * this.tableParams.limit; 102 | this.AdministrationSvc.getCaseList(this.tableParams).subscribe(res => { 103 | if (res && res.data && res.data.success) { 104 | this.tableData = res.data.data; 105 | } 106 | 107 | if (res.data.total >= 0) { 108 | this.tableParams.total = res.data.total; 109 | if (res.data.total == 0) this.tableParams.lastPage = 1; 110 | } 111 | 112 | if (res.data.total > 0) { 113 | this.tableParams.lastPage = Math.ceil(res.data.total / this.tableParams.limit); 114 | } 115 | }); 116 | } 117 | 118 | changePage(val): void { 119 | switch (val) { 120 | case 'first': 121 | this.tableParams.page = 1; 122 | break; 123 | case 'prev': 124 | if (this.tableParams.page > 1) this.tableParams.page--; 125 | break; 126 | 127 | case 'next': 128 | if (this.tableParams.page < this.tableParams.lastPage) this.tableParams.page++; 129 | break; 130 | 131 | case 'last': 132 | this.tableParams.page = this.tableParams.lastPage; 133 | break; 134 | } 135 | 136 | this.loadData(); 137 | } 138 | 139 | openAddModal(item, isEdit) { 140 | if(isEdit) { 141 | this.modalTitle = 'Editare pacient'; 142 | this.idPatient = item.case_id; 143 | } else { 144 | this.modalTitle = 'Adăugare pacient'; 145 | } 146 | 147 | this.display = true; 148 | } 149 | 150 | onDialogClose(event) { 151 | this.display = event; 152 | this.idPatient = null; 153 | this.loadData(); 154 | } 155 | 156 | deleteItem(caseNo) { 157 | this.ConfirmationSvc.confirm({ 158 | key: 'deleteConfirm', 159 | message: 'Sunteți sigur(ă) că doriți să ștergeți definitiv înregistrarea?', 160 | accept: () => { 161 | this.AdministrationSvc.deleteCase({ case_id: caseNo }).subscribe(res => { 162 | if (res && res.data.success) { 163 | this.loadData(); 164 | } else if (!res.data.success) { 165 | this.showMessage(res.data.message) 166 | } 167 | }); 168 | } 169 | }); 170 | } 171 | 172 | showMessage(msg) { 173 | this.ConfirmationSvc.confirm({ 174 | key: 'warningConfirm', 175 | message: msg 176 | }); 177 | } 178 | 179 | copyStringToClipboard(textToCppy) { 180 | if(!textToCppy) return; 181 | var el = document.createElement('textarea'); 182 | el.value = textToCppy; 183 | el.setAttribute('readonly', ''); 184 | el.setAttribute('style', 'position: absolute, left: -9999px') 185 | document.body.appendChild(el); 186 | el.select(); 187 | document.execCommand('copy'); 188 | document.body.removeChild(el); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {Routes, RouterModule} from '@angular/router'; 3 | import {MainComponent} from './components/main/main.component'; 4 | import {LoginComponent} from './components/login/login.component'; 5 | import {AuthGuard} from './guards/auth.guard.service'; 6 | import {SkipIfLoggedIn} from './guards/skipIfLoggedIn.guard'; 7 | import {AdministrationComponent} from './components/main/administration/administration.component'; 8 | import {UserListComponent} from './components/main/administration/user-list/user-list.component'; 9 | import {PatientsListComponent} from './components/main/administration/patients-list/patients-list.component'; 10 | import {HomeComponent} from './components/main/home/home.component'; 11 | import {AddNewPatientComponent} from './components/main/administration/patients-list/add-new-patient/add-new-patient.component'; 12 | import {StatisticsComponent} from './components/main/statistics/statistics.component'; 13 | import {AboutComponent} from './components/main/about/about.component'; 14 | import {RelationCasesComponent} from './components/main/statistics/relation-cases/relation-cases.component'; 15 | import {GeneralStatisticsComponent} from './components/main/statistics/general-statistics/general-statistics.component'; 16 | import {CoronavirusEuropeComponent} from './components/main/statistics/coronavirus-europe/coronavirus-europe.component'; 17 | import {ManifestComponent} from './components/main/manifest/manifest.component'; 18 | import {MapsComponent} from './components/main/maps/maps.component'; 19 | import {No2EmissionComponent} from './components/main/maps/no2-emission/no2-emission.component'; 20 | import {EuropeanContextComponent} from './components/main/maps/european-context/european-context.component'; 21 | import {SocialInterestPointsComponent} from './components/main/maps/social-interest-points/social-interest-points.component'; 22 | import {FrontierSituationComponent} from './components/main/maps/frontier-situation/frontier-situation.component'; 23 | import {HospitalInfrastructureComponent} from './components/main/maps/hospital-infrastructure/hospital-infrastructure.component'; 24 | import {CountiesCasesComponent} from './components/main/statistics/counties-cases/counties-cases.component'; 25 | import {LogsComponent} from './components/main/logs/logs.component'; 26 | import { MobilityComponent } from './components/main/statistics/mobility/mobility.component'; 27 | import { AirQualityComponent } from './components/main/statistics/air-quality/air-quality.component'; 28 | import { EuropaCasesGraphComponent } from './components/main/statistics/europa-cases-graph/europa-cases-graph.component'; 29 | import { DailyTestsComponent } from './components/main/statistics/daily-tests/daily-tests.component'; 30 | import { EuropeSituationComponent } from './components/main/statistics/europe-situation/europe-situation.component'; 31 | import { CommunityComponent } from './components/main/community/community.component'; 32 | import { DeathsComponent } from './components/main/statistics/deaths/deaths.component'; 33 | import { CommunitiesComponent } from './components/main/maps/communities/communities.component'; 34 | 35 | 36 | const routes: Routes = [ 37 | { 38 | path: '', 39 | component: MainComponent, 40 | children: [ 41 | { 42 | path: '', 43 | pathMatch: 'full', 44 | component: HomeComponent 45 | }, 46 | {path: 'despre', component: AboutComponent}, 47 | {path: 'manifest', component: ManifestComponent}, 48 | { 49 | path: 'harti', 50 | component: MapsComponent, 51 | children: [ 52 | {path: '', redirectTo: 'hospital-infrastructure', pathMatch: 'full'}, 53 | {path: 'no2-emission', component: No2EmissionComponent}, 54 | {path: 'europe', component: EuropeanContextComponent}, 55 | {path: 'social-interest-points', component: SocialInterestPointsComponent}, 56 | {path: 'frontier-situation', component: FrontierSituationComponent}, 57 | {path: 'hospital-infrastructure', component: HospitalInfrastructureComponent}, 58 | {path: 'comunitati-marginalizate', component: CommunitiesComponent} 59 | ] 60 | }, 61 | { 62 | path: 'statistici', 63 | component: StatisticsComponent, 64 | children: [ 65 | {path: '', redirectTo: 'statistici-generale', pathMatch: 'full'}, 66 | {path: 'statistici-generale', component: GeneralStatisticsComponent}, 67 | {path: 'relationare-cazuri', component: RelationCasesComponent}, 68 | {path: 'repartitie-cazuri-judete', component: CountiesCasesComponent}, 69 | // {path: 'situatie-europa', component: CoronavirusEuropeComponent}, 70 | // {path: 'situatie-europa-grafic', component: EuropaCasesGraphComponent}, 71 | {path: 'teste-efectuate', component: DailyTestsComponent}, 72 | {path: 'mobilitate', component: MobilityComponent}, 73 | {path: 'calitate-aer', component: AirQualityComponent}, 74 | {path: 'situatie-europa', component: EuropeSituationComponent}, 75 | {path: 'decese', component: DeathsComponent} 76 | ] 77 | }, 78 | { 79 | path: 'jurnal-aplicatie', 80 | component: LogsComponent 81 | }, 82 | { 83 | path: 'comunitate', 84 | component: CommunityComponent 85 | }, 86 | { 87 | path: 'administration', 88 | component: AdministrationComponent, 89 | canActivate: [AuthGuard], 90 | children: [ 91 | {path: '', redirectTo: 'patients-list', pathMatch: 'full'}, 92 | { 93 | path: 'patients-list', 94 | component: PatientsListComponent, 95 | children: [ 96 | {path: 'add-new-patient', component: AddNewPatientComponent} 97 | ] 98 | }, 99 | {path: 'user-list', component: UserListComponent} 100 | ] 101 | } 102 | ] 103 | }, 104 | {path: 'login', component: LoginComponent, resolve: {skipLoggedIn: SkipIfLoggedIn}}, 105 | {path: '**', redirectTo: ''} 106 | ]; 107 | 108 | 109 | @NgModule({ 110 | imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})], 111 | exports: [RouterModule] 112 | }) 113 | export class AppRoutingModule { 114 | } 115 | -------------------------------------------------------------------------------- /src/app/components/main/administration/patients-list/add-new-patient/add-new-patient.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | {{modalTitle}} 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 | 26 |
27 | 28 |
29 | 30 | 34 |
35 | 36 |
37 | 38 | 42 |
43 | 44 |
45 | 46 | 48 |
49 | 50 |
51 | 52 | 54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 | 63 | 64 |
65 | 66 |
67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 |
75 | 76 |
77 | 78 | 79 |
80 | 81 |
82 | 83 | 84 |
85 | 86 |
87 | 88 | 89 |
90 | 91 |
92 | 93 | 94 |
95 | 96 |
97 |
98 |

Nu ați completat câmpul "Număr caz".

99 |

Nu ați completat câmpul "Dată diagnostic".

100 |
101 |
102 |
103 |
104 | 105 | 106 | 108 | 110 | 111 | 112 |
113 | 114 | 115 | 116 | 117 | 119 | 120 | -------------------------------------------------------------------------------- /src/app/components/main/statistics/deaths/deaths.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-deaths', 7 | templateUrl: './deaths.component.html', 8 | styleUrls: ['./deaths.component.css'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class DeathsComponent implements OnInit { 12 | list: any[]; 13 | activeChart: any; 14 | 15 | constructor( 16 | private sanitizer: DomSanitizer, 17 | private route: ActivatedRoute, 18 | private router: Router, 19 | ) { 20 | this.list = [ 21 | { 22 | id: 1, 23 | title: 'Evoluție decese noi pe zile', 24 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1553327102&format=image'), 25 | path: 'evolutie-decese-noi-pe-zile', 26 | width: 1290, 27 | height: 534 28 | }, 29 | { 30 | id: 2, 31 | title: 'Evoluție decese pe zile', 32 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1513704494&format=image'), 33 | path: 'evolutie-decese-pe-zile', 34 | width: 1302, 35 | height: 525 36 | }, 37 | { 38 | id: 3, 39 | title: 'Decese înregistrate pe județ', 40 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1177448773&format=image'), 41 | path: 'decese-inregistrate-judet', 42 | width: 862, 43 | height: 587 44 | }, 45 | { 46 | id: 4, 47 | title: 'Decese înregistrate pe intervale de vârstă', 48 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=643243211&format=image'), 49 | path: 'evolutie-decese-intervale-varsta', 50 | width: 746, 51 | height: 583 52 | }, 53 | { 54 | id: 5, 55 | title: 'Interval timp (zile) de la deces la comunicatul Ministerului Sănătății', 56 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=533453048&format=image'), 57 | path: 'interval-timp-comunicat-ministerul-sanatatii', 58 | width: 574, 59 | height: 436 60 | }, 61 | { 62 | id: 6, 63 | title: 'Decese înregistrate pe gen', 64 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1864416366&format=image'), 65 | path: 'decese-inregistrate-pe-gen', 66 | width: 524, 67 | height: 436 68 | }, 69 | { 70 | id: 7, 71 | title: 'Decese confirmate post-mortem', 72 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1233344860&format=image'), 73 | path: 'decese-confirmate-post-mortem', 74 | width: 479, 75 | height: 436 76 | }, 77 | { 78 | id: 8, 79 | title: 'Frecvență decese după generație', 80 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1032636154&format=image'), 81 | path: 'frecventa-decese-generatie', 82 | width: 600, 83 | height: 436 84 | }, 85 | { 86 | id: 9, 87 | title: 'Cazuri și decese dupa vârstă', 88 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1563374408&format=image'), 89 | path: 'cazuri-decese-varsta', 90 | width: 524, 91 | height: 1624 92 | }, 93 | { 94 | id: 10, 95 | title: 'Decese/Vârstă și gen', 96 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1122563301&format=image'), 97 | path: 'decese-varsta-gen', 98 | width: 410, 99 | height: 1180 100 | }, 101 | { 102 | id: 11, 103 | title: 'Decese înregistrate la persoane internate în spital', 104 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1241073497&format=image'), 105 | path: 'decese-persoane-internate', 106 | width: 524, 107 | height: 436 108 | }, 109 | { 110 | id: 12, 111 | title: 'Perioada Test - Confirmare Covid 19 (zile)', 112 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=238369558&format=image'), 113 | path: 'decese-test-confirmare-covid', 114 | width: 574, 115 | height: 486 116 | }, 117 | { 118 | id: 13, 119 | title: 'Perioada Confirmare Covid 19 - Deces (zile)', 120 | url: this.getSafeUrl('https://docs.google.com/spreadsheets/d/e/2PACX-1vTtAvfu-VWAOiT7CS_Ryrg9DI-Fx1G1eohA9TBCm54h0go06yi5uniY558t8vVFfWMaq7cA3_UkftC8/pubchart?oid=1775422462&format=image'), 121 | path: 'decese-perioada-confirmare', 122 | width: 659, 123 | height: 589 124 | } 125 | ] 126 | } 127 | 128 | ngOnInit(): void { 129 | this.route.queryParams.subscribe(params => { 130 | let entry = params.chart ? this.list.find(e => e.path === params.chart) : undefined; 131 | 132 | if (!entry) entry = this.list[0]; 133 | 134 | this.activeChart = entry; 135 | this.updateQueryParams(); 136 | }); 137 | } 138 | 139 | getSafeUrl(url) { 140 | return this.sanitizer.bypassSecurityTrustResourceUrl(url) 141 | } 142 | 143 | updateQueryParams(){ 144 | this.router.navigate([], { 145 | relativeTo: this.route, 146 | queryParams: { 147 | chart: this.activeChart.path 148 | }, 149 | queryParamsHandling: 'merge', 150 | skipLocationChange: true 151 | }); 152 | } 153 | 154 | onGraphChange(){ 155 | this.updateQueryParams(); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/app/components/main/manifest/manifest.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Update 02.04.2020: Autoritățile au reluat raportarea cazurilor la nivel de județ.
3 |
4 | 5 |
BUCUREȘTI, 20 MARTIE 2020
6 |
7 |
8 |

9 | Către: Președinția României
10 |  Guvernul României
11 |  Ministerul Afacerilor Interne
12 |  Departamentului pentru Situații de Urgență
13 |  Ministerul Sănătății
14 |  Institutul Național de Sănătate Publică 15 |

16 |
17 | 18 |

Manifest pentru publicarea completă a datelor privind evoluția pandemiei Covid-19 pe teritoriul României

19 |
20 |

21 | 22 | Asociația geo-spatial.org solicită autorităților să ofere public o prezentare completă, structurată a tuturor cazurilor oficiale (confirmate, vindecări, decese) actualizată cât mai des posibil, pentru a disemina aceste informații vitale și a evita apariția informațiilor false, potențial alarmiste. 23 | 24 |

25 |

26 | Pentru a sprijini accesul cetățenilor la informațiile de interes public, am realizat (prin utilizarea tehnologiilor open source) o hartă a distribuției spațiale a tuturor cazurilor COVID-19 confirmate oficial - Coronavirus COVID-19 România -, o statistică ilustrând evoluția pe zile, precum și un grafic ce permite vizualizarea legăturilor de contaminare. 27 |

28 | 29 |

30 | Pentru a menține Coronavirus COVID-19 România actualizat și, astfel, pentru a susține demersul autorităților și al organizațiilor media de a informa corect și prompt populația, solicităm deschiderea datelor publice statistice privind evoluția pandemiei Covid-19 pe teritoriul României, prin publicarea: 31 |

32 |
    33 |
  1. Numărului de cazuri noi confirmate pe județ incluzând: sexul, vârsta, precum și sursa contaminării (numărul pacientului de la care fiecare nou pacient a contractat virusul), acolo unde se poate identifica.
  2. 34 |
  3. Numărului de persoane vindecate pe județ, incluzând sexul și vârsta (pentru a putea elimina din numărătoarea cazurilor pe fiecare județ).
  4. 35 |
  5. Numărului deceselor pe județ, incluzând sexul și vârsta (pentru a putea elimina din numărătoarea cazurilor pe fiecare județ).
  6. 36 |
  7. Numărului persoanelor izolate pe județe, incluzând: sexul și vârsta.
  8. 37 |
  9. Numărului persoanelor aflate în carantină pe județe, incluzând: sexul și vârsta.
  10. 38 |
  11. Numărului testelor efectuate zilnic, indiferent de rezultat, pe județ (incluzând acolo unde este disponibilă categoria de caz suspect: istoric de călătorie într-o zonă cu transmitere locală a virusului, contact al unui caz confirmat sau pacient cu infecție respiratorie acută severă de etiologie necunoscută)
  12. 39 |
40 | 41 | 42 |

Menționăm că vizualizările noastre au fost preluate de numeroase organizații media în exemplificarea articolelor publicate pentru informarea corectă a populației (HotNews.ro, G4Media.ro, Libertatea.ro, RRI.ro, Euractiv.ro, CRJI, etc) privind evoluția pandemiei.

43 |

Demersul nostru, realizat pe bază de voluntariat, a fost inițiat în urma răspândirii coronavirusului Covid-19 până la nivel de pandemie.

44 |

Astăzi, gradul de incertitudine privind modificările vieții noastre de zi cu zi, privind evoluția tuturor resorturilor lumii așa cum o cunoșteam, atinge cotele maxime ale generației noastre. Consecințele pandemiei abia se prefigurează, pe fondul adoptării unor măsuri drastice dar necesare: granițe închise, flote întregi de avioane ținute la sol, școli și universități închise, fabrici care își încetează activitatea, trimițând mii de oameni în șomaj tehnic, restaurante, cluburi, cafenele, magazine, locuri de recreere închise, spectacole, campionate, turnee, conferințe anulate, instituirea stării de urgență și multe altele.

45 |

Pe fondul noii realități, neliniștea populației crește, conducând la comportamente sociale atipice, precum cozile interminabile de la magazinele alimentare, bancomate sau farmacii. Acestea ar putea degenera, în lipsa unei comunicări publice adecvate, în panică. În astfel de vremuri, doar informarea constantă și actualizată este singura metodă reală prin care populația poate fi conectată la evoluția pandemiei pe teritoriul României, precum și la eforturile autorităților de a o restrânge.

46 | 47 |

Apreciem eforturile susținute ale autorităților în a informa inclusiv prin comunicatul de presă zilnic stabilit la ora 13:00 și sugerăm eficientizarea acestei comunicări conform celor explicate în acest document. Autoritățile nu trebuie lăsate singure în acest uriaș efort de comunicare, iar actorii civili pot aduce o semnificativă contribuție în procesarea și vizualizarea datelor, în fluidizarea fluxului informativ către public.

48 | 49 |

Menționăm că:

50 |
    51 |
  • Coronavirus COVID-19 România conține strict doar cazurile confirmate oficial, indicând pentru fiecare în parte sursa oficială a informației.
  • 52 |
  • Informațiile oficiale transformate în date ce pot fi prelucrate cu ușurință în mod automat sunt puse la dispoziția întregii comunități naționale și internaționale pentru includerea acestora în multiplele studii și analize statistice ce sunt realizate, identificând în mod clar sursa acestora.
  • 53 |
  • Autoritatea care pune la dispoziție aceste informații oficiale nu va fi răspunzătoare pentru niciun fel de pagube directe, indirecte, accidentale rezultate din utilizarea sau imposibilitatea utilizării informațiilor.
  • 54 |
  • Vizualizările realizate sau informațiile trimise nu permit identificarea pacienților, datele fiind complet anonimizate.
  • 55 |
56 | 57 |

Înțelegem presiunea uriașă și apreciem eforturile autorităților române de a lupta și a restrânge efectele pandemiei de Covid-19. Considerăm însă că eficientizarea fluxului de informare al populației, precum și accesul deschis la datele oficiale complete enumerate mai sus ar reprezenta un ajutor semnificativ, cel puțin din perspectiva informării corecte a populației României.

58 | 59 |
60 |

Manifest redactat de către Asociația geo-spatial.org și susținut de către:

61 |

62 | Asociația pentru Tehnologie si Internet - ApTI
63 | Centrul pentru Inovare Publică
64 | Funky Citizens
65 | Expert Forum
66 | Comunitatea Declic
67 | Asociația CivicNet
68 | Centrul de Resurse pentru participare publică
69 | Centrul pentru Jurnalism Independent
70 | Centrul Român pentru Jurnalism de Investigație
71 | Freedom House Romania
72 | ActiveWatch
73 | Asociația Pentru Apărarea Drepturilor Omului în România - Comitetul Helsinki (APADOR-CH)
74 | Centrul pentru Inovație în Medicină
75 | Casa Jurnalistului
76 | Asociația Urbannect
77 | Corupția ucide
78 | Geeks 4 Democracy 79 |

80 |
81 | 82 |

83 | Persoană de contact: Vasile Crăciunescu
84 | Mail: vasile@geo-spatial.org 85 |

86 |
87 | 88 | --------------------------------------------------------------------------------