Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1c408df
✨ feat: 아르바이트 시간 포멧팅 함수 추가
Yun-Jinwoo Jun 5, 2025
0adf8b8
✨ feat: 현재로부터 얼마 전인지 계산하는 함수 추가
Yun-Jinwoo Jun 5, 2025
7eee992
✨ feat: 알림 모달창 내에서 각 알림을 나타낼 NotificationCard 컴포넌트 구현
Yun-Jinwoo Jun 5, 2025
ee0200f
✨ feat: props 및 return type에 타입 명시
Yun-Jinwoo Jun 5, 2025
ed74a67
🎨 style: NotificationCard 스타일 추가
Yun-Jinwoo Jun 6, 2025
92d36b8
🖼️ feat: 닫기 아이콘 추가
Yun-Jinwoo Jun 6, 2025
a01886a
✨ feat: NotificationModal 컴포넌트 구현
Yun-Jinwoo Jun 7, 2025
0fd2d63
🎨 style: NotificationCard 스타일링 추가
Yun-Jinwoo Jun 7, 2025
baf166b
🎨 style: index.css에 그림자 스타일변수 추가 및 스크롤바 숨기기 위한 tailwind-scrollbar-hid…
Yun-Jinwoo Jun 7, 2025
e8a93e8
✨ feat: NotificationModal 기능 구현 - 데이터 보여주기
Yun-Jinwoo Jun 7, 2025
c3a9679
🔧 chore: gitignore 수정
Yun-Jinwoo Jun 7, 2025
286cede
Merge remote-tracking branch 'origin/develop' into feat/notification-…
Yun-Jinwoo Jun 7, 2025
3d7ede8
✨ feat: 닫기 버튼 모바일에서만 보이도록 수정 및 닫기 버튼 클릭시 이전 페이지로 이동
Yun-Jinwoo Jun 7, 2025
4ee6e35
✨ feat: type 지정 및 type들 따로 분리
Yun-Jinwoo Jun 7, 2025
bc0d286
🔥 remove: 분리했던 타입들 다시 해당 파일에 위치
Yun-Jinwoo Jun 7, 2025
187474b
📝 docs: 각 파일에 jsDoc 추가하여 문서화
Yun-Jinwoo Jun 7, 2025
2a09bc2
♻️ refactor: data의 타입을 item 안쪽으로 설정
Yun-Jinwoo Jun 8, 2025
4eae6ee
🐛 fix: 근무가 끝나는 시간이 24시 이상인 경우에 대한 처리 추가
Yun-Jinwoo Jun 8, 2025
20c536a
✨ feat: 날짜 형식이 타임스탬프 형식으로 주어질 경우 추가
Yun-Jinwoo Jun 8, 2025
03a3047
Merge branch 'develop' of https://github.com/codeit-6team/The-julge i…
Yun-Jinwoo Jun 9, 2025
c320b43
🎨 style: spacing 관련 수정 (4px -> 1px) 및 버튼 포인터 속성 제거
Yun-Jinwoo Jun 9, 2025
b150026
📝 docs: 주석의 양 좀 줄이기
Yun-Jinwoo Jun 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
mocks

# Editor directories and files
.vscode/*
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.6.1",
"tailwind-scrollbar-hide": "^4.0.0",
"tailwindcss": "^4.1.8"
},
"devDependencies": {
Expand Down
Empty file removed src/assets/icons/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions src/assets/icons/ic_close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file removed src/components/common/.gitkeep
Empty file.
64 changes: 64 additions & 0 deletions src/components/common/NotificationModal/NotificationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import calculateTimeDifference from '@/utils/calculateTimeDefference';
import formatWorkTime from '@/utils/formatWorkTime';

/**
* 알림 카드에 필요한 props 정의
*/
interface NotificationCardProps {
/** 공고 지원 상태 ('accepted' | 'rejected') */
status: 'accepted' | 'rejected';

/** 음식점 이름 */
restaurantName: string;

/** 공고 시작 시간 (ISO 8601 문자열) */
startsAt: string;

/** 근무 시간 (시간 단위) */
workHour: number;

/** 알림 생성 시간 (ISO 8601 문자열) */
createdAt: string;
}

