대시보드, GNB 추가
This commit is contained in:
+103
-40
@@ -11,6 +11,9 @@ import {
|
||||
ListChecks,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
LayoutDashboard,
|
||||
FolderKanban,
|
||||
SunMoon,
|
||||
} from "lucide-react";
|
||||
|
||||
interface ProjectListItem {
|
||||
@@ -29,6 +32,7 @@ export default function ProjectsPage() {
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [selectedSlugs, setSelectedSlugs] = useState<string[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const pageSize = 10;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -179,7 +183,7 @@ export default function ProjectsPage() {
|
||||
window.localStorage.removeItem(`pb:autosave:${slug}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (okSlugs.length !== slugs.length) {
|
||||
setStatus("error");
|
||||
setErrorMessage(
|
||||
@@ -195,51 +199,101 @@ export default function ProjectsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleTheme = () => {
|
||||
if (typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
root.classList.toggle("dark");
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="min-h-screen flex flex-col bg-slate-950 text-slate-50">
|
||||
<header className="border-b border-slate-800 px-6 py-4 flex items-center justify-between bg-slate-950/80 backdrop-blur">
|
||||
<main className="min-h-screen flex flex-col bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-50">
|
||||
<header className="border-b border-slate-200 px-6 py-4 flex items-center justify-between bg-white/80 backdrop-blur dark:border-slate-800 dark:bg-slate-950/80">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">프로젝트 목록</h1>
|
||||
<p className="text-xs text-slate-400">저장된 프로젝트들을 한 눈에 볼 수 있는 목록</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">프로젝트 목록</h1>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400">저장된 프로젝트들을 한 눈에 볼 수 있는 목록</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<nav className="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white/80 px-1.5 py-1 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-sky-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:hover:text-sky-200"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" aria-hidden="true" />
|
||||
<span>대시보드</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects"
|
||||
aria-current="page"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-semibold bg-sky-600 text-white shadow-sm hover:bg-sky-700"
|
||||
>
|
||||
<FolderKanban className="w-4 h-4" aria-hidden="true" />
|
||||
<span>프로젝트 목록</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects/submissions"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-emerald-700 hover:bg-emerald-50 hover:text-emerald-800 dark:text-emerald-200 dark:hover:bg-emerald-900/60 dark:hover:text-emerald-100"
|
||||
>
|
||||
<ListChecks className="w-4 h-4" aria-hidden="true" />
|
||||
<span>전체 제출 내역</span>
|
||||
</Link>
|
||||
</nav>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 rounded border border-slate-700 px-3 py-1 text-slate-200 hover:bg-slate-900"
|
||||
onClick={() => {
|
||||
void handleLogout();
|
||||
}}
|
||||
className="inline-flex items-center gap-1 rounded-full border border-slate-300 bg-white/80 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
|
||||
onClick={handleToggleTheme}
|
||||
>
|
||||
로그아웃
|
||||
<SunMoon className="w-4 h-4" aria-hidden="true" />
|
||||
<span>테마 전환</span>
|
||||
</button>
|
||||
<Link
|
||||
href="/projects/submissions"
|
||||
className="inline-flex items-center gap-1 rounded border border-emerald-700 bg-emerald-950 px-3 py-1 text-emerald-100 hover:bg-emerald-900 shadow-sm"
|
||||
>
|
||||
<ListChecks className="w-4 h-4" aria-hidden="true" />
|
||||
<span>전체 제출 내역</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/editor?new=1"
|
||||
className="inline-flex items-center gap-1 rounded border border-sky-700 bg-sky-950 px-3 py-1 text-sky-100 hover:bg-sky-900 shadow-sm"
|
||||
>
|
||||
<FilePlus2 className="w-4 h-4" aria-hidden="true" />
|
||||
<span>새 프로젝트 만들기</span>
|
||||
</Link>
|
||||
<div className="relative">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 rounded-full border border-slate-300 bg-white/80 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
|
||||
onClick={() => {
|
||||
setIsMenuOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
메뉴
|
||||
</button>
|
||||
{isMenuOpen && (
|
||||
<div className="absolute right-0 mt-1 w-32 rounded border border-slate-200 bg-white shadow-lg z-20 dark:border-slate-700 dark:bg-slate-900/95">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full px-3 py-2 text-left text-xs text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
|
||||
onClick={() => {
|
||||
setIsMenuOpen(false);
|
||||
void handleLogout();
|
||||
}}
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section className="flex-1 px-6 py-4 overflow-auto">
|
||||
<section className="flex-1 px-6 py-4 overflow-auto bg-slate-50/60 dark:bg-transparent">
|
||||
{status === "error" && (
|
||||
<p className="text-xs text-red-300 mb-3">
|
||||
<p className="text-xs text-red-500 mb-3 dark:text-red-300">
|
||||
{errorMessage ?? "프로젝트 목록을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."}
|
||||
</p>
|
||||
)}
|
||||
{projects.length === 0 && status === "idle" && (
|
||||
<p className="text-xs text-slate-400">아직 저장된 프로젝트가 없습니다. 에디터에서 프로젝트를 저장해 보세요.</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">아직 저장된 프로젝트가 없습니다. 에디터에서 프로젝트를 저장해 보세요.</p>
|
||||
)}
|
||||
{projects.length > 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-2 text-[11px] text-slate-300">
|
||||
<div
|
||||
data-testid="projects-toolbar"
|
||||
className="flex items-center justify-between mb-2 text-[11px] text-slate-300"
|
||||
>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-slate-900/70 border border-slate-700">
|
||||
<ListChecks className="w-3 h-3 text-slate-400" aria-hidden="true" />
|
||||
@@ -248,17 +302,26 @@ export default function ProjectsPage() {
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 rounded border border-red-700 px-2 py-1 text-red-200 hover:bg-red-900/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
disabled={selectedSlugs.length === 0}
|
||||
onClick={() => {
|
||||
void handleBulkDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" aria-hidden="true" />
|
||||
<span>선택 삭제</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center gap-1 rounded border border-red-700 px-2 py-1 text-red-200 hover:bg-red-900/40 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
disabled={selectedSlugs.length === 0}
|
||||
onClick={() => {
|
||||
void handleBulkDelete();
|
||||
}}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" aria-hidden="true" />
|
||||
<span>선택 삭제</span>
|
||||
</button>
|
||||
<Link
|
||||
href="/editor?new=1"
|
||||
className="inline-flex items-center gap-1 rounded border border-sky-700 bg-sky-950 px-2 py-1 text-sky-100 hover:bg-sky-900 shadow-sm"
|
||||
>
|
||||
<FilePlus2 className="w-3 h-3" aria-hidden="true" />
|
||||
<span>새 프로젝트 만들기</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<table className="w-full text-xs text-left border-collapse mt-2">
|
||||
<thead>
|
||||
|
||||
Reference in New Issue
Block a user