diff --git a/app/(route)/(bottom-nav)/mypage/_components/EventCalendar.tsx b/app/(route)/(bottom-nav)/mypage/_components/EventCalendar.tsx deleted file mode 100644 index 187fd3ac..00000000 --- a/app/(route)/(bottom-nav)/mypage/_components/EventCalendar.tsx +++ /dev/null @@ -1,51 +0,0 @@ -"use client"; - -import { useState } from "react"; -import Calendar from "react-calendar"; -import "react-calendar/dist/Calendar.css"; -import VerticalEventCard from "@/components/card/VerticalEventCard"; -import { MOCK_EVENTS } from "@/constants/mock"; - -const EventCalendar = () => { - const [date, setDate] = useState(null); - - // 계속 selectedDate의 type은 Date가 될 수 없고 Value여야한다는 오류 발생... 하지만 Value라는 type은 존재하지 않는데.. ㄷㄷ - const handleClickToday = (selectedDate: any) => { - setDate(selectedDate); - }; - - const tileContent = ({ date }: { date: Date }) => { - const eventsForDate = MOCK_EVENTS.filter((event) => new Date(event.startDate).getTime() <= date.getTime() && new Date(event.endDate).getTime() >= date.getTime()); - - // 추후 장소 이름 말고 디자인으로 수정 예정 - if (eventsForDate.length > 0) { - return ( -
- {eventsForDate.map((event, index) => ( -
{event.placeName}
- ))} -
- ); - } - - return null; - }; - - return ( -
- -
-
    - {/* 날짜를 선택하지 않았을 때 모든 Event를 보여줌 */} - {MOCK_EVENTS.filter((event) => !date || (new Date(event.startDate).getTime() <= date.getTime() && new Date(event.endDate).getTime() >= date.getTime())).map( - (event, index) => ( - - ), - )} -
-
-
- ); -}; - -export default EventCalendar; diff --git a/app/(route)/(bottom-nav)/mypage/_components/UserProfile.tsx b/app/(route)/(bottom-nav)/mypage/_components/UserProfile.tsx index 4205ee73..40f6f622 100644 --- a/app/(route)/(bottom-nav)/mypage/_components/UserProfile.tsx +++ b/app/(route)/(bottom-nav)/mypage/_components/UserProfile.tsx @@ -14,12 +14,12 @@ const UserProfile = ({ data }: Props) => { const { bottomSheet, openBottomSheet, closeBottomSheet } = useBottomSheet(); return ( -
-
+
+
이미지 추가 버튼 -
-

{data.nickName}

-

{data.email}

+
+

{data.nickName}

+

{data.email}

-
-
+
+ +
{data.map((cardList) => ( { ))}
- +
); }; diff --git a/app/(route)/(bottom-nav)/mypage/_components/tab/EventTab.tsx b/app/(route)/(bottom-nav)/mypage/_components/tab/EventTab.tsx new file mode 100644 index 00000000..ebd1c346 --- /dev/null +++ b/app/(route)/(bottom-nav)/mypage/_components/tab/EventTab.tsx @@ -0,0 +1,155 @@ +import { useEffect, useState } from "react"; +import Calendar from "react-calendar"; +import "react-calendar/dist/Calendar.css"; +import HorizontalEventCard from "@/components/card/HorizontalEventCard"; +import ChipButton from "@/components/chip/ChipButton"; +import { sortEvents } from "@/utils/sortEventList"; +import { MYPAGE_CALENDAR_STYLE } from "@/constants/calendarStyle"; +import NextIcon from "@/public/icon/arrow-left_lg.svg"; +import PrevIcon from "@/public/icon/arrow-right_lg.svg"; +import { ScheduleDataProps } from "../../page"; + +const EventTab = ({ scheduleData }: { scheduleData: ScheduleDataProps[] }) => { + const [data, setData] = useState(scheduleData); + const [calendarStyle, setCalendarStyle] = useState(""); + + const tileContent = ({ date }: { date: Date }) => { + const eventsForDate = data.filter((event) => new Date(event.startDate).getTime() <= date.getTime() && new Date(event.endDate).getTime() >= date.getTime()); + + if (eventsForDate.length > 0) { + const sortedEvents: ScheduleDataProps[] = sortEvents(eventsForDate); + + return ( +
+ {sortedEvents.map((event) => { + let type; + if (event.startDate === event.endDate) { + type = SHAPE_TYPE.oneDay; + } else if (new Date(event.startDate).getTime() === date.getTime()) { + type = SHAPE_TYPE.firstDay; + } else if (new Date(event.endDate).getTime() === date.getTime()) { + type = SHAPE_TYPE.lastDay; + } else { + type = SHAPE_TYPE.middleDay; + } + return ; + })} +
+ ); + } + + return null; + }; + + useEffect(() => { + setCalendarStyle(MYPAGE_CALENDAR_STYLE); + }, []); + + const [selectedDate, setSelectedDate] = useState(null); + + const handleClickToday = (date: any) => { + if (selectedDate?.getTime() === date.getTime()) { + setSelectedDate(null); + return; + } + setSelectedDate(date); + }; + + const handleHeartClick = () => { + console.log("내 행사 데이터 다시 받아오기"); + }; + + const [currentLabel, setCurrentLabel] = useState(""); + + const handleChipClick = (label: "예정" | "진행중" | "종료") => { + const today = new Date(); + + switch (label) { + case currentLabel: + setData(scheduleData); + setCurrentLabel(""); + break; + case "예정": + setData( + scheduleData.filter((event) => { + return new Date(event.startDate) > today; + }), + ); + setCurrentLabel(label); + break; + case "종료": + setData( + scheduleData.filter((event) => { + return new Date(event.endDate) < today; + }), + ); + setCurrentLabel(label); + break; + case "진행중": + setData( + scheduleData.filter((event) => { + return new Date(event.startDate) <= today && new Date(event.endDate) >= today; + }), + ); + setCurrentLabel(label); + break; + } + }; + + return ( + <> +
+ + {calendarStyle !== "" && ( + } + prevLabel={} + next2Label={null} + prev2Label={null} + formatDay={(locale, date) => date.getDate().toString()} + formatShortWeekday={(locale, date) => { + const shortWeekdays = ["S", "M", "T", "W", "T", "F", "S"]; + return shortWeekdays[date.getDay()]; + }} + /> + )} +
+
+ handleChipClick("예정")} selected={currentLabel === "예정"} /> + handleChipClick("진행중")} selected={currentLabel === "진행중"} /> + handleChipClick("종료")} selected={currentLabel === "종료"} /> +
+
    + {data + .filter((event) => !selectedDate || (new Date(event.startDate).getTime() <= selectedDate.getTime() && new Date(event.endDate).getTime() >= selectedDate.getTime())) + .map((event) => ( + + ))} +
