Skip to content

Commit

Permalink
Merge pull request #108 from COW-dev/feature/#107
Browse files Browse the repository at this point in the history
필터 기능 리팩토링
  • Loading branch information
keemsebin authored Feb 18, 2024
2 parents e4aee2a + 383daa4 commit 895bfd1
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 164 deletions.
44 changes: 44 additions & 0 deletions src/components/home/FilterCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { Dispatch, SetStateAction } from 'react';
import { CatogoryColor } from '@/constants/color';

interface Props {
option: { category: string[]; recruit: string[]; sort: boolean };
setOption: Dispatch<
SetStateAction<{ category: string[]; recruit: string[]; sort: boolean }>
>;
}

function FilterCategory({ option, setOption }: Props) {
function filterCategory(item: string) {
const updatedCategory = option.category.includes(item)
? option.category.filter((club) => club !== item)
: [...option.category, item];
setOption((prev) => ({ ...prev, category: updatedCategory }));
}

return (
<div className="my-2 hidden w-full rounded-xl bg-gray-50 p-2 px-4 font-semibold text-gray-500 md:flex">
<span
className={`cursor-pointer ${
option.category.length === 0 && 'text-blue-500'
}`}
onClick={() => setOption((prev) => ({ ...prev, category: [] }))}
>
전체
</span>
{CatogoryColor.map((category, index) => (
<div
onClick={() => filterCategory(category.title)}
className={`cursor-pointer before:p-2 before:text-gray-300 before:content-['|'] ${
option.category.includes(category.title) && 'text-blue-500'
}`}
key={`category${index}`}
>
{category.title}
</div>
))}
</div>
);
}

export default FilterCategory;
188 changes: 54 additions & 134 deletions src/components/modal/FilterOption.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import Image from 'next/image';
import CheckboxImg from '@/assets/checkbox.svg';
import { CatogoryColor } from '@/constants/color';
import { Club } from '@/types';
import CheckBox from '../home/CheckBox';
import Category from './filter/Category';
import RecruitStatus from './filter/RecruitStatus';
import Sort from './filter/Sort';

type Props = {
clubs: Club[];
filteredClubs: Club[];
Expand All @@ -21,23 +21,18 @@ export default function FilterOption({
option,
setOption,
}: Props) {
const [recruitClubList, setRecruitClubList] = useState<Club[]>(
filteredClubs ?? clubs,
);
const [categoryClubList, setCategoryClubList] = useState<Club[]>(
filteredClubs ?? clubs,
);
const filteredList = filteredClubs ?? clubs;
const [recruitClubList, setRecruitClubList] = useState<Club[]>(filteredList);
const [categoryClubList, setCategoryClubList] =
useState<Club[]>(filteredList);
const { category, recruit, sort } = option;
const [isFilter, setIsFilter] = useState<boolean>(false);
const [active, setActive] = useState<string>('');

useEffect(() => {
handleList();
handleFilter();
}, [category, recruit, sort]);

useEffect(() => {
handleFilter();
}, [recruitClubList, categoryClubList]);
});

