-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from Kim-jaeyeon/develop
Feat(modify Notice, Create QnA): 공지사항, qna 페이지 수정 및 제작
- Loading branch information
Showing
7 changed files
with
681 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}); |
Oops, something went wrong.