diff --git a/pages/_app.tsx b/pages/_app.tsx index 2213dae4..3bdb290f 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -6,7 +6,10 @@ import Confirm from "@shared/modal/Confirm"; import Modal from "@shared/modal/Modal"; import "@shared/styles/globals.css"; -if (process.env.NODE_ENV === "development" && process.env.MOCK === "true") { +if ( + process.env.NEXT_PUBLIC_NODE_ENV === "development" && + process.env.NEXT_PUBLIC_MOCK === "true" +) { import("mock"); } @@ -15,9 +18,9 @@ function MyApp({ Component, pageProps }: AppProps) {
+ - ); } diff --git a/pages/api/mySchedule.ts b/pages/api/mySchedule.ts new file mode 100644 index 00000000..4064c89c --- /dev/null +++ b/pages/api/mySchedule.ts @@ -0,0 +1,164 @@ +import axios from "axios"; + +export const getAllSchedule = async ( + tab?: string, + title?: string, + startDate?: Date, + endDate?: Date, +) => { + let tabParam; + + if (tab === "진행 예정") { + tabParam = "proceed"; + } else if (tab === "진행중/완료 일정") { + tabParam = "ongoing"; + } + + return await axios.get(`${process.env.NEXT_PUBLIC_BASE_URL}/getAllSchedule`, { + params: { + tab: tabParam, + title, + startDate, + endDate, + }, + }); +}; + +export const getRecruitSchedule = async ( + tab?: string, + title?: string, + startDate?: Date, + endDate?: Date, +) => { + let tabParam; + + if (tab === "모집 중/예정") { + tabParam = "proceed"; + } else if (tab === "모집 완료") { + tabParam = "ongoing"; + } + + return await axios.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/getRecruitSchedule`, + { + params: { + tab: tabParam, + title, + startDate, + endDate, + }, + }, + ); +}; + +export const getParticipateSchedule = async ( + tab?: string, + title?: string, + startDate?: Date, + endDate?: Date, +) => { + let tabParam = 0; + + if (tab === "승인 대기 중") { + tabParam = 1; + } else if (tab === "승인 완료") { + tabParam = 2; + } else if (tab === "승인 거절") { + tabParam = 3; + } + + return await axios.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/getParticipateSchedule`, + { + params: { + tab: tabParam, + title, + startDate, + endDate, + }, + }, + ); +}; + +export const getScrapSchedule = async ( + title?: string, + startDate?: Date, + endDate?: Date, +) => { + return await axios.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/getScrapSchedule`, + { + params: { + title, + startDate, + endDate, + }, + }, + ); +}; + +export const getTemporarySchedule = async ( + title?: string, + startDate?: Date, + endDate?: Date, +) => { + return await axios.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/getTemporarySchedule`, + { + params: { + title, + startDate, + endDate, + }, + }, + ); +}; + +export const getMainSchedule = async () => { + try { + const response = await axios.all([ + getAllSchedule(), + getRecruitSchedule(), + getParticipateSchedule(), + getTemporarySchedule(), + ]); + + // axios.spread를 사용하여 각 요청의 결과를 개별적으로 처리 + const [ + allSchedule, + recruitSchedule, + participateSchedule, + temporarySchedule, + ] = response; + + // 각 요청의 데이터를 반환합니다. + return { + allSchedule: allSchedule.data, + recruitSchedule: recruitSchedule.data, + participateSchedule: participateSchedule.data, + temporarySchedule: temporarySchedule.data, + }; + } catch (error) { + console.error("API 호출 중 오류 발생:", error); + throw error; // 오류를 호출자에게 전달 + } +}; + +export const getApplicantsList = async (id: number) => { + return await axios.get( + `${process.env.NEXT_PUBLIC_BASE_URL}/getApplicantsList`, + { + params: { + id, + }, + }, + ); +}; + +export const getItemList = async (title?: string) => { + return await axios.get(`${process.env.NEXT_PUBLIC_BASE_URL}/getItemList`, { + params: { + title, + }, + }); +}; diff --git a/pages/schedule/all.tsx b/pages/schedule/all.tsx index 11eb8479..3a8f0eb5 100644 --- a/pages/schedule/all.tsx +++ b/pages/schedule/all.tsx @@ -1,8 +1,13 @@ //모든 일정 페이지 import All from "@schedule/components/All"; +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; const create = () => { - return ; + return ( + + ; + + ); }; export default create; diff --git a/pages/schedule/index.tsx b/pages/schedule/index.tsx index f8cb5030..4ab183eb 100644 --- a/pages/schedule/index.tsx +++ b/pages/schedule/index.tsx @@ -1,9 +1,14 @@ // 내 일정 페이지 import Main from "@schedule/components/Main"; +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; import React from "react"; const index = () => { - return
; + return ( + +
; + + ); }; export default index; diff --git a/pages/schedule/items.tsx b/pages/schedule/items.tsx new file mode 100644 index 00000000..cdd32ceb --- /dev/null +++ b/pages/schedule/items.tsx @@ -0,0 +1,13 @@ +import Items from "@schedule/components/Items"; +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; +import React from "react"; + +const items = () => { + return ( + + ; + + ); +}; + +export default items; diff --git a/pages/schedule/participate.tsx b/pages/schedule/participate.tsx new file mode 100644 index 00000000..055b7fe9 --- /dev/null +++ b/pages/schedule/participate.tsx @@ -0,0 +1,13 @@ +import Participate from "@schedule/components/Participate"; +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; +import React from "react"; + +const participate = () => { + return ( + + ; + + ); +}; + +export default participate; diff --git a/pages/schedule/recruit.tsx b/pages/schedule/recruit.tsx new file mode 100644 index 00000000..7fdaee50 --- /dev/null +++ b/pages/schedule/recruit.tsx @@ -0,0 +1,13 @@ +import Recruit from "@schedule/components/Recruit"; +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; +import React from "react"; + +const recruit = () => { + return ( + + ; + + ); +}; + +export default recruit; diff --git a/pages/schedule/scrap.tsx b/pages/schedule/scrap.tsx new file mode 100644 index 00000000..e013c54a --- /dev/null +++ b/pages/schedule/scrap.tsx @@ -0,0 +1,13 @@ +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; +import Scrap from "@schedule/components/Scrap"; +import React from "react"; + +const participate = () => { + return ( + + ; + + ); +}; + +export default participate; diff --git a/pages/schedule/temporary.tsx b/pages/schedule/temporary.tsx new file mode 100644 index 00000000..1c9aba19 --- /dev/null +++ b/pages/schedule/temporary.tsx @@ -0,0 +1,13 @@ +import ScheduleWrapper from "@schedule/components/ScheduleWrapper"; +import Temporary from "@schedule/components/Temporary"; +import React from "react"; + +const temporary = () => { + return ( + + ; + + ); +}; + +export default temporary; diff --git a/public/assets/schedule/arrow-left.svg b/public/assets/schedule/arrow-left.svg new file mode 100644 index 00000000..4733f61e --- /dev/null +++ b/public/assets/schedule/arrow-left.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/schedule/arrow-right.svg b/public/assets/schedule/arrow-right.svg new file mode 100644 index 00000000..a780e48d --- /dev/null +++ b/public/assets/schedule/arrow-right.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/schedule/card_toggle.svg b/public/assets/schedule/card_toggle.svg new file mode 100644 index 00000000..08f42182 --- /dev/null +++ b/public/assets/schedule/card_toggle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/assets/schedule/filter.svg b/public/assets/schedule/filter.svg new file mode 100644 index 00000000..c2313b87 --- /dev/null +++ b/public/assets/schedule/filter.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/schedule/partying_face.svg b/public/assets/schedule/partying_face.svg new file mode 100644 index 00000000..309f7ace --- /dev/null +++ b/public/assets/schedule/partying_face.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/schedule/pencil.png b/public/assets/schedule/pencil.png new file mode 100644 index 00000000..911f9928 Binary files /dev/null and b/public/assets/schedule/pencil.png differ diff --git a/src/mock/browser.ts b/src/mock/browser.ts index bcd82e48..f429ff42 100644 --- a/src/mock/browser.ts +++ b/src/mock/browser.ts @@ -1,4 +1,5 @@ import { setupWorker } from "msw/browser"; import { handlers } from "./handlers"; +import { myScheduleHandlers } from "./handlers/mySchedule"; -export const worker = setupWorker(...handlers); +export const worker = setupWorker(...handlers, ...myScheduleHandlers); diff --git a/src/mock/data/schedule.ts b/src/mock/data/schedule.ts new file mode 100644 index 00000000..bdc9721b --- /dev/null +++ b/src/mock/data/schedule.ts @@ -0,0 +1,287 @@ +export const allScheduleList = [ + { + id: 1, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "1단풍구경 관악산 등반 하실 분?", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/30", + durationEnd: "2023/11/30", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 9, + participateCapacity: 10, + recruitStart: "2023/11/26", + recruitEnd: "2023/11/28", + }, + { + id: 2, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "2단풍구경 관악산 등반 하실 분? 말이 길어지면 줄임", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/12/01", + durationEnd: "2023/12/02", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 5, + participateCapacity: 10, + recruitStart: "2023/11/30", + recruitEnd: "2023/11/30", + }, + { + id: 3, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "3단풍구경 관악산 등반 하실 분?", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/18", + durationEnd: "2023/11/19", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 6, + participateCapacity: 15, + recruitStart: "2023/11/10", + recruitEnd: "2023/11/17", + }, + { + id: 4, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "4단풍구경 관악산 등반 하실 분?", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/19", + durationEnd: "2023/11/20", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 9, + participateCapacity: 15, + recruitStart: "2023/11/19", + recruitEnd: "2023/11/20", + }, +]; + +export const defaultParticipateList = [ + { + id: 6, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "1단풍구경 관악산 등반 하실 분? 말이 길어지면 줄임", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/15", + durationEnd: "2023/11/17", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 5, + participateCapacity: 10, + recruitStart: "2023/11/10", + recruitEnd: "2023/11/13", + approvalStatus: 2, + }, + { + id: 7, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "2단풍구경 관악산 등반 하실 분?", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/18", + durationEnd: "2023/11/19", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 6, + participateCapacity: 15, + recruitStart: "2023/11/10", + recruitEnd: "2023/11/17", + approvalStatus: 1, + }, + { + id: 8, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "3단풍구경 관악산 등반 하실 분?", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/19", + durationEnd: "2023/11/20", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 9, + participateCapacity: 15, + recruitStart: "2023/11/18", + recruitEnd: "2023/11/19", + approvalStatus: 0, + }, +]; + +export const temporaryCardList = [ + { + id: 10, + theme: "캠핑 레져", + img: "", + title: "", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산 길어지면 짜를거야아아아아이렇게에에에에에에ㅋㅋㅋㅋㅋㅋ으으음", + writer: "명란마요", + status: true, + location: "", + durationStart: "", + durationEnd: "", + createdAt: "2023/10/24", + like: 0, + comment: 0, + marked: 0, + participateNum: 0, + participateCapacity: 0, + recruitStart: "2023/11/18", + recruitEnd: "2023/11/19", + }, + { + id: 11, + theme: "캠핑 레져", + img: "/assets/schedule/sample_img.png", + title: "2단풍구경 관악산 등반 하실 분? 말이 길어지면 줄임", + content: + "관악산으로 토요일 오전 10시에 등산 일정 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 방향으로 하산", + writer: "명란마요", + status: true, + location: "관악구", + durationStart: "2023/11/17", + durationEnd: "2023/11/19", + createdAt: "2023/10/24", + like: 13, + comment: 5, + marked: 7, + participateNum: 5, + participateCapacity: 10, + recruitStart: "2023/11/18", + recruitEnd: "2023/11/19", + }, +]; + +export const applicants = [ + { + id: 1, + list: [ + { + user_id: 2, + nickname: "사용자1", + gender: "여성", + applicationDate: "2023-01-01", + status: true, + info: "상세 정보 1", + }, + { + user_id: 3, + nickname: "사용자2", + gender: "남성", + applicationDate: "2023-01-02", + status: false, + info: "상세 정보 2", + }, + ], + }, + { + id: 2, + list: [ + { + user_id: 4, + nickname: "사용자닉네임", + gender: "남성", + applicationDate: "2021-01-01", + status: false, + info: "상세 정보", + }, + { + user_id: 5, + nickname: "사용자오2", + // gender: "", + applicationDate: "2021-01-01", + status: false, + info: "상세 정보", + }, + { + user_id: 6, + nickname: "사용자오오", + // gender: "", + applicationDate: "2021-01-01", + status: false, + info: "상세 정보", + }, + ], + }, + { + id: 3, + list: [ + { + user_id: 4, + nickname: "사용자닉네임", + gender: "남성", + applicationDate: "2021-01-01", + status: false, + info: "상세 정보", + }, + ], + }, + { + id: 4, + list: [], + }, +]; + +export const defaultItems = [ + { + id: 1, + theme: "문화생활", + title: "도서관 가서 신간 읽기", + place: "연남동", + content: "코엑스 별마당 도서관을 가서 신간을 찾아가보자아아", + }, + { + id: 2, + theme: "문화생활", + title: "도서관 자기", + place: "잠실", + content: "롯데 타워 놀러가자 !", + }, +]; diff --git a/src/mock/handlers.ts b/src/mock/handlers/index.ts similarity index 100% rename from src/mock/handlers.ts rename to src/mock/handlers/index.ts diff --git a/src/mock/handlers/mySchedule.ts b/src/mock/handlers/mySchedule.ts new file mode 100644 index 00000000..65056515 --- /dev/null +++ b/src/mock/handlers/mySchedule.ts @@ -0,0 +1,286 @@ +import { + allScheduleList, + applicants, + defaultItems, + defaultParticipateList, + temporaryCardList, +} from "mock/data/schedule"; +import { http } from "msw"; + +const baseUrl = "http://localhost:3000/api"; + +export const myScheduleHandlers = [ + http.get(`${baseUrl}/getAllSchedule`, ({ request }) => { + const currentDate = new Date(); + const url = new URL(request.url); + const tab = url.searchParams.get("tab"); + const title = url.searchParams.get("title") ?? ""; + const startDate = url.searchParams.get("startDate"); + const endDate = url.searchParams.get("endDate"); + + let start: Date, end: Date; + startDate + ? (start = new Date(startDate)) + : (start = new Date("2019/01/01")); + endDate ? (end = new Date(endDate)) : (end = new Date("2025/01/01")); + + if (tab == "proceed") { + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + new Date(item.durationStart) > currentDate && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end && + item.title.includes(title) + ); + }), + ), + ); + } + + if (tab === "ongoing") { + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + new Date(item.durationStart) <= currentDate && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end && + item.title.includes(title) + ); + }), + ), + ); + } + + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end && + item.title.includes(title) + ); + }), + ), + ); + }), + + http.get(`${baseUrl}/getRecruitSchedule`, ({ request }) => { + const currentDate = new Date(); + const url = new URL(request.url); + const tab = url.searchParams.get("tab"); + const title = url.searchParams.get("title") ?? ""; + const startDate = url.searchParams.get("startDate"); + const endDate = url.searchParams.get("endDate"); + + let start: Date, end: Date; + startDate + ? (start = new Date(startDate)) + : (start = new Date("2019-01-01")); + endDate ? (end = new Date(endDate)) : (end = new Date("2025/01/01")); + + // 모집 중/예정 + if (tab == "proceed") { + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + (new Date(item.recruitStart) > currentDate || + (new Date(item.recruitStart) <= currentDate && + new Date(item.recruitEnd) >= currentDate && + item.participateNum < item.participateCapacity)) && + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + } + + // 모집 완료 + if (tab === "ongoing") { + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + (new Date(item.recruitEnd) < currentDate || + item.participateNum === item.participateCapacity) && + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + } + + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end && + item.title.includes(title) + ); + }), + ), + ); + }), + + http.get(`${baseUrl}/getParticipateSchedule`, ({ request }) => { + const url = new URL(request.url); + const tab = url.searchParams.get("tab"); + const title = url.searchParams.get("title") ?? ""; + const startDate = url.searchParams.get("startDate"); + const endDate = url.searchParams.get("endDate"); + + let start: Date, end: Date; + startDate + ? (start = new Date(startDate)) + : (start = new Date("2019-01-01")); + endDate ? (end = new Date(endDate)) : (end = new Date("2025/01/01")); + + // 승인 대기 중 + if (Number(tab) === 1) { + return new Response( + JSON.stringify( + defaultParticipateList.filter((item) => { + return ( + item.approvalStatus === 0 && + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + } + + // 승인 완료 + if (Number(tab) === 2) { + return new Response( + JSON.stringify( + defaultParticipateList.filter((item) => { + return ( + item.approvalStatus === 1 && + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + } + + // 승인 거절 + if (Number(tab) === 3) { + return new Response( + JSON.stringify( + defaultParticipateList.filter((item) => { + return ( + item.approvalStatus === 2 && + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + } + + return new Response( + JSON.stringify( + defaultParticipateList.filter((item) => { + return ( + item.title.includes(title) && + new Date(item.durationStart) >= start && + new Date(item.durationEnd) <= end + ); + }), + ), + ); + }), + + http.get(`${baseUrl}/getScrapSchedule`, ({ request }) => { + const url = new URL(request.url); + const title = url.searchParams.get("title") ?? ""; + const startDate = url.searchParams.get("startDate"); + const endDate = url.searchParams.get("endDate"); + + let start: Date, end: Date; + startDate + ? (start = new Date(startDate)) + : (start = new Date("2019/01/01")); + endDate ? (end = new Date(endDate)) : (end = new Date("2025/01/01")); + + return new Response( + JSON.stringify( + allScheduleList.filter((item) => { + return ( + new Date(item.durationStart ? item.durationStart : "2019/01/01") >= + start && + new Date(item.durationEnd ? item.durationEnd : "2015/01/01") <= + end && + item.title.includes(title) + ); + }), + ), + ); + }), + + http.get(`${baseUrl}/getTemporarySchedule`, ({ request }) => { + const url = new URL(request.url); + const title = url.searchParams.get("title") ?? ""; + const startDate = url.searchParams.get("startDate"); + const endDate = url.searchParams.get("endDate"); + + let start: Date, end: Date; + startDate + ? (start = new Date(startDate)) + : (start = new Date("2019/01/01")); + endDate ? (end = new Date(endDate)) : (end = new Date("2025/01/01")); + + return new Response( + JSON.stringify( + temporaryCardList.filter((item) => { + return ( + new Date(item.durationStart ? item.durationStart : "2019/01/01") >= + start && + new Date(item.durationEnd ? item.durationEnd : "2015/01/01") <= + end && + item.title.includes(title) + ); + }), + ), + ); + }), + + http.get(`${baseUrl}/getApplicantsList`, ({ request }) => { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + + const matchedApplicant = applicants.find( + (applicant) => applicant.id.toString() === id, + ); + + return new Response( + JSON.stringify(matchedApplicant ? matchedApplicant.list : []), + ); + }), + + http.get(`${baseUrl}/getItemList`, ({ request }) => { + const url = new URL(request.url); + const title = url.searchParams.get("title") ?? ""; + + return new Response( + JSON.stringify( + defaultItems.filter((item) => { + return item.title.includes(title); + }), + ), + ); + }), +]; diff --git a/src/modalContent/RecruitManage.tsx b/src/modalContent/RecruitManage.tsx new file mode 100644 index 00000000..4c6bd8d9 --- /dev/null +++ b/src/modalContent/RecruitManage.tsx @@ -0,0 +1,250 @@ +import { Applicant } from "@schedule/components/ScheduleCard"; +import Image from "next/image"; +import React, { useEffect, useState } from "react"; + +export interface RecruitManageProps { + scheduleTitle: string; + applicants: Applicant[]; + participateCapacity: number; + noCallback: VoidFunction; +} + +const RecruitManage = ({ + scheduleTitle, + applicants: defaultApplicants, + participateCapacity, + noCallback, +}: RecruitManageProps) => { + const [applicants, setApplicants] = useState(defaultApplicants); + const [selectedIds, setSelectedIds] = useState>(new Set()); + const [filter, setFilter] = useState("전체"); + + const handleSelectAll = (e: React.ChangeEvent) => { + if (e.target.checked) { + const allIds = applicants.map((applicant) => applicant.id); + setSelectedIds(new Set(allIds)); + } else { + setSelectedIds(new Set()); + } + }; + + const handleSelect = (id: number, isChecked: boolean) => { + setSelectedIds((prev) => { + const newSet = new Set(prev); + isChecked ? newSet.add(id) : newSet.delete(id); + return newSet; + }); + }; + + const isAllSelected = + applicants.length > 0 && + applicants.every((applicant) => selectedIds.has(applicant.id)); + + const countTrueStatus = defaultApplicants.reduce((count, applicant) => { + return applicant.status === true ? count + 1 : count; + }, 0); + + const handleChange = (e: React.ChangeEvent) => { + setFilter(e.target.value); + }; + + const handleSelectApproval = (selectedList: number[]) => { + // TODO: 서버에 승인, 승인 취소 요청 + + console.log("selectedIds", selectedList); + }; + + useEffect(() => { + let filteredApplicants; + + if (filter === "승인") { + filteredApplicants = defaultApplicants.filter( + (applicant) => applicant.status === true, + ); + } else if (filter === "대기중") { + filteredApplicants = defaultApplicants.filter( + (applicant) => applicant.status === false, + ); + } else { + filteredApplicants = defaultApplicants; + } + + setApplicants(filteredApplicants); + }, [filter]); + + return ( +
+
+ pencil +
+
+
+
+ + 신청자 관리 + +
+ {scheduleTitle} +
+
+ + 모집정원: {participateCapacity}명 / 승인대기: + {defaultApplicants.length - countTrueStatus}명 / 승인완료: + {countTrueStatus}명 + +
+
+
+ pencil + + 신청자 상태 + +
+ +
+ + + +
+
+
+
+
+ +
+ + + + + + + + + + + + + + + {applicants.map((applicant, i) => ( + + + + + + + + + + + ))} + +
+ + No.닉네임성별신청일시상태 + 신청자 정보 +
+ + handleSelect(applicant.id, e.target.checked) + } + /> + {i + 1} +
+ {applicant.nickname} +
+
+ {applicant.gender ?? "-"} + + {applicant.applicationDate} + +
+
+
+
+
+ {applicant.status ? "승인" : "대기 중"} +
+
+
+ {/* TODO: 사용자 프로필로 라우팅 */} + + + {/* TODO: 승인, 승인 취소 로직 */} + +
+
+
+ +
+
+ +
+
+ ); +}; + +export default RecruitManage; diff --git a/src/modalContent/index.tsx b/src/modalContent/index.tsx index 4656b592..769a22e4 100644 --- a/src/modalContent/index.tsx +++ b/src/modalContent/index.tsx @@ -1,5 +1,6 @@ import { ModalContentId } from "@shared/recoil/modal"; import CalendarSelector from "./CalendarSelector"; +import RecruitManage, { RecruitManageProps } from "./RecruitManage"; import ThumbnailSelector, { ThumbnailSelectorProps } from "./ThumbnailSelector"; export interface ModalContentProps { @@ -11,6 +12,8 @@ const ModalContent = (modalProps: ModalContentProps) => { switch (contentId) { case "thumbnailSelector": return ; + case "RecruitManage": + return ; case "calendarSelector_start": return ; case "calendarSelector_end": diff --git a/src/schedule/components/All.tsx b/src/schedule/components/All.tsx index 8aa1a3a3..3fa00c06 100644 --- a/src/schedule/components/All.tsx +++ b/src/schedule/components/All.tsx @@ -1,23 +1,21 @@ // 모든 일정 -import { defaultCardList } from "@schedule/const"; -import React, { useEffect, useState } from "react"; -import AllContent, { CardItemType } from "./AllContent"; -import InputCalender from "./InputCalender"; -import ScheduleHeader from "./ScheduleHeader"; +import { useEffect, useState } from "react"; +import { getAllSchedule } from "@pages/api/mySchedule"; +import ContentFilter from "./ContentFilter"; +import ScheduleContent, { CardItemType, scheduleType } from "./ScheduleContent"; import ScheduleTab from "./ScheduleTab"; type TabType = "전체" | "진행 예정" | "진행중/완료 일정"; -interface DateProps { +export interface DateProps { start: Date | undefined; end: Date | undefined; } const All = () => { const [tab, setTab] = useState("전체"); - const [cardList, setCardList] = useState(defaultCardList); + const [cardList, setCardList] = useState([]); const [title, setTitle] = useState(""); const [date, setDate] = useState({ - // TODO: 초기값을 undefined로 설정하면 warning이 발생 start: undefined, end: undefined, }); @@ -50,53 +48,41 @@ const All = () => { }; const onClickTab = (tab: string) => { - setTab(tab); + setTab(tab as TabType); setShowCalendar({ start: false, end: false, }); + setTitle(""); setDate({ start: undefined, end: undefined }); + + try { + getAllSchedule(tab).then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } }; const onClickSearch = () => { - const searchData = { - title: title, - startDate: date.start, - endDate: date.end ?? new Date(), - }; - - console.log("searchData", searchData); - // TODO: API 요청 추가 ??? 아님 프론트에서 다시 필터 ??? <- 확인해야 함 + getAllSchedule(tab, title, date.start, date.end).then((res) => + setCardList(res.data), + ); }; - // tab으로 필터링 - const filteredInTab = (tab: string, dataList: CardItemType[]) => { - const currentDate = new Date(); - - if (tab === "진행 예정") { - return dataList.filter((item) => { - return new Date(item.durationStart) > currentDate; - }); - } else if (tab === "진행중/완료 일정") { - return dataList.filter((item) => { - return new Date(item.durationStart) <= currentDate; + useEffect(() => { + try { + getAllSchedule(tab).then((res) => { + setCardList(res.data); }); - } else { - return dataList; + } catch (error) { + console.error("API 호출 오류", error); } - }; - - useEffect(() => { - setCardList(filteredInTab(tab, defaultCardList)); - }, [tab]); + }, []); return ( - <> -
- {/* 탭 */} - -
- +
{/* 일정 탭 */}
@@ -109,48 +95,28 @@ const All = () => {
- {/* 일정 */} -
- {/* 필터링 */} -
-
일정 제목
-
- setTitle(e.target.value)} - /> -
-
- 모집 기간 -
- handleCalendarClick("start")} - handleDateChange={handleStartDateChange} - /> -
-
- handleCalendarClick("end")} - handleDateChange={handleEndDateChange} - /> - -
- - +
+ {/* 일정 필터*/} + + {/* 일정 카드*/} + +
- +
); }; diff --git a/src/schedule/components/AllContent.tsx b/src/schedule/components/AllContent.tsx deleted file mode 100644 index be40b25f..00000000 --- a/src/schedule/components/AllContent.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { Dispatch, SetStateAction, useState } from "react"; -import ScheduleCard from "./ScheduleCard"; - -interface AllContentProps { - cardList: CardItemType[]; - setCardList: Dispatch>; -} - -export interface CardItemType { - theme: string; - img?: string; - title: string; - content?: string; - writer: string; - status: boolean; - location: string; - durationStart: string; - durationEnd: string; - createdAt: string; - like: number; - comment: number; - marked: number; -} - -const AllContent = ({ cardList, setCardList }: AllContentProps) => { - const [isMySchedule, setIsMySchedule] = useState(false); - - // TODO: 내가 만든 일정만 필터 로직 추가 - const onClickMySchedule = () => setIsMySchedule((prev) => !prev); - - // TODO: 삭제 요청 추가 - const onClickDelete = (i: number) => { - const updatedCardList = [...cardList]; - updatedCardList.splice(i, 1); - setCardList(updatedCardList); - }; - return ( -
-
-
- 전체 {cardList.length}개 -
-
- -
-
- -
- {cardList.map((card, i) => ( - - ))} -
-
- ); -}; - -export default AllContent; diff --git a/src/schedule/components/CardStatus.tsx b/src/schedule/components/CardStatus.tsx new file mode 100644 index 00000000..d3d99880 --- /dev/null +++ b/src/schedule/components/CardStatus.tsx @@ -0,0 +1,114 @@ +// 일정카드의 상태 뱃지 +import React, { useEffect, useState } from "react"; +import { getDaysDifference } from "@shared/utils"; +import { scheduleType } from "./ScheduleContent"; + +interface CardStatusProps { + id: number; + scheduleType: scheduleType; + durationStart: string; + participateNum?: number; + participateCapacity?: number; + recruitStart?: string; + recruitEnd?: string; + approvalStatus?: number; +} + +// TODO: 임시저장 데이터 관련 값 백에서 어떻게 넘겨주는지 확인 후 prop 수정 +const CardStatus = ({ + id, + scheduleType, + durationStart, + participateNum = 0, + participateCapacity = 10, + recruitStart = "2023-11-17", + recruitEnd = "2023-11-17", + approvalStatus = 0, +}: CardStatusProps) => { + const [status, setStatus] = useState(""); + const [bgColor, setBgColor] = useState(""); + + const scheduleStatus = (inputDate: string) => { + const dateStatus = getDaysDifference(inputDate); + + if (dateStatus < 0) { + setBgColor("bg-amber-300"); + return "진행예정"; + } else if (dateStatus === 0) { + setBgColor("bg-emerald-400"); + return "진행 중"; + } else if (dateStatus > 0) { + setBgColor("bg-red-600"); + return "진행완료"; + } + return ""; + }; + + const recruitStatus = ( + recruitStart: string, + recruitEnd: string, + participateNum: number, + participateCapacity: number, + ) => { + const now = new Date(); + const startDate = new Date(recruitStart); + const endDate = new Date(recruitEnd); + + if (startDate > now) { + setBgColor("bg-amber-300"); + return "모집 예정"; + } else if ( + now >= startDate && + now <= endDate && + participateNum < participateCapacity + ) { + setBgColor("bg-emerald-400"); + return "모집 중"; + } else if (now > endDate || participateNum === participateCapacity) { + setBgColor("bg-red-600"); + return "모집 완료"; + } + return ""; + }; + + const participateStatus = (approvalStatus: number) => { + if (approvalStatus === 0) { + setBgColor("bg-amber-300"); + return "승인 대기 중"; + } else if (approvalStatus === 1) { + setBgColor("bg-emerald-400"); + return "승인"; + } else if (approvalStatus === 2) { + setBgColor("bg-red-600"); + return "승인 거절"; + } + return ""; + }; + + useEffect(() => { + if (scheduleType === "all") { + setStatus(scheduleStatus(durationStart)); + } else if (scheduleType === "recruit") { + setStatus( + recruitStatus( + recruitStart, + recruitEnd, + participateNum, + participateCapacity, + ), + ); + } else if (scheduleType === "participate") { + setStatus(participateStatus(approvalStatus)); + } + }, [id]); + + return ( +
+ {status} +
+ ); +}; + +export default CardStatus; diff --git a/src/schedule/components/ContentFilter.tsx b/src/schedule/components/ContentFilter.tsx new file mode 100644 index 00000000..5f52dd36 --- /dev/null +++ b/src/schedule/components/ContentFilter.tsx @@ -0,0 +1,73 @@ +// 제목, 달력 필터 컴포넌트 +import React, { Dispatch, SetStateAction } from "react"; +import { DateProps } from "./All"; +import InputCalender from "./InputCalender"; + +interface ShowCalendarType { + start: boolean; + end: boolean; +} + +interface ContentFilterProps { + title: string; + setTitle: Dispatch>; + date: DateProps; + setDate: Dispatch>; + showCalendar: ShowCalendarType; + handleCalendarClick: (type: "start" | "end") => void; + handleStartDateChange: (newDate: Date) => void; + handleEndDateChange: (newDate: Date) => void; + onClickSearch: () => void; +} + +const ContentFilter = ({ + title, + setTitle, + date, + setDate, + showCalendar, + handleCalendarClick, + handleStartDateChange, + handleEndDateChange, + onClickSearch, +}: ContentFilterProps) => { + return ( +
+
일정 제목
+
+ setTitle(e.target.value)} + /> +
+
+ 모집 기간 +
+ handleCalendarClick("start")} + handleDateChange={handleStartDateChange} + /> +
-
+ handleCalendarClick("end")} + handleDateChange={handleEndDateChange} + /> + +
+ ); +}; + +export default ContentFilter; diff --git a/src/schedule/components/EmptyContent.tsx b/src/schedule/components/EmptyContent.tsx new file mode 100644 index 00000000..615d2813 --- /dev/null +++ b/src/schedule/components/EmptyContent.tsx @@ -0,0 +1,24 @@ +// 일정 없을 시 보이는 컴포넌트 +import React from "react"; + +interface EmptyContentProps { + title: string; + content: string; + link: string; +} + +const EmptyContent = ({ title, content, link }: EmptyContentProps) => { + return ( +
+ {title} +
+        {content}
+      
+ +
+ ); +}; + +export default EmptyContent; diff --git a/src/schedule/components/InputCalender.tsx b/src/schedule/components/InputCalender.tsx index 60b9ba84..ee9411e0 100644 --- a/src/schedule/components/InputCalender.tsx +++ b/src/schedule/components/InputCalender.tsx @@ -18,20 +18,22 @@ const InputCalender = ({ handleDateChange, }: InputCalenderProps) => { return ( -
- -
- calender +
+ +
+ calender +
{visible && (
diff --git a/src/schedule/components/ItemCard.tsx b/src/schedule/components/ItemCard.tsx new file mode 100644 index 00000000..c931ed9c --- /dev/null +++ b/src/schedule/components/ItemCard.tsx @@ -0,0 +1,51 @@ +import React, { useState } from "react"; + +interface ItemCardProps { + theme: string; + title: string; + place: string; + content: string; +} + +const ItemCard = ({ theme, title, place, content }: ItemCardProps) => { + const [isHovered, setIsHovered] = useState(false); + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {isHovered && ( +
+ + +
+ )} +
+
+
+
+
+
+ {theme} +
+
+ {title} +
+
+ {place} +
+
+
+
+ {content} +
+
+
+
+
+
+ ); +}; + +export default ItemCard; diff --git a/src/schedule/components/ItemContent.tsx b/src/schedule/components/ItemContent.tsx new file mode 100644 index 00000000..c42c7533 --- /dev/null +++ b/src/schedule/components/ItemContent.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import ItemCard from "./ItemCard"; +import { ItemCardType } from "./Items"; + +interface ItemContentProps { + itemList: ItemCardType[]; +} + +const ItemContent = ({ itemList }: ItemContentProps) => { + return ( +
+
+
+ 전체 {itemList.length}개 +
+
+ {/* 카드 */} +
+ {itemList.map((item, i) => ( + + ))} + {/* itemList가 3의 배수가 아닐 때 필요한 만큼 빈 div를 추가합니다. */} + {[...Array(3 - (itemList.length % 3)).keys()].map((i) => ( +
+ ))} +
+
+ ); +}; + +export default ItemContent; diff --git a/src/schedule/components/Items.tsx b/src/schedule/components/Items.tsx new file mode 100644 index 00000000..11eb3460 --- /dev/null +++ b/src/schedule/components/Items.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from "react"; +import { getItemList } from "@pages/api/mySchedule"; +import ItemContent from "./ItemContent"; +import ScheduleTab from "./ScheduleTab"; +import TitleSearch from "./TitleSearch"; + +type TabType = "전체"; + +export interface ItemCardType { + theme: string; + title: string; + place: string; + content: string; +} + +const Items = () => { + const [tab, setTab] = useState("전체"); + const [itemList, setItemList] = useState([]); + const [title, setTitle] = useState(""); + + const onClickTab = (tab: string) => { + setTab(tab as TabType); + + try { + getItemList().then((res) => { + setItemList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }; + + const onClickSearch = () => { + getItemList(title).then((res) => setItemList(res.data)); + }; + + useEffect(() => { + try { + getItemList().then((res) => { + setItemList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + return ( +
+
+ {/* 일정 탭 */} +
+ +
+
+ +
+ + + +
+
+ ); +}; + +export default Items; + +const itemsTabItems: Record<"title", TabType>[] = [{ title: "전체" }]; diff --git a/src/schedule/components/Main.tsx b/src/schedule/components/Main.tsx index 3a32aa40..50e4ed86 100644 --- a/src/schedule/components/Main.tsx +++ b/src/schedule/components/Main.tsx @@ -1,51 +1,24 @@ -import React, { useState } from "react"; -import ScheduleHeader from "./ScheduleHeader"; -import ScheduleTab from "./ScheduleTab"; - -type TabType = - | "진행 예정" - | "진행 중" - | "진행 완료" - | "모집 중" - | "참여 신청" - | "보관"; +// 내 일정 메인 +import React from "react"; +import MyScheduleContent from "./MyScheduleContent"; const Main = () => { - const [tab, setTab] = useState("진행 예정"); - - const onClickTab = (tab: string) => { - setTab(tab); - }; - return (
-
- {/* 탭 */} - -
-
{/* 일정 탭 */} -
- +
+
내 일정
+ +
+ {/* MainContent */} + + +
); }; export default Main; - -const allTabItems: Record<"title", TabType>[] = [ - { title: "진행 예정" }, - { title: "진행 중" }, - { title: "진행 완료" }, - { title: "모집 중" }, - { title: "참여 신청" }, - { title: "보관" }, -]; diff --git a/src/schedule/components/MyScheduleContent.tsx b/src/schedule/components/MyScheduleContent.tsx new file mode 100644 index 00000000..f0f1ce88 --- /dev/null +++ b/src/schedule/components/MyScheduleContent.tsx @@ -0,0 +1,52 @@ +// 내 일정 목록 +import React, { useEffect, useState } from "react"; +import { getMainSchedule } from "@pages/api/mySchedule"; +import ScheduleCard, { cardType } from "./ScheduleCard"; +import { CardItemType, scheduleType } from "./ScheduleContent"; +import TitleCardContainer from "./TitleCardContainer"; + +const MyScheduleContent = () => { + const [onComingCardList, setOnComingCardList] = useState([]); + const [temporaryCardList, setTemporaryCardList] = useState( + [], + ); + const [recruitCardList, setRecruitCardList] = useState([]); + const [participateCardList, setParticipateCardList] = useState< + CardItemType[] + >([]); + + const onClickDelete = (i: number) => { + // TODO: 삭제 요청 추가 + // const updatedCardList = [...temporaryCardList]; + // updatedCardList.splice(i, 1); + // setTemporarayCardList(updatedCardList); + }; + + useEffect(() => { + try { + getMainSchedule().then((res) => { + setOnComingCardList(res.allSchedule); + setRecruitCardList(res.recruitSchedule); + setParticipateCardList(res.participateSchedule); + setTemporaryCardList(res.temporarySchedule); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + return ( +
+ + + + +
+ ); +}; + +export default MyScheduleContent; diff --git a/src/schedule/components/Participate.tsx b/src/schedule/components/Participate.tsx new file mode 100644 index 00000000..f45abf16 --- /dev/null +++ b/src/schedule/components/Participate.tsx @@ -0,0 +1,127 @@ +// 참여 신청 일정 +import React, { useEffect, useState } from "react"; +import { getParticipateSchedule } from "@pages/api/mySchedule"; +import { DateProps } from "./All"; +import ContentFilter from "./ContentFilter"; +import ScheduleContent, { CardItemType, scheduleType } from "./ScheduleContent"; +import ScheduleTab from "./ScheduleTab"; + +type TabType = "전체" | "승인 대기 중" | "승인 완료" | "승인 거절"; + +const Participate = () => { + const [tab, setTab] = useState("전체"); + const [cardList, setCardList] = useState([]); + const [title, setTitle] = useState(""); + const [date, setDate] = useState({ + start: undefined, + end: undefined, + }); + const [showCalendar, setShowCalendar] = useState({ + start: false, + end: false, + }); + + const handleCalendarClick = (type: "start" | "end") => { + setShowCalendar((prev) => ({ + start: type === "start" ? !prev.start : false, + end: type === "end" ? !prev.end : false, + })); + }; + + const handleStartDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + start: newDate, + })); + handleCalendarClick("start"); + }; + + const handleEndDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + end: newDate, + })); + handleCalendarClick("end"); + }; + + const onClickTab = (tab: string) => { + try { + getParticipateSchedule(tab).then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + + setTab(tab as TabType); + setShowCalendar({ + start: false, + end: false, + }); + setTitle(""); + setDate({ start: undefined, end: undefined }); + }; + + const onClickSearch = () => { + getParticipateSchedule(tab, title, date.start, date.end).then((res) => + setCardList(res.data), + ); + }; + + useEffect(() => { + try { + getParticipateSchedule(tab).then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + console.log("cardList1", cardList, tab); + + return ( +
+
+ {/* 일정 탭 */} +
+ +
+
+ +
+ + {/* 일정 카드*/} + +
+
+ ); +}; + +export default Participate; + +const participateTabItems: Record<"title", TabType>[] = [ + { title: "전체" }, + { title: "승인 대기 중" }, + { title: "승인 완료" }, + { title: "승인 거절" }, +]; diff --git a/src/schedule/components/Recruit.tsx b/src/schedule/components/Recruit.tsx new file mode 100644 index 00000000..597c484e --- /dev/null +++ b/src/schedule/components/Recruit.tsx @@ -0,0 +1,120 @@ +// 모집 일정 +import React, { useEffect, useState } from "react"; +import { getRecruitSchedule } from "@pages/api/mySchedule"; +import { DateProps } from "./All"; +import ContentFilter from "./ContentFilter"; +import ScheduleContent, { CardItemType, scheduleType } from "./ScheduleContent"; +import ScheduleTab from "./ScheduleTab"; + +type TabType = "전체" | "모집 중/예정" | "모집 완료"; + +const Recruit = () => { + const [tab, setTab] = useState("전체"); + const [cardList, setCardList] = useState([]); + const [title, setTitle] = useState(""); + const [date, setDate] = useState({ + start: undefined, + end: undefined, + }); + const [showCalendar, setShowCalendar] = useState({ + start: false, + end: false, + }); + + const handleCalendarClick = (type: "start" | "end") => { + setShowCalendar((prev) => ({ + start: type === "start" ? !prev.start : false, + end: type === "end" ? !prev.end : false, + })); + }; + + const handleStartDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + start: newDate, + })); + handleCalendarClick("start"); + }; + + const handleEndDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + end: newDate, + })); + handleCalendarClick("end"); + }; + + const onClickTab = (tab: string) => { + setTab(tab as TabType); + setShowCalendar({ + start: false, + end: false, + }); + setTitle(""); + setDate({ start: undefined, end: undefined }); + + try { + getRecruitSchedule(tab).then((res) => setCardList(res.data)); + } catch (error) { + console.error("API 호출 오류", error); + } + }; + + const onClickSearch = () => { + getRecruitSchedule(tab, title, date.start, date.end).then((res) => + setCardList(res.data), + ); + }; + + useEffect(() => { + try { + getRecruitSchedule(tab).then((res) => setCardList(res.data)); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + return ( +
+
+ {/* 일정 탭 */} +
+ +
+
+ +
+ + {/* 일정 카드*/} + +
+
+ ); +}; + +export default Recruit; + +const recruitTabItems: Record<"title", TabType>[] = [ + { title: "전체" }, + { title: "모집 중/예정" }, + { title: "모집 완료" }, +]; diff --git a/src/schedule/components/ScheduleCard.tsx b/src/schedule/components/ScheduleCard.tsx index 5b0ba1d0..c6fcb9f4 100644 --- a/src/schedule/components/ScheduleCard.tsx +++ b/src/schedule/components/ScheduleCard.tsx @@ -1,27 +1,53 @@ // 일정 카드 컴포넌트 +import { RecruitManageProps } from "modalContent/RecruitManage"; import Image from "next/image"; import { useState } from "react"; +import { getApplicantsList } from "@pages/api/mySchedule"; +import { useModal } from "@shared/hook"; +import CardStatus from "./CardStatus"; +import { scheduleType } from "./ScheduleContent"; +import TemporaryCard from "./TemporaryCard"; + +export type cardType = "complete" | "temporary"; + +export interface Applicant { + id: number; + nickname: string; + gender?: string; + applicationDate: string; + status: boolean; + info: string; +} interface ScheduleCardProps { - idx: number; + cardType: cardType; + scheduleType: scheduleType; + id: number; theme: string; - img?: string; + img: string; title: string; - content?: string; + content: string; writer: string; status: boolean; location: string; durationStart: string; durationEnd: string; createdAt: string; - like: number; - comment: number; - marked: number; + participateNum?: number; + participateCapacity?: number; + recruitStart?: string; + recruitEnd?: string; + like?: number; + comment?: number; + marked?: number; + approvalStatus?: number; onClickDelete: (i: number) => void; } const ScheduleCard = ({ - idx, + id, + cardType, + scheduleType, theme, img, title, @@ -32,56 +58,67 @@ const ScheduleCard = ({ durationStart, durationEnd, createdAt, + participateNum, + participateCapacity, + recruitStart, + recruitEnd, like, comment, marked, + approvalStatus, onClickDelete, }: ScheduleCardProps) => { - const [isDelete, setIsDelete] = useState(false); - const handleDelete = () => { - setIsDelete((prev) => !prev); + const { openModal, closeModal } = useModal(); + const [isDeleteToggle, setIsDeleteToggle] = useState(false); + const handleDeleteToggle = () => { + setIsDeleteToggle((prev) => !prev); }; - return ( + const handleModal = async (id: number) => { + const res = await getApplicantsList(id); + + openModal({ + contentId: "RecruitManage", + scheduleTitle: title, + applicants: res.data, + participateCapacity: participateCapacity ?? 0, + noCallback: () => { + closeModal(); + }, + }); + }; + + return cardType === "complete" ? (
- {isDelete && ( -
-
-
-
- 취소 -
-
-
{ - handleDelete(); - onClickDelete(idx); - }} - > -
- 삭제하기 -
-
-
-
- )} - {/* 사진 및 테마 */}
-
-
- {theme} + {img.length !== 0 ? ( + sample img + ) : ( +
+ 대표 이미지가 없어요
-
+ )} +
+ +
delete icon
- - {img ? ( - sample img - ) : ( -
- 대표 이미지가 없어요 -
- )}
- {/* Content */}
-
{title}
+
{title}
{durationStart}~{durationEnd}
@@ -113,9 +141,32 @@ const ScheduleCard = ({ width={17} height={17} /> - {location} + + {location.length !== 0 ? location : "위치를 입력해주세요."} + +
+ +
+ + 작성일: {createdAt} + + + {scheduleType !== "all" && ( +
+ emoji +
+ {participateNum} + /{participateCapacity} +
+
+ )}
-
작성일: {createdAt}
+
{marked}
- {/* TODO: 일정 만들기로 라우팅 */} -
- 일정 만들기 바로가기 -
+ {/* TODO: 유저 정보 확인 후 내 일정인 경우 일정 수정하기 보이기 */} + {scheduleType === "all" ? ( +
+ 일정 수정하기 +
+ ) : scheduleType === "recruit" ? ( +
+
handleModal(id)} + > + 신청자 관리 +
+ {/* TODO: 일정 수정하기로 라우팅 */} +
+ 수정 +
+
+ ) : null} + {isDeleteToggle && ( +
+
+
+
+ 취소 +
+
+
{ + handleDeleteToggle(); + onClickDelete(id); + }} + > +
+ 삭제하기 +
+
+
+
+ )}
+ ) : ( + // 임시저장 일정 + ); }; diff --git a/src/schedule/components/ScheduleContent.tsx b/src/schedule/components/ScheduleContent.tsx new file mode 100644 index 00000000..3747e2cb --- /dev/null +++ b/src/schedule/components/ScheduleContent.tsx @@ -0,0 +1,110 @@ +// 일정 목록 +import React, { Dispatch, SetStateAction, useState } from "react"; +import ScheduleCard, { cardType } from "./ScheduleCard"; + +export type scheduleType = + | "main" + | "all" + | "recruit" + | "participate" + | "my" + | "temporary" + | "scrap"; + +interface ScheduleContentProps { + scheduleType: scheduleType; + cardList: CardItemType[]; + setCardList: Dispatch>; +} + +export interface CardItemType { + id: number; + theme: string; + img: string; + title: string; + content: string; + writer: string; + location: string; + durationStart: string; + durationEnd: string; + createdAt: string; + participateNum: number; + participateCapacity: number; + recruitStart: string; + recruitEnd: string; + like: number; + comment: number; + marked: number; + status: boolean; + approvalStatus?: number; +} + +const ScheduleContent = ({ + scheduleType, + cardList, + setCardList, +}: ScheduleContentProps) => { + const [isMySchedule, setIsMySchedule] = useState(false); + + // TODO: 내가 만든 일정만 필터 로직 추가 + const onClickMySchedule = () => setIsMySchedule((prev) => !prev); + + // TODO: 삭제 요청 추가 + const onClickDelete = (i: number) => { + // const updatedCardList = [...(cardList ?? [])]; + // updatedCardList.splice(i, 1); + // setCardList(updatedCardList); + }; + + return ( +
+
+
+ 전체 {cardList?.length}개 +
+ {scheduleType === "all" && ( +
+ +
+ )} +
+ + {/* TODO: 무한 스크롤 추가 */} +
+ {cardList?.map((card, i) => ( + + ))} + + {[...Array(3 - (cardList.length % 3)).keys()].map((i) => ( +
+ ))} +
+
+ ); +}; + +export default ScheduleContent; diff --git a/src/schedule/components/ScheduleHeader.tsx b/src/schedule/components/ScheduleHeader.tsx index 5c81b94a..e4edcca3 100644 --- a/src/schedule/components/ScheduleHeader.tsx +++ b/src/schedule/components/ScheduleHeader.tsx @@ -8,9 +8,9 @@ const ScheduleHeader = () => { {tabs.map((tab, i) => (
router.push(tab.path)} @@ -35,16 +35,20 @@ const tabs = [ }, { title: "모집 일정", - path: "/schedule/recruitment", + path: "/schedule/recruit", }, { - title: "참여 일정", - path: "/schedule/participation", + title: "참여 신청", + path: "/schedule/participate", }, { title: "스크랩", path: "/schedule/scrap", }, + { + title: "임시저장", + path: "/schedule/temporary", + }, { title: "나만의 아이템", path: "/schedule/items", diff --git a/src/schedule/components/ScheduleSmallCard.tsx b/src/schedule/components/ScheduleSmallCard.tsx new file mode 100644 index 00000000..7060a034 --- /dev/null +++ b/src/schedule/components/ScheduleSmallCard.tsx @@ -0,0 +1,150 @@ +// 일정 작은 카드 +import Image from "next/image"; +import React, { useState } from "react"; +import { getDaysDifference, stringToDate } from "@shared/utils"; +import cardToggle from "/public/assets/schedule/card_toggle.svg"; + +export type scheduleSmallCardType = "다가오는 일정" | "모집 중" | "참여 신청"; + +interface ScheduleSmallCardProps { + type: scheduleSmallCardType; + idx: number; + title: string; + location: string; + durationStart: string; + durationEnd: string; + participateNum: number; + participateCapacity: number; + onClickDelete: (i: number) => void; +} + +const ScheduleSmallCard = ({ + type, + idx, + title, + location, + durationStart, + durationEnd, + participateNum, + participateCapacity, + onClickDelete, +}: ScheduleSmallCardProps) => { + const [isToggle, setIsToggle] = useState(false); + + const handleClickToggle = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsToggle((prev) => !prev); + }; + + const handleClickShare = (e: React.MouseEvent) => { + e.stopPropagation(); + // TODO: 공유 로직 추가 + }; + + const handleClickUpdate = (e: React.MouseEvent) => { + e.stopPropagation(); + // TODO: 일정 수정으로 라우팅 + }; + + const handleClickSettingChange = (e: React.MouseEvent) => { + e.stopPropagation(); + // TODO: 설정 변경으로 라우팅 + }; + + const handleDelete = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsToggle((prev) => !prev); + onClickDelete(idx); + }; + + const smallCardToggleItems = [ + { + title: "공유", + onClick: handleClickShare, + }, + { + title: "일정 수정", + onClick: handleClickUpdate, + }, + { + title: "설정 변경", + onClick: handleClickSettingChange, + }, + { + title: "삭제하기", + onClick: handleDelete, + }, + ]; + + return ( +
+ {isToggle && ( +
+
+ {smallCardToggleItems.map((item, i) => ( +
+
+ {item.title} +
+
+ ))} +
+
+ )} +
+ location +
+
+ {title} +
+
+ location + {location} +
+
+
+ {stringToDate(durationStart)} ~ {stringToDate(durationEnd)} +
+ + {type === "다가오는 일정" ? ( +
+ D + {getDaysDifference(durationStart) === 0 + ? "-DAY" + : getDaysDifference(durationStart)} +
+ ) : ( +
+ emoji +
+ {participateNum} + /{participateCapacity} +
+
+ )} +
+
+ ); +}; + +export default ScheduleSmallCard; diff --git a/src/schedule/components/ScheduleTab.tsx b/src/schedule/components/ScheduleTab.tsx index 3fc085e2..a38aa037 100644 --- a/src/schedule/components/ScheduleTab.tsx +++ b/src/schedule/components/ScheduleTab.tsx @@ -20,17 +20,17 @@ const ScheduleTab = ({ {tabTitle}
- {tabItems.map((tab, i) => ( + {tabItems.map(({ title }, i) => (
onClickTab(tab.title)} + onClick={() => onClickTab(title)} > - {tab.title} + {title}
))}
diff --git a/src/schedule/components/ScheduleWrapper.tsx b/src/schedule/components/ScheduleWrapper.tsx new file mode 100644 index 00000000..9f7acd8c --- /dev/null +++ b/src/schedule/components/ScheduleWrapper.tsx @@ -0,0 +1,18 @@ +import React, { PropsWithChildren } from "react"; +import ScheduleHeader from "./ScheduleHeader"; + +interface ScheduleWrapperProps extends PropsWithChildren {} + +const ScheduleWrapper = ({ children }: ScheduleWrapperProps) => { + return ( +
+
+ {/* header */} + +
+ {children} +
+ ); +}; + +export default ScheduleWrapper; diff --git a/src/schedule/components/Scrap.tsx b/src/schedule/components/Scrap.tsx new file mode 100644 index 00000000..979ad2a3 --- /dev/null +++ b/src/schedule/components/Scrap.tsx @@ -0,0 +1,123 @@ +// 참여 신청 일정 +import React, { useEffect, useState } from "react"; +import { + getParticipateSchedule, + getScrapSchedule, +} from "@pages/api/mySchedule"; +import { DateProps } from "./All"; +import ContentFilter from "./ContentFilter"; +import ScheduleContent, { CardItemType, scheduleType } from "./ScheduleContent"; +import ScheduleTab from "./ScheduleTab"; + +type TabType = "전체"; + +const Scrap = () => { + const [tab, setTab] = useState("전체"); + const [cardList, setCardList] = useState([]); + const [title, setTitle] = useState(""); + const [date, setDate] = useState({ + start: undefined, + end: undefined, + }); + const [showCalendar, setShowCalendar] = useState({ + start: false, + end: false, + }); + + const handleCalendarClick = (type: "start" | "end") => { + setShowCalendar((prev) => ({ + start: type === "start" ? !prev.start : false, + end: type === "end" ? !prev.end : false, + })); + }; + + const handleStartDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + start: newDate, + })); + handleCalendarClick("start"); + }; + + const handleEndDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + end: newDate, + })); + handleCalendarClick("end"); + }; + + const onClickTab = (tab: string) => { + setTab(tab as TabType); + setShowCalendar({ + start: false, + end: false, + }); + setTitle(""); + setDate({ start: undefined, end: undefined }); + + try { + getScrapSchedule().then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }; + + const onClickSearch = () => { + getScrapSchedule(title, date.start, date.end).then((res) => + setCardList(res.data), + ); + }; + + useEffect(() => { + try { + getScrapSchedule().then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + return ( +
+
+ {/* 일정 탭 */} +
+ +
+
+ +
+ + {/* 일정 카드*/} + +
+
+ ); +}; + +export default Scrap; + +const scrapTabItems: Record<"title", TabType>[] = [{ title: "전체" }]; diff --git a/src/schedule/components/Temporary.tsx b/src/schedule/components/Temporary.tsx new file mode 100644 index 00000000..ca48e29f --- /dev/null +++ b/src/schedule/components/Temporary.tsx @@ -0,0 +1,120 @@ +// import { allSchedule } from "@schedule/const"; +import React, { useEffect, useState } from "react"; +import { getTemporarySchedule } from "@pages/api/mySchedule"; +import { DateProps } from "./All"; +import ContentFilter from "./ContentFilter"; +import ScheduleContent, { CardItemType, scheduleType } from "./ScheduleContent"; +import ScheduleTab from "./ScheduleTab"; + +type TabType = "전체"; + +const Temporary = () => { + const [tab, setTab] = useState("전체"); + const [cardList, setCardList] = useState([]); + const [title, setTitle] = useState(""); + const [date, setDate] = useState({ + start: undefined, + end: undefined, + }); + const [showCalendar, setShowCalendar] = useState({ + start: false, + end: false, + }); + + const handleCalendarClick = (type: "start" | "end") => { + setShowCalendar((prev) => ({ + start: type === "start" ? !prev.start : false, + end: type === "end" ? !prev.end : false, + })); + }; + + const handleStartDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + start: newDate, + })); + handleCalendarClick("start"); + }; + + const handleEndDateChange = (newDate: Date) => { + setDate((prevDate) => ({ + ...prevDate, + end: newDate, + })); + handleCalendarClick("end"); + }; + + const onClickTab = (tab: string) => { + setTab(tab as TabType); + setShowCalendar({ + start: false, + end: false, + }); + setTitle(""); + setDate({ start: undefined, end: undefined }); + + try { + getTemporarySchedule().then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }; + + const onClickSearch = () => { + getTemporarySchedule(title, date.start, date.end).then((res) => + setCardList(res.data), + ); + }; + + useEffect(() => { + try { + getTemporarySchedule().then((res) => { + setCardList(res.data); + }); + } catch (error) { + console.error("API 호출 오류", error); + } + }, []); + + return ( +
+
+ {/* 일정 탭 */} +
+ +
+
+ +
+ + {/* 일정 카드*/} + +
+
+ ); +}; + +export default Temporary; + +const temporaryTabItems: Record<"title", TabType>[] = [{ title: "전체" }]; diff --git a/src/schedule/components/TemporaryCard.tsx b/src/schedule/components/TemporaryCard.tsx new file mode 100644 index 00000000..0fd4489f --- /dev/null +++ b/src/schedule/components/TemporaryCard.tsx @@ -0,0 +1,127 @@ +// 임시저장 카드 +import Image from "next/image"; +import React from "react"; +import { scheduleType } from "./ScheduleContent"; + +interface TemporaryCardProps { + id: number; + scheduleType: scheduleType; + theme: string; + img: string; + title: string; + location: string; + content: string; + durationStart: string; + durationEnd: string; + isDeleteToggle: boolean; + handleDeleteToggle: VoidFunction; + onClickDelete: (i: number) => void; +} + +const TemporaryCard = ({ + id, + scheduleType, + theme, + img, + title, + location, + content, + durationStart, + durationEnd, + isDeleteToggle, + handleDeleteToggle, + onClickDelete, +}: TemporaryCardProps) => { + return ( +
+ {isDeleteToggle && ( +
+
+
+
+ 취소 +
+
+
{ + handleDeleteToggle(); + onClickDelete(id); + }} + > +
+ 삭제하기 +
+
+
+
+ )} + +
+
+
+ {theme} +
+
+ delete icon +
+
+ + {img.length !== 0 ? ( + sample img + ) : ( +
+ 대표 이미지가 없어요 +
+ )} +
+ +
+
+ {title.length !== 0 ? title : "제목을 입력해주세요."} +
+
+ location + + {location.length !== 0 ? location : "위치를 입력해주세요."} + +
+
+ {content.length !== 0 ? content : "내용을 입력해주세요."} +
+
+ {durationStart.length !== 0 ? durationStart : "미정"}~ + {durationEnd.length !== 0 ? durationEnd : "미정"} +
+
+ {/* TODO: 일정 만들기로 라우팅 */} +
+ 일정 만들기 바로가기 +
+
+ ); +}; + +export default TemporaryCard; diff --git a/src/schedule/components/TitleCardContainer.tsx b/src/schedule/components/TitleCardContainer.tsx new file mode 100644 index 00000000..aa118929 --- /dev/null +++ b/src/schedule/components/TitleCardContainer.tsx @@ -0,0 +1,143 @@ +// 제목, 작은 카드 목록 +import Image from "next/image"; +import React, { useEffect, useState } from "react"; +import EmptyContent from "./EmptyContent"; +import ScheduleCard, { cardType } from "./ScheduleCard"; +import { CardItemType, scheduleType } from "./ScheduleContent"; +import ScheduleSmallCard, { scheduleSmallCardType } from "./ScheduleSmallCard"; + +interface TitleCardContainerProps { + title: string; + cardList: CardItemType[]; + cardType?: string; +} + +const TitleCardContainer = ({ + title, + cardList, + cardType, +}: TitleCardContainerProps) => { + // TODO: cardList prop은 필터된 것(다가오는, 모집 중인, 참여하는 카드만) + const [filteredList, setFilteredList] = useState(cardList); + const [current, setCurrent] = useState(4); + const [carouselTransition, setCarouselTransition] = useState(""); + const infiniteCarousel = cardList?.length >= 4; + + useEffect(() => { + if (infiniteCarousel) { + setFilteredList([ + ...cardList.slice(-4), + ...cardList, + ...cardList.slice(0, 4), + ]); + } else { + setFilteredList(cardList); + setCurrent(0); + } + }, [cardList]); + + const handleSlide = (direction: string) => { + if (direction === "prev") setCurrent((current) => current - 1); + else if (direction === "next") setCurrent((current) => current + 1); + setCarouselTransition("transition 300ms ease-in-out"); + }; + + const handleOriginSlide = (index: number) => { + setTimeout(() => { + setCurrent(index); + setCarouselTransition(""); + }, 300); + }; + + useEffect(() => { + if (infiniteCarousel) { + if (current === filteredList.length - 4) handleOriginSlide(4); + else if (current === 0) handleOriginSlide(filteredList.length - 8); + } + }, [handleSlide]); + + // TODO: 삭제 요청 추가 + const onClickDelete = (i: number) => { + const updatedCardList = [...filteredList]; + updatedCardList.splice(i, 1); + setFilteredList(updatedCardList); + }; + + return ( + <> +
+
{title}
+ +
+ + +
+
+ {filteredList.length !== 0 ? ( +
+
+ {filteredList.map((card, i) => + cardType === "temporary" ? ( + + ) : ( + + ), + )} +
+
+ ) : ( + + )} + + ); +}; + +export default TitleCardContainer; diff --git a/src/schedule/components/TitleSearch.tsx b/src/schedule/components/TitleSearch.tsx new file mode 100644 index 00000000..12f4e25b --- /dev/null +++ b/src/schedule/components/TitleSearch.tsx @@ -0,0 +1,41 @@ +import React, { Dispatch, SetStateAction } from "react"; + +interface TitleSearchProps { + itemTitle: string; + itemPlaceholder: string; + title: string; + setTitle: Dispatch>; + onClickSearch: () => void; +} + +const TitleSearch = ({ + itemTitle, + itemPlaceholder, + title, + setTitle, + onClickSearch, +}: TitleSearchProps) => { + return ( +
+
+
{itemTitle}
+
+ setTitle(e.target.value)} + /> +
+ +
+
+ ); +}; + +export default TitleSearch; diff --git a/src/schedule/const/index.ts b/src/schedule/const/index.ts index d58d915c..68ae1d47 100644 --- a/src/schedule/const/index.ts +++ b/src/schedule/const/index.ts @@ -1,20 +1,4 @@ -export const defaultCardList = [ - { - theme: "캠핑 레져", - img: "/assets/schedule/sample_img.png", - title: "단풍구경 관악산 등반 하실 분?", - content: ` 관악산으로 토요일 오전 10시에 등산 일정 - - 연주대 12시 도착 및 12시부터 30분간 사진촬영 예정 / 2시에 서울대 - 방향으로 하산`, - writer: "명란마요", - status: true, - location: "관악구", - durationStart: "2023/11/07", - durationEnd: "2023/1", - createdAt: "2023/10/24", - like: 13, - comment: 5, - marked: 7, - }, -]; +export const allScheduleTab = { + 1: "proceed", + 2: "ongoing", +}; diff --git a/src/shared/recoil/modal.tsx b/src/shared/recoil/modal.tsx index 62be6df7..892a6219 100644 --- a/src/shared/recoil/modal.tsx +++ b/src/shared/recoil/modal.tsx @@ -88,4 +88,5 @@ export const useModalState = () => useRecoilState(modalState); export type ModalContentId = | "thumbnailSelector" | "calendarSelector_start" - | "calendarSelector_end"; + | "calendarSelector_end" + | "RecruitManage"; diff --git a/src/shared/styles/globals.css b/src/shared/styles/globals.css index 22d46a61..a5986f74 100644 --- a/src/shared/styles/globals.css +++ b/src/shared/styles/globals.css @@ -111,6 +111,26 @@ a { @apply overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-track-rounded-full scrollbar-thumb-[#FFA4A475] scrollbar-track-[#E2E2E2]; } +.truncate-text { + @apply whitespace-nowrap overflow-hidden overflow-ellipsis +} + +.truncate-text-wrap { + @apply overflow-hidden overflow-ellipsis; + + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.recruit-table-head { + @apply p-3 text-xs font-medium text-left text-gray-500 +} + +.recruit-table-data { + @apply p-3 text-sm text-gray-900 +} + @font-face { font-family: 'Noto Sans KR'; font-style: normal; @@ -130,4 +150,4 @@ a { font-style: normal; font-weight: 700; src: url('/fonts/NotoSansKR-Bold.ttf'); -} \ No newline at end of file +} diff --git a/src/shared/utils/convert.ts b/src/shared/utils/convert.ts new file mode 100644 index 00000000..37b5b846 --- /dev/null +++ b/src/shared/utils/convert.ts @@ -0,0 +1,19 @@ +export const stringToDate = (inputDate: string) => { + const dateObject = new Date(inputDate); + + const formattedDate = `${String(dateObject.getFullYear()).slice(-2)}.${String( + dateObject.getMonth() + 1, + ).padStart(2, "0")}.${String(dateObject.getDate()).padStart(2, "0")}`; + + return formattedDate; +}; + +export const getDaysDifference = (durationStart: string) => { + const startDate = new Date(durationStart); + const currentDate = new Date(); + + const timeDifference = currentDate.getTime() - startDate.getTime(); + const daysDifference = Math.floor(timeDifference / (1000 * 3600 * 24)); + + return daysDifference; +}; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index de9e64c7..4ad85648 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -2,3 +2,4 @@ export * from "./tag"; export * from "./regex"; export * from "./format"; export * from "./logout"; +export * from "./convert";