diff --git a/.gitignore b/.gitignore index 417c6ce..d47c336 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -.vscode/ \ No newline at end of file +.vscode/ +.netlify diff --git a/package-lock.json b/package-lock.json index f372eb0..94480ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -392,6 +392,30 @@ "warning": "^4.0.1" } }, + "@material-ui/icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz", + "integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==", + "requires": { + "@babel/runtime": "7.0.0", + "recompose": "^0.29.0" + }, + "dependencies": { + "recompose": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz", + "integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==", + "requires": { + "@babel/runtime": "^7.0.0", + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "react-lifecycles-compat": "^3.0.2", + "symbol-observable": "^1.0.4" + } + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -9325,6 +9349,27 @@ "object-visit": "^1.0.0" } }, + "material-ui-icons": { + "version": "1.0.0-beta.36", + "resolved": "https://registry.npmjs.org/material-ui-icons/-/material-ui-icons-1.0.0-beta.36.tgz", + "integrity": "sha512-7rS6b2EV5QXCB/gTi/Ac9Wbxd+h9EZv1Td3rLLJe4IER8mVHRgdqZccB3EsjW2DrJ7opdY1+8X3/vyrS7CQNpg==", + "requires": { + "recompose": "^0.26.0" + }, + "dependencies": { + "recompose": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz", + "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==", + "requires": { + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "symbol-observable": "^1.0.4" + } + } + } + }, "math-expression-evaluator": { "version": "1.2.17", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", @@ -9524,6 +9569,11 @@ "integrity": "sha512-9DITV2YEMcw7XojdfvGl3gDD8J9QjZTJ7ZOUuSAkP+F3T6rDbzMJuPktxptsdHYEvZcmXrCD3LMOhdSAEq6zKA==", "optional": true }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -11866,6 +11916,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-quiz-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/react-quiz-component/-/react-quiz-component-0.2.0.tgz", + "integrity": "sha512-oWUos0A4NtYNBNoSqAbjjpZC9ndEuw5SebYRDhX2EUv4I41iqWWVNMbY2Pu7qT6I7MuELfWHrpV+5p0XwaChaQ==" + }, "react-router": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", diff --git a/package.json b/package.json index e2ef165..de0dbff 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,16 @@ "private": true, "dependencies": { "@material-ui/core": "^3.1.0", + "@material-ui/icons": "^3.0.1", "firebase": "^5.5.2", "firebase-admin": "^6.0.0", "flamelink": "^0.19.2", "google-maps-react": "^2.0.2", + "material-ui-icons": "^1.0.0-beta.36", + "moment": "^2.22.2", "react": "^16.5.1", "react-dom": "^16.5.1", + "react-quiz-component": "0.2.0", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "react-scripts": "1.1.5" diff --git a/public/index.html b/public/index.html index ebb2c8a..d949bd5 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,17 @@ + + + + + @@ -30,15 +41,16 @@ You need to enable JavaScript to run this app.
- + + diff --git a/public/quizimages/question1.jpg b/public/quizimages/question1.jpg new file mode 100644 index 0000000..8ce9a50 Binary files /dev/null and b/public/quizimages/question1.jpg differ diff --git a/public/quizimages/question2.jpg b/public/quizimages/question2.jpg new file mode 100644 index 0000000..8f6d931 Binary files /dev/null and b/public/quizimages/question2.jpg differ diff --git a/public/quizimages/question3.jpg b/public/quizimages/question3.jpg new file mode 100644 index 0000000..c518c5e Binary files /dev/null and b/public/quizimages/question3.jpg differ diff --git a/public/quizimages/question4.jpg b/public/quizimages/question4.jpg new file mode 100644 index 0000000..0e52c59 Binary files /dev/null and b/public/quizimages/question4.jpg differ diff --git a/public/quizimages/question5.jpg b/public/quizimages/question5.jpg new file mode 100644 index 0000000..c49d92f Binary files /dev/null and b/public/quizimages/question5.jpg differ diff --git a/readme.md b/readme.md index 98663d1..f5db757 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,13 @@ # Marten Application -============ ### Background -Given some recent research into the American marten, there is a need for a web application that is able to document marten sightings. Graduate research has produced a model that predicts habitat suitability for the norther Lower Peninsula, but a lack of anything besides anecdotal evidence makes testing the model difficult. This application would allow for "citizen science". This is the outsourcing of research efforts to the public. This would allow people to log marten sightings for research purposes. The project will be a web platform and certain groups including hunters, hunting club members, wildlife managers and state park personnel will be targeted for citizen science. This application will hopefully help bolster research efforts for the American marten. +Given some recent research into the American marten, there is a need for a web application that is able to document marten sightings. Graduate research has produced a model that predicts habitat suitability for the northern Lower Peninsula, but a lack of anything besides anecdotal evidence makes testing the model difficult. This application would allow for "citizen science". This is the outsourcing of research efforts to the public. This would allow people to log marten sightings for research purposes. The project will be a web platform and certain groups including hunters, hunting club members, wildlife managers and state park personnel will be targeted for citizen science. This application will hopefully help bolster research efforts for the American marten. The application will allow for users to post marten sightings. They can attach photos and make comments on said photos. They also can include the location where the sighting occurred and rank their confidence in the sighting. This will all be stored in some type of database that can be queried for research purposes. We also will integrate a map API to allow for users to see where marten sightings have occurred. +### URL + +[Marten Tracker](https://marten-application.netlify.com/ "Click here to see the application in action.") + ### Intended Features * Ability to log marten sightings. * Include type of sighting on marten sighting post. @@ -16,8 +19,7 @@ The application will allow for users to post marten sightings. They can attach p ### Technologies * ReactJS -* PHP * Firebase * Google API -* Amazon AWS -* Possibly Grand Valley servers +* Netlify +* Flamelink CMS diff --git a/src/App.css b/src/App.css index 9dd2624..9494272 100644 --- a/src/App.css +++ b/src/App.css @@ -2,11 +2,15 @@ body { margin: 0; } -.report-google-map-container > div { - height: 92% !important; - width: 50% !important; +@media (min-width: 600px) { + .sighting-google-map-container > div { + width: calc(100% - 240px) !important; + height: calc(100% - 64px) !important; + } } -.sighting-google-map-container > div { - height: 92% !important; +@media (min-width: 960px) { + .report-google-map-container > div { + width: calc(100% - 50% - 120px) !important; + } } \ No newline at end of file diff --git a/src/components/Main.js b/src/components/Main.js index 2259d17..62336af 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -1,75 +1,209 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; +import Drawer from '@material-ui/core/Drawer'; import AppBar from '@material-ui/core/AppBar'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; +import Toolbar from '@material-ui/core/Toolbar'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Hidden from '@material-ui/core/Hidden'; +import Divider from '@material-ui/core/Divider'; +import MenuIcon from '@material-ui/icons/Menu'; +import HomeIcon from '@material-ui/icons/Home'; +import AssignmentIcon from '@material-ui/icons/Assignment'; +import MapIcon from '@material-ui/icons/Map'; +import ListIcon from '@material-ui/icons/List'; +import SlideshowIcon from '@material-ui/icons/Slideshow'; import Home from '../pages/Home'; import ViewMap from '../pages/ViewMap'; -import Quiz from '../pages/Quiz'; +import Quiz from '../pages/QuizPage'; import SightingList from '../pages/SightingList'; import Report from '../pages/Report'; -import Info from '../pages/Info'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import Collapse from '@material-ui/core/Collapse'; -function TabContainer(props) { - return ( - - {props.children} - - ); -} - -TabContainer.propTypes = { - children: PropTypes.node.isRequired, -}; +const drawerWidth = 240; const styles = theme => ({ root: { + display: 'flex', + }, + drawer: { + [theme.breakpoints.up('sm')]: { + width: drawerWidth, + flexShrink: 0, + }, + }, + nested: { + paddingLeft: theme.spacing.unit * 4, + }, + appBar: { + marginLeft: drawerWidth, + [theme.breakpoints.up('sm')]: { + width: `calc(100% - ${drawerWidth}px)`, + }, + }, + menuButton: { + marginRight: 20, + [theme.breakpoints.up('sm')]: { + display: 'none', + }, + }, + toolbar: theme.mixins.toolbar, + drawerPaper: { + width: drawerWidth, + }, + content: { flexGrow: 1, - backgroundColor: theme.palette.background.paper, + width: '60%' }, }); -class SimpleTabs extends React.Component { +class ResponsiveDrawer extends React.Component { state = { - value: 0, + mobileOpen: false, + key: '', + open: false }; - handleChange = (event, value) => { - this.setState({ value }); - }; + handleDrawerToggle = () => { + this.setState(state => ({ mobileOpen: !state.mobileOpen })); + } + + handleClick = () => { + this.setState(state => ({ open: !state.open })); + } + + nav = (text) => { + this.setState({ + key: text + }) + } render() { - const { classes } = this.props; - const { value } = this.state; + const { classes, theme } = this.props; - return ( -
- - - - - - - - - - - {value === 0 && } - {value === 1 && } - {value === 2 && } - {value === 3 && } - {value === 4 && } - {value === 5 && } -
- ); + const drawer = ( +
+
+ + + this.nav('Home')}> + + + + this.nav('Report')}> + + + + this.nav('Map')}> + + + + this.nav('List')}> + + + + + + + + + {this.state.open ? : } + + + + this.nav('Easy-Quiz')}> + + + this.nav('Medium-Quiz')}> + + + this.nav('Hard-Quiz')}> + + + + + + +
+ ); + + return ( +
+ + + + + + + + The American Marten + + + + +
+
+ {this.state.key === 'Home' && } + {this.state.key === 'Report' && } + {this.state.key === 'Map' && } + {this.state.key === 'List' && } + {this.state.key === 'Easy-Quiz' && } + {this.state.key === 'Medium-Quiz' && } + {this.state.key === 'Hard-Quiz' && } +
+
+ ); } } -SimpleTabs.propTypes = { +ResponsiveDrawer.propTypes = { classes: PropTypes.object.isRequired, + // Injected by the documentation to work in an iframe. + // You won't need it on your project. + container: PropTypes.object, + theme: PropTypes.object.isRequired, }; -export default withStyles(styles)(SimpleTabs); - +export default withStyles(styles, { withTheme: true })(ResponsiveDrawer); \ No newline at end of file diff --git a/src/components/QuizGame.js b/src/components/QuizGame.js new file mode 100644 index 0000000..1339978 --- /dev/null +++ b/src/components/QuizGame.js @@ -0,0 +1,297 @@ +import React, { Fragment } from 'react'; +import Grid from '@material-ui/core/Grid'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Quiz from 'react-quiz-component'; +import { Typography } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import RefreshIcon from '@material-ui/icons/Refresh'; + +// Style for the tabs. +const styles = theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, + button: { + margin: theme.spacing.unit, + }, + rightIcon: { + marginLeft: theme.spacing.unit, + }, +}); + +class QuizGame extends React.Component { + + /** + * Shuffles a given array. + * @param {*} array The array passed in. + */ + shuffleArray = array => { + let shuffled = array; + + var j, x, i; + + for (i = shuffled.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + x = shuffled[i]; + shuffled[i] = shuffled[j]; + shuffled[j] = x; + } + + return shuffled; + } + + reset = () => { + this.setState({ + difficulty: this.pickDifficulty(this.props.difficulty), + key: Math.random() + }); + } + + easy = { + "quizTitle": "Trail Cam Quiz: Easy", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + medium = { + "quizTitle": "Trail Cam Quiz: Medium", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + hard = { + "quizTitle": "Trail Cam Quiz: Hard", + "questions": [ + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Black bear", + "Common wombat", + "Raccoon", + "White-tailed deer" + ], + "correctAnswer": "1" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American beaver", + "Muskrat", + "Porcupine", + "Woodchuck" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American badger", + "Raccoon", + "Striped skunk", + "Virginia opossum" + ], + "correctAnswer": "2" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "Eastern fox squirrel", + "Eastern gray squirrel", + "Red squirrel", + "Southern flying squirrel" + ], + "correctAnswer": "3" + }, + { + "question": What animal is this?

, + "questionType": "text", + "answers": [ + "American Crow", + "Black Vulture", + "Turkey Vulture", + "Northern Raven" + ], + "correctAnswer": "3" + }, + ] + } + + /** + * This function returns the + * quiz data based on the difficulty + * level passed into it. + * @param {*} difficulty The difficulty setting passed in. + */ + pickDifficulty = difficulty => { + let level + + switch (difficulty) { + case 'Easy': + this.easy.questions = this.shuffleArray(this.easy.questions) + level = this.easy + break + case 'Medium': + this.medium.questions = this.shuffleArray(this.medium.questions) + level = this.medium + break + case 'Hard': + this.hard.questions = this.shuffleArray(this.hard.questions) + level = this.hard + break + default: + break + } + + return level + } + + // The state of the component. + state = { + difficulty: this.pickDifficulty(this.props.difficulty), + key: Math.random() + } + + // Renders the quiz component. + render() { + const { classes } = this.props; + + return ( + // Tabs +
+ + + + + + + + +
+ ); + } +} + +QuizGame.propTypes = { + classes: PropTypes.object.isRequired +}; + +export default withStyles(styles)(QuizGame); \ No newline at end of file diff --git a/src/components/ReportForm.js b/src/components/ReportForm.js index ac72000..7d50473 100644 --- a/src/components/ReportForm.js +++ b/src/components/ReportForm.js @@ -1,9 +1,13 @@ -import React, {Fragment} from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import Grid from '@material-ui/core/Grid'; import { withStyles } from '@material-ui/core/styles'; import MenuItem from '@material-ui/core/MenuItem'; import TextField from '@material-ui/core/TextField'; +import CheckCircleIcon from '@material-ui/icons/CheckCircle'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; import Button from '@material-ui/core/Button'; import firebase from '../firebase.js'; import GoogleMap from '../components/ReportMap'; @@ -16,7 +20,7 @@ import GoogleMap from '../components/ReportMap'; const styles = theme => ({ container: { display: 'flex', - flexWrap: 'wrap', + flexWrap: 'wrap' }, textField: { marginLeft: theme.spacing.unit * 2, @@ -33,6 +37,17 @@ const styles = theme => ({ dense: { marginTop: 30, }, + close: { + padding: theme.spacing.unit / 2, + }, + icon: { + fontSize: 20, + marginRight: theme.spacing.unit, + }, + message: { + display: 'flex', + alignItems: 'center', + }, menu: { width: 200, }, @@ -40,22 +55,15 @@ const styles = theme => ({ /** * Function for formatting the - * date as a string that - * Material UI can use. We'll - * also store the date like - * this string in the database. + * year as a string that + * Material UI can use. * @param {*} date, Date passed in. */ -function formatDate(date) { - var d = new Date(date), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); +function getYear(date) { + var d = new Date(date), + year = d.getFullYear(); - if (month.length < 2) month = '0' + month; - if (day.length < 2) day = '0' + day; - - return [year, month, day].join('-'); + return year; } /** @@ -90,33 +98,117 @@ const sightingTypes = [ }, ]; +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const monthTypes = [ + { + value: '01', + label: 'January', + }, + { + value: '02', + label: 'February', + }, + { + value: '03', + label: 'March', + }, + { + value: '04', + label: 'April', + }, + { + value: '05', + label: 'May', + }, + { + value: '06', + label: 'June', + }, + { + value: '07', + label: 'July', + }, + { + value: '08', + label: 'August', + }, + { + value: '09', + label: 'September', + }, + { + value: '10', + label: 'October', + }, + { + value: '11', + label: 'November', + }, + { + value: '12', + label: 'December', + }, +]; + +/** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. +*/ +const timeTypes = [ + { + value: 'unknown', + label: 'Unknown', + }, + { + value: 'morning', + label: 'Morning', + }, + { + value: 'midday', + label: 'Midday', + }, + { + value: 'evening', + label: 'Evening', + }, + { + value: 'night', + label: 'Night', + }, +]; + /** * Levels of confidence. Label is what is * viewed in the application, value is * what is stored in the database. */ const confidenceLevels = [ - { - value: '1', - label: '1 - Strongly unconfident', - }, - { - value: '2', - label: '2 - Unconfident', - }, - { - value: '3', - label: '3 - Somewhat confident', - }, - { - value: '4', - label: '4 - Confident', - }, - { - value: '5', - label: '5 - Very confident', - }, - ]; + { + value: '1', + label: '1 - Strongly disagree', + }, + { + value: '2', + label: '2 - Disagree', + }, + { + value: '3', + label: '3 - Neutral', + }, + { + value: '4', + label: '4 - Agree', + }, + { + value: '5', + label: '5 - Strongly agree', + }, +]; /** * The form component. @@ -136,13 +228,15 @@ class ReportForm extends React.Component { * State of form components. */ state = { - date: formatDate(new Date()), - time: '00:00', + month: '01', + year: getYear(new Date()), + time: 'unknown', type: 'visual', confidence: '1', desc: '', lat: '', - lng: '' + lng: '', + open: false }; /** @@ -155,17 +249,28 @@ class ReportForm extends React.Component { }); }; + /** + * Handles closing the toast. + */ + handleClose = (event, reason) => { + if (reason === 'clickaway') { + return; + } + + this.setState({ open: false }); + }; + /* * Get the coordinates * */ - getCoordinates = (lat,lng) => { + getCoordinates = (lat, lng) => { let latitude = lat; let longitude = lng; this.setState({ - lat: latitude, - lng: longitude + lat: latitude, + lng: longitude }); } @@ -181,7 +286,7 @@ class ReportForm extends React.Component { const sighting = { type: this.state.type, confidence: this.state.confidence, - date: this.state.date, + date: this.state.year + '-' + this.state.month, time: this.state.time, desc: this.state.desc, lat: this.state.lat, @@ -189,16 +294,18 @@ class ReportForm extends React.Component { } sightingsRef.push(sighting); this.setState({ - date: formatDate(new Date()), - time: '00:00', + year: getYear(new Date()), + month: '01', + time: 'unknown', type: 'visual', confidence: '1', desc: '', lat: '', - lng: '' + lng: '', + open: true }); }; - + /** * The render method for this component. @@ -210,129 +317,184 @@ class ReportForm extends React.Component { * The actual form. */ return ( - -
- - - - - - {sightingTypes.map(option => ( - - {option.label} - - ))} - - - - - - {confidenceLevels.map(option => ( - - {option.label} - - ))} - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + {sightingTypes.map(option => ( + + {option.label} + + ))} + - - + + + + {confidenceLevels.map(option => ( + + {option.label} + + ))} + + + + + + {timeTypes.map(option => ( + + {option.label} + + ))} + + + + + + {monthTypes.map(option => ( + + {option.label} + + ))} + + + + + + + + + + + + + + +
+ + + + + + + Report received.} + action={[ + + + , + ]} + /> +
); - } + } } ReportForm.propTypes = { diff --git a/src/components/ReportMap.js b/src/components/ReportMap.js index 5d697bf..882a49f 100644 --- a/src/components/ReportMap.js +++ b/src/components/ReportMap.js @@ -96,9 +96,6 @@ export class MapContainer extends Component { render() { - // TODO: This line is used by the custom marker icon - //const { google } = this.props; - return ( // Render the Google Map, Marker, and InfoWindow components
@@ -119,12 +116,6 @@ export class MapContainer extends Component { onClick = { this.onMarkerClick } title = { 'You are here' } name = { '' } - // FIXME: fix custom icon - // icon={{ - // url: "../images/marten-icon.png", - // anchor: new google.maps.Point(32,32), - // scaledSize: new google.maps.Size(64,64) - // }} /> { this.setState({ - myLatLng: { - lat: position.coords.latitude, - lng: position.coords.longitude - } + myLatLng: { + lat: position.coords.latitude, + lng: position.coords.longitude } + } ); }) } else { // If browser doesn't support geolocation or if user does not allow it, // center map on Grand Rapids, Michigan this.setState({ - myLatLng: { - lat: 42.9634, - lng: 85.6681 - } + myLatLng: { + lat: 42.9634, + lng: 85.6681 } + } ); } } @@ -85,6 +208,10 @@ export class MapContainer extends Component { } } + formatDate = date => { + return (moment(date, "YYYY-MM").format("MMMM YYYY").toString()) + } + // Set the state of the component to contain user coordinates and initial // marker and info window information state = { @@ -101,55 +228,55 @@ export class MapContainer extends Component { render() { return ( // Render the Google Map, Marker, and InfoWindow components -
+
+ style={mapStyles} + google={this.props.google} + initialCenter={this.state.myLatLng} + center={this.state.myLatLng} + defaultZoom={15} + onClick={this.onMapClick} > - - { this.state.sightings.map((sighting) => { + {this.state.sightings.map((sighting) => { return ( Confidence: {getConfidence(sighting.confidence)}} + date = {Date: {this.formatDate(sighting.date)}} + time = {Time: {getTime(sighting.time)}} + description = {Description: {sighting.desc}} /> ) })} + marker={this.state.activeMarker} + visible={this.state.showingInfoWindow} > - - { this.state.selectedPlace.type } + + {this.state.selectedPlace.type} - - { this.state.selectedPlace.confidence } + + {this.state.selectedPlace.confidence} - - { this.state.selectedPlace.date } + + {this.state.selectedPlace.date} - - { this.state.selectedPlace.time } + + {this.state.selectedPlace.time} - - { this.state.selectedPlace.description } + + {this.state.selectedPlace.description} diff --git a/src/components/ViewSightings.js b/src/components/ViewSightings.js new file mode 100644 index 0000000..822cf8e --- /dev/null +++ b/src/components/ViewSightings.js @@ -0,0 +1,232 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import Grid from '@material-ui/core/Grid'; +import TextField from '@material-ui/core/TextField' +import Button from '@material-ui/core/Button' +import Paper from '@material-ui/core/Paper' +import { withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import firebase from '../firebase.js'; + +const styles = theme => ({ + root: { + ...theme.mixins.gutters(), + paddingTop: theme.spacing.unit * 2, + paddingBottom: theme.spacing.unit * 2, + }, + container: { + display: 'flex', + flexWrap: 'wrap', + }, + textField: { + marginLeft: theme.spacing.unit * 2, + marginRight: theme.spacing.unit * 2, + marginTop: theme.spacing.unit * 2, + flexBasis: 280, + }, + button: { + marginLeft: theme.spacing.unit * 3, + marginRight: theme.spacing.unit * 3, + marginTop: theme.spacing.unit * 3, + }, + paper: { + marginLeft: theme.spacing.unit * 2, + marginRight: theme.spacing.unit, + marginTop: theme.spacing.unit * 2, + }, + }); + + /** + * Types of sightings. Label is what is + * viewed in the application, value is + * what is stored in the database. + */ + const sightingTypes = [ + { + value: 'visual', + label: 'Visual', + }, + { + value: 'roadkill', + label: 'Roadkill', + }, + { + value: 'trapped', + label: 'Trapped', + }, + { + value: 'viewed_tracks', + label: 'Viewed Tracks', + }, + { + value: 'photo', + label: 'Photo', + }, + { + value: 'other', + label: 'Other', + }, + ]; + + /** + * Levels of confidence. Label is what is + * viewed in the application, value is + * what is stored in the database. + */ + const confidenceLevels = [ + { + value: '1', + label: '1 - Strongly unconfident', + }, + { + value: '2', + label: '2 - Unconfident', + }, + { + value: '3', + label: '3 - Somewhat confident', + }, + { + value: '4', + label: '4 - Confident', + }, + { + value: '5', + label: '5 - Very confident', + }, + ]; + + /** + * Gets formatted confidence value. + */ + function getConfidence(item) { + for (var i = 0; i < confidenceLevels.length; i++) { + if (confidenceLevels[i].value === item) { + return confidenceLevels[i].label; + } + } + + } + + /** + * Gets formatted type value. + */ + function getType(item) { + for (var i = 0; i < sightingTypes.length; i++) { + if (sightingTypes[i].value === item) { + return sightingTypes[i].label; + } + } + } + +class ViewSightings extends React.Component { + constructor(props){ + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + } + + state = { + id: '', + type: 'N/A', + confidence: 'N/A', + date: 'N/A', + time: 'N/A', + desc: 'N/A', + lat: 'N/A', + lng: 'N/A' + }; + + + /** + * Handles state change. + */ + handleChange = name => event => { + this.setState({ + [name]: event.target.value, + }); + }; + + /** + * Handles submit on search. + */ + handleSubmit(e){ + e.preventDefault(); + const itemSighting = firebase.database().ref("sightings/" + this.state.id); + itemSighting.once("value").then((snapshot) => { + // Die if there's no data for that ID. + if (!snapshot.exists()) { + return; + } + + let data = snapshot.val(); + + this.setState({ + date: data.date, + time: data.time, + type: getType(data.type), + confidence: getConfidence(data.confidence), + desc: data.desc, + lat: data.lat, + lng: data.lng + }); + }); + }; + + render(){ + const { classes } = this.props; + return ( + /** + * The below houses the search + * and submit button along with + * the sighting information + * it pulls. + */ + + +
+ + + + + + +
+ + + + Sighting + + + Type: {this.state.type} {
} + Confidence: {this.state.confidence} {
} + Date: {this.state.date} {
} + Time: {this.state.time} {
} + Latitude: {this.state.lat} {
} + Longitude: {this.state.lng} {
} + Description: {this.state.desc} +
+
+
+
+
+ ) + + } + +} + +ViewSightings.propTypes = { + classes: PropTypes.object.isRequired, + }; + +export default withStyles(styles)(ViewSightings); \ No newline at end of file diff --git a/src/firebase.js b/src/firebase.js index 51e4393..e9b517f 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,5 +1,6 @@ import firebase from 'firebase/app'; import 'firebase/database'; +import 'firebase/storage'; const config = { apiKey: "AIzaSyAYf9AbeYwLY892NRiQfn0AMtG9xIFAJbo", diff --git a/src/pages/Quiz.js b/src/pages/Quiz.js deleted file mode 100644 index 28b9f15..0000000 --- a/src/pages/Quiz.js +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Component } from 'react'; -import Typography from '@material-ui/core/Typography'; - -class Quiz extends Component { - render() { - return ( - - Quiz - - ); - } -} - -export default Quiz; diff --git a/src/pages/QuizPage.js b/src/pages/QuizPage.js new file mode 100644 index 0000000..9422b7b --- /dev/null +++ b/src/pages/QuizPage.js @@ -0,0 +1,13 @@ +import React, { Component } from 'react'; +import QuizGame from '../components/QuizGame'; + + +class QuizPage extends Component { + render() { + return ( + + ); + } +} + +export default QuizPage; diff --git a/src/pages/SightingList.js b/src/pages/SightingList.js index d6d8594..9d035b9 100644 --- a/src/pages/SightingList.js +++ b/src/pages/SightingList.js @@ -1,11 +1,12 @@ import React, { Component } from 'react'; +import ViewSightings from '../components/ViewSightings.js'; import Typography from '@material-ui/core/Typography'; class Sighting extends Component { render() { return ( - Sightings + ); }