Add sorting to project solution listing

pull/8169/head
Kamran Ahmed 2 weeks ago
parent 6186e12b05
commit 8d25eabe3a
  1. 57
      src/components/Projects/ListProjectSolutions.tsx
  2. 2
      src/components/Projects/SelectLanguages.tsx
  3. 66
      src/components/Projects/SortProjects.tsx

@ -16,6 +16,7 @@ import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import { SelectLanguages } from './SelectLanguages.tsx';
import type { ProjectFrontmatter } from '../../lib/project.ts';
import { ProjectSolutionModal } from './ProjectSolutionModal.tsx';
import { SortProjects } from './SortProjects.tsx';
export interface ProjectStatusDocument {
_id?: string;
@ -57,11 +58,13 @@ type ListProjectSolutionsResponse = {
type QueryParams = {
p?: string;
l?: string;
s?: string;
};
type PageState = {
currentPage: number;
language: string;
sort: string;
};
type ListProjectSolutionsProps = {
@ -100,6 +103,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
const [pageState, setPageState] = useState<PageState>({
currentPage: 0,
language: '',
sort: 'rating',
});
const [isLoading, setIsLoading] = useState(true);
@ -108,12 +112,17 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
ListProjectSolutionsResponse['data'][number] | null
>(null);
const loadSolutions = async (page = 1, language: string = '') => {
const loadSolutions = async (
page = 1,
language: string = '',
sort: string = 'rating',
) => {
const { response, error } = await httpGet<ListProjectSolutionsResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-list-project-solutions/${projectId}`,
{
currPage: page,
...(language ? { languages: language } : {}),
sort,
},
);
@ -178,6 +187,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
setPageState({
currentPage: +(queryParams.p || '1'),
language: queryParams.l || '',
sort: queryParams.s || 'rating',
});
}, []);
@ -187,17 +197,27 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
return;
}
if (pageState.currentPage !== 1 || pageState.language !== '') {
if (
pageState.currentPage !== 1 ||
pageState.language !== '' ||
pageState.sort !== 'rating'
) {
setUrlParams({
p: String(pageState.currentPage),
l: pageState.language,
s: pageState.sort,
});
} else {
deleteUrlParam('p');
deleteUrlParam('l');
deleteUrlParam('s');
}
loadSolutions(pageState.currentPage, pageState.language).finally(() => {
loadSolutions(
pageState.currentPage,
pageState.language,
pageState.sort,
).finally(() => {
setIsLoading(false);
});
}, [pageState]);
@ -227,16 +247,27 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
<p className="text-sm text-gray-500">{projectData.description}</p>
</div>
{!isLoading && (
<SelectLanguages
projectId={projectId}
selectedLanguage={selectedLanguage}
onSelectLanguage={(language) => {
setPageState((prev) => ({
...prev,
language: prev.language === language ? '' : language,
}));
}}
/>
<div className="flex flex-shrink-0 items-center gap-2">
<SortProjects
selectedSort={pageState.sort}
onSelectSort={(sort) => {
setPageState((prev) => ({
...prev,
sort,
}));
}}
/>
<SelectLanguages
projectId={projectId}
selectedLanguage={selectedLanguage}
onSelectLanguage={(language) => {
setPageState((prev) => ({
...prev,
language: prev.language === language ? '' : language,
}));
}}
/>
</div>
)}
</div>

@ -114,7 +114,7 @@ export function SelectLanguages(props: SelectLanguagesProps) {
};
return (
<div className="relative flex">
<div className="relative flex flex-shrink-0">
<div className="relative">
<button
className="flex items-center gap-1 rounded-md border border-gray-300 py-1.5 pl-3 pr-2 text-xs font-medium text-gray-900"

@ -0,0 +1,66 @@
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { ChevronDown } from 'lucide-react';
type SortOption = {
label: string;
value: string;
};
const sortOptions: SortOption[] = [
{ label: 'Latest First', value: 'latest' },
{ label: 'Oldest First', value: 'oldest' },
{ label: 'Highest Rating', value: 'rating' },
];
type SortProjectsProps = {
selectedSort: string;
onSelectSort: (sort: string) => void;
};
export function SortProjects(props: SortProjectsProps) {
const { selectedSort, onSelectSort } = props;
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useOutsideClick(dropdownRef, () => {
setIsOpen(false);
});
const selectedOption =
sortOptions.find((option) => option.value === selectedSort) ||
sortOptions[0];
return (
<div className="relative flex-shrink-0" ref={dropdownRef}>
<button
className="flex items-center gap-1 rounded-md border border-gray-300 py-1.5 pl-3 pr-2 text-xs font-medium text-gray-900"
onClick={() => setIsOpen(!isOpen)}
>
{selectedOption.label}
<ChevronDown className="ml-1 h-4 w-4" />
</button>
{isOpen && (
<div className="absolute right-0 top-full z-10 mt-1.5 min-w-[150px] overflow-hidden rounded-md border border-gray-300 bg-white shadow-lg">
<div className="py-1">
{sortOptions.map((option) => (
<button
key={option.value}
className={`flex w-full items-center px-4 py-1.5 text-left text-sm text-gray-700 hover:bg-gray-100 ${
selectedSort === option.value ? 'bg-gray-100' : ''
}`}
onClick={() => {
onSelectSort(option.value);
setIsOpen(false);
}}
>
{option.label}
</button>
))}
</div>
</div>
)}
</div>
);
}
Loading…
Cancel
Save