From cd2acf8e81975a9d27d2405402c53beef37fc2ff Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Wed, 21 Oct 2020 18:30:11 -0500 Subject: [PATCH 01/16] remove double scroll on mobile - remove double scroll on list view or details view on mobile - fix padding on smaller desktop/tablet sizes --- client/src/components/Results/ResultsFilters.js | 4 ++-- client/src/components/Results/ResultsList.js | 1 - client/src/components/Search.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index ba921373a..f151faef8 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -53,13 +53,13 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: "#C7F573", boxShadow: "none", }, - [theme.breakpoints.down("xs")]: { + [theme.breakpoints.down("sm")]: { marginRight: ".5rem", }, }, buttonHolder: { display: "flex", - [theme.breakpoints.down("xs")]: { + [theme.breakpoints.down("sm")]: { marginTop: "0.5rem", }, }, diff --git a/client/src/components/Results/ResultsList.js b/client/src/components/Results/ResultsList.js index 16c345ace..5498f5c6d 100644 --- a/client/src/components/Results/ResultsList.js +++ b/client/src/components/Results/ResultsList.js @@ -10,7 +10,6 @@ const useStyles = makeStyles((theme, props) => ({ list: { textAlign: "center", fontSize: "12px", - overflow: "scroll", width: "100%", display: "flex", flexDirection: "column", diff --git a/client/src/components/Search.js b/client/src/components/Search.js index be3422b94..50b3b2b7d 100644 --- a/client/src/components/Search.js +++ b/client/src/components/Search.js @@ -23,7 +23,7 @@ const useStyles = makeStyles((theme) => ({ }, container: { width: "100%", - [theme.breakpoints.down("xs")]: { + [theme.breakpoints.down("sm")]: { marginLeft: ".5rem", }, position: "relative", From 29a69d2bc5a21e2cb6f33a7be2ef5afb16476603 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Thu, 22 Oct 2020 14:53:23 -0500 Subject: [PATCH 02/16] fix resizing - remove need to refresh the page for mobile sizes - remove passing `isWindowWide` as props and some inline styles --- .../components/Results/ResultsContainer.js | 23 ++--------- .../src/components/Results/ResultsFilters.js | 27 ++++++------- client/src/components/Results/ResultsList.js | 17 ++++++--- client/src/components/Results/ResultsMap.js | 38 ++++++++----------- client/src/helpers/index.js | 6 +-- 5 files changed, 46 insertions(+), 65 deletions(-) diff --git a/client/src/components/Results/ResultsContainer.js b/client/src/components/Results/ResultsContainer.js index 4836b6017..418b0b41b 100644 --- a/client/src/components/Results/ResultsContainer.js +++ b/client/src/components/Results/ResultsContainer.js @@ -30,14 +30,12 @@ export default function ResultsContainer(props) { const [sortedData, setSortedData] = useState([]); const classes = useStyles(); - const windowSize = window.innerWidth > 960 ? true : false; - const [isWindowWide, changeWindow] = useState(windowSize); - const [selectedStakeholder, onSelectStakeholder] = useState(null); const [isMapView, setIsMapView] = useState(true); + const mobileView = isMobile(); const doSelectStakeholder = (stakeholder) => { - if (stakeholder && !isMobile) { + if (stakeholder && !mobileView) { setViewport({ ...viewport, latitude: stakeholder.latitude, @@ -130,17 +128,6 @@ export default function ResultsContainer(props) { setSortedData(data.sort(sortOrganizations)); }, [data]); - useEffect(() => { - const changeInputContainerWidth = () => { - window.innerWidth > 960 ? changeWindow(true) : changeWindow(false); - }; - - window.addEventListener("resize", changeInputContainerWidth); - - return () => - window.removeEventListener("resize", changeInputContainerWidth); - }, []); - useEffect(() => { return () => { sessionStorage.clear(); @@ -160,7 +147,6 @@ export default function ResultsContainer(props) { selectVerified={selectVerified} userCoordinates={userCoordinates} search={search} - isWindowWide={isWindowWide} viewport={viewport} setViewport={setViewport} doSelectStakeholder={doSelectStakeholder} @@ -169,7 +155,7 @@ export default function ResultsContainer(props) { switchResultsView={switchResultsView} /> - {(!isMobile || (isMobile && !isMapView)) && ( + {(!mobileView || (mobileView && !isMapView)) && ( )} - {(!isMobile || (isMobile && isMapView)) && ( + {(!mobileView || (mobileView && isMapView)) && ( )} diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index f151faef8..26ec11913 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -27,10 +27,17 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.primary.main, padding: "1rem 0", flex: "1 0 auto", + [theme.breakpoints.up("md")]: { + justifyContent: "center", + }, }, inputContainer: { display: "flex", alignItems: "center", + width: "100%", + [theme.breakpoints.up("md")]: { + width: "30rem", + }, }, searchIcon: { width: 32, @@ -69,7 +76,6 @@ const distanceInfo = [0, 1, 2, 3, 5, 10, 20, 50, 100, 500]; const ResultsFilters = ({ search, - isWindowWide, viewport, setViewport, doSelectStakeholder, @@ -161,16 +167,10 @@ const ResultsFilters = ({ }); }; + const mobileView = isMobile(); + return ( - + - {isMobile && ( + {mobileView && ( - +
doHandleSearch(e)} diff --git a/client/src/components/Results/ResultsList.js b/client/src/components/Results/ResultsList.js index 5498f5c6d..54938e17a 100644 --- a/client/src/components/Results/ResultsList.js +++ b/client/src/components/Results/ResultsList.js @@ -6,7 +6,7 @@ import { makeStyles } from "@material-ui/core/styles"; import StakeholderPreview from "components/Stakeholder/StakeholderPreview"; import { isMobile } from "helpers"; -const useStyles = makeStyles((theme, props) => ({ +const useStyles = makeStyles((theme) => ({ list: { textAlign: "center", fontSize: "12px", @@ -18,9 +18,12 @@ const useStyles = makeStyles((theme, props) => ({ [theme.breakpoints.up("md")]: { height: "100%", }, - [theme.breakpoints.down("sm")]: { + [theme.breakpoints.only("sm")]: { order: 1, - height: (props) => (props.isMobile ? "100%" : "30em"), + height: "30em", + }, + [theme.breakpoints.down("xs")]: { + height: "100%", }, }, preview: { @@ -35,7 +38,7 @@ const ResultsList = ({ stakeholders, setToast, }) => { - const classes = useStyles({ isMobile }); + const classes = useStyles(); const listRef = useRef(); const itemsRef = useRef([]); @@ -45,15 +48,17 @@ const ResultsList = ({ itemsRef.current = itemsRef.current.slice(0, stakeholders.length); }, [stakeholders]); + const mobileView = isMobile(); + const selectStakeholder = (stakeholder) => { doSelectStakeholder(stakeholder); - if (stakeholder && isMobile) { + if (stakeholder && mobileView) { const index = stakeholders.findIndex((s) => s.id === stakeholder.id); const currentRef = itemsRef.current[index]; setPosition(currentRef.offsetTop); listRef.current.scrollTo(0, 0); } - if (!stakeholder && isMobile) { + if (!stakeholder && mobileView) { window.scrollTo(0, 0); listRef.current.scrollTo(0, position); } diff --git a/client/src/components/Results/ResultsMap.js b/client/src/components/Results/ResultsMap.js index d15e3f15e..74b2c15b9 100644 --- a/client/src/components/Results/ResultsMap.js +++ b/client/src/components/Results/ResultsMap.js @@ -22,12 +22,20 @@ const styles = { }, }; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme, props) => ({ map: { textAlign: "center", fontSize: "12px", - [theme.breakpoints.down("sm")]: { + [theme.breakpoints.up("md")]: { + height: "100%", + }, + [theme.breakpoints.down("xs")]: { + height: (props) => + props.selectedStakeholder ? "calc(100% - 120px)" : "100%", + }, + [theme.breakpoints.only("sm")]: { order: 0, + height: "50%", }, }, preview: { @@ -40,12 +48,11 @@ function Map({ categoryIds, doSelectStakeholder, selectedStakeholder, - isWindowWide, viewport, setViewport, setToast, }) { - const classes = useStyles(); + const classes = useStyles({ selectedStakeholder }); const categoryIdsOrDefault = categoryIds.length ? categoryIds : DEFAULT_CATEGORIES; @@ -57,7 +64,9 @@ function Map({ doSelectStakeholder(null); }; - if (showDetails && isMobile && selectedStakeholder) { + const mobileView = isMobile(); + + if (showDetails && mobileView && selectedStakeholder) { return ( - + setViewport(newViewport)} @@ -124,7 +118,7 @@ function Map({ })} - {!!selectedStakeholder && isMobile && ( + {!!selectedStakeholder && mobileView && ( { const baseUrl = `https://google.com/maps/place/`; @@ -17,9 +19,7 @@ export const getGoogleMapsUrl = (zip, address1, address2) => { return `${baseUrl}${address1url},+${zip}`; }; -export const isMobile = new RegExp("Mobi", "i").test(navigator.userAgent) - ? true - : false; +export const isMobile = () => window.innerWidth < theme.breakpoints.values.sm; export const extractNumbers = (numbers) => numbers.split(/(and)|,|&+/).map((n) => { From 484d651bb542e2321212020da77f480b736b4477 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Thu, 22 Oct 2020 14:57:31 -0500 Subject: [PATCH 03/16] remove iOS details render - explicitly set flexShrink to remove overlap error in stakeholder details --- client/src/components/Stakeholder/StakeholderDetails.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/Stakeholder/StakeholderDetails.js b/client/src/components/Stakeholder/StakeholderDetails.js index db8e4f7a7..d4a36f3a2 100644 --- a/client/src/components/Stakeholder/StakeholderDetails.js +++ b/client/src/components/Stakeholder/StakeholderDetails.js @@ -25,6 +25,7 @@ const useStyles = makeStyles((theme, props) => ({ padding: "1em", alignItems: "center", paddingBottom: "5em", + flexShrink: 0, }, topInfo: { width: "100%", From e1b0934f513fec5afabdc6498324a3e0806acc6f Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Thu, 22 Oct 2020 15:01:02 -0500 Subject: [PATCH 04/16] preserve scrolling on tablet/desktop - readd scroll for lists on all non mobile screens --- client/src/components/Results/ResultsList.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/Results/ResultsList.js b/client/src/components/Results/ResultsList.js index 54938e17a..9f665b52e 100644 --- a/client/src/components/Results/ResultsList.js +++ b/client/src/components/Results/ResultsList.js @@ -18,6 +18,9 @@ const useStyles = makeStyles((theme) => ({ [theme.breakpoints.up("md")]: { height: "100%", }, + [theme.breakpoints.up("sm")]: { + overflowY: "scroll", + }, [theme.breakpoints.only("sm")]: { order: 1, height: "30em", From ce6726724055c939f2e793478ca48fca6b8454a2 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Thu, 22 Oct 2020 15:03:11 -0500 Subject: [PATCH 05/16] disabled details disabled details button for inactive locations --- client/src/components/Stakeholder/StakeholderPreview.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/Stakeholder/StakeholderPreview.js b/client/src/components/Stakeholder/StakeholderPreview.js index 4f83763d2..aa3a6a71f 100644 --- a/client/src/components/Stakeholder/StakeholderPreview.js +++ b/client/src/components/Stakeholder/StakeholderPreview.js @@ -232,7 +232,11 @@ const StakeholderPreview = ({ stakeholder, doSelectStakeholder }) => { Call )} - From 1dddc754479e8899ec72dee06981ecf9267505aa Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Fri, 23 Oct 2020 11:53:37 -0500 Subject: [PATCH 06/16] sticky search bar - make search and filters sticky - reduce font size only on mobile views - fix details styling --- .../src/components/Results/ResultsFilters.js | 3 +++ client/src/components/Results/ResultsList.js | 2 +- client/src/components/Results/ResultsMap.js | 20 ++++++++++++++----- .../Stakeholder/StakeholderDetails.js | 5 ++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index 26ec11913..887d18b73 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -27,6 +27,9 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.primary.main, padding: "1rem 0", flex: "1 0 auto", + position: "sticky", + top: "48px", + zIndex: 1, [theme.breakpoints.up("md")]: { justifyContent: "center", }, diff --git a/client/src/components/Results/ResultsList.js b/client/src/components/Results/ResultsList.js index 9f665b52e..715a68d0e 100644 --- a/client/src/components/Results/ResultsList.js +++ b/client/src/components/Results/ResultsList.js @@ -9,7 +9,6 @@ import { isMobile } from "helpers"; const useStyles = makeStyles((theme) => ({ list: { textAlign: "center", - fontSize: "12px", width: "100%", display: "flex", flexDirection: "column", @@ -27,6 +26,7 @@ const useStyles = makeStyles((theme) => ({ }, [theme.breakpoints.down("xs")]: { height: "100%", + fontSize: "12px", }, }, preview: { diff --git a/client/src/components/Results/ResultsMap.js b/client/src/components/Results/ResultsMap.js index 74b2c15b9..f33724614 100644 --- a/client/src/components/Results/ResultsMap.js +++ b/client/src/components/Results/ResultsMap.js @@ -41,6 +41,14 @@ const useStyles = makeStyles((theme, props) => ({ preview: { margin: "0 1em", }, + details: { + textAlign: "center", + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + padding: "0 1em", + }, })); function Map({ @@ -68,11 +76,13 @@ function Map({ if (showDetails && mobileView && selectedStakeholder) { return ( - + + + ); } diff --git a/client/src/components/Stakeholder/StakeholderDetails.js b/client/src/components/Stakeholder/StakeholderDetails.js index d4a36f3a2..428edf6be 100644 --- a/client/src/components/Stakeholder/StakeholderDetails.js +++ b/client/src/components/Stakeholder/StakeholderDetails.js @@ -21,11 +21,14 @@ const useStyles = makeStyles((theme, props) => ({ width: "100%", display: "flex", flexDirection: "column", - justifyContent: "space-between", + textAlign: "center", padding: "1em", alignItems: "center", paddingBottom: "5em", flexShrink: 0, + [theme.breakpoints.down("xs")]: { + fontSize: "12px", + }, }, topInfo: { width: "100%", From 6d0dd334c83af71fb080c1cf3a54addfcd404e09 Mon Sep 17 00:00:00 2001 From: Brent Bovenzi Date: Wed, 28 Oct 2020 12:40:55 -0600 Subject: [PATCH 07/16] search more with bounds - Search this area uses bound search - fallback on center/distance search - adjust default zoom/radius/center for tenants - remove distance dropdown --- app/services/stakeholder-best-service.js | 22 ++++- .../components/Results/ResultsContainer.js | 81 +++++++------------ .../src/components/Results/ResultsFilters.js | 47 +---------- client/src/components/Results/ResultsMap.js | 14 +++- client/src/helpers/Configuration.js | 6 +- client/src/hooks/useOrganizationBests.js | 16 +++- .../src/services/stakeholder-best-service.js | 4 + 7 files changed, 81 insertions(+), 109 deletions(-) diff --git a/app/services/stakeholder-best-service.js b/app/services/stakeholder-best-service.js index d56fc8ada..995a703d7 100644 --- a/app/services/stakeholder-best-service.js +++ b/app/services/stakeholder-best-service.js @@ -12,6 +12,9 @@ record has been approved, it will be the most recent version (i.e., have If you make changes to the database structure, be sure to update these methods as well as the corresponding methods in the stakeholder-service.js. +You can search by max/min lat, lng bounds or by a center and radius(distance), +with bounds taking precedence. + */ const booleanEitherClause = (columnName, value) => { @@ -27,13 +30,16 @@ const search = async ({ latitude, longitude, distance, + maxLat, + maxLng, + minLat, + minLng, isInactive, verificationStatusId, tenantId, }) => { const locationClause = buildLocationClause(latitude, longitude); const categoryClause = buildCTEClause(categoryIds, ""); - const sql = `${categoryClause} select s.id, s.name, s.address_1, s.address_2, s.city, s.state, s.zip, s.phone, s.latitude, s.longitude, s.website, s.notes, @@ -69,9 +75,11 @@ const search = async ({ ${buildLoginSelectsClause()} from stakeholder_set as s ${buildLoginJoinsClause()} - where s.tenant_id = ${tenantId} + where s.tenant_id = ${tenantId} ${ - Number(distance) && locationClause + maxLat && maxLng && minLat && minLng + ? buildBounds({ maxLat, maxLng, minLat, minLng }) + : Number(distance) && locationClause ? `AND ${locationClause} < ${distance}` : "" } @@ -83,7 +91,6 @@ const search = async ({ } order by distance `; - // console.log(sql); let stakeholders = []; let categoriesResults = []; var stakeholderResult, stakeholder_ids; @@ -349,6 +356,13 @@ const buildLocationClause = (latitude, longitude) => { return locationClause; }; +const buildBounds = ({ maxLat, maxLng, minLat, minLng }) => { + return ` + AND s.latitude BETWEEN ${minLat} AND ${maxLat} + AND s.longitude BETWEEN ${minLng} AND ${maxLng} + `; +}; + const buildLoginJoinsClause = () => { return ` left join login L1 on s.created_login_id = L1.id diff --git a/client/src/components/Results/ResultsContainer.js b/client/src/components/Results/ResultsContainer.js index be68eb32b..fe36beaaf 100644 --- a/client/src/components/Results/ResultsContainer.js +++ b/client/src/components/Results/ResultsContainer.js @@ -40,27 +40,6 @@ export default function ResultsContainer({ const [selectedStakeholder, onSelectStakeholder] = useState(null); const [isMapView, setIsMapView] = useState(true); - const doSelectStakeholder = useCallback((stakeholder) => { - if (stakeholder && !isMobile) { - setViewport({ - ...viewport, - latitude: stakeholder.latitude, - longitude: stakeholder.longitude, - }); - } - onSelectStakeholder(stakeholder); - }); - - const switchResultsView = () => { - doSelectStakeholder(); - setIsMapView(!isMapView); - }; - - const initialCategories = storage.categoryIds - ? JSON.parse(storage.categoryIds) - : []; - const { categoryIds, toggleCategory } = useCategoryIds(initialCategories); - const initialCoords = { locationName: userSearch ? userSearch.locationName @@ -83,34 +62,41 @@ export default function ResultsContainer({ : originCoordinates.lon, }; - const [radius, setRadius] = useState( - storage?.radius ? JSON.parse(storage.radius) : 5 - ); const [origin, setOrigin] = useState(initialCoords); const [isVerifiedSelected, selectVerified] = useState( storage?.verified ? JSON.parse(storage.verified) : false ); - - const viewPortHash = { - 0: 4, - 1: 13.5, - 2: 12.5, - 3: 12, - 5: 11, - 10: 10, - 20: 9, - 50: 8, - 100: 7, - 500: 4.5, - }; - const [viewport, setViewport] = useState({ - zoom: viewPortHash[radius || 0], + zoom: originCoordinates.zoom, latitude: origin.latitude || JSON.parse(storage.origin).latitude, longitude: origin.longitude || JSON.parse(storage.origin).longitude, logoPosition: "top-left", }); + const initialCategories = storage.categoryIds + ? JSON.parse(storage.categoryIds) + : []; + const { categoryIds, toggleCategory } = useCategoryIds(initialCategories); + + const doSelectStakeholder = useCallback( + (stakeholder) => { + if (stakeholder && !isMobile) { + setViewport({ + ...viewport, + latitude: stakeholder.latitude, + longitude: stakeholder.longitude, + }); + } + onSelectStakeholder(stakeholder); + }, + [viewport, setViewport] + ); + + const switchResultsView = () => { + doSelectStakeholder(); + setIsMapView(!isMapView); + }; + // Component effects useEffect(() => { @@ -155,7 +141,7 @@ export default function ResultsContainer({ }, []); const handleSearch = useCallback( - (e, center) => { + (e, center, bounds) => { if (e) e.preventDefault(); search({ latitude: @@ -168,11 +154,13 @@ export default function ResultsContainer({ origin.longitude || userCoordinates.longitude || JSON.parse(storage.origin).longitude, - radius, categoryIds: categoryIds.length ? categoryIds : DEFAULT_CATEGORIES, isInactive: "either", verificationStatusId: 0, + bounds, + radius: originCoordinates.radius, }); + if (origin.locationName && origin.latitude && origin.longitude) storage.origin = JSON.stringify({ locationName: origin.locationName, @@ -181,11 +169,10 @@ export default function ResultsContainer({ }); storage.categoryIds = JSON.stringify(categoryIds); - storage.radius = JSON.stringify(radius); storage.verified = JSON.stringify(isVerifiedSelected); if (!center) setViewport({ - zoom: viewPortHash[radius || 0], + zoom: originCoordinates.zoom, latitude: origin.latitude, longitude: origin.longitude, }); @@ -198,14 +185,11 @@ export default function ResultsContainer({ origin.longitude, userCoordinates.latitude, userCoordinates.longitude, - radius, categoryIds, isVerifiedSelected, setViewport, doSelectStakeholder, - viewPortHash, storage.categoryIds, - storage.radius, storage.verified, storage.origin, ] @@ -214,8 +198,6 @@ export default function ResultsContainer({ return ( <> diff --git a/client/src/components/Results/ResultsFilters.js b/client/src/components/Results/ResultsFilters.js index acaec61ac..8342e4ece 100644 --- a/client/src/components/Results/ResultsFilters.js +++ b/client/src/components/Results/ResultsFilters.js @@ -1,7 +1,7 @@ import React, { useCallback, useEffect } from "react"; import PropTypes from "prop-types"; import { makeStyles } from "@material-ui/core/styles"; -import { Grid, Select, MenuItem, Button, Box } from "@material-ui/core"; +import { Grid, Button, Box } from "@material-ui/core"; import SearchIcon from "@material-ui/icons/Search"; import { @@ -64,22 +64,15 @@ const useStyles = makeStyles((theme) => ({ }, })); -const distanceInfo = [0, 1, 2, 3, 5, 10, 20, 50, 100, 500]; - const ResultsFilters = ({ handleSearch, isWindowWide, - viewport, - setViewport, origin, setOrigin, - radius, - setRadius, isVerifiedSelected, userCoordinates, categoryIds, toggleCategory, - viewPortHash, isMapView, switchResultsView, }) => { @@ -98,15 +91,7 @@ const ResultsFilters = ({ useEffect(() => { handleSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [origin, radius, categoryIds, isVerifiedSelected, toggleCategory]); - - const handleDistanceChange = (distance) => { - setRadius(distance); - setViewport({ - ...viewport, - zoom: viewPortHash[distance], - }); - }; + }, [origin, categoryIds, isVerifiedSelected, toggleCategory]); return ( - - -