Skip to content

Commit

Permalink
feat: add filters for needs, descriptions and location (#32)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Elara Liu <[email protected]>
  • Loading branch information
WelldoneM and ZL-Asica authored Dec 6, 2024
1 parent 0f35b8c commit 219b2e6
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 40 deletions.
96 changes: 73 additions & 23 deletions src/components/Home/DonorDashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,93 @@
import { Box, Typography } from '@mui/material';
import { filter, lowerCase, some } from 'es-toolkit/compat';
import { useState } from 'react';
import { useState, useMemo } from 'react';

import OrganizationCard from './OrganizationCard';

import { useOrganizationStore } from '@/stores';

import { SearchBar } from '@/components/common';
import { SearchBar, Filters } from '@/components/common';

const DonorDashboard = () => {
const [searchQuery, setSearchQuery] = useState('');
const [needsQuery, setNeedsQuery] = useState('');
const [descriptionQuery, setDescriptionQuery] = useState('');
const [locationQuery, setLocationQuery] = useState('');

const organizationProfiles = useOrganizationStore(
(state) => state.organizationProfiles
);

// Filtered organizations based on search query
const filteredOrganizations = filter(organizationProfiles, (org) => {
if (!org.name) return false;
const searchTerm = lowerCase(searchQuery);
return (
lowerCase(org.name).includes(searchTerm) ||
lowerCase(org.location).includes(searchTerm) ||
some(org.needs, (need) => lowerCase(need).includes(searchTerm))
);
});
const filteredOrganizations = useMemo(() => {
return organizationProfiles.filter((org) => {
const matchesSearch = searchQuery
? org.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
org.location.toLowerCase().includes(searchQuery.toLowerCase()) ||
(org.needs || []).some((need) =>
need.itemName.toLowerCase().includes(searchQuery.toLowerCase())
)
: true;

const matchesNeeds = needsQuery
? (org.needs || []).some((need) =>
need.itemName.toLowerCase().includes(needsQuery.toLowerCase())
)
: true;

const matchesDescription = descriptionQuery
? org.description
?.toLowerCase()
.includes(descriptionQuery.toLowerCase())
: true;

const matchesLocation = locationQuery
? org.location.toLowerCase().includes(locationQuery.toLowerCase())
: true;

return (
matchesSearch && matchesNeeds && matchesDescription && matchesLocation
);
});
}, [
organizationProfiles,
searchQuery,
needsQuery,
descriptionQuery,
locationQuery,
]);

return organizationProfiles.length > 0 ? (
<div>
<SearchBar onSearchChange={setSearchQuery} />
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
{filteredOrganizations.map((org) => (
<OrganizationCard
organization={org}
key={org.uid}
/>
))}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<SearchBar onSearchChange={setSearchQuery} />
<Filters
needsQuery={needsQuery}
setNeedsQuery={setNeedsQuery}
descriptionQuery={descriptionQuery}
setDescriptionQuery={setDescriptionQuery}
locationQuery={locationQuery}
setLocationQuery={setLocationQuery}
/>
</Box>
</div>

{filteredOrganizations.map((org) => (
<OrganizationCard
organization={org}
key={org.uid}
/>
))}
</Box>
) : (
<Typography
variant='body1'
Expand Down
147 changes: 147 additions & 0 deletions src/components/common/Filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { useState } from 'react';
import {
Box,
Button,
Popover,
FormControl,
InputLabel,
Select,
MenuItem,
TextField,
} from '@mui/material';
import FilterListIcon from '@mui/icons-material/FilterList';

import { useOrganizationStore } from '@/stores';

interface FiltersProps {
needsQuery: string;
setNeedsQuery: (query: string) => void;
descriptionQuery: string;
setDescriptionQuery: (query: string) => void;
locationQuery: string;
setLocationQuery: (query: string) => void;
}

const Filters = ({
needsQuery,
setNeedsQuery,
descriptionQuery,
setDescriptionQuery,
locationQuery,
setLocationQuery,
}: FiltersProps) => {
const organizationProfiles = useOrganizationStore(
(state) => state.organizationProfiles
);

const [anchorElement, setAnchorElement] = useState<null | HTMLElement>(null);

const handleClose = () => {
setAnchorElement(null);
};

const open = Boolean(anchorElement);
const id = open ? 'filters-popover' : undefined;

const needsOptions = [
...new Set(
organizationProfiles.flatMap(
(org) => org.needs?.flatMap((need) => need.itemName) || []
)
),
].filter(Boolean);

return (
<Box>
<Button
variant='contained'
onClick={(event_) => setAnchorElement(event_.currentTarget)}
startIcon={<FilterListIcon />}
sx={{ marginTop: 2 }}
>
Filters
</Button>
<Popover
id={id}
open={open}
anchorEl={anchorElement}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<Box
sx={{
p: 2,
display: 'flex',
flexDirection: 'column',
gap: 2,
width: '300px',
}}
>
{/* Needs dropdown */}
<FormControl fullWidth>
<InputLabel id='needs-filter-label'>Filter by Needs</InputLabel>
<Select
labelId='needs-filter-label'
value={needsQuery}
onChange={(event_) => setNeedsQuery(event_.target.value)}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
},
transformOrigin: {
vertical: 'top',
horizontal: 'left',
},
PaperProps: {
style: {
maxHeight: 'calc(80vh - 96px)',
overflowY: 'auto',
},
},
disablePortal: false,
}}
>
<MenuItem value=''>All</MenuItem>
{needsOptions.map((need, index) => (
<MenuItem
key={index}
value={String(need)}
>
{String(need)}
</MenuItem>
))}
</Select>
</FormControl>

{/* Description text field */}
<TextField
label='Filter by Description'
variant='outlined'
fullWidth
value={descriptionQuery}
onChange={(event_) => setDescriptionQuery(event_.target.value)}
/>

{/* Location text field */}
<TextField
label='Filter by Location'
variant='outlined'
fullWidth
value={locationQuery}
onChange={(event_) => setLocationQuery(event_.target.value)}
/>
</Box>
</Popover>
</Box>
);
};

export default Filters;
26 changes: 9 additions & 17 deletions src/components/common/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useEffect, useState } from 'react';
import { TextField, Box, Button } from '@mui/material';
import { TextField } from '@mui/material';
import { useSearchParams } from 'react-router-dom';
import { debounce } from 'es-toolkit/compat';

Expand Down Expand Up @@ -42,22 +42,14 @@ const SearchBar = ({ onSearchChange }: SearchBarProps) => {
}, [searchParams, onSearchChange]);

return (
<Box
display='flex'
alignItems='center'
mt={3}
mx={2}
>
<TextField
label='Search organizations...'
variant='outlined'
fullWidth
sx={{ marginRight: 1 }}
value={query}
onChange={handleInputChange}
/>
<Button variant='contained'>Recommend</Button>
</Box>
<TextField
label='Search organizations...'
variant='outlined'
fullWidth
sx={{ mx: 1, mt: 3, mr: 2 }}
value={query}
onChange={handleInputChange}
/>
);
};

Expand Down
1 change: 1 addition & 0 deletions src/components/common/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { default as ProtectedRoute } from './ProtectedRoute';
export { default as CustomDialog } from './CustomDialog';
export { default as RoleSelectionModal } from './RoleSelectionModal';
export { default as Layout } from './Layout';
export { default as Filters } from './Filters';

0 comments on commit 219b2e6

Please sign in to comment.