diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-07-29 09:46:05 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-07-29 09:46:05 +0700 |
| commit | 077467cf53b46d8049df8b812577cd1a03011eba (patch) | |
| tree | 0dc641a9acb1237a3caca3f7f8a157a3e938c0b8 /src/lib/maps | |
| parent | 0d28dc8ff5fb8c5399e356ed6ecae4fce2019ca6 (diff) | |
| parent | dc31efb2fec4c7b79917324d922ae820c4b5bb50 (diff) | |
<hafid> merging new release
Diffstat (limited to 'src/lib/maps')
| -rw-r--r-- | src/lib/maps/components/PinPointMap.jsx | 226 | ||||
| -rw-r--r-- | src/lib/maps/stores/useMaps.js | 32 |
2 files changed, 258 insertions, 0 deletions
diff --git a/src/lib/maps/components/PinPointMap.jsx b/src/lib/maps/components/PinPointMap.jsx new file mode 100644 index 00000000..c46d838a --- /dev/null +++ b/src/lib/maps/components/PinPointMap.jsx @@ -0,0 +1,226 @@ +import React, { useState, useCallback, useRef, useEffect } from 'react'; +import { + GoogleMap, + useJsApiLoader, + Marker, + Autocomplete, +} from '@react-google-maps/api'; +import { useMaps } from '../stores/useMaps'; +import { LocateFixed, MapPinIcon } from 'lucide-react'; +import { Button } from '@chakra-ui/react'; + +const containerStyle = { + width: '100%', + height: '400px', +}; + +const defaultCenter = { + lat: -6.2, + lng: 106.816666, +}; + +const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress }) => { + const { isLoaded } = useJsApiLoader({ + googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY, + libraries: ['places'], + }); + + const { + addressMaps, + setAddressMaps, + selectedPosition, + setSelectedPosition, + setDetailAddress, + setPinedMaps, + } = useMaps(); + + const [tempAddress, setTempAddress] = useState(initialAddress || ''); + const [tempPosition, setTempPosition] = useState( + initialLatitude && initialLongitude + ? { lat: parseFloat(initialLatitude), lng: parseFloat(initialLongitude) } + : selectedPosition.lat && selectedPosition.lng + ? selectedPosition + : defaultCenter + ); + + const [markerIcon, setMarkerIcon] = useState(null); + + const autocompleteRef = useRef(null); + + useEffect(() => { + if (isLoaded && window.google) { + setMarkerIcon({ + url: 'https://cdn.pixabay.com/photo/2014/04/03/10/03/google-309740_1280.png', + scaledSize: new window.google.maps.Size(25, 40), + }); + } + + // If we have initial coordinates but no address, fetch the address + if (initialLatitude && initialLongitude && !initialAddress) { + getAddress(parseFloat(initialLatitude), parseFloat(initialLongitude)); + } + }, [isLoaded, initialLatitude, initialLongitude, initialAddress]); + + const getAddressComponent = (components, type) => { + const component = components.find((comp) => comp.types.includes(type)); + return component ? component.long_name : ''; + }; + + const getAddress = async (lat, lng) => { + try { + const response = await fetch( + `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${process.env.NEXT_PUBLIC_GOOGLE_API_KEY}` + ); + const data = await response.json(); + + if (data.results[0]) { + const addressComponents = data.results[0].address_components; + const formattedAddress = data.results[0].formatted_address; + + const details = { + street: + getAddressComponent(addressComponents, 'route') + + ' ' + + getAddressComponent(addressComponents, 'street_number'), + province: getAddressComponent(addressComponents, 'administrative_area_level_1'), + district: getAddressComponent(addressComponents, 'administrative_area_level_2'), + subDistrict: getAddressComponent(addressComponents, 'administrative_area_level_3'), + village: getAddressComponent(addressComponents, 'administrative_area_level_4'), + postalCode: getAddressComponent(addressComponents, 'postal_code'), + }; + + setDetailAddress(details); + setTempAddress(formattedAddress); + } + } catch (error) { + console.error('Error fetching address:', error); + } + }; + + const onMapClick = useCallback((event) => { + const lat = event.latLng.lat(); + const lng = event.latLng.lng(); + const newPosition = { lat, lng }; + setTempPosition(newPosition); + getAddress(lat, lng); + }, []); + + const handlePlaceSelect = () => { + const place = autocompleteRef.current.getPlace(); + if (place && place.geometry) { + const lat = place.geometry.location.lat(); + const lng = place.geometry.location.lng(); + const newPosition = { lat, lng }; + setTempPosition(newPosition); + setTempAddress(place.formatted_address); + getAddress(lat, lng); + } + }; + + const handleUseCurrentLocation = () => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + const lat = position.coords.latitude; + const lng = position.coords.longitude; + const newPosition = { lat, lng }; + setTempPosition(newPosition); + getAddress(lat, lng); + }, + (error) => { + console.error('Error getting current location:', error); + } + ); + } + }; + + const handleSavePinpoint = (event) => { + event.preventDefault(); + if (tempAddress === '') { + alert('Silahkan pilih lokasi terlebih dahulu'); + return; + } + + setSelectedPosition(tempPosition); + setAddressMaps(tempAddress); + setPinedMaps(false); + }; + + return ( + <div className='w-full'> + <h3>Tentukan Pinpoint Lokasi</h3> + + <div style={{ marginBottom: '10px' }}> + {isLoaded ? ( + <Autocomplete + onLoad={(ref) => (autocompleteRef.current = ref)} + onPlaceChanged={handlePlaceSelect} + > + <input + type='text' + placeholder='Cari Alamat...' + value={tempAddress} + onChange={(e) => setTempAddress(e.target.value)} + style={{ width: '100%', padding: '8px' }} + /> + </Autocomplete> + ) : ( + <p>Loading autocomplete...</p> + )} + </div> + + <div> + {isLoaded ? ( + <GoogleMap + mapContainerStyle={containerStyle} + center={tempPosition} + zoom={15} + onClick={onMapClick} + > + {markerIcon && ( + <Marker + position={tempPosition} + draggable={true} + onDragEnd={(e) => { + const lat = e.latLng.lat(); + const lng = e.latLng.lng(); + const newPosition = { lat, lng }; + setTempPosition(newPosition); + getAddress(lat, lng); + }} + icon={markerIcon} + /> + )} + </GoogleMap> + ) : ( + <p>Loading map...</p> + )} + </div> + + <div style={{ marginTop: '20px' }}> + <Button variant='solid' onClick={handleUseCurrentLocation}> + <LocateFixed className='h-6 w-6 text-gray-500 mr-2' /> Gunakan Lokasi Saat Ini + </Button> + </div> + + <div style={{ marginTop: '10px' }}> + <p>PinPoint :</p> + <div className='flex gap-x-2 shadow-md rounded-sm text-gray-500 p-3 items-center'> + <MapPinIcon className='h-8 w-8 text-gray-500 mr-3' /> + <label>{tempAddress || 'Pilih lokasi di peta'}</label> + </div> + </div> + + <div className='mt-6 flex justify-end'> + <button + className='p-3 border border-red-500 bg-red-600 text-white font-semibold rounded-lg' + onClick={handleSavePinpoint} + > + Simpan Lokasi Ini + </button> + </div> + </div> + ); +}; + +export default PinpointLocation;
\ No newline at end of file diff --git a/src/lib/maps/stores/useMaps.js b/src/lib/maps/stores/useMaps.js new file mode 100644 index 00000000..c57a05ad --- /dev/null +++ b/src/lib/maps/stores/useMaps.js @@ -0,0 +1,32 @@ +import { create } from "zustand"; + +const center = { + lat: -6.200000, // Default latitude (Jakarta) + lng: 106.816666, // Default longitude (Jakarta) +}; + +export const useMaps = create((set) => ({ + // State existing + selectedPosition: center, + addressMaps: '', + detailAddress: {}, + pinedMaps: false, + + // State tambahan untuk penyimpanan posisi sementara + tempPositionCreate: null, + tempPositionEdit: null, + + // Setter existing + setSelectedPosition: (position) => set({ selectedPosition: position }), + setAddressMaps: (addressMaps) => set({ addressMaps }), + setDetailAddress: (detailAddress) => set({ detailAddress }), + setPinedMaps: (pinedMaps) => set({ pinedMaps }), + + // Setter tambahan untuk posisi sementara + setTempPositionCreate: (position) => set({ tempPositionCreate: position }), + setTempPositionEdit: (position) => set({ tempPositionEdit: position }), + + // Opsional: Reset jika ingin clear saat keluar halaman + resetTempPositionCreate: () => set({ tempPositionCreate: null }), + resetTempPositionEdit: () => set({ tempPositionEdit: null }), +})); |
