Skip to content

Commit

Permalink
#244 feat: 뉴스 전체 보기 페이지 초기
Browse files Browse the repository at this point in the history
  • Loading branch information
pillow12360 committed Jan 5, 2025
1 parent f2986f2 commit aabebd8
Show file tree
Hide file tree
Showing 2 changed files with 381 additions and 0 deletions.
138 changes: 138 additions & 0 deletions frontend/src/pages/News/News/News.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Eye } from 'lucide-react';
import moment from 'moment';
import {
Container,
NewsGrid,
NewsCard,
NewsImage,
NewsContent,
NewsTitle,
NewsDate,
NewsDescription,
NewsFooter,
NewsViews,
Pagination,
} from './NewsStyle';
import { apiEndpoints } from '../../../config/apiConfig';

interface NewsItem {
id: number;
title: string;
content: string;
view: number;
createDate: string;
link: string;
image: string;
}

interface NewsResponse {
message: string;
page: number;
totalPage: number;
data: NewsItem[];
}

const News = () => {
const [news, setNews] = useState<NewsItem[]>([]);
const [currentPage, setCurrentPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const pageSize = 10;

useEffect(() => {
fetchNews();
}, [currentPage]);

const fetchNews = async () => {
try {
setIsLoading(true);
const response = await fetch(
apiEndpoints.news.listWithPage(currentPage, pageSize),
);
if (!response.ok) {
throw new Error('Failed to fetch news');
}
const data: NewsResponse = await response.json();
setNews(data.data);
setTotalPages(data.totalPage);
} catch (error) {
console.error('Error fetching news:', error);
} finally {
setIsLoading(false);
}
};

const handleNewsClick = (newsId: number) => {
navigate(`/news/${newsId}`);
};

const handlePageChange = (newPage: number) => {
if (newPage >= 0 && newPage < totalPages) {
setCurrentPage(newPage);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};

const renderPagination = () => {
return (
<Pagination>
<button
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 0}
>
이전
</button>
{Array.from({ length: totalPages }, (_, i) => (
<button
key={i}
onClick={() => handlePageChange(i)}
className={currentPage === i ? 'active' : ''}
>
{i + 1}
</button>
))}
<button
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages - 1}
>
다음
</button>
</Pagination>
);
};

if (isLoading) {
return <div>Loading...</div>;
}

return (
<Container>
<NewsGrid>
{news.map((item) => (
<NewsCard key={item.id} onClick={() => handleNewsClick(item.id)}>
<NewsImage imageUrl={item.image} />
<NewsContent>
<NewsTitle>{item.title}</NewsTitle>
<NewsDate>
{moment(item.createDate).format('YYYY.MM.DD')}
</NewsDate>
<NewsDescription>{item.content}</NewsDescription>
<NewsFooter>
<NewsViews>
<Eye size={16} />
{item.view}
</NewsViews>
</NewsFooter>
</NewsContent>
</NewsCard>
))}
</NewsGrid>
{renderPagination()}
</Container>
);
};

export default News;
243 changes: 243 additions & 0 deletions frontend/src/pages/News/News/NewsStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import styled from 'styled-components';
import { media } from '../../../styles/media';
import { SEJONG_COLORS } from '../../../constants/colors';

export const Container = styled.div`
max-width: 1400px;
width: 95%;
margin: 0 auto;
padding: 40px 20px;
${media.mobile} {
padding: 20px 10px;
}
`;

