From ab8b20574c3cd602b60937ac16da02486c74d5a4 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:19:47 +0900 Subject: [PATCH 01/13] =?UTF-8?q?Feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=A7=88=ED=81=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/layout.css.ts | 40 +++++++++++++++++++++++++++++++++++++ src/app/admin/layout.tsx | 23 +++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/app/admin/layout.css.ts create mode 100644 src/app/admin/layout.tsx diff --git a/src/app/admin/layout.css.ts b/src/app/admin/layout.css.ts new file mode 100644 index 00000000..30b75e36 --- /dev/null +++ b/src/app/admin/layout.css.ts @@ -0,0 +1,40 @@ +import { style } from '@vanilla-extract/css'; +import { Header, BodyRegular } from '@/styles/font.css'; +import { vars } from '@/styles/theme.css'; + +export const container = style({ + height: '100vh', + display: 'flex', +}); + +export const nav = style({ + padding: '1.5rem', + + display: 'flex', + flexDirection: 'column', + gap: '3rem', + + borderRight: '2px solid', + borderRightColor: vars.color.bluegray6, +}); + +export const title = style([ + Header, + { + color: vars.color.bluegray8, + }, +]); + +export const menu = style([ + BodyRegular, + { + width: 200, + display: 'flex', + flexDirection: 'column', + gap: '2rem', + }, +]); + +export const main = style({ + flexGrow: 1, +}); diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx new file mode 100644 index 00000000..fcb7940f --- /dev/null +++ b/src/app/admin/layout.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; + +import * as styles from './layout.css'; +import Link from 'next/link'; + +interface AdminNoticeLayoutProps { + children: ReactNode; +} + +export default function AdminNoticeLayout({ children }: AdminNoticeLayoutProps) { + return ( +
+ +
{children}
+
+ ); +} From 1f67e5da7d57297208923178e55f2cb78f322fa3 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:33:35 +0900 Subject: [PATCH 02/13] =?UTF-8?q?Feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EC=97=B0=EB=8F=99=20=EB=B0=8F=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_api/notice/getAdminNotices.ts | 10 ++++++ src/app/admin/notice/page.tsx | 18 +++++++++++ src/lib/constants/queryKeys.ts | 32 +++++++++++++++----- src/lib/types/noticeType.ts | 42 +++++++++++++------------- 4 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 src/app/_api/notice/getAdminNotices.ts create mode 100644 src/app/admin/notice/page.tsx diff --git a/src/app/_api/notice/getAdminNotices.ts b/src/app/_api/notice/getAdminNotices.ts new file mode 100644 index 00000000..4799f6ed --- /dev/null +++ b/src/app/_api/notice/getAdminNotices.ts @@ -0,0 +1,10 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { AdminNoticeType } from '@/lib/types/noticeType'; + +const getAdminNotices = async () => { + const result = await axiosInstance.get('/admin/notices'); + + return result.data; +}; + +export default getAdminNotices; diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx new file mode 100644 index 00000000..646af868 --- /dev/null +++ b/src/app/admin/notice/page.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; + +import getAdminNotices from '@/app/_api/notice/getAdminNotices'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import { AdminNoticeType } from '@/lib/types/noticeType'; + +export default function AdminNoticesPage() { + const { data: notices } = useQuery({ + queryKey: [QUERY_KEYS.getAdminAllNotice], + queryFn: getAdminNotices, + }); + + console.log(notices); // 삭제 + + return
공지
; +} diff --git a/src/lib/constants/queryKeys.ts b/src/lib/constants/queryKeys.ts index 9b2d2eaa..de797b9b 100644 --- a/src/lib/constants/queryKeys.ts +++ b/src/lib/constants/queryKeys.ts @@ -1,36 +1,54 @@ export const QUERY_KEYS = { + // 사용자 userOne: 'userOne', + getDefaultBackgroundImages: 'getDefaultBackgroundImages', + getDefaultProfileImages: 'getDefaultProfileImages', + getRecommendedUsers: 'getRecommendedUsers', + + // 리스트 createList: 'createList', uploadImage: 'uploadImage', getAllList: 'getAllList', getListDetail: 'getListDetail', getCategories: 'getCategories', - getComments: 'getComments', getRecommendedLists: 'getRecommendedLists', getRecentLists: 'getRecentLists', getFollowingLists: 'getFollowingLists', - getRecommendedUsers: 'getRecommendedUsers', getTrendingLists: 'getTrendingLists', + getComments: 'getComments', + + // 알림 getNotificationAllChecked: 'getNotificationOnAllChecked', + notifications: 'notifications', + + // 팔로우 follow: 'follow', deleteFollow: 'deleteFollow', deleteFollower: 'deleteFollower', getFollowingList: 'getFollowingList', getFollowerList: 'getFollowerList', - getUsersByNicknameSearch: 'getUsersByNicknameSearch', - notifications: 'notifications', + + // 히스토리 getHistories: 'getHistories', toggleHistoryPublic: 'toggleHistoryPublic', deleteHistory: 'deleteHistory', - getDefaultBackgroundImages: 'getDefaultBackgroundImages', - getDefaultProfileImages: 'getDefaultProfileImages', + + // 검색 searchListResult: 'searchListResult', searchUserResult: 'searchUserResult', + getUsersByNicknameSearch: 'getUsersByNicknameSearch', + + // 콜렉션 collect: 'collect', getCollection: 'getCollection', getFolders: 'getFolders', getCollectionCategories: 'getCollectionCategories', // ver2.0 - getAdminTopics: 'getAdminTopics', + + // 요청주제 getTopics: 'getTopics', + + // 어드민 + getAdminTopics: 'getAdminTopics', getNoticeCategories: 'getNoticeCategories', + getAdminAllNotice: 'getAdminAllNotice', }; diff --git a/src/lib/types/noticeType.ts b/src/lib/types/noticeType.ts index 9bc4ac47..0525a350 100644 --- a/src/lib/types/noticeType.ts +++ b/src/lib/types/noticeType.ts @@ -20,34 +20,34 @@ export interface NoticeCreateType { contents: ItemsType[]; } -export interface NoticeListItemType { - id: number; - createdDate: string; - title: string; - itemImageUrl: string | null; - category: string; - description: string; -} +// 어드민 게시물 조회 +export type AdminNoticeType = Omit & { + isExposed: boolean; + didSendAlarm: boolean; +}; -export interface NoticeDetailType { +// 게시물 타입 +interface NoticeType { id: number; - category: string; + category: (typeof NOTICE_CATEGORY_NAME)[keyof typeof NOTICE_CATEGORY_NAME]; title: string; description: string; - content: NoticeContentType[]; createdDate: string; - prevNotice: { - id: number; - title: string; - description: string; - }; - nextNotice: { - id: number; - title: string; - description: string; - }; } +// 게시물 리스트 조회 +export type NoticeListItemType = NoticeType & { + itemImageUrl: string | null; +}; + +// 게시물 상세 조회 +export type NoticeDetailType = NoticeType & { + content: NoticeContentType[]; + prevNotice: Partial; + nextNotice: Partial; +}; + +// 게시물 내용(콘텐츠) export interface NoticeContentType { [key: string]: unknown; type: NoticeContentsType; From 0ff78a5a26607b21850587384feb96cb4d564915 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:14:29 +0900 Subject: [PATCH 03/13] =?UTF-8?q?Design:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=A7=88=ED=81=AC=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/layout.css.ts | 2 +- src/app/admin/notice/page.css.ts | 69 ++++++++++++++++++++++++++++++++ src/app/admin/notice/page.tsx | 57 ++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/app/admin/notice/page.css.ts diff --git a/src/app/admin/layout.css.ts b/src/app/admin/layout.css.ts index 30b75e36..e2c003a5 100644 --- a/src/app/admin/layout.css.ts +++ b/src/app/admin/layout.css.ts @@ -3,7 +3,7 @@ import { Header, BodyRegular } from '@/styles/font.css'; import { vars } from '@/styles/theme.css'; export const container = style({ - height: '100vh', + minHeight: '100vh', display: 'flex', }); diff --git a/src/app/admin/notice/page.css.ts b/src/app/admin/notice/page.css.ts new file mode 100644 index 00000000..1e3b8739 --- /dev/null +++ b/src/app/admin/notice/page.css.ts @@ -0,0 +1,69 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { BodyRegular, Label } from '@/styles/font.css'; + +export const page = style({ + padding: '1.5rem', + height: '100%', +}); + +export const table = style({ + maxWidth: '850px', + padding: '1rem', + + display: 'flex', + flexDirection: 'column', + gap: '1rem', + + backgroundColor: vars.color.white, + borderRadius: '8px', +}); + +export const headRow = style([ + BodyRegular, + { + padding: '1rem 0.5rem', + + display: 'grid', + gridTemplateColumns: 'repeat(8, 1fr)', + alignItems: 'center', + + textAlign: 'center', + }, +]); + +export const bodyRow = style([ + Label, + { + padding: '1rem 0.5rem', + marginBottom: '1rem', + borderBottom: `1px solid ${vars.color.bluegray6}`, + + display: 'grid', + gridTemplateColumns: 'repeat(8, 1fr)', + alignItems: 'center', + + textAlign: 'center', + }, +]); + +export const rowItem = style({ + gridColumn: 'span 2', + + display: 'flex', + flexDirection: 'column', + gap: '0.5rem', +}); + +export const button = style({ + padding: '0.5rem 1rem', + borderRadius: '4px', + backgroundColor: vars.color.blue, + color: vars.color.white, +}); + +export const editButtons = style({ + display: 'flex', + justifyContent: 'center', + gap: '0.5rem', +}); diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx index 646af868..a7c84490 100644 --- a/src/app/admin/notice/page.tsx +++ b/src/app/admin/notice/page.tsx @@ -2,9 +2,47 @@ import { useQuery } from '@tanstack/react-query'; +import * as styles from './page.css'; + import getAdminNotices from '@/app/_api/notice/getAdminNotices'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { AdminNoticeType } from '@/lib/types/noticeType'; +import formatDate from '@/lib/utils/dateFormat'; + +const TABLE_ROW = ['일시', '카테고리', '제목&소개', '편집', '미리보기', '알림', '공개']; + +interface NoticeItemProps { + notice: AdminNoticeType; +} + +function NoticeItem({ notice }: NoticeItemProps) { + return ( + + {formatDate(notice.createdDate)} + {notice.category} + + {notice.title} + {notice.description} + + + + + + + + + + + + + + + + ); +} export default function AdminNoticesPage() { const { data: notices } = useQuery({ @@ -12,7 +50,20 @@ export default function AdminNoticesPage() { queryFn: getAdminNotices, }); - console.log(notices); // 삭제 - - return
공지
; + return ( +
+ + + + {TABLE_ROW.map((item, index) => ( + + ))} + + + {notices?.map((notice) => )} +
+ {item} +
+
+ ); } From 65781b011280f95a84bd4f108b11a5fe9b9686c0 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:45:38 +0900 Subject: [PATCH 04/13] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_api/notice/deleteNotice.ts | 7 +++++++ src/app/admin/notice/page.css.ts | 10 ++++++++++ src/app/admin/notice/page.tsx | 26 ++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 src/app/_api/notice/deleteNotice.ts diff --git a/src/app/_api/notice/deleteNotice.ts b/src/app/_api/notice/deleteNotice.ts new file mode 100644 index 00000000..9ebcda99 --- /dev/null +++ b/src/app/_api/notice/deleteNotice.ts @@ -0,0 +1,7 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +const deleteNotice = async (noticeId: number) => { + await axiosInstance.delete(`/admin/notices/${noticeId}`); +}; + +export default deleteNotice; diff --git a/src/app/admin/notice/page.css.ts b/src/app/admin/notice/page.css.ts index 1e3b8739..f5c56bf5 100644 --- a/src/app/admin/notice/page.css.ts +++ b/src/app/admin/notice/page.css.ts @@ -55,11 +55,21 @@ export const rowItem = style({ gap: '0.5rem', }); +export const rowText = style({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + export const button = style({ padding: '0.5rem 1rem', borderRadius: '4px', backgroundColor: vars.color.blue, color: vars.color.white, + + ':hover': { + opacity: 0.7, + }, }); export const editButtons = style({ diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx index a7c84490..16191423 100644 --- a/src/app/admin/notice/page.tsx +++ b/src/app/admin/notice/page.tsx @@ -1,10 +1,12 @@ 'use client'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import * as styles from './page.css'; import getAdminNotices from '@/app/_api/notice/getAdminNotices'; +import deleteNotice from '@/app/_api/notice/deleteNotice'; + import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { AdminNoticeType } from '@/lib/types/noticeType'; import formatDate from '@/lib/utils/dateFormat'; @@ -16,17 +18,32 @@ interface NoticeItemProps { } function NoticeItem({ notice }: NoticeItemProps) { + const queryClient = useQueryClient(); + + const deleteMutation = useMutation({ + mutationFn: deleteNotice, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); + }, + }); + + const handleDeleteNotice = () => { + deleteMutation.mutate(notice.id); + }; + return ( {formatDate(notice.createdDate)} {notice.category} - {notice.title} - {notice.description} + {notice.title} + {notice.description} - + @@ -48,6 +65,7 @@ export default function AdminNoticesPage() { const { data: notices } = useQuery({ queryKey: [QUERY_KEYS.getAdminAllNotice], queryFn: getAdminNotices, + staleTime: 1000 * 60 * 30, }); return ( From 30a21824a0442fd38618bc41744b3a089695d296 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:34:23 +0900 Subject: [PATCH 05/13] =?UTF-8?q?Feat:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=B3=B4=EB=82=B4=EA=B8=B0,=20=EA=B3=B5?= =?UTF-8?q?=EA=B0=9C/=EB=B9=84=EA=B3=B5=EA=B0=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_api/notice/sendNoticeAlarm.ts | 7 ++++ src/app/_api/notice/updateNoticePublic.ts | 7 ++++ src/app/admin/notice/page.css.ts | 12 +++---- src/app/admin/notice/page.tsx | 40 +++++++++++++++++++++-- 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 src/app/_api/notice/sendNoticeAlarm.ts create mode 100644 src/app/_api/notice/updateNoticePublic.ts diff --git a/src/app/_api/notice/sendNoticeAlarm.ts b/src/app/_api/notice/sendNoticeAlarm.ts new file mode 100644 index 00000000..d4720e5f --- /dev/null +++ b/src/app/_api/notice/sendNoticeAlarm.ts @@ -0,0 +1,7 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +const sendNoticeAlarm = async (noticeId: number) => { + await axiosInstance.post(`/admin/notices/${noticeId}/alarm`); +}; + +export default sendNoticeAlarm; diff --git a/src/app/_api/notice/updateNoticePublic.ts b/src/app/_api/notice/updateNoticePublic.ts new file mode 100644 index 00000000..7e3b656b --- /dev/null +++ b/src/app/_api/notice/updateNoticePublic.ts @@ -0,0 +1,7 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +const updateNoticePublic = async (noticeId: number) => { + await axiosInstance.patch(`/admin/notices/${noticeId}`); +}; + +export default updateNoticePublic; diff --git a/src/app/admin/notice/page.css.ts b/src/app/admin/notice/page.css.ts index f5c56bf5..ccbf73ab 100644 --- a/src/app/admin/notice/page.css.ts +++ b/src/app/admin/notice/page.css.ts @@ -61,6 +61,12 @@ export const rowText = style({ whiteSpace: 'nowrap', }); +export const buttons = style({ + display: 'flex', + justifyContent: 'center', + gap: '0.5rem', +}); + export const button = style({ padding: '0.5rem 1rem', borderRadius: '4px', @@ -71,9 +77,3 @@ export const button = style({ opacity: 0.7, }, }); - -export const editButtons = style({ - display: 'flex', - justifyContent: 'center', - gap: '0.5rem', -}); diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx index 16191423..b4dad268 100644 --- a/src/app/admin/notice/page.tsx +++ b/src/app/admin/notice/page.tsx @@ -6,6 +6,8 @@ import * as styles from './page.css'; import getAdminNotices from '@/app/_api/notice/getAdminNotices'; import deleteNotice from '@/app/_api/notice/deleteNotice'; +import sendNoticeAlarm from '@/app/_api/notice/sendNoticeAlarm'; +import updateNoticePublic from '@/app/_api/notice/updateNoticePublic'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { AdminNoticeType } from '@/lib/types/noticeType'; @@ -27,10 +29,40 @@ function NoticeItem({ notice }: NoticeItemProps) { }, }); + const sendAlarmMutation = useMutation({ + mutationFn: sendNoticeAlarm, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); + }, + }); + + const updatePublicMutation = useMutation({ + mutationFn: updateNoticePublic, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); + }, + }); + const handleDeleteNotice = () => { deleteMutation.mutate(notice.id); }; + const handleSendAlarm = () => { + if (!notice.isExposed) { + alert('공개 게시물만 알림을 보낼 수 있어요.'); + return; + } + if (notice.didSendAlarm) { + alert('이미 알림을 보낸 게시물입니다.'); + return; + } + sendAlarmMutation.mutate(notice.id); + }; + + const handleTogglePublic = () => { + updatePublicMutation.mutate(notice.id); + }; + return ( {formatDate(notice.createdDate)} @@ -39,7 +71,7 @@ function NoticeItem({ notice }: NoticeItemProps) { {notice.title} {notice.description} - + - + - From 160b9b5818666e1ff855ce20c86c2384c0416bf8 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:48:44 +0900 Subject: [PATCH 06/13] =?UTF-8?q?Refactor:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EB=A6=AC=EC=95=A1=ED=8A=B8=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20mutation=20=EB=A1=9C=EC=A7=81=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=EC=9D=84=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/notice/page.tsx | 36 ++++++------------------------- src/hooks/queries/useNotice.ts | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 src/hooks/queries/useNotice.ts diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx index b4dad268..e6bc65a2 100644 --- a/src/app/admin/notice/page.tsx +++ b/src/app/admin/notice/page.tsx @@ -1,18 +1,17 @@ 'use client'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import * as styles from './page.css'; import getAdminNotices from '@/app/_api/notice/getAdminNotices'; -import deleteNotice from '@/app/_api/notice/deleteNotice'; -import sendNoticeAlarm from '@/app/_api/notice/sendNoticeAlarm'; -import updateNoticePublic from '@/app/_api/notice/updateNoticePublic'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { AdminNoticeType } from '@/lib/types/noticeType'; import formatDate from '@/lib/utils/dateFormat'; +import useNotice from '@/hooks/queries/useNotice'; + const TABLE_ROW = ['일시', '카테고리', '제목&소개', '편집', '미리보기', '알림', '공개']; interface NoticeItemProps { @@ -20,31 +19,10 @@ interface NoticeItemProps { } function NoticeItem({ notice }: NoticeItemProps) { - const queryClient = useQueryClient(); - - const deleteMutation = useMutation({ - mutationFn: deleteNotice, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); - }, - }); - - const sendAlarmMutation = useMutation({ - mutationFn: sendNoticeAlarm, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); - }, - }); - - const updatePublicMutation = useMutation({ - mutationFn: updateNoticePublic, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); - }, - }); + const { deletNoticeMutation, sendNoticeAlarmMutation, updateNoticePublicMutation } = useNotice(); const handleDeleteNotice = () => { - deleteMutation.mutate(notice.id); + deletNoticeMutation.mutate(notice.id); }; const handleSendAlarm = () => { @@ -56,11 +34,11 @@ function NoticeItem({ notice }: NoticeItemProps) { alert('이미 알림을 보낸 게시물입니다.'); return; } - sendAlarmMutation.mutate(notice.id); + sendNoticeAlarmMutation.mutate(notice.id); }; const handleTogglePublic = () => { - updatePublicMutation.mutate(notice.id); + updateNoticePublicMutation.mutate(notice.id); }; return ( diff --git a/src/hooks/queries/useNotice.ts b/src/hooks/queries/useNotice.ts new file mode 100644 index 00000000..0c040fb4 --- /dev/null +++ b/src/hooks/queries/useNotice.ts @@ -0,0 +1,39 @@ +'use client'; + +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'; + +import deleteNotice from '@/app/_api/notice/deleteNotice'; +import sendNoticeAlarm from '@/app/_api/notice/sendNoticeAlarm'; +import updateNoticePublic from '@/app/_api/notice/updateNoticePublic'; + +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; + +const useNoticeMutation = ( + mutationFn: (variables: TVariables) => Promise, + mutationOptions?: Omit, 'mutationFn'> +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getAdminAllNotice] }); + }, + ...mutationOptions, + }); +}; + +// React-query의 useMutation을 wrapping해주는 Notice 관련 custom hook +const useNotice = () => { + const deletNoticeMutation = useNoticeMutation(deleteNotice); + const sendNoticeAlarmMutation = useNoticeMutation(sendNoticeAlarm); + const updateNoticePublicMutation = useNoticeMutation(updateNoticePublic); + + return { + deletNoticeMutation, + sendNoticeAlarmMutation, + updateNoticePublicMutation, + }; +}; + +export default useNotice; From 5338c934dc5c606082c1d50e4ef4d5631d82f969 Mon Sep 17 00:00:00 2001 From: Sohyun Park <124856726+ParkSohyunee@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:03:25 +0900 Subject: [PATCH 07/13] =?UTF-8?q?Feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_api/notice/getNoticeDetail.ts | 10 ++ .../notice/_components/NoticeItem.css.ts | 55 +++++++++ .../admin/notice/_components/NoticeItem.tsx | 104 ++++++++++++++++++ src/app/admin/notice/page.css.ts | 40 +------ src/app/admin/notice/page.tsx | 63 +---------- src/app/notices/[noticeId]/NoticeDetail.tsx | 2 +- src/components/Modal/Modal.css.ts | 1 + .../NoticeDetail/NoticeDetailContents.css.ts | 57 ++++++++++ .../NoticeDetail/NoticeDetailContents.tsx | 60 ++++++++++ .../NoticeDetail/NoticeDetailInfo.css.ts | 56 ++++++++++ .../NoticeDetail/NoticeDetailInfo.tsx | 29 +++++ src/lib/constants/queryKeys.ts | 5 +- src/lib/types/noticeType.ts | 2 +- 13 files changed, 380 insertions(+), 104 deletions(-) create mode 100644 src/app/_api/notice/getNoticeDetail.ts create mode 100644 src/app/admin/notice/_components/NoticeItem.css.ts create mode 100644 src/app/admin/notice/_components/NoticeItem.tsx create mode 100644 src/components/NoticeDetail/NoticeDetailContents.css.ts create mode 100644 src/components/NoticeDetail/NoticeDetailContents.tsx create mode 100644 src/components/NoticeDetail/NoticeDetailInfo.css.ts create mode 100644 src/components/NoticeDetail/NoticeDetailInfo.tsx diff --git a/src/app/_api/notice/getNoticeDetail.ts b/src/app/_api/notice/getNoticeDetail.ts new file mode 100644 index 00000000..a7813216 --- /dev/null +++ b/src/app/_api/notice/getNoticeDetail.ts @@ -0,0 +1,10 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { NoticeDetailType } from '@/lib/types/noticeType'; + +const getNoticeDetail = async (noticeId: number) => { + const result = await axiosInstance.get(`/notices/${noticeId}`); + + return result.data; +}; + +export default getNoticeDetail; diff --git a/src/app/admin/notice/_components/NoticeItem.css.ts b/src/app/admin/notice/_components/NoticeItem.css.ts new file mode 100644 index 00000000..48d3a0e3 --- /dev/null +++ b/src/app/admin/notice/_components/NoticeItem.css.ts @@ -0,0 +1,55 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { Label } from '@/styles/font.css'; + +export const bodyRow = style([ + Label, + { + padding: '1rem 0.5rem', + marginBottom: '1rem', + borderBottom: `1px solid ${vars.color.bluegray6}`, + + display: 'grid', + gridTemplateColumns: 'repeat(8, 1fr)', + alignItems: 'center', + + textAlign: 'center', + }, +]); + +export const rowItem = style({ + gridColumn: 'span 2', + + display: 'flex', + flexDirection: 'column', + gap: '0.5rem', +}); + +export const rowText = style({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +export const buttons = style({ + display: 'flex', + justifyContent: 'center', + gap: '0.5rem', +}); + +export const button = style({ + padding: '0.5rem 1rem', + borderRadius: '4px', + backgroundColor: vars.color.blue, + color: vars.color.white, + + ':hover': { + opacity: 0.7, + }, +}); + +export const modal = style({ + width: '100%', + height: '100vh', + overflow: 'scroll', +}); diff --git a/src/app/admin/notice/_components/NoticeItem.tsx b/src/app/admin/notice/_components/NoticeItem.tsx new file mode 100644 index 00000000..573fbed1 --- /dev/null +++ b/src/app/admin/notice/_components/NoticeItem.tsx @@ -0,0 +1,104 @@ +import { useQuery } from '@tanstack/react-query'; + +import * as styles from './NoticeItem.css'; + +import useNotice from '@/hooks/queries/useNotice'; +import useBooleanOutput from '@/hooks/useBooleanOutput'; + +import Modal from '@/components/Modal/Modal'; +import NoticeDetailInfo from '@/components/NoticeDetail/NoticeDetailInfo'; + +import { AdminNoticeType, NoticeDetailType } from '@/lib/types/noticeType'; +import formatDate from '@/lib/utils/dateFormat'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; + +import getNoticeDetail from '@/app/_api/notice/getNoticeDetail'; + +interface NoticeDetailModalProps { + noticeId: number; +} + +function NoticeDetailModal({ noticeId }: NoticeDetailModalProps) { + const { data: notices } = useQuery({ + queryKey: [QUERY_KEYS.getNoticeDetail], + queryFn: () => getNoticeDetail(noticeId), + staleTime: 1000 * 60 * 30, + enabled: !!noticeId, + }); + + return <>{notices && }; +} + +interface NoticeItemProps { + notice: AdminNoticeType; +} + +function NoticeItem({ notice }: NoticeItemProps) { + const { deletNoticeMutation, sendNoticeAlarmMutation, updateNoticePublicMutation } = useNotice(); + const { isOn, handleSetOn, handleSetOff } = useBooleanOutput(); + + const handleDeleteNotice = () => { + deletNoticeMutation.mutate(notice.id); + }; + + const handleSendAlarm = () => { + if (!notice.isExposed) { + alert('공개 게시물만 알림을 보낼 수 있어요.'); + return; + } + if (notice.didSendAlarm) { + alert('이미 알림을 보낸 게시물입니다.'); + return; + } + sendNoticeAlarmMutation.mutate(notice.id); + }; + + const handleTogglePublic = () => { + updateNoticePublicMutation.mutate(notice.id); + }; + + return ( + <> + + {formatDate(notice.createdDate)} + {notice.category} + + {notice.title} + {notice.description} + + + + + + + + + + + + + + + + + {isOn && ( + +
+ +
+
+ )} + + ); +} + +export default NoticeItem; diff --git a/src/app/admin/notice/page.css.ts b/src/app/admin/notice/page.css.ts index ccbf73ab..7547106a 100644 --- a/src/app/admin/notice/page.css.ts +++ b/src/app/admin/notice/page.css.ts @@ -1,6 +1,6 @@ import { style } from '@vanilla-extract/css'; import { vars } from '@/styles/theme.css'; -import { BodyRegular, Label } from '@/styles/font.css'; +import { BodyRegular } from '@/styles/font.css'; export const page = style({ padding: '1.5rem', @@ -32,21 +32,6 @@ export const headRow = style([ }, ]); -export const bodyRow = style([ - Label, - { - padding: '1rem 0.5rem', - marginBottom: '1rem', - borderBottom: `1px solid ${vars.color.bluegray6}`, - - display: 'grid', - gridTemplateColumns: 'repeat(8, 1fr)', - alignItems: 'center', - - textAlign: 'center', - }, -]); - export const rowItem = style({ gridColumn: 'span 2', @@ -54,26 +39,3 @@ export const rowItem = style({ flexDirection: 'column', gap: '0.5rem', }); - -export const rowText = style({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); - -export const buttons = style({ - display: 'flex', - justifyContent: 'center', - gap: '0.5rem', -}); - -export const button = style({ - padding: '0.5rem 1rem', - borderRadius: '4px', - backgroundColor: vars.color.blue, - color: vars.color.white, - - ':hover': { - opacity: 0.7, - }, -}); diff --git a/src/app/admin/notice/page.tsx b/src/app/admin/notice/page.tsx index e6bc65a2..fd60ce05 100644 --- a/src/app/admin/notice/page.tsx +++ b/src/app/admin/notice/page.tsx @@ -8,71 +8,10 @@ import getAdminNotices from '@/app/_api/notice/getAdminNotices'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { AdminNoticeType } from '@/lib/types/noticeType'; -import formatDate from '@/lib/utils/dateFormat'; - -import useNotice from '@/hooks/queries/useNotice'; +import NoticeItem from './_components/NoticeItem'; const TABLE_ROW = ['일시', '카테고리', '제목&소개', '편집', '미리보기', '알림', '공개']; -interface NoticeItemProps { - notice: AdminNoticeType; -} - -function NoticeItem({ notice }: NoticeItemProps) { - const { deletNoticeMutation, sendNoticeAlarmMutation, updateNoticePublicMutation } = useNotice(); - - const handleDeleteNotice = () => { - deletNoticeMutation.mutate(notice.id); - }; - - const handleSendAlarm = () => { - if (!notice.isExposed) { - alert('공개 게시물만 알림을 보낼 수 있어요.'); - return; - } - if (notice.didSendAlarm) { - alert('이미 알림을 보낸 게시물입니다.'); - return; - } - sendNoticeAlarmMutation.mutate(notice.id); - }; - - const handleTogglePublic = () => { - updateNoticePublicMutation.mutate(notice.id); - }; - - return ( - - {formatDate(notice.createdDate)} - {notice.category} - - {notice.title} - {notice.description} - - - - - - - - - - - - - - - - ); -} - export default function AdminNoticesPage() { const { data: notices } = useQuery({ queryKey: [QUERY_KEYS.getAdminAllNotice], diff --git a/src/app/notices/[noticeId]/NoticeDetail.tsx b/src/app/notices/[noticeId]/NoticeDetail.tsx index 911e3e73..8fc79459 100644 --- a/src/app/notices/[noticeId]/NoticeDetail.tsx +++ b/src/app/notices/[noticeId]/NoticeDetail.tsx @@ -21,7 +21,7 @@ function NoticeDetailComponent() {
    - {data.content?.map((item: NoticeContentType, idx) => ( + {data.contents?.map((item: NoticeContentType, idx) => (
  • diff --git a/src/components/Modal/Modal.css.ts b/src/components/Modal/Modal.css.ts index 1dd71f39..817d2523 100644 --- a/src/components/Modal/Modal.css.ts +++ b/src/components/Modal/Modal.css.ts @@ -45,6 +45,7 @@ export const sizeVariants = styleVariants({ container, { minWidth: '327px', + maxWidth: '420px', width: '100%', margin: '0px 24px', padding: '6rem 2.5rem', diff --git a/src/components/NoticeDetail/NoticeDetailContents.css.ts b/src/components/NoticeDetail/NoticeDetailContents.css.ts new file mode 100644 index 00000000..df004838 --- /dev/null +++ b/src/components/NoticeDetail/NoticeDetailContents.css.ts @@ -0,0 +1,57 @@ +import { style } from '@vanilla-extract/css'; + +import { vars } from '@/styles/theme.css'; +import { BodyRegular, Subtitle, Label } from '@/styles/font.css'; + +export const subtitle = style([ + Subtitle, + { + color: vars.color.black, + }, +]); + +export const imgaeBox = style({ + position: 'relative', + height: '400px', +}); + +export const image = style({ + objectFit: 'cover', +}); + +export const button = style([ + BodyRegular, + { + padding: '16px 14px', + width: '100%', + + backgroundColor: vars.color.white, + color: vars.color.blue, + fontWeight: '700', + + border: `1px solid ${vars.color.blue}`, + borderRadius: '18px', + }, +]); + +export const line = style({ + width: '100%', + height: '2px', + margin: '1rem 0', + + backgroundColor: vars.color.lightblue, +}); + +export const notice = style([ + Label, + { + width: '100%', + minHeight: '120px', + + color: vars.color.bluegray8, + + border: 'none', + outline: 'none', + resize: 'none', + }, +]); diff --git a/src/components/NoticeDetail/NoticeDetailContents.tsx b/src/components/NoticeDetail/NoticeDetailContents.tsx new file mode 100644 index 00000000..4482d780 --- /dev/null +++ b/src/components/NoticeDetail/NoticeDetailContents.tsx @@ -0,0 +1,60 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import MDEditor from '@uiw/react-md-editor'; + +import * as styles from './NoticeDetailContents.css'; + +import { NoticeContentType } from '@/lib/types/noticeType'; + +function BodyContent({ description }: Pick) { + return ; +} + +function SubTitleContent({ description }: Pick) { + return

    {description}

    ; +} + +function ImageContent({ imageUrl }: Pick) { + return ( +
    + 이미지 +
    + ); +} + +function ButtonContent({ buttonLink, buttonName }: Pick) { + return ( + + + + ); +} + +function LineContent() { + return
    ; +} + +function NoteContent({ description }: Pick) { + return