From 2cec8ae450221cabcfc5897aca1694570e538c3a Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Mon, 27 Jun 2022 14:54:36 +0530 Subject: [PATCH] feat (GLocationPicker): changed leaflet location picker to gmap --- package-lock.json | 70 ++++++++ package.json | 4 + src/Common/env.tsx | 2 + src/Components/Common/GLocationPicker.tsx | 180 +++++++++++++++++++++ src/Components/Facility/FacilityCreate.tsx | 42 +++-- tsconfig.json | 5 +- 6 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 src/Components/Common/GLocationPicker.tsx diff --git a/package-lock.json b/package-lock.json index 6b3d345bb3..4bfa23c811 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@babel/core": "^7.14.3", "@date-io/date-fns": "^1.3.13", "@glennsl/bs-json": "^5.0.3", + "@googlemaps/react-wrapper": "^1.1.35", + "@googlemaps/typescript-guards": "^2.0.0", "@headlessui/react": "^1.6.4", "@loadable/component": "^5.15.0", "@material-ui/core": "^4.11.4", @@ -23,6 +25,7 @@ "@rescript/react": "^0.10.3", "@sentry/browser": "^6.12.0", "@types/echarts": "^4.9.9", + "@types/googlemaps": "^3.43.3", "@types/loadable__component": "^5.13.3", "@types/lodash": "^4.14.170", "@types/node": "^15.6.1", @@ -45,6 +48,7 @@ "date-fns-tz": "^1.0.10", "echarts": "^5.1.2", "echarts-for-react": "^3.0.1", + "fast-equals": "^4.0.1", "i18next": "^20.3.1", "i18next-browser-languagedetector": "^6.1.1", "libphonenumber-js": "^1.9.19", @@ -2462,6 +2466,30 @@ "resolved": "https://registry.npmjs.org/@glennsl/bs-json/-/bs-json-5.0.4.tgz", "integrity": "sha512-Th9DetZjRlMZrb74kgGJ44oWcoFyOTE884WlSuXft0Cd+J09vHRxiB7eVyK7Gthb4cSevsBBJDHYAbGGL25wPw==" }, + "node_modules/@googlemaps/js-api-loader": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.14.3.tgz", + "integrity": "sha512-6iIb+qpGgQpgIHmIFO44WhE1rDUxPVHuezNFL30wRJnkvhwFm94tD291UvNg9L05hLDSoL16jd0lbqqmdy4C5g==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@googlemaps/react-wrapper": { + "version": "1.1.35", + "resolved": "https://registry.npmjs.org/@googlemaps/react-wrapper/-/react-wrapper-1.1.35.tgz", + "integrity": "sha512-vK+BDQMHN0Oqr66cW3ZPWVK43BUmJJBu6P8T74tc6/fKpUJUlFEaZsupgIIRRRDW9ejB8uGagUmwOnA2gdcvbw==", + "dependencies": { + "@googlemaps/js-api-loader": "^1.13.2" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@googlemaps/typescript-guards": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@googlemaps/typescript-guards/-/typescript-guards-2.0.0.tgz", + "integrity": "sha512-gSJtAPukY4mm5tsDntL/M2UgNgF2KDb4cP16wIx7a21FDOuQPujYoK2cdABU6gz7AbFWUGMA+93fUdo3jCGHDA==" + }, "node_modules/@headlessui/react": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.5.tgz", @@ -4849,6 +4877,12 @@ "@types/range-parser": "*" } }, + "node_modules/@types/googlemaps": { + "version": "3.43.3", + "resolved": "https://registry.npmjs.org/@types/googlemaps/-/googlemaps-3.43.3.tgz", + "integrity": "sha512-ZWNoz/O8MPEpiajvj7QiqCY8tTLFNqNZ/a+s+zTV58wFVNAvvqV4bdGfnsjTb5Cs4V6wEsLrX8XRhmnyYJ2Tdg==", + "deprecated": "Types for the Google Maps browser API have moved to @types/google.maps. Note: these types are not for the googlemaps npm package, which is a Node API." + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -10671,6 +10705,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "node_modules/fast-equals": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.1.tgz", + "integrity": "sha512-OXqyj3MD0p8Kee16Jz7CbCnXo+5CHKKu4xBh5UhC1NbmMkHn8WScLRy/B2q5UOlWMlNSQJc4mwXW30Lz+JUZJw==" + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -29931,6 +29970,27 @@ "resolved": "https://registry.npmjs.org/@glennsl/bs-json/-/bs-json-5.0.4.tgz", "integrity": "sha512-Th9DetZjRlMZrb74kgGJ44oWcoFyOTE884WlSuXft0Cd+J09vHRxiB7eVyK7Gthb4cSevsBBJDHYAbGGL25wPw==" }, + "@googlemaps/js-api-loader": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.14.3.tgz", + "integrity": "sha512-6iIb+qpGgQpgIHmIFO44WhE1rDUxPVHuezNFL30wRJnkvhwFm94tD291UvNg9L05hLDSoL16jd0lbqqmdy4C5g==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "@googlemaps/react-wrapper": { + "version": "1.1.35", + "resolved": "https://registry.npmjs.org/@googlemaps/react-wrapper/-/react-wrapper-1.1.35.tgz", + "integrity": "sha512-vK+BDQMHN0Oqr66cW3ZPWVK43BUmJJBu6P8T74tc6/fKpUJUlFEaZsupgIIRRRDW9ejB8uGagUmwOnA2gdcvbw==", + "requires": { + "@googlemaps/js-api-loader": "^1.13.2" + } + }, + "@googlemaps/typescript-guards": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@googlemaps/typescript-guards/-/typescript-guards-2.0.0.tgz", + "integrity": "sha512-gSJtAPukY4mm5tsDntL/M2UgNgF2KDb4cP16wIx7a21FDOuQPujYoK2cdABU6gz7AbFWUGMA+93fUdo3jCGHDA==" + }, "@headlessui/react": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.5.tgz", @@ -31670,6 +31730,11 @@ "@types/range-parser": "*" } }, + "@types/googlemaps": { + "version": "3.43.3", + "resolved": "https://registry.npmjs.org/@types/googlemaps/-/googlemaps-3.43.3.tgz", + "integrity": "sha512-ZWNoz/O8MPEpiajvj7QiqCY8tTLFNqNZ/a+s+zTV58wFVNAvvqV4bdGfnsjTb5Cs4V6wEsLrX8XRhmnyYJ2Tdg==" + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -36145,6 +36210,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-equals": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.1.tgz", + "integrity": "sha512-OXqyj3MD0p8Kee16Jz7CbCnXo+5CHKKu4xBh5UhC1NbmMkHn8WScLRy/B2q5UOlWMlNSQJc4mwXW30Lz+JUZJw==" + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", diff --git a/package.json b/package.json index 8630379e60..d73006158a 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "@babel/core": "^7.14.3", "@date-io/date-fns": "^1.3.13", "@glennsl/bs-json": "^5.0.3", + "@googlemaps/react-wrapper": "^1.1.35", + "@googlemaps/typescript-guards": "^2.0.0", "@headlessui/react": "^1.6.4", "@loadable/component": "^5.15.0", "@material-ui/core": "^4.11.4", @@ -44,6 +46,7 @@ "@rescript/react": "^0.10.3", "@sentry/browser": "^6.12.0", "@types/echarts": "^4.9.9", + "@types/googlemaps": "^3.43.3", "@types/loadable__component": "^5.13.3", "@types/lodash": "^4.14.170", "@types/node": "^15.6.1", @@ -66,6 +69,7 @@ "date-fns-tz": "^1.0.10", "echarts": "^5.1.2", "echarts-for-react": "^3.0.1", + "fast-equals": "^4.0.1", "i18next": "^20.3.1", "i18next-browser-languagedetector": "^6.1.1", "libphonenumber-js": "^1.9.19", diff --git a/src/Common/env.tsx b/src/Common/env.tsx index 706209a49e..0b832b2868 100644 --- a/src/Common/env.tsx +++ b/src/Common/env.tsx @@ -1 +1,3 @@ export const RECAPTCHA_SITE_KEY = "6Lc67IceAAAAADO2oNsVRxlCOdKpFJufdBiefGrf"; + +export const GMAPS_API_KEY = "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk"; diff --git a/src/Components/Common/GLocationPicker.tsx b/src/Components/Common/GLocationPicker.tsx new file mode 100644 index 0000000000..0bbe3dd86d --- /dev/null +++ b/src/Components/Common/GLocationPicker.tsx @@ -0,0 +1,180 @@ +import React from "react"; +import { Wrapper, Status } from "@googlemaps/react-wrapper"; +import { createCustomEqual } from "fast-equals"; +import { isLatLngLiteral } from "@googlemaps/typescript-guards"; + +import { GMAPS_API_KEY } from "../../Common/env"; + +const render = (status: Status) => { + return

