Files
sensors/mapper/src/App.js
T

211 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import * as leaflet from 'leaflet';
import { MapContainer, Polyline, TileLayer, useMap } from 'react-leaflet';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import axios from 'axios';
import moment from 'moment-timezone';
import './App.css';
import 'leaflet/dist/leaflet.css';
let tzcache = {};
const durations = [
{id: 0, len: 'Day', win: '10m', full: '10 min', delta: [1, 'days'], format: 'HH'},
{id: 1, len: 'Day', win: '1h', full: '1 hour', delta: [1, 'days'], format: 'HH'},
{id: 2, len: 'Week', win: '1h', full: '1 hour', delta: [7, 'days'], format: 'HH'},
{id: 3, len: 'Week', win: '1d', full: '1 day', delta: [7, 'days'], format: 'D'},
{id: 4, len: 'Month', win: '1d', full: '1 day', delta: [1, 'months'], format: 'D'},
{id: 5, len: 'Month', win: '7d', full: '7 day', delta: [1, 'months'], format: 'D'},
{id: 6, len: 'Year', win: '1d', full: '1 day', delta: [1, 'years'], format: 'M/D'},
{id: 7, len: 'Year', win: '30d', full: '30 day', delta: [1, 'years'], format: 'M'},
];
const units = {
'PM10': ' ug/m³',
'PM2.5': ' ug/m³',
'VOC': ' / 500',
'CO2': ' ppm',
'Energy': ' kWh',
'Power': ' W',
'Temperature': ' °C',
'Humidity': '%',
'Setpoint': ' °C',
'State': '',
'Lux': ' lx',
};
function useSensor(measurement, name, end, duration) {
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
const get = async() => {
setLoading(true);
try {
const api_key = localStorage.getItem('api_key', 'null');
const params = { end: end.unix(), duration: duration.len.toLowerCase(), window: duration.win, api_key: api_key };
const res = await axios.get(
'https://sensors-api.dns.t0.vc/history/'+measurement+'/'+name,
{ params: params },
);
setData((d) => (res.data));
setLoading(false);
} catch (error) {
console.log(error);
}
};
get();
}, [end, duration]);
return [data, loading];
};
function Owntracks({end, duration}) {
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
const coords = data.length ? data.map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
return (
<>
{loading ?
<p>Loading...</p>
:
coords.length ?
<MapContainer center={coords[coords.length-1]} zoom={13} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Polyline pathOptions={{color: 'blue'}} positions={coords} />
</MapContainer>
:
<p>No coords</p>
}
</>
);
}
function Graphs({end, duration}) {
return (
<div className='container'>
<Owntracks end={end} duration={duration} />
</div>
);
}
function Menu({duration, setDuration, end, setEnd}) {
const [submenu, setSubmenu] = useState(false);
const chooseDuration = (x) => {
setSubmenu(false);
setDuration(x);
};
const chooseEnd = (x) => {
setSubmenu(false);
const newEnd = x.add(...duration.delta);
setEnd(newEnd);
};
const chooseNow = (x) => {
setSubmenu(false);
setEnd(moment());
};
const next = () => {
setSubmenu(false);
setEnd(prevEnd => moment(prevEnd).add(...duration.delta));
}
const prev = () => {
setSubmenu(false);
setEnd(prevEnd => moment(prevEnd).subtract(...duration.delta));
}
return (
<div className='menu'>
{!!submenu &&<div className='submenu'>
{submenu === 'end' &&
<>
<div className='submenu-header'>
<h2>Choose start date:</h2>
<button onClick={() => setSubmenu(false)}>×</button>
</div>
<div className='datepicker'>
<Datetime
input={false}
timeFormat={false}
onChange={(x) => chooseEnd(x)}
/>
</div>
<button onClick={chooseNow}>Jump to Now</button>
</>
}
{submenu === 'duration' &&
<>
<div className='submenu-header'>
<h2>Choose duration:</h2>
<button onClick={() => setSubmenu(false)}>×</button>
</div>
{durations.map(x =>
<button key={x.id} onClick={() => chooseDuration(x)}>Last {x.len} / {x.full} data</button>
)}
</>
}
</div>}
<div className='menu-container'>
<button onClick={() => prev()}>&lt;</button>
<button
onClick={() => setSubmenu('end')}
className={submenu === 'end' ? 'active' : ''}
>
{moment(end).subtract(duration.delta[0], duration.delta[1]).format('ddd MMM DD')}
</button>
<button
onClick={() => setSubmenu('duration')}
className={submenu === 'duration' ? 'active' : ''}
>
{duration.len} / {duration.win}
</button>
<button onClick={() => next()}>&gt;</button>
</div>
</div>
);
}
function App() {
const [duration, setDuration] = useState(durations[0]);
const [end, setEnd] = useState(moment());
return (
<div>
<Menu
duration={duration}
setDuration={setDuration}
end={end}
setEnd={setEnd}
/>
<Graphs
end={end}
duration={duration}
/>
</div>
);
}
export default App;