From 8095cd42d9db6e611a5ceb130f7c5d783e724253 Mon Sep 17 00:00:00 2001 From: pillow12360 Date: Sun, 5 Jan 2025 18:09:57 +0900 Subject: [PATCH] =?UTF-8?q?#244=20feat:=20=EB=89=B4=EC=8A=A4=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EC=B4=88=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/AppContent.tsx | 5 +- frontend/src/pages/News/News/News.tsx | 1 - frontend/src/pages/News/News/NewsDetail.tsx | 139 +++++++++++++++++ .../src/pages/News/News/NewsDetailStyle.ts | 140 ++++++++++++++++++ 4 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/News/News/NewsDetail.tsx create mode 100644 frontend/src/pages/News/News/NewsDetailStyle.ts diff --git a/frontend/src/AppContent.tsx b/frontend/src/AppContent.tsx index 92d8d458..7967b8b1 100644 --- a/frontend/src/AppContent.tsx +++ b/frontend/src/AppContent.tsx @@ -40,6 +40,7 @@ import SeminarDetail from './pages/Seminar/SeminarDetail'; import SeminarCreate from './pages/Seminar/SeminarCreate'; import SeminarEdit from './pages/Seminar/SeminarEdit'; import News from './pages/News/News/News'; +import NewsDetail from './pages/News/News/NewsDetail'; interface PageTransitionProps { children: React.ReactNode; @@ -214,7 +215,6 @@ function AppContent() { } /> {/* news */} - } /> } /> } /> } /> } /> } /> + {/*학부 뉴스*/} + } /> + } /> {/* 어드민 권한 보호 Routes */} { setIsLoading(false); } }; - const handleNewsClick = (newsId: number) => { navigate(`/news/${newsId}`); }; diff --git a/frontend/src/pages/News/News/NewsDetail.tsx b/frontend/src/pages/News/News/NewsDetail.tsx new file mode 100644 index 00000000..1d32009b --- /dev/null +++ b/frontend/src/pages/News/News/NewsDetail.tsx @@ -0,0 +1,139 @@ +// NewsDetail.tsx +import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Eye, ArrowLeft } from 'lucide-react'; +import moment from 'moment'; +import { apiEndpoints } from '../../../config/apiConfig'; +import { + Container, + BackButton, + NewsWrapper, + NewsHeader, + NewsTitle, + NewsMetadata, + NewsDate, + NewsViews, + NewsDivider, + NewsImage, + NewsContent, + NewsLink, + LoadingSpinner, + ErrorMessage, +} from './NewsDetailStyle'; + +interface NewsDetailData { + id: number; + title: string; + content: string; + view: number; + createDate: string; + link: string; + image: string; +} + +const NewsDetail = () => { + const { newsId } = useParams(); + const navigate = useNavigate(); + const [newsData, setNewsData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + const fetchNewsDetail = async () => { + try { + setIsLoading(true); + if (!newsId) return; + + const response = await fetch(apiEndpoints.news.get(parseInt(newsId))); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('뉴스를 찾을 수 없습니다.'); + } + throw new Error('뉴스를 불러오는데 실패했습니다.'); + } + + const data = await response.json(); + setNewsData(data); + } catch (error) { + setError( + error instanceof Error + ? error.message + : '알 수 없는 오류가 발생했습니다.', + ); + } finally { + setIsLoading(false); + } + }; + + if (newsId) { + fetchNewsDetail(); + } + }, [newsId]); + + const handleBack = () => { + navigate(-1); + }; + + if (isLoading) { + return ( + + 로딩 중... + + ); + } + + if (error) { + return ( + + {error} + + ); + } + + if (!newsData) { + return null; + } + + return ( + + + + 뒤로 가기 + + + + {newsData.title} + + + {moment(newsData.createDate).format('YYYY.MM.DD')} + + + + {newsData.view} + + + + + {newsData.image && ( + + )} + {newsData.content} + {newsData.link && ( + + 원문 보기 + + )} + + + ); +}; + +export default NewsDetail; diff --git a/frontend/src/pages/News/News/NewsDetailStyle.ts b/frontend/src/pages/News/News/NewsDetailStyle.ts new file mode 100644 index 00000000..6b081e73 --- /dev/null +++ b/frontend/src/pages/News/News/NewsDetailStyle.ts @@ -0,0 +1,140 @@ +// NewsDetailStyle.ts +import styled from 'styled-components'; +import { media } from '../../../styles/media'; +import { SEJONG_COLORS } from '../../../constants/colors'; + +export const Container = styled.div` + max-width: 1000px; + width: 95%; + margin: 0 auto; + padding: 40px 20px; + + ${media.mobile} { + padding: 20px 10px; + } +`; + +export const BackButton = styled.button` + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border: 1px solid ${SEJONG_COLORS.COOL_GRAY}; + border-radius: 4px; + background: white; + color: ${SEJONG_COLORS.GRAY}; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: ${SEJONG_COLORS.IVORY}; + color: ${SEJONG_COLORS.CRIMSON_RED}; + } +`; + +export const NewsWrapper = styled.article` + margin-top: 24px; + background: white; + border: 1px solid ${SEJONG_COLORS.COOL_GRAY}; + border-radius: 8px; + overflow: hidden; +`; + +export const NewsHeader = styled.header` + padding: 24px; +`; + +export const NewsTitle = styled.h1` + margin: 0; + font-size: 24px; + font-weight: 600; + color: ${SEJONG_COLORS.GRAY}; + line-height: 1.4; + + ${media.mobile} { + font-size: 20px; + } +`; + +export const NewsMetadata = styled.div` + display: flex; + align-items: center; + gap: 16px; + margin-top: 16px; +`; + +export const NewsDate = styled.span` + font-size: 14px; + color: ${SEJONG_COLORS.LIGHT_GRAY}; +`; + +export const NewsViews = styled.span` + display: flex; + align-items: center; + gap: 4px; + font-size: 14px; + color: ${SEJONG_COLORS.LIGHT_GRAY}; + + svg { + color: ${SEJONG_COLORS.WARM_GRAY1}; + } +`; + +export const NewsDivider = styled.hr` + margin: 0; + border: none; + border-top: 1px solid ${SEJONG_COLORS.COOL_GRAY}; +`; + +export const NewsImage = styled.img` + display: block; + width: 100%; + max-height: 500px; + object-fit: cover; + margin: 0 auto; +`; + +export const NewsContent = styled.div` + padding: 24px; + font-size: 16px; + line-height: 1.8; + color: ${SEJONG_COLORS.GRAY}; + white-space: pre-wrap; + + ${media.mobile} { + font-size: 15px; + } +`; + +export const NewsLink = styled.a` + display: inline-block; + margin: 0 24px 24px; + padding: 8px 16px; + border: 1px solid ${SEJONG_COLORS.CRIMSON_RED}; + border-radius: 4px; + color: ${SEJONG_COLORS.CRIMSON_RED}; + text-decoration: none; + font-size: 14px; + transition: all 0.2s; + + &:hover { + background: ${SEJONG_COLORS.CRIMSON_RED}; + color: white; + } +`; + +export const LoadingSpinner = styled.div` + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; + color: ${SEJONG_COLORS.CRIMSON_RED}; +`; + +export const ErrorMessage = styled.div` + text-align: center; + padding: 40px; + color: ${SEJONG_COLORS.CRIMSON_RED}; + font-size: 16px; +`;