From b890da28ae3a525d146be5cb4bfdd63ea48c3885 Mon Sep 17 00:00:00 2001 From: Kim Da Eun Date: Tue, 15 Oct 2024 16:59:16 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20name=EC=9D=84=20context=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20prop=EC=9C=BC=EB=A1=9C=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/ProcessBoard/index.tsx | 10 +- .../dashboard/ProcessColumn/index.tsx | 198 +++++++++--------- .../SearchApplicantInput.stories.tsx | 35 ---- .../dashboard/SearchApplicantInput/index.tsx | 15 -- .../dashboard/useSearchApplicant.ts | 18 ++ .../src/contexts/SearchApplicantContext.tsx | 39 ---- frontend/src/pages/Dashboard/index.tsx | 49 +++-- 7 files changed, 149 insertions(+), 215 deletions(-) delete mode 100644 frontend/src/components/dashboard/SearchApplicantInput/SearchApplicantInput.stories.tsx delete mode 100644 frontend/src/components/dashboard/SearchApplicantInput/index.tsx create mode 100644 frontend/src/components/dashboard/useSearchApplicant.ts delete mode 100644 frontend/src/contexts/SearchApplicantContext.tsx diff --git a/frontend/src/components/dashboard/ProcessBoard/index.tsx b/frontend/src/components/dashboard/ProcessBoard/index.tsx index dc0542d01..89ae8cb7c 100644 --- a/frontend/src/components/dashboard/ProcessBoard/index.tsx +++ b/frontend/src/components/dashboard/ProcessBoard/index.tsx @@ -1,7 +1,6 @@ import ApplicantModal from '@components/ApplicantModal'; import { Process } from '@customTypes/process'; -import { useSearchApplicant } from '@contexts/SearchApplicantContext'; import ProcessColumn from '../ProcessColumn'; import SideFloatingMessageForm from '../SideFloatingMessageForm'; import S from './style'; @@ -11,11 +10,14 @@ interface KanbanBoardProps { // eslint-disable-next-line react/no-unused-prop-types isSubTab?: boolean; showRejectedApplicant?: boolean; + searchedName?: string; } -export default function ProcessBoard({ processes, showRejectedApplicant = false }: KanbanBoardProps) { - const { debouncedName: searchedName } = useSearchApplicant(); - +export default function ProcessBoard({ + processes, + showRejectedApplicant = false, + searchedName = '', +}: KanbanBoardProps) { return ( {/* TODO: isSubTab을 가져와서 SubTab을 렌더링 합니다. */} diff --git a/frontend/src/components/dashboard/ProcessColumn/index.tsx b/frontend/src/components/dashboard/ProcessColumn/index.tsx index aa465e3f8..ac6b1c220 100644 --- a/frontend/src/components/dashboard/ProcessColumn/index.tsx +++ b/frontend/src/components/dashboard/ProcessColumn/index.tsx @@ -1,18 +1,18 @@ -import React, { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { useModal } from '@contexts/ModalContext'; -import { DropdownProvider } from '@contexts/DropdownContext'; -import { useFloatingEmailForm } from '@contexts/FloatingEmailFormContext'; -import { useSpecificProcessId } from '@contexts/SpecificProcessIdContext'; import { useSpecificApplicantId } from '@contexts/SpecificApplicnatIdContext'; - -import useProcess from '@hooks/useProcess'; +import { useSpecificProcessId } from '@contexts/SpecificProcessIdContext'; +import { Process } from '@customTypes/process'; import useApplicant from '@hooks/useApplicant'; -import specificApplicant from '@hooks/useSpecificApplicant'; +import useProcess from '@hooks/useProcess'; + +import { DropdownProvider } from '@contexts/DropdownContext'; import { DropdownItemType } from '@components/_common/molecules/DropdownItemRenderer'; -import { Process } from '@customTypes/process'; +import { useFloatingEmailForm } from '@contexts/FloatingEmailFormContext'; +import specificApplicant from '@hooks/useSpecificApplicant'; +import { useMemo } from 'react'; import ApplicantCard from '../ApplicantCard'; import ProcessDescription from './ProcessDescription'; import S from './style'; @@ -24,107 +24,101 @@ interface ProcessColumnProps { searchedName?: string; } -const shouldRerender = (prevProps: ProcessColumnProps, nextProps: ProcessColumnProps) => { - if (prevProps.process !== nextProps.process) return false; - if (prevProps.searchedName !== nextProps.searchedName) return false; - return true; -}; - -const ProcessColumn = React.memo( - ({ process, showRejectedApplicant, isPassedColumn = false, searchedName = '' }: ProcessColumnProps) => { - const { dashboardId, applyFormId } = useParams() as { dashboardId: string; applyFormId: string }; - const { processList } = useProcess({ dashboardId, applyFormId }); - const { mutate: moveApplicantProcess } = useApplicant({}); - const { mutate: rejectMutate } = specificApplicant.useRejectApplicant({ dashboardId, applyFormId }); +export default function ProcessColumn({ + process, + showRejectedApplicant, + isPassedColumn = false, + searchedName = '', +}: ProcessColumnProps) { + const { dashboardId, applyFormId } = useParams() as { dashboardId: string; applyFormId: string }; + const { processList } = useProcess({ dashboardId, applyFormId }); + const { mutate: moveApplicantProcess } = useApplicant({}); + const { mutate: rejectMutate } = specificApplicant.useRejectApplicant({ dashboardId, applyFormId }); - const { setApplicantId } = useSpecificApplicantId(); - const { setProcessId } = useSpecificProcessId(); - const { open } = useModal(); - const { open: sideEmailFormOpen } = useFloatingEmailForm(); + const { setApplicantId } = useSpecificApplicantId(); + const { setProcessId } = useSpecificProcessId(); + const { open } = useModal(); + const { open: sideEmailFormOpen } = useFloatingEmailForm(); - const menuItemsList = ({ applicantId }: { applicantId: number }) => { - const menuItems: DropdownItemType[] = [ - { - type: 'subTrigger', - id: 'moveProcess', - name: '단계 이동', - items: processList.map(({ processName, processId }) => ({ - type: 'clickable', - id: processId, - name: processName, - onClick: ({ targetProcessId }) => { - moveApplicantProcess({ processId: targetProcessId, applicants: [applicantId] }); - }, - })), - }, - { + const menuItemsList = ({ applicantId }: { applicantId: number }) => { + const menuItems: DropdownItemType[] = [ + { + type: 'subTrigger', + id: 'moveProcess', + name: '단계 이동', + items: processList.map(({ processName, processId }) => ({ type: 'clickable', - id: 'emailButton', - name: '이메일 보내기', - hasSeparate: true, - onClick: () => { - setApplicantId(applicantId); - sideEmailFormOpen(); + id: processId, + name: processName, + onClick: ({ targetProcessId }) => { + moveApplicantProcess({ processId: targetProcessId, applicants: [applicantId] }); }, + })), + }, + { + type: 'clickable', + id: 'emailButton', + name: '이메일 보내기', + hasSeparate: true, + onClick: () => { + setApplicantId(applicantId); + sideEmailFormOpen(); }, - { - type: 'clickable', - id: 'rejectButton', - name: '불합격 처리', - isHighlight: true, - hasSeparate: true, - onClick: () => { - rejectMutate({ applicantId }); - }, + }, + { + type: 'clickable', + id: 'rejectButton', + name: '불합격 처리', + isHighlight: true, + hasSeparate: true, + onClick: () => { + rejectMutate({ applicantId }); }, - ]; + }, + ]; - return menuItems; - }; + return menuItems; + }; - const cardClickHandler = (id: number) => { - setApplicantId(id); - setProcessId(process.processId); - open(); - }; + const cardClickHandler = (id: number) => { + setApplicantId(id); + setProcessId(process.processId); + open(); + }; - const filteredApplicants = useMemo( - () => - process.applicants.filter(({ applicantName, isRejected }) => { - const matchesName = applicantName.includes(searchedName); - const matchesRejection = showRejectedApplicant ? isRejected : !isRejected; - return matchesName && matchesRejection; - }), - [searchedName, showRejectedApplicant], - ); + const filteredApplicants = useMemo( + () => + process.applicants.filter(({ applicantName, isRejected }) => { + const matchesName = searchedName ? applicantName.includes(searchedName) : true; + const matchesRejection = showRejectedApplicant ? isRejected : !isRejected; + return matchesName && matchesRejection; + }), + [searchedName, showRejectedApplicant, process.applicants], + ); - return ( - - - {process.name} - - - - {filteredApplicants.map( - ({ applicantId, applicantName, createdAt, evaluationCount, averageScore, isRejected }) => ( - - cardClickHandler(applicantId)} - /> - - ), - )} - - - ); - }, - shouldRerender, -); - -export default ProcessColumn; + return ( + + + {process.name} + + + + {filteredApplicants.map( + ({ applicantId, applicantName, createdAt, evaluationCount, averageScore, isRejected }) => ( + + cardClickHandler(applicantId)} + /> + + ), + )} + + + ); +} diff --git a/frontend/src/components/dashboard/SearchApplicantInput/SearchApplicantInput.stories.tsx b/frontend/src/components/dashboard/SearchApplicantInput/SearchApplicantInput.stories.tsx deleted file mode 100644 index a3675dae5..000000000 --- a/frontend/src/components/dashboard/SearchApplicantInput/SearchApplicantInput.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import type { Meta, StoryObj } from '@storybook/react'; -import SearchApplicantInput from '.'; - -const meta: Meta = { - title: 'Organisms/Dashboard/ApplicantSearchInput', - component: SearchApplicantInput, - parameters: { - layout: 'centered', - docs: { - description: { - component: '', - }, - }, - }, - args: {}, - - tags: ['autodocs'], - decorators: [ - (Child) => ( -
- -
- ), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/frontend/src/components/dashboard/SearchApplicantInput/index.tsx b/frontend/src/components/dashboard/SearchApplicantInput/index.tsx deleted file mode 100644 index 94f358c52..000000000 --- a/frontend/src/components/dashboard/SearchApplicantInput/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useSearchApplicant } from '@contexts/SearchApplicantContext'; -import InputField from '../../_common/molecules/InputField'; - -export default function SearchApplicantInput() { - const { name, handleName } = useSearchApplicant(); - - return ( - handleName(e.target.value)} - /> - ); -} diff --git a/frontend/src/components/dashboard/useSearchApplicant.ts b/frontend/src/components/dashboard/useSearchApplicant.ts new file mode 100644 index 000000000..308e70ce3 --- /dev/null +++ b/frontend/src/components/dashboard/useSearchApplicant.ts @@ -0,0 +1,18 @@ +import useDebounce from '@hooks/utils/useDebounce'; +import { useCallback, useState } from 'react'; + +export const useSearchApplicant = () => { + const [name, setName] = useState(''); + + const handleName = useCallback((newName: string) => { + setName(newName); + }, []); + + const debouncedName = useDebounce(name, 300); + + return { + name, + debouncedName, + handleName, + }; +}; diff --git a/frontend/src/contexts/SearchApplicantContext.tsx b/frontend/src/contexts/SearchApplicantContext.tsx deleted file mode 100644 index 2a5d5babf..000000000 --- a/frontend/src/contexts/SearchApplicantContext.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import useDebounce from '@hooks/utils/useDebounce'; -import { createContext, useCallback, useContext, useMemo, useState } from 'react'; - -interface SearchApplicantContextProps { - name: string; - debouncedName: string; - handleName: (name: string) => void; -} - -const SearchApplicantContext = createContext(null); - -export function SearchApplicantContextProvider({ children }: { children: React.ReactNode }) { - const [name, setName] = useState(''); - - const handleName = useCallback((newName: string) => { - setName(newName); - }, []); - - const debouncedName = useDebounce(name, 300); - - const obj = useMemo( - () => ({ - name, - debouncedName, - handleName, - }), - [name, debouncedName, handleName], - ); - - return {children}; -} - -export const useSearchApplicant = () => { - const context = useContext(SearchApplicantContext); - if (!context) { - throw new Error('useSearchApplicant은 SearchApplicantContextProvider 내부에서 관리되어야 합니다.'); - } - return context; -}; diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index e55b246b6..57301a914 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -1,24 +1,24 @@ /* eslint-disable no-trailing-spaces */ import { useParams } from 'react-router-dom'; +import CopyToClipboard from '@components/_common/atoms/CopyToClipboard'; +import OpenInNewTab from '@components/_common/atoms/OpenInNewTab'; import Tab from '@components/_common/molecules/Tab'; -import ProcessBoard from '@components/dashboard/ProcessBoard'; import ApplyManagement from '@components/applyManagement'; -import ProcessManageBoard from '@components/processManagement/ProcessManageBoard'; +import ProcessBoard from '@components/dashboard/ProcessBoard'; +import InputField from '@components/_common/molecules/InputField'; import PostManageBoard from '@components/postManagement/PostManageBoard'; -import OpenInNewTab from '@components/_common/atoms/OpenInNewTab'; -import CopyToClipboard from '@components/_common/atoms/CopyToClipboard'; -// import SearchApplicantInput from '@components/dashboard/SearchApplicantInput'; +import ProcessManageBoard from '@components/processManagement/ProcessManageBoard'; +import { useSearchApplicant } from '@components/dashboard/useSearchApplicant'; import useTab from '@components/_common/molecules/Tab/useTab'; import useProcess from '@hooks/useProcess'; import { DASHBOARD_TAB_MENUS } from '@constants/constants'; -import { SpecificApplicantIdProvider } from '@contexts/SpecificApplicnatIdContext'; -import { SpecificProcessIdProvider } from '@contexts/SpecificProcessIdContext'; import { FloatingEmailFormProvider } from '@contexts/FloatingEmailFormContext'; import { MultiApplicantContextProvider } from '@contexts/MultiApplicantContext'; -import { SearchApplicantContextProvider } from '@contexts/SearchApplicantContext'; +import { SpecificProcessIdProvider } from '@contexts/SpecificProcessIdContext'; +import { SpecificApplicantIdProvider } from '@contexts/SpecificApplicnatIdContext'; import S from './style'; @@ -30,6 +30,10 @@ export default function Dashboard() { const { currentMenu, moveTab } = useTab({ defaultValue: '지원자 관리' }); + // const { debouncedName } = useSearchApplicant(); + // TODO: [10.15-lesser] sub tab이 구현되면 아래 코드를 사용합니다. + const { debouncedName, name, handleName } = useSearchApplicant(); + return ( @@ -62,18 +66,23 @@ export default function Dashboard() { - - {/* */} - - - - - - - + {/* [10.15-lesser] sub tab이 구현되면 아래 코드를 사용합니다. */} + handleName(e.target.value)} + /> + + + + + +