export const NewsGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin-top: 20px;
${media.mobile} {
grid-template-columns: 1fr;
gap: 16px;
}
`;

export const NewsCard = styled.div`
border: 1px solid ${SEJONG_COLORS.COOL_GRAY};
border-radius: 8px;
overflow: hidden;
background-color: white;
cursor: pointer;
transition: all 0.2s ease-in-out;
&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
`;

export const NewsImage = styled.div<{ imageUrl: string }>`
width: 100%;
height: 200px;
background-image: url(${(props) => props.imageUrl});
background-size: cover;
background-position: center;
background-color: ${SEJONG_COLORS.COOL_GRAY};
`;

export const NewsContent = styled.div`
padding: 20px;
`;

export const NewsTitle = styled.h3`
margin: 0;
font-size: 18px;
font-weight: 600;
color: ${SEJONG_COLORS.GRAY};
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
${media.mobile} {
font-size: 16px;
}
`;

export const NewsDate = styled.p`
margin: 8px 0 0 0;
font-size: 14px;
color: ${SEJONG_COLORS.LIGHT_GRAY};
`;

export const NewsDescription = styled.p`
margin: 12px 0;
font-size: 14px;
color: ${SEJONG_COLORS.WARM_GRAY2};
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
height: 4.8em;
${media.mobile} {
font-size: 13px;
}
`;

export const NewsFooter = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid ${SEJONG_COLORS.COOL_GRAY};
`;

export const NewsViews = styled.span`
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: ${SEJONG_COLORS.LIGHT_GRAY};
svg {
width: 16px;
height: 16px;
color: ${SEJONG_COLORS.WARM_GRAY1};
}
`;

export const PaginationButton = styled.button<{
direction?: 'prev' | 'next';
disabled?: boolean;
}>`
display: flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
min-width: 80px;
height: 36px;
border: 1px solid
${(props) =>
props.disabled ? SEJONG_COLORS.COOL_GRAY : SEJONG_COLORS.CRIMSON_RED};
background-color: white;
color: ${(props) =>
props.disabled ? SEJONG_COLORS.LIGHT_GRAY : SEJONG_COLORS.CRIMSON_RED};
font-size: 14px;
border-radius: 4px;
cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
transition: all 0.2s;
&:hover:not(:disabled) {
background-color: ${SEJONG_COLORS.IVORY};
}
svg {
width: 16px;
height: 16px;
margin: ${(props) =>
props.direction === 'prev' ? '0 4px 0 0' : '0 0 0 4px'};
}
${media.mobile} {
min-width: 60px;
padding: 6px 8px;
font-size: 13px;
}
`;

export const PageEllipsis = styled.span`
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
color: ${SEJONG_COLORS.GRAY};
${media.mobile} {
width: 32px;
height: 32px;
}
`;

export const Pagination = styled.nav`
display: flex;
justify-content: center;
align-items: center;
margin-top: 40px;
`;

export const PageList = styled.ul`
display: flex;
list-style: none;
padding: 0;
gap: 8px;
`;

export const PageItem = styled.li`
margin: 0;
padding: 0;
`;

export const PageButton = styled.button<{ isActive?: boolean }>`
min-width: 36px;
height: 36px;
padding: 0 12px;
border: 1px solid
${(props) =>
props.isActive ? SEJONG_COLORS.CRIMSON_RED : SEJONG_COLORS.COOL_GRAY};
background: ${(props) =>
props.isActive ? SEJONG_COLORS.CRIMSON_RED : 'white'};
color: ${(props) => (props.isActive ? 'white' : SEJONG_COLORS.GRAY)};
font-size: 14px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: ${(props) =>
props.isActive ? SEJONG_COLORS.CRIMSON_RED : SEJONG_COLORS.IVORY};
}
&:disabled {
background: ${SEJONG_COLORS.COOL_GRAY};
border-color: ${SEJONG_COLORS.COOL_GRAY};
color: ${SEJONG_COLORS.LIGHT_GRAY};
cursor: not-allowed;
}
${media.mobile} {
min-width: 32px;
height: 32px;
padding: 0 8px;
font-size: 13px;
}
`;

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;
`;

export const NoResults = styled.div`
text-align: center;
padding: 40px;
color: ${SEJONG_COLORS.GRAY};
font-size: 16px;
`;

0 comments on commit aabebd8

Please sign in to comment.