diff --git a/components/viewProduct/FilterDropdown.tsx b/components/viewProduct/FilterDropdown.tsx index 005332b..9933fd9 100644 --- a/components/viewProduct/FilterDropdown.tsx +++ b/components/viewProduct/FilterDropdown.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; -import { FilterDropdownProps } from '../../types/viewProduct'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { checkedItemsState, filterTagState } from '../../core/atom'; +import { FilterDropdownProps, FilterTagProps } from '../../types/viewProduct'; export default function FilterDropdown(props: FilterDropdownProps) { const { @@ -8,31 +10,67 @@ export default function FilterDropdown(props: FilterDropdownProps) { isExcept, categoryIdx, checkedItem, - handleCheckedItems, + categoryKey, } = props; + const [checkedItems, setCheckedItems] = useRecoilState(checkedItemsState); + const [filterTagList, setFilterTagList] = + useRecoilState(filterTagState); + const filterTagKeys = Object.keys(filterTagList); + const filterTagValues = Object.values(filterTagList); + const handleCheckedItems = ( + categoryIdx: number, + elementIdx: number, + tagText: string, + ) => { + const tag: FilterTagProps = { + categoryIdx: categoryIdx, + elementIdx: elementIdx, + categoryKey: categoryKey, + tagText: tagText, + }; + if (checkedItems[categoryIdx].has(elementIdx)) { + checkedItems[categoryIdx].delete(elementIdx); + const deleteidx = filterTagList.findIndex((item) => { + return ( + item.categoryIdx === categoryIdx && item.elementIdx === elementIdx + ); + }); - const handleCheckedItem = (elementIdx: number) => { - if (checkedItem.has(elementIdx)) { - checkedItem.delete(elementIdx); + let copyFilterTagList = [...filterTagList]; + copyFilterTagList.splice(deleteidx, 1); + setFilterTagList(copyFilterTagList); + console.log(filterTagList); } else { - checkedItem.add(elementIdx); + checkedItems[categoryIdx].add(elementIdx); + setFilterTagList([...filterTagList, tag]); + console.log(filterTagList); } - handleCheckedItems(checkedItem, categoryIdx); + setCheckedItems({ + ...checkedItems, + [categoryIdx]: checkedItems[categoryIdx], + }); }; return ( - {categoryInfo.map((sort: string, elementIdx: number) => { + {categoryInfo.map((tagText: string, elementIdx: number) => { return ( handleCheckedItem(elementIdx)} - isChecked={checkedItem.has(elementIdx)} + htmlFor={`${tagText}${categoryIdx}`} + key={`${tagText}${categoryIdx}`} + onChange={() => + handleCheckedItems(categoryIdx, elementIdx, tagText) + } + isChecked={checkedItems[categoryIdx].has(elementIdx)} > - - {sort} + + {tagText} ); })} diff --git a/components/viewProduct/FilterTag.tsx b/components/viewProduct/FilterTag.tsx new file mode 100644 index 0000000..0def053 --- /dev/null +++ b/components/viewProduct/FilterTag.tsx @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { + checkedItemsState, + filterListState, + filterTagState, +} from '../../core/atom'; +import { IcDeleteTag } from '../../public/assets/icons'; +import { FilterTagProps } from '../../types/viewProduct'; + +export default function FilterTag(props: FilterTagProps) { + const { categoryIdx, elementIdx, categoryKey, tagText } = props; + const [checkedItems, setCheckedItems] = useRecoilState(checkedItemsState); + const [filterTagList, setFilterTagList] = + useRecoilState(filterTagState); + const filterTagValues = Object.values(filterTagList); + const handleFilterTag = ( + categoryIdx: number, + elementIdx: number, + tagText: string, + ) => { + const tag: FilterTagProps = { + categoryIdx: categoryIdx, + elementIdx: elementIdx, + categoryKey: categoryKey, + tagText: tagText, + }; + if (checkedItems[categoryIdx].has(elementIdx)) { + checkedItems[categoryIdx].delete(elementIdx); + const deleteidx = filterTagList.findIndex((item) => { + return ( + item.categoryIdx === categoryIdx && item.elementIdx === elementIdx + ); + }); + + let copyFilterTagList = [...filterTagList]; + copyFilterTagList.splice(deleteidx, 1); + setFilterTagList(copyFilterTagList); + } else { + checkedItems[categoryIdx].add(elementIdx); + setFilterTagList([...filterTagList, tag]); + console.log(filterTagList); + } + + setCheckedItems({ + ...checkedItems, + [categoryIdx]: checkedItems[categoryIdx], + }); + }; + return ( + +

{tagText === '기타' ? `${tagText} (${categoryKey})` : tagText}

+ handleFilterTag(categoryIdx, elementIdx, tagText)} + /> +
+ ); +} +const StFilterTag = styled.div` + display: flex; + align-items: center; + + width: fit-content; + padding: 0.6rem 0.8rem 0.6rem 1rem; + margin: 0.5rem; + + border-radius: 0.6rem; + background-color: ${({ theme }) => theme.colors.gray002}; + color: ${({ theme }) => theme.colors.gray009}; + ${({ theme }) => theme.fonts.b5_14_medium_140}; +`; +const StDeleteBtn = styled(IcDeleteTag)` + margin-left: 0.713rem; + + cursor: pointer; +`; diff --git a/components/viewProduct/ProductFilter.tsx b/components/viewProduct/ProductFilter.tsx index c6efe29..4530789 100644 --- a/components/viewProduct/ProductFilter.tsx +++ b/components/viewProduct/ProductFilter.tsx @@ -1,19 +1,13 @@ import styled from '@emotion/styled'; -import { LoaderValue } from 'next/dist/shared/lib/image-config'; -import React, { EventHandler, useState } from 'react'; +import { useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { checkedItemsState, filterListState } from '../../core/atom'; import { IcClose, IcOpen } from '../../public/assets/icons'; import FilterDropdown from './FilterDropdown'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { filterListState } from '../../core/atom'; -interface ProductFilterIcon { - title: string; - value: boolean; -} + export default function ProductFilter() { - const { filterList } = useRecoilValue(filterListState); + const filterlist = useRecoilValue(filterListState); - const filterListData = Object.values(filterList); - const filterListKeys = Object.keys(filterList); const [visibility, setVisibility] = useState([ false, false, @@ -21,14 +15,10 @@ export default function ProductFilter() { false, false, ]); - const [checkedItems, setCheckedItems] = useState[]>([ - new Set(), - new Set(), - new Set(), - new Set(), - new Set(), - ]); - console.log(checkedItems); + + const filterListData = Object.values(filterlist.filterList); + const filterListKeys = Object.keys(filterlist.filterList); + const [checkedItems, setcheckedItems] = useRecoilState(checkedItemsState); const handleDropdown = (idx: number) => { setVisibility({ @@ -37,10 +27,6 @@ export default function ProductFilter() { }); }; - const handleCheckedItems = (copyCheckedItem: Set, idx: number) => { - setCheckedItems({ ...checkedItems, [idx]: copyCheckedItem }); - }; - //const [repeat, setRepeat] = useState(); // const handleDrop = (idx: number) => { // if (visibility[idx]) { @@ -83,7 +69,9 @@ export default function ProductFilter() { isDrop={visibility[idx]} checkedItem={checkedItems[idx]} categoryKey={title} + handleCheckedItems={handleCheckedItems} + /> )} @@ -94,9 +82,10 @@ export default function ProductFilter() { const StFilterWrapper = styled.div` width: 20rem; - height: 28rem; + height: fit-content; padding-left: 1.2rem; margin-right: 2.4rem; + margin-bottom: 7.2rem; `; const StFilterTitle = styled.div` display: flex; @@ -114,7 +103,3 @@ const StFilterSection = styled.section<{ isDrop: boolean }>` ${({ theme }) => theme.fonts.b4_15_semibold_146}; cursor: pointer; `; - -// function repeat(repeat: any) { -// throw new Error('Function not implemented.'); -// } diff --git a/components/viewProduct/TagSection.tsx b/components/viewProduct/TagSection.tsx new file mode 100644 index 0000000..9ede498 --- /dev/null +++ b/components/viewProduct/TagSection.tsx @@ -0,0 +1,65 @@ +import styled from '@emotion/styled'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { filterListState, filterTagState } from '../../core/atom'; +import { IcUndoBtn } from '../../public/assets/icons'; +import { FilterTagProps } from '../../types/viewProduct'; +import FilterTag from './FilterTag'; + +export default function TagSection() { + const filterTagList = useRecoilValue(filterTagState); + + return ( + + + {filterTagList.map( + ({ categoryIdx, elementIdx, categoryKey, tagText }) => { + return ( + + ); + }, + )} + + +

모두 해제

+ +
+
+ ); +} + +const StTagWrapper = styled.article` + display: flex; + justify-content: flex-start; + flex-flow: row wrap; + + width: 77.6rem; + height: fit-content; +`; +const StTagSection = styled.div` + display: flex; + align-items: flex-start; + justify-content: space-between; + + padding: 1.5rem 0; + margin-left: 2.4rem; + border-bottom: 0.1rem solid ${({ theme }) => theme.colors.gray002}; +`; +const StUndoAllTagBtn = styled.button` + display: flex; + align-items: center; + + padding: 0 0 0 1rem; + margin-top: 0.5rem; + + background-color: ${({ theme }) => theme.colors.white}; + border-radius: 0.6rem; + border: 0.1rem solid ${({ theme }) => theme.colors.gray004}; + color: ${({ theme }) => theme.colors.gray007}; + ${({ theme }) => theme.fonts.b5_14_medium_140}; +`; diff --git a/components/viewProduct/index.ts b/components/viewProduct/index.ts index c525f54..e0231ab 100644 --- a/components/viewProduct/index.ts +++ b/components/viewProduct/index.ts @@ -3,4 +3,6 @@ export { default as ProductFilter } from './ProductFilter'; export { default as FilterDropdown } from './FilterDropdown'; export { default as ToyList } from './ToyList'; export { default as ToyPreview } from './ToyPreview'; +export { default as FilterTag } from './FilterTag'; +export { default as TagSection } from './TagSection'; export { default as TopFloatingBtn } from './TopFloatingBtn'; diff --git a/core/atom.ts b/core/atom.ts index e1838c3..dda7984 100644 --- a/core/atom.ts +++ b/core/atom.ts @@ -1,8 +1,9 @@ -import { atom } from 'recoil'; +import { atom, selector, selectorFamily } from 'recoil'; import { recoilPersist } from 'recoil-persist'; //페이지가 변경되더라도 상태관리를 유지 import { PostCommunityBody } from '../types/community'; import { PostLoginBody, UserData } from '../types/user'; -import { FilterTagProps } from '../types/viewProduct'; +import { FilterDropdownProps, FilterTagProps } from '../types/viewProduct'; + const { persistAtom } = recoilPersist(); @@ -17,6 +18,7 @@ export const userInfoState = atom({ effects_UNSTABLE: [persistAtom], }); + export const newPostInfoState = atom({ key: 'newPostInfo', default: { @@ -26,6 +28,7 @@ export const newPostInfoState = atom({ }, effects_UNSTABLE: [persistAtom], }); + export const filterListState = atom({ key: 'filterListState', default: { @@ -82,3 +85,14 @@ export const filterTagState = atom({ key: 'filterTagState', default: [], }); + +export const checkedItemsState = atom[]>({ + key: 'checkedItemsState', + default: [ + new Set(), + new Set(), + new Set(), + new Set(), + new Set(), + ], +}); diff --git a/pages/viewProduct.tsx b/pages/viewProduct.tsx index a752677..2ade126 100644 --- a/pages/viewProduct.tsx +++ b/pages/viewProduct.tsx @@ -1,5 +1,7 @@ import { + FilterTag, ProductFilter, + TagSection, TopFloatingBtn, ViewProductBanner, } from '../components/viewProduct'; @@ -12,17 +14,25 @@ import { LandingPriceSort, LandingProductFilter, } from '../components/landing/viewProduct'; + import { PriceFilter, PageNavigation } from '../components/common'; import { ToyData } from '../types/toy'; import { toyMockData } from '../mocks/data/toyMockData'; import { GetServerSideProps, InferGetServerSidePropsType } from 'next'; +import theme from '../styles/theme'; +import { useRecoilValue } from 'recoil'; +import { FilterTagProps } from '../types/viewProduct'; +import { filterTagState } from '../core/atom'; + + const limit = 40; export default function viewProduct({ data, }: InferGetServerSidePropsType) { - //default는 낮은 가격순 + + const [priceDesc, setPriceDesc] = useState(true); const [toyList, setToyList] = useState([]); const [currentPage, setCurrentPage] = useState(1); @@ -33,7 +43,14 @@ export default function viewProduct({ const handleCurrentPage = (nextPage: number) => { setCurrentPage(nextPage); - }; + }; + + const [selectPrice, setSelectPrice] = useState([true, false]); + // useSWR로 로딩 판단할 것임 + const isLoading = false; + const filterTagList = useRecoilValue(filterTagState); + + // let { productList, isLoading, isError } = priceDesc // ? (useGetCollectionProduct('price-desc') as GetCollectionProduct) @@ -75,6 +92,7 @@ export default function viewProduct({ + {filterTagList.length != 0 && } + + + + diff --git a/public/assets/icons/ic_updo_gray006.svg b/public/assets/icons/ic_updo_gray006.svg new file mode 100644 index 0000000..52bc42c --- /dev/null +++ b/public/assets/icons/ic_updo_gray006.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/icons/index.ts b/public/assets/icons/index.ts index 51bffae..80b543c 100644 --- a/public/assets/icons/index.ts +++ b/public/assets/icons/index.ts @@ -37,6 +37,8 @@ export { default as IcViewBookmarkSelected } from './ic_bookmark_selected_40.svg export { default as IcViewBookmarkUnselected } from './ic_bookmark_unselected_40.svg'; export { default as IcMainBookmarkSelected } from './ic_bookmark_selected_48.svg'; export { default as IcMainBookmarkUnselected } from './ic_bookmark_unselected_48.svg'; +export { default as IcDeleteTag } from './delete_circle.svg'; +export { default as IcUndoBtn } from './ic_updo_gray006.svg'; export { default as IcSignupLogo } from './nori.svg'; export { default as IcLoginNori } from './ic_character_small.svg'; export { default as IcKakaoBtn } from './btn_social_login_kakao.svg'; diff --git a/types/viewProduct.ts b/types/viewProduct.ts index d394b46..06128aa 100644 --- a/types/viewProduct.ts +++ b/types/viewProduct.ts @@ -6,6 +6,13 @@ export interface FilterDropdownProps { checkedItem: Set; categoryKey: string; handleCheckedItems: (copyCheckedItem: Set, idx: number) => void; + +} +export interface FilterTagProps { + categoryIdx: number; + elementIdx: number; + categoryKey: string; + tagText: string; } export interface FilterTagProps { categoryIdx: number; diff --git a/utils/handleCheckedItems.ts b/utils/handleCheckedItems.ts new file mode 100644 index 0000000..caddbcc --- /dev/null +++ b/utils/handleCheckedItems.ts @@ -0,0 +1,34 @@ +import { useRecoilState } from 'recoil'; +import { checkedItemsState, filterTagState } from '../core/atom'; +import { FilterDropdownProps, FilterTagProps } from '../types/viewProduct'; + +export const handleCheckedItems = (props: FilterTagProps) => { + const { categoryIdx, elementIdx, categoryKey, tagText } = props; + const [checkedItems, setCheckedItems] = useRecoilState(checkedItemsState); + const [filterTagList, setFilterTagList] = + useRecoilState(filterTagState); + + if (checkedItems[categoryIdx].has(elementIdx)) { + checkedItems[categoryIdx].delete(elementIdx); + setFilterTagList( + filterTagList.filter( + (item) => + item.categoryIdx !== categoryIdx && + item.elementIdx !== elementIdx && + item.tagText !== tagText, + ), + ); + } else { + checkedItems[categoryIdx].add(elementIdx); + const tag: FilterTagProps = { + categoryIdx: categoryIdx, + elementIdx: elementIdx, + categoryKey: categoryKey, + tagText: Object.values(checkedItems[categoryIdx])[elementIdx], + }; + setFilterTagList([...filterTagList, tag]); + console.log(filterTagList); + } + + setCheckedItems({ ...checkedItems, [elementIdx]: checkedItems[categoryIdx] }); +};