-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1669 from airqo-platform/PLAT-customize-locations
[PLATFORM] Create and Update User Preferences
- Loading branch information
Showing
20 changed files
with
639 additions
and
111 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
176 changes: 176 additions & 0 deletions
176
platform/src/common/components/Customise/LocationsContentComponent.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { | ||
setSelectedLocations, | ||
getAllGridLocations, | ||
} from '@/lib/store/services/deviceRegistry/GridsSlice'; | ||
import SearchIcon from '@/icons/Common/search_md.svg'; | ||
import LocationIcon from '@/icons/SideBar/Sites.svg'; | ||
import TrashIcon from '@/icons/Actions/bin_icon.svg'; | ||
import StarIcon from '@/icons/Actions/star_icon.svg'; | ||
import StarIconLight from '@/icons/Actions/star_icon_light.svg'; | ||
import DragIcon from '@/icons/Actions/drag_icon.svg'; | ||
import DragIconLight from '@/icons/Actions/drag_icon_light.svg'; | ||
|
||
const LocationsContentComponent = () => { | ||
const dispatch = useDispatch(); | ||
|
||
const gridLocationsState = useSelector((state) => state.grids.gridLocations); | ||
const gridSitesLocations = gridLocationsState.map((grid) => grid.sites); | ||
const gridLocationsData = [].concat(...gridSitesLocations); | ||
|
||
const [location, setLocation] = useState(''); | ||
const [inputSelect, setInputSelect] = useState(false); | ||
const [locationArray, setLocationArray] = useState([]); | ||
const [filteredLocations, setFilteredLocations] = useState(gridLocationsData); | ||
const [unSelectedLocations, setUnSelectedLocations] = useState(gridLocationsData); | ||
|
||
const handleLocationEntry = (e) => { | ||
setInputSelect(false); | ||
filterBySearch(e); | ||
setLocation(e.target.value); | ||
}; | ||
|
||
const toggleInputSelect = () => { | ||
setFilteredLocations(unSelectedLocations); | ||
inputSelect ? setInputSelect(false) : setInputSelect(true); | ||
}; | ||
|
||
const filterBySearch = (e) => { | ||
const query = e.target.value; | ||
let locationList = [...unSelectedLocations]; | ||
locationList = locationList.filter((location) => { | ||
return location.name.toLowerCase().indexOf(query.toLowerCase()) !== -1; | ||
}); | ||
setFilteredLocations(locationList); | ||
}; | ||
|
||
const handleLocationSelect = (item) => { | ||
unSelectedLocations.includes(item) | ||
? setUnSelectedLocations(unSelectedLocations.filter((location) => location._id !== item._id)) | ||
: null; | ||
setLocationArray((locations) => [...locations, item]); | ||
setInputSelect(true); | ||
setLocation(''); | ||
}; | ||
|
||
const removeLocation = (item) => { | ||
setLocationArray(locationArray.filter((location) => location._id !== item._id)); | ||
setUnSelectedLocations((locations) => [...locations, item]); | ||
}; | ||
|
||
// TODO: HandleSubmit function that updates user defaults endpoint with the selected locations | ||
|
||
useEffect(() => { | ||
dispatch(getAllGridLocations()); | ||
}, []); | ||
|
||
return ( | ||
<form> | ||
<div className='mt-6'> | ||
<div className='w-full flex flex-row items-center justify-start'> | ||
<div className='flex items-center justify-center pl-3 bg-white border h-12 rounded-lg rounded-r-none border-r-0 border-input-light-outline focus:border-input-light-outline'> | ||
<SearchIcon /> | ||
</div> | ||
<input | ||
onChange={(e) => { | ||
handleLocationEntry(e); | ||
}} | ||
onClick={() => toggleInputSelect()} | ||
value={location} | ||
placeholder='Search Villages, Cities or Country' | ||
className='input text-sm text-secondary-neutral-light-800 w-full h-12 ml-0 rounded-lg bg-white border-l-0 rounded-l-none border-input-light-outline focus:border-input-light-outline' | ||
/> | ||
</div> | ||
{location !== '' && ( | ||
<div | ||
className={`bg-white max-h-48 overflow-y-scroll px-3 pt-2 pr-1 my-1 border border-input-light-outline rounded-md ${ | ||
inputSelect ? 'hidden' : 'relative' | ||
}`}> | ||
{filteredLocations.length > 0 ? ( | ||
filteredLocations.map((location, key) => ( | ||
<div | ||
className='flex flex-row justify-start items-center mb-0.5 text-sm w-full hover:cursor-pointer' | ||
onClick={() => { | ||
handleLocationSelect(location); | ||
}} | ||
key={key}> | ||
<LocationIcon /> | ||
<div className='text-sm ml-1 text-black capitalize'>{location.name}</div> | ||
</div> | ||
)) | ||
) : ( | ||
<div className='flex flex-row justify-start items-center mb-0.5 text-sm w-full'> | ||
<LocationIcon /> | ||
<div className='text-sm ml-1 text-black font-medium capitalize'> | ||
Location not found | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
)} | ||
</div> | ||
{inputSelect && ( | ||
<div className='mt-4'> | ||
{locationArray.length > 0 ? ( | ||
locationArray.map((location) => ( | ||
<div | ||
className='border rounded-lg bg-secondary-neutral-light-25 border-input-light-outline flex flex-row justify-between items-center p-3 w-full mb-2' | ||
key={location._id}> | ||
<div className='flex flex-row items-center overflow-x-clip'> | ||
<div> | ||
<DragIcon /> | ||
</div> | ||
<span className='text-sm text-secondary-neutral-light-800 font-medium'> | ||
{location.name} | ||
</span> | ||
</div> | ||
<div className='flex flex-row'> | ||
<div | ||
className='mr-1 hover:cursor-pointer' | ||
onClick={() => removeLocation(location)}> | ||
<TrashIcon /> | ||
</div> | ||
<div className='bg-primary-600 rounded-md p-2 flex items-center justify-center hover:cursor-pointer'> | ||
<StarIcon /> | ||
</div> | ||
</div> | ||
</div> | ||
)) | ||
) : ( | ||
<></> | ||
)} | ||
</div> | ||
)} | ||
<div className='mt-6'> | ||
<h3 className='text-sm text-black-800 font-semibold'>Suggestions</h3> | ||
<div className='mt-3'> | ||
{unSelectedLocations.length > 0 && | ||
unSelectedLocations.slice(0, 15).map((location) => ( | ||
<div | ||
className='border rounded-lg bg-secondary-neutral-light-25 border-input-light-outline flex flex-row justify-between items-center p-3 w-full mb-2' | ||
key={location._id}> | ||
<div className='flex flex-row items-center overflow-x-clip'> | ||
<div> | ||
<DragIconLight /> | ||
</div> | ||
<span className='text-sm text-secondary-neutral-light-800 font-medium'> | ||
{location.name} | ||
</span> | ||
</div> | ||
<div className='flex flex-row'> | ||
<div | ||
className='border border-input-light-outline rounded-md p-2 flex items-center justify-center hover:cursor-pointer' | ||
onClick={() => handleLocationSelect(location)}> | ||
<StarIconLight /> | ||
</div> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</form> | ||
); | ||
}; | ||
|
||
export default LocationsContentComponent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React, { useState } from 'react'; | ||
import CloseIcon from '@/icons/Actions/close.svg'; | ||
import LocationsContentComponent from './LocationsContentComponent'; | ||
|
||
const CustomiseLocationsComponent = ({ toggleCustomise }) => { | ||
const [selectedTab, setSelectedTab] = useState('locations'); | ||
|
||
const handleSelectedTab = (tab) => { | ||
setSelectedTab(tab); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<div | ||
className='absolute right-0 top-0 w-full lg:w-3/12 h-full overflow-y-scroll bg-white z-20 border-l-grey-50 px-6' | ||
style={{ boxShadow: '0px 16px 32px 0px rgba(83, 106, 135, 0.20)' }}> | ||
<div className='flex flex-row justify-between items-center mt-6'> | ||
<h3 className='text-xl text-black-800 font-semibold'>Customise</h3> | ||
<div | ||
className='p-3 rounded-md border border-secondary-neutral-light-100 bg-white hover:cursor-pointer' | ||
onClick={() => toggleCustomise()}> | ||
<CloseIcon /> | ||
</div> | ||
</div> | ||
<div className='mt-6'> | ||
<p className='text-grey-350 text-sm font-normal'> | ||
Select at least 4 locations you would like to feature on your overview page. | ||
</p> | ||
</div> | ||
<div className='mt-6'> | ||
<div className='flex flex-row justify-center items-center bg-secondary-neutral-light-25 rounded-md border border-secondary-neutral-light-50 p-1'> | ||
<div | ||
onClick={() => handleSelectedTab('locations')} | ||
className={`px-3 py-2 flex justify-center items-center w-full hover:cursor-pointer text-sm font-medium text-secondary-neutral-light-600${ | ||
selectedTab === 'locations' ? 'border rounded-md bg-white shadow-sm' : '' | ||
}`}> | ||
Locations | ||
</div> | ||
<div | ||
onClick={() => handleSelectedTab('pollutants')} | ||
className={`px-3 py-2 flex justify-center items-center w-full hover:cursor-pointer text-sm font-medium text-secondary-neutral-light-600${ | ||
selectedTab === 'pollutants' ? 'border rounded-md bg-white shadow-sm' : '' | ||
}`}> | ||
Pollutants | ||
</div> | ||
</div> | ||
</div> | ||
{selectedTab === 'locations' && <LocationsContentComponent />} | ||
{/* TODO: Pollutant component and post selection to user defaults */} | ||
</div> | ||
<div className='absolute w-full lg:w-3/12 bg-white z-30 bottom-0 right-0 border-t border-input-light-outline py-4 px-6'> | ||
<div className='flex flex-row justify-end items-center'> | ||
<button className='mr-3 border border-input-light-outline text-sm text-secondary-neutral-light-800 font-medium py-3 px-4 rounded-lg'> | ||
Cancel | ||
</button> | ||
{/* TODO: Update user preferences onclick */} | ||
<button className='bg-primary-600 text-sm text-white font-medium py-3 px-4 rounded-lg'> | ||
Apply | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default CustomiseLocationsComponent; |
Oops, something went wrong.