diff --git a/package-lock.json b/package-lock.json index c801035..6f28d4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,11 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@fullcalendar/core": "^6.1.19", + "@fullcalendar/daygrid": "^6.1.19", + "@fullcalendar/list": "^6.1.19", + "@fullcalendar/react": "^6.1.19", + "@fullcalendar/timegrid": "^6.1.19", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", "axios": "^1.11.0", @@ -2040,6 +2045,56 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@fullcalendar/core": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.19.tgz", + "integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==", + "license": "MIT", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.19.tgz", + "integrity": "sha512-IAAfnMICnVWPjpT4zi87i3FEw0xxSza0avqY/HedKEz+l5MTBYvCDPOWDATpzXoLut3aACsjktIyw9thvIcRYQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.19" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.19.tgz", + "integrity": "sha512-knZHpAVF0LbzZpSJSUmLUUzF0XlU/MRGK+Py2s0/mP93bCtno1k2L3XPs/kzh528hSjehwLm89RgKTSfW1P6cA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.19" + } + }, + "node_modules/@fullcalendar/react": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.19.tgz", + "integrity": "sha512-FP78vnyylaL/btZeHig8LQgfHgfwxLaIG6sKbNkzkPkKEACv11UyyBoTSkaavPsHtXvAkcTED1l7TOunAyPEnA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.19", + "react": "^16.7.0 || ^17 || ^18 || ^19", + "react-dom": "^16.7.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.19.tgz", + "integrity": "sha512-OuzpUueyO9wB5OZ8rs7TWIoqvu4v3yEqdDxZ2VcsMldCpYJRiOe7yHWKr4ap5Tb0fs7Rjbserc/b6Nt7ol6BRg==", + "license": "MIT", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.19" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.19" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -7772,6 +7827,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index b027376..cdb8122 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,11 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@fullcalendar/core": "^6.1.19", + "@fullcalendar/daygrid": "^6.1.19", + "@fullcalendar/list": "^6.1.19", + "@fullcalendar/react": "^6.1.19", + "@fullcalendar/timegrid": "^6.1.19", "@tanstack/react-query": "^5.83.0", "@tanstack/react-query-devtools": "^5.83.0", "axios": "^1.11.0", diff --git a/src/app/(afterLogin)/schedule/_components/calendar.tsx b/src/app/(afterLogin)/schedule/_components/calendar.tsx new file mode 100644 index 0000000..621c8bb --- /dev/null +++ b/src/app/(afterLogin)/schedule/_components/calendar.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useState } from "react"; +import { useSchedule } from "@/hooks/useSchedule"; +import FullCalendar from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/daygrid"; +import timeGridPlugin from "@fullcalendar/timegrid"; +import listPlugin from "@fullcalendar/list"; +import LoadingSpinner from "@/app/_components/loadingSpinner"; +import koLocale from "@fullcalendar/core/locales/ko"; + +export default function Calendar() { + function formatDateToApiString(date: Date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = "00"; + const minutes = "00"; + const seconds = "00"; + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + } + + const [dateRange, setDateRange] = useState(() => { + const today = new Date(); + const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0); + return { + start: formatDateToApiString(startOfMonth), + end: formatDateToApiString(endOfMonth), + }; + }); + + const { data: scheduleData, isLoading: scheduleLoading } = useSchedule( + dateRange.start, + dateRange.end + ); + + const events = + scheduleData?.data.content + .filter((schedule: { memo: string }) => schedule.memo === "마감일") + .map((schedule: { title: string; dateTime: string }) => ({ + title: schedule.title, + date: schedule.dateTime, + })) || []; + + const handleDatesSet = (arg: { start: Date; end: Date }) => { + const newStartDate = formatDateToApiString(arg.start); + const newEndDate = formatDateToApiString(arg.end); + + setDateRange({ start: newStartDate, end: newEndDate }); + }; + + return ( + : undefined} + /> + ); +} diff --git a/src/app/(afterLogin)/schedule/_components/sideSection.tsx b/src/app/(afterLogin)/schedule/_components/sideSection.tsx new file mode 100644 index 0000000..810d4be --- /dev/null +++ b/src/app/(afterLogin)/schedule/_components/sideSection.tsx @@ -0,0 +1,69 @@ +"use client"; + +import CalendarCheck from "@/assets/CalendarCheck.svg"; +import { useSchedule } from "@/hooks/useSchedule"; +import { Schedule } from "@/type/schedule"; +import LoadingSpinner from "@/app/_components/loadingSpinner"; + +export default function SideSection() { + function formatDateToApiString(date: Date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const seconds = String(date.getSeconds()).padStart(2, "0"); + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; + } + + const startDate = formatDateToApiString(new Date()); + const endDate = formatDateToApiString( + new Date(new Date().setDate(new Date().getDate() + 7)) + ); + + function getDDay(dateTime: string) { + const today = new Date(); + const targetDate = new Date(dateTime); + + const todayOnly = new Date( + today.getFullYear(), + today.getMonth(), + today.getDate() + ); + const targetOnly = new Date( + targetDate.getFullYear(), + targetDate.getMonth(), + targetDate.getDate() + ); + + const diffTime = targetOnly.getTime() - todayOnly.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays > 0) return `D-${diffDays}`; + else if (diffDays === 0) return `D-Day`; + else return `D+${Math.abs(diffDays)}`; + } + + const { data: scheduleData, isLoading: scheduleLoading } = useSchedule( + startDate, + endDate + ); + + if (scheduleLoading) return ; + + return ( +
+ {scheduleData?.data.content.map((schedule: Schedule) => ( +
+
+ + {schedule.title} + + {getDDay(schedule.dateTime)} + +
+
+ ))} +
+ ); +} diff --git a/src/app/(afterLogin)/schedule/page.tsx b/src/app/(afterLogin)/schedule/page.tsx new file mode 100644 index 0000000..ea56916 --- /dev/null +++ b/src/app/(afterLogin)/schedule/page.tsx @@ -0,0 +1,14 @@ +import Calendar from "./_components/calendar"; +import SideSection from "./_components/sideSection"; + +export default function SchedulePage() { + return ( +
+
+ +
+ +
+ ); + } + \ No newline at end of file diff --git a/src/hooks/useApplications.ts b/src/hooks/useApplications.ts index 7ec3dd1..14bd54c 100644 --- a/src/hooks/useApplications.ts +++ b/src/hooks/useApplications.ts @@ -52,6 +52,7 @@ export const useUpdateApplication = () => { mutationFn: updateApplication, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["applications"] }); + queryClient.invalidateQueries({ queryKey: ["schedule"] }); }, }); }; diff --git a/src/hooks/useSchedule.ts b/src/hooks/useSchedule.ts index 75bc062..09eb7cc 100644 --- a/src/hooks/useSchedule.ts +++ b/src/hooks/useSchedule.ts @@ -3,7 +3,8 @@ import { useQuery } from "@tanstack/react-query"; export const useSchedule = (startDate: string, endDate: string) => { return useQuery({ - queryKey: ["schedule"], + queryKey: ["schedule", startDate, endDate], queryFn: () => fetchAllSchedules(startDate, endDate), + staleTime: 1000 * 60 * 60, }); };