diff --git a/src/app/(afterLogin)/_components/protectedPage.tsx b/src/app/(afterLogin)/_components/protectedPage.tsx index 0788129..28a6fe6 100644 --- a/src/app/(afterLogin)/_components/protectedPage.tsx +++ b/src/app/(afterLogin)/_components/protectedPage.tsx @@ -64,7 +64,7 @@ export default function ProtectedPage({ children }: Props) { }; initializeAuth(); - }, []); + }, [isInitialized, router, searchParams, setInitialized, token, checkAuth]); if (!isInitialized) return null; diff --git a/src/app/(afterLogin)/applications/edit/[id]/_components/EditApplication.tsx b/src/app/(afterLogin)/applications/edit/[id]/_components/EditApplication.tsx index 841cd63..b5a2a96 100644 --- a/src/app/(afterLogin)/applications/edit/[id]/_components/EditApplication.tsx +++ b/src/app/(afterLogin)/applications/edit/[id]/_components/EditApplication.tsx @@ -13,6 +13,7 @@ import { } from "@/hooks/useApplications"; import { ApplicationStatus, + CompanyApplication, CompanyApplicationWithId, ScheduleStatus, typeLabels, @@ -147,7 +148,7 @@ export default function EditApplicationsPage({ id }: { id: number }) { try { await mutateAsync({ - changedApplication: changedApplication, + changedApplication: changedApplication as CompanyApplication, applicationId: id, }); diff --git a/src/app/(afterLogin)/applications/page.tsx b/src/app/(afterLogin)/applications/page.tsx index 7116be2..da3d072 100644 --- a/src/app/(afterLogin)/applications/page.tsx +++ b/src/app/(afterLogin)/applications/page.tsx @@ -12,7 +12,7 @@ import Divider from "../documents/_components/divider"; import LoadingSpinner from "@/app/_components/loadingSpinner"; import SearchIcon from "@/assets/Search.svg"; import PlusIcon from "@/assets/Plus.svg"; -import { CompanyApplicationWithId, Schedule } from "@/type/applicationType"; +import { ApplicationStatus, CompanyApplicationWithId, Schedule } from "@/type/applicationType"; import PencilSimpleIcon from "@/assets/PencilSimple.svg"; import TrashSimpleIcon from "@/assets/TrashSimple.svg"; @@ -95,7 +95,7 @@ export default function ApplicationsPage() { alert("선택된 항목이 모두 삭제되었습니다."); setSelectedIds([]); refetch(); - } catch (error) { + } catch { alert("일부 항목 삭제에 실패했습니다. 페이지를 새로고침합니다."); refetch(); } @@ -115,26 +115,24 @@ export default function ApplicationsPage() { const handleStatusChange = ( app: CompanyApplicationWithId, - status: string + newStatus: string ) => { - const changedApp = { ...app, status }; - updateMutate( - { applicationId: app.id, changedApplication: changedApp }, - { - onError: () => { - alert("오류가 발생하였습니다. 잠시 후 시도해주세요."); - }, - } - ); + const queryKey = ["applications", page, searchQuery]; + updateMutate({ + applicationId: app.id, + changedApplication: { ...app, status: newStatus as ApplicationStatus }, + queryKey: queryKey, + newStatus: newStatus, + }); }; - const formatUrl = (url?: string) => { - if (!url) return ""; - if (url.startsWith("http://") || url.startsWith("https://")) { - return url; - } - return `https://${url}`; - }; + // const formatUrl = (url?: string) => { + // if (!url) return ""; + // if (url.startsWith("http://") || url.startsWith("https://")) { + // return url; + // } + // return `https://${url}`; + // }; return ( <> diff --git a/src/app/(afterLogin)/dashboard/_components/applicationStatusSection.tsx b/src/app/(afterLogin)/dashboard/_components/applicationStatusSection.tsx deleted file mode 100644 index 6adce30..0000000 --- a/src/app/(afterLogin)/dashboard/_components/applicationStatusSection.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useFetchAllApplications } from "@/hooks/useApplications"; -import { useMemo } from "react"; -import { CompanyApplication } from "@/type/applicationType"; - -export default function ApplicationStatusSection() { - const { data: ApplicationsData, isLoading } = useFetchAllApplications(0, ""); - - const applicationStatus = useMemo(() => { - if (!ApplicationsData?.data.content) { - return { APPLIED: 0, DOCUMENT_PASSED: 0, FINAL_PASSED: 0, REJECTED: 0 }; - } - - const applications = ApplicationsData.data - .content as Array; - - return applications.reduce( - (counts, app) => { - if (counts.hasOwnProperty(app.status)) { - counts[app.status as keyof typeof counts]++; - } - return counts; - }, - { APPLIED: 0, DOCUMENT_PASSED: 0, FINAL_PASSED: 0, REJECTED: 0 } - ); - }, [ApplicationsData]); - - const box_style = "w-60 border rounded-sm px-9 py-5 flex flex-col gap-4"; - return ( -
-
- 지원 회사 - {applicationStatus.APPLIED}개 -
-
- 서류 합격 - - {applicationStatus.DOCUMENT_PASSED}개 - -
-
- 불합격 - - {applicationStatus.REJECTED}개 - -
-
- 최종 합격 - - {applicationStatus.FINAL_PASSED}개 - -
-
- ); -} diff --git a/src/app/(afterLogin)/dashboard/_components/scheduleSection.tsx b/src/app/(afterLogin)/dashboard/_components/scheduleSection.tsx deleted file mode 100644 index 6c02c48..0000000 --- a/src/app/(afterLogin)/dashboard/_components/scheduleSection.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useSchedule } from "@/hooks/useSchedule"; -import { Schedule } from "@/type/applicationType"; -import CalendarIcon from "@/assets/CalendarCheck.svg"; -import { useMemo } from "react"; - -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}`; -} - -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)}`; -} - -export default function ScheduleSection() { - const { startDate, endDate } = useMemo(() => { - const now = new Date(); - const futureDate = new Date(now); - futureDate.setDate(now.getDate() + 7); - - return { - startDate: formatDateToApiString(now), - endDate: formatDateToApiString(futureDate), - }; - }, []); - - const { data: ScheduleData, isLoading: scheduleLoading } = useSchedule( - startDate, - endDate - ); - - return ( -
-

다가오는 일정

- -
- {ScheduleData?.data.content.map((schedule: Schedule) => ( -
- - - {schedule.title} -
- {getDDay(schedule.dateTime)} -
-
-
- ))} -
-
- ); -} diff --git a/src/app/(afterLogin)/documents/[documentId]/_components/documentDescription.tsx b/src/app/(afterLogin)/documents/[documentId]/_components/documentDescription.tsx index 3623782..141efe8 100644 --- a/src/app/(afterLogin)/documents/[documentId]/_components/documentDescription.tsx +++ b/src/app/(afterLogin)/documents/[documentId]/_components/documentDescription.tsx @@ -1,10 +1,7 @@ "use client"; -import PlusCircleIcon from "@/assets/PlusCircle.svg"; import Divider from "../../_components/divider"; import DownloadIcon from "@/assets/Download.svg"; -import UploadIcon from "@/assets/Upload.svg"; -import Link from "next/link"; import { useDocumentStore } from "@/store/documents/documentStore"; import { ApplicationStatus, Document } from "@/type/applicationType"; import { DocumentType } from "@/type/documentType"; @@ -75,8 +72,7 @@ export default function DocumentDescription({ link.parentNode?.removeChild(link); window.URL.revokeObjectURL(blobUrl); - } catch (error) { - console.error("Download error:", error); + } catch { alert("파일 다운로드 중 오류가 발생했습니다."); } }; diff --git a/src/app/(afterLogin)/documents/_components/mainDocuments.tsx b/src/app/(afterLogin)/documents/_components/mainDocuments.tsx index b41af0e..0d4baa4 100644 --- a/src/app/(afterLogin)/documents/_components/mainDocuments.tsx +++ b/src/app/(afterLogin)/documents/_components/mainDocuments.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useMemo } from "react"; import { formatDate } from "date-fns"; import { useCreateDocument, useDocuments } from "@/hooks/useDocuments"; import VersionBadge from "./versionBadge"; @@ -16,7 +16,7 @@ import UploadFileButton from "./uploadFileButton"; export default function MainDocuments() { const [page, setPage] = useState(0); const { data, error, isLoading } = useDocuments(page); - const documents = data?.data?.data.content ?? []; + const documents = useMemo(() => data?.data.data.content || [], [data?.data.data.content]); const router = useRouter(); const [isAdding, setIsAdding] = useState(false); diff --git a/src/hooks/useApplications.ts b/src/hooks/useApplications.ts index 59493b5..bebe6b1 100644 --- a/src/hooks/useApplications.ts +++ b/src/hooks/useApplications.ts @@ -7,6 +7,7 @@ import { updateApplication, uploadApplications, } from "@/lib/applications"; +import { CompanyApplication, CompanyApplicationWithId } from "@/type/applicationType"; // 업로드 export const useUploadApplications = () => { @@ -17,8 +18,8 @@ export const useUploadApplications = () => { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["applications"] }); }, - onError: (err) => { - console.error("지원서 업로드 실패:", err); + onError: () => { + console.error("지원서 업로드 실패:"); }, }); }; @@ -44,21 +45,107 @@ export const useFetchApplication = (applicationId: number) => { }); }; -// 상태 업데이트 + +type UpdateApplicationVariables = { + applicationId: number; + changedApplication: CompanyApplication; + queryKey?: (string | number)[]; + newStatus?: string; +}; + +interface UpdateContext { + previousListData?: { + data: { content: CompanyApplicationWithId[] }; + }; + previousSingleData?: CompanyApplicationWithId; + queryKey?: (string | number)[]; + singleAppQueryKey: (string | number)[]; +} + +// 상태 업데이트 훅 export const useUpdateApplication = () => { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: updateApplication, - onSuccess: () => { + return useMutation< + void, + Error, + UpdateApplicationVariables, + UpdateContext + >({ + mutationFn: ({ applicationId, changedApplication }) => + updateApplication({ applicationId, changedApplication }), + + onMutate: async (variables) => { + const { applicationId, queryKey, newStatus, changedApplication } = variables; + + const context: UpdateContext = { + singleAppQueryKey: ["application", applicationId], + }; + + await queryClient.cancelQueries({ queryKey: context.singleAppQueryKey }); + const previousSingle = queryClient.getQueryData( + context.singleAppQueryKey + ); + context.previousSingleData = previousSingle; + + if (previousSingle) { + queryClient.setQueryData( + context.singleAppQueryKey, + { + ...previousSingle, + ...changedApplication, + } + ); + } + + if (queryKey && newStatus) { + await queryClient.cancelQueries({ queryKey }); + const previousList = queryClient.getQueryData<{ + data: { content: CompanyApplicationWithId[] }; + }>(queryKey); + context.previousListData = previousList; + context.queryKey = queryKey; + + if (previousList?.data?.content) { + const updatedContent = previousList.data.content.map((item) => + item.id === applicationId ? { ...item, status: newStatus } : item + ); + queryClient.setQueryData(queryKey, { + ...previousList, + data: { ...previousList.data, content: updatedContent }, + }); + } + } + + return context; + }, + + onError: (_err, _variables, context) => { + alert("오류가 발생하였습니다."); + + if (context?.previousSingleData) { + queryClient.setQueryData( + context.singleAppQueryKey, + context.previousSingleData + ); + } + + if (context?.previousListData && context?.queryKey) { + queryClient.setQueryData(context.queryKey, context.previousListData); + } + }, + + onSettled: (_data, _error, variables) => { queryClient.invalidateQueries({ queryKey: ["applications"] }); + queryClient.invalidateQueries({ + queryKey: ["application", variables.applicationId], + }); queryClient.invalidateQueries({ queryKey: ["dashboard"] }); queryClient.invalidateQueries({ queryKey: ["schedule"] }); }, }); }; -// 삭제 export const useDeleteApplication = () => { const queryClient = useQueryClient(); return useMutation({