From 8cb233549d5086c8ce4d95c39b2635c859ceeab6 Mon Sep 17 00:00:00 2001 From: Seo San <dannysir@naver.com> Date: Tue, 12 Nov 2024 18:31:00 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20#62?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Search/SearchCard.tsx | 49 +++++++++++++++++++ .../components/Search/SearchHistoryList.tsx | 2 +- FE/src/components/Search/SearchList.tsx | 16 ++++++ FE/src/components/Search/index.tsx | 28 ++++++++--- 4 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 FE/src/components/Search/SearchCard.tsx create mode 100644 FE/src/components/Search/SearchList.tsx diff --git a/FE/src/components/Search/SearchCard.tsx b/FE/src/components/Search/SearchCard.tsx new file mode 100644 index 00000000..12edffef --- /dev/null +++ b/FE/src/components/Search/SearchCard.tsx @@ -0,0 +1,49 @@ +type SearchCardProps = { + companyName: string; + previousClose: number; + priceChange: number; +}; + +export default function SearchCard({ + companyName = '회사명', + previousClose = 50000, + priceChange = 2.5, +}: SearchCardProps) { + const isPositive = priceChange > 0; + const isNegative = priceChange < 0; + + const getPriceChangeColor = () => { + if (isPositive) return 'text-red-500'; + if (isNegative) return 'text-blue-500'; + return 'text-gray-500'; + }; + + const formattedPreviousClose = previousClose.toLocaleString(); + const formattedPriceChange = Math.abs(priceChange).toFixed(2); + const priceChangeSymbol = isPositive ? '+' : isNegative ? '-' : ''; + + return ( + <li className='h-[52px] w-full rounded-xl hover:cursor-pointer hover:bg-gray-50'> + <div className='my-2 flex w-full items-center justify-between px-4'> + <div className='flex-1'> + <p className='text-left font-medium text-juga-grayscale-black'> + {companyName} + </p> + </div> + + <div className='flex flex-col items-end justify-center gap-0.5'> + <p className='text-right text-sm font-medium text-gray-900'> + {formattedPreviousClose} + </p> + + <p + className={`text-right text-xs font-medium ${getPriceChangeColor()}`} + > + {priceChangeSymbol} + {formattedPriceChange}% + </p> + </div> + </div> + </li> + ); +} diff --git a/FE/src/components/Search/SearchHistoryList.tsx b/FE/src/components/Search/SearchHistoryList.tsx index 666504bd..53c67759 100644 --- a/FE/src/components/Search/SearchHistoryList.tsx +++ b/FE/src/components/Search/SearchHistoryList.tsx @@ -10,7 +10,7 @@ export function SearchHistoryList({ onDeleteItem, }: SearchHistoryListProps) { return ( - <div className={'flex w-full flex-col'}> + <div className={'flex w-full flex-col pb-2'}> <div className={'mb-2 flex items-center justify-between'}> <div className={'text-start text-sm font-bold'}>최근 검색</div> </div> diff --git a/FE/src/components/Search/SearchList.tsx b/FE/src/components/Search/SearchList.tsx new file mode 100644 index 00000000..fe633b56 --- /dev/null +++ b/FE/src/components/Search/SearchList.tsx @@ -0,0 +1,16 @@ +import SearchCard from './SearchCard.tsx'; + +export default function SearchList() { + return ( + <> + <div className={'my-4 flex items-center justify-between'}> + <div className={'text-start text-sm font-bold'}>검색 결과</div> + </div> + <ul className='flex h-full w-full flex-col items-center justify-between overflow-y-auto'> + {Array.from({ length: 30 }, (_, index) => { + return <SearchCard key={index} />; + })} + </ul> + </> + ); +} diff --git a/FE/src/components/Search/index.tsx b/FE/src/components/Search/index.tsx index 69ddd642..59e19595 100644 --- a/FE/src/components/Search/index.tsx +++ b/FE/src/components/Search/index.tsx @@ -3,6 +3,7 @@ import useSearchModalStore from '../../store/useSearchModalStore'; import Overlay from '../../utils/ModalOveray'; import { SearchInput } from './SearchInput'; import { SearchHistoryList } from './SearchHistoryList'; +import SearchList from './SearchList.tsx'; export default function SearchModal() { const { isOpen, toggleSearchModal } = useSearchModalStore(); @@ -22,13 +23,26 @@ export default function SearchModal() { return ( <> <Overlay onClick={() => toggleSearchModal()} /> - <section className='fixed left-1/2 top-3 flex w-[640px] -translate-x-1/2 flex-col rounded-2xl bg-white shadow-lg'> - <div className={'flex flex-col gap-5 p-3'}> - <SearchInput value={searchTerm} onChange={setSearchTerm} /> - <SearchHistoryList - searchHistory={searchHistory} - onDeleteItem={handleDeleteHistoryItem} - /> + <section + className={`${searchTerm === '' ? '' : 'h-[520px]'} fixed left-1/2 top-3 flex w-[640px] -translate-x-1/2 flex-col rounded-2xl bg-white shadow-lg`} + > + <div className='flex h-full flex-col p-3'> + <div className='mb-5'> + <SearchInput value={searchTerm} onChange={setSearchTerm} /> + </div> + <div className='flex-1 overflow-hidden'> + <SearchHistoryList + searchHistory={searchHistory} + onDeleteItem={handleDeleteHistoryItem} + /> + {searchTerm === '' ? ( + <></> + ) : ( + <div className='h-full overflow-y-auto'> + <SearchList /> + </div> + )} + </div> </div> </section> </> From aab7b669627b937dc2d4548f240d85545a82a9db Mon Sep 17 00:00:00 2001 From: Seo San <dannysir@naver.com> Date: Tue, 12 Nov 2024 18:56:46 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8=20feat:=20zustand=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=B4=20=ED=83=80=EC=9D=B4=ED=95=91=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EA=B8=B0=EB=A1=9D=20&=20UX=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20#62=20zustand=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=20=EA=B2=80=EC=83=89=EC=B0=BD=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=ED=96=88=EB=8D=98=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=9D=84=20header=EC=97=90=20=ED=91=9C=EC=8B=9C.=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=B0=BD=EC=9D=84=20=EC=97=B4=EC=97=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20input=EC=9C=BC=EB=A1=9C=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=ED=8F=AC=EC=BB=A4=EC=8A=A4=EA=B0=80=20=EA=B0=80=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Header.tsx | 3 ++ FE/src/components/Search/SearchCard.tsx | 36 +++++------------------- FE/src/components/Search/SearchInput.tsx | 8 ++++++ FE/src/components/Search/index.tsx | 9 +++--- FE/src/store/useSearchInputStore.ts | 21 ++++++++++++++ 5 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 FE/src/store/useSearchInputStore.ts diff --git a/FE/src/components/Header.tsx b/FE/src/components/Header.tsx index 182f0991..9ec8dc7a 100644 --- a/FE/src/components/Header.tsx +++ b/FE/src/components/Header.tsx @@ -2,11 +2,13 @@ import { Link } from 'react-router-dom'; import useAuthStore from 'store/authStore'; import useLoginModalStore from 'store/useLoginModalStore'; import useSearchModalStore from '../store/useSearchModalStore.ts'; +import useSearchInputStore from '../store/useSearchInputStore.ts'; export default function Header() { const { toggleModal } = useLoginModalStore(); const { isLogin, resetToken } = useAuthStore(); const { toggleSearchModal } = useSearchModalStore(); + const { searchInput } = useSearchInputStore(); return ( <header className='fixed left-0 top-0 h-[60px] w-full'> @@ -26,6 +28,7 @@ export default function Header() { <input type='text' placeholder='Search...' + value={searchInput} className='h-[36px] w-[280px] rounded-lg bg-juga-grayscale-50 px-4 py-2' onClick={toggleSearchModal} /> diff --git a/FE/src/components/Search/SearchCard.tsx b/FE/src/components/Search/SearchCard.tsx index 12edffef..d48733f6 100644 --- a/FE/src/components/Search/SearchCard.tsx +++ b/FE/src/components/Search/SearchCard.tsx @@ -1,26 +1,7 @@ -type SearchCardProps = { - companyName: string; - previousClose: number; - priceChange: number; -}; - -export default function SearchCard({ - companyName = '회사명', - previousClose = 50000, - priceChange = 2.5, -}: SearchCardProps) { - const isPositive = priceChange > 0; - const isNegative = priceChange < 0; - - const getPriceChangeColor = () => { - if (isPositive) return 'text-red-500'; - if (isNegative) return 'text-blue-500'; - return 'text-gray-500'; - }; - - const formattedPreviousClose = previousClose.toLocaleString(); - const formattedPriceChange = Math.abs(priceChange).toFixed(2); - const priceChangeSymbol = isPositive ? '+' : isNegative ? '-' : ''; +export default function SearchCard() { + const companyName = '회사명'; + const previousClose = 50000; + const priceChange = 2.5; return ( <li className='h-[52px] w-full rounded-xl hover:cursor-pointer hover:bg-gray-50'> @@ -33,14 +14,11 @@ export default function SearchCard({ <div className='flex flex-col items-end justify-center gap-0.5'> <p className='text-right text-sm font-medium text-gray-900'> - {formattedPreviousClose} + {previousClose.toLocaleString()} </p> - <p - className={`text-right text-xs font-medium ${getPriceChangeColor()}`} - > - {priceChangeSymbol} - {formattedPriceChange}% + <p className={'text-right text-xs font-medium text-red-500'}> + +{Math.abs(priceChange).toFixed(2)}% </p> </div> </div> diff --git a/FE/src/components/Search/SearchInput.tsx b/FE/src/components/Search/SearchInput.tsx index 1f3bb6fa..3d698f9d 100644 --- a/FE/src/components/Search/SearchInput.tsx +++ b/FE/src/components/Search/SearchInput.tsx @@ -1,4 +1,5 @@ import { MagnifyingGlassIcon } from '@heroicons/react/16/solid'; +import { useEffect, useRef } from 'react'; interface SearchInputProps { value: string; @@ -6,6 +7,12 @@ interface SearchInputProps { } export function SearchInput({ value, onChange }: SearchInputProps) { + const inputRef = useRef<HTMLInputElement>(null); + + useEffect(() => { + inputRef.current?.focus(); + }, []); + return ( <div className={ @@ -14,6 +21,7 @@ export function SearchInput({ value, onChange }: SearchInputProps) { > <MagnifyingGlassIcon className={'ml-3 h-4 w-4 fill-juga-grayscale-200'} /> <input + ref={inputRef} className={ 'h-[36px] w-full rounded-lg bg-juga-grayscale-50 py-2 pl-[10px] pr-10 text-sm font-normal focus:outline-none' } diff --git a/FE/src/components/Search/index.tsx b/FE/src/components/Search/index.tsx index 59e19595..68aa7377 100644 --- a/FE/src/components/Search/index.tsx +++ b/FE/src/components/Search/index.tsx @@ -4,10 +4,11 @@ import Overlay from '../../utils/ModalOveray'; import { SearchInput } from './SearchInput'; import { SearchHistoryList } from './SearchHistoryList'; import SearchList from './SearchList.tsx'; +import useSearchInputStore from '../../store/useSearchInputStore.ts'; export default function SearchModal() { const { isOpen, toggleSearchModal } = useSearchModalStore(); - const [searchTerm, setSearchTerm] = useState(''); + const { searchInput, setSearchInput } = useSearchInputStore(); const [searchHistory, setSearchHistory] = useState<string[]>([]); useEffect(() => { @@ -24,18 +25,18 @@ export default function SearchModal() { <> <Overlay onClick={() => toggleSearchModal()} /> <section - className={`${searchTerm === '' ? '' : 'h-[520px]'} fixed left-1/2 top-3 flex w-[640px] -translate-x-1/2 flex-col rounded-2xl bg-white shadow-lg`} + className={`${searchInput === '' ? '' : 'h-[520px]'} fixed left-1/2 top-3 flex w-[640px] -translate-x-1/2 flex-col rounded-2xl bg-white shadow-lg`} > <div className='flex h-full flex-col p-3'> <div className='mb-5'> - <SearchInput value={searchTerm} onChange={setSearchTerm} /> + <SearchInput value={searchInput} onChange={setSearchInput} /> </div> <div className='flex-1 overflow-hidden'> <SearchHistoryList searchHistory={searchHistory} onDeleteItem={handleDeleteHistoryItem} /> - {searchTerm === '' ? ( + {searchInput === '' ? ( <></> ) : ( <div className='h-full overflow-y-auto'> diff --git a/FE/src/store/useSearchInputStore.ts b/FE/src/store/useSearchInputStore.ts new file mode 100644 index 00000000..b6fe6451 --- /dev/null +++ b/FE/src/store/useSearchInputStore.ts @@ -0,0 +1,21 @@ +import { create } from 'zustand'; + +interface SearchInputStore { + searchInput: string; + setSearchInput: (input: string) => void; + resetSearchInput: () => void; +} + +const useSearchInputStore = create<SearchInputStore>((set) => ({ + searchInput: '', + + setSearchInput: (input: string) => { + set({ searchInput: input }); + }, + + resetSearchInput: () => { + set({ searchInput: '' }); + }, +})); + +export default useSearchInputStore; From 789aca11008829954ac0608b3cae2c028c6e6321 Mon Sep 17 00:00:00 2001 From: Seo San <dannysir@naver.com> Date: Tue, 12 Nov 2024 19:16:59 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A7=20fix:=20App.tsx=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=EC=97=90=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EB=88=84=EB=9D=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FE/src/App.tsx b/FE/src/App.tsx index fd68bddd..d3d0e74d 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -9,6 +9,7 @@ import Home from 'page/Home'; import StocksDetail from 'page/StocksDetail'; import Header from 'components/Header'; import Login from 'components/Login'; +import SearchModal from './components/Search'; function App() { return ( @@ -33,6 +34,7 @@ function Layout() { <Outlet /> </main> <Login /> + <SearchModal /> </> ); } From f56fc90f33cbadb35669dd8c46b9e2c25c937069 Mon Sep 17 00:00:00 2001 From: Seo San <dannysir@naver.com> Date: Tue, 12 Nov 2024 19:24:31 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/page/Home.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/FE/src/page/Home.tsx b/FE/src/page/Home.tsx index d79f6fce..3ae5da96 100644 --- a/FE/src/page/Home.tsx +++ b/FE/src/page/Home.tsx @@ -1,6 +1,5 @@ import TopFive from 'components/TopFive/TopFive'; import StockIndex from 'components/StockIndex/index.tsx'; -import SearchModal from '../components/Search'; export default function Home() { return ( From ba1bd743cd7d299a22917d8d72d3ac2a93471a31 Mon Sep 17 00:00:00 2001 From: Seo San <dannysir@naver.com> Date: Tue, 12 Nov 2024 19:27:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=A7=20fix:=20eslint=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/TopFive/Nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/TopFive/Nav.tsx b/FE/src/components/TopFive/Nav.tsx index 941c8249..5fc2bef3 100644 --- a/FE/src/components/TopFive/Nav.tsx +++ b/FE/src/components/TopFive/Nav.tsx @@ -43,7 +43,7 @@ export default function Nav() { key={market} ref={(el) => (buttonRefs.current[index] = el)} onClick={() => handleMarketChange(market)} - className={`relative px-2 py-2`} + className={'relative px-2 py-2'} > {market} </button>