Skip to content

Commit

Permalink
Merge pull request #204 from TripInfoWeb/dev_gathering
Browse files Browse the repository at this point in the history
feat: 뒤로가기 시 주소에 따라 필터 관련 UI 렌더링되게 변경
  • Loading branch information
ssssksss authored Aug 15, 2024
2 parents 98a523c + a23499d commit caf817f
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 209 deletions.
3 changes: 3 additions & 0 deletions public/common/dropdown-down-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 28 additions & 9 deletions src/components/common/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { useState } from "react";
import { IoIosArrowDown } from "react-icons/io";
import useOutsideClick from "@/hooks/useOutsideClick";
import Image from "next/image";
import { useEffect, useRef, useState } from "react";

interface IDropdown<T> {
options: { value: T, name: string }[];
dropdownHandler: (value: T) => void;
defaultValue: T;
value: T;
}

export default function Dropdown<T>({ options, dropdownHandler, defaultValue }: IDropdown<T>) {
export default function Dropdown<T>({ options, dropdownHandler, defaultValue, value }: IDropdown<T>) {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<T>(defaultValue);
const [documentBody, setDocumentBody] = useState<HTMLElement | null>(null);
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
setDocumentBody(document.body);
}, []);
useOutsideClick(ref, () => {
setIsOpen(false);
});

useEffect(() => {
setSelectedOption(value);
},[value])

