-
Notifications
You must be signed in to change notification settings - Fork 1
✨ feat: 알림 모달창 구현 #23
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
Merged
Merged
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
1c408df
✨ feat: 아르바이트 시간 포멧팅 함수 추가
Yun-Jinwoo 0adf8b8
✨ feat: 현재로부터 얼마 전인지 계산하는 함수 추가
Yun-Jinwoo 7eee992
✨ feat: 알림 모달창 내에서 각 알림을 나타낼 NotificationCard 컴포넌트 구현
Yun-Jinwoo ee0200f
✨ feat: props 및 return type에 타입 명시
Yun-Jinwoo ed74a67
🎨 style: NotificationCard 스타일 추가
Yun-Jinwoo 92d36b8
🖼️ feat: 닫기 아이콘 추가
Yun-Jinwoo a01886a
✨ feat: NotificationModal 컴포넌트 구현
Yun-Jinwoo 0fd2d63
🎨 style: NotificationCard 스타일링 추가
Yun-Jinwoo baf166b
🎨 style: index.css에 그림자 스타일변수 추가 및 스크롤바 숨기기 위한 tailwind-scrollbar-hid…
Yun-Jinwoo e8a93e8
✨ feat: NotificationModal 기능 구현 - 데이터 보여주기
Yun-Jinwoo c3a9679
🔧 chore: gitignore 수정
Yun-Jinwoo 286cede
Merge remote-tracking branch 'origin/develop' into feat/notification-…
Yun-Jinwoo 3d7ede8
✨ feat: 닫기 버튼 모바일에서만 보이도록 수정 및 닫기 버튼 클릭시 이전 페이지로 이동
Yun-Jinwoo 4ee6e35
✨ feat: type 지정 및 type들 따로 분리
Yun-Jinwoo bc0d286
🔥 remove: 분리했던 타입들 다시 해당 파일에 위치
Yun-Jinwoo 187474b
📝 docs: 각 파일에 jsDoc 추가하여 문서화
Yun-Jinwoo 2a09bc2
♻️ refactor: data의 타입을 item 안쪽으로 설정
Yun-Jinwoo 4eae6ee
🐛 fix: 근무가 끝나는 시간이 24시 이상인 경우에 대한 처리 추가
Yun-Jinwoo 20c536a
✨ feat: 날짜 형식이 타임스탬프 형식으로 주어질 경우 추가
Yun-Jinwoo 03a3047
Merge branch 'develop' of https://github.com/codeit-6team/The-julge i…
Yun-Jinwoo c320b43
🎨 style: spacing 관련 수정 (4px -> 1px) 및 버튼 포인터 속성 제거
Yun-Jinwoo b150026
📝 docs: 주석의 양 좀 줄이기
Yun-Jinwoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ node_modules | |
| dist | ||
| dist-ssr | ||
| *.local | ||
| mocks | ||
|
|
||
| # Editor directories and files | ||
| .vscode/* | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
64 changes: 64 additions & 0 deletions
64
src/components/common/NotificationModal/NotificationCard.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
Yun-Jinwoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** 근무 시간 (시간 단위) */ | ||
| 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}) 공고 지원이{' '} | ||
Yun-Jinwoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <span className={formattedStatusClass}>{formattedStatus}</span> | ||
| 되었어요. | ||
| </h2> | ||
| <p className="text-caption/4 text-gray-40">{formattedCreatedAt}</p> | ||
| </div> | ||
| ); | ||
| } | ||
86 changes: 86 additions & 0 deletions
86
src/components/common/NotificationModal/NotificationModal.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"> | ||
Yun-Jinwoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| <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> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Moon-ju-young marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}년 전`; | ||
|
Collaborator
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. ~~년 까지 표기로 만드시다니! 좋습니다!! |
||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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} `; | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.