diff --git a/src/App.tsx b/src/App.tsx
index f6053e9..2ec9ece 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -46,7 +46,7 @@ const RouterPath = [
element: ,
},
{
- paht: "QnA",
+ path: "QnA",
element: ,
},
{
@@ -65,5 +65,4 @@ const router = createBrowserRouter([
function App() {
return ;
}
-
export default App;
diff --git a/src/assets/background.svg b/src/assets/background.svg
new file mode 100644
index 0000000..ef32578
--- /dev/null
+++ b/src/assets/background.svg
@@ -0,0 +1,80 @@
+
diff --git a/src/assets/copyright.svg b/src/assets/copyright.svg
new file mode 100644
index 0000000..ee61103
--- /dev/null
+++ b/src/assets/copyright.svg
@@ -0,0 +1,22 @@
+
diff --git a/src/assets/downbtn.svg b/src/assets/downbtn.svg
new file mode 100644
index 0000000..37638e2
--- /dev/null
+++ b/src/assets/downbtn.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/assets/search.svg b/src/assets/search.svg
new file mode 100644
index 0000000..77f123c
--- /dev/null
+++ b/src/assets/search.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/Notice/index.tsx b/src/pages/Notice/index.tsx
index e2ac67b..c5c5d4a 100644
--- a/src/pages/Notice/index.tsx
+++ b/src/pages/Notice/index.tsx
@@ -1,7 +1,227 @@
-export default function Notice() {
+import React, { useState } from "react";
+import {
+ container,
+ titleText,
+ mid,
+ notice,
+ noticeList,
+ noticeContentWrapper,
+ noticeContent,
+ noticeContainer,
+ searchbar,
+ searchContainer,
+ copyright,
+ noticeNumber,
+ noticeDownBtn,
+ noticeDetail,
+} from "./notice.css.ts"; // 스타일 가져오기
+
+// NoticeItem 타입 정의: 제목은 string, 콘텐츠는 JSX.Element로 지정
+interface NoticeItem {
+ id: number;
+ title: string;
+ content: JSX.Element;
+ detail: JSX.Element;
+}
+
+// 임시 데이터 예시
+const noticeData: NoticeItem[] = [
+ {
+ id: 1,
+ title: "2024 강원대학교 백령대동제 개최 안내",
+ content:
This 강원대 the content for notice 1.
,
+ detail: 여기는 마지막 내용입니다. Notice 1에 대한 정보가 더 있습니다.
,
+ },
+ {
+ id: 2,
+ title: "2024 강원대학교 백령대동제 개최 안내",
+ content: This is the content for notice 2.
,
+ detail: 여기는 룰루 내용입니다.
,
+ },
+ {
+ id: 3,
+ title: "랄랄라 신규 게시물",
+ content: This is the content for notice 2.
,
+ detail: 여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.
,
+ },
+ {
+ id: 4,
+ title: "2024 강원대학교 백령대동제 개최 안내",
+ content: This is the content for notice 2.
,
+ detail: 여기는 룰루 내용입니다.
,
+ },
+ {
+ id: 5,
+ title: "랄랄라 신규 게시물",
+ content: This is the content for notice 2.
,
+ detail: 여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.
,
+ },
+ {
+ id: 6,
+ title: "2024 강원대학교 백령대동제 개최 안내",
+ content: This is the content for notice 2.
,
+ detail: 여기는 룰루 내용입니다.
,
+ },
+ {
+ id: 7,
+ title: "랄랄라 신규 게시물",
+ content: This is the content for notice 2.
,
+ detail: 여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.
,
+ },
+ {
+ id: 8,
+ title: "2024 강원대학교 백령대동제 개최 안내",
+ content: This is the content for notice 2.
,
+ detail: 여기는 룰루 내용입니다.
,
+ },
+ {
+ id: 9,
+ title: "랄랄라 신규 게시물",
+ content: This is the content for notice 2.
,
+ detail: 여기는 상세 내용입니다. Notice 2에 대한 정보가 더 있습니다잇.
,
+ },
+ // 나머지 공지사항 추가
+];
+
+function Notice() {
+ const [query, setQuery] = useState(""); // 검색어의 타입을 string으로 지정
+ const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 상태
+ const [expandedNotices, setExpandedNotices] = useState([]); // 확장된 공지 ID들을 관리
+ const itemsPerPage = 6;
+
+ // 가장 최근 게시물의 ID 찾기 (ID가 제일 큰 것이 최신 게시물이라고 가정)
+ const mostRecentNoticeId = Math.max(...noticeData.map((notice) => notice.id));
+
+ // 검색어 입력 시 상태 업데이트
+ const handleSearch = (event: React.ChangeEvent) => {
+ setQuery(event.target.value);
+ setCurrentPage(1);
+ };
+
+ // downbtn 클릭 시 상세 내용을 보여주는 로직
+ const toggleNotice = (id: number) => {
+ setExpandedNotices((prev) =>
+ prev.includes(id) ? prev.filter((noticeId) => noticeId !== id) : [...prev, id],
+ );
+ };
+
+ // 필터링 로직
+ const filteredNotices = noticeData
+ .filter((noticeItem) => {
+ const searchText = query.toLowerCase();
+ return (
+ noticeItem.title.toLowerCase().includes(searchText) ||
+ noticeItem.detail.props.children.toString().toLowerCase().includes(searchText)
+ );
+ })
+ .reverse(); // 공지사항을 내림차순으로 정렬 (마지막 공지사항이 제일 위로 오게)
+
+ // 현재 페이지에 맞는 데이터 추출
+ const indexOfLastItem = currentPage * itemsPerPage;
+ const indexOfFirstItem = indexOfLastItem - itemsPerPage;
+ const currentNotices = filteredNotices.slice(indexOfFirstItem, indexOfLastItem);
+
+ // 페이지 수 계산
+ const totalPages = Math.ceil(filteredNotices.length / itemsPerPage);
+
+ // 페이지 변경 함수
+ const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
+
return (
-
Notice
+
+
+
+
+
+
+ {/* noticeList 렌더링 */}
+
+ {filteredNotices.length === 0 ? (
+
+ 해당하는 게시글이 없어요!
+
+ ) : (
+ currentNotices.map((noticeItem, index) => (
+
+
+ {/* 번호와 제목 표시 */}
+
+
{index + 1 + (currentPage - 1) * itemsPerPage}
+ {/* 가장 최근 게시물에만 'New' 표시 */}
+ {noticeItem.id === mostRecentNoticeId && (
+
+ New
+
+ )}
+
+
+
+ {/* downbtn을 클릭하면 토글 */}
+
+
+
+ {/* 상세 내용 표시 - 확장 시만 보여줌 */}
+ {expandedNotices.includes(noticeItem.id) && (
+
{noticeItem.detail}
+ )}
+
+ ))
+ )}
+
+
+ {/* 페이지네이션 */}
+ {filteredNotices.length > 0 && (
+
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNumber) => (
+
+ ))}
+
+ )}
+
+
+
);
}
+
+export default Notice;
diff --git a/src/pages/Notice/notice.css.ts b/src/pages/Notice/notice.css.ts
new file mode 100644
index 0000000..4ce0b9d
--- /dev/null
+++ b/src/pages/Notice/notice.css.ts
@@ -0,0 +1,165 @@
+import { style, globalStyle } from "@vanilla-extract/css";
+
+// body에 글로벌 스타일 적용
+globalStyle("body", {
+ position: "relative",
+ margin: 0,
+ padding: 0,
+ backgroundImage: 'url("src/assets/background.svg")', // 배경 이미지 경로
+ backgroundSize: "cover", // 배경 이미지를 화면 전체에 맞춤
+ backgroundPosition: "center", // 이미지를 화면 중앙에 위치
+ backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정
+ width: "100%",
+ height: "100vh", // 전체 화면 높이 적용
+ overflow: "hidden", // 스크롤바가 나타나지 않도록 설정
+ fontFamily: `'Pretendard', sans-serif`,
+});
+
+globalStyle("::-webkit-scrollbar", {
+ width: "0px",
+ height: "0px",
+});
+
+export const titleText = style({
+ fontSize: 20,
+ fontWeight: 600,
+ color: "white",
+ marginTop: 60,
+});
+// 검색 필드와 버튼을 감싸는 컨테이너
+export const searchContainer = style({
+ display: "flex",
+ alignItems: "center",
+ backgroundColor: "white",
+ borderRadius: "50px",
+ boxShadow: "0 5px 5px rgba(0,0,0,0.25)",
+ width: "80%",
+ height: "40px",
+ position: "fixed",
+ top: 150,
+ marginBottom: 20,
+});
+
+// 검색 입력 필드 스타일
+export const searchbar = style({
+ width: "100%", // 너비 조정
+ height: "100%",
+ border: "none",
+ outline: "none",
+ borderRadius: 50, // 왼쪽만 둥글게
+ padding: "0px 15px",
+ fontFamily: `'Pretendard', sans-serif`,
+ fontSize: "18px",
+ fontWeight: 500,
+});
+
+// 상단의 빨간 컨테이너
+export const container = style({
+ width: "100%",
+ height: 110,
+ display: "flex",
+ justifyContent: "center",
+});
+
+// 중간에 검색창을 배치하는 컨테이너
+export const mid = style({
+ position: "relative",
+ width: "100%",
+ height: 650,
+ display: "flex",
+ margin: "20px 0",
+ justifyContent: "center",
+ flexDirection: "column",
+ alignItems: "center",
+});
+
+export const noticeList = style({
+ width: "100%",
+ height: 455,
+ overflowY: "scroll",
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "column",
+});
+
+export const noticeContainer = style({
+ width: "95%",
+ height: "auto",
+ borderBottom: "1px solid white",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ flexDirection: "column",
+ marginBottom: 7,
+});
+
+export const notice = style({
+ width: "95%",
+ height: 60,
+ backgroundColor: "white",
+ borderRadius: 10,
+ marginBottom: 7,
+ display: "flex",
+ alignItems: "center",
+ flexDirection: "row",
+ position: "relative",
+});
+
+export const copyright = style({
+ position: "absolute",
+ bottom: 0,
+ margin: 0,
+ padding: 0,
+ backgroundImage: 'url("src/assets/copyright.svg")', // 배경 이미지 경로
+ backgroundPosition: "center", // 이미지를 화면 중앙에 위치
+ backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정
+ width: "90%",
+ height: 110, // 전체 화면 높이 적용
+ overflow: "hidden", // 스크롤바가 나타나지 않도록 설정
+});
+
+export const noticeNumber = style({
+ fontSize: 25,
+ fontWeight: 700,
+ color: "#0081C9",
+ marginLeft: 20,
+});
+
+export const noticeContentWrapper = style({
+ width: "90%", // 부모 요소의 너비에 맞추기
+ overflow: "hidden", // 넘치는 부분을 숨김
+ position: "relative",
+ marginLeft: 10,
+});
+
+export const noticeContent = style({
+ display: "inline-block", // 텍스트가 한 줄로 스크롤되도록 설정
+ whiteSpace: "nowrap", // 텍스트를 한 줄로 유지
+ fontSize: 16,
+ fontWeight: 500,
+ color: "black",
+ margin: 5,
+});
+export const noticeDownBtn = style({
+ backgroundImage: 'url("src/assets/downbtn.svg")', // 배경 이미지 경로
+ backgroundSize: "cover", // 배경 이미지를 화면 전체에 맞춤
+ backgroundPosition: "center", // 이미지를 화면 중앙에 위치
+ backgroundRepeat: "no-repeat", // 이미지를 반복하지 않도록 설정
+ backgroundColor: "white",
+ width: 30,
+ height: 30,
+ position: "absolute",
+ right: 10,
+ border: "none",
+});
+
+// 공지사항 상세 내용 (애니메이션 효과 포함)
+export const noticeDetail = style({
+ overflow: "hidden",
+ maxHeight: "500px", // 최대 높이를 설정
+ backgroundColor: "rgba(255,255,255,0.6)", // 배경색
+ padding: "10px",
+ marginTop: -12,
+ marginBottom: 5,
+ width: "90%",
+});
diff --git a/vite.config.ts b/vite.config.ts
index bf4565d..dd7d7fa 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -2,7 +2,6 @@ import { defineConfig } from "vite";
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
import react from "@vitejs/plugin-react";
-// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), vanillaExtractPlugin()],
});