summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json1
-rw-r--r--src/lib/address/components/CreateAddress.jsx323
-rw-r--r--src/lib/maps/components/PinPointMap.jsx163
-rw-r--r--src/lib/maps/stores/useMaps.js13
-rw-r--r--src/lib/product/components/ProductSearch.jsx4
5 files changed, 360 insertions, 144 deletions
diff --git a/package.json b/package.json
index a846749c..82ad1d33 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@hookform/resolvers": "^2.9.10",
"@react-email/components": "^0.0.2",
"@react-email/render": "^0.0.6",
+ "@react-google-maps/api": "^2.20.3",
"@tailwindcss/line-clamp": "^0.4.2",
"axios": "^1.1.3",
"camelcase-object-deep": "^1.1.7",
diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx
index 9d70e8fc..70307401 100644
--- a/src/lib/address/components/CreateAddress.jsx
+++ b/src/lib/address/components/CreateAddress.jsx
@@ -1,7 +1,7 @@
import HookFormSelect from '@/core/components/elements/Select/HookFormSelect';
import useAuth from '@/core/hooks/useAuth';
import { useRouter } from 'next/router';
-import { Controller, useForm } from 'react-hook-form';
+import { Controller, set, useForm } from 'react-hook-form';
import * as Yup from 'yup';
import cityApi from '../api/cityApi';
import districtApi from '../api/districtApi';
@@ -14,6 +14,12 @@ import Menu from '@/lib/auth/components/Menu';
import useAddresses from '../hooks/useAddresses';
import stateApi from '../api/stateApi';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import PinPointMap from '../../maps/components/PinPointMap';
+import { Button } from '@chakra-ui/react';
+import { MapPinIcon } from 'lucide-react';
+import { useMaps } from '../../maps/stores/useMaps';
+
const CreateAddress = () => {
const auth = useAuth();
const router = useRouter();
@@ -34,6 +40,8 @@ const CreateAddress = () => {
const [districts, setDistricts] = useState([]);
const [subDistricts, setSubDistricts] = useState([]);
const [filteredTypes, setFilteredTypes] = useState(types); // State to manage filtered types
+ const [pinedMaps, setPinedMaps] = useState(false);
+ const { addressMaps, setAddressMaps } = useMaps();
useEffect(() => {
const loadState = async () => {
@@ -52,7 +60,7 @@ const CreateAddress = () => {
setValue('city', '');
if (watchState) {
const loadCities = async () => {
- let dataCities = await cityApi({stateId: watchState});
+ let dataCities = await cityApi({ stateId: watchState });
dataCities = dataCities.map((city) => ({
value: city.id,
label: city.name,
@@ -133,167 +141,198 @@ const CreateAddress = () => {
};
return (
- <div className='max-w-none md:container mx-auto flex p-0 md:py-10'>
- <div className='hidden md:block w-3/12 pr-4'>
- <Menu />
- </div>
- <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
- <form onSubmit={handleSubmit(onSubmitHandler)}>
- <div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
- <div>
- <label className='form-label mb-2'>Label Alamat</label>
- <Controller
- name='type'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- isSearchable={false}
- options={filteredTypes}
- />
- )}
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.type?.message}
- </div>
+ <>
+ <BottomPopup
+ className=' !h-[75%]'
+ title='Pin Maps Address'
+ active={pinedMaps}
+ close={() => setPinedMaps(false)}
+ >
+ <div className='flex mt-4'>
+ <PinPointMap />
+ </div>
+ </BottomPopup>
+ <div className='max-w-none md:container mx-auto flex p-0 md:py-10'>
+ <div className='hidden md:block w-3/12 pr-4'>
+ <Menu />
+ </div>
+ <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'>
+ <form onSubmit={handleSubmit(onSubmitHandler)}>
+ <div className='mb-4 items-start'>
+ <label className='form-label mb-2'>PinPoint</label>
+ {addressMaps ? (
+ <div className='flex gap-x-2 items-center'>
+ <MapPinIcon class='h-6 w-6 text-gray-500 mr-3' />{' '}
+ <span> {addressMaps} </span>
+ </div>
+ ) : (
+ <Button variant='plain' onClick={() => setPinedMaps(true)}>
+ <MapPinIcon class='h-6 w-6 text-gray-500 mr-3' />
+ Pin Alamat
+ </Button>
+ )}
+
</div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4'>
+ <div>
+ <label className='form-label mb-2'>Label Alamat</label>
+ <Controller
+ name='type'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ isSearchable={false}
+ options={filteredTypes}
+ />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.type?.message}
+ </div>
+ </div>
- <div>
- <label className='form-label mb-2'>Nama</label>
- <input
- {...register('name')}
- placeholder='John Doe'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.name?.message}
+ <div>
+ <label className='form-label mb-2'>Nama</label>
+ <input
+ {...register('name')}
+ placeholder='John Doe'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.name?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Email</label>
- <input
- {...register('email')}
- placeholder='contoh@email.com'
- type='email'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.email?.message}
+ <div>
+ <label className='form-label mb-2'>Email</label>
+ <input
+ {...register('email')}
+ placeholder='contoh@email.com'
+ type='email'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.email?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Mobile</label>
- <input
- {...register('mobile')}
- placeholder='08xxxxxxxx'
- type='tel'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.mobile?.message}
+ <div>
+ <label className='form-label mb-2'>Mobile</label>
+ <input
+ {...register('mobile')}
+ placeholder='08xxxxxxxx'
+ type='tel'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.mobile?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Alamat</label>
- <input
- {...register('street')}
- placeholder='Jl. Bandengan Utara 85A'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.street?.message}
+ <div>
+ <label className='form-label mb-2'>Alamat</label>
+ <input
+ {...register('street')}
+ placeholder='Jl. Bandengan Utara 85A'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.street?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Kode Pos</label>
- <input
- {...register('zip')}
- placeholder='10100'
- type='number'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.zip?.message}
+ <div>
+ <label className='form-label mb-2'>Kode Pos</label>
+ <input
+ {...register('zip')}
+ placeholder='10100'
+ type='number'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.zip?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Provinsi</label>
- <Controller
- name='state'
- control={control}
- render={(props) => (
- <HookFormSelect {...props} options={states} />
- )}
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.state?.message}
+ <div>
+ <label className='form-label mb-2'>Provinsi</label>
+ <Controller
+ name='state'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={states} />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.state?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Kota</label>
- <Controller
- name='city'
- control={control}
- render={(props) => (
- <HookFormSelect {...props} options={cities} disabled={!watchState}/>
- )}
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.city?.message}
+ <div>
+ <label className='form-label mb-2'>Kota</label>
+ <Controller
+ name='city'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ options={cities}
+ disabled={!watchState}
+ />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.city?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Kecamatan</label>
- <Controller
- name='district'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={districts}
- disabled={!watchCity}
- />
- )}
- />
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.district?.message}
+ <div>
+ <label className='form-label mb-2'>Kecamatan</label>
+ <Controller
+ name='district'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ options={districts}
+ disabled={!watchCity}
+ />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.district?.message}
+ </div>
</div>
- </div>
- <div>
- <label className='form-label mb-2'>Kelurahan</label>
- <Controller
- name='subDistrict'
- control={control}
- render={(props) => (
- <HookFormSelect
- {...props}
- options={subDistricts}
- disabled={!watchDistrict}
- />
- )}
- />
+ <div>
+ <label className='form-label mb-2'>Kelurahan</label>
+ <Controller
+ name='subDistrict'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ options={subDistricts}
+ disabled={!watchDistrict}
+ />
+ )}
+ />
+ </div>
</div>
- </div>
- <button
- type='submit'
- className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'
- >
- Simpan
- </button>
- </form>
+ <button
+ type='submit'
+ className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'
+ >
+ Simpan
+ </button>
+ </form>
+ </div>
</div>
- </div>
+ </>
);
};
diff --git a/src/lib/maps/components/PinPointMap.jsx b/src/lib/maps/components/PinPointMap.jsx
new file mode 100644
index 00000000..0781d8a8
--- /dev/null
+++ b/src/lib/maps/components/PinPointMap.jsx
@@ -0,0 +1,163 @@
+import React, { useState, useCallback, useRef } 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 center = {
+ lat: -6.2, // Default latitude (Jakarta)
+ lng: 106.816666, // Default longitude (Jakarta)
+};
+
+const PinpointLocation = () => {
+ const { isLoaded } = useJsApiLoader({
+ googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY, // Pastikan API key ada di .env.local
+ libraries: ['places'],
+ });
+
+ const { addressMaps, setAddressMaps, selectedPosition, setSelectedPosition } =
+ useMaps();
+
+ const [tempAddress, setTempAddress] = useState('');
+ const [tempPosition, setTempPosition] = useState(center);
+
+ const autocompleteRef = useRef(null);
+
+ const onMapClick = useCallback((event) => {
+ const lat = event.latLng.lat();
+ const lng = event.latLng.lng();
+ setTempPosition({ lat, lng });
+ 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();
+ setTempPosition({ lat, lng });
+ setTempAddress(place.formatted_address);
+ }
+ };
+
+ 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]) {
+ setTempAddress(data.results[0].formatted_address);
+ }
+ } catch (error) {
+ console.error('Error fetching address:', error);
+ }
+ };
+
+ const handleUseCurrentLocation = () => {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ const lat = position.coords.latitude;
+ const lng = position.coords.longitude;
+ setTempPosition({ lat, lng });
+ 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);
+ };
+
+ 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...'
+ style={{ width: '100%', padding: '8px' }}
+ />
+ </Autocomplete>
+ ) : (
+ <p>Loading autocomplete...</p>
+ )}
+ </div>
+
+ <div>
+ {isLoaded ? (
+ <GoogleMap
+ mapContainerStyle={containerStyle}
+ center={tempPosition}
+ zoom={15}
+ onClick={onMapClick}
+ >
+ <Marker
+ position={tempPosition}
+ draggable={true}
+ onDragEnd={(e) => onMapClick(e)}
+ icon={{
+ url: 'https://maps.google.com/mapfiles/ms/icons/red-pushpin.png',
+ scaledSize: new window.google.maps.Size(40, 40),
+ }}
+ />
+ </GoogleMap>
+ ) : (
+ <p>Loading map...</p>
+ )}
+ </div>
+
+ <div style={{ marginTop: '20px' }}>
+ <Button
+ variant='solid'
+ onClick={handleUseCurrentLocation}
+ >
+ <LocateFixed class='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 class='h-8 w-8 text-gray-500 mr-3' />
+ <label> {tempAddress}</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;
diff --git a/src/lib/maps/stores/useMaps.js b/src/lib/maps/stores/useMaps.js
new file mode 100644
index 00000000..1720e663
--- /dev/null
+++ b/src/lib/maps/stores/useMaps.js
@@ -0,0 +1,13 @@
+import { create } from "zustand";
+
+const center = {
+ lat: -6.200000, // Default latitude (Jakarta)
+ lng: 106.816666, // Default longitude (Jakarta)
+};
+
+export const useMaps = create((set) => ({
+ selectedPosition: center,
+ addressMaps: '',
+ setSelectedPosition: (position) => set({ selectedPosition: position }),
+ setAddressMaps: (addressMaps) => set({ addressMaps }),
+ })); \ No newline at end of file
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index f7b044aa..3e342bf0 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -501,7 +501,7 @@ const ProductSearch = ({
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ url={`${prefixUrl}?${toQuery(_.omit(query, ['page', 'fq']))}`}
// url={prefixUrl.includes('category') || prefixUrl.includes('lob')? `${prefixUrl}?${toQuery(_.omit(finalQuery, ['page']))}` : `${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
className='mt-6 mb-2'
/>
@@ -691,7 +691,7 @@ const ProductSearch = ({
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ url={`${prefixUrl}?${toQuery(_.omit(query, ['page', 'fq']))}`}
// url={prefixUrl.includes('category') || prefixUrl.includes('lob')? `${prefixUrl}?${toQuery(_.omit(finalQuery, ['page']))}` : `${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
className='!justify-end'
/>