diff --git a/src/@types/models/getSearch.ts b/src/@types/models/getSearch.ts
new file mode 100644
index 0000000..e26f213
--- /dev/null
+++ b/src/@types/models/getSearch.ts
@@ -0,0 +1,30 @@
+import { AssetGroupSymbol, AssetTypeSymbol } from "./assetSymbol";
+import { CategoryTypeSymbol } from "./categoryTypeSymbol";
+
+/**
+ * 검색 조회 Response Interface
+ */
+
+export type GetSearchItems = GetSearchItem[];
+
+export type GetSearchItem = {
+ accountId: number;
+ categoryName: string;
+ assetGroup: AssetGroupSymbol;
+ assetType: AssetTypeSymbol;
+ assetName: string;
+ amount: number;
+ transactionType: CategoryTypeSymbol;
+ transactionDetail: string;
+ transactedAt: string;
+ address: string;
+ memo: string;
+ imageIds: number[];
+ recurringType: string;
+ isInstallment: boolean;
+ installmentMonth: number;
+ createdAt: string;
+ updatedAt: string;
+ latitude: string;
+ longitude: string;
+};
diff --git a/src/components/Common/DayIncomeExpenseInfo/index.tsx b/src/components/Common/DayIncomeExpenseInfo/index.tsx
index 682f4bb..711d2bd 100644
--- a/src/components/Common/DayIncomeExpenseInfo/index.tsx
+++ b/src/components/Common/DayIncomeExpenseInfo/index.tsx
@@ -1,11 +1,12 @@
+import { GetSearchItem } from "@/src/@types/models/getSearch";
import { theme } from "../../../assets/theme";
import { AccountsData } from "../DayIncomeExpenseInfos";
import { DayIncomeExpenseInfoUI } from "./style";
import { ChangeNumberForAccounting, ChangeTime } from "@/src/assets/util";
interface DayIncomeExpenseInfoProps {
- onClick: (item: AccountsData) => void;
- item: AccountsData;
+ onClick: (item: AccountsData | GetSearchItem) => void;
+ item: AccountsData | GetSearchItem;
}
function DayIncomeExpenseInfo({ onClick, item }: DayIncomeExpenseInfoProps) {
diff --git a/src/components/Common/DayIncomeExpenseInfos/index.tsx b/src/components/Common/DayIncomeExpenseInfos/index.tsx
index a00f9d4..eee5d73 100644
--- a/src/components/Common/DayIncomeExpenseInfos/index.tsx
+++ b/src/components/Common/DayIncomeExpenseInfos/index.tsx
@@ -1,4 +1,9 @@
+import { useRecoilState } from "recoil";
import DayIncomeExpenseInfo from "../DayIncomeExpenseInfo";
+import { searchResultDataAtom } from "@/src/hooks/recoil/searchResultData";
+import { searchKeywordClickedState } from "@/src/hooks/recoil/searchKeywordClickedState";
+import { GetSearchItem } from "@/src/@types/models/getSearch";
+import { DayIncomeExpenseInfosUI } from "./style";
export interface AccountsData {
accountId: number;
@@ -56,14 +61,29 @@ function DayIncomeExpenseInfos() {
},
];
- const eventHandler = (item: AccountsData) => {
+ const [searchResultData] = useRecoilState(searchResultDataAtom);
+ const [searchKeywordClicked] = useRecoilState(searchKeywordClickedState);
+
+ const eventHandler = (item: AccountsData | GetSearchItem) => {
console.log("item: ", item);
};
return (
-
- {exampleAccounts === undefined ? (
- Nothing!
+
+ {searchKeywordClicked && searchResultData.length > 0 ? (
+ searchResultData.map(item => (
+ eventHandler(item)}
+ item={item}
+ />
+ ))
+ ) : searchKeywordClicked && searchResultData.length === 0 ? (
+
+ 가계부 기록이 없습니다.
+
+ ) : exampleAccounts === undefined ? (
+ 가계부 기록이 없습니다.!
) : (
exampleAccounts.map(item => (
))
)}
-
+
);
}
diff --git a/src/components/Common/DayIncomeExpenseInfos/style.ts b/src/components/Common/DayIncomeExpenseInfos/style.ts
new file mode 100644
index 0000000..9e2581c
--- /dev/null
+++ b/src/components/Common/DayIncomeExpenseInfos/style.ts
@@ -0,0 +1,20 @@
+import styled from "@emotion/styled";
+
+const ContainerUl = styled.ul`
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+`;
+
+const Nothing = styled.div`
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, 50%);
+ display: flex;
+ justify-content: center;
+ align-itmes: center;
+ font-size: 13px;
+`;
+
+export const DayIncomeExpenseInfosUI = { ContainerUl, Nothing } as const;
diff --git a/src/components/SearchInput/index.tsx b/src/components/SearchInput/index.tsx
index a2ec4ec..fc6c593 100644
--- a/src/components/SearchInput/index.tsx
+++ b/src/components/SearchInput/index.tsx
@@ -1,35 +1,85 @@
import SearchSVG from "@/public/icon/Search.svg";
import CancleSVG from "@/public/icon/Cancle.svg";
import { SearchInputUI } from "./style";
-import { ChangeEvent, useState } from "react";
+import { ChangeEvent, useEffect, useState } from "react";
+import { AccountBookAPI } from "@/src/core/api/accountBook";
+import { useRecoilState } from "recoil";
+import {
+ autoCompleteAtom,
+ getSearchAtom,
+} from "@/src/hooks/recoil/useGetSearch";
+import { autoCompleteDatasAtom } from "@/src/hooks/recoil/autoCompleteResults";
+import { showKeywordResultsaAtom } from "@/src/hooks/recoil/showKeywordResultsState";
function SearchInput() {
const [showCancleButton, setShowCancleButton] = useState(false);
- const [keyword, setKeyword] = useState("");
- const handleFocus = () => {
- setShowCancleButton(true);
- };
+ const [autoKeyword, setAutoKeyword] = useRecoilState(autoCompleteAtom);
+ const [, setAutoCompleteDatas] = useRecoilState(autoCompleteDatasAtom);
+ const [, setShowKeywordResults] = useRecoilState(showKeywordResultsaAtom);
+ const [getSearchParams, setGetSearchParams] = useRecoilState(getSearchAtom);
const handleChange = (e: ChangeEvent) => {
- const newValue = e.currentTarget.value;
+ const newValue = e.target.value;
console.log("new value: ", newValue);
- setKeyword(newValue);
+
+ setAutoKeyword(prev => ({
+ ...prev,
+ query: newValue,
+ }));
+
+ if (autoKeyword.query.length >= 0) {
+ setShowCancleButton(true);
+ } else if (autoKeyword.query.length <= 0) {
+ setShowCancleButton(false);
+ }
};
const handleCancleButtonClick = () => {
- setKeyword("");
+ setAutoKeyword(prev => ({
+ ...prev,
+ query: "",
+ }));
+ setGetSearchParams(prev => ({
+ ...prev,
+ categoryName: "",
+ }));
};
+ useEffect(() => {
+ const debounce = setTimeout(() => {
+ const getAutoCompleteDatas = async () => {
+ try {
+ const response = await AccountBookAPI.getAutocomplete(
+ autoKeyword.limit,
+ autoKeyword.query
+ );
+ if (response.status === 200) {
+ console.log("자동완성 검색 성공: ", response.data);
+ setAutoCompleteDatas(response.data);
+ setShowKeywordResults(true);
+ }
+ } catch (error) {
+ console.log("자동완성 검색 실패: ", error);
+ }
+ };
+
+ if (autoKeyword.query.length > 0) getAutoCompleteDatas();
+ }, 200);
+
+ return () => {
+ clearTimeout(debounce);
+ };
+ }, [autoKeyword.query, setAutoCompleteDatas]);
+
return (
{showCancleButton && (
diff --git a/src/components/SearchKeywordResults/index.tsx b/src/components/SearchKeywordResults/index.tsx
new file mode 100644
index 0000000..6b3d6a3
--- /dev/null
+++ b/src/components/SearchKeywordResults/index.tsx
@@ -0,0 +1,73 @@
+import { useRecoilState } from "recoil";
+import { SearchKeywordResultsUI } from "./style";
+import {
+ autoCompleteAtom,
+ getSearchAtom,
+} from "@/src/hooks/recoil/useGetSearch";
+import { autoCompleteDatasAtom } from "@/src/hooks/recoil/autoCompleteResults";
+import { AccountBookAPI } from "@/src/core/api/accountBook";
+import { useEffect } from "react";
+import { searchResultDataAtom } from "@/src/hooks/recoil/searchResultData";
+import { showKeywordResultsaAtom } from "@/src/hooks/recoil/showKeywordResultsState";
+import { searchKeywordClickedState } from "@/src/hooks/recoil/searchKeywordClickedState";
+
+function SearchKeywordResults() {
+ const [getSearchParams, setGetSearchParams] = useRecoilState(getSearchAtom);
+ const [autoKeyword] = useRecoilState(autoCompleteAtom);
+ const [autoCompleteDatas] = useRecoilState(autoCompleteDatasAtom);
+ const [, setSearchResultData] = useRecoilState(searchResultDataAtom);
+ const [showKeywordResults, setShowKeywordResults] = useRecoilState(
+ showKeywordResultsaAtom
+ );
+ const [, setSearchKeywordClicked] = useRecoilState(searchKeywordClickedState);
+
+ const handleKeywordClick = (item: string) => {
+ setGetSearchParams(prev => ({
+ ...prev,
+ categoryName: item,
+ }));
+ setSearchKeywordClicked(true);
+ setShowKeywordResults(false);
+ };
+
+ useEffect(() => {
+ const serachData = async () => {
+ try {
+ const response = await AccountBookAPI.getSearch(
+ getSearchParams.categoryName
+ );
+ if (response.status === 200) {
+ console.log("검색 성공: ", response.data);
+ setSearchResultData(response.data);
+ }
+ } catch (error) {
+ console.log("getSearch Error: ", error);
+ }
+ };
+
+ serachData();
+ }, [getSearchParams.categoryName]);
+
+ return (
+ <>
+ {autoKeyword.query.length > 0 &&
+ autoCompleteDatas &&
+ showKeywordResults && (
+
+ {autoCompleteDatas.map((item, index) => (
+ handleKeywordClick(item)}>
+
+ {item}
+
+
+ 카테고리
+
+
+ ))}
+
+ )}
+ >
+ );
+}
+
+export default SearchKeywordResults;
diff --git a/src/components/SearchLists/style.ts b/src/components/SearchKeywordResults/style.ts
similarity index 68%
rename from src/components/SearchLists/style.ts
rename to src/components/SearchKeywordResults/style.ts
index 83998ba..a6f5d94 100644
--- a/src/components/SearchLists/style.ts
+++ b/src/components/SearchKeywordResults/style.ts
@@ -3,11 +3,13 @@ import { theme } from "@/src/assets/theme";
const containerWidth = "100% - 40px";
-const ContainerUl = styled.div`
+const ContainerUl = styled.ul`
width: calc(${containerWidth});
+ max-height: 350px;
${theme.border_radius};
box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
background-color: ${theme.font_color.white};
+ overflow: auto;
position: absolute;
top: 140px; // 77(hearder)+46+17
@@ -16,18 +18,18 @@ const ContainerUl = styled.div`
li {
height: 50px;
+ padding: 0 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
cursor: pointer;
- // &:hover {
- // background-color: rgba(25, 118, 210, 0.04);
- // }
+ &:hover {
+ background-color: rgba(25, 118, 210, 0.04);
- & > div:first-of-type {
- height: inherit;
- padding: 0 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
+ & > div:first-of-type {
+ font-weight: bold;
+ }
}
}
`;
@@ -49,4 +51,4 @@ const Badge = styled.div`
align-items: center;
`;
-export const SearchListsUI = { ContainerUl, Name, Badge } as const;
+export const SearchKeywordResultsUI = { ContainerUl, Name, Badge } as const;
diff --git a/src/components/SearchLists/index.tsx b/src/components/SearchLists/index.tsx
deleted file mode 100644
index 898487e..0000000
--- a/src/components/SearchLists/index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { SearchListsUI } from "./style";
-
-function SearchLists() {
- return (
-
-
-
- 카테고리 이름
- 카테고리
-
-
-
- );
-}
-
-export default SearchLists;
diff --git a/src/components/SearchResults/index.tsx b/src/components/SearchResults/index.tsx
index fbf98dd..3947db8 100644
--- a/src/components/SearchResults/index.tsx
+++ b/src/components/SearchResults/index.tsx
@@ -1,10 +1,11 @@
import DayIncomeExpenseInfos from "../Common/DayIncomeExpenseInfos";
+import { SearchResultsUI } from "./style";
function SearchResults() {
return (
-
+
-
+
);
}
diff --git a/src/components/SearchResults/style.ts b/src/components/SearchResults/style.ts
index 9451ba4..de6c534 100644
--- a/src/components/SearchResults/style.ts
+++ b/src/components/SearchResults/style.ts
@@ -1,4 +1,11 @@
import styled from "@emotion/styled";
-import { theme } from "@/src/assets/theme";
+import { HeaderHeight, NavigationItemsHeight } from "@/src/assets/height";
-export const SearchResultsUI = {} as const;
+const searchResutlsHeight = `100% - ${HeaderHeight}px - 80px - ${NavigationItemsHeight}px`;
+
+const Container = styled.div`
+ width: 100%;
+ height: calc(${searchResutlsHeight});
+`;
+
+export const SearchResultsUI = { Container } as const;
diff --git a/src/core/api/accountBook.ts b/src/core/api/accountBook.ts
index bc38ca4..48b3d96 100644
--- a/src/core/api/accountBook.ts
+++ b/src/core/api/accountBook.ts
@@ -112,19 +112,38 @@ export const AccountBookAPI = {
/** COMPLETED: getSearch GET 요청하기 */
getSearch: (
categoryName: string,
- endDate: string,
- keyword: string,
- limit: number,
- maxPrice: number,
- minPrice: number,
- page: number,
- startDate: string
+ endDate?: string,
+ keyword?: string,
+ maxPrice?: number,
+ minPrice?: number,
+ startDate?: string,
+ page?: number | undefined,
+ limit?: number | undefined
) => {
- return APIInstance.get(
- ACCOUNTS +
- `/search?categoryName=${categoryName}&endDate=${endDate}&keyword=${keyword}&limit=${limit}&maxPrice=${maxPrice}&minPrice=${minPrice}&page=${page}&startDate=${startDate}`
- );
+ // 쿼리 스트링을 담을 객체
+ const queryParams: Record = {
+ categoryName,
+ };
+
+ // 파라미터가 존재하면 값 추가
+ if (page !== undefined) {
+ queryParams.page = page.toString();
+ }
+ if (limit !== undefined) {
+ queryParams.limit = limit.toString();
+ }
+
+ // 객체를 쿼리 스트링으로 변환
+ const queryString = Object.entries(queryParams)
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+ .join("&");
+
+ // 최종 URL 생성
+ const url = `${ACCOUNTS}/search?${queryString}`;
+
+ return APIInstance.get(url);
},
+
/** COMPLETED: getTransactionStatistics GET 요청하기 */
getTransactionStatistics: (
endDate: string,
diff --git a/src/hooks/recoil/autoCompleteResults.ts b/src/hooks/recoil/autoCompleteResults.ts
new file mode 100644
index 0000000..b31219d
--- /dev/null
+++ b/src/hooks/recoil/autoCompleteResults.ts
@@ -0,0 +1,6 @@
+import { atom } from "recoil";
+
+export const autoCompleteDatasAtom = atom({
+ key: "autoCompleteResultsAtom",
+ default: [],
+});
diff --git a/src/hooks/recoil/searchKeywordClickedState.ts b/src/hooks/recoil/searchKeywordClickedState.ts
new file mode 100644
index 0000000..de616d7
--- /dev/null
+++ b/src/hooks/recoil/searchKeywordClickedState.ts
@@ -0,0 +1,6 @@
+import { atom } from "recoil";
+
+export const searchKeywordClickedState = atom({
+ key: "searchKeywodClickedState",
+ default: false,
+});
diff --git a/src/hooks/recoil/searchResultData.ts b/src/hooks/recoil/searchResultData.ts
new file mode 100644
index 0000000..e9b17fe
--- /dev/null
+++ b/src/hooks/recoil/searchResultData.ts
@@ -0,0 +1,7 @@
+import { GetSearchItems } from "@/src/@types/models/getSearch";
+import { atom } from "recoil";
+
+export const searchResultDataAtom = atom({
+ key: "searchResultDataAtom",
+ default: [],
+});
diff --git a/src/hooks/recoil/showKeywordResultsState.ts b/src/hooks/recoil/showKeywordResultsState.ts
new file mode 100644
index 0000000..24cf41d
--- /dev/null
+++ b/src/hooks/recoil/showKeywordResultsState.ts
@@ -0,0 +1,6 @@
+import { atom } from "recoil";
+
+export const showKeywordResultsaAtom = atom({
+ key: "showKeywordResultsaAtom",
+ default: false,
+});
diff --git a/src/hooks/recoil/useGetSearch.ts b/src/hooks/recoil/useGetSearch.ts
new file mode 100644
index 0000000..6ec14af
--- /dev/null
+++ b/src/hooks/recoil/useGetSearch.ts
@@ -0,0 +1,43 @@
+import { atom } from "recoil";
+
+export interface GetSearchAtomProps {
+ categoryName: string;
+ endDate: string;
+ keyword: string;
+ limit: number;
+ maxPrice: number;
+ minPrice: number;
+ page: number;
+ startDate: string;
+}
+
+export interface AutoCompleteAtomProps {
+ limit: number;
+ query: string;
+}
+
+const initialGetSearchAtom = {
+ categoryName: "",
+ endDate: "",
+ keyword: "",
+ limit: 1,
+ maxPrice: 0,
+ minPrice: 0,
+ page: 0,
+ startDate: "",
+};
+
+const getSearchAtom = atom({
+ key: "getSearchAtom",
+ default: initialGetSearchAtom,
+});
+
+const autoCompleteAtom = atom({
+ key: "autoCompleteAtom",
+ default: {
+ limit: 19,
+ query: "",
+ },
+});
+
+export { initialGetSearchAtom, getSearchAtom, autoCompleteAtom };
diff --git a/src/pages/AccountPage/index.tsx b/src/pages/AccountPage/index.tsx
index de643ad..22a1540 100644
--- a/src/pages/AccountPage/index.tsx
+++ b/src/pages/AccountPage/index.tsx
@@ -3,8 +3,16 @@ import ChosenYearMonth from "@/src/components/Common/ChosenYearMonth";
import FinancialSummary from "@/src/components/Common/FinancialSummary";
import FixedCircleButton from "@/src/components/Common/FixedCircleButton";
import Header from "@/src/components/Common/Header";
+import { useNavigate } from "react-router-dom";
function AccountPage() {
+ const navigate = useNavigate();
+
+ const handleSearchButton = () => {
+ navigate("/search");
+ };
+ const handleFilterButton = () => {};
+
return (
<>
diff --git a/src/pages/SearchPage/index.tsx b/src/pages/SearchPage/index.tsx
index 8c297d0..8a510f8 100644
--- a/src/pages/SearchPage/index.tsx
+++ b/src/pages/SearchPage/index.tsx
@@ -1,6 +1,6 @@
import Header from "@/src/components/Common/Header";
import SearchInput from "@/src/components/SearchInput";
-import SearchLists from "@/src/components/SearchLists";
+import SearchKeywordResults from "@/src/components/SearchKeywordResults";
import SearchResults from "@/src/components/SearchResults";
function SearchPage() {
@@ -15,7 +15,7 @@ function SearchPage() {
isMoreButton={false}
/>
-
+
>
);