summaryrefslogtreecommitdiff
path: root/src/lib/maps
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-07-29 09:46:05 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-07-29 09:46:05 +0700
commit077467cf53b46d8049df8b812577cd1a03011eba (patch)
tree0dc641a9acb1237a3caca3f7f8a157a3e938c0b8 /src/lib/maps
parent0d28dc8ff5fb8c5399e356ed6ecae4fce2019ca6 (diff)
parentdc31efb2fec4c7b79917324d922ae820c4b5bb50 (diff)
<hafid> merging new release
Diffstat (limited to 'src/lib/maps')
-rw-r--r--src/lib/maps/components/PinPointMap.jsx226
-rw-r--r--src/lib/maps/stores/useMaps.js32
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 }),
+}));