From ae4d2a385e44227249f439643a0d94cd9ede9771 Mon Sep 17 00:00:00 2001 From: qkrdmstlr3 Date: Sat, 31 Aug 2024 03:21:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(my-recruit):=20duedate=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(my-info)/apis/useGetOnboadaStatus.ts | 1 + .../(my-info)/containers/Onboarding/Logo.tsx | 12 +- .../containers/Onboarding/LogoLeaf.tsx | 12 +- .../Onboarding/OnboardingDialog.tsx | 6 +- src/app/(sidebar)/my-recruit/[id]/page.tsx | 2 +- .../my-recruit/api/usePostRecruitSchedule.ts | 6 +- .../NewRecruitDialogContent/InputField.tsx | 4 +- .../NewRecruitDialogContent.tsx | 2 +- .../ProgressingRecruitList.tsx | 1 - .../containers/components/Card/BoxCard.tsx | 8 +- .../containers/components/Card/RowCard.tsx | 4 +- .../containers/components/DueDateDialog.tsx | 105 --------------- .../components/DueDateDialog/AddIcon.tsx | 21 +++ .../DueDateDialog/DueDateDialog.tsx | 124 ++++++++++-------- .../components/DueDateDialog/Form.tsx | 63 ++------- src/app/(sidebar)/my-recruit/page.tsx | 9 +- src/app/landing/containers/Header/Logo.tsx | 12 +- src/app/landing/containers/Header/Text.tsx | 4 +- src/container/Sidebar/Sidebar.tsx | 9 +- src/system/components/Calendar/Calendar.tsx | 1 + src/system/components/Icon/SVG/Add.tsx | 2 +- src/system/components/Icon/SVG/Calendar.tsx | 25 ++-- src/system/components/Icon/SVG/FolderFill.tsx | 4 +- src/system/components/Icon/SVG/LogoOnly.tsx | 12 +- src/utils/immer.ts | 3 + 25 files changed, 176 insertions(+), 276 deletions(-) create mode 100644 src/app/(sidebar)/(my-info)/apis/useGetOnboadaStatus.ts delete mode 100644 src/app/(sidebar)/my-recruit/containers/components/DueDateDialog.tsx create mode 100644 src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/AddIcon.tsx create mode 100644 src/utils/immer.ts diff --git a/src/app/(sidebar)/(my-info)/apis/useGetOnboadaStatus.ts b/src/app/(sidebar)/(my-info)/apis/useGetOnboadaStatus.ts new file mode 100644 index 00000000..4bd4d9f2 --- /dev/null +++ b/src/app/(sidebar)/(my-info)/apis/useGetOnboadaStatus.ts @@ -0,0 +1 @@ +const getOnboardStatus = () => {}; diff --git a/src/app/(sidebar)/(my-info)/containers/Onboarding/Logo.tsx b/src/app/(sidebar)/(my-info)/containers/Onboarding/Logo.tsx index 0c2fa4bd..8ac7d8b9 100644 --- a/src/app/(sidebar)/(my-info)/containers/Onboarding/Logo.tsx +++ b/src/app/(sidebar)/(my-info)/containers/Onboarding/Logo.tsx @@ -12,8 +12,8 @@ export function Logo() { @@ -26,16 +26,16 @@ export function Logo() { diff --git a/src/app/(sidebar)/(my-info)/containers/Onboarding/LogoLeaf.tsx b/src/app/(sidebar)/(my-info)/containers/Onboarding/LogoLeaf.tsx index 500d93d7..6164c766 100644 --- a/src/app/(sidebar)/(my-info)/containers/Onboarding/LogoLeaf.tsx +++ b/src/app/(sidebar)/(my-info)/containers/Onboarding/LogoLeaf.tsx @@ -7,8 +7,8 @@ export function LogoLeaf() { /> @@ -19,15 +19,15 @@ export function LogoLeaf() { /> diff --git a/src/app/(sidebar)/(my-info)/containers/Onboarding/OnboardingDialog.tsx b/src/app/(sidebar)/(my-info)/containers/Onboarding/OnboardingDialog.tsx index ddb4b9c3..d842ebad 100644 --- a/src/app/(sidebar)/(my-info)/containers/Onboarding/OnboardingDialog.tsx +++ b/src/app/(sidebar)/(my-info)/containers/Onboarding/OnboardingDialog.tsx @@ -169,9 +169,9 @@ export function OnboardingDialog({}: OnboardingDialogProps) { animate={step} className="absolute bg-white top-0 left-0 w-full h-full rounded-[24px]"> -
+
+
diff --git a/src/app/(sidebar)/my-recruit/[id]/page.tsx b/src/app/(sidebar)/my-recruit/[id]/page.tsx index c0366927..e968c9be 100644 --- a/src/app/(sidebar)/my-recruit/[id]/page.tsx +++ b/src/app/(sidebar)/my-recruit/[id]/page.tsx @@ -57,7 +57,7 @@ export default function CompanyDetail({ params: { id: recruitId } }: { params: { - {}} /> + diff --git a/src/app/(sidebar)/my-recruit/api/usePostRecruitSchedule.ts b/src/app/(sidebar)/my-recruit/api/usePostRecruitSchedule.ts index 027325f3..889da9b1 100644 --- a/src/app/(sidebar)/my-recruit/api/usePostRecruitSchedule.ts +++ b/src/app/(sidebar)/my-recruit/api/usePostRecruitSchedule.ts @@ -1,6 +1,7 @@ import { http } from '@/apis/http'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { GET_RECRUIT_SCHEDULE } from '@/app/(sidebar)/my-recruit/api/useGetRecruitSchedule'; +import { GET_ALL_RECRUITS_KEY } from '@/app/(sidebar)/my-recruit/api/useGetAllRecruits'; +import { GET_PROGRESSING_RECRUITS_KEY } from '@/app/(sidebar)/my-recruit/api/useGetProgressingRecruits'; interface Request { id: number; @@ -18,7 +19,8 @@ export function usePostRecruitSchedule() { const mutate = useMutation({ mutationFn: (data: Request) => postRecruitSchedule(data), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [GET_RECRUIT_SCHEDULE] }); + queryClient.invalidateQueries({ queryKey: [GET_ALL_RECRUITS_KEY] }); + queryClient.invalidateQueries({ queryKey: [GET_PROGRESSING_RECRUITS_KEY] }); }, }); diff --git a/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/InputField.tsx b/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/InputField.tsx index 239fd67d..611e81b4 100644 --- a/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/InputField.tsx +++ b/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/InputField.tsx @@ -9,9 +9,9 @@ interface Props extends ComponentProps<'input'> { export function InputField({ required = false, right, value, ...inputProps }: Props) { return ( -
+
-
*
+
*
diff --git a/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/NewRecruitDialogContent.tsx b/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/NewRecruitDialogContent.tsx index 46d62915..55e9b8a3 100644 --- a/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/NewRecruitDialogContent.tsx +++ b/src/app/(sidebar)/my-recruit/components/NewRecruitDialogContent/NewRecruitDialogContent.tsx @@ -33,7 +33,7 @@ export function NewRecruitDialogContent({ onSubmit }: NewRecruitDialogContentPro const [title, setTitle] = useState(''); const [siteUrl, setSiteUrl] = useState(''); const [selectedDate, setSelectedDate] = useState(); - const [currentRecruitStage, setCurrentRecruitStage] = useState(recruitStatusList[3].text); + const [currentRecruitStage, setCurrentRecruitStage] = useState(recruitScheduleStageList[0]); const [selectedSeason, setSelectedSeason] = useState(); const seasonList = useGetSeasons()?.data ?? []; diff --git a/src/app/(sidebar)/my-recruit/containers/ProgressingRecruitment/ProgressingRecruitList.tsx b/src/app/(sidebar)/my-recruit/containers/ProgressingRecruitment/ProgressingRecruitList.tsx index 5c8c89ae..a1d57981 100644 --- a/src/app/(sidebar)/my-recruit/containers/ProgressingRecruitment/ProgressingRecruitList.tsx +++ b/src/app/(sidebar)/my-recruit/containers/ProgressingRecruitment/ProgressingRecruitList.tsx @@ -56,7 +56,6 @@ export function ProgressingRecruitList() { {}} onRecruitDelete={deleteRecruit} onRecruitStatusChange={(id, status) => { patchRecruitStatus({ id, recruitStatus: status }); diff --git a/src/app/(sidebar)/my-recruit/containers/components/Card/BoxCard.tsx b/src/app/(sidebar)/my-recruit/containers/components/Card/BoxCard.tsx index 44902679..cee11399 100644 --- a/src/app/(sidebar)/my-recruit/containers/components/Card/BoxCard.tsx +++ b/src/app/(sidebar)/my-recruit/containers/components/Card/BoxCard.tsx @@ -5,15 +5,14 @@ import { dday } from '@/utils/date'; import { MoreButton } from '@/app/(sidebar)/my-recruit/containers/components/Card/common/MoreButton'; import { StatusButton } from '@/app/(sidebar)/my-recruit/containers/components/Card/common/StatusButton'; import { Dialog } from '@/system/components/Dialog/ShadcnDialog'; -import Link from 'next/link'; import { DueDateDialog } from '../DueDateDialog/DueDateDialog'; import { RecruitCard } from '@/app/(sidebar)/my-recruit/type'; import { useRouter } from 'next/navigation'; +import { AsyncBoundaryWithQuery } from '@/lib'; interface BoxCardProps extends RecruitCard { onRecruitDelete: (id: number) => void; onRecruitStatusChange: (id: number, status: string) => void; - onDuedateAppend: () => void; } export const MIN_CARD_WIDTH = 250; @@ -24,7 +23,6 @@ export function BoxCard({ recruitStatus, season, nearestSchedule, - onDuedateAppend, onRecruitStatusChange, onRecruitDelete, }: BoxCardProps) { @@ -41,7 +39,9 @@ export function BoxCard({ - onDuedateAppend()} /> + }> + + ) : ( diff --git a/src/app/(sidebar)/my-recruit/containers/components/Card/RowCard.tsx b/src/app/(sidebar)/my-recruit/containers/components/Card/RowCard.tsx index 9c27c98d..b7f0283d 100644 --- a/src/app/(sidebar)/my-recruit/containers/components/Card/RowCard.tsx +++ b/src/app/(sidebar)/my-recruit/containers/components/Card/RowCard.tsx @@ -40,7 +40,7 @@ export function RowCard({
router.push(`/my-recruit/${id}`)}> -
+
- {recruitStatus} D-{dday(nearestSchedule?.deadLine!)} + {nearestSchedule?.recruitScheduleStage} D-{dday(nearestSchedule?.deadLine!)}
diff --git a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog.tsx b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog.tsx deleted file mode 100644 index d6d8dca7..00000000 --- a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { recruitStatusList } from '@/app/(sidebar)/my-recruit/constant'; -import { Button, Dropdown, Icon } from '@/system/components'; -import { Calendar } from '@/system/components/Calendar/Calendar'; -import { Popover, PopoverContent, PopoverTrigger } from '@/system/components/Popover/Popover'; -import { color } from '@/system/token/color'; -import { Spacing } from '@/system/utils/Spacing'; -import { cn } from '@/utils'; -import clsx from 'clsx'; -import { format } from 'date-fns/format'; -import { motion } from 'framer-motion'; -import { useState } from 'react'; -import { recruitScheduleStageList } from '../../constant'; - -interface DueDateDialogProps { - title?: string; - onDuedateAppend: () => void; -} - -export function DueDateDialog({ title }: DueDateDialogProps) { - const [selectedDate, setSelectedDate] = useState(); - const [currentRecruitStage, setCurrentRecruitStage] = useState(recruitStatusList[3].text); - const [dueDateList, setDueDateList] = useState< - Array<{ - recruitScheduleStage: string | null; - deadLine: `${number}-${number}-${number}` | null; - }> - >([]); - - const isDateSelected = selectedDate != null; - const activatedAddButton = - dueDateList.length !== 0 && dueDateList[0].deadLine != null && dueDateList[0].recruitScheduleStage != null; - - return ( -
-
- {title && ( - <> - - - - )} - - {title ? `${title}의 공고 일정 등록하기` : '공고 일정 등록하기'} - -
- - - 일정을 등록하면 잊지 않도록 알려드릴게요! - - - {/* 마감일 입력 */} -
- - -
- {currentRecruitStage} - -
-
- - {recruitScheduleStageList.map((item, index) => ( - setCurrentRecruitStage(item)}> - {item} - - ))} - -
- - - - - - {isDateSelected ? format(selectedDate, 'yyyy.mm.dd') : '마감일을 선택해주세요'} - - - - - - - -
- - -
- ); -} diff --git a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/AddIcon.tsx b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/AddIcon.tsx new file mode 100644 index 00000000..cfe43881 --- /dev/null +++ b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/AddIcon.tsx @@ -0,0 +1,21 @@ +export function AddIcon() { + return ( + + + + + + + + + ); +} diff --git a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/DueDateDialog.tsx b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/DueDateDialog.tsx index 3913d356..a2b76ea7 100644 --- a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/DueDateDialog.tsx +++ b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/DueDateDialog.tsx @@ -1,42 +1,58 @@ -import { Button, Icon } from '@/system/components'; +import { Icon } from '@/system/components'; import { color } from '@/system/token/color'; import { Spacing } from '@/system/utils/Spacing'; import { cn } from '@/utils'; import clsx from 'clsx'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useGetRecruitSchedule } from '../../../api/useGetRecruitSchedule'; import { Form } from '@/app/(sidebar)/my-recruit/containers/components/DueDateDialog/Form'; -import { If } from '@/system/utils/If'; -import { useQueryClient } from '@tanstack/react-query'; -import { GET_ALL_RECRUITS_KEY } from '../../../api/useGetAllRecruits'; -import { GET_PROGRESSING_RECRUITS_KEY } from '../../../api/useGetProgressingRecruits'; -import { useDeleteRecruitSchedule } from '../../../api/useDeleteRecruitSchedule'; +import { TouchButton } from '@/components/TouchButton'; +import { AddIcon } from '@/app/(sidebar)/my-recruit/containers/components/DueDateDialog/AddIcon'; +import { recruitScheduleStageList } from '@/app/(sidebar)/my-recruit/constant'; +import { usePostRecruitSchedule } from '../../../api/usePostRecruitSchedule'; +import { format } from 'date-fns/format'; +import { DialogClose } from '@/system/components/Dialog/Dialog'; +import { immer } from '@/utils/immer'; interface DueDateDialogProps { id: number; title?: string; - onDuedateAppend: () => void; } +const DEFAULT_FORM = { + recruitScheduleStage: recruitScheduleStageList[0], + deadLine: undefined, +}; + export function DueDateDialog({ id, title }: DueDateDialogProps) { - const [additonalScheduleForm, setAdditionalScheduleForm] = useState(false); - const scheduleList = useGetRecruitSchedule({ id }).data; + const { mutate: postRecruitSchedule } = usePostRecruitSchedule(); + const [scheduleList, setScheduleList] = useState< + Array<{ + recruitScheduleStage: string; + deadLine?: Date; + }> + >([DEFAULT_FORM]); - const { mutate: deleteRecruitSchedule } = useDeleteRecruitSchedule(); + console.log(scheduleList); - const activatedAddButton = - additonalScheduleForm === false && - scheduleList.length !== 0 && - scheduleList[0].deadLine != null && - scheduleList[0].recruitScheduleStage != null; + const handleDelete = (index: number) => { + const newList = [...scheduleList]; + newList.splice(index, 1); + setScheduleList(newList); + }; - const queryClient = useQueryClient(); - useEffect(() => { - return () => { - queryClient.invalidateQueries({ queryKey: [GET_PROGRESSING_RECRUITS_KEY] }); - queryClient.invalidateQueries({ queryKey: [GET_ALL_RECRUITS_KEY] }); - }; - }, []); + const save = () => { + scheduleList.forEach((schedule) => { + if (schedule.deadLine == null) { + return; + } + postRecruitSchedule({ + id, + recruitScheduleStage: schedule.recruitScheduleStage, + deadLine: format(schedule.deadLine, 'yyyy-MM-dd'), + }); + }); + }; return (
@@ -59,42 +75,42 @@ export function DueDateDialog({ id, title }: DueDateDialogProps) { {/* 마감일 입력 */}
- {scheduleList.length === 0 ? ( -
- ) : ( - scheduleList.map((schedule, index) => ( - deleteRecruitSchedule({ id, recruitScheduleId: schedule.id })} - /> - )) - )} - + {scheduleList.map((schedule, index) => ( setAdditionalScheduleForm(false)} + onStageClick={(stage) => { + const newSchedule = immer(scheduleList); + newSchedule[index].recruitScheduleStage = stage; + setScheduleList([...newSchedule]); + }} + onDeadLineClick={(date) => { + const newSchedule = immer(scheduleList); + newSchedule[index].deadLine = date; + setScheduleList([...newSchedule]); + }} + onDeleteClick={() => handleDelete(index)} /> - + ))}
- + setScheduleList([...scheduleList, DEFAULT_FORM])} + className="hover:bg-neutral-3 w-full h-46 gap-[4px] rounded-[6px] py-[13px] flex items-center justify-center"> + + 일정 추가 + + + + + 저장하기 + +
); } diff --git a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/Form.tsx b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/Form.tsx index 4d210bd7..d3370931 100644 --- a/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/Form.tsx +++ b/src/app/(sidebar)/my-recruit/containers/components/DueDateDialog/Form.tsx @@ -7,72 +7,29 @@ import { format } from 'date-fns/format'; import { motion } from 'framer-motion'; import { recruitScheduleStageList } from '@/app/(sidebar)/my-recruit/constant'; import { useState } from 'react'; -import { usePostRecruitSchedule } from '../../../api/usePostRecruitSchedule'; -import { usePutRecruitScheduleDeadline } from '../../../api/usePutRecruitScheduleDeadline'; -import { usePutRecruitScheduleStage } from '../../../api/usePutRecruitScheduleStage'; import { If } from '@/system/utils/If'; interface Props { - id?: number; - recruitId: number; - deadLine?: string; - recruitScheduleStage?: string; + selectedDate?: Date; + currentRecruitStage: string; hasDeleteButton?: boolean; hasArrow: boolean; + onStageClick: (item: string) => void; + onDeadLineClick: (date?: Date) => void; onDeleteClick?: () => void; } // 매번 서버요청 안하도록 리팩토링 export function Form({ - id, - recruitId, hasArrow, - deadLine, - recruitScheduleStage, + currentRecruitStage, + selectedDate, hasDeleteButton, + onStageClick, + onDeadLineClick, onDeleteClick, }: Props) { - const [selectedDate, setSelectedDate] = useState(deadLine != null ? new Date(deadLine) : undefined); - const [currentRecruitStage, setCurrentRecruitStage] = useState( - recruitScheduleStage ?? recruitScheduleStageList[0], - ); - const isDateSelected = selectedDate != null; - const { mutate: postRecruitSchedule } = usePostRecruitSchedule(); - const { mutate: putRecruitScheduleDeadline } = usePutRecruitScheduleDeadline(); - const { mutate: putRecruitScheduleState } = usePutRecruitScheduleStage(); - - const handleStageClick = (item: string) => { - if (id != null) { - putRecruitScheduleState({ id: recruitId, recruitScheduleId: id, recruitScheduleStage: item }); - return; - } - setCurrentRecruitStage(item); - if (selectedDate == null) { - return; - } - postRecruitSchedule({ - id: recruitId, - deadLine: format(selectedDate, 'yyyy-MM-dd'), - recruitScheduleStage: currentRecruitStage, - }); - }; - - const handleDeadlineClick = (date?: Date) => { - if (date == null) { - return; - } - if (id != null) { - putRecruitScheduleDeadline({ id: recruitId, recruitScheduleId: id, deadLine: format(date, 'yyyy-MM-dd') }); - return; - } - setSelectedDate(date); - postRecruitSchedule({ - id: recruitId, - deadLine: format(date, 'yyyy-MM-dd'), - recruitScheduleStage: currentRecruitStage, - }); - }; return (
@@ -89,7 +46,7 @@ export function Form({ key={index} checked={currentRecruitStage === item} disabled={currentRecruitStage === item} - onClick={() => handleStageClick(item)}> + onClick={() => onStageClick(item)}> {item} ))} @@ -115,7 +72,7 @@ export function Form({ - +
- - - + + + + + ); diff --git a/src/app/landing/containers/Header/Logo.tsx b/src/app/landing/containers/Header/Logo.tsx index ee556db3..badc6b3d 100644 --- a/src/app/landing/containers/Header/Logo.tsx +++ b/src/app/landing/containers/Header/Logo.tsx @@ -8,8 +8,8 @@ export function LOGO() { /> @@ -20,15 +20,15 @@ export function LOGO() { /> diff --git a/src/app/landing/containers/Header/Text.tsx b/src/app/landing/containers/Header/Text.tsx index 151c9b06..e6b1b13e 100644 --- a/src/app/landing/containers/Header/Text.tsx +++ b/src/app/landing/containers/Header/Text.tsx @@ -8,8 +8,8 @@ export function Text() { diff --git a/src/container/Sidebar/Sidebar.tsx b/src/container/Sidebar/Sidebar.tsx index e237431f..ae82a665 100644 --- a/src/container/Sidebar/Sidebar.tsx +++ b/src/container/Sidebar/Sidebar.tsx @@ -27,6 +27,9 @@ export function Sidebar() { deleteCookie('refreshToken'); router.push('/login'); }; + + const isRecruitPage = pathname.includes(MY_RECRUIT_PATH); + return (