+
+
+ + ); +}; + +export default EventTab; + +const COLOR_TYPE: Record = { + 1: `bg-sub-pink`, + 2: `bg-sub-yellow`, + 3: `bg-sub-skyblue`, + 4: `bg-sub-blue`, + 5: `bg-sub-purple`, + 6: `bg-sub-red`, +}; + +const SHAPE_TYPE = { + oneDay: "w-36", + firstDay: "ml-8 w-44", + lastDay: "mr-8 w-44", + middleDay: "w-52", +}; diff --git a/app/(route)/(bottom-nav)/mypage/_components/tab/MyReviewTab.tsx b/app/(route)/(bottom-nav)/mypage/_components/tab/MyReviewTab.tsx index 68f5db5c..5dd4226b 100644 --- a/app/(route)/(bottom-nav)/mypage/_components/tab/MyReviewTab.tsx +++ b/app/(route)/(bottom-nav)/mypage/_components/tab/MyReviewTab.tsx @@ -3,15 +3,13 @@ import { ReviewType } from "@/types/index"; const MyReviewTab = ({ reviewList }: { reviewList: ReviewType[] }) => { return ( -
-
    - {reviewList.map((data, index) => ( -
  • - -
  • - ))} -
-
+
    + {reviewList.map((data, index) => ( +
  • + +
  • + ))} +
); }; diff --git a/app/(route)/(bottom-nav)/mypage/page.tsx b/app/(route)/(bottom-nav)/mypage/page.tsx index c03b1f32..62553dc2 100644 --- a/app/(route)/(bottom-nav)/mypage/page.tsx +++ b/app/(route)/(bottom-nav)/mypage/page.tsx @@ -1,20 +1,18 @@ "use client"; import { MOCK, MOCK_REVIEWS } from "app/_constants/mock"; -import Header from "@/components/Header"; import Tabs from "@/components/Tabs"; import UserProfile from "./_components/UserProfile"; import ArtistTab from "./_components/tab/ArtistTab"; +import EventTab from "./_components/tab/EventTab"; import MyReviewTab from "./_components/tab/MyReviewTab"; const MyPage = () => { return ( -
-
-
-
+
- + + @@ -40,31 +38,47 @@ const MOCK_USER_INFO = { // } // // 데이터베이스 구조에 따라 사라질 타입이라 해당 파일에 선언 -// const REVIEWS: MyReviewProps[] = [ -// { -// place: "지유가오카핫초메 압구정로데오점", -// public: true, -// rate: true, -// description: "메뉴도 진짜 맛있고 특전도 너무 예뻐요. KTX타고 3시간 걸려서 갔는데 후회 없습니다. 3일차 오후라서 포카 없을까봐 걱정했는데 다행히 수량 넉넉해서 다 받았어요.", -// images: [ -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// ], -// like: 2, -// }, -// { -// place: "앤디스커피 홍대점", -// public: false, -// rate: false, -// description: "사람 너무 많아요 특전 다나갔습니다 가실분들 참고", -// images: [ -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// "https://thumb.mtstarnews.com/06/2023/09/2023090715013844673_1.jpg/dims/optimize", -// ], -// like: 0, -// }, -// ]; +export interface ScheduleDataProps { + id: number; + placeName: string; + artists: string[]; + eventType: "카페"; + address: string; + startDate: string; + endDate: string; + eventImages?: string[]; + tags: ["포토카드"]; +} + +const mockScheduleData: ScheduleDataProps[] = [ + { + id: 1, + placeName: "윤정한 카페", + artists: ["윤정한"], + eventType: "카페", + address: "서울특별시 마포구 와우산로 00-00 1층", + startDate: "2024-01-28T00:00:00+09:00", + endDate: "2024-02-01T00:00:00+09:00", + tags: ["포토카드"], + }, + { + id: 2, + placeName: "김정우 카페", + artists: ["김정우"], + eventType: "카페", + address: "서울특별시 강남구 와우산로 00-00 1층", + startDate: "2024-01-26T00:00:00+09:00", + endDate: "2024-01-26T00:00:00+09:00", + tags: ["포토카드"], + }, + { + id: 3, + placeName: "김민지 카페", + artists: ["김민지"], + eventType: "카페", + address: "서울특별시 강남구 와우산로 00-00 1층", + startDate: "2024-01-26T00:00:00+09:00", + endDate: "2024-01-28T00:00:00+09:00", + tags: ["포토카드"], + }, +]; diff --git a/app/(route)/(header)/signup/_components/SearchArtist.tsx b/app/(route)/(header)/signup/_components/SearchArtist.tsx index 2628828a..fe6151cd 100644 --- a/app/(route)/(header)/signup/_components/SearchArtist.tsx +++ b/app/(route)/(header)/signup/_components/SearchArtist.tsx @@ -1,10 +1,11 @@ -import { ArtistType, MOCK } from "app/_constants/mock"; +import { MOCK } from "app/_constants/mock"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import ArtistCard from "@/components/ArtistCard"; import InputText from "@/components/input/InputText"; import ReqNewArtistModal from "@/components/modal/ReqNewArtistModal"; import { useModal } from "@/hooks/useModal"; +import { ArtistType } from "@/types/index"; interface Props { data: ArtistType[]; diff --git a/app/_components/ArtistCard.tsx b/app/_components/ArtistCard.tsx index d4bb5b97..6550baa2 100644 --- a/app/_components/ArtistCard.tsx +++ b/app/_components/ArtistCard.tsx @@ -10,15 +10,17 @@ interface Props { const ArtistCard = ({ children, onClick, profileImage, isChecked = false, isSmall = false }: Props) => { return ( -
- 아티스트 이미지 -

{children}

+
+
+ 아티스트 이미지 +
+

{children}

); }; diff --git a/app/_components/button/HeartButton.tsx b/app/_components/button/HeartButton.tsx index 7a38f166..aa93056b 100644 --- a/app/_components/button/HeartButton.tsx +++ b/app/_components/button/HeartButton.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useState } from "react"; import Heart from "@/public/icon/heart.svg"; @@ -8,14 +10,21 @@ interface Props { } const HeartButton = ({ isSmall = false, isSelected = false, onClick, ...props }: Props) => { + const [selected, setSelected] = useState(isSelected); + + const handleClick = () => { + setSelected((prev) => !prev); + onClick?.(); + }; + return ( - ); diff --git a/app/_components/card/HorizontalEventCard.tsx b/app/_components/card/HorizontalEventCard.tsx index 8fa3be14..6f76bafe 100644 --- a/app/_components/card/HorizontalEventCard.tsx +++ b/app/_components/card/HorizontalEventCard.tsx @@ -1,25 +1,29 @@ +import { ScheduleDataProps } from "@/(route)/(bottom-nav)/mypage/page"; import Image from "next/image"; import HeartButton from "@/components/button/HeartButton"; import Chip from "@/components/chip/Chip"; import { formatAddress, formatDate } from "@/utils/formatString"; import { EventInfoType } from "@/types/index"; +import NoImage from "@/public/image/no-profile.png"; interface Props { - data: EventInfoType; + data: EventInfoType | ScheduleDataProps; + hasHeart?: boolean; + onHeartClick?: () => void; } -const HorizontalEventCard = ({ data }: Props) => { +const HorizontalEventCard = ({ data, hasHeart = false, onHeartClick }: Props) => { const formattedDate = formatDate(data.startDate, data.endDate); const formattedAddress = formatAddress(data.address); return (
- 행사 포스터 + 행사 포스터
- +

{data.placeName}

@@ -30,7 +34,7 @@ const HorizontalEventCard = ({ data }: Props) => { {formattedDate} {formattedAddress}

-
    {data.tags?.map((tag) => )}
+
    {data.tags?.map((tag, i) => )}
); diff --git a/app/_constants/calendarStyle.ts b/app/_constants/calendarStyle.ts index 54e0a66c..a21d8108 100644 --- a/app/_constants/calendarStyle.ts +++ b/app/_constants/calendarStyle.ts @@ -22,3 +22,112 @@ export const CALENDAR_STYLE = ` font-weight:700; } `; + +export const MYPAGE_CALENDAR_STYLE = ` +.react-calendar { + width: 320px; + padding: 16px 0 ; + font-family: Pretendard; + border: 1px solid #F1F2F4; + border-radius: 12px; +} + +.react-calendar__navigation { + height: 24px +} + +.react-calendar__navigation button { + color: black; + min-width: 16px; + padding: 0 24px; + background: none; + font-weight: 500; + font-size: 16px +} + +.react-calendar__navigation button:enabled:hover, +.react-calendar__navigation button:enabled:focus { + background-color: white; +} + +.react-calendar__month-view__weekdays { + text-align: center; + text-transform: uppercase; + font-size: 0.75em; + font-weight: 500; + padding-bottom: 4px; + abbr { + color: #C1C5CC; + text-decoration: none + } +} + +.react-calendar__tile { + width: 44px; + text-align: center; + min-height: 40px; + padding: 2px 6.6667px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 2px; +} + +.react-calendar__tile > div { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.react-calendar__month-view__days__day { + font-size: 10px; + font-family: Pretendard; + font-weight: 500; +} + +.react-calendar__month-view__days__day--weekend { + color: #F63D3D +} + +.react-calendar__month-view__days__day:not(.react-calendar__month-view__days__day--weekend):not(.react-calendar__month-view__days__day--neighboringMonth) + + .react-calendar__month-view__days__day--weekend { + color: black +} + +.react-calendar__month-view__days__day--neighboringMonth { + color: #D2D5DA; +} + +.react-calendar__tile { + abbr { + width: 36px; + height: 12px; + border-radius: 4px; + } +} + +.react-calendar__tile:enabled:hover, +.react-calendar__tile--now:hover { + background: #F1F2F3; + border-radius: 4px; +} + +.react-calendar__tile:enabled:focus, +.react-calendar__tile--active { + abbr { + background-color: rgba(255, 80, 170, 0.5); + } + background: none; + color: black; +} + +.react-calendar__tile--now { + abbr { + background-color: #F1F2F3 + } + background: none; + color: black; +} +`; diff --git a/app/_constants/mock.ts b/app/_constants/mock.ts index fce39efc..2548c7be 100644 --- a/app/_constants/mock.ts +++ b/app/_constants/mock.ts @@ -1,10 +1,4 @@ -import { EventInfoType } from "../_types"; - -export type ArtistType = { - name: string; - group?: string[]; - profileImage: string; -}; +import { ArtistType, EventInfoType } from "../_types"; export const MOCK: ArtistType[] = [ { diff --git a/app/_types/index.ts b/app/_types/index.ts index 4a8db484..95b3e1cf 100644 --- a/app/_types/index.ts +++ b/app/_types/index.ts @@ -72,3 +72,9 @@ export interface ReviewType { reviewImages?: string[]; like: number; } + +export type ArtistType = { + name: string; + group?: string[]; + profileImage: string; +}; diff --git a/app/_utils/formatString.ts b/app/_utils/formatString.ts index 92d81956..6f14b935 100644 --- a/app/_utils/formatString.ts +++ b/app/_utils/formatString.ts @@ -6,7 +6,7 @@ export const formatDate = (startDate: string, endDate: string, extend: boolean = return `${start[0].slice(2)}.${start[1]}.${start[2]} ~ ${end[0].slice(2)}.${end[1]}.${end[2]}`; } - return `${start[1]}.${start[2]} ~ ${end[1]}.${end[2]}`; + return `${start[1]}.${start[2].split("T")[0]} ~ ${end[1]}.${end[2].split("T")[0]}`; }; export const formatAddress = (address: string) => { diff --git a/app/_utils/sortEventList.ts b/app/_utils/sortEventList.ts new file mode 100644 index 00000000..56f0e095 --- /dev/null +++ b/app/_utils/sortEventList.ts @@ -0,0 +1,19 @@ +import { ScheduleDataProps } from "@/(route)/(bottom-nav)/mypage/page"; + +export const sortEvents = (events: ScheduleDataProps[]) => { + const sortedEvents = events.sort((a, b) => { + const startDateA = new Date(a.startDate).getTime(); + const startDateB = new Date(b.startDate).getTime(); + + if (startDateA === startDateB) { + const endDateA = new Date(a.endDate).getTime(); + const endDateB = new Date(b.endDate).getTime(); + + return endDateB - endDateA; + } + + return startDateA - startDateB; + }); + + return sortedEvents; +}; diff --git a/public/icon/arrow-left_lg.svg b/public/icon/arrow-left_lg.svg index d7ddb06b..401a8aa2 100644 --- a/public/icon/arrow-left_lg.svg +++ b/public/icon/arrow-left_lg.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icon/arrow-right_lg.svg b/public/icon/arrow-right_lg.svg index ca13f466..d796246b 100644 --- a/public/icon/arrow-right_lg.svg +++ b/public/icon/arrow-right_lg.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/image/no-profile.png b/public/image/no-profile.png index f7b4d5f2..d8321464 100644 Binary files a/public/image/no-profile.png and b/public/image/no-profile.png differ