diff --git a/src/components/features/index.ts b/src/components/features/index.ts
index 17b0d29..401c83d 100644
--- a/src/components/features/index.ts
+++ b/src/components/features/index.ts
@@ -1 +1 @@
-export { NoticeListSection, RecentNoticeList } from './noticeList';
+export { CustomNotice, NoticeEmpty, NoticeListSection, RecentNoticeList } from './noticeList';
diff --git a/src/components/features/noticeList/customNotice.tsx b/src/components/features/noticeList/customNotice.tsx
new file mode 100644
index 0000000..bc5f093
--- /dev/null
+++ b/src/components/features/noticeList/customNotice.tsx
@@ -0,0 +1,42 @@
+import { Container, HorizontalScroll } from '@/components/layout';
+import { Post, SkeletonUI } from '@/components/ui';
+import useAuth from '@/hooks/useAuth';
+import useCustomNotices from './hooks/useCustomNotices';
+
+const CustomNoticeList = () => {
+ const { user } = useAuth();
+ const { notices, isLoading, error } = useCustomNotices(user?.address);
+
+ if (error) {
+ return
{error}
;
+ }
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+ {notices.map(notice => (
+
+
+
+ ))}
+
+ )}
+ >
+ );
+};
+
+const CustomNotice = () => (
+
+
+ 맞춤공고
+
+
+
+);
+export default CustomNotice;
diff --git a/src/components/features/noticeList/hooks/useCustomNotices.ts b/src/components/features/noticeList/hooks/useCustomNotices.ts
new file mode 100644
index 0000000..c83ef17
--- /dev/null
+++ b/src/components/features/noticeList/hooks/useCustomNotices.ts
@@ -0,0 +1,55 @@
+import useAsync from '@/hooks/useAsync';
+import axiosInstance from '@/lib/axios';
+import { paramsSerializer } from '@/lib/utils/paramsSerializer';
+import { toPostCard } from '@/lib/utils/parse';
+import { NoticeQuery } from '@/types/api';
+import { PostCard } from '@/types/notice';
+import { useCallback, useEffect } from 'react';
+
+const useCustomNotices = (address?: string) => {
+ const { data, isLoading, error, fetch } = useAsync();
+
+ const fetchCustom = useCallback(async () => {
+ const now = new Date();
+ now.setSeconds(now.getSeconds() + 15);
+
+ // 기본 쿼리
+ const baseQuery: NoticeQuery = {
+ sort: 'time',
+ startsAtGte: now.toISOString(),
+ limit: 3,
+ };
+
+ const firstQuery: NoticeQuery = address ? { ...baseQuery, address: [address] } : baseQuery;
+ const getCustom = axiosInstance
+ .get('/notices', {
+ params: firstQuery,
+ paramsSerializer: { serialize: paramsSerializer },
+ })
+ .then(async res => {
+ const items = res.data.items.map(toPostCard);
+ if (items.length === 0) {
+ const fallbackRes = await axiosInstance.get('/notices', {
+ params: baseQuery,
+ paramsSerializer: { serialize: paramsSerializer },
+ });
+ return fallbackRes.data.items.map(toPostCard);
+ }
+ return items;
+ });
+
+ await fetch(getCustom);
+ }, [address, fetch]);
+
+ useEffect(() => {
+ fetchCustom();
+ }, []);
+
+ return {
+ notices: data ?? [],
+ isLoading,
+ error,
+ fetchCustom,
+ };
+};
+export default useCustomNotices;
diff --git a/src/components/features/noticeList/hooks/useNotices.ts b/src/components/features/noticeList/hooks/useNotices.ts
new file mode 100644
index 0000000..7801941
--- /dev/null
+++ b/src/components/features/noticeList/hooks/useNotices.ts
@@ -0,0 +1,86 @@
+import useAsync from '@/hooks/useAsync';
+import axiosInstance from '@/lib/axios';
+import { paramsSerializer } from '@/lib/utils/paramsSerializer';
+import { toPostCard } from '@/lib/utils/parse';
+import { NoticeQuery, PaginatedResponse } from '@/types/api';
+import { PostCard } from '@/types/notice';
+import { useCallback, useState } from 'react';
+
+const INIT_FILTER_DATA: NoticeQuery = {
+ sort: 'time',
+};
+
+const useNotices = (initialQuery: Partial = {}) => {
+ const { data, isLoading, isInitialized, error, fetch } = useAsync();
+ const [filters, setFiltersState] = useState(INIT_FILTER_DATA);
+ const [pagination, setPagination] = useState({
+ offset: 0,
+ limit: 6,
+ count: 0,
+ hasNext: false,
+ });
+
+ const changeTimeFilter = useCallback((q: Partial): Partial => {
+ const now = new Date();
+ now.setSeconds(now.getSeconds() + 15); // 서버 시간 오차 대비
+
+ // startsAtGte가 없거나, 현재보다 과거면 현재 시각으로 보정
+ if (!q.startsAtGte || new Date(q.startsAtGte) < now) {
+ return { ...q, startsAtGte: now.toISOString() };
+ }
+
+ return q;
+ }, []);
+
+ const fetchNotices = useCallback(
+ async (query?: Partial) => {
+ // 검색 필터 업데이트
+ const mergedFilter: NoticeQuery = {
+ ...filters, // 내부 초기값
+ limit: pagination.limit,
+ offset: pagination.offset,
+ ...initialQuery, // 외부 초기값
+ ...(query ?? {}), // fetchNotices 호출 시 추가 값
+ };
+ const queryUpdate = changeTimeFilter(mergedFilter) as NoticeQuery;
+ // 상태에도 반영하여 UI와 동기화
+
+ setFiltersState(prev => ({ ...prev, ...queryUpdate }));
+ // 필터기반 패치
+ const getNotices = axiosInstance
+ .get('/notices', {
+ params: queryUpdate,
+ paramsSerializer: { serialize: paramsSerializer },
+ })
+ .then(res => {
+ setPagination({
+ offset: res.data.offset,
+ limit: res.data.limit,
+ count: res.data.count,
+ hasNext: res.data.hasNext,
+ });
+ return res.data.items.map(toPostCard);
+ });
+
+ await fetch(getNotices);
+ },
+ [initialQuery, fetch, filters, changeTimeFilter]
+ );
+
+ const reset = useCallback(() => {
+ setFiltersState(INIT_FILTER_DATA);
+ fetchNotices(INIT_FILTER_DATA);
+ }, []);
+
+ return {
+ notices: data ?? [],
+ pagination,
+ isLoading,
+ isInitialized,
+ error,
+ fetchNotices,
+ filters,
+ reset,
+ };
+};
+export default useNotices;
diff --git a/src/components/features/noticeList/hooks/useRecentNotice.ts b/src/components/features/noticeList/hooks/useRecentNotice.ts
index a508dbc..7fb2efc 100644
--- a/src/components/features/noticeList/hooks/useRecentNotice.ts
+++ b/src/components/features/noticeList/hooks/useRecentNotice.ts
@@ -6,8 +6,6 @@ const RECENT_KEY = 'thejulge_recent';
// 최근 본 공고 저장
export const useRecentNotice = (notice: NoticeCard) => {
const handleRecentNotice = useCallback(() => {
- if (!notice) return;
-
const current: RecentNotice = {
id: notice.id,
shopId: notice.shopId,
@@ -42,7 +40,7 @@ export const useRecentNotice = (notice: NoticeCard) => {
};
// 최근 본 공고 불러오기
-export function useRecentNoticeList() {
+export const useRecentNoticeList = () => {
const [recentNotices, setRecentNotices] = useState([]);
useEffect(() => {
@@ -54,4 +52,4 @@ export function useRecentNoticeList() {
}, []);
return { recentNotices };
-}
+};
diff --git a/src/components/features/noticeList/index.ts b/src/components/features/noticeList/index.ts
index 3129276..7f6a357 100644
--- a/src/components/features/noticeList/index.ts
+++ b/src/components/features/noticeList/index.ts
@@ -1,3 +1,4 @@
-export { default as NoticeListSection } from './noticeListSection';
-
+export { default as CustomNotice } from './customNotice';
export { default as RecentNoticeList } from './recentNoticeList';
+export { default as NoticeListSection } from './noticeListSection';
+export { default as NoticeEmpty } from './noticeEmpty';
diff --git a/src/components/features/noticeList/noticeEmpty.tsx b/src/components/features/noticeList/noticeEmpty.tsx
index f0caac8..37fb264 100644
--- a/src/components/features/noticeList/noticeEmpty.tsx
+++ b/src/components/features/noticeList/noticeEmpty.tsx
@@ -25,7 +25,7 @@ const NoticeEmpty = ({ q, onReset }: NoticeEmptyProps) => {
홈으로 돌아가기
) : (
-