207 lines
6.9 KiB
JavaScript
207 lines
6.9 KiB
JavaScript
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
|
import Navbar from "./Navbar";
|
|
|
|
import {AuthContext} from "../App";
|
|
import Griddle, {ColumnDefinition, plugins, RowDefinition} from "griddle-react";
|
|
import {query} from "../auth";
|
|
import "flatpickr/dist/themes/material_blue.css";
|
|
import "../styles/tables.css"
|
|
import Flatpickr from "react-flatpickr";
|
|
import {Input} from "../custom";
|
|
import {max, mean, median, min, mode, std} from "mathjs";
|
|
import {toast} from "react-toastify";
|
|
|
|
|
|
const background = require('../assets/wave.png')
|
|
const iqr = require('compute-iqr');
|
|
|
|
|
|
const defaultData = () => [{
|
|
ID: "No Data Found",
|
|
Date: "",
|
|
Precipitation: "",
|
|
Latitude: "",
|
|
Longitude: "",
|
|
Remarks: "",
|
|
}]
|
|
|
|
function ViewAll({history, ...props}) {
|
|
const {authState} = useContext(AuthContext)
|
|
const [data, setData] = useState(defaultData())
|
|
const [items, setItems] = useState(10)
|
|
const [status, setStatus] = useState("")
|
|
|
|
const initialDates = () => {
|
|
const now = new Date()
|
|
const lastYear = new Date();
|
|
lastYear.setFullYear(lastYear.getFullYear() - 1)
|
|
return [lastYear, now]
|
|
}
|
|
const [dateRange, setDateRange] = useState([]);
|
|
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(props.location.search)
|
|
let range = initialDates()
|
|
if (params.get("after")) range[0] = new Date(params.get("after"))
|
|
if (params.get("before")) range[1] = new Date(params.get("before"))
|
|
setDateRange(range)
|
|
}, [props.location])
|
|
|
|
|
|
useEffect(() => {
|
|
if (!authState.loggedIn) history.push("/login?return=all")
|
|
}, [authState, history])
|
|
|
|
const initiateData = useCallback(async () => {
|
|
if (dateRange.length !== 2) return
|
|
let after = dateRange[0]
|
|
let before = dateRange[1]
|
|
before.setDate(before.getDate() + 1)
|
|
after.setDate(after.getDate() - 1)
|
|
|
|
const now = new Date();
|
|
now.setDate(now.getDate() + 5)
|
|
const hundred = new Date(1900, 0, 0)
|
|
|
|
if (after > now || after < hundred ||
|
|
before > now || before < hundred)
|
|
return toast.error("Invalid Date Range!")
|
|
|
|
setStatus("Loading Data...")
|
|
let {json, status} = await query("/data/query", {
|
|
token: authState.token,
|
|
validated: true,
|
|
after_date: after,
|
|
before_date: before,
|
|
}).catch(() => ({status: 500, json: {reason: "The authentication server is offline!"}}))
|
|
setStatus("")
|
|
|
|
if (status !== 200) return toast.error(json.reason)
|
|
|
|
let newData = defaultData()
|
|
const formatDate = (date) => {
|
|
return date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, "0") + "-" + date.getDate().toString().padStart(2, "0")
|
|
}
|
|
|
|
json.entries.forEach((entry, index) => {
|
|
const date = new Date(entry.date);
|
|
const group = json.groups.find(gr => gr.id === entry.group_id)
|
|
newData[index] = {
|
|
ID: entry.id,
|
|
Date: formatDate(date),
|
|
Precipitation: entry.precipitation,
|
|
Latitude: group.latitude,
|
|
Longitude: group.longitude,
|
|
Remarks: entry.remarks,
|
|
}
|
|
})
|
|
setData(newData)
|
|
before.setDate(before.getDate() - 1)
|
|
after.setDate(after.getDate() + 1)
|
|
window.history.pushState("Search", "Rain Track", "/all?after=" + formatDate(after) + "&before=" + formatDate(before))
|
|
},
|
|
[setData, authState.token, dateRange])
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
initiateData()
|
|
})();
|
|
}, [setData, initiateData, dateRange, authState.username])
|
|
const styleConfig = {
|
|
classNames: {
|
|
Filter: "input h-8",
|
|
Table: "table table-bordered table-hover table-striped lg:overflow-x-auto overflow-y-scroll",
|
|
PreviousButton: "border border-gray-300 px-2 py-1 w-20 mr-2 rounded",
|
|
NextButton: "border border-gray-300 px-2 py-1 w-20 rounded ml-2"
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="w-screen h-screen overflow-x-hidden bg-cover bg-center text-white flex items-center flex-col"
|
|
style={{backgroundImage: `url(${background})`}}>
|
|
<Navbar history={history} color={"text-blue-600"} hover={"hover:border-blue-600"}/>
|
|
|
|
<div className="container text-gray-700 bg-white p-6 mt-12 mb-32 rounded">
|
|
<div className="flex">
|
|
|
|
<Flatpickr
|
|
value={dateRange}
|
|
onChange={date => setDateRange(date)}
|
|
options={{
|
|
dateFormat: "m-d-Y",
|
|
mode: "range",
|
|
wrap: true,
|
|
position: "below"
|
|
}}>
|
|
<div className="w-72">
|
|
<Input name="date" type='text' others={{"data-input": true}} className="input h-8"
|
|
placeholder="Select Date Range">
|
|
Select Date Range
|
|
</Input>
|
|
</div>
|
|
</Flatpickr>
|
|
<div className="italic text-sm ml-4 w-full flex items-center">{status}</div>
|
|
</div>
|
|
|
|
<hr className="bg-gray-400 mb-4"/>
|
|
<Griddle data={data} plugins={[plugins.LocalPlugin]} styleConfig={styleConfig}
|
|
pageProperties={{
|
|
currentPage: 1,
|
|
pageSize: items
|
|
}}
|
|
components={{
|
|
Layout: Layout(setItems)
|
|
}}>
|
|
<RowDefinition>
|
|
<ColumnDefinition id="ID"/>
|
|
<ColumnDefinition id="Date"/>
|
|
<ColumnDefinition id="Precipitation"/>
|
|
<ColumnDefinition id="Latitude"/>
|
|
<ColumnDefinition id="Longitude"/>
|
|
<ColumnDefinition id="Remarks"/>
|
|
</RowDefinition>
|
|
</Griddle>
|
|
</div>
|
|
|
|
<div className="container text-gray-700 bg-white p-6 mb-32 rounded">
|
|
<div className="text-2xl font-semibold mb-4">Statistics</div>
|
|
<div className="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 row-gap-4 col-gap-12">
|
|
<Stat data={data} calculate={(items) => iqr(items)}>Interquartile Range</Stat>
|
|
<Stat data={data} calculate={(items) => std(items)}>Standard Deviation</Stat>
|
|
<Stat data={data} calculate={(items) => mean(items)}>Mean (Average)</Stat>
|
|
<Stat data={data} calculate={(items) => median(items)}>Median</Stat>
|
|
<Stat data={data} calculate={(items) => mode(items)}>Mode</Stat>
|
|
<Stat data={data} calculate={(items) => max(items) - min(items)}>Range</Stat>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Stat = ({children, data, calculate}) => (
|
|
<div className="rounded border-t-4 border-blue-400 shadow p-2 pt-1">
|
|
<div className="lg:text-xl font-semibold text-gray-700">{children}</div>
|
|
<div className="text-md">{data.length !== 0 && calculate(data.map(set => set.Precipitation)).toString()}</div>
|
|
</div>
|
|
)
|
|
|
|
export default ViewAll;
|
|
|
|
const Layout = (setItems) => (({Table, Filter, Pagination}) => (
|
|
<div>
|
|
<div className="flex w-96 pb-4 h-12 justify-between">
|
|
|
|
<Filter/>
|
|
<select onChange={e => setItems(Number(e.target.value))}>
|
|
<option value="10">Show 10 items</option>
|
|
<option value="20">Show 20 items</option>
|
|
<option value="50">Show 50 items</option>
|
|
<option value="100">Show 100 items</option>
|
|
</select>
|
|
</div>
|
|
<div className="lg:overflow-x-auto overflow-x-scroll">
|
|
<Table/>
|
|
</div>
|
|
<Pagination/>
|
|
</div>
|
|
)) |