/**
* 사용자가 공고에 지원한 결과를 알리는 알림 카드 컴포넌트입니다.
*
* 상태에 따라 '승인' 혹은 '거절'로 표시되며,
* 가게 이름과 알바 시간, 생성 시간을 보여줍니다.
*
* @component
* @param {NotificationCardProps} props - 알림 카드에 전달되는 props
* @returns {JSX.Element} 렌더링된 알림 카드
*/
export default function NotificationCard({
status,
restaurantName,
startsAt,
workHour,
createdAt,
}: NotificationCardProps) {
const formattedTime = formatWorkTime({
startsAt,
workHour,
});
const formattedCreatedAt = calculateTimeDifference(createdAt);
const formattedStatus = status === 'accepted' ? '승인' : '거절';
const formattedStatusClass =
status === 'accepted' ? 'text-blue-20' : 'text-red-20';
return (
<div className="flex flex-col gap-1 md:w-[328px] py-4 px-3 bg-white border border-gray-20 rounded-[5px] ">
{status === 'accepted' ? (
<div className="w-[5px] h-[5px] rounded-full bg-blue-20"></div>
) : (
<div className="w-[5px] h-[5px] rounded-full bg-red-20"></div>
)}
<h2 className="text-body2/[22px] font-regular">
{restaurantName} ({formattedTime}) 공고 지원이{' '}
<span className={formattedStatusClass}>{formattedStatus}</span>
되었어요.
</h2>
<p className="text-caption/4 text-gray-40">{formattedCreatedAt}</p>
</div>
);
}
86 changes: 86 additions & 0 deletions src/components/common/NotificationModal/NotificationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useNavigate } from 'react-router-dom';
import NotificationCard from './NotificationCard';
import close from '@/assets/icons/ic_close.svg';

/**
* 개별 알림 항목을 나타내는 인터페이스입니다.
*/
interface NotificationItem {
/** 생성 일시 (ISO 8601 문자열) */
createdAt: string;

/** 결과 상태 ('accepted' 또는 'rejected') */
result: 'accepted' | 'rejected';

/** 읽음 여부 */
read: boolean;

/** 가게 정보 */
shop: {
item: {
/** 가게 이름 */
name: string;
};
};

/** 공고 정보 */
notice: {
item: {
/** 근무 시작 시간 (ISO 8601 문자열) */
startsAt: string;

/** 근무 시간 (시간 단위) */
workhour: number;
};
};
}


/**
* NotificationModal 컴포넌트에 전달되는 props의 타입입니다.
*/
interface NotificationModalProps {
/** 알림 데이터 배열 */
data: NotificationItem[];

/** 알림 개수 */
count: number;
}