{status}

; +}; + +interface GLocationPickerProps { + lat: number; + lng: number; + handleOnChange: (location: google.maps.LatLng) => void; +} + +const GLocationPicker = ({ + lat, + lng, + handleOnChange, +}: GLocationPickerProps) => { + const [location, setLocation] = React.useState( + null + ); + const [zoom, setZoom] = React.useState(4); + const [center, setCenter] = React.useState({ + lat, + lng, + }); + + React.useEffect(() => { + (async function () { + const latLng = await new google.maps.LatLng(lat, lng); + setLocation(latLng); + })(); + }, [lat, lng]); + + const onClick = (e: google.maps.MapMouseEvent) => { + handleOnChange(e.latLng); + }; + + const onIdle = (m: google.maps.Map) => { + console.log("onIdle"); + setZoom(m.getZoom()!); + setCenter(m.getCenter()!.toJSON()); + }; + + return ( +
+ + + {location && } + + +
+ ); +}; +interface MapProps extends google.maps.MapOptions { + style: { [key: string]: string }; + onClick?: (e: google.maps.MapMouseEvent) => void; + onIdle?: (map: google.maps.Map) => void; + children?: React.ReactNode; +} + +const Map: React.FC = ({ + onClick, + onIdle, + children, + style, + ...options +}) => { + const ref = React.useRef(null); + const [map, setMap] = React.useState(); + + React.useEffect(() => { + if (ref.current && !map) { + setMap(new window.google.maps.Map(ref.current, {})); + } + }, [ref, map]); + + useDeepCompareEffectForMaps(() => { + if (map) { + map.setOptions(options); + } + }, [map, options]); + + React.useEffect(() => { + if (map) { + ["click", "idle"].forEach((eventName) => + google.maps.event.clearListeners(map, eventName) + ); + + if (onClick) { + map.addListener("click", onClick); + } + + if (onIdle) { + map.addListener("idle", () => onIdle(map)); + } + } + }, [map, onClick, onIdle]); + + return ( + <> +
+ {React.Children.map(children, (child) => { + if (React.isValidElement(child)) { + return React.cloneElement(child, { map }); + } + })} + + ); +}; + +const Marker: React.FC = (options) => { + const [marker, setMarker] = React.useState(); + + React.useEffect(() => { + if (!marker) { + setMarker(new google.maps.Marker()); + } + + return () => { + if (marker) { + marker.setMap(null); + } + }; + }, [marker]); + + React.useEffect(() => { + if (marker) { + marker.setOptions(options); + } + }, [marker, options]); + + return null; +}; + +const deepCompareEqualsForMaps = createCustomEqual( + // @ts-ignore + (deepEqual) => (a: any, b: any) => { + if ( + isLatLngLiteral(a) || + a instanceof google.maps.LatLng || + isLatLngLiteral(b) || + b instanceof google.maps.LatLng + ) { + return new google.maps.LatLng(a).equals(new google.maps.LatLng(b)); + } + + // @ts-ignore + return deepEqual(a, b); + } +); + +function useDeepCompareMemoize(value: any) { + const ref = React.useRef(); + + if (!deepCompareEqualsForMaps(value, ref.current)) { + ref.current = value; + } + + return ref.current; +} + +function useDeepCompareEffectForMaps( + callback: React.EffectCallback, + dependencies: any[] +) { + React.useEffect(callback, dependencies.map(useDeepCompareMemoize)); +} + +export default GLocationPicker; diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index d8816b5ce7..186c4b4e91 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -46,7 +46,7 @@ import { SelectField, TextInputField, } from "../Common/HelperInputFields"; -import { LocationSearchAndPick } from "../Common/LocationSearchAndPick"; +import GLocationPicker from "../Common/GLocationPicker"; const Loading = loadable(() => import("../Common/Loading")); const PageTitle = loadable(() => import("../Common/PageTitle")); @@ -100,8 +100,8 @@ const initForm: FacilityForm = { kasp_empanelled: "false", address: "", phone_number: "", - latitude: "", - longitude: "", + latitude: DEFAULT_MAP_LOCATION[0].toString(), + longitude: DEFAULT_MAP_LOCATION[1].toString(), pincode: "", oxygen_capacity: "", type_b_cylinders: "", @@ -162,7 +162,6 @@ export const FacilityCreate = (props: FacilityProps) => { const [anchorEl, setAnchorEl] = React.useState< (EventTarget & Element) | null >(null); - const [mapLoadLocation, setMapLoadLocation] = useState(DEFAULT_MAP_LOCATION); const headerText = !facilityId ? "Create Facility" : "Update Facility"; const buttonText = !facilityId ? "Save Facility" : "Update Facility"; @@ -296,6 +295,17 @@ export const FacilityCreate = (props: FacilityProps) => { }); }; + const handleLocationChange = (location: any) => { + dispatch({ + type: "set_form", + form: { + ...state.form, + latitude: location.lat(), + longitude: location.lng(), + }, + }); + }; + const handleValueChange = (value: any, field: string) => { dispatch({ type: "set_form", @@ -306,10 +316,6 @@ export const FacilityCreate = (props: FacilityProps) => { const handleClickLocationPicker = (event: React.MouseEvent) => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((position) => { - setMapLoadLocation([ - position.coords.latitude, - position.coords.longitude, - ]); dispatch({ type: "set_form", form: { @@ -469,18 +475,6 @@ export const FacilityCreate = (props: FacilityProps) => { } }; - const handleLocationSelect = (location: { lat: string; lon: string }) => { - dispatch({ - type: "set_form", - form: { - ...state.form, - latitude: location.lat, - longitude: location.lon, - }, - }); - setMapLoadLocation([parseFloat(location.lat), parseFloat(location.lon)]); - }; - if (isLoading) { return ; } @@ -894,10 +888,10 @@ export const FacilityCreate = (props: FacilityProps) => { horizontal: "left", }} > -
diff --git a/tsconfig.json b/tsconfig.json index 9d379a3c4a..d749976d03 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,10 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": [ + "googlemaps" + ] }, "include": ["src"] }