diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e0..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index a55e7a1..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
deleted file mode 100644
index 615c24b..0000000
--- a/.idea/dataSources.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- postgresql
- true
- org.postgresql.Driver
- jdbc:postgresql://dev.teamortix.com:5432/hamza
-
-
-
\ No newline at end of file
diff --git a/.idea/dictionaries/dev.xml b/.idea/dictionaries/dev.xml
deleted file mode 100644
index 283ade4..0000000
--- a/.idea/dictionaries/dev.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- navbar
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index d5cfc95..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index e0a50ca..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 912b893..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 8d09836..9bf51d4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3728,6 +3728,23 @@
}
}
},
+ "compute-iqr": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/compute-iqr/-/compute-iqr-1.1.0.tgz",
+ "integrity": "sha1-Lqrmncpv+BCz/o8F8MZGwBmM7mc=",
+ "requires": {
+ "compute-quantile": "^1.0.0",
+ "validate.io-object": "^1.0.0"
+ }
+ },
+ "compute-quantile": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/compute-quantile/-/compute-quantile-1.0.1.tgz",
+ "integrity": "sha1-MHI7rGFtnMXifA4HdanTVn37rWY=",
+ "requires": {
+ "validate.io-object": "^1.0.0"
+ }
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -5477,6 +5494,11 @@
"strip-eof": "^1.0.0"
}
},
+ "exenv": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+ "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
+ },
"exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@@ -5936,6 +5958,11 @@
"write": "1.0.3"
}
},
+ "flatpickr": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.3.tgz",
+ "integrity": "sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ=="
+ },
"flatted": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
@@ -5980,22 +6007,9 @@
}
},
"follow-redirects": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
- "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
- "requires": {
- "debug": "^3.0.0"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
- "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
- "requires": {
- "ms": "^2.1.1"
- }
- }
- }
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
+ "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg=="
},
"for-in": {
"version": "1.0.2",
@@ -6663,9 +6677,9 @@
"integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q="
},
"http-proxy": {
- "version": "1.18.0",
- "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
- "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
@@ -8111,9 +8125,9 @@
}
},
"lodash": {
- "version": "4.17.15",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ "version": "4.17.19",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
+ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash-es": {
"version": "4.17.15",
@@ -11402,11 +11416,28 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
},
+ "react-flatpickr": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/react-flatpickr/-/react-flatpickr-3.10.0.tgz",
+ "integrity": "sha512-eeU5OlUZgWDyp2XvIeZ4+rI4T4VkqU1MdZUb+cXtTHfHYfLV+gpP0lAhRrij+QXlbPXTJFAbPerihohlOeTJ0A==",
+ "requires": {
+ "flatpickr": "^4.5.7",
+ "prop-types": "^15.5.10"
+ }
+ },
"react-hook-form": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-5.7.2.tgz",
"integrity": "sha512-bJvY348vayIvEUmSK7Fvea/NgqbT2racA2IbnJz/aPlQ3GBtaTeDITH6rtCa6y++obZzG6E3Q8VuoXPir7QYUg=="
},
+ "react-icons": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.10.0.tgz",
+ "integrity": "sha512-WsQ5n1JToG9VixWilSo1bHv842Cj5aZqTGiS3Ud47myF6aK7S/IUY2+dHcBdmkQcCFRuHsJ9OMUI0kTDfjyZXQ==",
+ "requires": {
+ "camelcase": "^5.0.0"
+ }
+ },
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -11417,6 +11448,17 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-modal": {
+ "version": "3.11.2",
+ "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz",
+ "integrity": "sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==",
+ "requires": {
+ "exenv": "^1.2.0",
+ "prop-types": "^15.5.10",
+ "react-lifecycles-compat": "^3.0.0",
+ "warning": "^4.0.3"
+ }
+ },
"react-redux": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
@@ -13955,6 +13997,19 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "validate.io-array": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
+ "integrity": "sha1-W1osr9j4uFq7L4hroVPy2Tond00="
+ },
+ "validate.io-object": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/validate.io-object/-/validate.io-object-1.0.4.tgz",
+ "integrity": "sha1-3KAezu45DhENvCr4Q8gfe/M6Qas=",
+ "requires": {
+ "validate.io-array": "^1.0.1"
+ }
+ },
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
@@ -14016,6 +14071,14 @@
"makeerror": "1.0.x"
}
},
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"watchpack": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz",
diff --git a/package.json b/package.json
index bb9c213..b395369 100644
--- a/package.json
+++ b/package.json
@@ -6,13 +6,17 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
+ "compute-iqr": "^1.1.0",
"cross-env": "^7.0.2",
"cssnano": "^4.1.10",
"griddle-react": "^1.13.1",
"mathjs": "^7.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
+ "react-flatpickr": "^3.10.0",
"react-hook-form": "^5.7.2",
+ "react-icons": "^3.10.0",
+ "react-modal": "^3.11.2",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-toastify": "^6.0.4",
diff --git a/postcss.config.js b/postcss.config.js
index 43eaef7..bcc8f1a 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,30 +1,29 @@
-const tailwindcss = require('tailwindcss');
-const nano = require('cssnano')
-
-const purgecss = require('@fullhuman/postcss-purgecss')({
-//
- content: [
- "./src/components/*.js",
- "./node_modules/react-toastify/dist/ReactToastify.min.css",
- "./public/index.html",
- "./src/custom.js",
- "./src/App.js",
- ],
-
- defaultExtractor: content => {
- const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
- const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []
-
- return broadMatches.concat(innerMatches)
- }
-})
-
-module.exports = {
- plugins: [
- tailwindcss('./tailwind.js'),
- require('autoprefixer'),
- ...process.env.NODE_ENV === 'production'
- ? [purgecss, nano]
- : []
- ]
+const tailwindcss = require('tailwindcss');
+const nano = require('cssnano')
+
+const purgecss = require('@fullhuman/postcss-purgecss')({
+//
+ content: [
+ "./src/components/*.js",
+ "./node_modules/react-toastify/dist/ReactToastify.min.css",
+ "./public/index.html",
+ "./src/custom.js",
+ "./src/App.js",
+ ],
+
+ defaultExtractor: content => content.match(/[\w-:/]+(?
-
+
Rain Track
diff --git a/raintrack-ui.iml b/raintrack-ui.iml
index 78aaf41..da07e02 100644
--- a/raintrack-ui.iml
+++ b/raintrack-ui.iml
@@ -1,10 +1,11 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index a23a51a..0901e9c 100644
--- a/src/App.js
+++ b/src/App.js
@@ -5,7 +5,6 @@ import 'react-toastify/dist/ReactToastify.min.css'
import './styles/app.css'
import {authenticate, defaultAuth, reducer} from "./auth";
-
// pages
import Home from "./components/Home";
import Login from "./components/Login";
@@ -14,6 +13,8 @@ import Register from "./components/Register";
import Upload from "./components/Upload";
import NotFound from "./components/NotFound";
import ViewAll from "./components/ViewAll";
+import Admin from "./components/Admin";
+import MyData from "./components/ViewOwn";
export const AuthContext = React.createContext(undefined)
@@ -37,6 +38,9 @@ function App() {
+
+
+
diff --git a/src/auth.js b/src/auth.js
index a8fcbf0..cc38d6e 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -1,74 +1,75 @@
-import {toast} from "react-toastify";
-
-// export const SERVER_URL = "http://192.168.1.8:8080/api"
-export const SERVER_URL = "http://dev.teamortix.com:4290/api"
-
-export const defaultAuth = () => ({
- loggedIn: window.localStorage.getItem("authtoken") !== null,
- token: window.localStorage.getItem("authtoken"),
- username: '',
- email: '',
- admin: false,
-})
-
-
-export const reducer = (state, action) => {
- return {...state, ...action.response}
-}
-
-export async function authenticate(token) {
- if (token == null)
- return defaultAuth()
-
- const {status, json} = await query("/account/info", {token}).catch(() => {
- toast.error("The authentication server is offline!")
- return defaultAuth()
- })
-
- if (status !== 200) {
- window.localStorage.removeItem("authtoken")
- toast.warn("You have been logged out.")
- return defaultAuth()
- }
- console.debug("Authentication Response:", json)
-
- const {email, username, admin} = json;
- return {username, email, admin, loggedIn: true}
-}
-
-export async function registration(username, email, password) {
- const {status, json} = await query("/register", {username, email, password}).catch(() => {
- return {status: 500, json: {reason: "The authentication server is offline!"}}
- })
-
- console.debug("Registration Response:", json)
- return {status, json}
-}
-
-export async function login(usernameOrEmail, password) {
- const {status, json} = await query("/login", {usernameOrEmail, password}).catch(() => {
- return {status: 500, json: {reason: "The authentication server is offline!"}}
- })
- console.debug("Login Response:", json)
-
- return {status, json}
-}
-
-export function logout(token) {
- window.localStorage.removeItem("authtoken")
- query("/logout", {token}).then(() => {
- toast.warn("You have been logged out.")
- })
- return defaultAuth()
-}
-
-export async function query(url, data) {
- console.debug("FETCH: " + SERVER_URL + url + " - " + JSON.stringify(data))
- let response = await fetch(SERVER_URL + url, {
- method: "POST",
- headers: {"Content-Type": "application/json"},
- body: JSON.stringify(data)
- })
-
- return {status: response.status, json: await response.json()}
+import {toast} from "react-toastify";
+
+// export const SERVER_URL = "http://192.168.1.8:8080/api"
+export const SERVER_URL = "http://dev.teamortix.com:4290/api"
+
+export const defaultAuth = () => ({
+ loggedIn: window.localStorage.getItem("authtoken") !== null,
+ token: window.localStorage.getItem("authtoken"),
+ username: '',
+ email: '',
+ admin: false,
+})
+
+
+export const reducer = (state, action) => {
+ return {...state, ...action.response}
+}
+
+export async function authenticate(token) {
+ if (token == null)
+ return defaultAuth()
+
+ const {status, json} = await query("/account/info", {token}).catch(() => {
+ toast.error("The authentication server is offline!")
+ return defaultAuth()
+ })
+
+ if (status !== 200) {
+ window.localStorage.removeItem("authtoken")
+ toast.warn("You have been logged out.")
+ return defaultAuth()
+ }
+ console.debug("Authentication Response:", json)
+
+ const {email, username, admin} = json;
+ return {username, email, admin, loggedIn: true}
+}
+
+export async function registration(username, email, password) {
+ const {status, json} = await query("/register", {username, email, password}).catch(() => {
+ return {status: 500, json: {reason: "The authentication server is offline!"}}
+ })
+
+ console.debug("Registration Response:", json)
+ return {status, json}
+}
+
+export async function login(usernameOrEmail, password) {
+ const {status, json} = await query("/login", {usernameOrEmail, password}).catch(() => {
+ return {status: 500, json: {reason: "The authentication server is offline!"}}
+ })
+ console.debug("Login Response:", json)
+
+ return {status, json}
+}
+
+export function logout(token) {
+ window.localStorage.removeItem("authtoken")
+ query("/logout", {token}).then(() => {
+ toast.warn("You have been logged out.")
+ })
+ return defaultAuth()
+}
+
+export async function query(url, data) {
+ console.debug("FETCH: " + SERVER_URL + url + " - " + JSON.stringify(data))
+ let response = await fetch(SERVER_URL + url, {
+ method: "POST",
+ headers: {"Content-Type": "application/json"},
+ body: JSON.stringify(data)
+ })
+ const json = await response.json()
+ console.debug(json)
+ return {status: response.status, json}
}
\ No newline at end of file
diff --git a/src/components/Admin.js b/src/components/Admin.js
new file mode 100644
index 0000000..da324c2
--- /dev/null
+++ b/src/components/Admin.js
@@ -0,0 +1,243 @@
+import React, {useCallback, useContext, useEffect, useReducer, 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 {toast} from "react-toastify";
+import {AiFillDelete, AiFillEdit} from "react-icons/ai";
+import Modal from "react-modal"
+import FileManualForm from "./FileManualForm";
+
+
+const background = require('../assets/wave.png')
+
+const defaultData = () => [{
+ ID: "No Data Found",
+ Date: "",
+ Precipitation: "",
+ Latitude: "",
+ Longitude: "",
+ Remarks: "",
+ Edit: "",
+}]
+
+function Admin({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([]);
+ const [editModal, setEditModal] = useState({open: false, data: {}})
+ const [updateData, dispatchEdit] = useReducer((state, action) => action, [])
+
+ 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)
+ }, [])
+
+ useEffect(() => {
+ console.log(authState)
+ if (!authState.loggedIn) history.push("/login?return=admin")
+ if (!authState.admin) history.push("/")
+ }, [authState, history])
+
+ const deleteRow = async (data) => {
+ const {status, json} = await query("/data/delete", data).catch(e => {
+ console.debug(e)
+ return {status: 500, json: {reason: "The upload server is offline!"}}
+ })
+ console.debug("Delete Row Response", json)
+ return {status, json}
+ }
+
+ const initiateData = useCallback(async () => {
+ if (dateRange.length !== 2 || !authState.username) 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,
+ after_date: after,
+ before_date: before,
+ username: authState.username,
+ }).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,
+ Verified: group.validated ? "Yes" : "No",
+ Remarks: entry.remarks,
+ }
+ })
+ setData(newData)
+
+ before.setDate(before.getDate() - 1)
+ after.setDate(after.getDate() + 1)
+ window.history.pushState("Search", "Rain Track", "/mydata?after=" + formatDate(after) + "&before=" + formatDate(before))
+ }, [setData, authState.token, dateRange, authState.username])
+
+ 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"
+ }
+ }
+ useEffect(() => {
+ if (updateData.length !== 0) {
+ const row = data[updateData[1]]
+ if (!row) return
+ switch (updateData[0]) {
+ case 0:
+ setEditModal({open: true, data: row})
+ break;
+ case 1:
+ window.confirm("Are you sure you would like to delete this row?")
+ && (async () => {
+ let {json, status} = await deleteRow({token: authState.token, id: row.ID})
+ if (status !== 200) return toast.error(json.reason)
+ toast.success("Entry successfully deleted")
+ dispatchEdit([])
+ setData(data.filter(r => r !== row))
+ })()
+ break;
+ default:
+ break;
+ }
+ }
+ }, [updateData, data, authState])
+
+
+ return (
+
+
+
+
+
setDateRange(date)}
+ options={{
+ dateFormat: "m-d-Y",
+ mode: "range",
+ wrap: true,
+ position: "below"
+ }}>
+
+
+ Select Date Range
+
+
+
+
{status}
+
+
+
+
+
+
+
+
+
+
+
+
+ }/>
+
+
+
+ setEditModal({open: false, data: {ID: 0}})}
+ shouldCloseOnOverlayClick
+ isOpen={editModal.open}>
+
+
+
+
+
+ );
+}
+
+export default Admin;
+
+const Layout = (setItems) => (({Table, Filter, Pagination}) => (
+
+
+
+
+
+
+
+
+))
+
+const EditComponent = ({griddleKey, dispatch}) => {
+
+ return
+
dispatch([0, griddleKey])}>
+
dispatch([1, griddleKey])}>
+
+}
\ No newline at end of file
diff --git a/src/components/FileManualForm.js b/src/components/FileManualForm.js
index eaa2854..39a5620 100644
--- a/src/components/FileManualForm.js
+++ b/src/components/FileManualForm.js
@@ -1,98 +1,116 @@
-import React, {useContext, useState} from 'react';
-import {toast} from "react-toastify";
-import {AuthContext} from "../App";
-import {useForm} from "react-hook-form";
-import {query} from "../auth";
-import {Input} from "../custom";
-
-function FileManualForm() {
- const {authState} = useContext(AuthContext)
- const [manualError, setManualError] = useState("")
- const {register, handleSubmit, errors, setError, reset} = useForm();
-
- const manualUpload = async (data) => {
- const {status, json} = await query("/data/add", data).catch(() => {
- return {status: 500, json: {reason: "The upload server is offline!"}}
- })
- console.debug("Manual Upload Response:", json)
- return {status, json}
- }
-
- function submitManual(data) {
- let req = data
-
- let split = data.date.split(/[/-]/);
- // noinspection JSCheckFunctionSignatures
- let date = new Date(split[2], Number(split[0]) - 1, split[1])
- req.date = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()
- if (date > new Date()) return setError("date", "time", "Your date must be in the past.")
- if (date < new Date(1900, 0, 0)) return setError("date", "time", "Your date must be after 1900.")
- if (date.getDay() === 0) return setError("date", "format", "Invalid Date format (MM-DD-YYYY)")
-
- if (isNaN(parseFloat(data["precipitation"]))) {
- setError("precipitation", "format", "Invalid format. Precipitation must be a number. E.g 0.24")
- }
- req.precipitation = parseFloat(data["precipitation"])
- req.token = authState.token;
-
- (async () => {
- let {json, status} = await manualUpload(data)
- if (status !== 200) return setManualError(json.reason)
- reset()
- toast.success("Entry successfully uploaded")
- })();
- }
-
-
- return (
-
- );
-}
-
+import React, {useContext, useEffect, useState} from 'react';
+import {toast} from "react-toastify";
+import {AuthContext} from "../App";
+import {useForm} from "react-hook-form";
+import {query} from "../auth";
+import {Input} from "../custom";
+
+function FileManualForm({id, title = "Upload Individual Entry", defaultData = {}, editType: edit = false}) {
+ const {authState} = useContext(AuthContext)
+ const [manualError, setManualError] = useState("")
+ const {register, handleSubmit, errors, setError, reset, setValue} = useForm();
+
+ const manualUpload = async (data) => {
+ const {status, json} = await query("/data/add", data).catch(() => {
+ return {status: 500, json: {reason: "The upload server is offline!"}}
+ })
+ console.debug("Manual Upload Response:", json)
+ return {status, json}
+ }
+
+ function submitManual(data) {
+ let req = data
+
+ let split = data.date.split(/[/-]/);
+ // noinspection JSCheckFunctionSignatures
+ let date = new Date(split[2], Number(split[0]) - 1, split[1])
+ req.date = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()
+ if (date > new Date()) return setError("date", "time", "Your date must be in the past.")
+ if (date < new Date(1900, 0, 0)) return setError("date", "time", "Your date must be after 1900.")
+ if (date.getDate() === 0) return setError("date", "format", "Invalid Date format (MM-DD-YYYY)")
+
+ if (isNaN(parseFloat(data["precipitation"]))) {
+ setError("precipitation", "format", "Invalid format. Precipitation must be a number. E.g 0.24")
+ }
+ req.precipitation = parseFloat(data["precipitation"])
+ req.id = id
+ req.token = authState.token;
+
+ (async () => {
+ let {json, status} = await manualUpload(data)
+ if (status !== 200) return setManualError(json.reason)
+ reset()
+ if (edit) window.location.reload()
+ toast.success("Entry successfully uploaded")
+ })();
+ }
+
+ const formClasses = edit
+ ? "bg-white shadow-lg p-4 overflow-hidden rounded-lg my-6 px-6 max-w-lg w-128"
+ : "bg-white shadow-lg p-4 overflow-hidden rounded-lg my-6 px-6 w-11/12 lg:mx-2 lg:my-5 lg:px-5 lg:w-5/12"
+
+ useEffect(() => {
+ if (edit) {
+ const date = new Date(defaultData.Date)
+ const formattedDate = (date.getMonth() + 1).toString().padStart(2, "0") + "-" + date.getDate().toString().padStart(2, "0") + "-" + date.getFullYear()
+ setValue([
+ {date: formattedDate},
+ {precipitation: defaultData.Precipitation},
+ {latitude: defaultData.Latitude},
+ {longitude: defaultData.Longitude},
+ {remarks: defaultData.Remarks},
+ ])
+ }
+ }, [defaultData, edit, setValue])
+
+ return (
+
+ );
+}
+
export default FileManualForm;
\ No newline at end of file
diff --git a/src/components/FileUploadForm.js b/src/components/FileUploadForm.js
index 5fa0fe5..e5febf5 100644
--- a/src/components/FileUploadForm.js
+++ b/src/components/FileUploadForm.js
@@ -1,129 +1,129 @@
-import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react';
-import {toast} from "react-toastify";
-import {AuthContext} from "../App";
-import {SERVER_URL} from "../auth";
-import {Input} from "../custom";
-
-const LIMIT = 2 * 1024 * 1024; // 2 mb
-
-const dispatch = (state, action) => {
- switch (action.type) {
- case "lat":
- return {...state, lat: action.value}
- case "long":
- return {...state, long: action.value}
- default:
- return;
- }
-}
-
-function FileUploadForm() {
- const {authState} = useContext(AuthContext)
- const [files, setFiles] = useState(null);
- const [load, setLoad] = useState(0)
- const [status, setStatus] = useState("Upload a file")
- const [latLong, setLatLong] = useReducer(dispatch, {lat: "", long: ""}, undefined)
- let form = useRef(null);
-
- async function query(url, data) {
- console.debug("FETCH: " + SERVER_URL + url + " - " + data)
- return new Promise((resolve, reject) => {
- let req = new XMLHttpRequest()
- req.open('post', SERVER_URL + url)
- req.upload.addEventListener('progress', e => {
- if (e.loaded === e.total) setStatus("Processing...")
- else {
- let percent = (e.loaded / e.total * 100)
- setLoad(percent)
- setStatus("Uploading " + percent.toFixed(0) + "%")
- }
- });
- req.addEventListener('load', () => {
- resolve({status: req.status, json: JSON.parse(req.response)})
- });
- req.addEventListener('error', e => {
- resolve({status: req.status, json: JSON.parse(req.response)})
- })
- req.send(data)
- })
- }
-
- const upload = useCallback(async () => {
- if (!files || files.length === 0) return
- const target = files[0]
- if (target.size > LIMIT) return toast.error("File size too large! Max 2 mb.")
- if (!target.name.includes(".xlsx")) return toast.error("Invalid file type!")
- const {lat, long} = latLong;
-
- setStatus("Uploading")
-
- let file = new FormData()
- file.append("file", target)
- file.append("token", authState.token);
- file.append("latitude", lat);
- file.append("longitude", long);
-
- let {status, json} = await query("/data/upload", file)
- form.current.reset();
-
- setStatus("Upload a file")
- setLoad(0)
-
- if (status !== 200) return toast.error(json.reason)
- return toast.success("File successfully uploaded.")
- }, [files, authState.token, form, latLong])
-
- useEffect(() => {
- (async () => upload())();
- }, [files, authState.token, upload, authState, setLoad, setStatus])
-
- return
-
-
Upload File Entries
-
- Click the button to upload an .xlsx file
-
-
-
-
-
;
-}
-
+import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react';
+import {toast} from "react-toastify";
+import {AuthContext} from "../App";
+import {SERVER_URL} from "../auth";
+import {Input} from "../custom";
+
+const LIMIT = 2 * 1024 * 1024; // 2 mb
+
+const dispatch = (state, action) => {
+ switch (action.type) {
+ case "lat":
+ return {...state, lat: action.value}
+ case "long":
+ return {...state, long: action.value}
+ default:
+ return;
+ }
+}
+
+function FileUploadForm() {
+ const {authState} = useContext(AuthContext)
+ const [files, setFiles] = useState(null);
+ const [load, setLoad] = useState(0)
+ const [status, setStatus] = useState("Upload a file")
+ const [latLong, setLatLong] = useReducer(dispatch, {lat: "", long: ""}, undefined)
+ let form = useRef(null);
+
+ async function query(url, data) {
+ console.debug("FETCH: " + SERVER_URL + url + " - " + data)
+ return new Promise((resolve, reject) => {
+ let req = new XMLHttpRequest()
+ req.open('post', SERVER_URL + url)
+ req.upload.addEventListener('progress', e => {
+ if (e.loaded === e.total) setStatus("Processing...")
+ else {
+ let percent = (e.loaded / e.total * 100)
+ setLoad(percent)
+ setStatus("Uploading " + percent.toFixed(0) + "%")
+ }
+ });
+ req.addEventListener('load', () => {
+ resolve({status: req.status, json: JSON.parse(req.response)})
+ });
+ req.addEventListener('error', e => {
+ resolve({status: req.status, json: JSON.parse(req.response)})
+ })
+ req.send(data)
+ })
+ }
+
+ const upload = useCallback(async () => {
+ if (!files || files.length === 0) return
+ const target = files[0]
+ if (target.size > LIMIT) return toast.error("File size too large! Max 2 mb.")
+ if (!target.name.includes(".xlsx")) return toast.error("Invalid file type!")
+ const {lat, long} = latLong;
+
+ setStatus("Uploading")
+
+ let file = new FormData()
+ file.append("file", target)
+ file.append("token", authState.token);
+ file.append("latitude", lat);
+ file.append("longitude", long);
+
+ let {status, json} = await query("/data/upload", file)
+ form.current.reset();
+
+ setStatus("Upload a file")
+ setLoad(0)
+
+ if (status !== 200) return toast.error(json.reason)
+ return toast.success("File successfully uploaded.")
+ }, [files, authState.token, form, latLong])
+
+ useEffect(() => {
+ (async () => upload())();
+ }, [files, authState.token, upload, authState, setLoad, setStatus])
+
+ return
+
+
Upload File Entries
+
+ Click the button to upload an .xlsx file
+
+
+
+
+
;
+}
+
export default FileUploadForm;
\ No newline at end of file
diff --git a/src/components/Home.js b/src/components/Home.js
index 3c9bc54..ae53880 100644
--- a/src/components/Home.js
+++ b/src/components/Home.js
@@ -1,30 +1,30 @@
-import React from 'react';
-import Navbar from "./Navbar";
-import {Link} from "react-router-dom";
-
-const background = require('../assets/background.png')
-
-function Home(props) {
- return (
-
-
-
-
-
Your Rainfall Data in One Place
-
-
Look through your rainfall data with ease.
-
-
-
- Start Uploading Data
-
-
-
-
- );
-}
-
+import React from 'react';
+import Navbar from "./Navbar";
+import {Link} from "react-router-dom";
+
+const background = require('../assets/background.png')
+
+function Home(props) {
+ return (
+
+
+
+
+
Your Rainfall Data in One Place
+
+
Look through your rainfall data with ease.
+
+
+
+ Start Uploading Data
+
+
+
+
+ );
+}
+
export default Home;
\ No newline at end of file
diff --git a/src/components/Login.js b/src/components/Login.js
index 9f21eba..1abb8c9 100644
--- a/src/components/Login.js
+++ b/src/components/Login.js
@@ -1,83 +1,83 @@
-import React, {useContext, useEffect, useState} from 'react';
-import Navbar from "./Navbar";
-import {Link} from "react-router-dom";
-import {Input} from "../custom";
-import {AuthContext} from "../App";
-import {login} from "../auth";
-import {toast} from "react-toastify";
-import {useForm} from "react-hook-form";
-
-const background = require('../assets/background.png')
-
-function Login(props) {
- const {register, handleSubmit, errors} = useForm();
- const [error, setError] = useState("")
- const [status, setStatus] = useState("Sign In")
- const {authState, authDispatch} = useContext(AuthContext)
- const [canLogin, setLogin] = useState(true)
- const [cooldown, setCooldown] = useState(0)
-
- function submit(data) {
- if (!canLogin) return;
- setError("");
- setStatus("Loading..");
- setLogin(false);
-
- (async () => {
- let {status, json} = await login(data.username.trim(), data.password.trim())
- setCooldown(setTimeout(() => setLogin(true), 1000));
-
- if (status !== 200) {
- setStatus("Sign In")
- return setError(json.reason)
- }
-
- toast.success("You have been logged in.")
- window.localStorage.setItem("authtoken", json.token)
- authDispatch({response: {token: json.token}})
- })()
-
- }
-
- useEffect(() => {
- let loc = new URLSearchParams(props.location.search).get("return")
- if (authState.loggedIn) props.history.push("/" + (loc ? loc : ""))
- return () => clearTimeout(cooldown)
- }, [props, authState, cooldown])
-
-
- return ;
-}
-
-export default Login;
-
+import React, {useContext, useEffect, useState} from 'react';
+import Navbar from "./Navbar";
+import {Link} from "react-router-dom";
+import {Input} from "../custom";
+import {AuthContext} from "../App";
+import {login} from "../auth";
+import {toast} from "react-toastify";
+import {useForm} from "react-hook-form";
+
+const background = require('../assets/background.png')
+
+function Login(props) {
+ const {register, handleSubmit, errors} = useForm();
+ const [error, setError] = useState("")
+ const [status, setStatus] = useState("Sign In")
+ const {authState, authDispatch} = useContext(AuthContext)
+ const [canLogin, setLogin] = useState(true)
+ const [cooldown, setCooldown] = useState(0)
+
+ function submit(data) {
+ if (!canLogin) return;
+ setError("");
+ setStatus("Loading..");
+ setLogin(false);
+
+ (async () => {
+ let {status, json} = await login(data.username.trim(), data.password.trim())
+ setCooldown(setTimeout(() => setLogin(true), 1000));
+
+ if (status !== 200) {
+ setStatus("Sign In")
+ return setError(json.reason)
+ }
+
+ toast.success("You have been logged in.")
+ window.localStorage.setItem("authtoken", json.token)
+ authDispatch({response: {token: json.token}})
+ })()
+
+ }
+
+ useEffect(() => {
+ let loc = new URLSearchParams(props.location.search).get("return")
+ if (authState.loggedIn) props.history.push("/" + (loc ? loc : ""))
+ return () => clearTimeout(cooldown)
+ }, [props, authState, cooldown])
+
+
+ return ;
+}
+
+export default Login;
+
diff --git a/src/components/Logout.js b/src/components/Logout.js
index c138518..20efc2f 100644
--- a/src/components/Logout.js
+++ b/src/components/Logout.js
@@ -1,20 +1,20 @@
-import React, {useContext, useEffect} from 'react';
-import {AuthContext} from "../App";
-import {logout} from "../auth";
-import {Link} from "react-router-dom";
-import {toast} from "react-toastify";
-
-function Logout(props) {
- const {authDispatch, authState} = useContext(AuthContext)
- useEffect(() => {
- if (authState.loggedIn) authDispatch({response: logout(authState.token)})
- else toast.warn("You are not logged in!")
- props.history.push("/")
- })
-
- return (
- You will soon be redirected. Click here if you are not.
- );
-}
-
-export default Logout;
+import React, {useContext, useEffect} from 'react';
+import {AuthContext} from "../App";
+import {logout} from "../auth";
+import {Link} from "react-router-dom";
+import {toast} from "react-toastify";
+
+function Logout(props) {
+ const {authDispatch, authState} = useContext(AuthContext)
+ useEffect(() => {
+ if (authState.loggedIn) authDispatch({response: logout(authState.token)})
+ else toast.warn("You are not logged in!")
+ props.history.push("/")
+ })
+
+ return (
+ You will soon be redirected. Click here if you are not.
+ );
+}
+
+export default Logout;
diff --git a/src/components/Navbar.js b/src/components/Navbar.js
index cc1baaa..c6b62ae 100644
--- a/src/components/Navbar.js
+++ b/src/components/Navbar.js
@@ -1,69 +1,78 @@
-import React, {useContext, useEffect, useState} from 'react';
-import {Link} from "react-router-dom";
-import {AuthContext} from "../App";
-
-const pathsCenter = [
- {name: "All Data", to: "/all", extra: ""},
- {name: "Upload Data", to: "/upload", extra: ""},
- {name: "Your Data", to: "/", extra: ""},
-]
-
-const pathsGuest = [
- {name: "Log In", to: "/login", extra: ""},
- {name: "Sign Up", to: "/register", extra: "rounded-full border border-white px-4 pb-1 pt-1 inline-block"},
-]
-
-const pathsLoggedIn = [
- {name: "Account", to: "/account", extra: ""},
- {name: "Sign Out", to: "/logout", extra: ""},
-]
-
-function Navbar({color = "text-white", hover="hover:border-white"}) {
- const [showDropdown, setDropdown] = useState(false)
- const [userPaths, setUserPaths] = useState(pathsGuest)
- const {authState} = useContext(AuthContext)
-
- useEffect(() => {
- if (authState.loggedIn) {
- setUserPaths(() => pathsLoggedIn)
- } else setUserPaths(() => pathsGuest)
- }, [authState.loggedIn])
-
- return
-
-
-
RainTrack
-
-
-
-
-
-
-
-
- ;
-}
-
-function LinksContainer(props) {
- return
- {props.paths.map(path =>
-
- {path.name}
-
- )}
-
-}
-
-export default Navbar;
-
+import React, {useContext, useEffect, useState} from 'react';
+import {Link} from "react-router-dom";
+import {AuthContext} from "../App";
+
+const pathsCenter = [
+ {name: "All Data", to: "/all", extra: ""},
+ {name: "Upload Data", to: "/upload", extra: ""},
+ {name: "My Data", to: "/mydata", extra: ""},
+]
+
+const pathsGuest = [
+ {name: "Log In", to: "/login", extra: ""},
+ {name: "Sign Up", to: "/register", extra: "rounded-full border border-white px-4 pb-1 pt-1 inline-block"},
+]
+
+const pathsLoggedIn = [
+ {name: "Account", to: "/account", extra: ""},
+ {name: "Logout", to: "/logout", extra: ""},
+]
+
+const pathsAdmin = [
+ {name: "Admin", to: "/admin", extra: ""},
+ {name: "Account", to: "/account", extra: ""},
+ {name: "Logout", to: "/logout", extra: ""},
+]
+
+function Navbar({color = "text-white", hover = "hover:border-white"}) {
+ const [showDropdown, setDropdown] = useState(false)
+ const [userPaths, setUserPaths] = useState(pathsGuest)
+ const {authState} = useContext(AuthContext)
+
+ useEffect(() => {
+ console.log(authState)
+ if (authState.loggedIn) {
+ if (authState.admin) setUserPaths(() => pathsAdmin)
+ else setUserPaths(() => pathsLoggedIn)
+
+ } else setUserPaths(() => pathsGuest)
+ }, [authState])
+
+ return
+
+
+
RainTrack
+
+
+
+
+
+
+
+
+ ;
+}
+
+function LinksContainer(props) {
+ return
+ {props.paths.map(path =>
+
+ {path.name}
+
+ )}
+
+}
+
+export default Navbar;
+
diff --git a/src/components/NotFound.js b/src/components/NotFound.js
index 1b0cde8..7e82e6f 100644
--- a/src/components/NotFound.js
+++ b/src/components/NotFound.js
@@ -1,28 +1,28 @@
-import React from 'react';
-import Navbar from "./Navbar";
-import {Link} from "react-router-dom";
-
-const background = require('../assets/background.png')
-
-function NotFound(props) {
- return (
-
-
-
-
-
- The page you requested was not found.
- Please confirm your URL or go back to the home page.
-
-
-
-
Go Home
-
-
-
- );
-}
-
+import React from 'react';
+import Navbar from "./Navbar";
+import {Link} from "react-router-dom";
+
+const background = require('../assets/background.png')
+
+function NotFound(props) {
+ return (
+
+
+
+
+
+ The page you requested was not found.
+ Please confirm your URL or go back to the home page.
+
+
+
+
Go Home
+
+
+
+ );
+}
+
export default NotFound;
\ No newline at end of file
diff --git a/src/components/Register.js b/src/components/Register.js
index 732ad75..6d96680 100644
--- a/src/components/Register.js
+++ b/src/components/Register.js
@@ -1,86 +1,86 @@
-import React, {useContext, useEffect, useState} from 'react';
-import Navbar from "./Navbar";
-import {Input} from "../custom";
-import {AuthContext} from "../App";
-import {registration} from "../auth";
-import {toast} from "react-toastify";
-import {useForm} from "react-hook-form";
-
-const background = require('../assets/background.png')
-
-function Register(props) {
- const {authState, authDispatch} = useContext(AuthContext)
- const {register, handleSubmit, errors} = useForm();
- const [error, setError] = useState("")
- const [status, setStatus] = useState("Register")
- const [canRegister, setRegister] = useState(true)
-
- function submit(data) {
- if (!canRegister) return;
- setError("")
- setStatus("Loading..");
- setRegister(false);
-
- (async () => {
- let {json, status} = await registration(data.user.trim(), data.email.trim(), data.pass.trim())
- setTimeout(() => setRegister(true), 1000);
- if (status !== 200) {
- setStatus("Register");
- return setError(json.reason)
- }
-
- toast.success("Your account has been registered.")
- window.localStorage.setItem("authtoken", json.token)
- authDispatch({response: {token: json.token, loggedIn: true}})
- })()
- }
-
-
- useEffect(() => {
- if (authState.loggedIn) props.history.push("/")
- }, [authState.loggedIn, props.history])
-
- return ;
-}
-
+import React, {useContext, useEffect, useState} from 'react';
+import Navbar from "./Navbar";
+import {Input} from "../custom";
+import {AuthContext} from "../App";
+import {registration} from "../auth";
+import {toast} from "react-toastify";
+import {useForm} from "react-hook-form";
+
+const background = require('../assets/background.png')
+
+function Register(props) {
+ const {authState, authDispatch} = useContext(AuthContext)
+ const {register, handleSubmit, errors} = useForm();
+ const [error, setError] = useState("")
+ const [status, setStatus] = useState("Register")
+ const [canRegister, setRegister] = useState(true)
+
+ function submit(data) {
+ if (!canRegister) return;
+ setError("")
+ setStatus("Loading..");
+ setRegister(false);
+
+ (async () => {
+ let {json, status} = await registration(data.user.trim(), data.email.trim(), data.pass.trim())
+ setTimeout(() => setRegister(true), 1000);
+ if (status !== 200) {
+ setStatus("Register");
+ return setError(json.reason)
+ }
+
+ toast.success("Your account has been registered.")
+ window.localStorage.setItem("authtoken", json.token)
+ authDispatch({response: {token: json.token, loggedIn: true}})
+ })()
+ }
+
+
+ useEffect(() => {
+ if (authState.loggedIn) props.history.push("/")
+ }, [authState.loggedIn, props.history])
+
+ return ;
+}
+
export default Register;
\ No newline at end of file
diff --git a/src/components/Upload.js b/src/components/Upload.js
index ab3b9d9..ce7614b 100644
--- a/src/components/Upload.js
+++ b/src/components/Upload.js
@@ -1,32 +1,32 @@
-import React, {useContext, useEffect} from 'react';
-import Navbar from "./Navbar";
-import {AuthContext} from "../App";
-import FileUploadForm from "./FileUploadForm";
-import FileManualForm from "./FileManualForm";
-
-const background = require('../assets/wave.png')
-
-
-function Upload(props) {
- const {authState} = useContext(AuthContext)
-
- useEffect(() => {
- if (!authState.loggedIn) props.history.push("/login?return=upload")
- }, [authState.loggedIn, props.history])
-
- return (
-
- );
-}
-
+import React, {useContext, useEffect} from 'react';
+import Navbar from "./Navbar";
+import {AuthContext} from "../App";
+import FileUploadForm from "./FileUploadForm";
+import FileManualForm from "./FileManualForm";
+
+const background = require('../assets/wave.png')
+
+
+function Upload(props) {
+ const {authState} = useContext(AuthContext)
+
+ useEffect(() => {
+ if (!authState.loggedIn) props.history.push("/login?return=upload")
+ }, [authState.loggedIn, props.history])
+
+ return (
+
+ );
+}
+
export default Upload;
\ No newline at end of file
diff --git a/src/components/ViewAll.js b/src/components/ViewAll.js
index 3533a77..acefeaa 100644
--- a/src/components/ViewAll.js
+++ b/src/components/ViewAll.js
@@ -1,109 +1,207 @@
-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";
-
-const background = require('../assets/wave.png')
-
-
-function ViewAll(props) {
- const {authState} = useContext(AuthContext)
- const [data, setData] = useState([{}])
- const [items, setItems] = useState(10)
-
- useEffect(() => {
- if (!authState.loggedIn) props.history.push("/login?return=all")
- }, [authState.loggedIn, props.history])
-
- const initiateData = useCallback(async () => {
- let {json, status} = await query("/data/query", {token: authState.token, validated: false}).catch(() => {
- return {status: 500, json: {reason: "The authentication server is offline!"}}
- })
- if (status !== 200) {
- return
- }
-
- let data = []
- json.entries.forEach((entry, index) => {
- const date = new Date(entry.date);
- const formattedDate = date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, "0") + "-" + date.getDate().toString().padStart(2, "0")
- const group = json.groups.find(gr => gr.id === entry.group_id)
- data[index] = {
- ID: entry.id,
- Date: formattedDate,
- Precipitation: entry.precipitation,
- Latitude: group.latitude,
- Longitude: group.longitude,
- Remarks: entry.remarks,
- }
- })
- setData(data)
-
- }, [setData, authState.token])
-
- useEffect(() => {
- (async () => {
- initiateData()
- })();
- }, [setData, initiateData])
-
- const styleConfig = {
- classNames: {
- Filter: "shadow appearance-none border rounded mb-1 py-2 px-3 mb-4 text-gray-700 leading-tight h-8",
- Table: "table table-bordered table-hover table-striped",
- 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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default ViewAll;
-
-const Layout = (setItems) => (({Table, Filter, Pagination}) => (
-
-
-
-
-
-
-
-
+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 (
+
+
+
+
+
+
+
setDateRange(date)}
+ options={{
+ dateFormat: "m-d-Y",
+ mode: "range",
+ wrap: true,
+ position: "below"
+ }}>
+
+
+ Select Date Range
+
+
+
+
{status}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Statistics
+
+ iqr(items)}>Interquartile Range
+ std(items)}>Standard Deviation
+ mean(items)}>Mean (Average)
+ median(items)}>Median
+ mode(items)}>Mode
+ max(items) - min(items)}>Range
+
+
+
+ );
+}
+
+const Stat = ({children, data, calculate}) => (
+
+
{children}
+
{data.length !== 0 && calculate(data.map(set => set.Precipitation)).toString()}
+
+)
+
+export default ViewAll;
+
+const Layout = (setItems) => (({Table, Filter, Pagination}) => (
+
+
+
+
+
+
+
+
+
))
\ No newline at end of file
diff --git a/src/components/ViewOwn.js b/src/components/ViewOwn.js
new file mode 100644
index 0000000..3c22994
--- /dev/null
+++ b/src/components/ViewOwn.js
@@ -0,0 +1,241 @@
+import React, {useCallback, useContext, useEffect, useReducer, 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 {toast} from "react-toastify";
+import {AiFillDelete, AiFillEdit} from "react-icons/ai";
+import Modal from "react-modal"
+import FileManualForm from "./FileManualForm";
+
+
+const background = require('../assets/wave.png')
+
+const defaultData = () => [{
+ ID: "No Data Found",
+ Date: "",
+ Precipitation: "",
+ Latitude: "",
+ Longitude: "",
+ Remarks: "",
+ Edit: "",
+}]
+
+function MyData({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([]);
+ const [editModal, setEditModal] = useState({open: false, data: {}})
+ const [updateData, dispatchEdit] = useReducer((state, action) => action, [])
+
+ 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=mydata")
+ }, [authState, history])
+
+ const deleteRow = async (data) => {
+ const {status, json} = await query("/data/delete", data).catch(e => {
+ console.debug(e)
+ return {status: 500, json: {reason: "The upload server is offline!"}}
+ })
+ console.debug("Delete Row Response", json)
+ return {status, json}
+ }
+
+ const initiateData = useCallback(async () => {
+ if (dateRange.length !== 2 || !authState.username) 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,
+ after_date: after,
+ before_date: before,
+ username: authState.username,
+ }).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,
+ Verified: group.validated ? "Yes" : "No",
+ Remarks: entry.remarks,
+ }
+ })
+ setData(newData)
+
+ before.setDate(before.getDate() - 1)
+ after.setDate(after.getDate() + 1)
+ window.history.pushState("Search", "Rain Track", "/mydata?after=" + formatDate(after) + "&before=" + formatDate(before))
+ }, [setData, authState.token, dateRange, authState.username])
+
+ 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"
+ }
+ }
+ useEffect(() => {
+ if (updateData.length !== 0) {
+ const row = data[updateData[1]]
+ if (!row) return
+ switch (updateData[0]) {
+ case 0:
+ setEditModal({open: true, data: row})
+ break;
+ case 1:
+ window.confirm("Are you sure you would like to delete this row?")
+ && (async () => {
+ let {json, status} = await deleteRow({token: authState.token, id: row.ID})
+ if (status !== 200) return toast.error(json.reason)
+ toast.success("Entry successfully deleted")
+ dispatchEdit([])
+ setData(data.filter(r => r !== row))
+ })()
+ break;
+ default:
+ break;
+ }
+ }
+ }, [updateData, data, authState])
+
+
+ return (
+
+
+
+
+
setDateRange(date)}
+ options={{
+ dateFormat: "m-d-Y",
+ mode: "range",
+ wrap: true,
+ position: "below"
+ }}>
+
+
+ Select Date Range
+
+
+
+
{status}
+
+
+
+
+
+
+
+
+
+
+
+
+ }/>
+
+
+
+ setEditModal({open: false, data: {ID: 0}})}
+ shouldCloseOnOverlayClick
+ isOpen={editModal.open}>
+
+
+
+
+
+ );
+}
+
+export default MyData;
+
+const Layout = (setItems) => (({Table, Filter, Pagination}) => (
+
+
+
+
+
+
+
+
+))
+
+const EditComponent = ({griddleKey, dispatch}) => {
+
+ return
+
dispatch([0, griddleKey])}>
+
dispatch([1, griddleKey])}>
+
+}
\ No newline at end of file
diff --git a/src/custom.js b/src/custom.js
index 81647fd..96bc725 100644
--- a/src/custom.js
+++ b/src/custom.js
@@ -1,14 +1,14 @@
-import React from "react";
-
-export function Input({name, type, errors = {}, mainClasses = "mb-2", ...props}) {
- const classes = () => "input " + (errors[name] ? "error" : "")
-
- return
-
-
props.setValue(e.target.value))}
- />
-
{errors[name]?.message}
-
+import React from "react";
+
+export function Input({name, type, errors = {}, mainClasses = "mb-2", ...props}) {
+ const classes = () => "input w-full h-10 " + (errors[name] ? "error" : "")
+
+ return
+
+
props.setValue(e.target.value))}
+ />
+
{errors[name]?.message}
+
}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index a7f9cb4..80e3890 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,9 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
+import ReactModal from "react-modal";
+ReactModal.setAppElement("#root")
ReactDOM.render(
diff --git a/src/styles/app.css b/src/styles/app.css
index af60b20..4d536b2 100644
--- a/src/styles/app.css
+++ b/src/styles/app.css
@@ -608,289 +608,6 @@ video {
}
}
-table {
- border-collapse: collapse;
-}
-
-caption {
- padding-top: .75rem;
- padding-bottom: .75rem;
- color: #6c757d;
- text-align: left;
- caption-side: bottom;
-}
-
-th {
- text-align: inherit;
-}
-
-.table {
- width: 100%;
- max-width: 100%;
- margin-bottom: 1rem;
- background-color: transparent;
-}
-
-.table td,.table th {
- padding: .75rem;
- vertical-align: top;
- border-top: 1px solid #dee2e6;
-}
-
-.table td {
- border-top: 1px solid #dee2e6;
-}
-
-.table thead th {
- vertical-align: bottom;
- border-bottom: 2px solid #dee2e6;
-}
-
-.table tbody+tbody {
- border-top: 2px solid #dee2e6;
-}
-
-.table .table {
- background-color: #fff;
-}
-
-.table-sm td,.table-sm th {
- padding: .3rem;
-}
-
-.table-bordered {
- border: 1px solid #dee2e6;
-}
-
-.table-bordered td,.table-bordered th {
- border: 1px solid #dee2e6;
-}
-
-.table-bordered thead td,.table-bordered thead th {
- border-bottom-width: 2px;
-}
-
-.table-striped tbody tr:nth-of-type(odd) {
- background-color: rgba(0,0,0,.05);
-}
-
-.table-hover tbody tr:hover {
- background-color: rgba(0,0,0,.075);
-}
-
-.table-primary,.table-primary>td,.table-primary>th {
- background-color: #b8daff;
-}
-
-.table-hover .table-primary:hover {
- background-color: #9fcdff;
-}
-
-.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th {
- background-color: #9fcdff;
-}
-
-.table-secondary,.table-secondary>td,.table-secondary>th {
- background-color: #d6d8db;
-}
-
-.table-hover .table-secondary:hover {
- background-color: #c8cbcf;
-}
-
-.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th {
- background-color: #c8cbcf;
-}
-
-.table-success,.table-success>td,.table-success>th {
- background-color: #c3e6cb;
-}
-
-.table-hover .table-success:hover {
- background-color: #b1dfbb;
-}
-
-.table-hover .table-success:hover>td,.table-hover .table-success:hover>th {
- background-color: #b1dfbb;
-}
-
-.table-info,.table-info>td,.table-info>th {
- background-color: #bee5eb;
-}
-
-.table-hover .table-info:hover {
- background-color: #abdde5;
-}
-
-.table-hover .table-info:hover>td,.table-hover .table-info:hover>th {
- background-color: #abdde5;
-}
-
-.table-warning,.table-warning>td,.table-warning>th {
- background-color: #ffeeba;
-}
-
-.table-hover .table-warning:hover {
- background-color: #ffe8a1;
-}
-
-.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th {
- background-color: #ffe8a1;
-}
-
-.table-danger,.table-danger>td,.table-danger>th {
- background-color: #f5c6cb;
-}
-
-.table-hover .table-danger:hover {
- background-color: #f1b0b7;
-}
-
-.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th {
- background-color: #f1b0b7;
-}
-
-.table-light,.table-light>td,.table-light>th {
- background-color: #fdfdfe;
-}
-
-.table-hover .table-light:hover {
- background-color: #ececf6;
-}
-
-.table-hover .table-light:hover>td,.table-hover .table-light:hover>th {
- background-color: #ececf6;
-}
-
-.table-dark,.table-dark>td,.table-dark>th {
- background-color: #c6c8ca;
-}
-
-.table-hover .table-dark:hover {
- background-color: #b9bbbe;
-}
-
-.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th {
- background-color: #b9bbbe;
-}
-
-.table-active,.table-active>td,.table-active>th {
- background-color: rgba(0,0,0,.075);
-}
-
-.table-hover .table-active:hover {
- background-color: rgba(0,0,0,.075);
-}
-
-.table-hover .table-active:hover>td,.table-hover .table-active:hover>th {
- background-color: rgba(0,0,0,.075);
-}
-
-.table .thead-dark th {
- color: #fff;
- background-color: #212529;
- border-color: #32383e;
-}
-
-.table .thead-light th {
- color: #495057;
- background-color: #e9ecef;
- border-color: #dee2e6;
-}
-
-.table-dark {
- color: #fff;
- background-color: #212529;
-}
-
-.table-dark td,.table-dark th,.table-dark thead th {
- border-color: #32383e;
-}
-
-.table-dark.table-bordered {
- border: 0;
-}
-
-.table-dark.table-striped tbody tr:nth-of-type(odd) {
- background-color: rgba(255,255,255,.05);
-}
-
-.table-dark.table-hover tbody tr:hover {
- background-color: rgba(255,255,255,.075);
-}
-
-.table-responsive {
- display: block;
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
-}
-
-.table-responsive>.table-bordered {
- border: 0;
-}
-
-.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th {
- border: 0;
-}
-
-@media (max-width:575.98px) {
- .table-responsive-sm {
- display: block;
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- }
-
- .table-responsive-sm>.table-bordered {
- border: 0;
- }
-}
-
-@media (max-width:767.98px) {
- .table-responsive-md {
- display: block;
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- }
-
- .table-responsive-sm>.table-bordered {
- border: 0;
- }
-}
-
-@media (max-width:991.98px) {
- .table-responsive-lg {
- display: block;
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- }
-
- .table-responsive-sm>.table-bordered {
- border: 0;
- }
-}
-
-@media (max-width:1199.98px) {
- .table-responsive-xl {
- display: block;
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- }
-
- .table-responsive-sm>.table-bordered {
- border: 0;
- }
-}
-
/* purgecss end ignore */
.space-y-0 > :not(template) ~ :not(template) {
@@ -8211,6 +7928,10 @@ th {
max-width: 92rem;
}
+.max-w-10xl {
+ max-width: 128rem;
+}
+
.max-w-full {
max-width: 100%;
}
@@ -15295,6 +15016,10 @@ th {
/*forms*/
+body {
+ overflow-x: hidden;
+}
+
.input {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
-webkit-appearance: none;
@@ -15302,7 +15027,6 @@ th {
appearance: none;
border-width: 1px;
border-radius: 0.25rem;
- width: 100%;
margin-bottom: 0.25rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
@@ -15312,7 +15036,6 @@ th {
color: #4a5568;
color: rgba(74, 85, 104, var(--text-opacity));
line-height: 1.25;
- height: 2.5rem;
vertical-align: text-top;
transition: all 0.3s;
}
@@ -15355,6 +15078,13 @@ th {
table-layout: auto;
}
+.flatpickr-weekday, .flatpickr-day {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */ /* Konqueror HTML */ /* Firefox */ /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
+}
+
@media (min-width: 640px) {
.sm\:space-y-0 > :not(template) ~ :not(template) {
--space-y-reverse: 0;
@@ -22674,6 +22404,10 @@ th {
max-width: 92rem;
}
+ .sm\:max-w-10xl {
+ max-width: 128rem;
+ }
+
.sm\:max-w-full {
max-width: 100%;
}
@@ -37076,6 +36810,10 @@ th {
max-width: 92rem;
}
+ .md\:max-w-10xl {
+ max-width: 128rem;
+ }
+
.md\:max-w-full {
max-width: 100%;
}
@@ -51478,6 +51216,10 @@ th {
max-width: 92rem;
}
+ .lg\:max-w-10xl {
+ max-width: 128rem;
+ }
+
.lg\:max-w-full {
max-width: 100%;
}
@@ -65880,6 +65622,10 @@ th {
max-width: 92rem;
}
+ .xl\:max-w-10xl {
+ max-width: 128rem;
+ }
+
.xl\:max-w-full {
max-width: 100%;
}
diff --git a/src/styles/tables.css b/src/styles/tables.css
new file mode 100644
index 0000000..4e7d5c4
--- /dev/null
+++ b/src/styles/tables.css
@@ -0,0 +1 @@
+table{border-collapse:collapse}caption{padding-top:0.75rem;padding-bottom:0.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:0.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table td{border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:0.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary > td,.table-primary > th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover > td,.table-hover .table-primary:hover > th{background-color:#9fcdff}.table-secondary,.table-secondary > td,.table-secondary > th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover > td,.table-hover .table-secondary:hover > th{background-color:#c8cbcf}.table-success,.table-success > td,.table-success > th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover > td,.table-hover .table-success:hover > th{background-color:#b1dfbb}.table-info,.table-info > td,.table-info > th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover > td,.table-hover .table-info:hover > th{background-color:#abdde5}.table-warning,.table-warning > td,.table-warning > th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover > td,.table-hover .table-warning:hover > th{background-color:#ffe8a1}.table-danger,.table-danger > td,.table-danger > th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover > td,.table-hover .table-danger:hover > th{background-color:#f1b0b7}.table-light,.table-light > td,.table-light > th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover > td,.table-hover .table-light:hover > th{background-color:#ececf6}.table-dark,.table-dark > td,.table-dark > th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover > td,.table-hover .table-dark:hover > th{background-color:#b9bbbe}.table-active,.table-active > td,.table-active > th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover > td,.table-hover .table-active:hover > th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive > .table-bordered{border:0}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm > .table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm > .table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm > .table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm > .table-bordered{border:0}}
\ No newline at end of file
diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css
index 8be88f9..37fce90 100644
--- a/src/styles/tailwind.css
+++ b/src/styles/tailwind.css
@@ -1,47 +1,61 @@
-/* purgecss start ignore */
-@tailwind base;
-@tailwind components;
-/* purgecss end ignore */
-
-@tailwind utilities;
-
-/*forms*/
-
-.input {
- @apply shadow appearance-none border rounded w-full mb-1 py-2 px-3 text-gray-700 leading-tight h-10 align-text-top;
- transition: all 0.3s;
-}
-
-.input.error {
- @apply border-red-500
-}
-
-.input:focus {
- @apply text-black outline-none;
- -webkit-box-shadow: 0px 0px 4px 3px #4f89ed;
- -moz-box-shadow: 0px 0px 4px 3px #4f89ed;
- box-shadow: 0px 0px 4px 3px #4f89ed;
-}
-
-.input.error:focus {
- -webkit-box-shadow: 0px 0px 4px 3px #f56565;
- -moz-box-shadow: 0px 0px 4px 3px #f56565;
- box-shadow: 0px 0px 4px 3px #f56565;
-}
-
-.input-error {
- @apply text-red-500 text-xs italic h-5
-
-}
-
-.Toastify__toast {
- padding-left: 24px !important;
- @apply text-sm;
-}
-
-
-/* table stuff */
-
-.griddle-table {
- @apply table-auto;
+/* purgecss start ignore */
+@tailwind base;
+@tailwind components;
+/* purgecss end ignore */
+
+@tailwind utilities;
+
+/*forms*/
+
+body {
+ overflow-x: hidden;
+}
+
+.input {
+ @apply shadow appearance-none border rounded mb-1 py-2 px-3 text-gray-700 leading-tight align-text-top;
+ transition: all 0.3s;
+}
+
+.input.error {
+ @apply border-red-500
+}
+
+.input:focus {
+ @apply text-black outline-none;
+ -webkit-box-shadow: 0px 0px 4px 3px #4f89ed;
+ -moz-box-shadow: 0px 0px 4px 3px #4f89ed;
+ box-shadow: 0px 0px 4px 3px #4f89ed;
+}
+
+.input.error:focus {
+ -webkit-box-shadow: 0px 0px 4px 3px #f56565;
+ -moz-box-shadow: 0px 0px 4px 3px #f56565;
+ box-shadow: 0px 0px 4px 3px #f56565;
+}
+
+.input-error {
+ @apply text-red-500 text-xs italic h-5
+
+}
+
+.Toastify__toast {
+ padding-left: 24px !important;
+ @apply text-sm;
+}
+
+
+/* table stuff */
+
+.griddle-table {
+ @apply table-auto;
+}
+
+.flatpickr-weekday, .flatpickr-day {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome and Opera */
}
\ No newline at end of file
diff --git a/tailwind.js b/tailwind.js
index 028cc40..e3e8ed8 100644
--- a/tailwind.js
+++ b/tailwind.js
@@ -345,6 +345,7 @@ module.exports = {
'5xl': '64rem',
'6xl': '72rem',
'8xl': '92rem',
+ '10xl': '128rem',
full: '100%',
...breakpoints(theme('screens')),
}),
@@ -737,6 +738,5 @@ module.exports = {
},
corePlugins: {},
plugins: [
- require('tailwindcss-tables')(),
],
}