const toggleDropdown = () => {
setIsOpen(!isOpen);
Expand All @@ -21,30 +36,34 @@ export default function Dropdown<T>({ options, dropdownHandler, defaultValue }:
};

return (
<div className="relative inline-block text-left ">
<div ref={ref} className="relative inline-block text-left ">
<div>
<button
onClick={toggleDropdown}
className="items-center hover:text-main inline-flex justify-between w-full rounded-md px-0 py-2 bg-white text-sm font-medium text-gray-700 focus:outline-none "
className="items-center gap-1 hover:text-main inline-flex justify-between w-full rounded-md px-0 py-2 text-sm font-medium text-gray-700 focus:outline-none "
>
{options.filter(i=>i.value == selectedOption)[0].name}
<IoIosArrowDown />
{
isOpen ?
<Image src="/common/dropdown-down-arrow.svg" className="translate-y-[50%] rotate-180" alt="location-icon" width={8} height={4} /> :
<Image src="/common/dropdown-down-arrow.svg" className="translate-y-[25%]" alt="location-icon" width={8} height={4} />
}
</button>
</div>

{isOpen && (
<div
className="origin-top-right absolute right-0 mt-2 w-[6rem] rounded-md bg-white ring-2 ring-offset-2 ring-black ring-opacity-5 focus:outline-none transition ease-out duration-200"
className="absolute w-full mt-2 rounded-md bg-white ring-2 ring-offset-2 ring-black ring-opacity-5 focus:outline-none transition ease-out duration-200"
>
<div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
<div className={"py-1 min-w-max "} role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
{options.map((i) => (
<button
key={i.name}
onClick={() => {
dropdownHandler(i.value);
handleOptionClick(i.value);
}}
className={`block px-4 py-2 text-sm text-gray-700 hover:text-main w-full hover:bg-white ${
className={`block px-2 py-2 text-sm text-gray-700 hover:text-main w-full hover:bg-white ${
selectedOption === i.value ? "bg-white text-main" : ""
}`}
role="menuitem"
Expand Down
21 changes: 21 additions & 0 deletions src/components/gathering/GatheringCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { GatheringRecommend } from "@/types/GatheringDto";
import GatheringItem from "../common/GatheringItem";

interface IGatheringCardList {
data: GatheringRecommend[]
}
const GatheringCardList = ({data}: IGatheringCardList) => {
return (
<div className="min-h-[20rem]">
<div className="my-6 grid min-[745px]:grid-cols-2 m-auto gap-x-3 gap-y-3">
{data?.map((i, index) => (
<GatheringItem
key={i.gatheringId}
{...i}
/>
))}
</div>
</div>
);
};
export default GatheringCardList
16 changes: 15 additions & 1 deletion src/components/gathering/GatheringFilterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { add, format } from "date-fns";
import ko from "date-fns/locale/ko";
import Image from "next/image";
import { useSearchParams } from "next/navigation";
import { useState } from "react";
import { useEffect, useState } from "react";
import { DateRangePicker, RangeKeyDict } from "react-date-range";

interface IGatheringFilterModalProps {
Expand Down Expand Up @@ -175,6 +175,20 @@ const GatheringFilterModal = ({closeModal}: IGatheringFilterModalProps) => {
closeModal();
};

useEffect(() => {
setLocation(+(searchParams.get('location') || 0));
setSex(searchParams.get('sex') || "ALL");
setStartAge(+(searchParams.get('startAge') || 20));
setEndAge(+(searchParams.get('endAge') || 59));
setCalendarDate([
{
startDate: (searchParams.get('startDate') ? new Date(searchParams.get('startDate') as string) : new Date()),
endDate: (searchParams.get('endDate') ? new Date(searchParams.get('endDate') as string) : new Date()),
key: "selection",
},
]);
}, [searchParams])

return (
<div
className={
Expand Down
104 changes: 19 additions & 85 deletions src/components/gathering/GatheringList.tsx
Original file line number Diff line number Diff line change
@@ -1,110 +1,44 @@
"use client"

import { Modal } from "@/components/common/modal/Modal";
import GatheringCardListContainer from "@/containers/gathering/GatheringCardListContainer";
import GatheringCategoryListContainer from "@/containers/gathering/GatheringCategoryListContainer";
import GatheringExcludeCompleteContainer from "@/containers/gathering/GatheringExcludeCompleteContainer";
import GatheringFilterContainer from "@/containers/gathering/GatheringFilterContainer";
import GatheringSearchContainer from "@/containers/gathering/GatheringSearchContainer";
import GatheringSortContainer from "@/containers/gathering/GatheringSortContainer";
import { GatheringCategoryListType } from "@/types/GatheringCategoryDto";
import Image from "next/image";
import "react-date-range/dist/styles.css";
import "react-date-range/dist/theme/default.css";
import { VscSettings } from "react-icons/vsc";
import Dropdown from "../common/dropdown/Dropdown";
import GatheringFilterModal from "./GatheringFilterModal";
import GatheringSubCategoryList from "./GatheringSubCategoryList";

interface IGatheringList {
isModal: boolean;
closeModal: () => void;
openModal: () => void;
gatheringCategoryList: GatheringCategoryListType;
activeGatheringCategoryId: number;
isExcludeCompleted: boolean;
checkExcludeCompleteGatheringHandler: () => void;
changeGatheringCategoryHandler: (_id: number) => void;
sortHandler: (value: string) => void;
searchHandler: (value: string) => void;
keywordRef: React.RefObject<HTMLInputElement>;
sortDefaultValue: string;
}

const GatheringList = (props: IGatheringList) => {

return (
<div className="w-full flex flex-col pt-[5.5rem] ">
<div className="w-full flex flex-col pt-[5.5rem]">
<article className="flex flex-col gap-y-4 max-[768px]:items-start max-[768px]:space-y-6 max-[768px]:space-y-reverse">
<div className="w-full flex flex-row max-[744px]:flex-col max-[744px]:gap-y-5 justify-between items-center max-[768px]:w-full max-[768px]:justify-between">
<label className="relative min-[745px]:w-full min-[745px]:max-w-[28rem] w-full max-[768px]:w-full group">
<input
className="bg-[0rem_center] w-full pb-1 pl-8 pr-[3.5rem] border-b-[0.0625rem] border-black bg-search-icon bg-[length:1rem] bg-no-repeat text-sm outline-none placeholder:font-medium placeholder:text-gray2"
type="text"
autoComplete="search"
name="search"
placeholder="검색하기"
maxLength={30}
ref={props.keywordRef}
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.ctrlKey && e.key === 'Enter' && props.keywordRef.current) {
props.searchHandler(props.keywordRef.current.value);
}
}}
/>
<button
className="absolute right-[0rem] bg-main text-white px-3 rounded-md opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-within:opacity-100"
onClick={() => {
if (props.keywordRef.current) {
props.searchHandler(props.keywordRef.current.value);
}
}}
>
검색
</button>
</label>
<GatheringSearchContainer />
<div className="max-[745px]:w-full flex flex-row justify-between items-center gap-4 text-sm font-medium text-gray1 ">
<div className={"flex gap-4"}>
<button
className="flex flex-row items-center hover:text-main"
onClick={() => props.openModal()}
>
<VscSettings size={"1.25rem"} />
<div>필터</div>
</button>
<Modal isOpen={props.isModal} onClose={() => props.closeModal()}>
<GatheringFilterModal closeModal={() => props.closeModal()} />
</Modal>
<Dropdown options={[{
value: "",
name: "최신순",
}, {
value: "likes",
name: "인기순",
}, {
value: "views",
name: "조회순",
}]} dropdownHandler={props.sortHandler} defaultValue={props.sortDefaultValue} />
<GatheringFilterContainer />
<GatheringSortContainer />
</div>
<div className="min-[745px]:hidden">
<GatheringExcludeCompleteContainer />
</div>
<button className={"flex gap-1 text-sm text-black font-medium min-[745px]:hidden"} onClick={props.checkExcludeCompleteGatheringHandler}>
{
props.isExcludeCompleted ?
<Image src="/common/check-active-icon.svg" alt="location-icon" width={20} height={20} /> :
<Image src="/common/check-empty-icon.svg" alt="location-icon" width={20} height={20} />
}
모집완료 제외
</button>
</div>
</div>
<div className="w-full flex justify-between">
<GatheringSubCategoryList gatheringCategoryList={props.gatheringCategoryList} activeGatheringCategoryId={props.activeGatheringCategoryId} changeGatheringCategoryHandler={props.changeGatheringCategoryHandler} />
<button className={"flex gap-1 text-sm text-black font-medium max-[744px]:hidden items-center"} onClick={props.checkExcludeCompleteGatheringHandler}>
{
props.isExcludeCompleted ?
<Image src="/common/check-active-icon.svg" alt="location-icon" width={20} height={20} /> :
<Image src="/common/check-empty-icon.svg" alt="location-icon" width={20} height={20} />
}
모집완료 제외
</button>
<div className="w-full flex justify-between items-center">
<GatheringCategoryListContainer gatheringCategoryList={props.gatheringCategoryList} />
<div className="max-[744px]:hidden min-w-max ">
<GatheringExcludeCompleteContainer />
</div>
</div>
</article>
<div className="mt-6 grid grid-cols-3 gap-5 max-[1024px]:grid-cols-2 max-[744px]:grid-cols-1">

</div>
<GatheringCardListContainer />
</div>
);
};
Expand Down
35 changes: 35 additions & 0 deletions src/components/gathering/GatheringSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
interface IGatheringSearch {
keywordRef: React.RefObject<HTMLInputElement>;
searchHandler: (value: string) => void;
}
const GatheringSearch = (props: IGatheringSearch) => {
return (
<label className="relative min-[745px]:w-full min-[745px]:max-w-[28rem] w-full max-[768px]:w-full group">
<input
className="bg-[0rem_center] w-full pb-1 pl-8 pr-[3.5rem] border-b-[0.0625rem] border-black bg-search-icon bg-[length:1rem] bg-no-repeat text-sm outline-none placeholder:font-medium placeholder:text-gray2"
type="text"
autoComplete="search"
name="search"
placeholder="검색하기"
maxLength={30}
ref={props.keywordRef}
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.ctrlKey && e.key === 'Enter' && props.keywordRef.current) {
props.searchHandler(props.keywordRef.current.value);
}
}}
/>
<button
className="absolute right-[0rem] bg-main text-white px-3 rounded-md opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-within:opacity-100"
onClick={() => {
if (props.keywordRef.current) {
props.searchHandler(props.keywordRef.current.value);
}
}}
>
검색
</button>
</label>
);
};
export default GatheringSearch
29 changes: 0 additions & 29 deletions src/components/gathering/GatheringSubCategoryList.tsx

This file was deleted.

40 changes: 40 additions & 0 deletions src/containers/gathering/GatheringCardListContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import GatheringCardList from "@/components/gathering/GatheringCardList";
import UrlQueryStringToObject from "@/utils/UrlQueryStringToObject";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import PaginationContainer from "../common/PaginationContainer";

interface IGatheringCardListContainer {

}
const GatheringCardListContainer = (props: IGatheringCardListContainer) => {
const searchParams = useSearchParams();
const [page, setPage] = useState(searchParams.get('page') || 0);
const [totalData, setTotalData] = useState(10);
const changeGatheringPageHandler = (id: number) => {
let _url = `/gathering?`;
let temp = UrlQueryStringToObject(window.location.href) || {};
if (page != 0) {
temp.page = id;
}
Object.entries(temp).map(i => {
_url += i[0]+"="+i[1]+"&"
})
if (_url.endsWith("&")) {
_url = _url.slice(0, -1);
}
console.log("GatheringListContainer.tsx 파일 : ", _url);
window.history.pushState(null, "", _url);
}

useEffect(() => {

}, [searchParams])
return (
<div>
<GatheringCardList data={[]} />
<PaginationContainer currentPage={+page} totalPages={totalData} />
</div>
);
};
export default GatheringCardListContainer
Loading

0 comments on commit caf817f

Please sign in to comment.