Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] technology filter functionality #85

Merged
merged 9 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions api/maps/AddMarkers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import ReactDOM from 'react-dom/client';
import { Cluster, MarkerClusterer } from '@googlemaps/markerclusterer';
import { useMap } from '@vis.gl/react-google-maps';
Expand All @@ -16,6 +16,8 @@
null,
); // track currently open modal

const [clusterer, setClusterer] = useState<MarkerClusterer | null>(null);

const map = useMap();

const handleMarkerClick = (
Expand Down Expand Up @@ -92,15 +94,19 @@
return mapZoom;
};

const clusterer = useMemo(() => {
if (!map) return null;
useEffect(() => {
if (!map || !projects) return;

// clear previous cluster markers
if (clusterer) {
clusterer.clearMarkers();
}

const renderer = {
render(cluster: Cluster) {
const count = cluster.markers?.length ?? 0;
const position = cluster.position;

// create a container for the custom icon
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
root.render(<ClusterIcon count={count} />);
Expand All @@ -112,22 +118,28 @@
},
};

const setClusterer = new MarkerClusterer({ map, renderer });
const newClusterer = new MarkerClusterer({ map, renderer });

setClusterer.addListener('click', function (cluster: Cluster) {
// Set zoom behavior for clusters
newClusterer.addListener('click', (cluster: Cluster) => {
const mapZoom = map.getZoom() ?? 0;
const minZoom = getMinZoom(cluster, mapZoom);

if (mapZoom && mapZoom < minZoom) {
const idleListener = map.addListener('idle', function () {
const idleListener = map.addListener('idle', () => {
map.setZoom(minZoom);
idleListener.remove();
});
}
});

return setClusterer;
}, [map]);
setClusterer(newClusterer);

// Cleanup on unmount or dependencies change
return () => {
newClusterer.clearMarkers();
setClusterer(null);
};
}, [map, projects]);

Check warning on line 142 in api/maps/AddMarkers.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint, Prettier, and TypeScript compiler

React Hook useEffect has missing dependencies: 'clusterer' and 'getMinZoom'. Either include them or remove the dependency array

Check warning on line 142 in api/maps/AddMarkers.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint, Prettier, and TypeScript compiler

React Hook useEffect has missing dependencies: 'clusterer' and 'getMinZoom'. Either include them or remove the dependency array

return (
<>
Expand Down
3 changes: 3 additions & 0 deletions components/Filter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface FilterProps {
selectedFilters: Filters;
filterChangeHandlers: FilterChangeHandlers;
handleButtonClick: (filter: FilterType) => void;
handleFilterButtonClick: () => void;
}

export default function Filter({
Expand All @@ -24,6 +25,7 @@ export default function Filter({
selectedFilters,
filterChangeHandlers,
handleButtonClick,
handleFilterButtonClick,
}: FilterProps) {
return (
<FilterBackgroundStyles isActive={isActive}>
Expand All @@ -36,6 +38,7 @@ export default function Filter({
icon={filter.icon}
label={filter.label}
currFilter={filter}
handleFilterButtonClick={handleFilterButtonClick}
/>
) : filter.id === 'status' ? (
<StatusDropdown
Expand Down
3 changes: 3 additions & 0 deletions components/FilterBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ export const FilterBar = ({
onFilterChange,
selectedFilters,
setSelectedFilters,
handleFilterButtonClick,
}: {
filters: FilterType[];
onFilterChange: (filter: FilterType) => void;
selectedFilters: Filters;
setSelectedFilters: React.Dispatch<React.SetStateAction<Filters>>;
handleFilterButtonClick: () => void;
}) => {
const [activeFilter, setActiveFilter] = useState<FilterType | null>(null);

Expand Down Expand Up @@ -52,6 +54,7 @@ export const FilterBar = ({
selectedFilters={selectedFilters}
filterChangeHandlers={filterChangeHandlers}
handleButtonClick={handleButtonClick}
handleFilterButtonClick={handleFilterButtonClick}
/>
))}
</FilterContainerStyles>
Expand Down
53 changes: 44 additions & 9 deletions components/MapViewScreen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,54 @@ export default function MapViewScreen({
projectSize: { min: 0, max: 0 },
location: [],
});
const [filteredWithoutSearch, setFilteredWithoutSearch] =
useState<Project[]>(projects);

const handleFilterChange = (filter: FilterType) => {
console.log(filter);
// show projects based on selected filters
const handleFilterButtonClick = () => {
const { status, technology, projectSize, location } = selectedFilters;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure how these are used to filter projects. Perhaps we could setup the logic for filtering by all criteria (built in a way that would allow us to add/remove filters). For each project, we'd need to check that each selected feature is satisfied.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see, that's a good idea! I made the filters under the impression that they wouldn't need to be changed, but I agree that making the logic more flexible would be better in the long run.

The way filtering works currently is that I have a pre-defined object of possible filters (selectedFilters) that the projects can filter through within the handleFilterButtonClick method. selectedFilters gets updated every time a new filter is applied or un-applied. Is there anything specifically you would want me clarify?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wanted to make sure all filters were being used. It seems that the current implementation hardcodes filtering according to the technology criteria. For flexibility purposes, we could iterate over all attributes of the selectedFilters object in a way that is agnostic to what the filters actually are.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see, I think I'm doing that? The technology variable in line 80 is just a list that holds a bunch of strings (or is empty). Then line 82 filters all the projects by checking if its renewable_energy_technology attribute is within this technology list, without caring about specific values. Is this what you mean about not hardcoding the attributes?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, given that this PR is only for the technology filter, your implementation suffices. I was just curious if there was a way to iterate over the selectedFilters object so we could easily implement status, projectSize, and location filters as well. However doing so in a way where each filter is it's own function/reducer that, when applied to a given row, can immediately determine whether it belongs in the output. Does that make sense? It might not be necessary for this implementation, but it's one possible approach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see now, you're referring to the general filters. I think we can definitely do that and it makes sense to do so- this probably will involve some mapping of the filters to the project attributes in question. And then the method can just do an agnostic loop over that mapping to filter the projects. Thanks Ronnie! I'll adjust my PR for this.

console.log(
'status: ',
status,
'technology: ',
technology,
'projectSize: ',
projectSize,
'location: ',
location,
);
// add all filtering logic here
const filteredProjects = projects?.filter(project =>
technology.includes(project.renewable_energy_technology),
);
setFilteredProjects(filteredProjects);
setFilteredWithoutSearch(filteredProjects);
};

// clear filters
// const clearFilters = () => {
// setSelectedFilters({
// status: [],
// technology: [],
// projectSize: { min: 0, max: 0 },
// location: [],
// });
// setFilteredProjects(projects);
// setFilteredWithoutSearch(projects);
// };

// search within filtered projects
useEffect(() => {
let filtered: Project[] = [];
filtered =
projects?.filter(project =>
const searchedProjects =
filteredWithoutSearch?.filter(project =>
project.project_name.toLowerCase().includes(searchTerm.toLowerCase()),
) ?? null;
) ?? [];
setFilteredProjects(searchedProjects);
}, [searchTerm, filteredWithoutSearch, setFilteredProjects]);

setFilteredProjects(filtered);
}, [projects, searchTerm, setFilteredProjects]);
const handleFilterChange = (filter: FilterType) => {
console.log(filter);
};

return (
<>
Expand All @@ -74,8 +108,9 @@ export default function MapViewScreen({
onFilterChange={handleFilterChange}
selectedFilters={selectedFilters}
setSelectedFilters={setSelectedFilters}
handleFilterButtonClick={handleFilterButtonClick}
/>
<Map projects={projects} />
<Map projects={filteredProjects} />
<ProjectsListingModal projects={filteredProjects} />
</>
);
Expand Down
7 changes: 6 additions & 1 deletion components/TechnologyDropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface TechnologyDropdownProps {
icon: React.ReactNode;
label: string;
currFilter: FilterType;
handleFilterButtonClick: () => void;
}

export default function TechnologyDropdown({
Expand All @@ -44,6 +45,7 @@ export default function TechnologyDropdown({
icon,
label,
currFilter,
handleFilterButtonClick,
}: TechnologyDropdownProps) {
const filter = {
categories: [
Expand Down Expand Up @@ -169,7 +171,10 @@ export default function TechnologyDropdown({
))}
</div>
))}
<ApplyButtonStyles isActive={isApplyButtonActive}>
<ApplyButtonStyles
isActive={isApplyButtonActive}
onClick={handleFilterButtonClick}
>
<ApplyFiltersText>APPLY</ApplyFiltersText>
</ApplyButtonStyles>
</FilterContentDiv>
Expand Down