Skip to content
Open
Show file tree
Hide file tree
Changes from 18 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
19 changes: 12 additions & 7 deletions apps/betterangels-backend/shelters/tests/baker_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,18 @@ def get_random_phone_number() -> str:


class related_m2m_unique(related):
def __init__(self, related_model: Any, choices_enum: Any) -> None:
def __init__(self, related_model: Any, choices_enum: Any, min_quantity: int = 0) -> None:
self.related_model = related_model
self.choices_enum = choices_enum
self.min_quantity = min_quantity

def make(self, **attrs: Any) -> Any:
related_objs = set()

# limit to 5 related objects per type, for readability
quantity = random.randint(0, min(len(self.choices_enum), 5))
upper = min(len(self.choices_enum), 5)
lower = min(self.min_quantity, upper) if upper > 0 else 0
quantity = random.randint(lower, upper) if upper > 0 else 0

for i in range(quantity):
choice_value = list(self.choices_enum)[i]
Expand Down Expand Up @@ -149,16 +152,18 @@ def make_services() -> list[Service]:
website=seq("shelter", suffix=".com"), # type: ignore
accessibility=related_m2m_unique(Accessibility, AccessibilityChoices),
cities=make_cities,
demographics=related_m2m_unique(Demographic, DemographicChoices),
demographics=related_m2m_unique(Demographic, DemographicChoices, min_quantity=1),
entry_requirements=related_m2m_unique(EntryRequirement, EntryRequirementChoices),
funders=related_m2m_unique(Funder, FunderChoices),
parking=related_m2m_unique(Parking, ParkingChoices),
pets=related_m2m_unique(Pet, PetChoices),
parking=related_m2m_unique(Parking, ParkingChoices, min_quantity=1),
pets=related_m2m_unique(Pet, PetChoices, min_quantity=1),
room_styles=related_m2m_unique(RoomStyle, RoomStyleChoices),
services=make_services,
shelter_programs=related_m2m_unique(ShelterProgram, ShelterProgramChoices),
shelter_types=related_m2m_unique(ShelterType, ShelterTypeChoices),
spa=related_m2m_unique(SPA, SPAChoices),
special_situation_restrictions=related_m2m_unique(SpecialSituationRestriction, SpecialSituationRestrictionChoices),
storage=related_m2m_unique(Storage, StorageChoices),
special_situation_restrictions=related_m2m_unique(
SpecialSituationRestriction, SpecialSituationRestrictionChoices, min_quantity=1
),
storage=related_m2m_unique(Storage, StorageChoices, min_quantity=1),
)
2 changes: 1 addition & 1 deletion libs/assets/src/icons/svg/locationIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions libs/react/shelter/src/lib/atoms/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { locationAtom, type TLocationSource } from './locationAtom';
export { shelterFiltersAtom } from './shelterFiltersAtom';
export { shelterPropertyFiltersAtom } from './shelterPropertyFiltersAtom';
export { sheltersAtom } from './sheltersAtom';
10 changes: 0 additions & 10 deletions libs/react/shelter/src/lib/atoms/locationAtom.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { atomWithReset } from 'jotai/utils';
import { TShelterPropertyFilters } from '../components/ShelterSearch';

