From 345e0f90f4c04b39923ad3a8503875b0d1c6183e Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Fri, 2 May 2025 18:22:21 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EA=B3=B5=EA=B3=A0=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20(=EC=95=8C=EB=B0=94?= =?UTF-8?q?=EC=83=9D)=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Router.tsx | 4 +- src/components/Post/Post.tsx | 5 +- src/components/Post/PostCard.tsx | 41 ++++-- src/components/Post/PostList.tsx | 2 +- src/constants/router.ts | 4 +- src/hooks/useNoticeInfo.ts | 36 +++++ src/hooks/useNotices.ts | 44 ++++++ src/pages/NoticeEmployeePage.tsx | 3 - .../NoticeEmployeePage/NoticeEmployeePage.tsx | 28 ++++ .../components/NoticeDetailInfo.tsx | 130 ++++++++++++++++++ .../components/RecentNotices.tsx | 36 +++++ .../components/ShopInfoPostCardSkeleton.tsx | 50 +++++++ 12 files changed, 363 insertions(+), 20 deletions(-) create mode 100644 src/hooks/useNoticeInfo.ts create mode 100644 src/hooks/useNotices.ts delete mode 100644 src/pages/NoticeEmployeePage.tsx create mode 100644 src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx create mode 100644 src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx create mode 100644 src/pages/NoticeEmployeePage/components/RecentNotices.tsx create mode 100644 src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx diff --git a/src/Router.tsx b/src/Router.tsx index 2039714..c6c3905 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -23,7 +23,9 @@ const NoticeListPage = lazy(() => import("@/pages/NoticeListPage")); const NoticeRegisterPage = lazy(() => import("@/pages/NoticeRegisterPage")); const NoticeEditPage = lazy(() => import("@/pages/NoticeEditPage")); const NoticeEmployerPage = lazy(() => import("@/pages/NoticeEmployerPage")); -const NoticeEmployeePage = lazy(() => import("@/pages/NoticeEmployeePage")); +const NoticeEmployeePage = lazy( + () => import("@/pages/NoticeEmployeePage/NoticeEmployeePage"), +); const authRoutes: RouteObject[] = [ { diff --git a/src/components/Post/Post.tsx b/src/components/Post/Post.tsx index 8567a40..698ea43 100644 --- a/src/components/Post/Post.tsx +++ b/src/components/Post/Post.tsx @@ -1,8 +1,9 @@ -import { cn } from "@/utils/cn"; import { Link } from "react-router-dom"; + +import { Location, Time, ArrowUp, ArrowUpBold } from "@/assets/icon"; +import { cn } from "@/utils/cn"; import { formatTimeRange, isPastDate } from "@/utils/datetime"; import { getPayRateText } from "@/utils/payRate"; -import { Location, Time, ArrowUp, ArrowUpBold } from "@/assets/icon"; interface PostProps { name: string; diff --git a/src/components/Post/PostCard.tsx b/src/components/Post/PostCard.tsx index 0084fb3..960dc61 100644 --- a/src/components/Post/PostCard.tsx +++ b/src/components/Post/PostCard.tsx @@ -1,7 +1,7 @@ +import { Location, Time, ArrowUp } from "@/assets/icon"; import { cn } from "@/utils/cn"; -import { formatTimeRange } from "@/utils/datetime"; +import { formatTimeRange, isPastDate } from "@/utils/datetime"; import { getPayRateText } from "@/utils/payRate"; -import { Location, Time, ArrowUp } from "@/assets/icon"; interface PostCardProps { name: string; @@ -15,6 +15,7 @@ interface PostCardProps { isShopInfo?: boolean; backgroundColor?: string; buttons?: React.ReactNode; + closed?: boolean; } export default function PostCard({ @@ -29,8 +30,14 @@ export default function PostCard({ isShopInfo = false, backgroundColor = "#ffffff", buttons = null, + closed, }: PostCardProps) { const { rateText } = getPayRateText(hourlyPay, originalHourlyPay); + const isPast = isPastDate( + startsAt ?? Date.now().toLocaleString(), + workhour ?? 0, + ); + const isDimmed = closed || isPast; const timeRange = startsAt && workhour !== undefined @@ -45,30 +52,42 @@ export default function PostCard({ backgroundColor === "#ffffff" && "border border-gray-20 shadow-sm", )} > -
+
{name} + {isDimmed && ( +
+

+ {!closed && isPast && "지난 공고"} + {closed && !isPast && "마감 완료"} +

+
+ )}
{!isShopInfo ? ( <>
-

시급

+

+ 시급 +

{hourlyPay?.toLocaleString()}원

- - {rateText} - - + {!closed && ( + + {rateText} + + + )}
-
+
@@ -77,11 +96,11 @@ export default function PostCard({

식당

)} {isShopInfo &&

{name}

} -
+
{address1}
-

+

{description}

diff --git a/src/components/Post/PostList.tsx b/src/components/Post/PostList.tsx index cd6b31a..efda071 100644 --- a/src/components/Post/PostList.tsx +++ b/src/components/Post/PostList.tsx @@ -1,6 +1,6 @@ import Post from "./Post"; -interface PostData { +export interface PostData { id: string; name: string; imageUrl: string; diff --git a/src/constants/router.ts b/src/constants/router.ts index cadfff7..2446bc3 100644 --- a/src/constants/router.ts +++ b/src/constants/router.ts @@ -18,8 +18,8 @@ const ROUTES = { REGISTER: "/notice/register", EDIT: "/notice/edit", NOTICE_ID: { - EMPLOYER: `/notice/:noticeId/employer`, - EMPLOYEE: `/notice/:noticeId/employee`, + EMPLOYER: `/notice/:shopId/:noticeId/employer`, + EMPLOYEE: `/notice/:shopId/:noticeId/employee`, }, }, } as const; diff --git a/src/hooks/useNoticeInfo.ts b/src/hooks/useNoticeInfo.ts new file mode 100644 index 0000000..05e9d09 --- /dev/null +++ b/src/hooks/useNoticeInfo.ts @@ -0,0 +1,36 @@ +import { DependencyList, useCallback, useEffect, useState } from "react"; + +import { NoticeItem } from "../types"; + +import { getNotice } from "@/apis/services/noticeService"; + +interface UseNoticeInfoParams { + shopId: string; + noticeId: string; + deps?: DependencyList; +} + +function useNoticeInfo({ shopId, noticeId, deps }: UseNoticeInfoParams) { + const dependencies = deps ?? []; + const [isLoading, setIsLoading] = useState(false); + const [noticeInfo, setNoticeInfo] = useState(); + + const fetchNoticeInfo = useCallback(async () => { + setIsLoading(true); + const result = await getNotice(shopId, noticeId); + + if (result.status === 200) { + setNoticeInfo(result.data.item); + } + + setIsLoading(false); + }, [shopId, noticeId, ...dependencies]); + + useEffect(() => { + fetchNoticeInfo(); + }, [shopId, noticeId, ...dependencies]); + + return { isLoading, noticeInfo }; +} + +export default useNoticeInfo; diff --git a/src/hooks/useNotices.ts b/src/hooks/useNotices.ts new file mode 100644 index 0000000..642c270 --- /dev/null +++ b/src/hooks/useNotices.ts @@ -0,0 +1,44 @@ +import { DependencyList, useCallback, useEffect, useState } from "react"; + +import { + GetNoticesParams, + NoticeListResponseWithoutUserApplication, +} from "../types"; + +import { getNotices } from "@/apis/services/noticeService"; + +interface UseNoticesParams { + deps?: DependencyList; + params?: GetNoticesParams; + dataFormatCallback: (params: NoticeListResponseWithoutUserApplication) => T[]; +} + +function useNotices({ + deps, + params, + dataFormatCallback, +}: UseNoticesParams) { + const dependencies = deps ?? []; + const [isLoading, setIsLoading] = useState(false); + const [notices, setNotices] = useState([]); + + const fetchNotices = useCallback(async () => { + setIsLoading(true); + const { data, status } = await getNotices(params); + + if (status === 200) { + const nextNotices = dataFormatCallback(data); + setNotices(nextNotices); + } + + setIsLoading(false); + }, [...dependencies]); + + useEffect(() => { + fetchNotices(); + }, [...dependencies]); + + return { fetchNotices, isLoading, notices }; +} + +export default useNotices; diff --git a/src/pages/NoticeEmployeePage.tsx b/src/pages/NoticeEmployeePage.tsx deleted file mode 100644 index cd723f4..0000000 --- a/src/pages/NoticeEmployeePage.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function NoticeEmployeePage() { - return
NoticeEmployeePage
; -} diff --git a/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx b/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx new file mode 100644 index 0000000..52f00f5 --- /dev/null +++ b/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx @@ -0,0 +1,28 @@ +import { useParams } from "react-router-dom"; + +import NoticeDetailInfo from "./components/NoticeDetailInfo"; +import RecentNotices from "./components/RecentNotices"; + +export default function NoticeEmployeePage() { + const { shopId, noticeId } = useParams() as { + shopId: string; + noticeId: string; + }; + + return ( + <> +
+
+ +
+
+ +
+
+

최근에 본 공고

+ +
+
+ + ); +} diff --git a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx new file mode 100644 index 0000000..8a5a5c6 --- /dev/null +++ b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx @@ -0,0 +1,130 @@ +import ShopInfoPostCardSkeleton from "./ShopInfoPostCardSkeleton"; + +import { + postApplication, + putApplication, +} from "@/apis/services/applicationService"; +import Button from "@/components/Button"; +import PostCard from "@/components/Post/PostCard"; +import useNoticeInfo from "@/hooks/useNoticeInfo"; +import { cn } from "@/utils/cn"; +import { isPastDate } from "@/utils/datetime"; + +interface NoticeDetailInfoProps { + shopId: string; + noticeId: string; +} + +function NoticeDetailInfo({ shopId, noticeId }: NoticeDetailInfoProps) { + const { noticeInfo, isLoading: isLoadingNoticeInfo } = useNoticeInfo({ + shopId, + noticeId, + }); + + if (!noticeInfo?.shop || isLoadingNoticeInfo) { + return ; + } + + const { + hourlyPay, + startsAt, + workhour, + closed, + description, + currentUserApplication, + } = noticeInfo; + + const { + name, + imageUrl, + address1, + originalHourlyPay, + description: shopDescription, + } = noticeInfo.shop.item; + + const applicationStatus = currentUserApplication?.item.status; + const applicationId = currentUserApplication?.item.id; + const isPast = isPastDate(startsAt, workhour); + const isDisabledNotice = isPast || closed || applicationStatus === "canceled"; + + const applyNotice = async () => { + const result = await postApplication(shopId, noticeId); + + if (result.status === 201) { + // Modal 병합 후 모달로 변경 예정 + alert("신청이 완료 되었습니다."); + } + }; + + const cancelApplication = async () => { + const result = await putApplication( + shopId, + noticeId, + applicationId ?? "", + "canceled", + ); + + if (result.status === 200) { + // Modal 병합 후 모달로 변경 예정 + alert("취소가 완료 되었습니다."); + } + }; + + return ( + <> + {!isLoadingNoticeInfo && ( + <> +
+ + 식당 + +

{name}

+
+ {noticeInfo && ( + + {applicationStatus === "pending" && "취소하기"} + {applicationStatus === "accepted" && "승낙"} + {applicationStatus === "rejected" && "지원 거절"} + {applicationStatus === "canceled" && "지원 취소"} + {!applicationStatus && isDisabledNotice && "신청 불가"} + {!applicationStatus && !closed && !isPast && "지원하기"} + + } + /> + )} +
+ 공고 설명 +

{description}

+
+ + )} + + ); +} + +export default NoticeDetailInfo; diff --git a/src/pages/NoticeEmployeePage/components/RecentNotices.tsx b/src/pages/NoticeEmployeePage/components/RecentNotices.tsx new file mode 100644 index 0000000..b8e0522 --- /dev/null +++ b/src/pages/NoticeEmployeePage/components/RecentNotices.tsx @@ -0,0 +1,36 @@ +import PostList, { PostData } from "@/components/Post/PostList"; +import useNotices from "@/hooks/useNotices"; + +interface RecentNoticesProps { + shopId: string; + noticeId: string; +} + +function RecentNotices({ shopId, noticeId }: RecentNoticesProps) { + const { notices: recentNotices, isLoading: isLoadingRecentNotices } = + useNotices({ + dataFormatCallback: ({ items }) => + items.map(({ item }) => ({ + id: item.id, + name: item.shop?.item.name ?? "", + imageUrl: item.shop?.item.imageUrl ?? "", + address1: item.shop?.item.address1 ?? "", + originalHourlyPay: item.shop?.item.originalHourlyPay ?? 0, + link: `/notice/${item.shop?.item.id}/${item.id}/employee`, + hourlyPay: item.hourlyPay, + startsAt: item.startsAt, + workhour: item.workhour, + closed: item.closed, + })), + params: { sort: "hour", limit: 6 }, + deps: [shopId, noticeId], + }); + + return isLoadingRecentNotices ? ( +
로딩 중...
+ ) : ( + + ); +} + +export default RecentNotices; diff --git a/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx b/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx new file mode 100644 index 0000000..862bbd8 --- /dev/null +++ b/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx @@ -0,0 +1,50 @@ +import { Location, Time } from "@/assets/icon"; + +function ShopInfoPostCardSkeleton() { + return ( + <> +
+ + 식당 + +

+

+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ +
+ 공고 설명 +

+

+ + ); +} + +export default ShopInfoPostCardSkeleton; From dc8c30c822e56171038fc34b408675020c5f7736 Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Fri, 2 May 2025 18:50:01 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20noticeEmployeeLoader=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Router.tsx | 2 + src/hooks/useNoticeInfo.ts | 36 ------ src/hooks/useNotices.ts | 44 ------- .../NoticeEmployeePage/NoticeEmployeePage.tsx | 14 ++- .../components/NoticeDetailInfo.tsx | 116 ++++++++---------- .../components/RecentNotices.tsx | 36 ------ .../loader/noticeEmployeeLoader.ts | 53 ++++++++ 7 files changed, 117 insertions(+), 184 deletions(-) delete mode 100644 src/hooks/useNoticeInfo.ts delete mode 100644 src/hooks/useNotices.ts delete mode 100644 src/pages/NoticeEmployeePage/components/RecentNotices.tsx create mode 100644 src/pages/NoticeEmployeePage/loader/noticeEmployeeLoader.ts diff --git a/src/Router.tsx b/src/Router.tsx index c6c3905..c359799 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -2,6 +2,7 @@ import { lazy } from "react"; import { createBrowserRouter, RouteObject } from "react-router-dom"; +import noticeEmployeeLoader from "./pages/NoticeEmployeePage/loader/noticeEmployeeLoader"; import profileLoader from "./pages/ProfilePage/loader/profileLoader"; import { ROUTES } from "./constants/router"; @@ -89,6 +90,7 @@ const noticeRoutes: RouteObject[] = [ { path: ROUTES.NOTICE.NOTICE_ID.EMPLOYEE, Component: NoticeEmployeePage, + loader: noticeEmployeeLoader, }, ]; diff --git a/src/hooks/useNoticeInfo.ts b/src/hooks/useNoticeInfo.ts deleted file mode 100644 index 05e9d09..0000000 --- a/src/hooks/useNoticeInfo.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { DependencyList, useCallback, useEffect, useState } from "react"; - -import { NoticeItem } from "../types"; - -import { getNotice } from "@/apis/services/noticeService"; - -interface UseNoticeInfoParams { - shopId: string; - noticeId: string; - deps?: DependencyList; -} - -function useNoticeInfo({ shopId, noticeId, deps }: UseNoticeInfoParams) { - const dependencies = deps ?? []; - const [isLoading, setIsLoading] = useState(false); - const [noticeInfo, setNoticeInfo] = useState(); - - const fetchNoticeInfo = useCallback(async () => { - setIsLoading(true); - const result = await getNotice(shopId, noticeId); - - if (result.status === 200) { - setNoticeInfo(result.data.item); - } - - setIsLoading(false); - }, [shopId, noticeId, ...dependencies]); - - useEffect(() => { - fetchNoticeInfo(); - }, [shopId, noticeId, ...dependencies]); - - return { isLoading, noticeInfo }; -} - -export default useNoticeInfo; diff --git a/src/hooks/useNotices.ts b/src/hooks/useNotices.ts deleted file mode 100644 index 642c270..0000000 --- a/src/hooks/useNotices.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { DependencyList, useCallback, useEffect, useState } from "react"; - -import { - GetNoticesParams, - NoticeListResponseWithoutUserApplication, -} from "../types"; - -import { getNotices } from "@/apis/services/noticeService"; - -interface UseNoticesParams { - deps?: DependencyList; - params?: GetNoticesParams; - dataFormatCallback: (params: NoticeListResponseWithoutUserApplication) => T[]; -} - -function useNotices({ - deps, - params, - dataFormatCallback, -}: UseNoticesParams) { - const dependencies = deps ?? []; - const [isLoading, setIsLoading] = useState(false); - const [notices, setNotices] = useState([]); - - const fetchNotices = useCallback(async () => { - setIsLoading(true); - const { data, status } = await getNotices(params); - - if (status === 200) { - const nextNotices = dataFormatCallback(data); - setNotices(nextNotices); - } - - setIsLoading(false); - }, [...dependencies]); - - useEffect(() => { - fetchNotices(); - }, [...dependencies]); - - return { fetchNotices, isLoading, notices }; -} - -export default useNotices; diff --git a/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx b/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx index 52f00f5..0231b57 100644 --- a/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx +++ b/src/pages/NoticeEmployeePage/NoticeEmployeePage.tsx @@ -1,9 +1,11 @@ -import { useParams } from "react-router-dom"; +import { useLoaderData, useParams } from "react-router-dom"; import NoticeDetailInfo from "./components/NoticeDetailInfo"; -import RecentNotices from "./components/RecentNotices"; + +import PostList from "@/components/Post/PostList"; export default function NoticeEmployeePage() { + const { noticeInfo, recentNotices } = useLoaderData(); const { shopId, noticeId } = useParams() as { shopId: string; noticeId: string; @@ -13,14 +15,18 @@ export default function NoticeEmployeePage() { <>
- +

최근에 본 공고

- +
diff --git a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx index 8a5a5c6..1e68477 100644 --- a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx +++ b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx @@ -1,30 +1,24 @@ -import ShopInfoPostCardSkeleton from "./ShopInfoPostCardSkeleton"; - import { postApplication, putApplication, } from "@/apis/services/applicationService"; import Button from "@/components/Button"; import PostCard from "@/components/Post/PostCard"; -import useNoticeInfo from "@/hooks/useNoticeInfo"; +import { NoticeItem } from "@/types/notice"; import { cn } from "@/utils/cn"; import { isPastDate } from "@/utils/datetime"; interface NoticeDetailInfoProps { + noticeInfo: NoticeItem; shopId: string; noticeId: string; } -function NoticeDetailInfo({ shopId, noticeId }: NoticeDetailInfoProps) { - const { noticeInfo, isLoading: isLoadingNoticeInfo } = useNoticeInfo({ - shopId, - noticeId, - }); - - if (!noticeInfo?.shop || isLoadingNoticeInfo) { - return ; - } - +function NoticeDetailInfo({ + shopId, + noticeId, + noticeInfo, +}: NoticeDetailInfoProps) { const { hourlyPay, startsAt, @@ -40,10 +34,10 @@ function NoticeDetailInfo({ shopId, noticeId }: NoticeDetailInfoProps) { address1, originalHourlyPay, description: shopDescription, - } = noticeInfo.shop.item; + } = noticeInfo.shop!.item; - const applicationStatus = currentUserApplication?.item.status; const applicationId = currentUserApplication?.item.id; + const applicationStatus = currentUserApplication?.item.status; const isPast = isPastDate(startsAt, workhour); const isDisabledNotice = isPast || closed || applicationStatus === "canceled"; @@ -72,57 +66,51 @@ function NoticeDetailInfo({ shopId, noticeId }: NoticeDetailInfoProps) { return ( <> - {!isLoadingNoticeInfo && ( - <> -
- - 식당 - -

{name}

-
- {noticeInfo && ( - - {applicationStatus === "pending" && "취소하기"} - {applicationStatus === "accepted" && "승낙"} - {applicationStatus === "rejected" && "지원 거절"} - {applicationStatus === "canceled" && "지원 취소"} - {!applicationStatus && isDisabledNotice && "신청 불가"} - {!applicationStatus && !closed && !isPast && "지원하기"} - +
+ + 식당 + +

{name}

+
+ {noticeInfo && ( + - )} -
- 공고 설명 -

{description}

-
- + > + {applicationStatus === "pending" && "취소하기"} + {applicationStatus === "accepted" && "승낙"} + {applicationStatus === "rejected" && "지원 거절"} + {applicationStatus === "canceled" && "지원 취소"} + {!applicationStatus && isDisabledNotice && "신청 불가"} + {!applicationStatus && !closed && !isPast && "지원하기"} + + } + /> )} +
+ 공고 설명 +

{description}

+
); } diff --git a/src/pages/NoticeEmployeePage/components/RecentNotices.tsx b/src/pages/NoticeEmployeePage/components/RecentNotices.tsx deleted file mode 100644 index b8e0522..0000000 --- a/src/pages/NoticeEmployeePage/components/RecentNotices.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import PostList, { PostData } from "@/components/Post/PostList"; -import useNotices from "@/hooks/useNotices"; - -interface RecentNoticesProps { - shopId: string; - noticeId: string; -} - -function RecentNotices({ shopId, noticeId }: RecentNoticesProps) { - const { notices: recentNotices, isLoading: isLoadingRecentNotices } = - useNotices({ - dataFormatCallback: ({ items }) => - items.map(({ item }) => ({ - id: item.id, - name: item.shop?.item.name ?? "", - imageUrl: item.shop?.item.imageUrl ?? "", - address1: item.shop?.item.address1 ?? "", - originalHourlyPay: item.shop?.item.originalHourlyPay ?? 0, - link: `/notice/${item.shop?.item.id}/${item.id}/employee`, - hourlyPay: item.hourlyPay, - startsAt: item.startsAt, - workhour: item.workhour, - closed: item.closed, - })), - params: { sort: "hour", limit: 6 }, - deps: [shopId, noticeId], - }); - - return isLoadingRecentNotices ? ( -
로딩 중...
- ) : ( - - ); -} - -export default RecentNotices; diff --git a/src/pages/NoticeEmployeePage/loader/noticeEmployeeLoader.ts b/src/pages/NoticeEmployeePage/loader/noticeEmployeeLoader.ts new file mode 100644 index 0000000..a25318f --- /dev/null +++ b/src/pages/NoticeEmployeePage/loader/noticeEmployeeLoader.ts @@ -0,0 +1,53 @@ +import { LoaderFunction } from "react-router-dom"; + +import { getNotice, getNotices } from "@/apis/services/noticeService"; + +interface LoadNoticeParams { + shopId: string; + noticeId: string; +} + +const loadNotice = async ({ shopId, noticeId }: LoadNoticeParams) => { + const noticeResult = await getNotice(shopId, noticeId); + + if (noticeResult.status === 200) { + return noticeResult.data.item; + } +}; + +const loadRecentNotices = async () => { + const recentNoticesResult = await getNotices({ sort: "hour", limit: 6 }); + + if (recentNoticesResult.status === 200) { + const recentNotices = recentNoticesResult.data.items.map(({ item }) => ({ + id: item.id, + name: item.shop?.item.name ?? "", + imageUrl: item.shop?.item.imageUrl ?? "", + address1: item.shop?.item.address1 ?? "", + originalHourlyPay: item.shop?.item.originalHourlyPay ?? 0, + link: `/notice/${item.shop?.item.id}/${item.id}/employee`, + hourlyPay: item.hourlyPay, + startsAt: item.startsAt, + workhour: item.workhour, + closed: item.closed, + })); + + return recentNotices; + } +}; + +const noticeEmployeeLoader: LoaderFunction = async ({ params }) => { + const { shopId, noticeId } = params as { + shopId: string; + noticeId: string; + }; + + const [noticeInfo, recentNotices] = await Promise.all([ + loadNotice({ shopId, noticeId }), + loadRecentNotices(), + ]); + + return { noticeInfo, recentNotices }; +}; + +export default noticeEmployeeLoader; From 58dadaf015ef1aebbefaf7727f0303e41ccd5b52 Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Fri, 2 May 2025 19:00:33 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20hydrateFallbackElement=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=88=EB=A0=88=ED=86=A4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Router.tsx | 2 + .../components/ShopInfoPostCardSkeleton.tsx | 70 ++++++++++--------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/Router.tsx b/src/Router.tsx index c359799..bbed10c 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -2,6 +2,7 @@ import { lazy } from "react"; import { createBrowserRouter, RouteObject } from "react-router-dom"; +import ShopInfoPostCardSkeleton from "./pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton"; import noticeEmployeeLoader from "./pages/NoticeEmployeePage/loader/noticeEmployeeLoader"; import profileLoader from "./pages/ProfilePage/loader/profileLoader"; @@ -91,6 +92,7 @@ const noticeRoutes: RouteObject[] = [ path: ROUTES.NOTICE.NOTICE_ID.EMPLOYEE, Component: NoticeEmployeePage, loader: noticeEmployeeLoader, + hydrateFallbackElement: , }, ]; diff --git a/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx b/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx index 862bbd8..b1d66ed 100644 --- a/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx +++ b/src/pages/NoticeEmployeePage/components/ShopInfoPostCardSkeleton.tsx @@ -2,48 +2,50 @@ import { Location, Time } from "@/assets/icon"; function ShopInfoPostCardSkeleton() { return ( - <> -
- - 식당 - -

-

-
-
-
+
+
+
+ + 식당 + +

+
+
+
+
-
-
-
- - -
-
-
-
+
-
- 공고 설명 -

+

+ 공고 설명 +

+

- + ); } From 06723b9b89cf6b4f678c755075cca2d9a2d0b73c Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Sat, 3 May 2025 14:53:02 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=B7=A8=EC=86=8C=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=20=EA=B3=B5=EA=B3=A0=20=EB=AC=B8=EA=B5=AC=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 --- src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx index 1e68477..84fd572 100644 --- a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx +++ b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx @@ -100,7 +100,8 @@ function NoticeDetailInfo({ {applicationStatus === "pending" && "취소하기"} {applicationStatus === "accepted" && "승낙"} {applicationStatus === "rejected" && "지원 거절"} - {applicationStatus === "canceled" && "지원 취소"} + {applicationStatus === "canceled" && + "이미 취소한 지원 공고 입니다."} {!applicationStatus && isDisabledNotice && "신청 불가"} {!applicationStatus && !closed && !isPast && "지원하기"} From 26e62c66dbbe58874113f30cd44d8aff0d533c89 Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Sat, 3 May 2025 15:28:19 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=83=80=EC=9E=85=20export=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Post/PostList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Post/PostList.tsx b/src/components/Post/PostList.tsx index efda071..cd6b31a 100644 --- a/src/components/Post/PostList.tsx +++ b/src/components/Post/PostList.tsx @@ -1,6 +1,6 @@ import Post from "./Post"; -export interface PostData { +interface PostData { id: string; name: string; imageUrl: string; From 56fa956aba08e26482895270986fa5f0d170069b Mon Sep 17 00:00:00 2001 From: cozy-ito Date: Sat, 3 May 2025 18:48:43 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20Modal,=20Toast=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/NoticeDetailInfo.tsx | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx index 84fd572..b45c0be 100644 --- a/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx +++ b/src/pages/NoticeEmployeePage/components/NoticeDetailInfo.tsx @@ -4,6 +4,8 @@ import { } from "@/apis/services/applicationService"; import Button from "@/components/Button"; import PostCard from "@/components/Post/PostCard"; +import { useToast } from "@/hooks/useToast"; +import { useModalStore } from "@/store/useModalStore"; import { NoticeItem } from "@/types/notice"; import { cn } from "@/utils/cn"; import { isPastDate } from "@/utils/datetime"; @@ -19,6 +21,9 @@ function NoticeDetailInfo({ noticeId, noticeInfo, }: NoticeDetailInfoProps) { + const { openModal } = useModalStore(); + const { showToast } = useToast(); + const { hourlyPay, startsAt, @@ -45,23 +50,30 @@ function NoticeDetailInfo({ const result = await postApplication(shopId, noticeId); if (result.status === 201) { - // Modal 병합 후 모달로 변경 예정 - alert("신청이 완료 되었습니다."); + showToast("신청 완료!"); } }; - const cancelApplication = async () => { - const result = await putApplication( - shopId, - noticeId, - applicationId ?? "", - "canceled", - ); + const cancelApplication = () => { + openModal({ + type: "confirm", + confirmText: "취소하기", + cancelText: "아니오", + iconType: "warning", + message: "신청을 취소하시겠어요?", + onConfirm: async () => { + const result = await putApplication( + shopId, + noticeId, + applicationId ?? "", + "canceled", + ); - if (result.status === 200) { - // Modal 병합 후 모달로 변경 예정 - alert("취소가 완료 되었습니다."); - } + if (result.status === 200) { + showToast("취소가 완료 되었습니다."); + } + }, + }); }; return (