/**
* 알림 모달 컴포넌트입니다.
* 알림 데이터들을 카드로 표시하며, 스크롤 가능합니다.
* PC/Tablet에서는 모달 형식으로, 바깥을 클릭하거나 esc를 통해 창을 닫을 수 있습니다.
* 모바일에서는 닫기 버튼으로 창을 닫을 수 있습니다.
*
* @component
* @param {NotificationModalProps} props - 컴포넌트 props
* @param {NotificationItem[]} props.data - 알림 항목 배열
* @param {number} props.count - 알림 개수
* @returns {JSX.Element} 알림 모달 UI
*/
export default function NotificationModal({ data, count }: NotificationModalProps) {
const navigate = useNavigate();
return (
<div className="flex flex-col px-5 py-10 bg-red-10 gap-4 h-screen md:py-6 md:border-1 md:border-gray-30 md:rounded-[10px] md:shadow-custom md:h-[420px] md:w-[368px]">
<div className="flex justify-between items-center">
<h1 className="text-h3 font-bold">알림 {count}개</h1>
<button onClick={()=> navigate(-1)} className="cursor-pointer md:hidden">
<img src={close} alt="닫기" />
</button>
</div>
<div className="flex flex-col gap-2 overflow-y-auto scrollbar-hide">
{data.map((data, index) => (
<NotificationCard
key={index}
status={data.result}
restaurantName={data.shop.item.name}
startsAt={data.notice.item.startsAt}
workHour={data.notice.item.workhour}
createdAt={data.createdAt}
/>
))}
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import 'tailwindcss';
@import 'tailwind-scrollbar-hide/v4';

@font-face {
font-family: 'SpoqaHanSansNeo-Regular';
Expand Down Expand Up @@ -90,4 +91,5 @@
--text-body1: 16px;
--text-body2: 14px;
--text-caption: 12px;
--shadow-custom: 0px 2px 8px 0px rgba(120, 116, 134, 0.25);
}
Empty file removed src/utils/.gitkeep
Empty file.
31 changes: 31 additions & 0 deletions src/utils/calculateTimeDefference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 주어진 날짜로부터 현재까지의 경과 시간을 사람이 읽기 좋은 문자열로 반환합니다.
*
* @param {string|number} createdAt - 기준이 되는 날짜 및 시간 (ISO 8601 형식 문자열 / timestamp 형식)
* @returns {string} 현재부터 `createdAt`까지의 경과 시간 (예: '방금', '5분 전', '3시간 전', '2일 전', '1달 전', '1년 전')
*/
export default function calculateTimeDifference(createdAt: string | number): string {
const currentDate = new Date();
const createdDate = new Date(createdAt);

const timeDifference = currentDate.getTime() - createdDate.getTime();
const minutes = Math.floor(timeDifference / (1000 * 60));
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const months = Math.floor(days / 30);
const years = Math.floor(months / 12);

if (minutes < 1) {
return '방금';
} else if (minutes <= 59) {
return `${minutes}분 전`;
} else if (hours <= 23) {
return `${hours}시간 전`;
} else if (days <= 30) {
return `${days}일 전`;
} else if (months <= 11) {
return `${months}달 전`;
} else {
return `${years}년 전`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~~년 까지 표기로 만드시다니! 좋습니다!!

}
}
45 changes: 45 additions & 0 deletions src/utils/formatWorkTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* 근무 시간 정보를 담은 객체 타입
*/
interface workTime {
/** 공고 시작 시간 (ISO 8601 형식 문자열) */
startsAt: string | number;

/** 근무 시간 (시간 단위) */
workHour: number;
}

/**
* 주어진 근무 시작 시간과 근무 시간을 기반으로
* KST(한국 표준시) 기준의 근무 시작~종료 시간을
* "YYYY-MM-DD HH:mm~HH:mm" 형식의 문자열로 반환합니다.
*
* @param {workTime} param0 - 근무 시간 정보
* @param {string} param0.startsAt - 공고 시작 시간 (ISO 문자열) (예: "2025-06-07T02:00:00.000Z")
* @param {number} param0.workHour - 근무 시간 (시간 단위) (예: 6)
* @returns {string} 포맷팅된 근무 시간 문자열 (예: "2025-06-07 11:00~17:00")
*/

export default function formatWorkTime({
startsAt,
workHour,
}: workTime): string {
const date = new Date(startsAt);

const kstDate = new Date(date.getTime() + 9 * 60 * 60 * 1000);

/** 시각이 24시를 넘으면 다음날로 설정되어 자동으로 처리 */
const endDate = new Date(kstDate);
endDate.setHours(endDate.getHours() + workHour);

const year = kstDate.getFullYear();
const month = String(kstDate.getMonth() + 1).padStart(2, '0');
const day = String(kstDate.getDate()).padStart(2, '0');
const startHours = String(kstDate.getHours()).padStart(2, '0');
const startMinutes = String(kstDate.getMinutes()).padStart(2, '0');

const endHours = String(endDate.getHours()).padStart(2, '0');
const endMinutes = String(endDate.getMinutes()).padStart(2, '0');

return `${year}-${month}-${day} ${startHours}:${startMinutes}~${endHours}:${endMinutes} `;
}