From e724aa90ded2e3eeaf860b509dc5bf1ac66c2f85 Mon Sep 17 00:00:00 2001 From: Ilgaz Er Date: Sun, 26 Nov 2023 18:44:17 +0300 Subject: [PATCH 1/3] Created annotations type of map item --- resq/frontend/src/components/DisasterMap.js | 38 ++-- resq/frontend/src/components/MapIcons.js | 28 +++ resq/frontend/src/pages/ListCards.js | 208 ++++++++++++++++++++ 3 files changed, 247 insertions(+), 27 deletions(-) create mode 100644 resq/frontend/src/components/MapIcons.js create mode 100644 resq/frontend/src/pages/ListCards.js diff --git a/resq/frontend/src/components/DisasterMap.js b/resq/frontend/src/components/DisasterMap.js index c394fd8e..fb178f92 100644 --- a/resq/frontend/src/components/DisasterMap.js +++ b/resq/frontend/src/components/DisasterMap.js @@ -1,7 +1,8 @@ import * as React from 'react'; -import {useState} from 'react'; +import {useEffect, useState} from 'react'; import {Map, Marker, ZoomControl} from 'pigeon-maps'; import {type_colors} from "../Colors"; +import {AnnotationIcon, MarkerIcon} from "./MapIcons"; const MAPBOX_TOKEN = "pk.eyJ1IjoiaWxnYXplciIsImEiOiJjbG80Nzg4Z3gwMjZ4MmtxcTR3bGI5enR3In0.QdNftxZYpJ79K0M0DfYHUw" const MAPBOX_STYLE = "mapbox/streets-v12" @@ -15,30 +16,13 @@ function mapboxProvider(x, y, z, dpr) { } -const MarkerIcon = ({color}) => ( - - - - - - -); - - -export default function DisasterMap({onPointSelected, markers = []}) { +export default function DisasterMap({onPointSelected, markers = [], center}) { const [zoom, setZoom] = useState(6.5); - const [center, setCenter] = useState([39, 34.5]) + const [mapCenter, setMapCenter] = useState([39, 34.5]) + + useEffect(() => { + center && setMapCenter(center); + }, [center]) const renderMarker = (marker) => { return ( @@ -51,7 +35,7 @@ export default function DisasterMap({onPointSelected, markers = []}) { event.preventDefault() }} > - {} + {marker.icon ? : } ); }; @@ -65,14 +49,14 @@ export default function DisasterMap({onPointSelected, markers = []}) { dprs={[1, 2]} //height={window.innerHeight - (ref?.y || 48)} //width={ref?.offsetWidth || 100} - center={center} + center={mapCenter} zoom={zoom} onClick={({event}) => { onPointSelected(null); event.preventDefault() }} onBoundsChanged={({center, zoom}) => { - setCenter(center) + setMapCenter(center) setZoom(zoom) }}> diff --git a/resq/frontend/src/components/MapIcons.js b/resq/frontend/src/components/MapIcons.js new file mode 100644 index 00000000..ff75041d --- /dev/null +++ b/resq/frontend/src/components/MapIcons.js @@ -0,0 +1,28 @@ +import {Cancel, LocalFireDepartment, LocalHospital} from "@mui/icons-material"; +import * as React from "react"; + +export const AnnotationIcon = ({icon, color}) => + ({ + fire: , + health: , + closed: + })[icon] +export const MarkerIcon = ({color}) => ( + + + + + + +); \ No newline at end of file diff --git a/resq/frontend/src/pages/ListCards.js b/resq/frontend/src/pages/ListCards.js new file mode 100644 index 00000000..ab1fc4ce --- /dev/null +++ b/resq/frontend/src/pages/ListCards.js @@ -0,0 +1,208 @@ +import * as React from "react"; +import {useEffect, useState} from "react"; +import axios from "axios"; +import {Card, CardActions, CardContent, CardHeader, Collapse, IconButton} from "@mui/material"; +import Avatar from "@mui/material/Avatar"; +import {type_colors} from "../Colors"; +import Typography from "@mui/material/Typography"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import styled from "styled-components"; +import {AnnotationIcon} from "../components/MapIcons"; + +const ExpandMore = styled(IconButton)` + transform: ${({expand}) => !expand ? 'rotate(0deg)' : 'rotate(180deg)'}; + margin-left: auto; + transition: transform 150ms; +` +const OffsetActions = styled(CardActions)` + transform: translate(-8px, -28px); + height: 0; + padding: 0; +` + +async function getAddress(latitude, longitude) { + try { + const response = await axios.get( + `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=AIzaSyCehlfJwJ-V_xOWZ9JK3s0rcjkV2ga0DVg` + ); + + return response.data.results[0]?.formatted_address || 'Unknown'; + } catch (error) { + console.error('Error fetching location name:', error); + } +} + + +export const AnnotationCard = ({item: {title, short_description, long_description, latitude, longitude, icon}}) => { + const [expanded, setExpanded] = useState(false); + const [locationName, setLocationName] = useState(''); + + useEffect(() => { + (async () => setLocationName(await getAddress(latitude, longitude)))(); + }, [latitude, longitude]); + + return + + + + } + titleTypographyProps={{variant: 'h6'}} + title={title} + /> + + + {short_description} + + + Location: {`${locationName}`} + + + + {/* + + + + + */} + setExpanded(!expanded)} + aria-expanded={expanded} + aria-label="show more" + > + + + + + + {long_description} + + + ; +} + +export const RequestCard = ({item: {requester, urgency, needs, status, longitude, latitude}}) => { + const [expanded, setExpanded] = useState(false); + const [locationName, setLocationName] = useState(''); + + useEffect(() => { + (async () => setLocationName(await getAddress(latitude, longitude)))(); + }, [latitude, longitude]); + + return + + Rq + + } + titleTypographyProps={{variant: 'h6'}} + title={needs.map(({name, quantity}) => `${quantity} ${name}`).join(", ")} + /> + + + Urgency: {urgency} | Status: {status} + + + + Made by: {requester.name} {requester.surname} + + + Location: {`${locationName}`} + + + + {/* + + + + + */} + setExpanded(!expanded)} + aria-expanded={expanded} + aria-label="show more" + > + + + + + + {needs.map(({name, description, quantity}) => + + {quantity} {name}: {description} + + )} + + + ; +} + + +export const ResourceCard = ({item: {owner, urgency, resources, status, longitude, latitude}}) => { + const [expanded, setExpanded] = useState(false); + const [locationName, setLocationName] = useState(''); + + useEffect(() => { + (async () => setLocationName(await getAddress(latitude, longitude)))(); + }, [latitude, longitude]); + + + return + + Rs + + } + titleTypographyProps={{variant: 'h6'}} + title={resources.map(({name, quantity}) => `${quantity} ${name}`).join(", ")} + /> + + + Urgency: {urgency} | Status: {status} + + + Owner: {owner.name} {owner.surname} + + + Location: {`${locationName}`} + + + + {/* + + + + + */} + setExpanded(!expanded)} + aria-expanded={expanded} + aria-label="show more" + > + + + + + + {resources.map(({name, description, quantity}) => + + {quantity} {name}: {description} + + )} + + + ; +} +export const cards = { + "Resource": ResourceCard, + "Request": RequestCard, + "Annotation": AnnotationCard +} \ No newline at end of file From 26ade84009f47cc169ed801fadc88322e8a76a97 Mon Sep 17 00:00:00 2001 From: Ilgaz Er Date: Sun, 26 Nov 2023 19:17:07 +0300 Subject: [PATCH 2/3] Add list of cards and filtering dropdown component --- resq/frontend/src/components/MapIcons.js | 6 +- resq/frontend/src/pages/ListCards.js | 3 +- resq/frontend/src/pages/MapDemo.js | 317 ++++++++--------------- 3 files changed, 119 insertions(+), 207 deletions(-) diff --git a/resq/frontend/src/components/MapIcons.js b/resq/frontend/src/components/MapIcons.js index ff75041d..8016c36b 100644 --- a/resq/frontend/src/components/MapIcons.js +++ b/resq/frontend/src/components/MapIcons.js @@ -3,9 +3,9 @@ import * as React from "react"; export const AnnotationIcon = ({icon, color}) => ({ - fire: , - health: , - closed: + fire: , + health: , + closed: })[icon] export const MarkerIcon = ({color}) => ( - + } titleTypographyProps={{variant: 'h6'}} diff --git a/resq/frontend/src/pages/MapDemo.js b/resq/frontend/src/pages/MapDemo.js index e86a6ca9..196c92b9 100644 --- a/resq/frontend/src/pages/MapDemo.js +++ b/resq/frontend/src/pages/MapDemo.js @@ -1,30 +1,12 @@ import * as React from 'react'; -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; +import {useEffect, useId, useState} from 'react'; import CssBaseline from '@mui/material/CssBaseline'; -import TextField from '@mui/material/TextField'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from '@mui/material/Checkbox'; -import Link from '@mui/material/Link'; import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid'; -import Typography from '@mui/material/Typography'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; -import disasterImage from '../disaster.png'; +import {createTheme, ThemeProvider} from '@mui/material/styles'; import Container from '@mui/material/Container'; -import { useNavigate } from 'react-router-dom'; import DisasterMap from "../components/DisasterMap"; -import { useState, useEffect } from 'react'; -import { Card, CardActions, CardContent, CardHeader, Collapse, IconButton } from "@mui/material"; -import { type_colors } from "../Colors"; -import FavoriteIcon from '@mui/icons-material/Favorite'; -import ShareIcon from '@mui/icons-material/Share'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import MoreVertIcon from '@mui/icons-material/MoreVert'; -import styled from "styled-components"; -import axios from 'axios'; -import Geocode from 'react-geocode'; -import MapDataGrid from '../components/MapDataGrid' +import {cards} from "./ListCards"; +import {Chip, FormControl, InputLabel, MenuItem, OutlinedInput, Select, useTheme} from "@mui/material"; const customTheme = createTheme({ @@ -36,171 +18,20 @@ const customTheme = createTheme({ }); -const ExpandMore = styled(IconButton)` - transform: ${({ expand }) => !expand ? 'rotate(0deg)' : 'rotate(180deg)'}; - margin-left: auto; - transition: transform 150ms; -` - -const OffsetActions = styled(CardActions)` - transform: translate(-8px, -28px); - height: 0; - padding: 0; -` - -const RequestCard = ({ request: { requester, urgency, needs, status, longitude, latitude } }) => { - const [expanded, setExpanded] = useState(false); - const [locationName, setLocationName] = useState(''); - const [cityName, setCityName] = useState(''); - - useEffect(() => { - const reverseGeocode = async () => { - try { - const response = await axios.get( - `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=AIzaSyCehlfJwJ-V_xOWZ9JK3s0rcjkV2ga0DVg` - ); - - const cityName = response.data.results[0]?.formatted_address || 'Unknown'; - setLocationName(cityName); - setCityName(cityName); - } catch (error) { - console.error('Error fetching location name:', error); - } - }; - - reverseGeocode(); - }, [latitude, longitude]); - - return - - Rq - - } - titleTypographyProps={{ variant: 'h6' }} - title={needs.map(({ name, quantity }) => `${quantity} ${name}`).join(", ")} - /> - - - Urgency: {urgency} | Status: {status} - - - - Made by: {requester.name} {requester.surname} - - - Location: {`${cityName}`} - - - - {/* - - - - - */} - setExpanded(!expanded)} - aria-expanded={expanded} - aria-label="show more" - > - - - - - - {needs.map(({ name, description, quantity }) => - - {quantity} {name}: {description} - - )} - - - ; -} - - -const ResourceCard = ({ request: { owner, urgency, resources, status, longitude, latitude } }) => { - const [expanded, setExpanded] = useState(false); - const [locationName, setLocationName] = useState(''); - const [cityName, setCityName] = useState(''); - - useEffect(() => { - const reverseGeocode = async () => { - try { - const response = await axios.get( - `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=AIzaSyCehlfJwJ-V_xOWZ9JK3s0rcjkV2ga0DVg` - ); - - const cityName = response.data.results[0]?.formatted_address || 'Unknown'; - setLocationName(cityName); - setCityName(cityName); - } catch (error) { - console.error('Error fetching location name:', error); - } - }; - - reverseGeocode(); - }, [latitude, longitude]); - - - return - - Rs - - } - titleTypographyProps={{ variant: 'h6' }} - title={resources.map(({ name, quantity }) => `${quantity} ${name}`).join(", ")} - /> - - - Urgency: {urgency} | Status: {status} - - - Owner: {owner.name} {owner.surname} - - - Location: {`${cityName}`} - - - - {/* - - - - - */} - setExpanded(!expanded)} - aria-expanded={expanded} - aria-label="show more" - > - - - - - - {resources.map(({ name, description, quantity }) => - - {quantity} {name}: {description} - - )} - - - ; -} - -const cards = { - "Resource": ResourceCard, - "Request": RequestCard -} - const mock_markers = [ + { + type: "Annotation", + latitude: 41.089, + longitude: 29.053, + icon: "health", + title: "First Aid Clinic", + short_description: "First aid clinic and emergency wound care. Open 24 hours.", + long_description: "Welcome to our First Aid Clinic, a dedicated facility committed to providing immediate and " + + "compassionate healthcare 24 hours a day. Our experienced team of healthcare professionals specializes in " + + "emergency wound care and first aid assistance, ensuring you receive prompt attention when you need it most. " + + "From minor cuts to more serious injuries, our clinic is equipped to handle a range of medical concerns, " + + "promoting healing and preventing complications." + }, { type: "Request", latitude: 37.08, @@ -262,31 +93,111 @@ const mock_markers = [ }, ] +function getDropDownStyles(name, personName, theme) { + return { + fontWeight: + personName.indexOf(name) === -1 + ? theme.typography.fontWeightRegular + : theme.typography.fontWeightMedium, + }; +} + +const MultiCheckbox = ({name, choices, onChosenChanged}) => { + const theme = useTheme(); + + const ITEM_HEIGHT = 48; + const ITEM_PADDING_TOP = 8; + const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, + }; + + const [currentChoices, setCurrentChoices] = React.useState([]); + const label_id = useId(); + const select_id = useId(); + const input_id = useId(); + + + const handleChange = (event) => { + const { + target: {value}, + } = event; + setCurrentChoices( + typeof value === 'string' ? value.split(',') : value, + ); + }; + + useEffect(() => { + onChosenChanged && onChosenChanged(currentChoices) + }, [onChosenChanged, currentChoices]) + + return + {name} + + +} export default function MapDemo() { - const navigate = useNavigate(); const [selectedPoint, setSelectedPoint] = useState(null) - const SelectedCard = selectedPoint && cards[selectedPoint.type] + // noinspection JSValidateTypes return ( - - - {selectedPoint && <> - - - - - - } - {(!selectedPoint) && <> - - - } - - + + + + + + + + {mock_markers.map((marker) => { + const SelectedCard = cards[marker.type] + return < SelectedCard item={marker} onClick={() => setSelectedPoint(marker)}/> + })} + + + + From 2ad61404b67305d72e7aa42848240cfa28f05231 Mon Sep 17 00:00:00 2001 From: Ilgaz Er Date: Sun, 26 Nov 2023 21:16:10 +0300 Subject: [PATCH 3/3] add filtering by category and type fix map dimensions --- resq/frontend/src/App.js | 85 ++++++----- resq/frontend/src/components/DisasterMap.js | 11 +- resq/frontend/src/pages/ListCards.js | 6 +- resq/frontend/src/pages/MapDemo.js | 161 ++++++++++---------- resq/frontend/src/pages/MultiCheckbox.js | 78 ++++++++++ 5 files changed, 218 insertions(+), 123 deletions(-) create mode 100644 resq/frontend/src/pages/MultiCheckbox.js diff --git a/resq/frontend/src/App.js b/resq/frontend/src/App.js index 44c062d6..835bbd17 100644 --- a/resq/frontend/src/App.js +++ b/resq/frontend/src/App.js @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; -import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom"; -import { Navbar, Container, Nav } from 'react-bootstrap'; +import React, {useEffect, useRef, useState} from 'react'; +import {BrowserRouter as Router, Routes, Route, Navigate} from "react-router-dom"; +import {Navbar, Container, Nav} from 'react-bootstrap'; import UserRoles from "./pages/UserRoles"; import SignIn from "./pages/SignIn"; import SignUp from "./pages/SignUp"; @@ -17,30 +17,42 @@ const SmallRedCircle = () => height="20" viewBox="0 0 20 20" > - + function App() { const [token, _setToken] = useState(localStorage.getItem("token")) const [role, setRole] = useState("") + // eslint-disable-next-line no-unused-vars + const [width, setWidth] = useState(window.innerWidth); + const [height, setHeight] = useState(window.innerHeight); + const updateDimensions = () => { + setWidth(window.innerWidth); + setHeight(window.innerHeight); + } + useEffect(() => { + window.addEventListener("resize", updateDimensions); + return () => window.removeEventListener("resize", updateDimensions); + }, []); + const setToken = t => { localStorage.setItem("token", t || "") _setToken(t) } const navLinks = [ - { path: '/map', label: Map Demo, component: MapDemo, icon: }, + {path: '/map', label: Map Demo, component: MapDemo, icon: }, token && { path: '/userroles', label: User Roles, component: UserRoles, - icon: + icon: }, (role === "responder") && { path: '/responder', label: Responder Panel, component:
Responder Panel
, - icon: + icon: }, ].filter(l => !!l); @@ -48,19 +60,21 @@ function App() { setToken(null) } + const ref = useRef(null) + return (
- - - + + + ResQ - +