Skip to content

Commit

Permalink
feat-fe: 지원자 이름 검색 (#814)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kim Da Eun <[email protected]>
  • Loading branch information
github-actions[bot] and llqqssttyy authored Oct 16, 2024
1 parent 149be66 commit c05f552
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 11 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/dashboard/ApplicantCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HiOutlineClock, HiOutlineChat } from 'react-icons/hi';
import { HiEllipsisVertical } from 'react-icons/hi2';

import { useRef, useEffect, useCallback } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';

import IconButton from '@components/_common/atoms/IconButton';
import PopOverMenu from '@components/_common/molecules/PopOverMenu';
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/components/dashboard/ProcessBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Process } from '@customTypes/process';
import ApplicantModal from '@components/ApplicantModal';
import { Process } from '@customTypes/process';

import ProcessColumn from '../ProcessColumn';
import SideFloatingMessageForm from '../SideFloatingMessageForm';
Expand All @@ -10,9 +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) {
export default function ProcessBoard({
processes,
showRejectedApplicant = false,
searchedName = '',
}: KanbanBoardProps) {
return (
<S.Container>
{/* TODO: isSubTab을 가져와서 SubTab을 렌더링 합니다. */}
Expand All @@ -23,6 +28,7 @@ export default function ProcessBoard({ processes, showRejectedApplicant = false
process={process}
showRejectedApplicant={showRejectedApplicant}
isPassedColumn={!showRejectedApplicant && index === processes.length - 1}
searchedName={searchedName}
/>
))}

Expand Down
27 changes: 22 additions & 5 deletions frontend/src/components/dashboard/ProcessColumn/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DropdownProvider } from '@contexts/DropdownContext';
import { useFloatingEmailForm } from '@contexts/FloatingEmailFormContext';
import { useMultiApplicant } from '@contexts/MultiApplicantContext';

import { useMemo } from 'react';
import ApplicantCard from '../ApplicantCard';
import ProcessDescription from './ProcessDescription';
import S from './style';
Expand All @@ -22,9 +23,15 @@ interface ProcessColumnProps {
process: Process;
showRejectedApplicant: boolean;
isPassedColumn: boolean;
searchedName?: string;
}

export default function ProcessColumn({ process, showRejectedApplicant, isPassedColumn = false }: ProcessColumnProps) {
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({});
Expand Down Expand Up @@ -90,16 +97,25 @@ export default function ProcessColumn({ process, showRejectedApplicant, isPassed
open();
};

const filteredApplicants = useMemo(
() =>
process.applicants.filter(({ applicantName, isRejected }) => {
const matchesName = searchedName ? applicantName.includes(searchedName) : true;
const matchesRejection = showRejectedApplicant === isRejected;
return matchesName && matchesRejection;
}),
[searchedName, showRejectedApplicant, process.applicants],
);

return (
<S.ProcessWrapper isPassedColumn={isPassedColumn}>
<S.Header>
<S.Title>{process.name}</S.Title>
<ProcessDescription description={process.description} />
</S.Header>
<S.ApplicantList>
{process.applicants
.filter(({ isRejected }) => (showRejectedApplicant ? isRejected : !isRejected))
.map(({ applicantId, applicantName, createdAt, evaluationCount, averageScore, isRejected }) => (
{filteredApplicants.map(
({ applicantId, applicantName, createdAt, evaluationCount, averageScore, isRejected }) => (
<DropdownProvider key={applicantId}>
<ApplicantCard
name={applicantName}
Expand All @@ -114,7 +130,8 @@ export default function ProcessColumn({ process, showRejectedApplicant, isPassed
onSelectApplicant={(isChecked: boolean) => applicantSelectHandler(applicantId, isChecked)}
/>
</DropdownProvider>
))}
),
)}
</S.ApplicantList>
</S.ProcessWrapper>
);
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/dashboard/useSearchApplicant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import useDebounce from '@hooks/utils/useDebounce';
import { useCallback, useState } from 'react';

export const useSearchApplicant = () => {
const [name, setName] = useState('');

const updateName = useCallback((newName: string) => {
setName(newName);
}, []);

const debouncedName = useDebounce(name, 300);

return {
name,
debouncedName,
updateName,
};
};
19 changes: 19 additions & 0 deletions frontend/src/hooks/utils/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from 'react';

const useDebounce = (value: string, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => {
clearTimeout(timer);
};
}, [value]);

return debouncedValue;
};

export default useDebounce;
20 changes: 17 additions & 3 deletions frontend/src/pages/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import ProcessManageBoard from '@components/processManagement/ProcessManageBoard
import PostManageBoard from '@components/postManagement/PostManageBoard';
import DashboardHeader from '@components/dashboard/DashboardHeader';

import useTab from '@components/_common/molecules/Tab/useTab';
import useProcess from '@hooks/useProcess';
import useTab from '@components/_common/molecules/Tab/useTab';
import { useSearchApplicant } from '@components/dashboard/useSearchApplicant';

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 { SpecificProcessIdProvider } from '@contexts/SpecificProcessIdContext';
import { SpecificApplicantIdProvider } from '@contexts/SpecificApplicnatIdContext';

import S from './style';

Expand All @@ -27,6 +28,10 @@ export default function Dashboard() {

const { currentMenu, moveTab } = useTab<DashboardTabItems>({ defaultValue: '지원자 관리' });

const { debouncedName } = useSearchApplicant();
// TODO: [10.15-lesser] sub tab이 구현되면 아래 코드를 사용합니다.
// const { debouncedName, name, updateName } = useSearchApplicant();

return (
<S.AppContainer>
<DashboardHeader
Expand Down Expand Up @@ -54,11 +59,20 @@ export default function Dashboard() {
<FloatingEmailFormProvider>
<MultiApplicantContextProvider>
<Tab.TabPanel isVisible={currentMenu === '지원자 관리'}>
{/* [10.15-lesser] sub tab이 구현되면 아래 코드를 사용합니다. */}
{/* <InputField
type="search"
placeholder="지원자 이름 검색"
value={name}
onChange={(e) => updateName(e.target.value)}
/> */}

<SpecificApplicantIdProvider>
<SpecificProcessIdProvider>
<ProcessBoard
isSubTab
processes={processes}
searchedName={debouncedName}
/>
</SpecificProcessIdProvider>
</SpecificApplicantIdProvider>
Expand Down

0 comments on commit c05f552

Please sign in to comment.