Skip to content

Commit

Permalink
Added question labels
Browse files Browse the repository at this point in the history
  • Loading branch information
Zafeeruddin committed Oct 5, 2024
1 parent 1ceeb14 commit 2e363af
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 7 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
"@prisma/client": "^5.18.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.1.1",
Expand Down
92 changes: 88 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface QueryParams {
timestamp?: number;
editCommentId?: number;
newPost?: 'open' | 'close';
tags?: string;
}
export enum TabType {
md = 'Most downvotes',
Expand Down
4 changes: 4 additions & 0 deletions src/app/(main)/(pages)/question/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { authOptions } from '@/lib/auth';
import PostCard from '@/components/posts/PostCard';
import Pagination from '@/components/Pagination';
import { redirect } from 'next/navigation';
import LabelDropdown from '@/components/LabelDropdown';
import { ClearFilter } from '@/components/questions/ClearFIlter';

type QuestionsResponse = {
data: ExtendedQuestion[] | null;
Expand Down Expand Up @@ -155,6 +157,7 @@ export default async function QuestionsPage({
<div className="flex flex-col justify-between gap-4 md:flex-row">
<Search />
<div className="flex items-center justify-between gap-2">
<LabelDropdown posts={response && response.data}/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size={'lg'}>
Expand Down Expand Up @@ -220,6 +223,7 @@ export default async function QuestionsPage({
</Link>
</div>
</div>
<ClearFilter/>
{/* Chat */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
{response?.data?.map((post) => (
Expand Down
127 changes: 127 additions & 0 deletions src/components/LabelDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
'use client';

import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { CheckIcon, Search, Tags } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { getUpdatedUrl } from '@/lib/utils';
import { Button } from './ui/button';
import { ExtendedQuestion } from '@/actions/question/types';
import Tag from './posts/tag';

export default function LabelDropdown({posts}:{posts:ExtendedQuestion[] | null}) {
const [selectedLabels, setSelectedLabels] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [labels,setLabels] = useState<string[]>([]);
const searchParams = useSearchParams();
const router = useRouter();

useEffect(() => {
const taggedPosts:ExtendedQuestion[]=[];
const filterPosts = () => {
if (posts!=null) {
posts.filter(post => {
if (post.tags.length!=0) taggedPosts.push(post);
});
}
};
filterPosts();

const newLabels:string[]=[];
taggedPosts.map(post => {
const tags = post.tags;
tags.map(tag => {
tag= tag.trim();
const checkTag = tag === tag.toUpperCase() ? tag : tag[0].toUpperCase() + tag.slice(1);
if (!newLabels.includes(checkTag) && checkTag.length!=0) {
newLabels.push(checkTag);
}
});
});

setLabels(newLabels.sort());
},[posts]);

const handleToggleLabel = (id: string) => {
setSelectedLabels((prev) =>
prev.includes(id) ? prev.filter((label) => label !== id) : [...prev, id]
);
};

const filteredLabels = labels.filter((label) =>
label.toLowerCase().includes(searchTerm.toLowerCase())
);

const updatedUrl = getUpdatedUrl('/question', Object.fromEntries(searchParams.entries()), {
tags: selectedLabels.join(','),
});

return (
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenu.Trigger asChild>
<Button size={"lg"} onClick={() => setIsOpen(true)}>
<Tags className='mr-1.5 h-[18px] w-[18px]'/>
Labels
</Button>
</DropdownMenu.Trigger>

<DropdownMenu.Portal>
<DropdownMenu.Content className="w-60 p-2 bg-slate-950 shadow-lg rounded-md overflow-auto max-h-60">
<div className='flex p-1 w-full border rounded bg-slate-900 mb-1'>
<input
type="text"
className="w-full h-9 text-sm border rounded-md pl-2"
placeholder="Filter labels"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Button
className='ml-3 h-8'
onClick={() => {
setIsOpen(false);
router.push(updatedUrl);
}}
size={"sm"}
>
<Search size={20}/>
</Button>
</div>

{filteredLabels.length > 0 ? (
<div className="space-y-1">
{filteredLabels.map((label,index) => (
<DropdownMenu.Item
key={`${label}-${index}`}
className="flex items-center justify-between px-2 py-1 text-sm cursor-pointer hover:bg-slate-600 rounded-md"
onSelect={(e) => {
e.preventDefault();
handleToggleLabel(label);
}}
>
<Label name={label}/>

{selectedLabels.includes(label) && (
<CheckIcon className="w-4 h-4 text-green-500" />
)}
</DropdownMenu.Item>
))}
</div>
) : (
<div className="text-sm text-gray-500">No labels found</div>
)}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
}

const Label = ({name}:{name:string}) => {
return (
<>
{ name!=="" &&
<Tag name={name}/>
}
</>
);
};
Loading

0 comments on commit 2e363af

Please sign in to comment.