Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

술 상세페이지 진입시 히스토리에 추가 #64

Merged
35 changes: 10 additions & 25 deletions client/src/components/SearchHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import { Button, Stack, StackProps, Typography } from "@mui/material";
import { useCallback, useState } from "react";
import XIcon from "@/assets/icons/XIcon.svg";
import { SearchHistoryKeyType } from "@/types/LocalStorageKey";
import useSearchHistory from "@/hooks/searchHistory/useSearchHistory";

interface SearchHistoryProps extends Omit<StackProps, "onClick"> {
storageKey: string;
onClick: () => void;
storageKey: SearchHistoryKeyType;
onClick: (keyword: string) => void;
}

const SearchHistory = ({ storageKey, onClick }: SearchHistoryProps) => {
const getItems = useCallback(() => {
return JSON.parse(localStorage.getItem(storageKey) ?? "[]") as string[];
}, [storageKey]);

const [searchHistory, setSearchHistory] = useState<string[]>(getItems());

const removeAll = useCallback(() => {
localStorage.setItem(storageKey, "[]");
setSearchHistory(getItems());
}, [storageKey]);

const removeByKeyword = useCallback(
(keyword: string) => {
const filteredHistory = searchHistory.filter(
(prevKeyword) => prevKeyword !== keyword
);
localStorage.setItem(storageKey, JSON.stringify(filteredHistory));
setSearchHistory(getItems());
},
[storageKey]
);
const {
state: searchHistory,
removeAll,
removeByKeyword,
} = useSearchHistory(storageKey);

return searchHistory.length > 0 ? (
<>
@@ -45,7 +30,7 @@ const SearchHistory = ({ storageKey, onClick }: SearchHistoryProps) => {
<Stack
key={keyword}
component="li"
onClick={onClick}
onClick={() => onClick(keyword)}
direction="row"
justifyContent="space-between"
alignItems="center"
1 change: 1 addition & 0 deletions client/src/components/newpost/SearchAlcoholInput.tsx
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ const SearchAlcoholInput = ({ setAlcoholNo }: SearchAlcoholInputInterface) => {
onBlur={() => setIsSearchingAlCohol(false)}
autoComplete="off"
/>
{/* FIXME List 컴포넌트로 분리 */}
{isSearchingAlcohol && (
<Box sx={WrapperStyle}>
<List sx={ListStyle}>
44 changes: 29 additions & 15 deletions client/src/components/wiki/AlcoholList.tsx
Original file line number Diff line number Diff line change
@@ -3,27 +3,41 @@ import AlcoholNameTag from "@/components/wiki/AlcoholNameTag";
import { AlcoholDetailInterface } from "@/types/alcohol/AlcoholInterface";
import { Typography } from "@mui/material";
import { memo } from "react";
import AlcoholListSkeleton from "./AlcoholListSkeleton";

const AlcoholList = ({
data: alcohols,
}: {
data: AlcoholDetailInterface[];
}) => {
return (
interface AlcoholList {
data?: AlcoholDetailInterface[];
onClickElement?: (data: AlcoholDetailInterface) => void;
}
/**
* 술 정보 Array 를 입력받아 List로 맵핑해주는 컴포넌트
* onClickElement 속성으로 각 엘리먼트 클릭 시 속성을 지정가능
* @returns
*/
const AlcoholList = ({ data: alcohols, onClickElement }: AlcoholList) => {
return alcohols ? (
<>
{alcohols?.length > 0 ? (
alcohols.map(({ alcoholName, alcoholNo, alcoholType }) => (
<AlcoholNameTag
key={alcoholNo}
alcoholName={alcoholName}
alcoholType={alcoholType}
alcoholNo={alcoholNo}
/>
))
{alcohols.length > 0 ? (
alcohols.map((alcohol) => {
const { alcoholName, alcoholNo, alcoholType } = alcohol;
return (
<AlcoholNameTag
key={alcoholNo}
alcoholName={alcoholName}
alcoholType={alcoholType}
alcoholNo={alcoholNo}
onClick={() => {
onClickElement && onClickElement(alcohol);
}}
/>
);
})
) : (
<Typography textAlign="center">검색 결과가 없어요</Typography>
)}
</>
) : (
<AlcoholListSkeleton disableTimer />
);
};
export default memo(AlcoholList);
10 changes: 2 additions & 8 deletions client/src/components/wiki/AlcoholNameTag.tsx
Original file line number Diff line number Diff line change
@@ -2,10 +2,8 @@ import { Box, BoxProps, Chip, IconButton, Typography } from "@mui/material";
import PostSeeMoreIcon from "@/assets/icons/PostSeeMoreIcon.svg";
import { AlcoholDetailInterface } from "@/types/alcohol/AlcoholInterface";
import XIcon from "@/assets/icons/XIcon.svg";
import { useRouter } from "next/navigation";
import { WIKI_DETAIL } from "@/const/clientPath";

interface AlcoholNameTagInterface extends BoxProps {
export interface AlcoholNameTagInterface extends BoxProps {
alcoholName: AlcoholDetailInterface["alcoholName"];
alcoholType: AlcoholDetailInterface["alcoholType"];
alcoholNo: AlcoholDetailInterface["alcoholNo"];
@@ -21,7 +19,6 @@ const AlcoholNameTag = ({
onClickRemove,
...others
}: AlcoholNameTagInterface) => {
const router = useRouter();
return (
<Box sx={WrapperStyle} {...others}>
<Box
@@ -51,10 +48,7 @@ const AlcoholNameTag = ({
<XIcon />
</IconButton>
) : (
<IconButton
sx={{ p: 0 }}
onClick={() => router.push(WIKI_DETAIL(String(alcoholNo)))}
>
<IconButton sx={{ p: 0 }}>
<PostSeeMoreIcon style={{ margin: "3px 0" }} />
</IconButton>
)}
15 changes: 7 additions & 8 deletions client/src/components/wiki/AlcoholPagination.tsx
Original file line number Diff line number Diff line change
@@ -2,19 +2,18 @@
import useGetAlcoholListQuery from "@/queries/alcohol/useGetAlcoholListQuery";
import AlcoholList from "@/components/wiki/AlcoholList";
import { Pagination, Stack } from "@mui/material";
import AlcoholListSkeleton from "@/components/wiki/AlcoholListSkeleton";
import usePushToWikiDetail from "@/hooks/wiki/usePushToWikiDetail";

const AlcoholPagenation = () => {
const { data: alcohols, isSuccess } = useGetAlcoholListQuery();

const { data: alcohols } = useGetAlcoholListQuery();
const onClickElementHandler = usePushToWikiDetail();
return (
<Stack alignItems="center" gap={2}>
<Stack gap={1} alignItems="center" width={"100%"} height={"232px"}>
{isSuccess ? (
<AlcoholList data={alcohols?.list} />
) : (
<AlcoholListSkeleton disableTimer />
)}
<AlcoholList
data={alcohols?.list}
onClickElement={onClickElementHandler}
/>
</Stack>
<Pagination count={alcohols?.totalCount} />
</Stack>
4 changes: 1 addition & 3 deletions client/src/components/wiki/searchDrawer/WikiSearchDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SwipeableDrawer, Stack, styled, Box } from "@mui/material";
import { Stack } from "@mui/material";
import { useContext } from "react";
import WikiPageContext from "@/store/wiki/WikiPageContext";
import WikiSerachArea from "@/components/wiki/searchDrawer/WikiSerachArea";
@@ -7,7 +7,6 @@ import CustomSwipeableDrawer from "@/components/CustomSwipeableDrawer";
const WikiSearchDrawer = () => {
const { isSearching, setIsSearching } = useContext(WikiPageContext);


return (
<CustomSwipeableDrawer
open={isSearching}
@@ -22,4 +21,3 @@ const WikiSearchDrawer = () => {
};

export default WikiSearchDrawer;

29 changes: 17 additions & 12 deletions client/src/components/wiki/searchDrawer/WikiSerachArea.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import useDebounce from "@/hooks/useDebounce";
import InputSearchIcon from "@/assets/icons/InputSearchIcon.svg";
import { Stack, TextField } from "@mui/material";
import useGetAlcoholListQuery from "@/queries/alcohol/useGetAlcoholListQuery";
import AlcoholList from "@/components/wiki/AlcoholList";
import AlcoholListSkeleton from "../AlcoholListSkeleton";
import SearchHistory from "@/components/SearchHistory";
import { ALCOHOL_SEARCH_HISTORY } from "@/const/localstorageKey";
import WikiPageContext from "@/store/wiki/WikiPageContext";
import usePushToWikiDetail from "@/hooks/wiki/usePushToWikiDetail";

const WikiSerachArea = () => {
const { setIsSearching } = useContext(WikiPageContext);

const [searchKeyword, setSearchKeyword] = useState("");
const debouncedValue = useDebounce(searchKeyword, 300);
const { data: alcohols, isSuccess } = useGetAlcoholListQuery(debouncedValue);
const inputRef = useRef<HTMLInputElement>(null);
const { data: alcohols } = useGetAlcoholListQuery(debouncedValue);

const onClickElementHandler = usePushToWikiDetail();

const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
@@ -37,17 +42,17 @@ const WikiSerachArea = () => {
<Stack gap={1} height={"232px"}>
{searchKeyword ? (
// 입력중인 경우
<>
{isSuccess ? (
<AlcoholList data={alcohols.list} />
) : (
<AlcoholListSkeleton />
)}
</>
<AlcoholList
data={alcohols?.list}
onClickElement={(alcoholData) => {
onClickElementHandler(alcoholData);
setIsSearching(false);
}}
/>
) : (
// 입력이 없는경우 검색기록 표출
<SearchHistory
onClick={() => console.log("눌림")}
onClick={(keyword) => setSearchKeyword(keyword)}
storageKey={ALCOHOL_SEARCH_HISTORY}
/>
)}
33 changes: 33 additions & 0 deletions client/src/hooks/localStorage/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useCallback, useEffect, useState } from "react";

const useLocalStorage = <T>(storageKey: string) => {
/**
* 로컬스토리지에 아이템을 stringify해 저장하는 함수
*/
const setItem = useCallback(
(keyword: T) => {
localStorage.setItem(storageKey, JSON.stringify(keyword));
},
[storageKey]
);
/**
* 로컬 스토리지 아이템을 파싱해서 리턴하는 함수
*/
const getItems = useCallback((): T | null => {
return JSON.parse(localStorage.getItem(storageKey) || "null");
}, [storageKey]);

const [storageValue, setStorageValue] = useState<T | null>(getItems());

// 새로운 값이 저장될 경우, 로컬스토리지에도 같이 저장
useEffect(() => {
if (!storageValue) {
return;
}
setItem(storageValue);
}, [storageValue]);

return [storageValue, setStorageValue] as const;
};

export default useLocalStorage;
54 changes: 54 additions & 0 deletions client/src/hooks/searchHistory/useSearchHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SearchHistoryKeyType } from "@/types/LocalStorageKey";
import { useCallback, useEffect } from "react";
import useLocalStorage from "../localStorage/useLocalStorage";

/**
* 로컬스토리지 키를 입력받아
* 해당 스토리지를 바라보는 state를 리턴 (State 업데이트시 자동으로 반영)
*
* @param storageKey 로컬스토리지 키
* @returns
*/
const useSearchHistory = (storageKey: SearchHistoryKeyType) => {
const [searchHistory, setSearchHistory] =
useLocalStorage<string[]>(storageKey);

useEffect(() => {
if (searchHistory === null) {
setSearchHistory([]);
}
}, []);

const removeAll = useCallback(() => {
setSearchHistory([]);
}, [storageKey]);

const removeByKeyword = useCallback(
(keyword: string) => {
const filteredHistory = (searchHistory ?? []).filter(
(prevKeyword) => prevKeyword !== keyword
);
setSearchHistory(filteredHistory);
},
[storageKey]
);
const addSearchHistory = useCallback(
(keyword: string) => {
setSearchHistory((prev) => {
return [
keyword,
...(prev ?? []).filter((prevKeyword) => prevKeyword !== keyword),
];
});
},
[storageKey]
);
return {
state: searchHistory ?? [],
add: addSearchHistory,
removeAll,
removeByKeyword,
};
};

export default useSearchHistory;
27 changes: 27 additions & 0 deletions client/src/hooks/wiki/usePushToWikiDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { AlcoholDetailInterface } from "@/types/alcohol/AlcoholInterface";
import { useRouter } from "next/navigation";
import { useCallback } from "react";
import useSearchHistory from "../searchHistory/useSearchHistory";
import { ALCOHOL_SEARCH_HISTORY } from "@/const/localstorageKey";
import { WIKI_DETAIL } from "@/const/clientPath";
/**
* 검색히스토리에 해당 술을 남기고, 디테일페이지로 이동시키는 함수를 리턴하는 훅
* @returns 해당 callback함수
*/
const usePushToWikiDetail = () => {
const { add: addToSearchHistory } = useSearchHistory(ALCOHOL_SEARCH_HISTORY);
const router = useRouter();
/**
* 검색히스토리에 해당 술을 남기고, 디테일페이지로 이동시키는 함수를 리턴하는 함수
*/
const onClickElementHandler = useCallback(
({ alcoholName, alcoholNo }: AlcoholDetailInterface) => {
addToSearchHistory(alcoholName);
router.push(WIKI_DETAIL(String(alcoholNo)));
},
[addToSearchHistory]
);
return onClickElementHandler;
};

export default usePushToWikiDetail;
4 changes: 4 additions & 0 deletions client/src/types/LocalStorageKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* 검색 기록 관련 키 타입
*/
export type SearchHistoryKeyType = "alcohol-search-history";