Skip to content

Commit 69e7f35

Browse files
authored
Merge pull request #161 from kakao-tech-campus-3rd-step3/refactor/applicant-filter-api#140
[REFACTOR] ๋Œ€์‹œ๋ณด๋“œ ์ง€์›์ž ๋ชฉ๋ก ์‹ค์‹œ๊ฐ„ ๋™๊ธฐํ™” ๋ฐ ํ•„ํ„ฐ๋ง ๊ฐœ์„  (#140)
2 parents bbff552 + 955d738 commit 69e7f35

File tree

15 files changed

+218
-131
lines changed

15 files changed

+218
-131
lines changed

โ€Žsrc/mocks/repositories/applicant.tsโ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const applicants: ApplicantData[] = [
99
department: '์†Œํ”„ํŠธ์›จ์–ด๊ณตํ•™๊ณผ',
1010
phoneNumber: '010-1010-1010',
1111
email: 'ddd@naver.com',
12-
status: '๋ฏธ์ •',
12+
status: 'PENDING',
1313
},
1414
{
1515
id: 2,
@@ -18,7 +18,7 @@ const applicants: ApplicantData[] = [
1818
department: '์†Œํ”„ํŠธ์›จ์–ด๊ณตํ•™๊ณผ',
1919
phoneNumber: '010-1010-1010',
2020
email: 'ddd@naver.com',
21-
status: 'ํ•ฉ๊ฒฉ',
21+
status: 'APPROVED',
2222
},
2323
{
2424
id: 3,
@@ -27,7 +27,7 @@ const applicants: ApplicantData[] = [
2727
department: '์†Œํ”„ํŠธ์›จ์–ด๊ณตํ•™๊ณผ',
2828
phoneNumber: '010-1010-1010',
2929
email: 'ddd@naver.com',
30-
status: '๋ถˆํ•ฉ๊ฒฉ',
30+
status: 'REJECTED',
3131
},
3232
{
3333
id: 4,
@@ -36,7 +36,7 @@ const applicants: ApplicantData[] = [
3636
department: '์†Œํ”„ํŠธ์›จ์–ด๊ณตํ•™๊ณผ',
3737
phoneNumber: '010-1010-1010',
3838
email: 'ddd@naver.com',
39-
status: '๋ฏธ์ •',
39+
status: 'PENDING',
4040
},
4141
];
4242

โ€Žsrc/pages/admin/ApplicationDetail/components/ApplicantProfileSection/ApplicantStatusButton.tsxโ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import styled from '@emotion/styled';
2-
import type { ApplicantData, ApplicantStatus } from '@/pages/admin/Dashboard/types/dashboard';
2+
import type { StatusLabel, ApplicationStatus } from '@/pages/admin/Dashboard/types/dashboard';
33

44
type Props = {
5-
label: ApplicantData['status'];
6-
value: ApplicantStatus;
5+
label: StatusLabel;
6+
value: ApplicationStatus;
77
selected: boolean;
8-
onClick: (status: ApplicantStatus) => void;
8+
onClick: (status: ApplicationStatus) => void;
99
};
1010

1111
export const ApplicantStatusButton = ({ label, value, selected, onClick }: Props) => {

โ€Žsrc/pages/admin/ApplicationDetail/components/ApplicantProfileSection/ApplicantStatusToggle.tsxโ€Ž

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import styled from '@emotion/styled';
22
import { useState } from 'react';
33
import { ApplicantStatusButton } from './ApplicantStatusButton';
4-
import type { ApplicantStatus } from '@/pages/admin/Dashboard/types/dashboard';
4+
import type { ApplicationStatus } from '@/pages/admin/Dashboard/types/dashboard';
55

66
type Props = {
7-
status?: ApplicantStatus;
8-
updateStatus: (status: ApplicantStatus) => void;
7+
status?: ApplicationStatus;
8+
updateStatus: (status: ApplicationStatus) => void;
99
};
1010

1111
export const ApplicantStatusToggle = ({ status, updateStatus }: Props) => {
1212
const [statusOption, setStatusOption] = useState(status);
1313

14-
const handleClick = (newStatus: ApplicantStatus) => {
14+
const handleClick = (newStatus: ApplicationStatus) => {
1515
setStatusOption(newStatus);
1616
updateStatus(newStatus);
1717
};
@@ -20,8 +20,8 @@ export const ApplicantStatusToggle = ({ status, updateStatus }: Props) => {
2020
<Container>
2121
<ApplicantStatusButton
2222
label={'ํ•ฉ๊ฒฉ'}
23-
value={'ACCEPTED'}
24-
selected={statusOption === 'ACCEPTED'}
23+
value={'APPROVED'}
24+
selected={statusOption === 'APPROVED'}
2525
onClick={handleClick}
2626
/>
2727
<ApplicantStatusButton

โ€Žsrc/pages/admin/ApplicationDetail/components/ApplicantProfileSection/index.tsxโ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import styled from '@emotion/styled';
22
import { Text } from '@/shared/components/Text';
33
import { ApplicantStarRating } from './ApplicantStarRating';
44
import { ApplicantStatusToggle } from './ApplicantStatusToggle';
5-
import type { ApplicantStatus } from '@/pages/admin/Dashboard/types/dashboard';
5+
import type { ApplicationStatus } from '@/pages/admin/Dashboard/types/dashboard';
66

77
type Props = {
88
name?: string;
99
department?: string;
10-
status?: ApplicantStatus;
10+
status?: ApplicationStatus;
1111
rating?: number;
12-
updateStatus: (status: ApplicantStatus) => void;
12+
updateStatus: (status: ApplicationStatus) => void;
1313
};
1414

1515
export const ApplicantProfileSection = ({

โ€Žsrc/pages/admin/ApplicationDetail/types/detailApplication.tsโ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { ApplicantStatus } from '@/pages/admin/Dashboard/types/dashboard';
1+
import type { ApplicationStatus } from '@/pages/admin/Dashboard/types/dashboard';
22

33
export type DetailApplication = {
44
applicationId: number;
5-
status: ApplicantStatus;
5+
status: ApplicationStatus;
66
rating: number;
77
applicantInfo: {
88
applicantId: number;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { DashboardSummary } from '@/pages/admin/Dashboard/types/dashboard';
2+
3+
export const fetchDashboardSummary = async (clubId: number): Promise<DashboardSummary> => {
4+
const response = await fetch(`/api/clubs/${clubId}/dashboard`);
5+
6+
if (!response.ok) {
7+
throw new Error('๋Œ€์‹œ๋ณด๋“œ ์š”์•ฝ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
8+
}
9+
10+
return response.json();
11+
};

โ€Žsrc/pages/admin/Dashboard/components/ApplicantListSection/ApplicantList.tsxโ€Ž

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,33 @@ export const ApplicantList = ({ filterOption }: Props) => {
3131
))}
3232
</ApplicantInfoCategoryList>
3333
<ApplicantInfoDataList>
34-
{applicants.map((applicant) => (
35-
<ApplicantListItem
36-
key={applicant.id}
37-
id={applicant.id}
38-
name={applicant.name}
39-
studentId={applicant.studentId}
40-
department={applicant.department}
41-
phoneNumber={applicant.phoneNumber}
42-
email={applicant.email}
43-
status={applicant.status}
44-
onClick={handleItemClick}
45-
/>
46-
))}
34+
{applicants.length > 0 ? (
35+
applicants.map((applicant) => (
36+
<ApplicantListItem
37+
key={applicant.id}
38+
id={applicant.id}
39+
name={applicant.name}
40+
studentId={applicant.studentId}
41+
department={applicant.department}
42+
phoneNumber={applicant.phoneNumber}
43+
email={applicant.email}
44+
status={applicant.status}
45+
onClick={handleItemClick}
46+
/>
47+
))
48+
) : (
49+
<EmptyMessage>
50+
{filterOption === 'ALL'
51+
? '์•„์ง ์ง€์›์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.'
52+
: `${filterOption === 'PENDING' ? '์‹ฌ์‚ฌ์ค‘' : filterOption === 'APPROVED' ? 'ํ•ฉ๊ฒฉ' : '๋ถˆํ•ฉ๊ฒฉ'} ์ง€์›์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`}
53+
</EmptyMessage>
54+
)}
4755
</ApplicantInfoDataList>
48-
<ButtonWrapper>
49-
<Button width={'15rem'}>๊ฒฐ๊ณผ ์ „์†กํ•˜๊ธฐ</Button>
50-
</ButtonWrapper>
56+
{applicants.length > 0 && (
57+
<ButtonWrapper>
58+
<Button width={'15rem'}>๊ฒฐ๊ณผ ์ „์†กํ•˜๊ธฐ</Button>
59+
</ButtonWrapper>
60+
)}
5161
</Container>
5262
);
5363
};
@@ -82,6 +92,13 @@ const ButtonWrapper = styled.div({
8292
width: '100%',
8393
});
8494

95+
const EmptyMessage = styled.div(({ theme }) => ({
96+
padding: '4rem',
97+
textAlign: 'center',
98+
color: theme.colors.gray500,
99+
fontSize: '1.4rem',
100+
}));
101+
85102
type ApplicateInfoCategory = '์ด๋ฆ„' | 'ํ•™๋ฒˆ' | 'ํ•™๊ณผ' | '์ „ํ™”๋ฒˆํ˜ธ' | '์ด๋ฉ”์ผ' | '๊ฒฐ๊ณผ';
86103
const INFO_CATEGORY: ApplicateInfoCategory[] = [
87104
'์ด๋ฆ„',

โ€Žsrc/pages/admin/Dashboard/components/ApplicantListSection/ApplicantListItem.tsxโ€Ž

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ type Props = ApplicantData & {
55
onClick: (id: number) => void;
66
};
77

8+
const STATUS_LABEL: Record<ApplicantData['status'], string> = {
9+
PENDING: '๋ฏธ์ •',
10+
REJECTED: '๋ถˆํ•ฉ๊ฒฉ',
11+
APPROVED: 'ํ•ฉ๊ฒฉ',
12+
};
13+
814
export const ApplicantListItem = ({
915
id,
1016
name,
@@ -22,7 +28,7 @@ export const ApplicantListItem = ({
2228
<InfoText>{department || '-'}</InfoText>
2329
<InfoText>{phoneNumber || '-'}</InfoText>
2430
<InfoText>{email || '-'}</InfoText>
25-
<StatusBadge status={status}>{status || '-'}</StatusBadge>
31+
<StatusBadge status={status}>{STATUS_LABEL[status] || '-'}</StatusBadge>
2632
</ItemWrapper>
2733
);
2834
};
@@ -47,15 +53,15 @@ const InfoText = styled.p(({ theme }) => ({
4753

4854
const StatusBadge = styled.p<Pick<ApplicantData, 'status'>>(({ theme, status }) => {
4955
const styles = {
50-
ํ•ฉ๊ฒฉ: {
56+
APPROVED: {
5157
backgroundColor: theme.colors.primary100,
5258
color: theme.colors.primary800,
5359
},
54-
๋ถˆํ•ฉ๊ฒฉ: {
60+
REJECTED: {
5561
backgroundColor: theme.colors.red100,
5662
color: theme.colors.red600,
5763
},
58-
๋ฏธ์ •: {
64+
PENDING: {
5965
backgroundColor: theme.colors.gray100,
6066
color: theme.colors.gray600,
6167
},

โ€Žsrc/pages/admin/Dashboard/components/ApplicantListSection/ApplicationFilter.tsxโ€Ž

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import styled from '@emotion/styled';
2+
import { useApplicants } from '@/pages/admin/Dashboard/hooks/useApplicants';
23
import { ApplicantFilterButton } from './ApplicationFilterButton';
34
import type { ApplicationFilterOption } from '@/pages/admin/Dashboard/types/dashboard';
45

@@ -8,29 +9,31 @@ export type Props = {
89
};
910

1011
export const ApplicationStatusFilter = ({ option, onOptionChange }: Props) => {
12+
const { counts } = useApplicants(1);
13+
1114
return (
1215
<Wrapper>
1316
<ApplicantFilterButton
1417
value={'ALL'}
15-
label={'์ „์ฒด'}
18+
label={`์ „์ฒด (${counts.ALL})`}
1619
selected={option === 'ALL'}
1720
onClick={onOptionChange}
1821
/>
1922
<ApplicantFilterButton
20-
value={'ACCEPTED'}
21-
label={'ํ•ฉ๊ฒฉ'}
22-
selected={option === 'ACCEPTED'}
23+
value={'APPROVED'}
24+
label={`ํ•ฉ๊ฒฉ (${counts.APPROVED})`}
25+
selected={option === 'APPROVED'}
2326
onClick={onOptionChange}
2427
/>
2528
<ApplicantFilterButton
2629
value={'REJECTED'}
27-
label={'๋ถˆํ•ฉ๊ฒฉ'}
30+
label={`๋ถˆํ•ฉ๊ฒฉ (${counts.REJECTED})`}
2831
selected={option === 'REJECTED'}
2932
onClick={onOptionChange}
3033
/>
3134
<ApplicantFilterButton
3235
value={'PENDING'}
33-
label={'์‹ฌ์‚ฌ์ค‘'}
36+
label={`์‹ฌ์‚ฌ์ค‘ (${counts.PENDING})`}
3437
selected={option === 'PENDING'}
3538
onClick={onOptionChange}
3639
/>

โ€Žsrc/pages/admin/Dashboard/components/DashboardSummarySection/SummaryCard.tsxโ€Ž

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import styled from '@emotion/styled';
22
import type { DashboardCard } from '@/pages/admin/Dashboard/types/dashboard';
33

4-
type Props = Omit<DashboardCard, 'id'>;
4+
type Props = Omit<DashboardCard, 'id'> & {
5+
isEmpty?: boolean;
6+
};
57

6-
export const SummaryCard = ({ label, value, image }: Props) => {
8+
export const SummaryCard = ({ label, value, image, isEmpty = false }: Props) => {
79
return (
810
<Wrapper>
9-
<IconWrapper>{image}</IconWrapper>
11+
<IconWrapper isEmpty={isEmpty}>{image}</IconWrapper>
1012
<TextWrapper>
1113
<Label>{label}</Label>
12-
<Value>{value}</Value>
14+
<Value isEmpty={isEmpty}>{value}</Value>
15+
{isEmpty && <EmptyText>์˜ˆ์ •๋œ ๋ชจ์ง‘์ด ์—†์Šต๋‹ˆ๋‹ค</EmptyText>}
1316
</TextWrapper>
1417
</Wrapper>
1518
);
@@ -26,8 +29,9 @@ const Wrapper = styled.div(({ theme }) => ({
2629
borderRadius: theme.radius.lg,
2730
}));
2831

29-
const IconWrapper = styled.div(({ theme }) => ({
30-
color: theme.colors.gray900,
32+
const IconWrapper = styled.div<{ isEmpty?: boolean }>(({ theme, isEmpty }) => ({
33+
color: isEmpty ? theme.colors.gray400 : theme.colors.gray900,
34+
transition: 'color 0.2s',
3135
}));
3236

3337
const TextWrapper = styled.div({
@@ -41,8 +45,15 @@ const Label = styled.p(({ theme }) => ({
4145
color: theme.colors.gray900,
4246
}));
4347

44-
const Value = styled.p(({ theme }) => ({
45-
fontSize: '2.2rem',
48+
const Value = styled.p<{ isEmpty?: boolean }>(({ theme, isEmpty }) => ({
49+
fontSize: '2rem',
4650
fontWeight: theme.font.weight.bold,
47-
color: theme.colors.gray900,
51+
color: isEmpty ? theme.colors.gray400 : theme.colors.gray900,
52+
transition: 'color 0.2s',
53+
}));
54+
55+
const EmptyText = styled.span(({ theme }) => ({
56+
fontSize: '1.1rem',
57+
color: theme.colors.gray500,
58+
marginTop: '-0.3rem',
4859
}));

0 commit comments

Comments
ย (0)