Skip to content

Commit 797f8a2

Browse files
committed
refactor: 팀회의 피드백 반영
1 parent efa0c5a commit 797f8a2

File tree

7 files changed

+85
-62
lines changed

7 files changed

+85
-62
lines changed

src/app/components/card/cardList/MyApplicationListItem.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface ApplicationListItemProps {
3131
id: number;
3232
}
3333

34+
// 지원 상태에 따른 Chip 컴포넌트의 variant를 반환하는 함수
3435
const getStatusVariant = (status: ApplicationStatus) => {
3536
switch (status) {
3637
case applicationStatus.HIRED:
@@ -42,6 +43,7 @@ const getStatusVariant = (status: ApplicationStatus) => {
4243
}
4344
};
4445

46+
// 지원 상태에 따른 한글 라벨을 반환하는 함수
4547
const getStatusLabel = (status: ApplicationStatus) => {
4648
switch (status) {
4749
case applicationStatus.HIRED:
@@ -57,24 +59,31 @@ const getStatusLabel = (status: ApplicationStatus) => {
5759
}
5860
};
5961

62+
// 내 지원 내역 카드 아이템 컴포넌트
6063
const MyApplicationListItem = ({ createdAt, status, resumeId, resumeName, form }: ApplicationListItemProps) => {
64+
// 현재 공고의 모집 상태를 가져옴
6165
const recruitmentStatus = getRecruitmentStatus(new Date(form.recruitmentEndDate));
6266

67+
// 이력서 다운로드 핸들러
6368
const handleResumeDownload = async () => {
6469
try {
70+
// API를 통해 이력서 파일을 다운로드
6571
const response = await axios.get(`/api/resumes/${resumeId}`, {
6672
responseType: "blob",
6773
});
6874

75+
// Blob 객체 생성 및 다운로드 링크 생성
6976
const blob = new Blob([response.data], { type: "application/pdf" });
7077
const url = window.URL.createObjectURL(blob);
7178

79+
// 가상의 링크를 생성하여 다운로드 실행
7280
const link = document.createElement("a");
7381
link.href = url;
7482
link.download = resumeName || `이력서_${resumeId}.pdf`;
7583
document.body.appendChild(link);
7684
link.click();
7785

86+
// 메모리 정리
7887
window.URL.revokeObjectURL(url);
7988
document.body.removeChild(link);
8089

@@ -103,24 +112,24 @@ const MyApplicationListItem = ({ createdAt, status, resumeId, resumeName, form }
103112
</button>
104113
</div>
105114

106-
{/* 중앙 컨텐츠 영역 */}
115+
{/* 중앙 컨텐츠 영역: 가게 정보, 제목, 설명 */}
107116
<div className="flex-1 space-y-3 py-4">
108-
{/* 가게 정보 영역 */}
117+
{/* 가게 프로필 이미지와 이름 */}
109118
<div className="flex items-center gap-2">
110119
<div className="relative h-10 w-10 overflow-hidden rounded-full md:h-14 md:w-14">
111120
<Image src={form.owner.imageUrl} alt={form.owner.storeName} fill className="object-cover" />
112121
</div>
113122
<span className="text-base font-medium text-gray-900 md:text-lg">{form.owner.storeName}</span>
114123
</div>
115124

116-
{/* 제목 */}
125+
{/* 공고 제목 */}
117126
<div className="text-lg font-bold text-gray-900 md:text-xl">{form.title}</div>
118127

119-
{/* 설명 */}
128+
{/* 공고 설명 (2줄 제한) */}
120129
<p className="line-clamp-2 text-sm text-gray-600 md:line-clamp-2 md:text-base">{form.description}</p>
121130
</div>
122131

123-
{/* 하단 상태 영역 */}
132+
{/* 하단 상태 표시 영역: 지원 상태와 모집 상태 */}
124133
<div className="flex gap-2">
125134
<div className="rounded-[4px] border border-primary-orange-300 md:text-base">
126135
<Chip label={getStatusLabel(status)} variant={getStatusVariant(status)} />

src/app/components/card/cardList/RecruitIcon.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { formatRecruitDate, getWorkDaysDisplay } from "@/utils/workDayFormatter";
22
import RecruitIconItem from "./RecruitIconItem";
33

4+
// 채용 공고 아이콘 컴포넌트의 Props 인터페이스
45
interface RecruitIconProps {
56
hourlyWage: number;
67
recruitmentStartDate: Date;
@@ -11,6 +12,7 @@ interface RecruitIconProps {
1112
workEndTime: string;
1213
}
1314

15+
// 채용 공고의 근무 조건을 아이콘으로 표시하는 컴포넌트
1416
export const RecruitIcon = ({
1517
hourlyWage,
1618
recruitmentStartDate,
@@ -20,11 +22,14 @@ export const RecruitIcon = ({
2022
workStartTime,
2123
workEndTime,
2224
}: RecruitIconProps) => {
25+
// 모집 기간을 반응형으로 표시하는 컴포넌트
2326
const periodValue = (
2427
<>
28+
{/* 모바일에서 표시되는 기간 형식 */}
2529
<span className="whitespace-normal md:hidden">
2630
{formatRecruitDate(recruitmentStartDate)}~{formatRecruitDate(recruitmentEndDate)}
2731
</span>
32+
{/* 데스크탑에서 표시되는 기간 형식 */}
2833
<span className="hidden whitespace-normal md:inline">
2934
{formatRecruitDate(recruitmentStartDate, true)}~
3035
<br />
@@ -33,6 +38,7 @@ export const RecruitIcon = ({
3338
</>
3439
);
3540

41+
// 근무 조건 데이터 배열
3642
const conditions = [
3743
{
3844
icon: {
@@ -69,7 +75,9 @@ export const RecruitIcon = ({
6975
];
7076

7177
return (
78+
// 반응형 컨테이너
7279
<div className="h-auto w-full sm:h-[156px] sm:w-[327px] sm:p-3 md:h-[336px] md:w-[640px]">
80+
{/* 2x2 그리드 레이아웃 */}
7381
<div className="grid h-full grid-cols-2 gap-2 sm:gap-3">
7482
{conditions.map((condition, index) => (
7583
<RecruitIconItem key={index} icon={condition.icon} label={condition.label} value={condition.value} />

src/app/components/card/cardList/RecruitIconItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface RecruitIconItemProps {
99
value: string | React.ReactNode;
1010
}
1111

12+
// 채용 공고의 근무 조건을 아이콘으로 표시하는 컴포넌트의 아이템 컴포넌트
1213
const RecruitIconItem = ({ icon, label, value }: RecruitIconItemProps) => {
1314
return (
1415
<div className="flex items-center gap-2 overflow-hidden rounded-lg border border-gray-200 p-1 sm:p-3 md:gap-6 md:p-6">

src/app/components/card/cardList/RecruitListItem.tsx

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import Image from "next/image";
22
import { formatRecruitDate } from "@/utils/workDayFormatter";
33
import { getRecruitmentStatus, getRecruitmentDday } from "@/utils/recruitDateFormatter";
4-
import { BsThreeDotsVertical, BsChevronLeft, BsChevronRight, BsDot } from "react-icons/bs";
4+
import { BsThreeDotsVertical } from "react-icons/bs";
55
import Chip from "@/app/components/chip/Chip";
66
import { useState, useEffect, useRef } from "react";
77
import { useRouter } from "next/navigation";
88
import useModalStore from "@/store/modalStore";
9+
import Indicator from "../../pagination/Indicator";
910

1011
/**
11-
* 알바 리스트 아이템 컴포넌트 Props
12+
* 채용 공고 리스트 아이템 컴포넌트 Props
1213
*/
1314
interface RecruitListItemProps {
1415
id: string; // formId를 id로 변경
@@ -23,8 +24,8 @@ interface RecruitListItemProps {
2324
}
2425

2526
/**
26-
* 알바 리스트 아이템 컴포넌트
27-
* 알바 정보를 카드 형태로 표시하며, 이미지 슬라이더와 수정/삭제 기능을 포함
27+
* 채용 공고 리스트 아이템 컴포넌트
28+
* 채용 공고 정보를 카드 형태로 표시하며, 이미지 인디케이터와 수정/삭제 기능을 포함
2829
*/
2930
const RecruitListItem = ({
3031
id,
@@ -87,18 +88,6 @@ const RecruitListItem = ({
8788
});
8889
};
8990

90-
// 이미지 슬라이더 이전 이미지로 이동
91-
const handlePrevImage = (e: React.MouseEvent) => {
92-
e.stopPropagation();
93-
setCurrentImageIndex((prev) => (prev === 0 ? imageUrls.length - 1 : prev - 1));
94-
};
95-
96-
// 이미지 슬라이더 다음 이미지로 이동
97-
const handleNextImage = (e: React.MouseEvent) => {
98-
e.stopPropagation();
99-
setCurrentImageIndex((prev) => (prev === imageUrls.length - 1 ? 0 : prev + 1));
100-
};
101-
10291
return (
10392
<div className="relative h-auto w-full overflow-hidden rounded-xl border border-gray-200 bg-white shadow-md transition-transform duration-300 hover:scale-[1.02] sm:h-[390px] sm:w-[327px] md:h-[536px] md:w-[477px]">
10493
{/* 이미지 슬라이더 영역 */}
@@ -113,39 +102,16 @@ const RecruitListItem = ({
113102
/>
114103
)}
115104

116-
{/* 슬라이더 네비게이션 버튼 */}
105+
{/* 이미지 인디케이터 */}
117106
{imageUrls.length > 1 && (
118-
<>
119-
<button
120-
onClick={handlePrevImage}
121-
className="bg-black/30 hover:bg-black/50 absolute left-2 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-white transition-opacity md:h-10 md:w-10"
122-
>
123-
<BsChevronLeft className="h-3 w-3 md:h-5 md:w-5" />
124-
</button>
125-
<button
126-
onClick={handleNextImage}
127-
className="bg-black/30 hover:bg-black/50 absolute right-2 top-1/2 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full text-white transition-opacity md:h-10 md:w-10"
128-
>
129-
<BsChevronRight className="h-3 w-3 md:h-5 md:w-5" />
130-
</button>
131-
</>
132-
)}
133-
134-
{/* 이미지 인디케이터 (하단 도트) */}
135-
<div className="absolute bottom-2 left-1/2 flex -translate-x-1/2 gap-2">
136-
{imageUrls.map((_, index) => (
137-
<button
138-
key={index}
139-
onClick={(e) => {
140-
e.stopPropagation();
141-
setCurrentImageIndex(index);
142-
}}
143-
className={`h-2 w-2 rounded-full transition-all ${
144-
currentImageIndex === index ? "bg-white" : "bg-white/50"
145-
}`}
107+
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
108+
<Indicator
109+
imageCount={imageUrls.length}
110+
currentPage={currentImageIndex}
111+
onPageChange={setCurrentImageIndex}
146112
/>
147-
))}
148-
</div>
113+
</div>
114+
)}
149115
</div>
150116

151117
{/* 콘텐츠 영역 */}

src/app/components/pagination/Indicator.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"use client";
22

3-
import { useState } from "react";
43
import { GoDotFill } from "react-icons/go";
54

6-
const Indicator = ({ imageCount }: { imageCount: number }) => {
7-
// 상세폼 이미지 캐러셀 하단 페이지네이션 컴포넌트
8-
const [currentPage, setCurrentPage] = useState<number>(1);
5+
interface IndicatorProps {
6+
imageCount: number;
7+
currentPage: number;
8+
onPageChange: (page: number) => void;
9+
}
10+
11+
const Indicator = ({ imageCount, currentPage, onPageChange }: IndicatorProps) => {
912
const activeStyle = "size-4 text-gray-300 opacity-60";
1013
const defaultStyle = "size-3 text-gray-50 opacity-60";
1114

@@ -15,7 +18,7 @@ const Indicator = ({ imageCount }: { imageCount: number }) => {
1518
.map((_, index) => (
1619
<GoDotFill
1720
key={index}
18-
onClick={() => setCurrentPage(index)}
21+
onClick={() => onPageChange(index)}
1922
className={currentPage === index ? activeStyle : defaultStyle}
2023
/>
2124
));

src/app/stories/design-system/components/card/cardList/RecruitListItem.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const mockProps = {
3535
"https://images.unsplash.com/photo-1514933651103-005eec06c04b?q=80&w=1974&auto=format&fit=crop",
3636
"https://images.unsplash.com/photo-1574126154517-d1e0d89ef734?q=80&w=1974&auto=format&fit=crop",
3737
"https://images.unsplash.com/photo-1554118811-1e0d58224f24?q=80&w=2047&auto=format&fit=crop",
38+
"https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?q=80&w=2070&auto=format&fit=crop",
39+
"https://images.unsplash.com/photo-1523242942815-1ba8fc07f592?q=80&w=2070&auto=format&fit=crop",
3840
],
3941
isPublic: true,
4042
recruitmentStartDate: new Date("2024-06-01"),
Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,55 @@
1-
import "react-datepicker/dist/react-datepicker.css";
21
import { Meta, StoryObj } from "@storybook/react";
32
import Indicator from "@/app/components/pagination/Indicator";
3+
import { useState } from "react";
44

55
const meta = {
6-
title: "Design System/Components/Pagination/IndicatorForCarousel",
6+
title: "Design System/Components/Pagination/Indicator",
77
component: Indicator,
88
parameters: {
99
layout: "centered",
1010
},
11+
tags: ["autodocs"],
1112
} satisfies Meta<typeof Indicator>;
1213

1314
export default meta;
1415

1516
type Story = StoryObj<typeof Indicator>;
1617

17-
export const IndicatorForCarousel: Story = {
18-
args: {
19-
imageCount: 3,
18+
// 상호작용 가능한 Indicator를 위한 래퍼 컴포넌트
19+
const IndicatorWrapper = ({ imageCount }: { imageCount: number }) => {
20+
const [currentPage, setCurrentPage] = useState(0);
21+
22+
return <Indicator imageCount={imageCount} currentPage={currentPage} onPageChange={setCurrentPage} />;
23+
};
24+
25+
// 3개 이미지 인디케이터
26+
export const ThreeImages: Story = {
27+
render: () => <IndicatorWrapper imageCount={3} />,
28+
};
29+
30+
// 5개 이미지 인디케이터
31+
export const FiveImages: Story = {
32+
render: () => <IndicatorWrapper imageCount={5} />,
33+
};
34+
35+
// 단일 이미지 인디케이터
36+
export const SingleImage: Story = {
37+
render: () => <IndicatorWrapper imageCount={1} />,
38+
};
39+
40+
// 많은 이미지 인디케이터
41+
export const ManyImages: Story = {
42+
render: () => <IndicatorWrapper imageCount={8} />,
43+
};
44+
45+
// 기본 Props로 렌더링되는 인디케이터
46+
export const Default: Story = {
47+
render: () => <IndicatorWrapper imageCount={3} />,
48+
parameters: {
49+
docs: {
50+
description: {
51+
story: "기본적으로 3개의 인디케이터를 보여주며, 클릭하여 페이지를 변경할 수 있습니다.",
52+
},
53+
},
2054
},
2155
};

0 commit comments

Comments
 (0)