function handleFilter() {
const filtered = recruitClubList.filter((item) =>
Expand All @@ -47,33 +42,31 @@ export default function FilterOption({
setFilteredClubs(sortedClubs);
}

function filterRecruitPeriod(item: string) {
const updatedRecruit = recruit.includes(item)
? recruit.filter((club) => club !== item)
: [...recruit, item];
setOption((prev) => ({ ...prev, recruit: updatedRecruit }));
}

function filterCategory(item: string) {
const updatedCategory = category.includes(item)
? category.filter((club) => club !== item)
: [...category, item];
setOption((prev) => ({ ...prev, category: updatedCategory }));
function filterOption(
item: string,
option: string[],
setOptionCallback: (updatedOption: string[]) => void,
) {
const updatedOption = option.includes(item)
? option.filter((value) => value !== item)
: [...option, item];
setOptionCallback(updatedOption);
}

function handleList() {
const filterRecuitList = [];
const filterCategoryList = [];
for (const club of clubs) {
if (recruit.includes(club.recruitStatus)) {
filterRecuitList.push(club);
const filterList = (
filterValues: string[],
property: 'recruitStatus' | 'category',
) => {
if (filterValues.length === 0) {
return clubs;
}
if (category.includes(club.category)) {
filterCategoryList.push(club);
}
}
setRecruitClubList(recruit.length === 0 ? clubs : filterRecuitList);
setCategoryClubList(category.length === 0 ? clubs : filterCategoryList);
return clubs.filter((club) => filterValues.includes(club[property]));
};
const filteredRecruitList = filterList(recruit, 'recruitStatus');
const filteredCategoryList = filterList(category, 'category');
setRecruitClubList(filteredRecruitList);
setCategoryClubList(filteredCategoryList);
}

const sortClubsByCategory = (clubs: Club[]) => {
Expand All @@ -84,123 +77,50 @@ export default function FilterOption({
);
};

const [active, setActive] = useState<string>('');
function handleOption(item: string) {
setActive(item);
item === active && isFilter ? setIsFilter(false) : setIsFilter(true);
item === active && isFilter ? focusoutOption() : setIsFilter(true);
}

function focusoutOption() {
setIsFilter(false);
setActive('');
}

return (
<div
className="mb-5 rounded-xl bg-gray-50 px-2 text-sm hover:cursor-pointer md:text-base"
tabIndex={0}
onBlur={() => setIsFilter(false)}
>
<div className="flex flex-row-reverse">
<div tabIndex={0} onBlur={focusoutOption}>
<div className="flex flex-row-reverse gap-2">
{['카테고리', '정렬', '모집기준'].map((item) => (
<div
key={`filter-${item}`}
onClick={() => handleOption(item)}
className={`m-2 gap-1 p-1 font-semibold ${
className={`mb-1.5 cursor-pointer font-semibold md:mb-2 ${
item === active ? `border-b text-blue-500` : `text-gray-500`
}`}
} ${item === '카테고리' && 'md:hidden'}`}
>
<span> {item}</span>
<span>{item}</span>
</div>
))}
</div>
<div className="relative m-auto flex max-w-6xl flex-row-reverse">
{isFilter && (
<div className="absolute w-50 rounded-xl bg-white p-2 shadow-xl">
<div className="absolute w-44 rounded-xl bg-white p-2 shadow-xl">
{active === '모집기준' && (
<div>
<div
onClick={() =>
setOption((prev) => ({ ...prev, recruit: [] }))
}
key={`recruit-option_all`}
>
<label className="w-46 mx-1 flex items-center justify-center gap-2 rounded-md p-1 hover:bg-gray-100 ">
{recruit.length === 0 ? (
<Image
src={CheckboxImg}
width={18}
height={18}
alt="checkbox"
/>
) : (
<div className="h-4 w-4 rounded-sm border border-gray-400"></div>
)}
<span className="w-[50%] py-1 ">전체 선택</span>
</label>
</div>
{['모집 마감', '모집 중', '모집 예정'].map((recruitType) => (
<div
onClick={() => filterRecruitPeriod(recruitType)}
key={`recruit-option_${recruitType}`}
>
<CheckBox title={recruitType} list={option.recruit} />
</div>
))}
</div>
<RecruitStatus
setOption={setOption}
option={option}
filterOption={filterOption}
/>
)}
{active === '정렬' && (
<div>
<div
onClick={() =>
setOption((prev) => ({ ...prev, sort: false }))
}
className={`rounded-xl p-2 px-5 ${
option.sort ? `opacity-50` : `bg-gray-100 opacity-100`
}`}
>
동아리명으로 정렬
</div>
<div
onClick={() => setOption((prev) => ({ ...prev, sort: true }))}
className={`rounded-xl p-2 px-5 ${
option.sort ? `bg-gray-100 opacity-100` : `opacity-50`
}`}
>
카테고리로 정렬
</div>
</div>
<Sort setOption={setOption} option={option} />
)}

{active === '카테고리' && (
<div>
<div
onClick={() =>
setOption((prev) => ({ ...prev, category: [] }))
}
key={`category-option_all`}
>
<label className="w-46 mx-1 flex items-center justify-center gap-2 rounded-md p-1 hover:bg-gray-100 ">
{category.length === 0 ? (
<Image
src={CheckboxImg}
width={18}
height={18}
alt="checkbox"
/>
) : (
<div className="h-4 w-4 rounded-sm border border-gray-400"></div>
)}
<span className="w-[50%] py-1 ">전체 선택</span>
</label>
</div>
{CatogoryColor.map((categoryItem) => (
<div
onClick={() => filterCategory(categoryItem.title)}
key={`category-option_${categoryItem.title}`}
>
<CheckBox
title={categoryItem.title}
list={option.category}
/>
</div>
))}
</div>
<Category
setOption={setOption}
option={option}
filterOption={filterOption}
/>
)}
</div>
)}
Expand Down
61 changes: 61 additions & 0 deletions src/components/modal/filter/Category.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { Dispatch, SetStateAction } from 'react';
import Image from 'next/image';
import CheckboxImg from '@/assets/checkbox.svg';
import { CatogoryColor } from '@/constants/color';
import CheckBox from '../../home/CheckBox';

interface Props {
setOption: Dispatch<
SetStateAction<{ category: string[]; recruit: string[]; sort: boolean }>
>;
option: { category: string[]; recruit: string[]; sort: boolean };
filterOption: (
item: string,
option: string[],
setOptionCallback: (updatedOption: string[]) => void,
) => void;
}

function Category({ setOption, option, filterOption }: Props) {
const { category } = option;

function fillCheckBox() {
if (category.length === 0) {
return <Image src={CheckboxImg} width={18} height={18} alt="checkbox" />;
}
return <div className="h-4 w-4 rounded-sm border border-gray-400"></div>;
}

function handleClickOption(title: string) {
filterOption(title, category, (updatedCategory) => {
setOption((prev) => ({
...prev,
category: updatedCategory,
}));
});
}

return (
<div className="md:hidden">
<div
onClick={() => setOption((prev) => ({ ...prev, category: [] }))}
key={`category-option_all`}
>
<label className="w-46 mx-1 flex items-center justify-center gap-2 rounded-md p-1 hover:bg-gray-100 ">
{fillCheckBox()}
<span className="w-[50%] py-1 ">전체 선택</span>
</label>
</div>
{CatogoryColor.map((categoryItem) => (
<div
onClick={() => handleClickOption(categoryItem.title)}
key={`category-option_${categoryItem.title}`}
>
<CheckBox title={categoryItem.title} list={option.category} />
</div>
))}
</div>
);
}

export default Category;
Loading

0 comments on commit 895bfd1

Please sign in to comment.