-
Notifications
You must be signed in to change notification settings - Fork 1
[REFACTOR] 대시보드 지원자 목록 실시간 동기화 및 필터링 개선 (#140) #161
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
Changes from all commits
a33e4ca
a6a4d11
adde828
5daa432
f5d9243
867aa03
018682c
77bbd03
194492c
112dd12
bb635b2
a46e6f9
ce13043
44d6570
955d738
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import type { DashboardSummary } from '@/pages/admin/Dashboard/types/dashboard'; | ||
|
|
||
| export const fetchDashboardSummary = async (clubId: number): Promise<DashboardSummary> => { | ||
| const response = await fetch(`/api/clubs/${clubId}/dashboard`); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error('대시보드 요약 정보를 불러오는 데 실패했습니다.'); | ||
| } | ||
|
|
||
| return response.json(); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,23 +31,33 @@ export const ApplicantList = ({ filterOption }: Props) => { | |
| ))} | ||
| </ApplicantInfoCategoryList> | ||
| <ApplicantInfoDataList> | ||
| {applicants.map((applicant) => ( | ||
| <ApplicantListItem | ||
| key={applicant.id} | ||
| id={applicant.id} | ||
| name={applicant.name} | ||
| studentId={applicant.studentId} | ||
| department={applicant.department} | ||
| phoneNumber={applicant.phoneNumber} | ||
| email={applicant.email} | ||
| status={applicant.status} | ||
| onClick={handleItemClick} | ||
| /> | ||
| ))} | ||
| {applicants.length > 0 ? ( | ||
| applicants.map((applicant) => ( | ||
| <ApplicantListItem | ||
| key={applicant.id} | ||
| id={applicant.id} | ||
| name={applicant.name} | ||
| studentId={applicant.studentId} | ||
| department={applicant.department} | ||
| phoneNumber={applicant.phoneNumber} | ||
| email={applicant.email} | ||
| status={applicant.status} | ||
| onClick={handleItemClick} | ||
| /> | ||
| )) | ||
| ) : ( | ||
| <EmptyMessage> | ||
| {filterOption === 'ALL' | ||
| ? '아직 지원자가 없습니다.' | ||
| : `${filterOption === 'PENDING' ? '심사중' : filterOption === 'APPROVED' ? '합격' : '불합격'} 지원자가 없습니다.`} | ||
|
Comment on lines
+50
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중첩된 삼항 연산자를 사용하여 빈 목록 메시지를 생성하는 것은 가독성을 저해하고 유지보수를 어렵게 만듭니다. 상태 값에 따른 표시 문자열을 객체로 매핑하여 사용하면 코드가 더 명확해지고 관리하기 쉬워집니다. 예를 들어, 컴포넌트 상단에 다음과 같은 맵을 정의하고 사용할 수 있습니다. const statusLabels = {
PENDING: '심사중',
APPROVED: '합격',
REJECTED: '불합격',
};
// JSX 내부
{
filterOption === 'ALL'
? '아직 지원자가 없습니다.'
: `${statusLabels[filterOption]} 지원자가 없습니다.`
} |
||
| </EmptyMessage> | ||
| )} | ||
| </ApplicantInfoDataList> | ||
| <ButtonWrapper> | ||
| <Button width={'15rem'}>결과 전송하기</Button> | ||
| </ButtonWrapper> | ||
| {applicants.length > 0 && ( | ||
| <ButtonWrapper> | ||
| <Button width={'15rem'}>결과 전송하기</Button> | ||
| </ButtonWrapper> | ||
| )} | ||
| </Container> | ||
| ); | ||
| }; | ||
|
|
@@ -82,6 +92,13 @@ const ButtonWrapper = styled.div({ | |
| width: '100%', | ||
| }); | ||
|
|
||
| const EmptyMessage = styled.div(({ theme }) => ({ | ||
| padding: '4rem', | ||
| textAlign: 'center', | ||
| color: theme.colors.gray500, | ||
| fontSize: '1.4rem', | ||
| })); | ||
|
|
||
| type ApplicateInfoCategory = '이름' | '학번' | '학과' | '전화번호' | '이메일' | '결과'; | ||
| const INFO_CATEGORY: ApplicateInfoCategory[] = [ | ||
| '이름', | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||
| import styled from '@emotion/styled'; | ||||||||||||||||||||
| import { useApplicants } from '@/pages/admin/Dashboard/hooks/useApplicants'; | ||||||||||||||||||||
| import { ApplicantFilterButton } from './ApplicationFilterButton'; | ||||||||||||||||||||
| import type { ApplicationFilterOption } from '@/pages/admin/Dashboard/types/dashboard'; | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -8,29 +9,31 @@ export type Props = { | |||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export const ApplicationStatusFilter = ({ option, onOptionChange }: Props) => { | ||||||||||||||||||||
| const { counts } = useApplicants(1); | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove hardcoded clubId. The hardcoded Pass export type Props = {
+ clubId: number;
option: ApplicationFilterOption;
onOptionChange: (option: ApplicationFilterOption) => void;
};
-export const ApplicationStatusFilter = ({ option, onOptionChange }: Props) => {
- const { counts } = useApplicants(1);
+export const ApplicationStatusFilter = ({ clubId, option, onOptionChange }: Props) => {
+ const { counts } = useApplicants(clubId);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <Wrapper> | ||||||||||||||||||||
| <ApplicantFilterButton | ||||||||||||||||||||
| value={'ALL'} | ||||||||||||||||||||
| label={'전체'} | ||||||||||||||||||||
| label={`전체 (${counts.ALL})`} | ||||||||||||||||||||
| selected={option === 'ALL'} | ||||||||||||||||||||
| onClick={onOptionChange} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| <ApplicantFilterButton | ||||||||||||||||||||
| value={'ACCEPTED'} | ||||||||||||||||||||
| label={'합격'} | ||||||||||||||||||||
| selected={option === 'ACCEPTED'} | ||||||||||||||||||||
| value={'APPROVED'} | ||||||||||||||||||||
| label={`합격 (${counts.APPROVED})`} | ||||||||||||||||||||
| selected={option === 'APPROVED'} | ||||||||||||||||||||
| onClick={onOptionChange} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| <ApplicantFilterButton | ||||||||||||||||||||
| value={'REJECTED'} | ||||||||||||||||||||
| label={'불합격'} | ||||||||||||||||||||
| label={`불합격 (${counts.REJECTED})`} | ||||||||||||||||||||
| selected={option === 'REJECTED'} | ||||||||||||||||||||
| onClick={onOptionChange} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| <ApplicantFilterButton | ||||||||||||||||||||
| value={'PENDING'} | ||||||||||||||||||||
| label={'심사중'} | ||||||||||||||||||||
| label={`심사중 (${counts.PENDING})`} | ||||||||||||||||||||
| selected={option === 'PENDING'} | ||||||||||||||||||||
| onClick={onOptionChange} | ||||||||||||||||||||
| /> | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Improve error handling with HTTP status information.
The generic error message provides no debugging context. Including the HTTP status code and response details would significantly improve error diagnostics and help differentiate between client errors (4xx) and server errors (5xx).
Apply this diff to enhance error reporting:
if (!response.ok) { - throw new Error('대시보드 요약 정보를 불러오는 데 실패했습니다.'); + const errorMessage = `대시보드 요약 정보를 불러오는 데 실패했습니다. (상태 코드: ${response.status})`; + throw new Error(errorMessage); }Alternatively, for more comprehensive error handling:
if (!response.ok) { - throw new Error('대시보드 요약 정보를 불러오는 데 실패했습니다.'); + let errorMessage = `대시보드 요약 정보를 불러오는 데 실패했습니다. (상태 코드: ${response.status})`; + try { + const errorBody = await response.json(); + if (errorBody?.message) { + errorMessage += ` - ${errorBody.message}`; + } + } catch { + // Ignore JSON parsing errors for error responses + } + throw new Error(errorMessage); }📝 Committable suggestion
🤖 Prompt for AI Agents