export const nullShelterFilters: TShelterPropertyFilters = {
export const nullShelterPropertyFilters: TShelterPropertyFilters = {
openNow: false,
pets: [],
demographics: [],
Expand All @@ -11,5 +11,5 @@ export const nullShelterFilters: TShelterPropertyFilters = {
parking: [],
};

export const shelterFiltersAtom =
atomWithReset<TShelterPropertyFilters>(nullShelterFilters);
export const shelterPropertyFiltersAtom =
atomWithReset<TShelterPropertyFilters>(nullShelterPropertyFilters);
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type TProps = {
placeholder?: string;
onPlaceSelect: (place: TPlaceResult | null) => void;
countryRestrictions?: ISO3166Alpha2 | ISO3166Alpha2[] | null;
leftIcon?: React.ReactElement;
};

export function AddressAutocomplete(props: TProps) {
Expand All @@ -23,6 +24,7 @@ export function AddressAutocomplete(props: TProps) {
countryRestrictions = 'us',
placeholder,
className = '',
leftIcon,
} = props;

const places = usePlacesClient();
Expand Down Expand Up @@ -107,7 +109,9 @@ export function AddressAutocomplete(props: TProps) {
placeholder={placeholder}
className="w-full"
onChange={handleInputChange}
leftIcon={<SearchIcon className="text-neutral-70 w-4 h-4" />}
leftIcon={
leftIcon || <SearchIcon className="text-neutral-70 w-4 h-4" />
}
/>

{predictions.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@monorepo/react/components';
import { mergeCss } from '@monorepo/react/shared';
import { useResetAtom } from 'jotai/utils';
import { shelterFiltersAtom } from '../../atoms';
import { shelterPropertyFiltersAtom } from '../../atoms';

type IProps = {
className?: string;
Expand All @@ -12,7 +12,7 @@ type IProps = {
export function FiltersActions(props: IProps) {
const { onDone, onReset, className } = props;

const resetFilters = useResetAtom(shelterFiltersAtom);
const resetFilters = useResetAtom(shelterPropertyFiltersAtom);

const parentCss = [
'flex',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Checkbox, ExpandableContainer } from '@monorepo/react/components';
import { mergeCss } from '@monorepo/react/shared';
import { useAtom } from 'jotai';
import { useEffect } from 'react';
import { shelterFiltersAtom } from '../../atoms';
import { shelterPropertyFiltersAtom } from '../../atoms';
import { TShelterPropertyFilters } from '../ShelterSearch';
import { FilterSelector } from './FilterSelector';
import {
Expand All @@ -23,7 +23,7 @@ type IProps = {
export function ShelterFilters(props: IProps) {
const { onChange, className } = props;

const [filters, setFilters] = useAtom(shelterFiltersAtom);
const [filters, setFilters] = useAtom(shelterPropertyFiltersAtom);

const parentCss = ['pb-24', className];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { TMapBounds } from '../Map';
import { TShelterPropertyFilters } from './types';

type TProps = {
className?: string;
nameFilter?: string;
mapBoundsFilter?: TMapBounds | null;
openNowFilter?: boolean | null;
propertyFilters?: TShelterPropertyFilters | null;
};

function propertyFiltersAffectQuery(
propertyFilters?: TShelterPropertyFilters | null
): boolean {
if (!propertyFilters) {
return false;
}

const { openNow, ...propertyOnly } = propertyFilters;

return Object.keys(propertyOnly).length > 0;
}

function formatWithOxfordComma(parts: string[]): string {
if (parts.length === 1) {
return parts[0];
}

if (parts.length === 2) {
return `${parts[0]} and ${parts[1]}`;
}

return `${parts.slice(0, -1).join(', ')}, and ${parts[parts.length - 1]}`;
}

export function ResultsSource(props: TProps) {
const {
nameFilter,
mapBoundsFilter,
openNowFilter,
propertyFilters,
className = '',
} = props;

const resultSourceParts: string[] = [];

if (mapBoundsFilter) {
resultSourceParts.push('map area');
}
if (openNowFilter || propertyFiltersAffectQuery(propertyFilters)) {
resultSourceParts.push('filters');
}
if (nameFilter?.trim()) {
resultSourceParts.push('name search');
}

const resultSource =
resultSourceParts.length > 0
? formatWithOxfordComma(resultSourceParts)
: 'search area';

return (
<div className={className}>
<span className="text-sm text-neutral">(based on {resultSource})</span>
</div>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { FilterIcon } from '@monorepo/react/icons';
import { FilterIcon, LocationIcon, SearchIcon } from '@monorepo/react/icons';
import { useAtom } from 'jotai';
import { useResetAtom } from 'jotai/utils';
import { useEffect, useState } from 'react';
import { locationAtom, shelterFiltersAtom } from '../../atoms';
import { shelterPropertyFiltersAtom } from '../../atoms';
import { AddressAutocomplete, TPlaceResult } from '../AddressAutocomplete';
import { TMapBounds } from '../Map';
import { Input } from '../Input';
import { TLatLng, TMapBounds } from '../Map';
import { ModalAnimationEnum, modalAtom } from '../Modal';
import { FilterPills, FiltersActions, ShelterFilters } from '../ShelterFilters';
import { SheltersDisplay } from './SheltersDisplay';
import { TShelterPropertyFilters } from './types';

type TProps = {
locationSearchInputKey?: number;
mapBoundsFilter?: TMapBounds;
nameSearchPinFitRequestId?: number;
onShelterPinsReadyForMapFit?: (pinLocations: TLatLng[]) => void;
onNameSearch: () => void;
setLocation: (location: TLatLng) => void;
};

export function ShelterSearch(props: TProps) {
const { mapBoundsFilter } = props;
const [location, setLocation] = useAtom(locationAtom);
const {
locationSearchInputKey = 0,
mapBoundsFilter,
nameSearchPinFitRequestId = 0,
onShelterPinsReadyForMapFit,
onNameSearch,
setLocation,
} = props;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_modal, setModal] = useAtom(modalAtom);
const [queryFilters, setQueryFilters] = useState<TShelterPropertyFilters>();
const [submitQueryTs, setSubmitQueryTs] = useState<number>();
const [filters] = useAtom(shelterFiltersAtom);
const resetFilters = useResetAtom(shelterFiltersAtom);
const [filters] = useAtom(shelterPropertyFiltersAtom);
const [nameFilter, setNameFilter] = useState<string>();
const resetFilters = useResetAtom(shelterPropertyFiltersAtom);
const [nameSearchValue, setNameSearchValue] = useState('');

function onPlaceSelect(place: TPlaceResult | null) {
if (!place) {
Expand All @@ -36,10 +50,11 @@ export function ShelterSearch(props: TProps) {
return;
}

onNameSearchChange('');

setLocation({
latitude,
longitude,
source: 'address',
});
}

Expand All @@ -49,7 +64,7 @@ export function ShelterSearch(props: TProps) {
}

setQueryFilters(filters);
}, [submitQueryTs]);
}, [submitQueryTs, filters]);

function onSubmitFilters() {
setSubmitQueryTs(Date.now());
Expand All @@ -66,27 +81,61 @@ export function ShelterSearch(props: TProps) {
});
}

function onNameSearchChange(value: string) {
setNameSearchValue(value);
if (!value.trim()) {
setNameFilter(undefined);
}
}

function onSearchClick() {
// Name search ignores any previously-selected property filters.
setQueryFilters(undefined);
resetFilters();
setNameFilter(nameSearchValue.trim());
console.log('nameFilter', nameFilter);
console.log('nameSearchValue', nameSearchValue);
onNameSearch();
}

return (
<>
<div className="mt-4 flex items-center justify-between">
<AddressAutocomplete
className="w-full"
placeholder="Search address"
onPlaceSelect={onPlaceSelect}
/>

<button onClick={onFilterClick} className="self-start ml-4 mt-4">
<FilterIcon className="w-6 text-primary-20" />
</button>
<div className="mt-4 flex flex-col items-center justify-between">
<div className="flex items-center justify-between w-full">
<AddressAutocomplete
key={locationSearchInputKey}
className="w-full"
placeholder="Search by location"
onPlaceSelect={onPlaceSelect}
leftIcon={<LocationIcon className="text-neutral-70 w-4 h-4" />}
/>
<button onClick={onFilterClick} className="self-start ml-4 mt-4">
<FilterIcon className="w-6 text-primary-20" />
</button>
</div>
<div className="mt-2 flex items-center justify-between w-full">
<Input
value={nameSearchValue}
placeholder="Search by name"
className="w-full"
onChange={onNameSearchChange}
leftIcon={<SearchIcon className="text-neutral-70 w-4 h-4" />}
/>

<button onClick={onSearchClick} className="self-start ml-4 mt-4">
<SearchIcon className="w-6 text-primary-20" />
</button>
</div>
</div>

<FilterPills className="mt-2" filters={filters} />
<SheltersDisplay
className="mt-8"
coordinates={location}
coordinatesSource={location?.source}
mapBoundsFilter={mapBoundsFilter}
propertyFilters={queryFilters}
nameFilter={nameFilter}
nameSearchPinFitRequestId={nameSearchPinFitRequestId}
onShelterPinsReadyForMapFit={onShelterPinsReadyForMapFit}
/>
</>
);
Expand Down
Loading
Loading