- Initial setup and all features from develop branch - Includes: auth, deploy, docker, style fixes - K3S deployment configuration
198 lines
8.8 KiB
TypeScript
198 lines
8.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { getAnnouncements, type Announcement } from "@/lib/services";
|
|
import { useAuth, usePagination } from "@/hooks";
|
|
import { FileTextIcon } from "lucide-react";
|
|
import Pagination from "@/components/Pagination";
|
|
|
|
export default function AnnouncementsPage() {
|
|
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const { user } = useAuth();
|
|
const { currentPage, totalPages, setCurrentPage, setTotalPages } = usePagination();
|
|
|
|
useEffect(() => {
|
|
loadData(currentPage);
|
|
}, [currentPage]);
|
|
|
|
const loadData = async (page: number) => {
|
|
setIsLoading(true);
|
|
try {
|
|
// 공지사항 목록 불러오기
|
|
const announcementsResponse = await getAnnouncements(page, 10);
|
|
setAnnouncements(announcementsResponse.data);
|
|
setTotalPages(announcementsResponse.pagination.totalPages);
|
|
} catch (error) {
|
|
console.error("Failed to load announcements:", error);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString("ko-KR", {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
});
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="space-y-8 w-full flex flex-col items-center">
|
|
<div className="max-w-7xl px-4 m-4 smalltablet:m-8 w-full">
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
{/* 테이블 헤더 스켈레톤 - 데스크톱 */}
|
|
<div className="hidden smalltablet:grid smalltablet:grid-cols-12 bg-gray-50 border-b border-gray-200 text-sm font-medium text-gray-700">
|
|
<div className="col-span-1 px-6 py-4 text-center">번호</div>
|
|
<div className="col-span-6 px-6 py-4">제목</div>
|
|
<div className="col-span-2 px-6 py-4 text-center">작성자</div>
|
|
<div className="col-span-2 px-6 py-4 text-center">작성일</div>
|
|
<div className="col-span-1 px-6 py-4 text-center">조회수</div>
|
|
</div>
|
|
|
|
{/* 테이블 바디 스켈레톤 */}
|
|
<div className="divide-y divide-gray-200">
|
|
{[...Array(5)].map((_, index) => (
|
|
<div
|
|
key={index}
|
|
className="grid grid-cols-1 smalltablet:grid-cols-12 animate-pulse"
|
|
>
|
|
{/* 모바일 뷰 스켈레톤 */}
|
|
<div className="smalltablet:hidden px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
|
|
<div className="flex items-center justify-between">
|
|
<div className="h-3 bg-gray-200 rounded w-20"></div>
|
|
<div className="h-3 bg-gray-200 rounded w-24"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 데스크톱 뷰 스켈레톤 */}
|
|
<div className="hidden smalltablet:block smalltablet:col-span-1 px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-8 mx-auto"></div>
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-6 px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-2 px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-16 mx-auto"></div>
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-2 px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-20 mx-auto"></div>
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-1 px-6 py-4">
|
|
<div className="h-4 bg-gray-200 rounded w-8 mx-auto"></div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-8 w-full flex flex-col items-center">
|
|
<div className="max-w-7xl px-4 m-4 smalltablet:m-8 w-full">
|
|
{/* 공지 작성 버튼 */}
|
|
{user && (
|
|
<div className="flex justify-end mb-4">
|
|
<Link
|
|
href="/announcements/create"
|
|
className="px-6 py-2.5 bg-linear-to-br from-[#7ba5d6] to-[#6b95c6] hover:from-[#6b95c6] hover:to-[#5b85b6] text-white rounded-lg shadow-md hover:shadow-lg transition-all font-medium text-sm"
|
|
>
|
|
공지 작성
|
|
</Link>
|
|
</div>
|
|
)}
|
|
|
|
{/* 테이블 */}
|
|
{announcements.length === 0 ? (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 text-center py-20 flex items-center justify-center flex-col">
|
|
<FileTextIcon className="mx-auto h-16 w-16 text-gray-300 mb-4" />
|
|
<p className="text-gray-500 text-lg">
|
|
등록된 주보가 없습니다.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
{/* 테이블 헤더 - 데스크톱 */}
|
|
<div className="hidden smalltablet:grid smalltablet:grid-cols-12 bg-gray-50 border-b border-gray-200 text-sm font-medium text-gray-700">
|
|
<div className="col-span-1 px-6 py-4 text-center">번호</div>
|
|
<div className="col-span-6 px-6 py-4">제목</div>
|
|
<div className="col-span-2 px-6 py-4 text-center">작성자</div>
|
|
<div className="col-span-2 px-6 py-4 text-center">작성일</div>
|
|
<div className="col-span-1 px-6 py-4 text-center">조회수</div>
|
|
</div>
|
|
|
|
{/* 테이블 바디 */}
|
|
<div className="divide-y divide-gray-200">
|
|
{announcements.map((item, index) => (
|
|
<Link
|
|
key={item.id}
|
|
href={`/announcements/${item.id}`}
|
|
className="grid grid-cols-1 smalltablet:grid-cols-12 hover:bg-gray-50 transition-colors"
|
|
>
|
|
{/* 모바일 뷰 */}
|
|
<div className="smalltablet:hidden px-6 py-4">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
{item.isImportant && (
|
|
<span className="px-2 py-0.5 bg-orange-100 text-orange-600 text-xs font-bold rounded">
|
|
필독
|
|
</span>
|
|
)}
|
|
</div>
|
|
<h3 className="font-medium text-gray-900 mb-2">{item.title}</h3>
|
|
<div className="flex items-center justify-between text-sm text-gray-500">
|
|
<span>{item.author.userName}</span>
|
|
<span>{formatDate(item.createdAt)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 데스크톱 뷰 */}
|
|
<div className="hidden smalltablet:block smalltablet:col-span-1 px-6 py-4 text-center text-sm text-gray-600">
|
|
{announcements.length - index}
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-6 px-6 py-4">
|
|
<div className="flex items-center gap-2">
|
|
{item.isImportant && (
|
|
<span className="px-2 py-0.5 bg-orange-100 text-orange-600 text-xs font-bold rounded">
|
|
필독
|
|
</span>
|
|
)}
|
|
<span className="text-gray-900 font-medium hover:text-blue-600 transition-colors">
|
|
{item.title}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-2 px-6 py-4 text-center text-sm text-gray-600">
|
|
{item.author.userName}
|
|
</div>
|
|
<div className="hidden smalltablet:flex smalltablet:col-span-2 px-6 py-4 justify-center text-center text-sm text-gray-600">
|
|
{formatDate(item.createdAt)}
|
|
</div>
|
|
<div className="hidden smalltablet:block smalltablet:col-span-1 px-6 py-4 text-center text-sm text-gray-600">
|
|
{item.viewCount}
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Pagination */}
|
|
<Pagination
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
onPageChange={setCurrentPage}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|