2025-01-24 20:05:48 +01:00
import React from "react" ;
import { useEffect } from "react" ;
import { useState } from "react" ;
import { useSearchParams } from "react-router-dom" ;
import PropertySpaceTile from "@/components/frontend/PropertySpaceTile" ;
import "react-calendar/dist/Calendar.css" ;
import { callCustomAPI } from "@/utils/callCustomAPI" ;
import PeopleIcon from "@/components/frontend/icons/PeopleIcon" ;
import { GlobalContext } from "@/globalContext" ;
import { useContext } from "react" ;
import CustomSelect from "@/components/frontend/CustomSelect" ;
import FilterIcon from "@/components/frontend/icons/FilterIcon" ;
import { Tooltip } from "react-tooltip" ;
import NoteIcon from "@/components/frontend/icons/NoteIcon" ;
import { formatDate } from "@/utils/date-time-utils" ;
import { DRAFT _STATUS , SPACE _STATUS } from "@/utils/constants" ;
import { useForm } from "react-hook-form" ;
2025-07-02 15:02:43 +01:00
// import CustomLocationAutoCompleteV2 from "@/components/CustomLocationAutoCompleteV2";
2025-01-24 20:05:48 +01:00
import DatePickerV3 from "@/components/DatePickerV3" ;
import { isValidDate , parseSearchParams } from "@/utils/utils" ;
import FilterCheckBoxesV2 from "@/components/FilterCheckBoxesV2" ;
import MkdSDK from "@/utils/MkdSDK" ;
import useAmenityCategories from "@/hooks/api/useAmenityCategories" ;
2025-07-02 15:02:43 +01:00
import CustomStaticLocationAutoCompleteV2 from "@/components/CustomStaticLocationAutoCompleteV2" ;
2025-01-24 20:05:48 +01:00
const prices = [
{ name : "$0 - $30" , id : 0 } ,
{ name : "$31 - $60" , id : 1 } ,
{ name : "$60 - $90" , id : 2 } ,
{ name : "$90 - $120" , id : 3 } ,
{ name : "$120 - $150" , id : 4 } ,
{ name : "$150 - $180" , id : 5 } ,
] ;
const capacity = [
{ name : "0 - 4" , id : 0 } ,
{ name : "5 - 9" , id : 1 } ,
{ name : "10 - 14" , id : 2 } ,
{ name : "15 - 19" , id : 3 } ,
{ name : "20 - 24" , id : 4 } ,
{ name : "25 - 30" , id : 5 } ,
{ name : "Greater Than 30" , id : 6 } ,
] ;
const reviews = [
{ name : "4" , id : 0 } ,
{ name : "3" , id : 1 } ,
{ name : "2" , id : 2 } ,
{ name : "1" , id : 3 } ,
] ;
const sdk = new MkdSDK ( ) ;
const ctrl = new AbortController ( ) ;
const SearchPage = ( ) => {
const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
2025-07-02 15:02:43 +01:00
const { dispatch : globalDispatch , state : globalState } =
useContext ( GlobalContext ) ;
2025-01-24 20:05:48 +01:00
const [ filterPopup , setFilterPopup ] = useState ( false ) ;
const spaceCategories = globalState . spaceCategories ;
const amenityCategories = useAmenityCategories ( ) ;
const [ propertySpaces , setPropertySpaces ] = useState ( [ ] ) ;
const [ render , forceRender ] = useState ( false ) ;
const { handleSubmit , control , setValue , resetField , register } = useForm ( {
defaultValues : ( ( ) => {
const params = parseSearchParams ( searchParams ) ;
return {
... params ,
location : params . location ? ? "" ,
booking _start _time : "" ,
category : params . category ? . split ( "," ) || [ ] ,
capacity : params . capacity ? . split ( "," ) || [ ] ,
price : params . price ? . split ( "," ) || [ ] ,
amenity : params . amenity ? . split ( "," ) || [ ] ,
review : params . review ? . split ( "," ) || [ ] ,
} ;
} ) ( ) ,
} ) ;
const [ sortAsc , setSortAsc ] = useState ( false ) ;
async function fetchSpaces ( ) {
const params = parseSearchParams ( searchParams ) ;
2025-07-02 15:02:43 +01:00
const location = params . location ? . split ( "," ) ;
2025-01-24 20:05:48 +01:00
const d = new Date ( params . booking _start _time || undefined ) ;
const filter = {
... params ,
booking _start _time : isNaN ( d ) ? undefined : d ,
category : params . category ? . split ( "," ) || [ ] ,
price : params . price ? . split ( "," ) || [ ] ,
capacity : params . capacity ? . split ( "," ) || [ ] ,
amenity : params . amenity ? . split ( "," ) || [ ] ,
review : params . review ? . split ( "," ) || [ ] ,
} ;
globalDispatch ( { type : "START_LOADING" } ) ;
// make sure only approved and non-draft spaces
2025-07-02 15:02:43 +01:00
var where = [
` ergo_property_spaces.space_status = ${ SPACE _STATUS . APPROVED } AND schedule_template_id IS NOT NULL AND ergo_property_spaces_images.is_approved = 1 AND ergo_property_spaces.draft_status = ${ DRAFT _STATUS . COMPLETED } AND ergo_property_spaces.deleted_at IS NULL ` ,
] ;
2025-01-24 20:05:48 +01:00
// use data.location to search address, city, country and zip
if ( filter . location ) {
where . push (
2025-07-02 15:02:43 +01:00
` (ergo_property.address_line_1 LIKE '% ${
filter . location
} %' OR ergo_property.address_line_2 LIKE '% ${
filter . location
} %' OR ergo_property.city LIKE '% ${
location [ 0 ] && location [ 0 ]
} %' OR ergo_property.country LIKE '% ${
location . length === 1
? location [ 0 ]
: location . length === 2
? location [ 1 ]
: location [ 2 ]
} %' OR ergo_property.zip LIKE '% ${
filter . location
} %' OR ergo_property.name LIKE '% ${ filter . location } %') `
2025-01-24 20:05:48 +01:00
) ;
}
if ( filter . size ) {
where . push ( ` ergo_property_spaces.size = ${ filter . size } ` ) ;
}
if ( filter . capacity . length > 0 ) {
2025-07-02 15:02:43 +01:00
if ( filter . capacity [ filter . capacity . length - 1 ] !== "Greater Than 30" ) {
const str = filter . capacity [ filter . capacity . length - 1 ] ; // Get the first (and only) element from the array
const numbers = str . split ( "-" ) . map ( ( num ) => num . trim ( ) ) ; // Split the string and trim spaces
2025-01-24 20:05:48 +01:00
const [ num1 , num2 ] = numbers ; // Destructure the resulting array to get the numbers
2025-07-02 15:02:43 +01:00
where . pop ( ) ;
2025-01-24 20:05:48 +01:00
where . push (
2025-07-02 15:02:43 +01:00
` ergo_property_spaces.max_capacity BETWEEN ${ num1 } AND ${ num2 } `
2025-01-24 20:05:48 +01:00
) ;
} else {
2025-07-02 15:02:43 +01:00
where . push ( ` ergo_property_spaces.max_capacity > 30 ` ) ;
2025-01-24 20:05:48 +01:00
}
}
if ( filter . category . length > 0 ) {
2025-07-02 15:02:43 +01:00
where . push (
` ( ${ filter . category
. map ( ( cg ) => ` ergo_spaces.category LIKE '% ${ cg } %' ` )
. join ( " OR " ) } ) `
) ;
2025-01-24 20:05:48 +01:00
}
if ( filter . amenity . length > 0 ) {
2025-07-02 15:02:43 +01:00
where . push (
` ( ${ filter . amenity
. map ( ( am ) => ` ergo_amenity.name LIKE '% ${ am } %' ` )
. join ( " OR " ) } ) `
) ;
2025-01-24 20:05:48 +01:00
}
if ( filter . review . length > 0 ) {
2025-07-02 15:02:43 +01:00
where . push (
` ( ${ filter . review
. map ( ( rv ) => ` ER.average_space_rating >= ${ rv . replace ( "+" , "" ) } ` )
. join ( " OR " ) } ) `
) ;
2025-01-24 20:05:48 +01:00
}
if ( filter . price . length > 0 ) {
where . push (
` ( ${ filter . price
. filter ( ( pr ) => pr . trim ( ) != "" )
. map ( ( pr ) => pr . split ( "-" ) )
2025-07-02 15:02:43 +01:00
. map (
( [ from , to ] ) =>
` ergo_property_spaces.rate BETWEEN ${ from
. trim ( )
. slice ( 1 ) } AND ${ to . trim ( ) . slice ( 1 ) } `
)
. join ( " OR " ) } ) `
2025-01-24 20:05:48 +01:00
) ;
}
try {
const user _id = Number ( localStorage . getItem ( "user" ) ) ;
const result = await sdk . callRawAPI (
"/v2/api/custom/ergo/popular/PAGINATE" ,
2025-07-02 15:02:43 +01:00
{
page : 1 ,
limit : 10000 ,
user _id : Number ( user _id ) ,
where ,
booking _start _time : isValidDate ( filter . booking _start _time || "" )
? new Date ( filter . booking _start _time ) . toISOString ( )
: undefined ,
} ,
2025-01-24 20:05:48 +01:00
"POST" ,
2025-07-02 15:02:43 +01:00
ctrl . signal
2025-01-24 20:05:48 +01:00
) ;
setPropertySpaces ( result . list ) ;
} catch ( err ) {
console . log ( "err" , err ) ;
globalDispatch ( {
type : "SHOW_ERROR" ,
payload : {
heading : "Operation failed" ,
message : err . message ,
} ,
} ) ;
}
globalDispatch ( { type : "STOP_LOADING" } ) ;
}
useEffect ( ( ) => {
if ( isValidDate ( searchParams . get ( "booking_start_time" ) ) ) {
2025-07-02 15:02:43 +01:00
setValue (
"booking_start_time" ,
new Date ( searchParams . get ( "booking_start_time" ) ) ,
{ shouldDirty : true }
) ;
2025-01-24 20:05:48 +01:00
}
} , [ ] ) ;
useEffect ( ( ) => {
if ( render ) {
fetchSpaces ( ) ;
}
} , [ render ] ) ;
const removeFilter = ( searchField , arrEl ) => {
if ( ! arrEl ) {
setValue ( searchField , "" ) ;
searchParams . set ( searchField , "" ) ;
setSearchParams ( searchParams ) ;
} else {
const prev = searchParams . get ( searchField ) ? ? "" ;
const arr = prev ? . split ( "," ) || [ ] ;
const removeElIndex = arr . indexOf ( arrEl ) ;
if ( removeElIndex > - 1 ) {
arr . splice ( removeElIndex , 1 ) ;
setValue ( searchField , arr ) ;
searchParams . set ( searchField , arr . join ( "," ) ) ;
}
}
setSearchParams ( searchParams ) ;
} ;
async function onSubmit ( data ) {
if ( globalState . location && globalState . location . includes ( "undefined" ) ) {
const parts = globalState . location . split ( "," ) ;
const result = parts [ 0 ] . trim ( ) ;
globalState . location = result ;
}
searchParams . set ( "location" , globalState . location ) ;
2025-07-02 15:02:43 +01:00
searchParams . set (
"booking_start_time" ,
isValidDate ( data . booking _start _time )
? data . booking _start _time . toISOString ( )
: ""
) ;
2025-01-24 20:05:48 +01:00
searchParams . set ( "category" , data . category . join ( "," ) ) ;
searchParams . set ( "price" , data . price . join ( "," ) ) ;
searchParams . set ( "amenity" , data . amenity . join ( "," ) ) ;
searchParams . set ( "review" , data . review . join ( "," ) ) ;
if ( data . max _capacity !== "NaN" ) {
searchParams . set ( "max_capacity" , Number ( data . max _capacity ) ) ;
}
2025-07-02 15:02:43 +01:00
searchParams . set ( "capacity" , data . capacity ) ;
2025-01-24 20:05:48 +01:00
setSearchParams ( searchParams ) ;
}
useEffect ( ( ) => {
fetchSpaces ( ) ;
} , [ searchParams ] ) ;
const sortRating = ( a , b ) => {
if ( sortAsc == 1 ) {
return ( a . average _space _rating ? ? 0 ) - ( b . average _space _rating ? ? 0 ) ;
}
return ( b . average _space _rating ? ? 0 ) - ( a . average _space _rating ? ? 0 ) ;
} ;
return (
2025-07-02 15:02:43 +01:00
< div className = 'min-h-screen bg-white' >
< section className = 'container mx-auto mb-[24px] bg-white px-6 pt-[120px] normal-case 2xl:px-32' >
2025-01-24 20:05:48 +01:00
< form
2025-07-02 15:02:43 +01:00
className = 'flex w-full flex-wrap justify-center'
2025-01-24 20:05:48 +01:00
onSubmit = { handleSubmit ( onSubmit ) }
2025-07-02 15:02:43 +01:00
id = 'search-bar'
2025-01-24 20:05:48 +01:00
>
< CustomStaticLocationAutoCompleteV2
/ / type = { true }
/ / control = { control }
/ / setValue = { ( val ) => setValue ( "location" , val ) }
/ / name = "location"
2025-07-02 15:02:43 +01:00
type = 'static'
setValue = { ( val ) =>
globalDispatch ( {
type : "SETLOCATION" ,
payload : {
location : val ,
} ,
} )
}
containerClassName = {
"mb-4 flex min-h-[45px] w-full max-w-full flex-grow items-center gap-2 border-2 px-4 lg:mb-0 lg:w-[unset] lg:border-r-0 lg:border-b-2"
}
className = 'border-0 focus:outline-none'
placeholder = 'Search by city or zip code'
2025-01-24 20:05:48 +01:00
suggestionType = { [ "(regions)" ] }
/ >
2025-07-02 15:02:43 +01:00
< div className = 'relative mb-6 flex h-[45px] w-full cursor-pointer items-center gap-2 border-2 p-2 lg:mb-0 lg:w-1/2 lg:w-[unset] lg:min-w-[230px] lg:border-r-0' >
2025-01-24 20:05:48 +01:00
< DatePickerV3
reset = { ( ) => resetField ( "booking_start_time" ) }
setValue = { ( val ) => setValue ( "booking_start_time" , val ) }
control = { control }
2025-07-02 15:02:43 +01:00
name = 'booking_start_time'
labelClassName = 'justify-between flex-grow flex-row-reverse'
placeholder = 'Select Date'
2025-01-24 20:05:48 +01:00
min = { new Date ( ) }
/ >
< / div >
{ /* <div className="flex h-[45px] w-1/2 items-center gap-2 border-2 px-4 lg:w-[unset] lg:border-r-0">
<PeopleIcon />
<input
type="number"
placeholder="2 People (Max Capacity)"
className="remove-arrow w-full focus:outline-none"
{...register("max_capacity")}
/>
</div> */ }
< button
2025-07-02 15:02:43 +01:00
className = 'login-btn-gradient mb-4 w-full whitespace-nowrap p-2 px-6 text-white disabled:text-[#98A2B3] lg:mb-0 lg:w-[unset]'
type = 'submit'
id = 'update-search'
2025-01-24 20:05:48 +01:00
>
Update Search
< / button >
< / form >
2025-07-02 15:02:43 +01:00
< div className = 'block lg:hidden' >
2025-01-24 20:05:48 +01:00
< button
2025-07-02 15:02:43 +01:00
type = 'button'
className = 'mb-6 flex w-full items-center justify-center gap-2 border-2 py-2 text-center'
2025-01-24 20:05:48 +01:00
onClick = { ( ) => setFilterPopup ( true ) }
>
< FilterIcon / >
Filters
< / button >
2025-07-02 15:02:43 +01:00
< div className = 'snap-scroll flex gap-4' >
2025-01-24 20:05:48 +01:00
{ Object . entries ( parseSearchParams ( searchParams ) ) . map ( ( [ k , v ] ) => {
if ( ! v ) return null ;
if ( k == "booking_start_time" ) {
return (
2025-07-02 15:02:43 +01:00
< span className = 'whitespace-nowrap rounded-[50px] bg-[#F2F4F7] px-[16px] py-[6px] text-[#475467]' >
2025-01-24 20:05:48 +01:00
{ formatDate ( v ) }
< button
2025-07-02 15:02:43 +01:00
type = 'button'
className = 'ml-3'
2025-01-24 20:05:48 +01:00
onClick = { ( ) => removeFilter ( k ) }
>
& # x2715 ;
< / button >
< / span >
) ;
}
if ( k == "max_capacity" && v == 0 ) {
return ;
}
let vArr = v . split ( "," ) ;
if ( vArr . length > 0 ) {
return vArr . map ( ( filter , i ) => (
< span
key = { i }
2025-07-02 15:02:43 +01:00
className = 'whitespace-nowrap rounded-[50px] bg-[#F2F4F7] px-[16px] py-[6px] text-[#475467]'
2025-01-24 20:05:48 +01:00
>
{ filter }
< button
2025-07-02 15:02:43 +01:00
type = 'button'
className = 'ml-3'
2025-01-24 20:05:48 +01:00
onClick = { ( ) => removeFilter ( k , filter ) }
>
& # x2715 ;
< / button >
< / span >
) ) ;
}
return (
< span
key = { k }
2025-07-02 15:02:43 +01:00
className = 'whitespace-nowrap rounded-[50px] bg-[#F2F4F7] px-[16px] py-[6px] text-[#475467]'
2025-01-24 20:05:48 +01:00
>
{ v }
< button
2025-07-02 15:02:43 +01:00
type = 'button'
className = 'ml-3'
2025-01-24 20:05:48 +01:00
onClick = { ( ) => removeFilter ( k ) }
>
& # x2715 ;
< / button >
< / span >
) ;
} ) }
< / div >
< / div >
< / section >
2025-07-02 15:02:43 +01:00
< section className = 'search-page-container container mx-auto flex gap-[32px] bg-white px-6 normal-case 2xl:px-32' >
2025-01-24 20:05:48 +01:00
< aside
2025-07-02 15:02:43 +01:00
className = { ` hidden xl:block xl:w-1/5 ${
filterPopup ? "popup-tablet" : ""
} ` }
2025-01-24 20:05:48 +01:00
onClick = { ( ) => setFilterPopup ( false ) }
>
< div
2025-07-02 15:02:43 +01:00
className = { ` ${
filterPopup ? "w-[80%] max-w-[500px] rounded-xl p-5" : ""
} flex flex-col bg-white ` }
2025-01-24 20:05:48 +01:00
onClick = { ( e ) => e . stopPropagation ( ) }
>
{ filterPopup ? (
2025-07-02 15:02:43 +01:00
< div className = 'mb-[18px] flex items-center justify-between border-b pb-2' >
< h3 className = 'text-2xl font-semibold' > Filters < / h3 >
2025-01-24 20:05:48 +01:00
< button
onClick = { ( ) => setFilterPopup ( false ) }
2025-07-02 15:02:43 +01:00
className = 'rounded-full border p-1 px-3 text-2xl font-normal duration-300 hover:bg-gray-200'
2025-01-24 20:05:48 +01:00
>
& # x2715 ;
< / button >
< / div >
) : null }
< div className = { ` ${ filterPopup ? "snap-scroll h-[60vh]" : "" } ` } >
< FilterCheckBoxesV2
control = { control }
2025-07-02 15:02:43 +01:00
name = 'category'
title = 'Spaces'
labelField = 'category'
valueField = 'category'
2025-01-24 20:05:48 +01:00
options = { spaceCategories }
2025-07-02 15:02:43 +01:00
reset = { ( ) => {
resetField ( "category" ) ;
searchParams . set ( "category" , "" ) ;
setSearchParams ( searchParams ) ;
} }
2025-01-24 20:05:48 +01:00
/ >
< FilterCheckBoxesV2
control = { control }
2025-07-02 15:02:43 +01:00
name = 'price'
title = 'Prices'
labelField = 'name'
valueField = 'name'
2025-01-24 20:05:48 +01:00
options = { prices }
2025-07-02 15:02:43 +01:00
reset = { ( ) => {
resetField ( "price" ) ;
searchParams . set ( "price" , "" ) ;
setSearchParams ( searchParams ) ;
} }
2025-01-24 20:05:48 +01:00
/ >
< FilterCheckBoxesV2
control = { control }
2025-07-02 15:02:43 +01:00
name = 'capacity'
title = 'Capacity'
labelField = 'name'
valueField = 'name'
2025-01-24 20:05:48 +01:00
options = { capacity }
2025-07-02 15:02:43 +01:00
reset = { ( ) => {
resetField ( "capacity" ) ;
searchParams . set ( "capacity" , "" ) ;
setSearchParams ( searchParams ) ;
} }
2025-01-24 20:05:48 +01:00
/ >
< FilterCheckBoxesV2
2025-07-02 15:02:43 +01:00
name = 'amenity'
2025-01-24 20:05:48 +01:00
control = { control }
2025-07-02 15:02:43 +01:00
title = 'Amenities'
2025-01-24 20:05:48 +01:00
options = { amenityCategories }
2025-07-02 15:02:43 +01:00
labelField = 'name'
valueField = 'name'
reset = { ( ) => {
resetField ( "amenity" ) ;
searchParams . set ( "amenity" , "" ) ;
setSearchParams ( searchParams ) ;
} }
2025-01-24 20:05:48 +01:00
/ >
< FilterCheckBoxesV2
2025-07-02 15:02:43 +01:00
name = 'review'
2025-01-24 20:05:48 +01:00
control = { control }
2025-07-02 15:02:43 +01:00
title = 'Reviews'
2025-01-24 20:05:48 +01:00
options = { reviews }
2025-07-02 15:02:43 +01:00
labelField = 'name'
valueField = 'name'
reset = { ( ) => {
resetField ( "review" ) ;
searchParams . set ( "review" , "" ) ;
setSearchParams ( searchParams ) ;
} }
2025-01-24 20:05:48 +01:00
/ >
< / div >
< / div >
< / aside >
2025-07-02 15:02:43 +01:00
< div className = 'mb-16 max-w-full flex-grow xl:w-4/5' >
< div className = 'mb-[15px] flex items-center justify-between' >
2025-01-24 20:05:48 +01:00
< h5 className = { propertySpaces . length == 0 ? "md:invisible" : "" } >
{ propertySpaces . length == 0 ? (
"No results Found"
) : (
< >
{ " " }
2025-07-02 15:02:43 +01:00
Results Found { " " }
< strong className = 'font-semibold' >
( { propertySpaces . length } )
< / strong >
2025-01-24 20:05:48 +01:00
< / >
) }
< / h5 >
< CustomSelect
options = { [
{ label : "Rating (High to Low)" , value : 0 } ,
{ label : "Rating (Low to High)" , value : 1 } ,
] }
onChange = { setSortAsc }
2025-07-02 15:02:43 +01:00
accessor = 'label'
valueAccessor = 'value'
className = 'min-w-[200px]'
2025-01-24 20:05:48 +01:00
listOptionClassName = { "pl-4" }
/ >
< / div >
2025-07-02 15:02:43 +01:00
< div className = 'flex flex-wrap justify-center gap-6 lg:block' >
2025-01-24 20:05:48 +01:00
{ propertySpaces . length == 0 && (
2025-07-02 15:02:43 +01:00
< div className = 'hidden min-h-[300px] items-center justify-center normal-case text-[#667085] md:flex' >
< h2 className = 'flex gap-3' >
2025-01-24 20:05:48 +01:00
< NoteIcon / > No results found
< / h2 >
< / div >
) }
{ propertySpaces . sort ( sortRating ) . map ( ( sp ) => (
< PropertySpaceTile
key = { sp . id }
data = { sp }
forceRender = { forceRender }
/ >
) ) }
< / div >
< / div >
< / section >
< Tooltip
2025-07-02 15:02:43 +01:00
anchorId = 'update-search'
place = 'right'
2025-01-24 20:05:48 +01:00
content = { "Search" }
noArrow
/ >
< / div >
) ;
} ;
export default SearchPage ;