Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 9 additions & 4 deletions api/supabase/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const columnMap: Record<string, string> = {

export default async function queryProjects(): Promise<Project[]> {
const { data: projects, error } = await supabase
.from('Projects_user_testing')
.from('Projects')
.select('*')
.not('longitude', 'is', null)
.not('latitude', 'is', null)
.eq('approved', true);
.not('latitude', 'is', null);
// .eq('approved', true);

if (error) {
throw new Error(`Error fetching projects: ${error.message}`);
Expand Down Expand Up @@ -101,5 +101,10 @@ export async function queryCoordsForName(category: string, name: string) {
return [];
}

return data.map(row => Object.values(row)[0]);
if (!data[0]) {
return [];
}

const json_data = JSON.parse(data[0]['coordinates']);
return json_data;
}
15 changes: 15 additions & 0 deletions components/Filter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface FilterProps {
maxDefault: number;
setMaxDefault: React.Dispatch<React.SetStateAction<number>>;
selectedProjectId: number | null;
map: google.maps.Map | null;
setMap: React.Dispatch<React.SetStateAction<google.maps.Map | null>>;
}

export default function Filter({
Expand All @@ -53,6 +55,7 @@ export default function Filter({
maxDefault,
setMaxDefault,
selectedProjectId,
map,
}: FilterProps) {
const [statusFiltersApplied, setStatusFiltersApplied] = useState(false);
const [technologyFiltersApplied, setTechnologyFiltersApplied] =
Expand All @@ -65,6 +68,10 @@ export default function Filter({

const [appliedCategory, setAppliedCategory] = useState<string | null>(null);

const [currentPolygons, setCurrentPolygons] = useState<
google.maps.Polygon[] | null
>([]);

const applyButtonHandler = (filter: keyof Filters) => {
switch (filter) {
case 'technology':
Expand Down Expand Up @@ -119,6 +126,11 @@ export default function Filter({
case 'location':
setLocationFiltersApplied(false);
setAppliedCategory(null);
// remove all polygons from map
currentPolygons?.forEach(polygon => {
polygon.setMap(null);
});
setCurrentPolygons([]);
break;
}
};
Expand Down Expand Up @@ -199,6 +211,9 @@ export default function Filter({
setAppliedCategory={setAppliedCategory}
applyButtonHandler={applyButtonHandler}
locationFiltersApplied={locationFiltersApplied}
map={map}
currentPolygons={currentPolygons}
setCurrentPolygons={setCurrentPolygons}
></LocationDropdown>
) : // Add other filter dropdown components here
null
Expand Down
6 changes: 6 additions & 0 deletions components/FilterBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface FilterBarProps {
clearFilters: () => void;
projectSizes: number[];
selectedProjectId: number | null;
map: google.maps.Map | null;
setMap: React.Dispatch<React.SetStateAction<google.maps.Map | null>>;
}

export const FilterBar = ({
Expand All @@ -30,6 +32,8 @@ export const FilterBar = ({
projectSizes,
setActiveLocationCategory,
selectedProjectId,
map,
setMap,
}: FilterBarProps) => {
const [activeFilter, setActiveFilter] = useState<FilterType | null>(null);

Expand Down Expand Up @@ -151,6 +155,8 @@ export const FilterBar = ({
setMinDefault={setMinDefault}
setMaxDefault={setMaxDefault}
selectedProjectId={selectedProjectId}
map={map}
setMap={setMap}
/>
))}
</FilterContainerStyles>
Expand Down
164 changes: 134 additions & 30 deletions components/LocationCategoryPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import React, { useState } from 'react';
import { queryCoordsForName } from '@/api/supabase/queries/query';
import COLORS from '@/styles/colors';
import {
ApplyFiltersText,
ClearFiltersText,
PanelTitle,
SearchInput,
} from '@/styles/texts';
import { Filters, FilterType } from '@/types/schema';
import {
Coord,
Filters,
FilterType,
MultiPolygonCoords,
PolygonCoords,
} from '@/types/schema';
import {
BackArrowIcon,
SearchIcon,
Expand All @@ -28,6 +36,63 @@ import {
Underline,
} from './styles';

async function getCoords(
category: string,
name: string,
): Promise<(PolygonCoords[] | MultiPolygonCoords)[]> {
const coords = await queryCoordsForName(category, name);
return coords;
}

function isMultiPolygon(
coords: PolygonCoords[] | MultiPolygonCoords,
): coords is MultiPolygonCoords {
return Array.isArray(coords[0]) && Array.isArray((coords[0] as Coord[])[0]);
}

function drawPolygonsFromCoords(
mapInstance: google.maps.Map,
coords: PolygonCoords[] | MultiPolygonCoords,
bounds: google.maps.LatLngBounds,
): google.maps.Polygon[] {
const polygons: google.maps.Polygon[] = [];

// wrap single polygon in array
const polygonSets = isMultiPolygon(coords) ? coords : [coords];

// create path for each polygon and extend bounds
// need to add all latLngs to path to allow for holes in polygons
const path = [];
for (const poly of polygonSets) {
const subPath = [];
for (const [lng, lat] of poly) {
const latLng: google.maps.LatLngLiteral = {
lat: Number(lat),
lng: Number(lng),
};
bounds.extend(latLng);
subPath.push(latLng);
}
path.push(subPath);
}

const polygon = new google.maps.Polygon({
paths: path,
strokeColor: COLORS.electricBlue,
strokeOpacity: 1,
strokeWeight: 2,
fillColor: COLORS.electricBlue,
fillOpacity: 0.15,
});

if (!polygon) return [];

polygon.setMap(mapInstance);
polygons.push(polygon);

return polygons;
}

export default function LocationCategoryPanel({
onBack,
category,
Expand All @@ -40,6 +105,10 @@ export default function LocationCategoryPanel({
activeCategory,
setAppliedCategory,
applyButtonHandler,
map,
currentPolygons,
setCurrentPolygons,
appliedCategory,
}: {
onBack: () => void;
category: string;
Expand All @@ -55,38 +124,75 @@ export default function LocationCategoryPanel({
activeCategory: string | null;
setAppliedCategory: React.Dispatch<React.SetStateAction<string | null>>;
applyButtonHandler: (filter: keyof Filters) => void;
map: google.maps.Map | null;
currentPolygons: google.maps.Polygon[] | null;
setCurrentPolygons: React.Dispatch<
React.SetStateAction<google.maps.Polygon[] | null>
>;
appliedCategory: string | null;
}) {
const [searchTerm, setSearchTerm] = useState('');
const options: string[] | null = categoryOptionsMap[category] ?? null;
const [selectedItem, setSelectedItem] = useState<string | null>(
selectedLocationFilters[0] ?? null,
);
const [searchTerm, setSearchTerm] = useState('');

const options: string[] | null = categoryOptionsMap[category] ?? null;
const clearButtonHandlerLocation = () => {
clearButtonHandler('location');
setSelectedItem(null);
};

const uniqueOptions = options
? Array.from(new Set(options.map(item => item.trim()))).sort((a, b) =>
a.localeCompare(b, 'en-US', { numeric: true, sensitivity: 'base' }),
)
: [];
function checkBoxClickHandler(option: string): void {
setSelectedLocationFilters({ value: [option], isTemp: true });
setSelectedItem(option);
}

const filteredOptions = uniqueOptions?.filter(item =>
item.toLowerCase().includes(searchTerm.toLowerCase()),
const seen = new Set();
const filteredOptions = options?.filter(option => {
const name = option.toLowerCase();
const matchesSearch = name.includes(searchTerm.toLowerCase());
if (matchesSearch && !seen.has(name)) {
seen.add(name);
return true;
}
return false;
});

filteredOptions?.sort((a, b) =>
a.localeCompare(b, 'en-US', { numeric: true, sensitivity: 'base' }),
);

const applyButtonHandlerLocation = () => {
setAppliedCategory(activeCategory);
applyButtonHandler('location');
};

const clearButtonHandlerLocation = () => {
clearButtonHandler('location');
setSelectedItem(null);
};
if (!selectedItem || !map) return;

function checkBoxClickHandler(item: string): void {
setSelectedLocationFilters({ value: [item], isTemp: true });
setSelectedItem(item);
}
if ((currentPolygons?.length ?? 0) > 0) {
// remove all polygons from map
currentPolygons?.forEach(poly => poly.setMap(null));
setCurrentPolygons([]);
}

getCoords(category, selectedItem).then(coordsList => {
if (!coordsList || coordsList.length === 0) return;
const polygons: google.maps.Polygon[] = [];
const bounds = new google.maps.LatLngBounds();

for (const coords of coordsList) {
const newPolygons = drawPolygonsFromCoords(map, coords, bounds);
polygons.push(...newPolygons);
}

map.fitBounds(bounds);
const currentZoom = map.getZoom();
if (currentZoom !== undefined && currentZoom !== null) {
map.setZoom(currentZoom - 1.5);
}

setCurrentPolygons(polygons);
});
};

return (
<PanelContainer>
Expand Down Expand Up @@ -114,25 +220,23 @@ export default function LocationCategoryPanel({
<Underline />
</SearchBar>
<ItemContainer>
{filteredOptions?.map(item => (
{filteredOptions?.map(option => (
<LocationCategoryOption
key={item}
label={item}
selected={selectedItem === item}
onClick={() => checkBoxClickHandler(item)}
key={option}
label={option}
selected={selectedItem === option}
onClick={() => checkBoxClickHandler(option)}
/>
))}
</ItemContainer>
</CategoryInnerContainer>

<ApplyClearButtonContainer>
<ApplyButtonStyles
$isActive={selectedItem !== null}
onClick={() => {
if (selectedItem) {
applyButtonHandlerLocation();
}
}}
$isActive={
selectedItem !== null || appliedCategory === activeCategory
}
onClick={applyButtonHandlerLocation}
>
<ApplyFiltersText>APPLY</ApplyFiltersText>
</ApplyButtonStyles>
Expand Down
12 changes: 12 additions & 0 deletions components/LocationDropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
setAppliedCategory,
iconApplied,
locationFiltersApplied,
map,
currentPolygons,
setCurrentPolygons,
}: {
handleButtonClick: (filter: FilterType) => void;
icon: React.ReactNode;
Expand All @@ -65,6 +68,11 @@
applyButtonHandler: (filter: keyof Filters) => void;
iconApplied: React.ReactNode;
locationFiltersApplied: boolean;
map: google.maps.Map | null;
currentPolygons: google.maps.Polygon[] | null;
setCurrentPolygons: React.Dispatch<
React.SetStateAction<google.maps.Polygon[] | null>
>;
}) {
const locationCategories = [
'County',
Expand Down Expand Up @@ -96,7 +104,7 @@
};

fetchAllCategoryOptions();
}, []);

Check warning on line 107 in components/LocationDropdown/index.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint, Prettier, and TypeScript compiler

React Hook useEffect has a missing dependency: 'locationCategories'. Either include it or remove the dependency array

Check warning on line 107 in components/LocationDropdown/index.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint, Prettier, and TypeScript compiler

React Hook useEffect has a missing dependency: 'locationCategories'. Either include it or remove the dependency array

return activeCategory === null ? (
<LocationStyleDiv>
Expand Down Expand Up @@ -201,6 +209,10 @@
activeCategory={activeCategory}
setAppliedCategory={setAppliedCategory}
applyButtonHandler={applyButtonHandler}
map={map}
currentPolygons={currentPolygons}
setCurrentPolygons={setCurrentPolygons}
appliedCategory={appliedCategory}
/>
);
}
2 changes: 2 additions & 0 deletions components/MapViewScreen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ export default function MapViewScreen({
setActiveLocationCategory={setActiveLocationCategory}
projectSizes={projectSizes}
selectedProjectId={selectedProjectId}
map={map}
setMap={setMap}
/>
<Map
projects={projects}
Expand Down
4 changes: 4 additions & 0 deletions types/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ type FilterHandlerArgs = {
export type FilterChangeHandlers = {
[K in keyof FilterHandlerArgs]: (args: FilterHandlerArgs[K]) => void;
};

export type Coord = [number, number];
export type PolygonCoords = Coord[];
export type MultiPolygonCoords = PolygonCoords[][];