mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-10-02 03:46:42 +02:00
Add sorting to project solution listing
This commit is contained in:
@@ -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"
|
||||
|
66
src/components/Projects/SortProjects.tsx
Normal file
66
src/components/Projects/SortProjects.tsx
Normal file
@@ -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>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user