2024-01-30 14:20:53 +00:00
|
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
2023-12-20 04:29:02 +00:00
|
|
|
import { useCombobox } from 'downshift';
|
2024-01-25 11:08:40 +00:00
|
|
|
import { Project } from 'gql-client';
|
2024-01-30 14:20:53 +00:00
|
|
|
import { useDebounce } from 'usehooks-ts';
|
2023-12-20 04:29:02 +00:00
|
|
|
|
|
|
|
import {
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListItemPrefix,
|
|
|
|
Card,
|
|
|
|
Typography,
|
|
|
|
} from '@material-tailwind/react';
|
|
|
|
|
|
|
|
import SearchBar from '../SearchBar';
|
2024-01-24 14:47:43 +00:00
|
|
|
import { useGQLClient } from '../../context/GQLClientContext';
|
2023-12-20 04:29:02 +00:00
|
|
|
|
|
|
|
interface ProjectsSearchProps {
|
2024-01-24 14:47:43 +00:00
|
|
|
onChange?: (data: Project) => void;
|
2023-12-20 04:29:02 +00:00
|
|
|
}
|
|
|
|
|
2024-01-24 14:47:43 +00:00
|
|
|
const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
|
|
|
|
const [items, setItems] = useState<Project[]>([]);
|
|
|
|
const [selectedItem, setSelectedItem] = useState<Project | null>(null);
|
|
|
|
const client = useGQLClient();
|
2023-12-20 04:29:02 +00:00
|
|
|
|
|
|
|
const {
|
|
|
|
isOpen,
|
|
|
|
getMenuProps,
|
|
|
|
getInputProps,
|
|
|
|
getItemProps,
|
|
|
|
highlightedIndex,
|
2023-12-21 11:12:06 +00:00
|
|
|
inputValue,
|
2023-12-20 04:29:02 +00:00
|
|
|
} = useCombobox({
|
|
|
|
items,
|
|
|
|
itemToString(item) {
|
2024-01-24 14:47:43 +00:00
|
|
|
return item ? item.name : '';
|
2023-12-20 04:29:02 +00:00
|
|
|
},
|
|
|
|
selectedItem,
|
|
|
|
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
|
|
|
|
if (newSelectedItem) {
|
|
|
|
setSelectedItem(newSelectedItem);
|
|
|
|
|
|
|
|
if (onChange) {
|
|
|
|
onChange(newSelectedItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2024-01-30 14:20:53 +00:00
|
|
|
const debouncedInputValue = useDebounce<string>(inputValue, 500);
|
|
|
|
|
2024-01-24 14:47:43 +00:00
|
|
|
const fetchProjects = useCallback(
|
|
|
|
async (inputValue: string) => {
|
|
|
|
const { searchProjects } = await client.searchProjects(inputValue);
|
|
|
|
setItems(searchProjects);
|
|
|
|
},
|
|
|
|
[client],
|
|
|
|
);
|
|
|
|
|
2024-01-30 14:20:53 +00:00
|
|
|
useEffect(() => {
|
|
|
|
if (debouncedInputValue) {
|
|
|
|
fetchProjects(debouncedInputValue);
|
|
|
|
}
|
|
|
|
}, [fetchProjects, debouncedInputValue]);
|
|
|
|
|
2023-12-20 04:29:02 +00:00
|
|
|
return (
|
|
|
|
<div className="relative">
|
|
|
|
<SearchBar {...getInputProps()} />
|
|
|
|
<Card
|
2023-12-21 11:12:06 +00:00
|
|
|
className={`absolute w-1/2 max-h-52 -mt-1 overflow-y-auto ${
|
|
|
|
(!inputValue || !isOpen) && 'hidden'
|
2023-12-20 04:29:02 +00:00
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<List {...getMenuProps()}>
|
2023-12-21 11:12:06 +00:00
|
|
|
{items.length ? (
|
|
|
|
<>
|
|
|
|
<div className="p-3">
|
|
|
|
<Typography variant="small" color="gray">
|
|
|
|
Suggestions
|
2023-12-20 04:29:02 +00:00
|
|
|
</Typography>
|
|
|
|
</div>
|
2023-12-21 11:12:06 +00:00
|
|
|
{items.map((item, index) => (
|
|
|
|
<ListItem
|
|
|
|
selected={highlightedIndex === index || selectedItem === item}
|
|
|
|
key={item.id}
|
|
|
|
{...getItemProps({ item, index })}
|
|
|
|
>
|
|
|
|
<ListItemPrefix>
|
|
|
|
<i>^</i>
|
|
|
|
</ListItemPrefix>
|
|
|
|
<div>
|
|
|
|
<Typography variant="h6" color="blue-gray">
|
2024-01-24 14:47:43 +00:00
|
|
|
{item.name}
|
2023-12-21 11:12:06 +00:00
|
|
|
</Typography>
|
|
|
|
<Typography
|
|
|
|
variant="small"
|
|
|
|
color="gray"
|
|
|
|
className="font-normal"
|
|
|
|
>
|
2024-01-24 14:47:43 +00:00
|
|
|
{item.organization.name}
|
2023-12-21 11:12:06 +00:00
|
|
|
</Typography>
|
|
|
|
</div>
|
|
|
|
</ListItem>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<div className="p-3">
|
|
|
|
<Typography>^ No projects matching this name</Typography>
|
|
|
|
</div>
|
|
|
|
)}
|
2023-12-20 04:29:02 +00:00
|
|
|
</List>
|
|
|
|
</Card>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-12-22 08:49:59 +00:00
|
|
|
export default ProjectSearchBar;
|