Skip to content

Commit

Permalink
Refactor: projects list/filtering (#243)
Browse files Browse the repository at this point in the history
* refactor projects list/filtering

* format

* use project length percentage as timeout
  • Loading branch information
DaveDarsa authored May 15, 2024
1 parent 1eb5452 commit 027fbbf
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 37 deletions.
4 changes: 4 additions & 0 deletions src/components/Projects/StyledProjects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const ProjectsHeader = styled.div`
}
}
}
.projectCount {
display: flex;
gap: 0.5rem;
}
`;

export const SearchInput = styled.input`
Expand Down
120 changes: 83 additions & 37 deletions src/components/Projects/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Highlighter from 'react-highlight-words';

import { LoadingOutlined } from '@ant-design/icons';
import { Spin } from 'antd';
import Box from 'components/Box';
import ProjectLink from 'components/link/Project';
import { debounce } from 'lib/util';

import {
ProjectsHeader,
Expand All @@ -18,6 +21,8 @@ import {
*/
const Projects = ({ projects = [], initialSearch }) => {
const [searchInput, setSearchInput] = useState(initialSearch);
const [isFiltering, setIsFiltering] = useState(false);
const [filteredProjects, setFilteredProjects] = useState(projects);

const searchInputRef = useRef(null);

Expand All @@ -26,32 +31,94 @@ const Projects = ({ projects = [], initialSearch }) => {
searchInputRef.current.focus();
}
}, []);
const filteredProjects = projects.filter(key => {
const sortByName = key.name.toLowerCase().includes(searchInput.trim().toLowerCase());
let sortByUrl = '';
if (key.environments[0] !== void 0) {
if (key.environments[0].route !== null) {
sortByUrl = key.environments[0].route.toLowerCase().includes(searchInput.trim().toLowerCase());
}
}
return ['name', 'environments', '__typename'].includes(key) ? false : (true && sortByName) || sortByUrl;
});

const timerLengthPercentage = useMemo(
() => Math.min(1000, Math.max(40, Math.floor(projects.length * 0.0725))),
[projects.length]
);

const debouncedSearch = useCallback(
debounce(searchVal => {
setSearchInput(searchVal);
}, timerLengthPercentage),
[]
);

const handleSearch = searchVal => {
setIsFiltering(true);
debouncedSearch(searchVal);
};

useEffect(() => {
const filterProjects = async () => {
return new Promise(resolve => {
const filtered = projects.filter(key => {
const sortByName = key.name.toLowerCase().includes(searchInput.trim().toLowerCase());
let sortByUrl = '';
if (key.environments[0] !== void 0) {
if (key.environments[0].route !== null) {
sortByUrl = key.environments[0].route.toLowerCase().includes(searchInput.trim().toLowerCase());
}
}
return ['name', 'environments', '__typename'].includes(key) ? false : (true && sortByName) || sortByUrl;
});

resolve(filtered);
});
};

filterProjects()
.then(filtered => setFilteredProjects(filtered))
.finally(() => setIsFiltering(false));
}, [searchInput, projects]);

const MemoizedHighlighter = React.memo(Highlighter);

const mappedProjects = useMemo(() => {
return filteredProjects.map(project => (
<ProjectLink projectSlug={project.name} key={project.id}>
<Box className="box">
<StyledProject>
<h4>
<MemoizedHighlighter
searchWords={[searchInput.trim()]}
autoEscape={true}
textToHighlight={project.name}
/>
</h4>
<StyledRoute data-cy="projects">
{project.environments.map((environment, index) => (
<MemoizedHighlighter
key={index}
searchWords={[searchInput.trim()]}
autoEscape={true}
textToHighlight={environment.route ? environment.route.replace(/^https?\:\/\//i, '') : ''}
/>
))}
</StyledRoute>
</StyledProject>
<StyledCustomer></StyledCustomer>
</Box>
</ProjectLink>
));
}, [filteredProjects]);

return (
<ProjectsPage id="projects">
<ProjectsHeader>
<label data-cy="projectsLength">
{filteredProjects.length <= 1 ? `${filteredProjects.length} Project` : `${filteredProjects.length} Projects`}
<label className="projectCount" data-cy="projectsLength">
{`${filteredProjects.length} Projects`}
{isFiltering && <Spin indicator={<LoadingOutlined />} />}
</label>
<label></label>
<SearchInput
data-cy="searchBar"
defaultValue={searchInput}
ref={searchInputRef}
aria-labelledby="search"
className="searchInput"
type="text"
value={searchInput}
onChange={e => setSearchInput(e.target.value)}
onChange={e => handleSearch(e.target.value)}
placeholder="Type to search"
disabled={projects.length === 0}
/>
Expand All @@ -70,28 +137,7 @@ const Projects = ({ projects = [], initialSearch }) => {
</div>
</Box>
)}
{filteredProjects.map(project => (
<ProjectLink projectSlug={project.name} key={project.id}>
<Box className="box">
<StyledProject>
<h4>
<Highlighter searchWords={[searchInput.trim()]} autoEscape={true} textToHighlight={project.name} />
</h4>
<StyledRoute data-cy="projects">
{project.environments.map((environment, index) => (
<Highlighter
key={index}
searchWords={[searchInput.trim()]}
autoEscape={true}
textToHighlight={environment.route ? environment.route.replace(/^https?\:\/\//i, '') : ''}
/>
))}
</StyledRoute>
</StyledProject>
<StyledCustomer></StyledCustomer>
</Box>
</ProjectLink>
))}
{mappedProjects}
</ProjectsPage>
);
};
Expand Down
11 changes: 11 additions & 0 deletions src/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,14 @@ export const queryStringToObject = R.pipe(
);

export const makeSafe = string => string.toLocaleLowerCase().replace(/[^0-9a-z-]/g, '-');

export const debounce = (fn, delay) => {
let timeoutId;

return function (val) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.call(null, val);
}, delay);
};
};

0 comments on commit 027fbbf

Please sign in to comment.