Skip to content

Commit

Permalink
Merge pull request #12 from Kim-jaeyeon/develop
Browse files Browse the repository at this point in the history
Feat(modify Notice, Create QnA): 공지사항, qna 페이지 수정 및 제작
  • Loading branch information
Kim-jaeyeon authored Sep 15, 2024
2 parents 2dc2fee + a00ae30 commit 5f853b1
Show file tree
Hide file tree
Showing 7 changed files with 681 additions and 6 deletions.
1 change: 0 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,4 @@ const router = createBrowserRouter([
function App() {
return <RouterProvider router={router} />;
}

export default App;
205 changes: 203 additions & 2 deletions src/pages/Notice/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,208 @@
export default function Notice() {
import React, { useState } from "react";
import {
container,
titleText,
mid,
notice,
noticeList,
noticeContentWrapper,
noticeContent,
noticeContainer,
searchbar,
searchContainer,
noticeNumber,
noticeDetail,
arrowButton,
newBadge,
pageButton,
activePageButton,
} from "./notice.css.ts"; // 스타일 가져오기

// NoticeItem 타입 정의: 제목은 string, 콘텐츠는 JSX.Element로 지정
interface NoticeItem {
id: number;
title: string;
content: string;
detail: string;
}

// 임시 데이터 예시
const noticeData: NoticeItem[] = [
{
id: 1,
title: "2024 강원대학교 백령대동제 개최 안내",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 2,
title: "2024 강원대학교 백령대동제 개최 안내",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 3,
title: "랄랄라 신규 게시물",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 4,
title: "2024 강원대학교 백령대동제 개최 안내",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 5,
title: "랄랄라 신규 게시물",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 6,
title: "2024 강원대학교 백령대동제 개최 안내",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 7,
title: "랄랄라 신규 게시물",
content: "This 강원대 the content for notice 1.",
detail: "여기는 세부내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 8,
title: "2024 강원대학교 백령대동제 개최 안내",
content: "This 강원대 the content for notice 1.",
detail: "여기는 룰루입니다. Notice 1에 대한 정보가 더 있습니다.",
},
{
id: 9,
title: "랄랄라 신규 게시물",
content: "This 강원대 the content for notice 1.",
detail: "여기는 부스내용입니다. Notice 1에 대한 정보가 더 있습니다.",
},
// 나머지 공지사항 추가
];

function Notice() {
const [query, setQuery] = useState<string>(""); // 검색어의 타입을 string으로 지정
const [currentPage, setCurrentPage] = useState<number>(1); // 현재 페이지 상태
const [expandedNotices, setExpandedNotices] = useState<number[]>([]); // 확장된 공지 ID들을 관리
const itemsPerPage = 6;

// 가장 최근 게시물의 ID 찾기 (ID가 제일 큰 것이 최신 게시물이라고 가정)
const mostRecentNoticeId = Math.max(...noticeData.map((notice) => notice.id));

// 검색어 입력 시 상태 업데이트
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
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.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 (
<div>
<h1>Notice</h1>
<div className={container}>
<p className={titleText}>공지사항</p>
</div>
<div className={mid}>
<div className={searchContainer}>
<input
placeholder="키워드 검색"
type="text"
value={query}
onChange={handleSearch}
className={searchbar}
/>
</div>

{/* noticeList 렌더링 */}
<div className={noticeList}>
{filteredNotices.length === 0 ? (
<p style={{ textAlign: "center", color: "white", fontSize: "18px" }}>
해당하는 게시글이 없어요!
</p>
) : (
currentNotices.map((noticeItem, index) => (
<div className={noticeContainer} key={noticeItem.id}>
<div className={notice}>
{/* 번호와 제목 표시 */}
<div style={{ display: "flex", alignItems: "center" }}>
<p className={noticeNumber}>{index + 1 + (currentPage - 1) * itemsPerPage}</p>
{/* 가장 최근 게시물에만 'New' 표시 */}
{noticeItem.id === mostRecentNoticeId && <span className={newBadge}>New</span>}
</div>

<div className={noticeContentWrapper}>
<p className={noticeContent}>{noticeItem.title}</p>
</div>
{/* downbtn을 클릭하면 토글 */}
<button onClick={() => toggleNotice(noticeItem.id)} className={arrowButton}>
<span className="material-symbols-outlined">
{expandedNotices.includes(noticeItem.id)
? "arrow_drop_up"
: "arrow_drop_down"}
</span>
</button>
</div>

{/* 상세 내용 표시 - 확장 시만 보여줌 */}
{expandedNotices.includes(noticeItem.id) && (
<div className={noticeDetail}>{noticeItem.detail}</div>
)}
</div>
))
)}
</div>

{/* 페이지네이션 */}
{filteredNotices.length > 0 && (
<div>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNumber) => (
<button
key={pageNumber}
onClick={() => paginate(pageNumber)}
className={currentPage === pageNumber ? activePageButton : pageButton}
>
{pageNumber}
</button>
))}
</div>
)}
</div>

<div className={container}></div>
</div>
);
}

export default Notice;
163 changes: 163 additions & 0 deletions src/pages/Notice/notice.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { style } from "@vanilla-extract/css";
import { vars } from "../../shared/styles/vars.css";

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: "absolute",
top: 10,
marginBottom: 20,
});

// 검색 입력 필드 스타일
export const searchbar = style({
width: "100%", // 너비 조정
height: "100%",
border: "none",
outline: "none",
borderRadius: 50, // 왼쪽만 둥글게
padding: "0px 15px",
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 noticeNumber = style({
fontSize: 25,
fontWeight: 700,
color: `${vars.color.blue1}`,
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 noticeDetail = style({
overflow: "hidden",
maxHeight: "500px", // 최대 높이를 설정
backgroundColor: "rgba(255,255,255,0.6)", // 배경색
padding: "10px",
marginTop: -12,
marginBottom: 5,
width: "90%",
});

//새 게시물에 뜨는 new 태그
export const newBadge = style({
marginLeft: "10px",
padding: "3px 6px",
backgroundColor: "#e74c3c",
color: "#fff",
borderRadius: "3px",
fontSize: "12px",
fontWeight: "bold",
});

export const arrowButton = style({
width: "30px",
height: "30px",
position: "absolute",
color: "#0081e4",
right: "10px",
border: "none",
backgroundColor: "transparent",
cursor: "pointer",
});

export const pageButton = style({
margin: "5px",
marginTop: "15px",
padding: "5px 10px",
backgroundColor: "#ddd",
color: "#000",
border: "none",
borderRadius: "3px",
cursor: "pointer",
});

export const activePageButton = style({
margin: "5px",
marginTop: "15px",
padding: "5px 10px",
backgroundColor: "#3498db",
color: "#fff",
border: "none",
borderRadius: "3px",
cursor: "pointer",
});
Loading

0 comments on commit 5f853b1

Please sign in to comment.