From 5727b8039a7e5a64441492d58561bca2591177a0 Mon Sep 17 00:00:00 2001 From: Georgi Alexandrov Date: Tue, 28 Jan 2020 22:32:19 +0200 Subject: [PATCH] Data monitor: date, time, temp, humidity, PM10, PM2.5 --- client/src/App.js | 21 ++++++++++ client/src/components/AirQuality.tsx | 41 ++++++++++++++++++++ client/src/components/Calendar/Calendar.css | 21 ++++++++++ client/src/components/Calendar/index.tsx | 29 ++++++++++++++ client/src/components/Data/Humidity.tsx | 12 ++++++ client/src/components/Data/Particle.tsx | 17 ++++++++ client/src/components/Data/Temperature.tsx | 14 +++++++ client/src/components/Data/style.css | 39 +++++++++++++++++++ client/src/components/Time/Time.css | 4 ++ client/src/components/Time/index.tsx | 21 ++++++++++ client/src/pages/Dashboard.css | 17 ++++++++ client/src/pages/Dashboard.tsx | 19 +++++++++ schema/resolvers/index.js | 43 +++++++++++++++++++++ schema/schema.js | 23 +++++++++++ 14 files changed, 321 insertions(+) create mode 100644 client/src/App.js create mode 100644 client/src/components/AirQuality.tsx create mode 100644 client/src/components/Calendar/Calendar.css create mode 100644 client/src/components/Calendar/index.tsx create mode 100644 client/src/components/Data/Humidity.tsx create mode 100644 client/src/components/Data/Particle.tsx create mode 100644 client/src/components/Data/Temperature.tsx create mode 100644 client/src/components/Data/style.css create mode 100644 client/src/components/Time/Time.css create mode 100644 client/src/components/Time/index.tsx create mode 100644 client/src/pages/Dashboard.css create mode 100644 client/src/pages/Dashboard.tsx create mode 100644 schema/resolvers/index.js create mode 100644 schema/schema.js diff --git a/client/src/App.js b/client/src/App.js new file mode 100644 index 0000000..ac1bb89 --- /dev/null +++ b/client/src/App.js @@ -0,0 +1,21 @@ +import React from 'react'; +import ApolloClient from 'apollo-boost'; +import { ApolloProvider } from 'react-apollo'; +import './App.css'; +import Dashboard from './pages/Dashboard.tsx' + +const client = new ApolloClient({ + uri: 'http://localhost:4000/graphql' +}); + +function App() { + return ( +
+ + + +
+ ); +} + +export default App; diff --git a/client/src/components/AirQuality.tsx b/client/src/components/AirQuality.tsx new file mode 100644 index 0000000..4d2788b --- /dev/null +++ b/client/src/components/AirQuality.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { useQuery } from "@apollo/react-hooks"; +import gql from "graphql-tag"; + +import Calendar from "./Calendar/index"; +import Time from "./Time/index"; +import Humidity from "./Data/Humidity" +import Temperature from "./Data/Temperature" +import Particle from "./Data/Particle" + +const GET_AIR_QUALITY = gql` + { + latest_air_quality { + pm10 + pm25 + BME280_temperature + BME280_humidity + } + } +`; + +export default function AirQuality() { + const { data, loading } = useQuery(GET_AIR_QUALITY, {}); + + if (loading) return
Loading...
+ return ( +
+
+ +
+ +
+ + + + +
+
+ ); +} diff --git a/client/src/components/Calendar/Calendar.css b/client/src/components/Calendar/Calendar.css new file mode 100644 index 0000000..32d7cb1 --- /dev/null +++ b/client/src/components/Calendar/Calendar.css @@ -0,0 +1,21 @@ +.calendar { + width: 100%; + display: flex; + flex-direction: column; +} + +.calendar .date { + font-size: 4em; +} + +.calendar .day { + font-size: 2em; +} + +.calendar .month { + font-size: 1.5em; +} + +.calendar .year { + font-size: 1.2em; +} \ No newline at end of file diff --git a/client/src/components/Calendar/index.tsx b/client/src/components/Calendar/index.tsx new file mode 100644 index 0000000..b01ae1f --- /dev/null +++ b/client/src/components/Calendar/index.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import './Calendar.css'; + +const daysOfWeek = ['Неделя', 'Понеделник', 'Вторник', 'Сряда', 'Четвъртък', 'Петък', 'Събота', 'Неделя'] +const monthsOfYear = ['Януари', 'Февруари', 'Март', 'Април', 'Май', 'Юни', 'Юли', 'Август', 'Септември', 'Ноември', 'Октомври', 'Декември'] + +export default function Calendar() { + const today = () => { + let today = new Date() + return { + date: today.getDate(), + day: daysOfWeek[today.getDay()], + month: monthsOfYear[today.getMonth()], + year: today.getFullYear() + } + } + + return ( +
+ {today().day} + {today().date} + {today().month} + {today().year} +
+ ); +} + + + diff --git a/client/src/components/Data/Humidity.tsx b/client/src/components/Data/Humidity.tsx new file mode 100644 index 0000000..9383c26 --- /dev/null +++ b/client/src/components/Data/Humidity.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import './style.css' + +export default function Data(props: any) { + return ( +
+ 💦{props.data} +
+ ); +} + + diff --git a/client/src/components/Data/Particle.tsx b/client/src/components/Data/Particle.tsx new file mode 100644 index 0000000..f580788 --- /dev/null +++ b/client/src/components/Data/Particle.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export default function Data(props: any) { + const color = (particle: number) => { + if (particle < 10) return 'great'; + if (particle > 10 && particle < 30) return 'not_great'; + if (particle > 30 && particle < 50) return 'not_great_not_terrible'; + if (particle > 50) return 'terrible'; + } + + return ( +
+ {props.data} + {props.title} +
+ ); +} diff --git a/client/src/components/Data/Temperature.tsx b/client/src/components/Data/Temperature.tsx new file mode 100644 index 0000000..de67a91 --- /dev/null +++ b/client/src/components/Data/Temperature.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +export default function Data(props: any) { + return ( +
+ + + + {props.data} °C +
+ ); +} + + diff --git a/client/src/components/Data/style.css b/client/src/components/Data/style.css new file mode 100644 index 0000000..6883ec5 --- /dev/null +++ b/client/src/components/Data/style.css @@ -0,0 +1,39 @@ +.temperature, +.humidity { + font-size: 3em; +} + +.particle { + flex-basis: 1; + font-size: 3em; + flex-direction: column; +} + +.particle span { + flex-basis: 1; + width: 100%; +} + +.particle .title { + font-size: 0.4em; +} + +.great { + color: green; +} + +.not_great { + color: rgb(185, 185, 40); +} + +.not_great_not_terrible { + color: orange; +} + +.terrible { + color: red; +} + +.dark path { + fill: white; +} \ No newline at end of file diff --git a/client/src/components/Time/Time.css b/client/src/components/Time/Time.css new file mode 100644 index 0000000..fc07cc8 --- /dev/null +++ b/client/src/components/Time/Time.css @@ -0,0 +1,4 @@ +.time { + font-size: 3em; + width: 100%; +} \ No newline at end of file diff --git a/client/src/components/Time/index.tsx b/client/src/components/Time/index.tsx new file mode 100644 index 0000000..77a750d --- /dev/null +++ b/client/src/components/Time/index.tsx @@ -0,0 +1,21 @@ +import React, { useState } from "react"; +import './Time.css'; + +const formatDate = (now: Date = new Date()) => { + let hours = now.getHours() > 9 ? now.getHours() : '0' + now.getHours() + let minutes = now.getMinutes() > 9 ? now.getMinutes() : '0' + now.getMinutes() + + return `${hours}:${minutes}` +} + +export default function Time() { + let now = new Date() + const [time, updateTime] = useState(formatDate(now)) + + const timeout = (60 - now.getSeconds()) * 1000 + setTimeout(() => updateTime(formatDate()), timeout) + + return ( +
{time}
+ ); +} diff --git a/client/src/pages/Dashboard.css b/client/src/pages/Dashboard.css new file mode 100644 index 0000000..ca6b731 --- /dev/null +++ b/client/src/pages/Dashboard.css @@ -0,0 +1,17 @@ +.titleBar { + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +.dark { + background: black; + color: white; + width: 100%; + height: 100vh; +} + +.light { + background: white; + color: black; +} \ No newline at end of file diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx new file mode 100644 index 0000000..4e44211 --- /dev/null +++ b/client/src/pages/Dashboard.tsx @@ -0,0 +1,19 @@ +import React, { useState } from "react"; +import AirQuality from "../components/AirQuality"; +import './Dashboard.css'; + +export default function Dashboard(props: any) { + let [theme, setTheme] = useState('light'); + + const [themeName, themeTitle] = theme === 'dark' ? ['light', 'Light'] : ['dark', 'Dark'] + return ( +
+
+ +
+
+ +
+
+ ); +} diff --git a/schema/resolvers/index.js b/schema/resolvers/index.js new file mode 100644 index 0000000..a4fd9b5 --- /dev/null +++ b/schema/resolvers/index.js @@ -0,0 +1,43 @@ +const request = require('request'); + +module.exports = { + Query: { + latest_air_quality: () => { + const url = 'http://elastic:9200/air-quality/_search?sort=datetime:desc&size=1' + + return new Promise((resolve, rej) => { + request.post(url, { json: { "query": { "bool": {} } } }, (error, response, body) => { + if (!error && response.statusCode == 200) { + resolve(body.hits.hits[0]._source) + } + }); + }) + }, + daily_air_quality: () => { + const url = 'http://elastic:9200/air-quality/_search?sort=datetime:desc&size=584' + + return new Promise((resolve, rej) => { + request.post(url, { + json: { + "query": { + "range": { + "datetime": { + "gte": "2020-01-19T00:00:00", + "lte": "2020-01-20T00:00:00" + } + } + } + } + }, (error, response, body) => { + if (!error && response.statusCode == 200) { + resolve(body.hits.hits.map(hit => hit._source)) + } + }); + }) + } + }, +}; + + + + diff --git a/schema/schema.js b/schema/schema.js new file mode 100644 index 0000000..b1c3269 --- /dev/null +++ b/schema/schema.js @@ -0,0 +1,23 @@ +const { gql } = require('apollo-server'); + +module.exports = gql` +scalar Date + +type AirQualityRecord { + BME280_humidity: Float + BME280_pressure: Float + BME280_temperature: Float + datetime: Date + esp8266id: Int + max_micro: Int + min_micro: Int + pm10: Float + pm25: Float + signal: Int + software_version: String +} +type Query { + latest_air_quality: AirQualityRecord + daily_air_quality: [AirQualityRecord] +} +`; \ No newline at end of file