From 4a2ab2b1e33780a5c4b44b5efd18820025216b7b Mon Sep 17 00:00:00 2001 From: "seunghyun.lee" Date: Mon, 26 Jan 2026 12:49:24 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공지 페이지 구현 --- apps/admin/app/notice/layout.tsx | 16 ++ apps/admin/app/notice/page.tsx | 150 ++++++++++++++++++ apps/admin/components/app-sidebar.tsx | 5 + apps/admin/components/notice/notice-tag.tsx | 2 +- packages/shared/src/components/icons/index.ts | 1 + packages/shared/src/components/icons/plus.tsx | 33 ++++ .../shared/src/components/team-tab-bar.tsx | 2 +- 7 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 apps/admin/app/notice/layout.tsx create mode 100644 apps/admin/app/notice/page.tsx create mode 100644 packages/shared/src/components/icons/plus.tsx diff --git a/apps/admin/app/notice/layout.tsx b/apps/admin/app/notice/layout.tsx new file mode 100644 index 00000000..b310fc41 --- /dev/null +++ b/apps/admin/app/notice/layout.tsx @@ -0,0 +1,16 @@ +import { SidebarProvider } from '@dpm-core/shared'; + +import { AppSidebar } from '@/components/app-sidebar'; + +export default function NoticeLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + ); +} diff --git a/apps/admin/app/notice/page.tsx b/apps/admin/app/notice/page.tsx new file mode 100644 index 00000000..fe606a31 --- /dev/null +++ b/apps/admin/app/notice/page.tsx @@ -0,0 +1,150 @@ +'use client'; + +import { useState } from 'react'; +import { Button, Plus, SidebarInset, TeamTabBar } from '@dpm-core/shared'; + +import { NoticeCard } from '@/components/notice/notice-card'; +import { NoticeHeader } from '@/components/notice/notice-header'; + +type NoticeType = 'all' | 'general' | 'assignment' | 'etc'; + +interface Notice { + id: string; + title: string; + date: string; + readCount: number; + tags: Array<'default' | 'assignment' | 'individual' | 'team' | 'etc'>; +} + +const NoticePage = () => { + const [selectedType, setSelectedType] = useState('all'); + + // 목업 데이터 + const notices: Notice[] = [ + { + id: '1', + title: + '{공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다.}', + date: '(YYYY.MM.DD)', + readCount: 0, + tags: ['default'], + }, + { + id: '2', + title: + '{공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다.}', + date: '(YYYY.MM.DD)', + readCount: 0, + tags: ['assignment', 'individual'], + }, + { + id: '3', + title: + '{공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다.}', + date: '(YYYY.MM.DD)', + readCount: 0, + tags: ['assignment', 'team'], + }, + { + id: '4', + title: + '{공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다.}', + date: '(YYYY.MM.DD)', + readCount: 0, + tags: ['default'], + }, + { + id: '5', + title: + '{공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다. 공지 제목이 들어갑니다.}', + date: '(YYYY.MM.DD)', + readCount: 0, + tags: ['assignment', 'team'], + }, + ]; + + // 선택된 타입에 따라 필터링 + const filteredNotices = notices.filter((notice) => { + if (selectedType === 'all') return true; + if (selectedType === 'general') return notice.tags.includes('default'); + if (selectedType === 'assignment') return notice.tags.includes('assignment'); + if (selectedType === 'etc') return notice.tags.includes('etc'); + return true; + }); + + const handleNoticeClick = (noticeId: string) => { + console.log('공지 클릭:', noticeId); + // TODO: 공지 상세 페이지로 이동 + }; + + const handleCreateNotice = () => { + console.log('공지 등록 클릭'); + // TODO: 공지 등록 페이지로 이동 + }; + + return ( + +
+ {/* Header */} + + + {/* Body */} +
+ {/* Title + Button */} +
+
+

공지 목록

+ + {filteredNotices.length} + +
+ +
+ + {/* Tab Bar */} + setSelectedType(tabId as NoticeType)} + className="w-full px-10" + /> + + {/* Notice Card List */} +
+ {filteredNotices.map(({ id, title, date, readCount, tags }) => ( + handleNoticeClick(id)} + /> + ))} +
+ + {/* Empty State */} + {filteredNotices.length === 0 && ( +
+

등록된 공지가 없습니다.

+
+ )} +
+
+
+ ); +}; + +export default NoticePage; diff --git a/apps/admin/components/app-sidebar.tsx b/apps/admin/components/app-sidebar.tsx index 0ecdb556..233c4c43 100644 --- a/apps/admin/components/app-sidebar.tsx +++ b/apps/admin/components/app-sidebar.tsx @@ -56,6 +56,11 @@ const SIDEBAR_ITEMS = [ href: '/', icon: IconHome, }, + { + title: '공지', + href: '/notice', + icon: IconHome, // [TODO]: 아이콘 준비되면 교체 필요 + }, { title: '출석', href: `/attendance/search/session?week=${SESSION_ID}`, diff --git a/apps/admin/components/notice/notice-tag.tsx b/apps/admin/components/notice/notice-tag.tsx index 67dc8c00..8f5f2095 100644 --- a/apps/admin/components/notice/notice-tag.tsx +++ b/apps/admin/components/notice/notice-tag.tsx @@ -23,7 +23,7 @@ const noticeTagVariants = cva( ); const labelMap: Record = { - default: '일반 공지', + default: '필수 공지', assignment: '과제 공지', individual: '개인 과제', team: '팀 과제', diff --git a/packages/shared/src/components/icons/index.ts b/packages/shared/src/components/icons/index.ts index 089cb727..3337c812 100644 --- a/packages/shared/src/components/icons/index.ts +++ b/packages/shared/src/components/icons/index.ts @@ -15,6 +15,7 @@ export { Clock } from './clock'; export { CopyIcon } from './copy'; export { DpmText } from './dpm-text'; export { KakaoLogo } from './kakao-logo'; +export { Plus } from './plus'; export { TextLogo } from './text-logo'; export { UserAvatar } from './user-avatar'; export { XCircle } from './x-circle'; diff --git a/packages/shared/src/components/icons/plus.tsx b/packages/shared/src/components/icons/plus.tsx new file mode 100644 index 00000000..e5508e64 --- /dev/null +++ b/packages/shared/src/components/icons/plus.tsx @@ -0,0 +1,33 @@ +import { forwardRef } from 'react'; + +const Plus = forwardRef>((props, ref) => { + return ( + + 추가 + + + + ); +}); + +Plus.displayName = 'Plus'; + +export { Plus }; diff --git a/packages/shared/src/components/team-tab-bar.tsx b/packages/shared/src/components/team-tab-bar.tsx index 01934cd3..59b67802 100644 --- a/packages/shared/src/components/team-tab-bar.tsx +++ b/packages/shared/src/components/team-tab-bar.tsx @@ -25,7 +25,7 @@ export const TeamTabBar = ({ return (